@firtoz/websocket-do 13.0.0 → 13.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/BaseWebSocketDO.d.ts +9 -1
- package/dist/BaseWebSocketDO.js +1 -1
- package/dist/StandardSchemaWebSocketDO.d.ts +5 -0
- package/dist/StandardSchemaWebSocketDO.js +2 -2
- package/dist/{chunk-XFB6C3NZ.js → chunk-E4576ZE6.js} +10 -12
- package/dist/chunk-E4576ZE6.js.map +1 -0
- package/dist/{chunk-ULGH6X42.js → chunk-J275SVBA.js} +5 -4
- package/dist/chunk-J275SVBA.js.map +1 -0
- package/dist/index.js +2 -2
- package/package.json +7 -7
- package/src/BaseWebSocketDO.ts +22 -24
- package/src/StandardSchemaWebSocketDO.ts +7 -0
- package/dist/chunk-ULGH6X42.js.map +0 -1
- package/dist/chunk-XFB6C3NZ.js.map +0 -1
|
@@ -9,6 +9,14 @@ type BaseWebSocketDOOptions<TSession extends BaseSession<any, any, any, any>, TE
|
|
|
9
9
|
createSession: (ctx: Context<{
|
|
10
10
|
Bindings: TEnv;
|
|
11
11
|
}> | undefined, websocket: WebSocket) => TSession | Promise<TSession>;
|
|
12
|
+
/**
|
|
13
|
+
* If set, called on the WebSocketPair **server** socket before
|
|
14
|
+
* {@link DurableObjectState#acceptWebSocket}. Use `{ allowHalfOpen: true }` when you need
|
|
15
|
+
* to coordinate close independently (e.g. proxying). Omit for the usual hibernation-only path
|
|
16
|
+
* (no `WebSocket#accept` before `acceptWebSocket`), which matches
|
|
17
|
+
* [Durable Object WebSocket examples](https://developers.cloudflare.com/durable-objects/best-practices/websockets/).
|
|
18
|
+
*/
|
|
19
|
+
pairServerWebSocketAcceptOptions?: WebSocketAcceptOptions;
|
|
12
20
|
};
|
|
13
21
|
declare abstract class BaseWebSocketDO<TSession extends BaseSession<any, any, any, any> = BaseSession<any, any, any, any>, TEnv extends SessionEnv<TSession> = SessionEnv<TSession>> extends DurableObject<TEnv> implements DOWithHonoApp {
|
|
14
22
|
#private;
|
|
@@ -34,7 +42,7 @@ declare abstract class BaseWebSocketDO<TSession extends BaseSession<any, any, an
|
|
|
34
42
|
Bindings: TEnv;
|
|
35
43
|
}>, ws: WebSocket): Promise<void>;
|
|
36
44
|
webSocketMessage(ws: WebSocket, message: string | ArrayBuffer): Promise<void>;
|
|
37
|
-
webSocketClose(ws: WebSocket,
|
|
45
|
+
webSocketClose(ws: WebSocket, code: number, reason: string, _wasClean: boolean): Promise<void>;
|
|
38
46
|
webSocketError(ws: WebSocket, error: unknown): Promise<void>;
|
|
39
47
|
fetch(request: Request): Response | Promise<Response>;
|
|
40
48
|
}
|
package/dist/BaseWebSocketDO.js
CHANGED
|
@@ -16,6 +16,11 @@ type StandardSchemaWebSocketDOOptions<TSession extends StandardSchemaSession<any
|
|
|
16
16
|
createStandardSchemaSession: (ctx: Context<{
|
|
17
17
|
Bindings: TEnv;
|
|
18
18
|
}> | undefined, websocket: WebSocket, options: StandardSchemaSessionOptions<TClientMessage, TServerMessage>) => TSession | Promise<TSession>;
|
|
19
|
+
/**
|
|
20
|
+
* Optional `WebSocket#accept` options for the server side of the `WebSocketPair` (see
|
|
21
|
+
* `pairServerWebSocketAcceptOptions` on `BaseWebSocketDO` constructor options).
|
|
22
|
+
*/
|
|
23
|
+
pairServerWebSocketAcceptOptions?: WebSocketAcceptOptions;
|
|
19
24
|
};
|
|
20
25
|
declare abstract class StandardSchemaWebSocketDO<TSession extends StandardSchemaSession<any, any, any, any>, TClientMessage extends SessionClientMessage<TSession> = SessionClientMessage<TSession>, TServerMessage extends SessionServerMessage<TSession> = SessionServerMessage<TSession>, TEnv extends SessionEnv<TSession> = SessionEnv<TSession>> extends BaseWebSocketDO<TSession, TEnv> {
|
|
21
26
|
protected readonly standardSchemaSessionOptions: StandardSchemaSessionOptionsOrFactory<TClientMessage, TServerMessage, TEnv>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { StandardSchemaWebSocketDO } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { StandardSchemaWebSocketDO } from './chunk-J275SVBA.js';
|
|
2
|
+
import './chunk-E4576ZE6.js';
|
|
3
3
|
import './chunk-NOUFNU2O.js';
|
|
4
4
|
//# sourceMappingURL=StandardSchemaWebSocketDO.js.map
|
|
5
5
|
//# sourceMappingURL=StandardSchemaWebSocketDO.js.map
|
|
@@ -53,6 +53,10 @@ var BaseWebSocketDO = class extends DurableObject {
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
async handleSession(ctx, ws) {
|
|
56
|
+
const acceptOpts = this.options.pairServerWebSocketAcceptOptions;
|
|
57
|
+
if (acceptOpts !== void 0) {
|
|
58
|
+
ws.accept(acceptOpts);
|
|
59
|
+
}
|
|
56
60
|
this.ctx.acceptWebSocket(ws);
|
|
57
61
|
try {
|
|
58
62
|
const session = await this.options.createSession(ctx, ws);
|
|
@@ -82,7 +86,7 @@ var BaseWebSocketDO = class extends DurableObject {
|
|
|
82
86
|
console.error(`Error during session message: ${error}`);
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
async webSocketClose(ws,
|
|
89
|
+
async webSocketClose(ws, code, reason, _wasClean) {
|
|
86
90
|
const session = this.sessions.get(ws);
|
|
87
91
|
if (!session) return;
|
|
88
92
|
try {
|
|
@@ -90,17 +94,13 @@ var BaseWebSocketDO = class extends DurableObject {
|
|
|
90
94
|
} catch (error) {
|
|
91
95
|
console.error(`Error during session close: ${error}`);
|
|
92
96
|
} finally {
|
|
93
|
-
|
|
94
|
-
ws.close(1e3, "Normal closure");
|
|
95
|
-
}
|
|
97
|
+
ws.close(code, reason);
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
async webSocketError(ws, error) {
|
|
99
101
|
const session = this.sessions.get(ws);
|
|
100
102
|
if (!session) {
|
|
101
|
-
|
|
102
|
-
ws.close(1011, "Error during session setup.");
|
|
103
|
-
}
|
|
103
|
+
ws.close(1011, "Error during session setup.");
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
106
|
console.error(`Error for session: ${error}`);
|
|
@@ -109,9 +109,7 @@ var BaseWebSocketDO = class extends DurableObject {
|
|
|
109
109
|
} catch (error2) {
|
|
110
110
|
console.error(`Error during session close: ${error2}`);
|
|
111
111
|
} finally {
|
|
112
|
-
|
|
113
|
-
ws.close(1011, "Error during session.");
|
|
114
|
-
}
|
|
112
|
+
ws.close(1011, "Error during session.");
|
|
115
113
|
}
|
|
116
114
|
}
|
|
117
115
|
fetch(request) {
|
|
@@ -130,5 +128,5 @@ handleClose_fn = async function(session) {
|
|
|
130
128
|
};
|
|
131
129
|
|
|
132
130
|
export { BaseWebSocketDO };
|
|
133
|
-
//# sourceMappingURL=chunk-
|
|
134
|
-
//# sourceMappingURL=chunk-
|
|
131
|
+
//# sourceMappingURL=chunk-E4576ZE6.js.map
|
|
132
|
+
//# sourceMappingURL=chunk-E4576ZE6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/BaseWebSocketDO.ts"],"names":["error"],"mappings":"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","file":"chunk-E4576ZE6.js","sourcesContent":["import { DurableObject } from \"cloudflare:workers\";\nimport type { DOWithHonoApp } from \"@firtoz/hono-fetcher/honoDoFetcher\";\nimport { type Context, Hono } from \"hono\";\nimport type {\n\tBaseSession,\n\tSessionClientMessage,\n\tSessionEnv,\n} from \"./BaseSession\";\n\nexport type BaseWebSocketDOOptions<\n\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\tTSession extends BaseSession<any, any, any, any>,\n\tTEnv extends SessionEnv<TSession>,\n> = {\n\tcreateSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t) => TSession | Promise<TSession>;\n\t/**\n\t * If set, called on the WebSocketPair **server** socket before\n\t * {@link DurableObjectState#acceptWebSocket}. Use `{ allowHalfOpen: true }` when you need\n\t * to coordinate close independently (e.g. proxying). Omit for the usual hibernation-only path\n\t * (no `WebSocket#accept` before `acceptWebSocket`), which matches\n\t * [Durable Object WebSocket examples](https://developers.cloudflare.com/durable-objects/best-practices/websockets/).\n\t */\n\tpairServerWebSocketAcceptOptions?: WebSocketAcceptOptions;\n};\n\nexport abstract class BaseWebSocketDO<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\tTSession extends BaseSession<any, any, any, any> = BaseSession<\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany\n\t\t>,\n\t\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n\t>\n\textends DurableObject<TEnv>\n\timplements DOWithHonoApp\n{\n\tprotected readonly sessions = new Map<WebSocket, TSession>();\n\tabstract readonly app: Hono<{ Bindings: TEnv }>;\n\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\tprivate readonly options: BaseWebSocketDOOptions<TSession, TEnv>,\n\t) {\n\t\tsuper(ctx, env);\n\n\t\tthis.ctx.blockConcurrencyWhile(async () => {\n\t\t\tconst websockets = this.ctx.getWebSockets();\n\t\t\tawait Promise.all(\n\t\t\t\twebsockets.map(async (websocket) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// For resumed sessions, we don't have a Hono context\n\t\t\t\t\t\t// Pass undefined and let implementers handle it\n\t\t\t\t\t\tconst session = await options.createSession(undefined, websocket);\n\t\t\t\t\t\tsession.resume();\n\t\t\t\t\t\tthis.sessions.set(websocket, session);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(`Error during session setup: ${error}`);\n\t\t\t\t\t\tawait this.webSocketError(websocket, error);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected getBaseApp() {\n\t\treturn new Hono<{ Bindings: TEnv }>().get(\n\t\t\t\"/websocket\",\n\t\t\tasync (ctx): Promise<Response> => {\n\t\t\t\tconst { req } = ctx;\n\t\t\t\tif (req.header(\"Upgrade\") !== \"websocket\") {\n\t\t\t\t\tconsole.error(\"Expected websocket\");\n\t\t\t\t\treturn ctx.text(\"Expected websocket\", 400);\n\t\t\t\t}\n\n\t\t\t\tconst [client, server] = Object.values(new WebSocketPair()) as [\n\t\t\t\t\tWebSocket,\n\t\t\t\t\tWebSocket,\n\t\t\t\t];\n\n\t\t\t\ttry {\n\t\t\t\t\tawait this.handleSession(ctx, server);\n\t\t\t\t\treturn new Response(null, { status: 101, webSocket: client });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(error);\n\t\t\t\t\tclient.accept();\n\t\t\t\t\tclient.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\terror: \"Uncaught exception during session setup.\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tclient.close(1011, \"Uncaught exception during session setup.\");\n\t\t\t\t\treturn new Response(null, { status: 101, webSocket: client });\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\tasync handleSession(\n\t\tctx: Context<{ Bindings: TEnv }>,\n\t\tws: WebSocket,\n\t): Promise<void> {\n\t\tconst acceptOpts = this.options.pairServerWebSocketAcceptOptions;\n\t\tif (acceptOpts !== undefined) {\n\t\t\tws.accept(acceptOpts);\n\t\t}\n\t\tthis.ctx.acceptWebSocket(ws);\n\t\ttry {\n\t\t\tconst session = await this.options.createSession(ctx, ws);\n\t\t\tsession.startFresh(ctx);\n\t\t\tthis.sessions.set(ws, session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session setup: ${error}`);\n\t\t\tawait this.webSocketError(ws, error);\n\t\t}\n\t}\n\n\toverride async webSocketMessage(\n\t\tws: WebSocket,\n\t\tmessage: string | ArrayBuffer,\n\t): Promise<void> {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) return;\n\n\t\ttry {\n\t\t\tif (message instanceof ArrayBuffer) {\n\t\t\t\tawait session.handleBufferMessage(message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rawMessageSession = session as BaseSession<\n\t\t\t\tunknown,\n\t\t\t\tunknown,\n\t\t\t\tunknown,\n\t\t\t\tTEnv\n\t\t\t> & {\n\t\t\t\thandleRawMessage?: (rawMessage: string) => Promise<void>;\n\t\t\t};\n\t\t\tif (rawMessageSession.handleRawMessage) {\n\t\t\t\tawait rawMessageSession.handleRawMessage(message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst parsed = JSON.parse(message) as SessionClientMessage<TSession>;\n\t\t\tawait session.handleMessage(parsed);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session message: ${error}`);\n\t\t\t// Let the implementer decide how to handle errors in their session implementation\n\t\t\t// The session can optionally implement error handling that closes the connection if needed\n\t\t}\n\t}\n\n\toverride async webSocketClose(\n\t\tws: WebSocket,\n\t\tcode: number,\n\t\treason: string,\n\t\t_wasClean: boolean,\n\t) {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) return;\n\n\t\ttry {\n\t\t\tawait this.#handleClose(session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\t// Pre–2026-04-07 (manual close reply): required to complete the Close handshake and avoid\n\t\t\t// abnormal client closure. With web_socket_auto_reply_to_close (default on 2026-04-07+),\n\t\t\t// the runtime already replied; `close()` is a no-op if already CLOSED.\n\t\t\tws.close(code, reason);\n\t\t}\n\t}\n\n\toverride async webSocketError(ws: WebSocket, error: unknown) {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) {\n\t\t\t// Idempotent: safe when the socket is already closed (auto close reply) or in CLOSING\n\t\t\t// (legacy manual reply to complete the handshake).\n\t\t\tws.close(1011, \"Error during session setup.\");\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.error(`Error for session: ${error}`);\n\t\ttry {\n\t\t\tawait this.#handleClose(session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\tws.close(1011, \"Error during session.\");\n\t\t}\n\t}\n\n\tasync #handleClose(session: TSession) {\n\t\ttry {\n\t\t\tawait session.handleClose();\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\tthis.sessions.delete(session.websocket);\n\t\t}\n\t}\n\n\toverride fetch(request: Request): Response | Promise<Response> {\n\t\treturn this.app.fetch(request, this.env);\n\t}\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseWebSocketDO } from './chunk-
|
|
1
|
+
import { BaseWebSocketDO } from './chunk-E4576ZE6.js';
|
|
2
2
|
|
|
3
3
|
// src/StandardSchemaWebSocketDO.ts
|
|
4
4
|
var StandardSchemaWebSocketDO = class extends BaseWebSocketDO {
|
|
@@ -11,7 +11,8 @@ var StandardSchemaWebSocketDO = class extends BaseWebSocketDO {
|
|
|
11
11
|
websocket,
|
|
12
12
|
schemaOptions
|
|
13
13
|
);
|
|
14
|
-
}
|
|
14
|
+
},
|
|
15
|
+
pairServerWebSocketAcceptOptions: options.pairServerWebSocketAcceptOptions
|
|
15
16
|
});
|
|
16
17
|
this.standardSchemaSessionOptions = options.standardSchemaSessionOptions;
|
|
17
18
|
this.createStandardSchemaSessionFn = options.createStandardSchemaSession;
|
|
@@ -19,5 +20,5 @@ var StandardSchemaWebSocketDO = class extends BaseWebSocketDO {
|
|
|
19
20
|
};
|
|
20
21
|
|
|
21
22
|
export { StandardSchemaWebSocketDO };
|
|
22
|
-
//# sourceMappingURL=chunk-
|
|
23
|
-
//# sourceMappingURL=chunk-
|
|
23
|
+
//# sourceMappingURL=chunk-J275SVBA.js.map
|
|
24
|
+
//# sourceMappingURL=chunk-J275SVBA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/StandardSchemaWebSocketDO.ts"],"names":["ctx"],"mappings":";;;AA+CO,IAAe,yBAAA,GAAf,cAQG,eAAA,CAAgC;AAAA,EAYzC,WAAA,CACC,GAAA,EACA,GAAA,EACA,OAAA,EAMC;AACD,IAAA,KAAA,CAAM,KAAK,GAAA,EAAK;AAAA,MACf,aAAA,EAAe,CAACA,IAAAA,EAAK,SAAA,KAAc;AAClC,QAAA,MAAM,aAAA,GACL,OAAO,OAAA,CAAQ,4BAAA,KAAiC,UAAA,GAC7C,QAAQ,4BAAA,CAA6BA,IAAAA,EAAK,SAAS,CAAA,GACnD,OAAA,CAAQ,4BAAA;AAEZ,QAAA,OAAO,OAAA,CAAQ,2BAAA;AAAA,UACdA,IAAAA;AAAA,UACA,SAAA;AAAA,UACA;AAAA,SACD;AAAA,MACD,CAAA;AAAA,MACA,kCACC,OAAA,CAAQ;AAAA,KACT,CAAA;AACD,IAAA,IAAA,CAAK,+BAA+B,OAAA,CAAQ,4BAAA;AAC5C,IAAA,IAAA,CAAK,gCAAgC,OAAA,CAAQ,2BAAA;AAAA,EAC9C;AACD","file":"chunk-J275SVBA.js","sourcesContent":["import type { Context } from \"hono\";\nimport type {\n\tSessionClientMessage,\n\tSessionEnv,\n\tSessionServerMessage,\n} from \"./BaseSession\";\nimport { BaseWebSocketDO } from \"./BaseWebSocketDO\";\nimport type {\n\tStandardSchemaSession,\n\tStandardSchemaSessionOptions,\n} from \"./StandardSchemaSession\";\n\nexport type StandardSchemaSessionOptionsOrFactory<\n\tTClientMessage,\n\tTServerMessage,\n\tTEnv extends Cloudflare.Env = Cloudflare.Env,\n> =\n\t| StandardSchemaSessionOptions<TClientMessage, TServerMessage>\n\t| ((\n\t\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\t\twebsocket: WebSocket,\n\t ) => StandardSchemaSessionOptions<TClientMessage, TServerMessage>);\n\nexport type StandardSchemaWebSocketDOOptions<\n\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\tTSession extends StandardSchemaSession<any, any, any, any>,\n\tTClientMessage,\n\tTServerMessage,\n\tTEnv extends SessionEnv<TSession>,\n> = {\n\tstandardSchemaSessionOptions: StandardSchemaSessionOptionsOrFactory<\n\t\tTClientMessage,\n\t\tTServerMessage,\n\t\tTEnv\n\t>;\n\tcreateStandardSchemaSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t\toptions: StandardSchemaSessionOptions<TClientMessage, TServerMessage>,\n\t) => TSession | Promise<TSession>;\n\t/**\n\t * Optional `WebSocket#accept` options for the server side of the `WebSocketPair` (see\n\t * `pairServerWebSocketAcceptOptions` on `BaseWebSocketDO` constructor options).\n\t */\n\tpairServerWebSocketAcceptOptions?: WebSocketAcceptOptions;\n};\n\nexport abstract class StandardSchemaWebSocketDO<\n\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\tTSession extends StandardSchemaSession<any, any, any, any>,\n\tTClientMessage extends\n\t\tSessionClientMessage<TSession> = SessionClientMessage<TSession>,\n\tTServerMessage extends\n\t\tSessionServerMessage<TSession> = SessionServerMessage<TSession>,\n\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n> extends BaseWebSocketDO<TSession, TEnv> {\n\tprotected readonly standardSchemaSessionOptions: StandardSchemaSessionOptionsOrFactory<\n\t\tTClientMessage,\n\t\tTServerMessage,\n\t\tTEnv\n\t>;\n\tprotected readonly createStandardSchemaSessionFn: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t\toptions: StandardSchemaSessionOptions<TClientMessage, TServerMessage>,\n\t) => TSession | Promise<TSession>;\n\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\toptions: StandardSchemaWebSocketDOOptions<\n\t\t\tTSession,\n\t\t\tTClientMessage,\n\t\t\tTServerMessage,\n\t\t\tTEnv\n\t\t>,\n\t) {\n\t\tsuper(ctx, env, {\n\t\t\tcreateSession: (ctx, websocket) => {\n\t\t\t\tconst schemaOptions =\n\t\t\t\t\ttypeof options.standardSchemaSessionOptions === \"function\"\n\t\t\t\t\t\t? options.standardSchemaSessionOptions(ctx, websocket)\n\t\t\t\t\t\t: options.standardSchemaSessionOptions;\n\n\t\t\t\treturn options.createStandardSchemaSession(\n\t\t\t\t\tctx,\n\t\t\t\t\twebsocket,\n\t\t\t\t\tschemaOptions,\n\t\t\t\t);\n\t\t\t},\n\t\t\tpairServerWebSocketAcceptOptions:\n\t\t\t\toptions.pairServerWebSocketAcceptOptions,\n\t\t});\n\t\tthis.standardSchemaSessionOptions = options.standardSchemaSessionOptions;\n\t\tthis.createStandardSchemaSessionFn = options.createStandardSchemaSession;\n\t}\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@ export { StandardSchemaSession } from './chunk-53MFRNQS.js';
|
|
|
2
2
|
export { standardSchemaMsgpack } from './chunk-QMGIRIHJ.js';
|
|
3
3
|
export { BaseSession } from './chunk-3C77OSOD.js';
|
|
4
4
|
export { StandardSchemaWebSocketClient } from './chunk-3LWVEY3R.js';
|
|
5
|
-
export { StandardSchemaWebSocketDO } from './chunk-
|
|
6
|
-
export { BaseWebSocketDO } from './chunk-
|
|
5
|
+
export { StandardSchemaWebSocketDO } from './chunk-J275SVBA.js';
|
|
6
|
+
export { BaseWebSocketDO } from './chunk-E4576ZE6.js';
|
|
7
7
|
export { WebsocketWrapper } from './chunk-KCPOB32E.js';
|
|
8
8
|
export { parseStandardSchema } from './chunk-CAX4POIL.js';
|
|
9
9
|
import './chunk-NOUFNU2O.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/websocket-do",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.2",
|
|
4
4
|
"description": "Type-safe WebSocket session management for Cloudflare Durable Objects with Hono integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -62,9 +62,9 @@
|
|
|
62
62
|
"url": "https://github.com/firtoz/fullstack-toolkit/issues"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@cloudflare/workers-types": "^4.
|
|
66
|
-
"@firtoz/hono-fetcher": "^2.7.
|
|
67
|
-
"hono": "^4.12.
|
|
65
|
+
"@cloudflare/workers-types": "^4.20260423.1",
|
|
66
|
+
"@firtoz/hono-fetcher": "^2.7.2",
|
|
67
|
+
"hono": "^4.12.14",
|
|
68
68
|
"react": ">=18.0.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependenciesMeta": {
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@standard-schema/spec": "^1.1.0",
|
|
77
|
-
"msgpackr": "^1.11.
|
|
77
|
+
"msgpackr": "^1.11.10"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
80
|
"node": ">=18.0.0"
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@types/react": "^19.2.14",
|
|
87
|
-
"bun-types": "^1.3.
|
|
88
|
-
"typescript": "^6.0.
|
|
87
|
+
"bun-types": "^1.3.13",
|
|
88
|
+
"typescript": "^6.0.3",
|
|
89
89
|
"zod": "^4.3.6"
|
|
90
90
|
}
|
|
91
91
|
}
|
package/src/BaseWebSocketDO.ts
CHANGED
|
@@ -16,6 +16,14 @@ export type BaseWebSocketDOOptions<
|
|
|
16
16
|
ctx: Context<{ Bindings: TEnv }> | undefined,
|
|
17
17
|
websocket: WebSocket,
|
|
18
18
|
) => TSession | Promise<TSession>;
|
|
19
|
+
/**
|
|
20
|
+
* If set, called on the WebSocketPair **server** socket before
|
|
21
|
+
* {@link DurableObjectState#acceptWebSocket}. Use `{ allowHalfOpen: true }` when you need
|
|
22
|
+
* to coordinate close independently (e.g. proxying). Omit for the usual hibernation-only path
|
|
23
|
+
* (no `WebSocket#accept` before `acceptWebSocket`), which matches
|
|
24
|
+
* [Durable Object WebSocket examples](https://developers.cloudflare.com/durable-objects/best-practices/websockets/).
|
|
25
|
+
*/
|
|
26
|
+
pairServerWebSocketAcceptOptions?: WebSocketAcceptOptions;
|
|
19
27
|
};
|
|
20
28
|
|
|
21
29
|
export abstract class BaseWebSocketDO<
|
|
@@ -101,6 +109,10 @@ export abstract class BaseWebSocketDO<
|
|
|
101
109
|
ctx: Context<{ Bindings: TEnv }>,
|
|
102
110
|
ws: WebSocket,
|
|
103
111
|
): Promise<void> {
|
|
112
|
+
const acceptOpts = this.options.pairServerWebSocketAcceptOptions;
|
|
113
|
+
if (acceptOpts !== undefined) {
|
|
114
|
+
ws.accept(acceptOpts);
|
|
115
|
+
}
|
|
104
116
|
this.ctx.acceptWebSocket(ws);
|
|
105
117
|
try {
|
|
106
118
|
const session = await this.options.createSession(ctx, ws);
|
|
@@ -149,8 +161,8 @@ export abstract class BaseWebSocketDO<
|
|
|
149
161
|
|
|
150
162
|
override async webSocketClose(
|
|
151
163
|
ws: WebSocket,
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
code: number,
|
|
165
|
+
reason: string,
|
|
154
166
|
_wasClean: boolean,
|
|
155
167
|
) {
|
|
156
168
|
const session = this.sessions.get(ws);
|
|
@@ -161,27 +173,19 @@ export abstract class BaseWebSocketDO<
|
|
|
161
173
|
} catch (error) {
|
|
162
174
|
console.error(`Error during session close: ${error}`);
|
|
163
175
|
} finally {
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
if
|
|
167
|
-
|
|
168
|
-
ws.readyState === WebSocket.CLOSING
|
|
169
|
-
) {
|
|
170
|
-
ws.close(1000, "Normal closure");
|
|
171
|
-
}
|
|
176
|
+
// Pre–2026-04-07 (manual close reply): required to complete the Close handshake and avoid
|
|
177
|
+
// abnormal client closure. With web_socket_auto_reply_to_close (default on 2026-04-07+),
|
|
178
|
+
// the runtime already replied; `close()` is a no-op if already CLOSED.
|
|
179
|
+
ws.close(code, reason);
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
182
|
|
|
175
183
|
override async webSocketError(ws: WebSocket, error: unknown) {
|
|
176
184
|
const session = this.sessions.get(ws);
|
|
177
185
|
if (!session) {
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
ws.readyState === WebSocket.CLOSING
|
|
182
|
-
) {
|
|
183
|
-
ws.close(1011, "Error during session setup.");
|
|
184
|
-
}
|
|
186
|
+
// Idempotent: safe when the socket is already closed (auto close reply) or in CLOSING
|
|
187
|
+
// (legacy manual reply to complete the handshake).
|
|
188
|
+
ws.close(1011, "Error during session setup.");
|
|
185
189
|
return;
|
|
186
190
|
}
|
|
187
191
|
|
|
@@ -191,13 +195,7 @@ export abstract class BaseWebSocketDO<
|
|
|
191
195
|
} catch (error) {
|
|
192
196
|
console.error(`Error during session close: ${error}`);
|
|
193
197
|
} finally {
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
ws.readyState === WebSocket.OPEN ||
|
|
197
|
-
ws.readyState === WebSocket.CLOSING
|
|
198
|
-
) {
|
|
199
|
-
ws.close(1011, "Error during session.");
|
|
200
|
-
}
|
|
198
|
+
ws.close(1011, "Error during session.");
|
|
201
199
|
}
|
|
202
200
|
}
|
|
203
201
|
|
|
@@ -38,6 +38,11 @@ export type StandardSchemaWebSocketDOOptions<
|
|
|
38
38
|
websocket: WebSocket,
|
|
39
39
|
options: StandardSchemaSessionOptions<TClientMessage, TServerMessage>,
|
|
40
40
|
) => TSession | Promise<TSession>;
|
|
41
|
+
/**
|
|
42
|
+
* Optional `WebSocket#accept` options for the server side of the `WebSocketPair` (see
|
|
43
|
+
* `pairServerWebSocketAcceptOptions` on `BaseWebSocketDO` constructor options).
|
|
44
|
+
*/
|
|
45
|
+
pairServerWebSocketAcceptOptions?: WebSocketAcceptOptions;
|
|
41
46
|
};
|
|
42
47
|
|
|
43
48
|
export abstract class StandardSchemaWebSocketDO<
|
|
@@ -83,6 +88,8 @@ export abstract class StandardSchemaWebSocketDO<
|
|
|
83
88
|
schemaOptions,
|
|
84
89
|
);
|
|
85
90
|
},
|
|
91
|
+
pairServerWebSocketAcceptOptions:
|
|
92
|
+
options.pairServerWebSocketAcceptOptions,
|
|
86
93
|
});
|
|
87
94
|
this.standardSchemaSessionOptions = options.standardSchemaSessionOptions;
|
|
88
95
|
this.createStandardSchemaSessionFn = options.createStandardSchemaSession;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/StandardSchemaWebSocketDO.ts"],"names":["ctx"],"mappings":";;;AA0CO,IAAe,yBAAA,GAAf,cAQG,eAAA,CAAgC;AAAA,EAYzC,WAAA,CACC,GAAA,EACA,GAAA,EACA,OAAA,EAMC;AACD,IAAA,KAAA,CAAM,KAAK,GAAA,EAAK;AAAA,MACf,aAAA,EAAe,CAACA,IAAAA,EAAK,SAAA,KAAc;AAClC,QAAA,MAAM,aAAA,GACL,OAAO,OAAA,CAAQ,4BAAA,KAAiC,UAAA,GAC7C,QAAQ,4BAAA,CAA6BA,IAAAA,EAAK,SAAS,CAAA,GACnD,OAAA,CAAQ,4BAAA;AAEZ,QAAA,OAAO,OAAA,CAAQ,2BAAA;AAAA,UACdA,IAAAA;AAAA,UACA,SAAA;AAAA,UACA;AAAA,SACD;AAAA,MACD;AAAA,KACA,CAAA;AACD,IAAA,IAAA,CAAK,+BAA+B,OAAA,CAAQ,4BAAA;AAC5C,IAAA,IAAA,CAAK,gCAAgC,OAAA,CAAQ,2BAAA;AAAA,EAC9C;AACD","file":"chunk-ULGH6X42.js","sourcesContent":["import type { Context } from \"hono\";\nimport type {\n\tSessionClientMessage,\n\tSessionEnv,\n\tSessionServerMessage,\n} from \"./BaseSession\";\nimport { BaseWebSocketDO } from \"./BaseWebSocketDO\";\nimport type {\n\tStandardSchemaSession,\n\tStandardSchemaSessionOptions,\n} from \"./StandardSchemaSession\";\n\nexport type StandardSchemaSessionOptionsOrFactory<\n\tTClientMessage,\n\tTServerMessage,\n\tTEnv extends Cloudflare.Env = Cloudflare.Env,\n> =\n\t| StandardSchemaSessionOptions<TClientMessage, TServerMessage>\n\t| ((\n\t\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\t\twebsocket: WebSocket,\n\t ) => StandardSchemaSessionOptions<TClientMessage, TServerMessage>);\n\nexport type StandardSchemaWebSocketDOOptions<\n\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\tTSession extends StandardSchemaSession<any, any, any, any>,\n\tTClientMessage,\n\tTServerMessage,\n\tTEnv extends SessionEnv<TSession>,\n> = {\n\tstandardSchemaSessionOptions: StandardSchemaSessionOptionsOrFactory<\n\t\tTClientMessage,\n\t\tTServerMessage,\n\t\tTEnv\n\t>;\n\tcreateStandardSchemaSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t\toptions: StandardSchemaSessionOptions<TClientMessage, TServerMessage>,\n\t) => TSession | Promise<TSession>;\n};\n\nexport abstract class StandardSchemaWebSocketDO<\n\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\tTSession extends StandardSchemaSession<any, any, any, any>,\n\tTClientMessage extends\n\t\tSessionClientMessage<TSession> = SessionClientMessage<TSession>,\n\tTServerMessage extends\n\t\tSessionServerMessage<TSession> = SessionServerMessage<TSession>,\n\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n> extends BaseWebSocketDO<TSession, TEnv> {\n\tprotected readonly standardSchemaSessionOptions: StandardSchemaSessionOptionsOrFactory<\n\t\tTClientMessage,\n\t\tTServerMessage,\n\t\tTEnv\n\t>;\n\tprotected readonly createStandardSchemaSessionFn: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t\toptions: StandardSchemaSessionOptions<TClientMessage, TServerMessage>,\n\t) => TSession | Promise<TSession>;\n\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\toptions: StandardSchemaWebSocketDOOptions<\n\t\t\tTSession,\n\t\t\tTClientMessage,\n\t\t\tTServerMessage,\n\t\t\tTEnv\n\t\t>,\n\t) {\n\t\tsuper(ctx, env, {\n\t\t\tcreateSession: (ctx, websocket) => {\n\t\t\t\tconst schemaOptions =\n\t\t\t\t\ttypeof options.standardSchemaSessionOptions === \"function\"\n\t\t\t\t\t\t? options.standardSchemaSessionOptions(ctx, websocket)\n\t\t\t\t\t\t: options.standardSchemaSessionOptions;\n\n\t\t\t\treturn options.createStandardSchemaSession(\n\t\t\t\t\tctx,\n\t\t\t\t\twebsocket,\n\t\t\t\t\tschemaOptions,\n\t\t\t\t);\n\t\t\t},\n\t\t});\n\t\tthis.standardSchemaSessionOptions = options.standardSchemaSessionOptions;\n\t\tthis.createStandardSchemaSessionFn = options.createStandardSchemaSession;\n\t}\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/BaseWebSocketDO.ts"],"names":["error"],"mappings":"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","file":"chunk-XFB6C3NZ.js","sourcesContent":["import { DurableObject } from \"cloudflare:workers\";\nimport type { DOWithHonoApp } from \"@firtoz/hono-fetcher/honoDoFetcher\";\nimport { type Context, Hono } from \"hono\";\nimport type {\n\tBaseSession,\n\tSessionClientMessage,\n\tSessionEnv,\n} from \"./BaseSession\";\n\nexport type BaseWebSocketDOOptions<\n\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\tTSession extends BaseSession<any, any, any, any>,\n\tTEnv extends SessionEnv<TSession>,\n> = {\n\tcreateSession: (\n\t\tctx: Context<{ Bindings: TEnv }> | undefined,\n\t\twebsocket: WebSocket,\n\t) => TSession | Promise<TSession>;\n};\n\nexport abstract class BaseWebSocketDO<\n\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\tTSession extends BaseSession<any, any, any, any> = BaseSession<\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: We are using any on purpose to allow any type of session.\n\t\t\tany\n\t\t>,\n\t\tTEnv extends SessionEnv<TSession> = SessionEnv<TSession>,\n\t>\n\textends DurableObject<TEnv>\n\timplements DOWithHonoApp\n{\n\tprotected readonly sessions = new Map<WebSocket, TSession>();\n\tabstract readonly app: Hono<{ Bindings: TEnv }>;\n\n\tconstructor(\n\t\tctx: DurableObjectState,\n\t\tenv: TEnv,\n\t\tprivate readonly options: BaseWebSocketDOOptions<TSession, TEnv>,\n\t) {\n\t\tsuper(ctx, env);\n\n\t\tthis.ctx.blockConcurrencyWhile(async () => {\n\t\t\tconst websockets = this.ctx.getWebSockets();\n\t\t\tawait Promise.all(\n\t\t\t\twebsockets.map(async (websocket) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t// For resumed sessions, we don't have a Hono context\n\t\t\t\t\t\t// Pass undefined and let implementers handle it\n\t\t\t\t\t\tconst session = await options.createSession(undefined, websocket);\n\t\t\t\t\t\tsession.resume();\n\t\t\t\t\t\tthis.sessions.set(websocket, session);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(`Error during session setup: ${error}`);\n\t\t\t\t\t\tawait this.webSocketError(websocket, error);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t});\n\t}\n\n\tprotected getBaseApp() {\n\t\treturn new Hono<{ Bindings: TEnv }>().get(\n\t\t\t\"/websocket\",\n\t\t\tasync (ctx): Promise<Response> => {\n\t\t\t\tconst { req } = ctx;\n\t\t\t\tif (req.header(\"Upgrade\") !== \"websocket\") {\n\t\t\t\t\tconsole.error(\"Expected websocket\");\n\t\t\t\t\treturn ctx.text(\"Expected websocket\", 400);\n\t\t\t\t}\n\n\t\t\t\tconst [client, server] = Object.values(new WebSocketPair()) as [\n\t\t\t\t\tWebSocket,\n\t\t\t\t\tWebSocket,\n\t\t\t\t];\n\n\t\t\t\ttry {\n\t\t\t\t\tawait this.handleSession(ctx, server);\n\t\t\t\t\treturn new Response(null, { status: 101, webSocket: client });\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(error);\n\t\t\t\t\tclient.accept();\n\t\t\t\t\tclient.send(\n\t\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\t\terror: \"Uncaught exception during session setup.\",\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tclient.close(1011, \"Uncaught exception during session setup.\");\n\t\t\t\t\treturn new Response(null, { status: 101, webSocket: client });\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\tasync handleSession(\n\t\tctx: Context<{ Bindings: TEnv }>,\n\t\tws: WebSocket,\n\t): Promise<void> {\n\t\tthis.ctx.acceptWebSocket(ws);\n\t\ttry {\n\t\t\tconst session = await this.options.createSession(ctx, ws);\n\t\t\tsession.startFresh(ctx);\n\t\t\tthis.sessions.set(ws, session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session setup: ${error}`);\n\t\t\tawait this.webSocketError(ws, error);\n\t\t}\n\t}\n\n\toverride async webSocketMessage(\n\t\tws: WebSocket,\n\t\tmessage: string | ArrayBuffer,\n\t): Promise<void> {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) return;\n\n\t\ttry {\n\t\t\tif (message instanceof ArrayBuffer) {\n\t\t\t\tawait session.handleBufferMessage(message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rawMessageSession = session as BaseSession<\n\t\t\t\tunknown,\n\t\t\t\tunknown,\n\t\t\t\tunknown,\n\t\t\t\tTEnv\n\t\t\t> & {\n\t\t\t\thandleRawMessage?: (rawMessage: string) => Promise<void>;\n\t\t\t};\n\t\t\tif (rawMessageSession.handleRawMessage) {\n\t\t\t\tawait rawMessageSession.handleRawMessage(message);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst parsed = JSON.parse(message) as SessionClientMessage<TSession>;\n\t\t\tawait session.handleMessage(parsed);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session message: ${error}`);\n\t\t\t// Let the implementer decide how to handle errors in their session implementation\n\t\t\t// The session can optionally implement error handling that closes the connection if needed\n\t\t}\n\t}\n\n\toverride async webSocketClose(\n\t\tws: WebSocket,\n\t\t_code: number,\n\t\t_reason: string,\n\t\t_wasClean: boolean,\n\t) {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) return;\n\n\t\ttry {\n\t\t\tawait this.#handleClose(session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\t// Call close() for both OPEN and CLOSING states\n\t\t\t// For CLOSING, this can help ensure the WebSocket fully transitions to CLOSED\n\t\t\tif (\n\t\t\t\tws.readyState === WebSocket.OPEN ||\n\t\t\t\tws.readyState === WebSocket.CLOSING\n\t\t\t) {\n\t\t\t\tws.close(1000, \"Normal closure\");\n\t\t\t}\n\t\t}\n\t}\n\n\toverride async webSocketError(ws: WebSocket, error: unknown) {\n\t\tconst session = this.sessions.get(ws);\n\t\tif (!session) {\n\t\t\t// Call close() for both OPEN and CLOSING states\n\t\t\tif (\n\t\t\t\tws.readyState === WebSocket.OPEN ||\n\t\t\t\tws.readyState === WebSocket.CLOSING\n\t\t\t) {\n\t\t\t\tws.close(1011, \"Error during session setup.\");\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.error(`Error for session: ${error}`);\n\t\ttry {\n\t\t\tawait this.#handleClose(session);\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\t// Call close() for both OPEN and CLOSING states\n\t\t\tif (\n\t\t\t\tws.readyState === WebSocket.OPEN ||\n\t\t\t\tws.readyState === WebSocket.CLOSING\n\t\t\t) {\n\t\t\t\tws.close(1011, \"Error during session.\");\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleClose(session: TSession) {\n\t\ttry {\n\t\t\tawait session.handleClose();\n\t\t} catch (error) {\n\t\t\tconsole.error(`Error during session close: ${error}`);\n\t\t} finally {\n\t\t\tthis.sessions.delete(session.websocket);\n\t\t}\n\t}\n\n\toverride fetch(request: Request): Response | Promise<Response> {\n\t\treturn this.app.fetch(request, this.env);\n\t}\n}\n"]}
|