farion1231/cc-switch · error · Error

Invalid URL

Error message

Invalid URL

What it means

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'.

Solutions

  1. Pass fully-qualified http:// or https:// URLs to openExternal
  2. Normalize stored URLs to include a scheme when saving settings
  3. If distinct messaging is needed, move the scheme check outside the try so 'Unsupported URL scheme' survives (see exampleFix)

Example fix

// before
try {
  const u = new URL(url);
  const scheme = u.protocol.replace(':', '').toLowerCase();
  if (scheme !== 'http' && scheme !== 'https') {
    throw new Error('Unsupported URL scheme');
  }
} catch {
  throw new Error('Invalid URL'); // also swallows the scheme message
}

// after
let u: URL;
try {
  u = new URL(url);
} catch {
  throw new Error('Invalid URL');
}
const scheme = u.protocol.replace(':', '').toLowerCase();
if (scheme !== 'http' && scheme !== 'https') {
  throw new Error('Unsupported URL scheme');
}
Defensive patterns

Strategy: validation

Validate before calling

function isOpenableExternalUrl(url: string): boolean {
  try {
    const u = new URL(url);
    const scheme = u.protocol.replace(':', '').toLowerCase();
    return scheme === 'http' || scheme === 'https';
  } catch {
    return false;
  }
}
if (!isOpenableExternalUrl(link)) {
  // show a friendly message instead of calling openExternal
}

Try / catch

try {
  await settingsApi.openExternal(url);
} catch (error) {
  if (error instanceof Error && error.message === 'Invalid URL') {
    // hint: links must be absolute http(s)
  } else {
    throw error;
  }
}

Prevention

When it happens

Trigger: Calling openExternal('ftp://example.com/f'), openExternal('file:///etc/hosts'), openExternal('example.com') (no scheme, so the URL constructor throws), or any unparseable string.

Common situations: 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.

Related errors


AI-assisted analysis of farion1231/cc-switch@0b5da51016 (2026-08-20). Data as JSON: /api/errors/f041c76e9baa8887. Report an issue: GitHub.

Appendix: source

Thrown at src/lib/api/settings.ts:223

  async syncCurrentProvidersLive(): Promise<void> {
    const result = (await invoke("sync_current_providers_live")) as {
      success?: boolean;
      message?: string;
    };
    if (!result?.success) {
      throw new Error(result?.message || "Sync current providers failed");
    }
  },

  async openExternal(url: string): Promise<void> {
    try {
      const u = new URL(url);
      const scheme = u.protocol.replace(":", "").toLowerCase();
      if (scheme !== "http" && scheme !== "https") {
        throw new Error("Unsupported URL scheme");
      }
    } catch {
      throw new Error("Invalid URL");
    }
    await invoke("open_external", { url });
  },

  async setAutoLaunch(enabled: boolean): Promise<boolean> {
    return await invoke("set_auto_launch", { enabled });
  },

  async getAutoLaunchStatus(): Promise<boolean> {
    return await invoke("get_auto_launch_status");
  },

  async getToolVersions(
    tools?: string[],
    wslShellByTool?: Record<
      string,
      { wslShell?: string | null; wslShellFlag?: string | null }
    >,

View on GitHub (pinned to 0b5da51016)