Open
Description
I'm establishing a websocket connection with a durable object. In the client side (worker), if I explicitly close the websocket, no close
event is triggered.
If I obtain a websocket with new WebSocket(...)
, for instance to connect with an external websocket, it works as expected: explicitly closing the websocket triggers the close
event.
To reproduce the issue:
- create a worker project
- edit
src/index.ts
:
import { DurableObject } from "cloudflare:workers";
export class TestDurableObject extends DurableObject<Env> {
constructor(state: DurableObjectState, env: Env) {
super(state, env);
}
async fetch(request: Request) {
if (request.headers.get("Upgrade") !== "websocket")
return new Response("Expected WebSocket request", { status: 400 });
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
server.accept();
return new Response(null, {
status: 101,
webSocket: client,
});
}
}
export default {
async fetch(request, env): Promise<Response> {
const url = new URL(request.url);
let ws: WebSocket;
if (url.searchParams.has('ws')) {
// example: http://127.0.0.1:8787/?ws=wss://echo.websocket.org/
ws = new WebSocket(url.searchParams.get('ws')!);
} else {
const id = env.TEST_DO.idFromName('test-instance');
const obj = env.TEST_DO.get(id);
const response = await obj.fetch(request, {
headers: {
"Upgrade": "websocket"
}
});
ws = response.webSocket!;
ws.accept();
}
const logs: string[] = [];
const { promise, resolve } = Promise.withResolvers<void>();
ws.addEventListener('open', () => {
logs.push('Connection opened');
ws.close(1000);
});
ws.addEventListener('close', ({ code, reason }) => {
logs.push(`Connection closed: ${JSON.stringify({ code, reason })}`);
resolve();
});
await promise;
return new Response(logs.join('\n'), { status: 200 });
},
} satisfies ExportedHandler<Env>;
- edit
wrangler.jsonc
:
{
"name": "websocket-close-test",
"main": "src/index.ts",
"compatibility_date": "2025-05-25",
"compatibility_flags": [
"nodejs_compat",
],
"observability": {
"enabled": true
},
"durable_objects": {
"bindings": [
{
"name": "TEST_DO",
"class_name": "TestDurableObject"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["TestDurableObject"]
}
],
}
- Request http://127.0.0.1:8787
- I will block and eventually timeout
- To check that it works properly with
new WebSocket
, request http://127.0.0.1:8787/?ws=wss://echo.websocket.org/- it will successfully output:
Connection opened
Connection closed: {"code":1000,"reason":""}
Metadata
Metadata
Assignees
Labels
No labels