@firtoz/websocket-do 7.0.1 → 7.1.0
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/package.json +15 -10
- package/src/BaseWebSocketDO.ts +13 -0
- package/src/ZodSession.ts +38 -2
- package/src/ZodWebSocketClient.ts +10 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firtoz/websocket-do",
|
|
3
|
-
"version": "7.0
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "Type-safe WebSocket session management for Cloudflare Durable Objects with Hono integration",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"types": "./src/index.ts",
|
|
11
11
|
"import": "./src/index.ts",
|
|
12
12
|
"require": "./src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"./zod-client": {
|
|
15
|
+
"types": "./src/ZodWebSocketClient.ts",
|
|
16
|
+
"import": "./src/ZodWebSocketClient.ts",
|
|
17
|
+
"require": "./src/ZodWebSocketClient.ts"
|
|
13
18
|
}
|
|
14
19
|
},
|
|
15
20
|
"files": [
|
|
@@ -18,7 +23,7 @@
|
|
|
18
23
|
"README.md"
|
|
19
24
|
],
|
|
20
25
|
"scripts": {
|
|
21
|
-
"typecheck": "
|
|
26
|
+
"typecheck": "tsgo --noEmit -p ./tsconfig.json",
|
|
22
27
|
"lint": "biome check --write src",
|
|
23
28
|
"lint:ci": "biome ci src",
|
|
24
29
|
"format": "biome format src --write"
|
|
@@ -45,14 +50,14 @@
|
|
|
45
50
|
"url": "https://github.com/firtoz/fullstack-toolkit/issues"
|
|
46
51
|
},
|
|
47
52
|
"peerDependencies": {
|
|
48
|
-
"@cloudflare/workers-types": "^4.
|
|
53
|
+
"@cloudflare/workers-types": "^4.20260329.1",
|
|
49
54
|
"@firtoz/hono-fetcher": "^2.3.2",
|
|
50
|
-
"hono": "^4.
|
|
55
|
+
"hono": "^4.12.9"
|
|
51
56
|
},
|
|
52
57
|
"optionalDependencies": {
|
|
53
|
-
"msgpackr": "^1.11.
|
|
54
|
-
"react": "^19.2.
|
|
55
|
-
"zod": "^4.3.
|
|
58
|
+
"msgpackr": "^1.11.9",
|
|
59
|
+
"react": "^19.2.4",
|
|
60
|
+
"zod": "^4.3.6"
|
|
56
61
|
},
|
|
57
62
|
"engines": {
|
|
58
63
|
"node": ">=18.0.0"
|
|
@@ -61,8 +66,8 @@
|
|
|
61
66
|
"access": "public"
|
|
62
67
|
},
|
|
63
68
|
"devDependencies": {
|
|
64
|
-
"@types/react": "^19.2.
|
|
65
|
-
"bun-types": "^1.3.
|
|
66
|
-
"typescript": "^
|
|
69
|
+
"@types/react": "^19.2.14",
|
|
70
|
+
"bun-types": "^1.3.11",
|
|
71
|
+
"typescript": "^6.0.2"
|
|
67
72
|
}
|
|
68
73
|
}
|
package/src/BaseWebSocketDO.ts
CHANGED
|
@@ -125,6 +125,19 @@ export abstract class BaseWebSocketDO<
|
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
const rawMessageSession = session as BaseSession<
|
|
129
|
+
unknown,
|
|
130
|
+
unknown,
|
|
131
|
+
unknown,
|
|
132
|
+
TEnv
|
|
133
|
+
> & {
|
|
134
|
+
handleRawMessage?: (rawMessage: string) => Promise<void>;
|
|
135
|
+
};
|
|
136
|
+
if (rawMessageSession.handleRawMessage) {
|
|
137
|
+
await rawMessageSession.handleRawMessage(message);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
const parsed = JSON.parse(message) as SessionClientMessage<TSession>;
|
|
129
142
|
await session.handleMessage(parsed);
|
|
130
143
|
} catch (error) {
|
package/src/ZodSession.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { zodMsgpack } from "./zodMsgpack";
|
|
|
6
6
|
export type ZodSessionOptions<TClientMessage, TServerMessage> = {
|
|
7
7
|
clientSchema: ZodType<TClientMessage>;
|
|
8
8
|
serverSchema: ZodType<TServerMessage>;
|
|
9
|
+
serializeJson?: (value: unknown) => string;
|
|
10
|
+
deserializeJson?: (raw: string) => unknown;
|
|
9
11
|
enableBufferMessages?: boolean;
|
|
10
12
|
sendProtocolError?: (
|
|
11
13
|
websocket: WebSocket,
|
|
@@ -70,6 +72,28 @@ export class ZodSession<
|
|
|
70
72
|
this.enableBufferMessages = options.enableBufferMessages ?? false;
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
public async handleRawMessage(rawMessage: string): Promise<void> {
|
|
76
|
+
// If buffer messages are enabled, reject string messages
|
|
77
|
+
if (this.enableBufferMessages) {
|
|
78
|
+
console.error(
|
|
79
|
+
"String messages not allowed when buffer messages are enabled",
|
|
80
|
+
);
|
|
81
|
+
await this.sendProtocolError(
|
|
82
|
+
"String messages are not allowed. Please use buffer messages.",
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const parsed = this.deserializeJson(rawMessage);
|
|
89
|
+
const validatedMessage = this.options.clientSchema.parse(parsed);
|
|
90
|
+
await this.zodHandlers.handleValidatedMessage(validatedMessage);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error("Invalid client message received:", error);
|
|
93
|
+
await this._internalHandleValidationError(error, rawMessage);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
// Internal method used by the base class handlers
|
|
74
98
|
private async _internalHandleMessage(message: TClientMessage): Promise<void> {
|
|
75
99
|
// If buffer messages are enabled, reject JSON messages
|
|
@@ -152,7 +176,7 @@ export class ZodSession<
|
|
|
152
176
|
|
|
153
177
|
if (this.websocket.readyState !== WebSocket.OPEN) return;
|
|
154
178
|
|
|
155
|
-
this.websocket.send(
|
|
179
|
+
this.websocket.send(this.serializeJson(validatedMessage));
|
|
156
180
|
} catch (error) {
|
|
157
181
|
console.error("Invalid server message to send:", error);
|
|
158
182
|
}
|
|
@@ -180,13 +204,25 @@ export class ZodSession<
|
|
|
180
204
|
} else {
|
|
181
205
|
// Default implementation: send a simple error object - no schema validation needed
|
|
182
206
|
if (this.websocket.readyState !== WebSocket.OPEN) return;
|
|
183
|
-
this.websocket.send(
|
|
207
|
+
this.websocket.send(this.serializeJson({ error: errorMessage }));
|
|
184
208
|
}
|
|
185
209
|
} catch (error) {
|
|
186
210
|
console.error("Failed to send protocol error:", error);
|
|
187
211
|
}
|
|
188
212
|
}
|
|
189
213
|
|
|
214
|
+
private serializeJson(value: unknown): string {
|
|
215
|
+
return this.options.serializeJson
|
|
216
|
+
? this.options.serializeJson(value)
|
|
217
|
+
: JSON.stringify(value);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private deserializeJson(raw: string): unknown {
|
|
221
|
+
return this.options.deserializeJson
|
|
222
|
+
? this.options.deserializeJson(raw)
|
|
223
|
+
: JSON.parse(raw);
|
|
224
|
+
}
|
|
225
|
+
|
|
190
226
|
// Type-safe broadcast that validates server messages
|
|
191
227
|
// Automatically uses the correct format based on session configuration
|
|
192
228
|
public broadcast(message: TServerMessage, excludeSelf = false): void {
|
|
@@ -13,6 +13,8 @@ export interface ZodWebSocketClientOptions<TClientMessage, TServerMessage> {
|
|
|
13
13
|
webSocket?: WebSocket;
|
|
14
14
|
clientSchema: ZodType<TClientMessage>;
|
|
15
15
|
serverSchema: ZodType<TServerMessage>;
|
|
16
|
+
serializeJson?: (value: unknown) => string;
|
|
17
|
+
deserializeJson?: (raw: string) => unknown;
|
|
16
18
|
enableBufferMessages?: boolean;
|
|
17
19
|
onMessage?: (message: TServerMessage) => void;
|
|
18
20
|
onOpen?: (event: Event) => void;
|
|
@@ -25,6 +27,8 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
|
|
|
25
27
|
private ws: WebSocket;
|
|
26
28
|
private readonly clientSchema: ZodType<TClientMessage>;
|
|
27
29
|
private readonly serverSchema: ZodType<TServerMessage>;
|
|
30
|
+
private readonly serializeJson: (value: unknown) => string;
|
|
31
|
+
private readonly deserializeJson: (raw: string) => unknown;
|
|
28
32
|
private readonly enableBufferMessages: boolean;
|
|
29
33
|
private readonly onMessageCallback?: (message: TServerMessage) => void;
|
|
30
34
|
private readonly onValidationError?: (
|
|
@@ -37,6 +41,8 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
|
|
|
37
41
|
) {
|
|
38
42
|
this.clientSchema = options.clientSchema;
|
|
39
43
|
this.serverSchema = options.serverSchema;
|
|
44
|
+
this.serializeJson = options.serializeJson ?? JSON.stringify;
|
|
45
|
+
this.deserializeJson = options.deserializeJson ?? JSON.parse;
|
|
40
46
|
this.enableBufferMessages = options.enableBufferMessages ?? false;
|
|
41
47
|
this.onMessageCallback = options.onMessage;
|
|
42
48
|
this.onValidationError = options.onValidationError;
|
|
@@ -108,7 +114,7 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
|
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
// Parse and validate
|
|
111
|
-
const parsed =
|
|
117
|
+
const parsed = this.deserializeJson(event.data);
|
|
112
118
|
parsedMessage = this.serverSchema.parse(parsed);
|
|
113
119
|
}
|
|
114
120
|
|
|
@@ -132,12 +138,12 @@ export class ZodWebSocketClient<TClientMessage, TServerMessage> {
|
|
|
132
138
|
const validatedMessage = this.clientSchema.parse(message);
|
|
133
139
|
|
|
134
140
|
if (this.enableBufferMessages) {
|
|
135
|
-
// Encode as msgpack
|
|
141
|
+
// Encode as msgpack (ensure ArrayBufferView for WebSocket.send)
|
|
136
142
|
const packed = pack(validatedMessage);
|
|
137
|
-
this.ws.send(packed);
|
|
143
|
+
this.ws.send(new Uint8Array(packed));
|
|
138
144
|
} else {
|
|
139
145
|
// Encode as JSON
|
|
140
|
-
this.ws.send(
|
|
146
|
+
this.ws.send(this.serializeJson(validatedMessage));
|
|
141
147
|
}
|
|
142
148
|
} catch (error) {
|
|
143
149
|
console.error("Failed to send message:", error);
|