{"record":{"id":"167444c61759f555","repo":"schollz/croc","slug":"gateway-url-must-use-ws-or-wss","errorCode":null,"errorMessage":"Gateway URL must use ws:// or wss://","messagePattern":"Gateway URL must use ws:// or wss://","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/src/protocol/transport.ts","lineNumber":13,"sourceCode":"import { FrameDecoder, encodeFrame } from \"./framing\";\n\ntype Reader = {\n  resolve(value: Uint8Array): void;\n  reject(reason: Error): void;\n};\n\nfunction gatewayForPort(gateway: string, port: string) {\n  const base = new URL(gateway || \"/ws\", window.location.href);\n  if (base.protocol === \"http:\") base.protocol = \"ws:\";\n  if (base.protocol === \"https:\") base.protocol = \"wss:\";\n  if (base.protocol !== \"ws:\" && base.protocol !== \"wss:\") {\n    throw new Error(\"Gateway URL must use ws:// or wss://\");\n  }\n  base.searchParams.set(\"port\", port);\n  return base.toString();\n}\n\nexport class CrocSocket {\n  private socket: WebSocket;\n  private decoder = new FrameDecoder();\n  private messages: Uint8Array[] = [];\n  private readers: Reader[] = [];\n  private failure?: Error;\n\n  private constructor(socket: WebSocket, signal?: AbortSignal) {\n    this.socket = socket;\n    socket.binaryType = \"arraybuffer\";\n    socket.addEventListener(\"message\", (event) => {\n      try {\n        const chunk =","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/schollz/croc/blob/e25f1bdc04f07f094d50b0a1bf67e2563944b57a/web/src/protocol/transport.ts#L1-L31","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)"],"exampleFix":"# before: opening dist/index.html via file:// then connecting\nnew CrocSocket(gateway, port); // throws\n\n# after: serve and use an absolute gateway\nnpx serve web/dist   # open http://localhost:3000\nnew CrocSocket('wss://relay.example.com/ws', port);","handlingStrategy":"validation","validationCode":"function assertGatewayUsable(gateway: string) {\n  const u = new URL(gateway || '/ws', window.location.href);\n  if (!['http:', 'https:', 'ws:', 'wss:'].includes(u.protocol)) {\n    throw new Error(`unsupported page/gateway scheme: ${u.protocol} - serve the app over http(s)`);\n  }\n}","typeGuard":"function isHttpPageContext(): boolean {\n  return window.location.protocol === 'http:' || window.location.protocol === 'https:';\n}","tryCatchPattern":"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; }","preventionTips":["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"],"tags":["websocket","url-validation","environment","relay"],"backgroundTag":null,"analyzedSha":"e25f1bdc04f07f094d50b0a1bf67e2563944b57a","analyzedAt":"2026-08-15T12:53:39.096Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}