schollz/croc · error

Gateway URL must use ws:// or wss://

Error message

Gateway URL must use ws:// or wss://

What it means

Thrown by gatewayForPort() when building the WebSocket URL: the gateway base resolved to neither ws: nor wss:. The helper resolves the configured gateway (default '/ws') against window.location.href and upgrades http:->ws: and https:->wss:; any other resulting scheme (file:, ftp:, about:) is rejected before a connection is attempted.

Source

Thrown at web/src/protocol/transport.ts:13

import { FrameDecoder, encodeFrame } from "./framing";

type Reader = {
  resolve(value: Uint8Array): void;
  reject(reason: Error): void;
};

function gatewayForPort(gateway: string, port: string) {
  const base = new URL(gateway || "/ws", window.location.href);
  if (base.protocol === "http:") base.protocol = "ws:";
  if (base.protocol === "https:") base.protocol = "wss:";
  if (base.protocol !== "ws:" && base.protocol !== "wss:") {
    throw new Error("Gateway URL must use ws:// or wss://");
  }
  base.searchParams.set("port", port);
  return base.toString();
}

export class CrocSocket {
  private socket: WebSocket;
  private decoder = new FrameDecoder();
  private messages: Uint8Array[] = [];
  private readers: Reader[] = [];
  private failure?: Error;

  private constructor(socket: WebSocket, signal?: AbortSignal) {
    this.socket = socket;
    socket.binaryType = "arraybuffer";
    socket.addEventListener("message", (event) => {
      try {
        const chunk =

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Serve the app over http:// or https:// so the '/ws' default upgrades to ws/wss correctly
  2. Set the gateway to an absolute URL: 'wss://relay.example.com/ws'
  3. In tests, use a DOM environment with a real http(s) location (jsdom defaults to http://localhost)

Example fix

# before: opening dist/index.html via file:// then connecting
new CrocSocket(gateway, port); // throws

# after: serve and use an absolute gateway
npx serve web/dist   # open http://localhost:3000
new CrocSocket('wss://relay.example.com/ws', port);
Defensive patterns

Strategy: validation

Validate before calling

function assertGatewayUsable(gateway: string) {
  const u = new URL(gateway || '/ws', window.location.href);
  if (!['http:', 'https:', 'ws:', 'wss:'].includes(u.protocol)) {
    throw new Error(`unsupported page/gateway scheme: ${u.protocol} - serve the app over http(s)`);
  }
}

Type guard

function isHttpPageContext(): boolean {
  return window.location.protocol === 'http:' || window.location.protocol === 'https:';
}

Try / catch

try { socket = new CrocSocket(gateway, port); } catch (e) { if (e instanceof Error && e.message === 'Gateway URL must use ws:// or wss://') { showFatal('open the app via http(s), not file://'); return; } throw e; }

Prevention

When it happens

Trigger: Constructing a CrocSocket / starting a relay transfer while the page origin is file:// (so '/ws' resolves to file:), or when the gateway option is an absolute URL with a non-HTTP(S) scheme. about:blank or opaque-origin sandboxed iframes also resolve badly.

Common situations: Opening the built web bundle directly from disk instead of over an HTTP server; sandboxed iframes/opaque origins; a mistyped gateway string like 'wss:/host' (single slash).

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/167444c61759f555. Report an issue: GitHub.