farion1231/cc-switch · warning · Error

Unsupported URL scheme

Error message

Unsupported URL scheme

What it means

Intended to be thrown by settings.openExternal(url) when the URL parses but its scheme is not http/https (case-insensitive), guarding the Tauri open_external shell call against file:, javascript:, and custom protocols. Quirk: the throw happens inside the same try block whose catch rewrites every failure to 'Invalid URL', so callers currently observe 'Invalid URL' for non-http schemes - this exact message is unreachable as written.

Source

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

    return await invoke("s3_sync_fetch_remote_info");
  },

  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<

View on GitHub (pinned to a2e22f3302)

Solutions

  1. Restrict UI anchors to absolute http/https URLs before calling openExternal
  2. If distinct messages matter, hoist the scheme check out of the try block so 'Unsupported URL scheme' survives the catch
  3. Render custom-protocol links as plain text, or route them through an explicit user-consent dialog

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"); // swallowed by catch below
  }
} catch {
  throw new Error("Invalid URL");
}

// 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 isSafeExternalUrl(url: string): boolean {
  try {
    const { protocol } = new URL(url);
    return protocol === "http:" || protocol === "https:";
  } catch {
    return false;
  }
}

if (isSafeExternalUrl(href)) {
  await settingsApi.openExternal(href);
}

Type guard

type HttpUrl = `http://${string}` | `https://${string}`;

function isHttpUrl(v: string): v is HttpUrl {
  return /^https?:\/\/\S+$/.test(v);
}

Try / catch

// The scheme throw is rewritten to 'Invalid URL' by the shared catch,
// so match that message for both malformed and non-http(s) cases.
try {
  await settingsApi.openExternal(url);
} catch (e) {
  if (e instanceof Error && e.message === "Invalid URL") {
    toast("Only http:// and https:// links can be opened");
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: openExternal('file:///etc/hosts'), openExternal('javascript:alert(1)'), openExternal('slack://channel'), openExternal('mailto:a@b.c') - any parseable URL whose protocol is neither http nor https (observed by the caller as 'Invalid URL').

Common situations: Clicking deep links (zoom://, vscode://, msteams://); a user-configurable link field holding a custom protocol; security hardening that forbids file:/javascript: URLs from reaching the OS shell.

Related errors


AI-assisted analysis of farion1231/cc-switch@a2e22f3302 (2026-08-16). Data as JSON: /api/errors/2c7da822852c65e6. Report an issue: GitHub.