rivetkit 2.0.6 → 2.0.7-rc.1

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.
Files changed (170) hide show
  1. package/dist/schemas/actor-persist/v1.ts +0 -6
  2. package/dist/tsup/actor-router-consts-B3Lu87yJ.d.cts +28 -0
  3. package/dist/tsup/actor-router-consts-B3Lu87yJ.d.ts +28 -0
  4. package/dist/tsup/{chunk-MRRT2CZD.cjs → chunk-3MBP4WNC.cjs} +7 -7
  5. package/dist/tsup/{chunk-MRRT2CZD.cjs.map → chunk-3MBP4WNC.cjs.map} +1 -1
  6. package/dist/tsup/{chunk-TWGATZ3X.cjs → chunk-3Y45CIF4.cjs} +922 -872
  7. package/dist/tsup/chunk-3Y45CIF4.cjs.map +1 -0
  8. package/dist/tsup/chunk-4GP7BZSR.js +102 -0
  9. package/dist/tsup/chunk-4GP7BZSR.js.map +1 -0
  10. package/dist/tsup/{chunk-UFWAK3X2.cjs → chunk-5ZOHIKWG.cjs} +660 -385
  11. package/dist/tsup/chunk-5ZOHIKWG.cjs.map +1 -0
  12. package/dist/tsup/{chunk-5JBFVV4C.cjs → chunk-6EUWRXLT.cjs} +21 -7
  13. package/dist/tsup/chunk-6EUWRXLT.cjs.map +1 -0
  14. package/dist/tsup/{chunk-UTI5NCES.cjs → chunk-6OVKCDSH.cjs} +6 -6
  15. package/dist/tsup/{chunk-UTI5NCES.cjs.map → chunk-6OVKCDSH.cjs.map} +1 -1
  16. package/dist/tsup/{chunk-VPV4MWXR.js → chunk-7N56ZUC7.js} +3 -3
  17. package/dist/tsup/{chunk-DIAYNQTE.cjs → chunk-B3TLRM4Q.cjs} +12 -12
  18. package/dist/tsup/{chunk-DIAYNQTE.cjs.map → chunk-B3TLRM4Q.cjs.map} +1 -1
  19. package/dist/tsup/{chunk-4CKHQRXG.js → chunk-BW5DPM6Z.js} +515 -240
  20. package/dist/tsup/chunk-BW5DPM6Z.js.map +1 -0
  21. package/dist/tsup/{chunk-NTCUGYSD.cjs → chunk-DFS77KAA.cjs} +34 -31
  22. package/dist/tsup/chunk-DFS77KAA.cjs.map +1 -0
  23. package/dist/tsup/{chunk-VCEHU56K.js → chunk-E4UVJKSV.js} +2 -2
  24. package/dist/tsup/chunk-G4ABMAQY.cjs +102 -0
  25. package/dist/tsup/chunk-G4ABMAQY.cjs.map +1 -0
  26. package/dist/tsup/{chunk-ZYLTS2EM.js → chunk-GZVBFXBI.js} +2 -2
  27. package/dist/tsup/{chunk-W6LN7AF5.js → chunk-HPT3I7UU.js} +866 -816
  28. package/dist/tsup/chunk-HPT3I7UU.js.map +1 -0
  29. package/dist/tsup/{chunk-7OUKNSTU.js → chunk-JD54PXWP.js} +17 -14
  30. package/dist/tsup/chunk-JD54PXWP.js.map +1 -0
  31. package/dist/tsup/{chunk-KG3C7MKR.cjs → chunk-K4ENQCC4.cjs} +3 -3
  32. package/dist/tsup/{chunk-KG3C7MKR.cjs.map → chunk-K4ENQCC4.cjs.map} +1 -1
  33. package/dist/tsup/{chunk-WC2PSJWN.js → chunk-PUSQNDJG.js} +2 -2
  34. package/dist/tsup/{chunk-RGQR2J7S.js → chunk-RVP5RUSC.js} +20 -6
  35. package/dist/tsup/chunk-RVP5RUSC.js.map +1 -0
  36. package/dist/tsup/{chunk-TCUI5JFE.cjs → chunk-SAZCNSVY.cjs} +45 -18
  37. package/dist/tsup/chunk-SAZCNSVY.cjs.map +1 -0
  38. package/dist/tsup/{chunk-G75SVQON.js → chunk-SBKRVQS2.js} +9 -5
  39. package/dist/tsup/chunk-SBKRVQS2.js.map +1 -0
  40. package/dist/tsup/{chunk-6P6RA47N.cjs → chunk-TZGUSEIJ.cjs} +14 -10
  41. package/dist/tsup/chunk-TZGUSEIJ.cjs.map +1 -0
  42. package/dist/tsup/{chunk-2K3JMDAN.js → chunk-YQ4XQYPM.js} +40 -13
  43. package/dist/tsup/chunk-YQ4XQYPM.js.map +1 -0
  44. package/dist/tsup/client/mod.cjs +9 -9
  45. package/dist/tsup/client/mod.d.cts +7 -8
  46. package/dist/tsup/client/mod.d.ts +7 -8
  47. package/dist/tsup/client/mod.js +8 -8
  48. package/dist/tsup/common/log.cjs +3 -3
  49. package/dist/tsup/common/log.js +2 -2
  50. package/dist/tsup/common/websocket.cjs +4 -4
  51. package/dist/tsup/common/websocket.js +3 -3
  52. package/dist/tsup/{connection-BLemxi4f.d.ts → conn-DCSQgIlw.d.ts} +1605 -1353
  53. package/dist/tsup/{connection-CpDIydXf.d.cts → conn-DdzHTm2E.d.cts} +1605 -1353
  54. package/dist/tsup/driver-helpers/mod.cjs +31 -5
  55. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  56. package/dist/tsup/driver-helpers/mod.d.cts +7 -8
  57. package/dist/tsup/driver-helpers/mod.d.ts +7 -8
  58. package/dist/tsup/driver-helpers/mod.js +33 -7
  59. package/dist/tsup/driver-test-suite/mod.cjs +317 -222
  60. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  61. package/dist/tsup/driver-test-suite/mod.d.cts +7 -7
  62. package/dist/tsup/driver-test-suite/mod.d.ts +7 -7
  63. package/dist/tsup/driver-test-suite/mod.js +582 -487
  64. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  65. package/dist/tsup/inspector/mod.cjs +16 -6
  66. package/dist/tsup/inspector/mod.cjs.map +1 -1
  67. package/dist/tsup/inspector/mod.d.cts +34 -7
  68. package/dist/tsup/inspector/mod.d.ts +34 -7
  69. package/dist/tsup/inspector/mod.js +17 -7
  70. package/dist/tsup/mod.cjs +10 -20
  71. package/dist/tsup/mod.cjs.map +1 -1
  72. package/dist/tsup/mod.d.cts +9 -7
  73. package/dist/tsup/mod.d.ts +9 -7
  74. package/dist/tsup/mod.js +9 -19
  75. package/dist/tsup/test/mod.cjs +11 -11
  76. package/dist/tsup/test/mod.d.cts +6 -7
  77. package/dist/tsup/test/mod.d.ts +6 -7
  78. package/dist/tsup/test/mod.js +10 -10
  79. package/dist/tsup/utils.cjs +4 -2
  80. package/dist/tsup/utils.cjs.map +1 -1
  81. package/dist/tsup/utils.d.cts +11 -1
  82. package/dist/tsup/utils.d.ts +11 -1
  83. package/dist/tsup/utils.js +3 -1
  84. package/package.json +8 -4
  85. package/src/actor/action.ts +1 -1
  86. package/src/actor/config.ts +1 -1
  87. package/src/actor/conn-drivers.ts +205 -0
  88. package/src/actor/conn-socket.ts +6 -0
  89. package/src/actor/{connection.ts → conn.ts} +78 -84
  90. package/src/actor/context.ts +1 -1
  91. package/src/actor/driver.ts +4 -43
  92. package/src/actor/instance.ts +162 -86
  93. package/src/actor/mod.ts +1 -11
  94. package/src/actor/persisted.ts +2 -5
  95. package/src/actor/protocol/old.ts +1 -1
  96. package/src/actor/router-endpoints.ts +142 -106
  97. package/src/actor/router.ts +81 -45
  98. package/src/actor/utils.ts +5 -1
  99. package/src/client/actor-conn.ts +154 -23
  100. package/src/client/client.ts +1 -1
  101. package/src/client/config.ts +7 -0
  102. package/src/common/actor-router-consts.ts +29 -8
  103. package/src/common/router.ts +2 -1
  104. package/src/common/versioned-data.ts +5 -5
  105. package/src/driver-helpers/mod.ts +14 -1
  106. package/src/driver-test-suite/mod.ts +11 -2
  107. package/src/driver-test-suite/test-inline-client-driver.ts +36 -18
  108. package/src/driver-test-suite/tests/actor-conn-state.ts +66 -22
  109. package/src/driver-test-suite/tests/actor-conn.ts +65 -126
  110. package/src/driver-test-suite/tests/actor-reconnect.ts +160 -0
  111. package/src/driver-test-suite/tests/actor-sleep.ts +0 -1
  112. package/src/driver-test-suite/tests/raw-websocket.ts +0 -35
  113. package/src/driver-test-suite/utils.ts +3 -3
  114. package/src/drivers/default.ts +8 -7
  115. package/src/drivers/engine/actor-driver.ts +53 -31
  116. package/src/drivers/engine/config.ts +4 -0
  117. package/src/drivers/file-system/actor.ts +0 -6
  118. package/src/drivers/file-system/global-state.ts +3 -14
  119. package/src/drivers/file-system/manager.ts +12 -8
  120. package/src/inspector/actor.ts +4 -3
  121. package/src/inspector/config.ts +10 -1
  122. package/src/inspector/mod.ts +1 -0
  123. package/src/inspector/utils.ts +23 -4
  124. package/src/manager/driver.ts +11 -1
  125. package/src/manager/gateway.ts +407 -0
  126. package/src/manager/router.ts +269 -468
  127. package/src/manager-api/actors.ts +61 -0
  128. package/src/manager-api/common.ts +4 -0
  129. package/src/mod.ts +1 -1
  130. package/src/registry/mod.ts +119 -10
  131. package/src/remote-manager-driver/actor-http-client.ts +30 -19
  132. package/src/remote-manager-driver/actor-websocket-client.ts +43 -16
  133. package/src/remote-manager-driver/api-endpoints.ts +19 -21
  134. package/src/remote-manager-driver/api-utils.ts +10 -1
  135. package/src/remote-manager-driver/mod.ts +51 -48
  136. package/src/remote-manager-driver/ws-proxy.ts +2 -9
  137. package/src/test/mod.ts +6 -2
  138. package/src/utils.ts +21 -2
  139. package/dist/tsup/actor-router-consts-BK6arfy8.d.cts +0 -17
  140. package/dist/tsup/actor-router-consts-BK6arfy8.d.ts +0 -17
  141. package/dist/tsup/chunk-2K3JMDAN.js.map +0 -1
  142. package/dist/tsup/chunk-42I3OZ3Q.js +0 -15
  143. package/dist/tsup/chunk-42I3OZ3Q.js.map +0 -1
  144. package/dist/tsup/chunk-4CKHQRXG.js.map +0 -1
  145. package/dist/tsup/chunk-5JBFVV4C.cjs.map +0 -1
  146. package/dist/tsup/chunk-6P6RA47N.cjs.map +0 -1
  147. package/dist/tsup/chunk-7OUKNSTU.js.map +0 -1
  148. package/dist/tsup/chunk-G75SVQON.js.map +0 -1
  149. package/dist/tsup/chunk-KUPQZYUQ.cjs +0 -15
  150. package/dist/tsup/chunk-KUPQZYUQ.cjs.map +0 -1
  151. package/dist/tsup/chunk-NTCUGYSD.cjs.map +0 -1
  152. package/dist/tsup/chunk-RGQR2J7S.js.map +0 -1
  153. package/dist/tsup/chunk-TCUI5JFE.cjs.map +0 -1
  154. package/dist/tsup/chunk-TWGATZ3X.cjs.map +0 -1
  155. package/dist/tsup/chunk-UFWAK3X2.cjs.map +0 -1
  156. package/dist/tsup/chunk-W6LN7AF5.js.map +0 -1
  157. package/dist/tsup/common-CXCe7s6i.d.cts +0 -218
  158. package/dist/tsup/common-CXCe7s6i.d.ts +0 -218
  159. package/src/actor/generic-conn-driver.ts +0 -246
  160. package/src/common/fake-event-source.ts +0 -267
  161. package/src/manager-api/routes/actors-create.ts +0 -16
  162. package/src/manager-api/routes/actors-delete.ts +0 -4
  163. package/src/manager-api/routes/actors-get-by-id.ts +0 -7
  164. package/src/manager-api/routes/actors-get-or-create-by-id.ts +0 -29
  165. package/src/manager-api/routes/actors-get.ts +0 -7
  166. package/src/manager-api/routes/common.ts +0 -18
  167. /package/dist/tsup/{chunk-VPV4MWXR.js.map → chunk-7N56ZUC7.js.map} +0 -0
  168. /package/dist/tsup/{chunk-VCEHU56K.js.map → chunk-E4UVJKSV.js.map} +0 -0
  169. /package/dist/tsup/{chunk-ZYLTS2EM.js.map → chunk-GZVBFXBI.js.map} +0 -0
  170. /package/dist/tsup/{chunk-WC2PSJWN.js.map → chunk-PUSQNDJG.js.map} +0 -0
@@ -0,0 +1,407 @@
1
+ import type { Context as HonoContext, Next } from "hono";
2
+ import type { WSContext } from "hono/ws";
3
+ import { MissingActorHeader, WebSocketsNotEnabled } from "@/actor/errors";
4
+ import type { Encoding, Transport } from "@/client/mod";
5
+ import {
6
+ HEADER_RIVET_ACTOR,
7
+ HEADER_RIVET_TARGET,
8
+ WS_PROTOCOL_ACTOR,
9
+ WS_PROTOCOL_CONN_ID,
10
+ WS_PROTOCOL_CONN_PARAMS,
11
+ WS_PROTOCOL_CONN_TOKEN,
12
+ WS_PROTOCOL_ENCODING,
13
+ WS_PROTOCOL_TARGET,
14
+ } from "@/common/actor-router-consts";
15
+ import { deconstructError, noopNext } from "@/common/utils";
16
+ import type { UniversalWebSocket, UpgradeWebSocketArgs } from "@/mod";
17
+ import type { RunConfig } from "@/registry/run-config";
18
+ import { promiseWithResolvers, stringifyError } from "@/utils";
19
+ import type { ManagerDriver } from "./driver";
20
+ import { logger } from "./log";
21
+
22
+ /**
23
+ * Provides an endpoint to connect to individual actors.
24
+ *
25
+ * Routes requests based on the Upgrade header:
26
+ * - WebSocket requests: Uses sec-websocket-protocol for routing (target.actor, actor.{id})
27
+ * - HTTP requests: Uses x-rivet-target and x-rivet-actor headers for routing
28
+ */
29
+ export async function actorGateway(
30
+ runConfig: RunConfig,
31
+ managerDriver: ManagerDriver,
32
+ c: HonoContext,
33
+ next: Next,
34
+ ) {
35
+ // Skip test routes - let them be handled by their specific handlers
36
+ if (c.req.path.startsWith("/.test/")) {
37
+ return next();
38
+ }
39
+
40
+ // Check if this is a WebSocket upgrade request
41
+ if (c.req.header("upgrade") === "websocket") {
42
+ return await handleWebSocketGateway(runConfig, managerDriver, c);
43
+ }
44
+
45
+ // Handle regular HTTP requests
46
+ return await handleHttpGateway(managerDriver, c, next);
47
+ }
48
+
49
+ /**
50
+ * Handle WebSocket requests using sec-websocket-protocol for routing
51
+ */
52
+ async function handleWebSocketGateway(
53
+ runConfig: RunConfig,
54
+ managerDriver: ManagerDriver,
55
+ c: HonoContext,
56
+ ) {
57
+ const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
58
+ if (!upgradeWebSocket) {
59
+ throw new WebSocketsNotEnabled();
60
+ }
61
+
62
+ // Parse configuration from Sec-WebSocket-Protocol header
63
+ const protocols = c.req.header("sec-websocket-protocol");
64
+ let target: string | undefined;
65
+ let actorId: string | undefined;
66
+ let encodingRaw: string | undefined;
67
+ let connParamsRaw: string | undefined;
68
+ let connIdRaw: string | undefined;
69
+ let connTokenRaw: string | undefined;
70
+
71
+ if (protocols) {
72
+ const protocolList = protocols.split(",").map((p) => p.trim());
73
+ for (const protocol of protocolList) {
74
+ if (protocol.startsWith(WS_PROTOCOL_TARGET)) {
75
+ target = protocol.substring(WS_PROTOCOL_TARGET.length);
76
+ } else if (protocol.startsWith(WS_PROTOCOL_ACTOR)) {
77
+ actorId = protocol.substring(WS_PROTOCOL_ACTOR.length);
78
+ } else if (protocol.startsWith(WS_PROTOCOL_ENCODING)) {
79
+ encodingRaw = protocol.substring(WS_PROTOCOL_ENCODING.length);
80
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_PARAMS)) {
81
+ connParamsRaw = decodeURIComponent(
82
+ protocol.substring(WS_PROTOCOL_CONN_PARAMS.length),
83
+ );
84
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_ID)) {
85
+ connIdRaw = protocol.substring(WS_PROTOCOL_CONN_ID.length);
86
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_TOKEN)) {
87
+ connTokenRaw = protocol.substring(WS_PROTOCOL_CONN_TOKEN.length);
88
+ }
89
+ }
90
+ }
91
+
92
+ if (target !== "actor") {
93
+ return c.text("WebSocket upgrade requires target.actor protocol", 400);
94
+ }
95
+
96
+ if (!actorId) {
97
+ throw new MissingActorHeader();
98
+ }
99
+
100
+ logger().debug({
101
+ msg: "proxying websocket to actor",
102
+ actorId,
103
+ path: c.req.path,
104
+ encoding: encodingRaw,
105
+ });
106
+
107
+ const encoding = encodingRaw || "json";
108
+ const connParams = connParamsRaw ? JSON.parse(connParamsRaw) : undefined;
109
+
110
+ // Include query string if present
111
+ const pathWithQuery = c.req.url.includes("?")
112
+ ? c.req.path + c.req.url.substring(c.req.url.indexOf("?"))
113
+ : c.req.path;
114
+
115
+ return await managerDriver.proxyWebSocket(
116
+ c,
117
+ pathWithQuery,
118
+ actorId,
119
+ encoding as any, // Will be validated by driver
120
+ connParams,
121
+ connIdRaw,
122
+ connTokenRaw,
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Handle HTTP requests using x-rivet headers for routing
128
+ */
129
+ async function handleHttpGateway(
130
+ managerDriver: ManagerDriver,
131
+ c: HonoContext,
132
+ next: Next,
133
+ ) {
134
+ const target = c.req.header(HEADER_RIVET_TARGET);
135
+ const actorId = c.req.header(HEADER_RIVET_ACTOR);
136
+
137
+ if (target !== "actor") {
138
+ return next();
139
+ }
140
+
141
+ if (!actorId) {
142
+ throw new MissingActorHeader();
143
+ }
144
+
145
+ logger().debug({
146
+ msg: "proxying request to actor",
147
+ actorId,
148
+ path: c.req.path,
149
+ method: c.req.method,
150
+ });
151
+
152
+ // Preserve all headers except the routing headers
153
+ const proxyHeaders = new Headers(c.req.raw.headers);
154
+ proxyHeaders.delete(HEADER_RIVET_TARGET);
155
+ proxyHeaders.delete(HEADER_RIVET_ACTOR);
156
+
157
+ // Build the proxy request with the actor URL format
158
+ const url = new URL(c.req.url);
159
+ const proxyUrl = new URL(`http://actor${url.pathname}${url.search}`);
160
+
161
+ const proxyRequest = new Request(proxyUrl, {
162
+ method: c.req.raw.method,
163
+ headers: proxyHeaders,
164
+ body: c.req.raw.body,
165
+ signal: c.req.raw.signal,
166
+ });
167
+
168
+ return await managerDriver.proxyRequest(c, proxyRequest, actorId);
169
+ }
170
+
171
+ /**
172
+ * Creates a WebSocket proxy for test endpoints that forwards messages between server and client WebSockets
173
+ */
174
+ export async function createTestWebSocketProxy(
175
+ clientWsPromise: Promise<UniversalWebSocket>,
176
+ ): Promise<UpgradeWebSocketArgs> {
177
+ // Store a reference to the resolved WebSocket
178
+ let clientWs: UniversalWebSocket | null = null;
179
+ const {
180
+ promise: serverWsPromise,
181
+ resolve: serverWsResolve,
182
+ reject: serverWsReject,
183
+ } = promiseWithResolvers<WSContext>();
184
+ try {
185
+ // Resolve the client WebSocket promise
186
+ logger().debug({ msg: "awaiting client websocket promise" });
187
+ const ws = await clientWsPromise;
188
+ clientWs = ws;
189
+ logger().debug({
190
+ msg: "client websocket promise resolved",
191
+ constructor: ws?.constructor.name,
192
+ });
193
+
194
+ // Wait for ws to open
195
+ await new Promise<void>((resolve, reject) => {
196
+ const onOpen = () => {
197
+ logger().debug({ msg: "test websocket connection to actor opened" });
198
+ resolve();
199
+ };
200
+ const onError = (error: any) => {
201
+ logger().error({ msg: "test websocket connection failed", error });
202
+ reject(
203
+ new Error(`Failed to open WebSocket: ${error.message || error}`),
204
+ );
205
+ serverWsReject();
206
+ };
207
+
208
+ ws.addEventListener("open", onOpen);
209
+
210
+ ws.addEventListener("error", onError);
211
+
212
+ ws.addEventListener("message", async (clientEvt: MessageEvent) => {
213
+ const serverWs = await serverWsPromise;
214
+
215
+ logger().debug({
216
+ msg: `test websocket connection message from client`,
217
+ dataType: typeof clientEvt.data,
218
+ isBlob: clientEvt.data instanceof Blob,
219
+ isArrayBuffer: clientEvt.data instanceof ArrayBuffer,
220
+ dataConstructor: clientEvt.data?.constructor?.name,
221
+ dataStr:
222
+ typeof clientEvt.data === "string"
223
+ ? clientEvt.data.substring(0, 100)
224
+ : undefined,
225
+ });
226
+
227
+ if (serverWs.readyState === 1) {
228
+ // OPEN
229
+ // Handle Blob data
230
+ if (clientEvt.data instanceof Blob) {
231
+ clientEvt.data
232
+ .arrayBuffer()
233
+ .then((buffer) => {
234
+ logger().debug({
235
+ msg: "converted client blob to arraybuffer, sending to server",
236
+ bufferSize: buffer.byteLength,
237
+ });
238
+ serverWs.send(buffer as any);
239
+ })
240
+ .catch((error) => {
241
+ logger().error({
242
+ msg: "failed to convert blob to arraybuffer",
243
+ error,
244
+ });
245
+ });
246
+ } else {
247
+ logger().debug({
248
+ msg: "sending client data directly to server",
249
+ dataType: typeof clientEvt.data,
250
+ dataLength:
251
+ typeof clientEvt.data === "string"
252
+ ? clientEvt.data.length
253
+ : undefined,
254
+ });
255
+ serverWs.send(clientEvt.data as any);
256
+ }
257
+ }
258
+ });
259
+
260
+ ws.addEventListener("close", async (clientEvt: any) => {
261
+ const serverWs = await serverWsPromise;
262
+
263
+ logger().debug({
264
+ msg: `test websocket connection closed`,
265
+ });
266
+
267
+ if (serverWs.readyState !== 3) {
268
+ // Not CLOSED
269
+ serverWs.close(clientEvt.code, clientEvt.reason);
270
+ }
271
+ });
272
+
273
+ ws.addEventListener("error", async () => {
274
+ const serverWs = await serverWsPromise;
275
+
276
+ logger().debug({
277
+ msg: `test websocket connection error`,
278
+ });
279
+
280
+ if (serverWs.readyState !== 3) {
281
+ // Not CLOSED
282
+ serverWs.close(1011, "Error in client websocket");
283
+ }
284
+ });
285
+ });
286
+ } catch (error) {
287
+ logger().error({
288
+ msg: `failed to establish client websocket connection`,
289
+ error,
290
+ });
291
+ return {
292
+ onOpen: (_evt, serverWs) => {
293
+ serverWs.close(1011, "Failed to establish connection");
294
+ },
295
+ onMessage: () => {},
296
+ onError: () => {},
297
+ onClose: () => {},
298
+ };
299
+ }
300
+
301
+ // Create WebSocket proxy handlers to relay messages between client and server
302
+ return {
303
+ onOpen: (_evt: any, serverWs: WSContext) => {
304
+ logger().debug({
305
+ msg: `test websocket connection from client opened`,
306
+ });
307
+
308
+ // Check WebSocket type
309
+ logger().debug({
310
+ msg: "clientWs info",
311
+ constructor: clientWs.constructor.name,
312
+ hasAddEventListener: typeof clientWs.addEventListener === "function",
313
+ readyState: clientWs.readyState,
314
+ });
315
+
316
+ serverWsResolve(serverWs);
317
+ },
318
+ onMessage: (evt: { data: any }) => {
319
+ logger().debug({
320
+ msg: "received message from server",
321
+ dataType: typeof evt.data,
322
+ isBlob: evt.data instanceof Blob,
323
+ isArrayBuffer: evt.data instanceof ArrayBuffer,
324
+ dataConstructor: evt.data?.constructor?.name,
325
+ dataStr:
326
+ typeof evt.data === "string" ? evt.data.substring(0, 100) : undefined,
327
+ });
328
+
329
+ // Forward messages from server websocket to client websocket
330
+ if (clientWs.readyState === 1) {
331
+ // OPEN
332
+ // Handle Blob data
333
+ if (evt.data instanceof Blob) {
334
+ evt.data
335
+ .arrayBuffer()
336
+ .then((buffer) => {
337
+ logger().debug({
338
+ msg: "converted blob to arraybuffer, sending",
339
+ bufferSize: buffer.byteLength,
340
+ });
341
+ clientWs.send(buffer);
342
+ })
343
+ .catch((error) => {
344
+ logger().error({
345
+ msg: "failed to convert blob to arraybuffer",
346
+ error,
347
+ });
348
+ });
349
+ } else {
350
+ logger().debug({
351
+ msg: "sending data directly",
352
+ dataType: typeof evt.data,
353
+ dataLength:
354
+ typeof evt.data === "string" ? evt.data.length : undefined,
355
+ });
356
+ clientWs.send(evt.data);
357
+ }
358
+ }
359
+ },
360
+ onClose: (
361
+ event: {
362
+ wasClean: boolean;
363
+ code: number;
364
+ reason: string;
365
+ },
366
+ serverWs: WSContext,
367
+ ) => {
368
+ logger().debug({
369
+ msg: `server websocket closed`,
370
+ wasClean: event.wasClean,
371
+ code: event.code,
372
+ reason: event.reason,
373
+ });
374
+
375
+ // HACK: Close socket in order to fix bug with Cloudflare leaving WS in closing state
376
+ // https://github.com/cloudflare/workerd/issues/2569
377
+ serverWs.close(1000, "hack_force_close");
378
+
379
+ // Close the client websocket when the server websocket closes
380
+ if (
381
+ clientWs &&
382
+ clientWs.readyState !== clientWs.CLOSED &&
383
+ clientWs.readyState !== clientWs.CLOSING
384
+ ) {
385
+ // Don't pass code/message since this may affect how close events are triggered
386
+ clientWs.close(1000, event.reason);
387
+ }
388
+ },
389
+ onError: (error: unknown) => {
390
+ logger().error({
391
+ msg: `error in server websocket`,
392
+ error,
393
+ });
394
+
395
+ // Close the client websocket on error
396
+ if (
397
+ clientWs &&
398
+ clientWs.readyState !== clientWs.CLOSED &&
399
+ clientWs.readyState !== clientWs.CLOSING
400
+ ) {
401
+ clientWs.close(1011, "Error in server websocket");
402
+ }
403
+
404
+ serverWsReject();
405
+ },
406
+ };
407
+ }