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
- Serve the app over http:// or https:// so the '/ws' default upgrades to ws/wss correctly
- Set the gateway to an absolute URL: 'wss://relay.example.com/ws'
- 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
- Never load the bundle via file://; serve it with any static server
- Configure gateway as an absolute https/wss URL in production
- Give tests a DOM with a real http(s) location
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
- Relay connection closed while sending
- Relay returned an invalid port list: ${banner}
- Relay rejected the connection: ${response}
- Relay could not open the room: ${confirmation}
- Message is too large (${payload.byteLength} bytes)
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/167444c61759f555.
Report an issue: GitHub.