H
Hono•3d ago
playsonmac

Websocket subprotocol results in Error: The script will never generate a response

Websockets - sending subprotocols (auth token smuggling) results in Error: The script will never generate a response. Client:
const ws = new WebSocket(wsUrl, "potato");
const ws = new WebSocket(wsUrl, "potato");
Hono:
app.get(
"/ws",
upgradeWebSocket((c) => {
const rawProtocols = c.req.header("sec-websocket-protocol");
console.log(rawProtocols);

return {
onMessage: (event) => {
console.log(event.data);
},
};
})
);
app.get(
"/ws",
upgradeWebSocket((c) => {
const rawProtocols = c.req.header("sec-websocket-protocol");
console.log(rawProtocols);

return {
onMessage: (event) => {
console.log(event.data);
},
};
})
);
- Fails right away at the upgrade with Error: The script will never generate a response. on the server. - This only happens if I send a subprotocol such as "potato" which automatically gets converted to the sec-websocket-protocol header as it should. Curiously I can see the header in the console log. - Also tried with hc, same issue.
13 Replies
ambergristle
ambergristle•2d ago
hey! i came across this, which might help: https://github.com/cloudflare/cloudflare-docs/pull/15014 try manually closing the ws server-side in a close event handler
playsonmac
playsonmacOP•2d ago
Thanks! Do you mean adding a onClose method tot he return of the upgradeWebSocket handler? I have it (I just trimmed down the minimal example so it doesn't take up space) and I still have the issue. Or do you mean something else?
playsonmac
playsonmacOP•2d ago
Yep, I have it. Everything works until I send a protocol (which I'm trying to use to authenticate the user since websockets don't support typical headers)
app.get(
"/ws",
upgradeWebSocket((c) => {
// console.log("c.req", c.req);
const rawProtocols = c.req.header("sec-websocket-protocol");
console.log(rawProtocols);

return {
onMessage: (evt, ws) => {
console.log("evt in onMessage", evt);
console.log("ws in onMessage", ws);
},

onError(evt, ws) { // < ----------------------- here it is

console.log("evt in onError", evt);
console.log("ws in onError", ws);
},
onClose(evt, ws) {
console.log("evt in onClose", evt);
console.log("ws in onClose", ws);
},
};
})
);
app.get(
"/ws",
upgradeWebSocket((c) => {
// console.log("c.req", c.req);
const rawProtocols = c.req.header("sec-websocket-protocol");
console.log(rawProtocols);

return {
onMessage: (evt, ws) => {
console.log("evt in onMessage", evt);
console.log("ws in onMessage", ws);
},

onError(evt, ws) { // < ----------------------- here it is

console.log("evt in onError", evt);
console.log("ws in onError", ws);
},
onClose(evt, ws) {
console.log("evt in onClose", evt);
console.log("ws in onClose", ws);
},
};
})
);
ambergristle
ambergristle•2d ago
where is it? i don't see it also, can you syntax highlight your snippets?
playsonmac
playsonmacOP•2d ago
I've highlighted it above. It's the onClose from the docs - https://hono.dev/docs/helpers/websocket
ambergristle
ambergristle•2d ago
from the docs i linked above:
server.addEventListener("close", () => {
// This missing line would keep a WebSocket connection open indefinitely
// and results in "The script will never generate a response" errors
// server.close();
});
server.addEventListener("close", () => {
// This missing line would keep a WebSocket connection open indefinitely
// and results in "The script will never generate a response" errors
// server.close();
});
where do you server.close()?
playsonmac
playsonmacOP•2d ago
Oh, do you mean on the frontend? Here's my frontend implementation:
const handleWS = async () => {
const wsUrl = `${process.env.NEXT_PUBLIC_API_URL}/ws`;

const ws = new WebSocket(wsUrl, ["Authorization", "sometoken"]);

// Just added this but doesn't really help..
ws.addEventListener("close", () => {
ws.close();
});

ws.onopen = () => {
setWsStatus("Open");
const initMessage = JSON.stringify({
type: "init",
payload: { userId: "user-123", message: "Test message from client" },
});
ws.send(initMessage);
};

ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
switch (data.type) {
case "ack":
console.log("Ack received:", data.payload.responseId);
break;
case "data":
setStreamData((prev) => prev + data.payload.chunk);
break;
case "complete":
setWsStatus("Completed");
break;
case "error":
console.error("Error from server:", data.payload.message);
setWsStatus(`Error: ${data.payload.message}`);
break;
default:
console.warn("Unknown message type:", data.type);
}
} catch (err) {
console.error("Failed to parse message:", err);
}
};

ws.onclose = (event) => {
console.log(`WebSocket closed: ${event.code} - ${event.reason}`);
setWsStatus(`Closed: ${event.code} - ${event.reason}`);
};

ws.onerror = (event) => {
console.log("WebSocket encountered error:", JSON.stringify(event));
setWsStatus("Error encountered");
};
};
const handleWS = async () => {
const wsUrl = `${process.env.NEXT_PUBLIC_API_URL}/ws`;

const ws = new WebSocket(wsUrl, ["Authorization", "sometoken"]);

// Just added this but doesn't really help..
ws.addEventListener("close", () => {
ws.close();
});

ws.onopen = () => {
setWsStatus("Open");
const initMessage = JSON.stringify({
type: "init",
payload: { userId: "user-123", message: "Test message from client" },
});
ws.send(initMessage);
};

ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
switch (data.type) {
case "ack":
console.log("Ack received:", data.payload.responseId);
break;
case "data":
setStreamData((prev) => prev + data.payload.chunk);
break;
case "complete":
setWsStatus("Completed");
break;
case "error":
console.error("Error from server:", data.payload.message);
setWsStatus(`Error: ${data.payload.message}`);
break;
default:
console.warn("Unknown message type:", data.type);
}
} catch (err) {
console.error("Failed to parse message:", err);
}
};

ws.onclose = (event) => {
console.log(`WebSocket closed: ${event.code} - ${event.reason}`);
setWsStatus(`Closed: ${event.code} - ${event.reason}`);
};

ws.onerror = (event) => {
console.log("WebSocket encountered error:", JSON.stringify(event));
setWsStatus("Error encountered");
};
};
ambergristle
ambergristle•2d ago
no, i mean the backend please read the docs i sent you i haven't worked w webhooks a ton, so i can't explain it any more clearly than cloudflare does i just searched the error message you were seeing, and it took me to a section of the cloudflare docs that addresses that specific error message
playsonmac
playsonmacOP•2d ago
I appreciate your help 🙌
The issue is I'm only using workers through hono and I don't really know how to surface any of the interals. I'm literally using the hono upgradeWebSocket hanlder on one of the routes of the hono app. I might fall back to just sending the token as a query param
ambergristle
ambergristle•2d ago
i would expect this to work (server-side, in your upgrade options)
{
onClose: (event, ws) => {
ws.close();
}
}
{
onClose: (event, ws) => {
ws.close();
}
}
i assume that upgradeWebSocket is a fairly minimal wrapper yeah, pretty much: https://github.com/honojs/hono/blob/65edaf277a2dd3bb47406379904996ffdd3563e5/src/adapter/cloudflare-workers/websocket.ts#L37
playsonmac
playsonmacOP•2d ago
Woah, thanks! I'll look into it!
ambergristle
ambergristle•2d ago
the hono codebase is insanely accessible

Did you find this page helpful?