{"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":"error","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/0b5da510168914b251481654a568c3ffacd62cf4/src/lib/api/settings.ts#L205-L241","documentation":"settingsApi.openExternal validates the URL before handing it to the Tauri open_external command: it must parse with new URL and its scheme must be http or https. Because both the parse failure and the scheme mismatch land in the same catch, the specific 'Unsupported URL scheme' message is swallowed and every rejection surfaces as the generic 'Invalid URL'.","triggerScenarios":"Calling openExternal('ftp://example.com/f'), openExternal('file:///etc/hosts'), openExternal('example.com') (no scheme, so the URL constructor throws), or any unparseable string.","commonSituations":"Clicking a help/update link stored without a scheme; user-supplied docs or webhook URLs; trying to open mailto: or custom deep links that this guard forbids.","solutions":["Pass fully-qualified http:// or https:// URLs to openExternal","Normalize stored URLs to include a scheme when saving settings","If distinct messaging is needed, move the scheme check outside the try so 'Unsupported URL scheme' survives (see exampleFix)"],"exampleFix":"// before\ntry {\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'); // also swallows the scheme message\n}\n\n// after\nlet u: URL;\ntry {\n  u = new URL(url);\n} catch {\n  throw new Error('Invalid URL');\n}\nconst scheme = u.protocol.replace(':', '').toLowerCase();\nif (scheme !== 'http' && scheme !== 'https') {\n  throw new Error('Unsupported URL scheme');\n}","handlingStrategy":"validation","validationCode":"function isOpenableExternalUrl(url: string): boolean {\n  try {\n    const u = new URL(url);\n    const scheme = u.protocol.replace(':', '').toLowerCase();\n    return scheme === 'http' || scheme === 'https';\n  } catch {\n    return false;\n  }\n}\nif (!isOpenableExternalUrl(link)) {\n  // show a friendly message instead of calling openExternal\n}","typeGuard":null,"tryCatchPattern":"try {\n  await settingsApi.openExternal(url);\n} catch (error) {\n  if (error instanceof Error && error.message === 'Invalid URL') {\n    // hint: links must be absolute http(s)\n  } else {\n    throw error;\n  }\n}","preventionTips":["Store URLs with an explicit scheme at save time","Never feed user-typed strings straight to openExternal","If you need scheme-specific messages, keep the scheme check outside the parse try-block"],"tags":["url","validation","tauri","external-links"],"backgroundTag":"unsupported-url-scheme","analyzedSha":"0b5da510168914b251481654a568c3ffacd62cf4","analyzedAt":"2026-08-20T14:29:00.113Z","contentChangedAt":"2026-08-20T14:29:00.113Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}