{"record":{"id":"f041c76e9baa8887","repo":"farion1231/cc-switch","slug":"invalid-url","errorCode":null,"errorMessage":"Invalid URL","messagePattern":"Invalid URL","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/lib/api/settings.ts","lineNumber":223,"sourceCode":"  async syncCurrentProvidersLive(): Promise<void> {\n    const result = (await invoke(\"sync_current_providers_live\")) as {\n      success?: boolean;\n      message?: string;\n    };\n    if (!result?.success) {\n      throw new Error(result?.message || \"Sync current providers failed\");\n    }\n  },\n\n  async openExternal(url: string): Promise<void> {\n    try {\n      const u = new URL(url);\n      const scheme = u.protocol.replace(\":\", \"\").toLowerCase();\n      if (scheme !== \"http\" && scheme !== \"https\") {\n        throw new Error(\"Unsupported URL scheme\");\n      }\n    } catch {\n      throw new Error(\"Invalid URL\");\n    }\n    await invoke(\"open_external\", { url });\n  },\n\n  async setAutoLaunch(enabled: boolean): Promise<boolean> {\n    return await invoke(\"set_auto_launch\", { enabled });\n  },\n\n  async getAutoLaunchStatus(): Promise<boolean> {\n    return await invoke(\"get_auto_launch_status\");\n  },\n\n  async getToolVersions(\n    tools?: string[],\n    wslShellByTool?: Record<\n      string,\n      { wslShell?: string | null; wslShellFlag?: string | null }\n    >,","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/farion1231/cc-switch/blob/a2e22f330273a5b6ffa87cb8b82b624601bac562/src/lib/api/settings.ts#L205-L241","documentation":"Thrown by settings.openExternal(url) when new URL(url) fails (the value is not a parseable absolute URL) - and, because the scheme-check throw shares the same catch, it is also the message surfaced for any non-http/https scheme. It is the single error callers see for malformed or disallowed input to the external-open API.","triggerScenarios":"openExternal('example.com/path') (no scheme, new URL throws), openExternal(''), openExternal('http://'), plus ftp://x and other non-http schemes via the rewritten catch.","commonSituations":"Config fields storing bare domains without a scheme; template strings producing empty hrefs; the 'Unsupported URL scheme' case masquerading as this message.","solutions":["Normalize input to a full URL first: url.startsWith('http') ? url : `https://${url}`","Validate with new URL() plus a protocol check in the UI layer before invoking","Treat an empty string as a no-op click instead of calling the API"],"exampleFix":"// before\nawait settingsApi.openExternal(href); // 'example.com/docs' -> Invalid URL\n\n// after\nconst normalized = /^https?:\\/\\//.test(href) ? href : `https://${href}`;\nawait settingsApi.openExternal(normalized);","handlingStrategy":"validation","validationCode":"function normalizeExternalUrl(href: string): string | null {\n  if (!href.trim()) return null;\n  const candidate = /^https?:\\/\\//i.test(href) ? href : `https://${href}`;\n  try {\n    const { protocol } = new URL(candidate);\n    return protocol === \"http:\" || protocol === \"https:\" ? candidate : null;\n  } catch {\n    return null;\n  }\n}\n\nconst url = normalizeExternalUrl(href);\nif (url) await settingsApi.openExternal(url);","typeGuard":null,"tryCatchPattern":"try {\n  await settingsApi.openExternal(url);\n} catch (e) {\n  if (e instanceof Error && e.message === \"Invalid URL\") {\n    log.warn(\"Rejected external URL\", { url });\n    toast(\"This link is not a valid http(s) URL\");\n  } else {\n    throw e;\n  }\n}","preventionTips":["Always store absolute URLs including the scheme","Default bare domains to https:// at entry time","Never bind open actions to empty href values"],"tags":["url","validation","tauri"],"backgroundTag":null,"analyzedSha":"a2e22f330273a5b6ffa87cb8b82b624601bac562","analyzedAt":"2026-08-16T03:46:07.889Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}