rivetkit 2.0.6 → 2.0.7

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-7OUKNSTU.js → chunk-2NL3KGJ7.js} +17 -14
  5. package/dist/tsup/chunk-2NL3KGJ7.js.map +1 -0
  6. package/dist/tsup/{chunk-6P6RA47N.cjs → chunk-3ALZ7EGX.cjs} +14 -10
  7. package/dist/tsup/chunk-3ALZ7EGX.cjs.map +1 -0
  8. package/dist/tsup/chunk-4EXJ4ITR.cjs +102 -0
  9. package/dist/tsup/chunk-4EXJ4ITR.cjs.map +1 -0
  10. package/dist/tsup/{chunk-ZYLTS2EM.js → chunk-54MAHBLL.js} +2 -2
  11. package/dist/tsup/{chunk-NTCUGYSD.cjs → chunk-7OOBMCQI.cjs} +34 -31
  12. package/dist/tsup/chunk-7OOBMCQI.cjs.map +1 -0
  13. package/dist/tsup/{chunk-VCEHU56K.js → chunk-B6N6VM37.js} +2 -2
  14. package/dist/tsup/{chunk-VPV4MWXR.js → chunk-DIHKN7NM.js} +3 -3
  15. package/dist/tsup/{chunk-MRRT2CZD.cjs → chunk-ETDWYT2P.cjs} +7 -7
  16. package/dist/tsup/{chunk-MRRT2CZD.cjs.map → chunk-ETDWYT2P.cjs.map} +1 -1
  17. package/dist/tsup/{chunk-TWGATZ3X.cjs → chunk-F7YL5G7Q.cjs} +922 -872
  18. package/dist/tsup/chunk-F7YL5G7Q.cjs.map +1 -0
  19. package/dist/tsup/{chunk-UTI5NCES.cjs → chunk-GWJTWY3G.cjs} +6 -6
  20. package/dist/tsup/{chunk-UTI5NCES.cjs.map → chunk-GWJTWY3G.cjs.map} +1 -1
  21. package/dist/tsup/{chunk-W6LN7AF5.js → chunk-KHRZPP5T.js} +866 -816
  22. package/dist/tsup/chunk-KHRZPP5T.js.map +1 -0
  23. package/dist/tsup/{chunk-5JBFVV4C.cjs → chunk-LXAVET4A.cjs} +21 -7
  24. package/dist/tsup/chunk-LXAVET4A.cjs.map +1 -0
  25. package/dist/tsup/{chunk-TCUI5JFE.cjs → chunk-NDCVQZBS.cjs} +45 -18
  26. package/dist/tsup/chunk-NDCVQZBS.cjs.map +1 -0
  27. package/dist/tsup/{chunk-4CKHQRXG.js → chunk-NII4KKHD.js} +515 -240
  28. package/dist/tsup/chunk-NII4KKHD.js.map +1 -0
  29. package/dist/tsup/{chunk-2K3JMDAN.js → chunk-NRELKXIX.js} +40 -13
  30. package/dist/tsup/chunk-NRELKXIX.js.map +1 -0
  31. package/dist/tsup/{chunk-UFWAK3X2.cjs → chunk-NUA6LOOJ.cjs} +660 -385
  32. package/dist/tsup/chunk-NUA6LOOJ.cjs.map +1 -0
  33. package/dist/tsup/{chunk-DIAYNQTE.cjs → chunk-OSK2VSJF.cjs} +12 -12
  34. package/dist/tsup/{chunk-DIAYNQTE.cjs.map → chunk-OSK2VSJF.cjs.map} +1 -1
  35. package/dist/tsup/chunk-PD6HCAJE.js +102 -0
  36. package/dist/tsup/chunk-PD6HCAJE.js.map +1 -0
  37. package/dist/tsup/{chunk-RGQR2J7S.js → chunk-RLBM6D4L.js} +20 -6
  38. package/dist/tsup/chunk-RLBM6D4L.js.map +1 -0
  39. package/dist/tsup/{chunk-KG3C7MKR.cjs → chunk-VAF63BEI.cjs} +3 -3
  40. package/dist/tsup/{chunk-KG3C7MKR.cjs.map → chunk-VAF63BEI.cjs.map} +1 -1
  41. package/dist/tsup/{chunk-G75SVQON.js → chunk-WAT5AE7S.js} +9 -5
  42. package/dist/tsup/chunk-WAT5AE7S.js.map +1 -0
  43. package/dist/tsup/{chunk-WC2PSJWN.js → chunk-YL4VZMMT.js} +2 -2
  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-ZYLTS2EM.js.map → chunk-54MAHBLL.js.map} +0 -0
  168. /package/dist/tsup/{chunk-VCEHU56K.js.map → chunk-B6N6VM37.js.map} +0 -0
  169. /package/dist/tsup/{chunk-VPV4MWXR.js.map → chunk-DIHKN7NM.js.map} +0 -0
  170. /package/dist/tsup/{chunk-WC2PSJWN.js.map → chunk-YL4VZMMT.js.map} +0 -0
@@ -2,22 +2,24 @@ import * as cbor from "cbor-x";
2
2
  import type { Context as HonoContext, HonoRequest } from "hono";
3
3
  import { type SSEStreamingApi, streamSSE } from "hono/streaming";
4
4
  import type { WSContext } from "hono/ws";
5
+ import invariant from "invariant";
5
6
  import { ActionContext } from "@/actor/action";
6
- import type { AnyConn } from "@/actor/connection";
7
+ import type { AnyConn } from "@/actor/conn";
7
8
  import {
8
- CONNECTION_DRIVER_HTTP,
9
- CONNECTION_DRIVER_SSE,
10
- CONNECTION_DRIVER_WEBSOCKET,
11
9
  generateConnId,
10
+ generateConnSocketId,
12
11
  generateConnToken,
13
- } from "@/actor/connection";
12
+ } from "@/actor/conn";
13
+ import { ConnDriverKind } from "@/actor/conn-drivers";
14
14
  import * as errors from "@/actor/errors";
15
15
  import type { AnyActorInstance } from "@/actor/instance";
16
16
  import type { InputData } from "@/actor/protocol/serde";
17
17
  import { type Encoding, EncodingSchema } from "@/actor/protocol/serde";
18
18
  import {
19
19
  HEADER_ACTOR_QUERY,
20
+ HEADER_CONN_ID,
20
21
  HEADER_CONN_PARAMS,
22
+ HEADER_CONN_TOKEN,
21
23
  HEADER_ENCODING,
22
24
  } from "@/common/actor-router-consts";
23
25
  import type { UpgradeWebSocketArgs } from "@/common/inline-websocket-adapter2";
@@ -36,22 +38,18 @@ import {
36
38
  deserializeWithEncoding,
37
39
  serializeWithEncoding,
38
40
  } from "@/serde";
39
- import { bufferToArrayBuffer } from "@/utils";
41
+ import { bufferToArrayBuffer, promiseWithResolvers } from "@/utils";
40
42
  import type { ActorDriver } from "./driver";
41
- import type {
42
- GenericHttpDriverState,
43
- GenericSseDriverState,
44
- GenericWebSocketDriverState,
45
- } from "./generic-conn-driver";
46
43
  import { loggerWithoutContext } from "./log";
47
44
  import { parseMessage } from "./protocol/old";
48
45
 
46
+ export const SSE_PING_INTERVAL = 1000;
47
+
49
48
  export interface ConnectWebSocketOpts {
50
49
  req?: HonoRequest;
51
50
  encoding: Encoding;
52
51
  actorId: string;
53
52
  params: unknown;
54
- authData: unknown;
55
53
  }
56
54
 
57
55
  export interface ConnectWebSocketOutput {
@@ -65,7 +63,6 @@ export interface ConnectSseOpts {
65
63
  encoding: Encoding;
66
64
  params: unknown;
67
65
  actorId: string;
68
- authData: unknown;
69
66
  }
70
67
 
71
68
  export interface ConnectSseOutput {
@@ -79,7 +76,6 @@ export interface ActionOpts {
79
76
  actionName: string;
80
77
  actionArgs: unknown[];
81
78
  actorId: string;
82
- authData: unknown;
83
79
  }
84
80
 
85
81
  export interface ActionOutput {
@@ -97,14 +93,12 @@ export interface ConnsMessageOpts {
97
93
  export interface FetchOpts {
98
94
  request: Request;
99
95
  actorId: string;
100
- authData: unknown;
101
96
  }
102
97
 
103
98
  export interface WebSocketOpts {
104
99
  request: Request;
105
100
  websocket: UniversalWebSocket;
106
101
  actorId: string;
107
- authData: unknown;
108
102
  }
109
103
 
110
104
  /**
@@ -117,7 +111,8 @@ export async function handleWebSocketConnect(
117
111
  actorId: string,
118
112
  encoding: Encoding,
119
113
  parameters: unknown,
120
- authData: unknown,
114
+ connId?: string,
115
+ connToken?: string,
121
116
  ): Promise<UpgradeWebSocketArgs> {
122
117
  const exposeInternalError = req ? getRequestExposeInternalError(req) : false;
123
118
 
@@ -126,7 +121,7 @@ export async function handleWebSocketConnect(
126
121
  promise: handlersPromise,
127
122
  resolve: handlersResolve,
128
123
  reject: handlersReject,
129
- } = Promise.withResolvers<{
124
+ } = promiseWithResolvers<{
130
125
  conn: AnyConn;
131
126
  actor: AnyActorInstance;
132
127
  connId: string;
@@ -158,40 +153,47 @@ export async function handleWebSocketConnect(
158
153
  };
159
154
  }
160
155
 
156
+ // Promise used to wait for the websocket close in `disconnect`
157
+ const closePromise = promiseWithResolvers<void>();
158
+ const socketId = generateConnSocketId();
159
+
161
160
  return {
162
161
  onOpen: (_evt: any, ws: WSContext) => {
163
- actor.rLog.debug("websocket open");
162
+ actor.rLog.debug("actor websocket open");
164
163
 
165
164
  // Run async operations in background
166
165
  (async () => {
167
166
  try {
168
- const connId = generateConnId();
169
- const connToken = generateConnToken();
170
- const connState = await actor.prepareConn(parameters, req);
171
-
172
- // Save socket
173
- const connGlobalState =
174
- actorDriver.getGenericConnGlobalState(actorId);
175
- connGlobalState.websockets.set(connId, ws);
167
+ let conn: AnyConn;
168
+
169
+ // Create or reconnect connection
176
170
  actor.rLog.debug({
177
- msg: "registered websocket for conn",
171
+ msg: connId
172
+ ? "websocket reconnection attempt"
173
+ : "new websocket connection",
174
+ connId,
178
175
  actorId,
179
- totalCount: connGlobalState.websockets.size,
180
176
  });
181
177
 
182
- // Create connection
183
- const conn = await actor.createConn(
178
+ conn = await actor.createConn(
179
+ {
180
+ socketId,
181
+ driverState: {
182
+ [ConnDriverKind.WEBSOCKET]: {
183
+ encoding,
184
+ websocket: ws,
185
+ closePromise,
186
+ },
187
+ },
188
+ },
189
+ parameters,
190
+ req,
184
191
  connId,
185
192
  connToken,
186
- parameters,
187
- connState,
188
- CONNECTION_DRIVER_WEBSOCKET,
189
- { encoding } satisfies GenericWebSocketDriverState,
190
- authData,
191
193
  );
192
194
 
193
195
  // Unblock other handlers
194
- handlersResolve({ conn, actor, connId });
196
+ handlersResolve({ conn, actor, connId: conn.id });
195
197
  } catch (error) {
196
198
  handlersReject(error);
197
199
 
@@ -263,6 +265,10 @@ export async function handleWebSocketConnect(
263
265
  },
264
266
  ws: WSContext,
265
267
  ) => {
268
+ handlersReject(`WebSocket closed (${event.code}): ${event.reason}`);
269
+
270
+ closePromise.resolve();
271
+
266
272
  if (event.wasClean) {
267
273
  actor.rLog.info({
268
274
  msg: "websocket closed",
@@ -285,24 +291,8 @@ export async function handleWebSocketConnect(
285
291
 
286
292
  // Handle cleanup asynchronously
287
293
  handlersPromise
288
- .then(({ conn, actor, connId }) => {
289
- const connGlobalState =
290
- actorDriver.getGenericConnGlobalState(actorId);
291
- const didDelete = connGlobalState.websockets.delete(connId);
292
- if (didDelete) {
293
- actor.rLog.info({
294
- msg: "removing websocket for conn",
295
- totalCount: connGlobalState.websockets.size,
296
- });
297
- } else {
298
- actor.rLog.warn({
299
- msg: "websocket does not exist for conn",
300
- actorId,
301
- totalCount: connGlobalState.websockets.size,
302
- });
303
- }
304
-
305
- actor.__removeConn(conn);
294
+ .then(({ conn, actor }) => {
295
+ actor.__connDisconnected(conn, event.wasClean, socketId);
306
296
  })
307
297
  .catch((error) => {
308
298
  deconstructError(
@@ -337,46 +327,50 @@ export async function handleSseConnect(
337
327
  _runConfig: RunConfig,
338
328
  actorDriver: ActorDriver,
339
329
  actorId: string,
340
- authData: unknown,
341
330
  ) {
331
+ c.header("Content-Encoding", "Identity");
332
+
342
333
  const encoding = getRequestEncoding(c.req);
343
334
  const parameters = getRequestConnParams(c.req);
335
+ const socketId = generateConnSocketId();
336
+
337
+ // Check for reconnection parameters
338
+ const connId = c.req.header(HEADER_CONN_ID);
339
+ const connToken = c.req.header(HEADER_CONN_TOKEN);
344
340
 
345
341
  // Return the main handler with all async work inside
346
342
  return streamSSE(c, async (stream) => {
347
343
  let actor: AnyActorInstance | undefined;
348
- let connId: string | undefined;
349
- let connToken: string | undefined;
350
- let connState: unknown;
351
344
  let conn: AnyConn | undefined;
352
345
 
353
346
  try {
354
347
  // Do all async work inside the handler
355
348
  actor = await actorDriver.loadActor(actorId);
356
- connId = generateConnId();
357
- connToken = generateConnToken();
358
- connState = await actor.prepareConn(parameters, c.req.raw);
359
349
 
360
- actor.rLog.debug("sse open");
361
-
362
- // Save stream
363
- actorDriver
364
- .getGenericConnGlobalState(actorId)
365
- .sseStreams.set(connId, stream);
350
+ // Create or reconnect connection
351
+ actor.rLog.debug({
352
+ msg: connId ? "sse reconnection attempt" : "sse open",
353
+ connId,
354
+ });
366
355
 
367
- // Create connection
368
356
  conn = await actor.createConn(
357
+ {
358
+ socketId,
359
+ driverState: {
360
+ [ConnDriverKind.SSE]: {
361
+ encoding,
362
+ stream: stream,
363
+ },
364
+ },
365
+ },
366
+ parameters,
367
+ c.req.raw,
369
368
  connId,
370
369
  connToken,
371
- parameters,
372
- connState,
373
- CONNECTION_DRIVER_SSE,
374
- { encoding } satisfies GenericSseDriverState,
375
- authData,
376
370
  );
377
371
 
378
372
  // Wait for close
379
- const abortResolver = Promise.withResolvers();
373
+ const abortResolver = promiseWithResolvers();
380
374
 
381
375
  // HACK: This is required so the abort handler below works
382
376
  //
@@ -385,18 +379,14 @@ export async function handleSseConnect(
385
379
 
386
380
  // Handle stream abort (when client closes the connection)
387
381
  c.req.raw.signal.addEventListener("abort", async () => {
388
- const rLog = actor?.rLog ?? loggerWithoutContext();
382
+ invariant(actor, "actor should exist");
383
+ const rLog = actor.rLog ?? loggerWithoutContext();
389
384
  try {
390
385
  rLog.debug("sse stream aborted");
391
386
 
392
387
  // Cleanup
393
- if (connId) {
394
- actorDriver
395
- .getGenericConnGlobalState(actorId)
396
- .sseStreams.delete(connId);
397
- }
398
- if (conn && actor) {
399
- actor.__removeConn(conn);
388
+ if (conn) {
389
+ actor.__connDisconnected(conn, false, socketId);
400
390
  }
401
391
 
402
392
  abortResolver.resolve(undefined);
@@ -406,24 +396,33 @@ export async function handleSseConnect(
406
396
  }
407
397
  });
408
398
 
409
- // HACK: Will throw if not configured
410
- try {
411
- c.executionCtx.waitUntil(abortResolver.promise);
412
- } catch {}
399
+ // // HACK: Will throw if not configured
400
+ // try {
401
+ // c.executionCtx.waitUntil(abortResolver.promise);
402
+ // } catch {}
403
+
404
+ // Send ping every second to keep the connection alive
405
+ //
406
+ // NOTE: This is required on Cloudflare Workers in order to detect when the connection is closed
407
+ while (true) {
408
+ if (stream.closed || stream.aborted) {
409
+ actor?.rLog.debug({
410
+ msg: "sse stream closed",
411
+ closed: stream.closed,
412
+ aborted: stream.aborted,
413
+ });
414
+ break;
415
+ }
413
416
 
414
- // Wait until connection aborted
415
- await abortResolver.promise;
417
+ await stream.writeSSE({ event: "ping", data: "" });
418
+ await stream.sleep(SSE_PING_INTERVAL);
419
+ }
416
420
  } catch (error) {
417
421
  loggerWithoutContext().error({ msg: "error in sse connection", error });
418
422
 
419
423
  // Cleanup on error
420
- if (connId !== undefined) {
421
- actorDriver
422
- .getGenericConnGlobalState(actorId)
423
- .sseStreams.delete(connId);
424
- }
425
424
  if (conn && actor !== undefined) {
426
- actor.__removeConn(conn);
425
+ actor.__connDisconnected(conn, false, socketId);
427
426
  }
428
427
 
429
428
  // Close the stream on error
@@ -441,7 +440,6 @@ export async function handleAction(
441
440
  actorDriver: ActorDriver,
442
441
  actionName: string,
443
442
  actorId: string,
444
- authData: unknown,
445
443
  ) {
446
444
  const encoding = getRequestEncoding(c.req);
447
445
  const parameters = getRequestConnParams(c.req);
@@ -454,6 +452,7 @@ export async function handleAction(
454
452
  HTTP_ACTION_REQUEST_VERSIONED,
455
453
  );
456
454
  const actionArgs = cbor.decode(new Uint8Array(request.args));
455
+ const socketId = generateConnSocketId();
457
456
 
458
457
  // Invoke the action
459
458
  let actor: AnyActorInstance | undefined;
@@ -465,15 +464,13 @@ export async function handleAction(
465
464
  actor.rLog.debug({ msg: "handling action", actionName, encoding });
466
465
 
467
466
  // Create conn
468
- const connState = await actor.prepareConn(parameters, c.req.raw);
469
467
  conn = await actor.createConn(
470
- generateConnId(),
471
- generateConnToken(),
468
+ {
469
+ socketId,
470
+ driverState: { [ConnDriverKind.HTTP]: {} },
471
+ },
472
472
  parameters,
473
- connState,
474
- CONNECTION_DRIVER_HTTP,
475
- {} satisfies GenericHttpDriverState,
476
- authData,
473
+ c.req.raw,
477
474
  );
478
475
 
479
476
  // Call action
@@ -481,7 +478,8 @@ export async function handleAction(
481
478
  output = await actor.executeAction(ctx, actionName, actionArgs);
482
479
  } finally {
483
480
  if (conn) {
484
- actor?.__removeConn(conn);
481
+ // HTTP connections don't have persistent sockets, so no socket ID needed
482
+ actor?.__connDisconnected(conn, true, socketId);
485
483
  }
486
484
  }
487
485
 
@@ -494,7 +492,9 @@ export async function handleAction(
494
492
  responseData,
495
493
  HTTP_ACTION_RESPONSE_VERSIONED,
496
494
  );
497
- return c.body(serialized as Uint8Array, 200, {
495
+
496
+ // TODO: Remvoe any, Hono is being a dumbass
497
+ return c.body(serialized as Uint8Array as any, 200, {
498
498
  "Content-Type": contentTypeForEncoding(encoding),
499
499
  });
500
500
  }
@@ -539,12 +539,48 @@ export async function handleConnectionMessage(
539
539
  return c.json({});
540
540
  }
541
541
 
542
+ export async function handleConnectionClose(
543
+ c: HonoContext,
544
+ _runConfig: RunConfig,
545
+ actorDriver: ActorDriver,
546
+ connId: string,
547
+ connToken: string,
548
+ actorId: string,
549
+ ) {
550
+ const actor = await actorDriver.loadActor(actorId);
551
+
552
+ // Find connection
553
+ const conn = actor.conns.get(connId);
554
+ if (!conn) {
555
+ throw new errors.ConnNotFound(connId);
556
+ }
557
+
558
+ // Authenticate connection
559
+ if (conn._token !== connToken) {
560
+ throw new errors.IncorrectConnToken();
561
+ }
562
+
563
+ // Check if this is an SSE connection
564
+ if (
565
+ !conn.__socket?.driverState ||
566
+ !(ConnDriverKind.SSE in conn.__socket.driverState)
567
+ ) {
568
+ throw new errors.UserError(
569
+ "Connection close is only supported for SSE connections",
570
+ );
571
+ }
572
+
573
+ // Close the SSE connection
574
+ await conn.disconnect("Connection closed by client request");
575
+
576
+ return c.json({});
577
+ }
578
+
542
579
  export async function handleRawWebSocketHandler(
543
580
  req: Request | undefined,
544
581
  path: string,
545
582
  actorDriver: ActorDriver,
546
583
  actorId: string,
547
- authData: unknown,
548
584
  ): Promise<UpgradeWebSocketArgs> {
549
585
  const actor = await actorDriver.loadActor(actorId);
550
586
 
@@ -11,19 +11,24 @@ import {
11
11
  type ConnectWebSocketOutput,
12
12
  type ConnsMessageOpts,
13
13
  handleAction,
14
+ handleConnectionClose,
14
15
  handleConnectionMessage,
15
16
  handleRawWebSocketHandler,
16
17
  handleSseConnect,
17
18
  handleWebSocketConnect,
18
19
  } from "@/actor/router-endpoints";
19
20
  import {
20
- HEADER_AUTH_DATA,
21
21
  HEADER_CONN_ID,
22
22
  HEADER_CONN_PARAMS,
23
23
  HEADER_CONN_TOKEN,
24
24
  HEADER_ENCODING,
25
25
  PATH_CONNECT_WEBSOCKET,
26
26
  PATH_RAW_WEBSOCKET_PREFIX,
27
+ WS_PROTOCOL_CONN_ID,
28
+ WS_PROTOCOL_CONN_PARAMS,
29
+ WS_PROTOCOL_CONN_TOKEN,
30
+ WS_PROTOCOL_ENCODING,
31
+ WS_PROTOCOL_TOKEN,
27
32
  } from "@/common/actor-router-consts";
28
33
  import {
29
34
  handleRouteError,
@@ -35,8 +40,9 @@ import {
35
40
  type ActorInspectorRouterEnv,
36
41
  createActorInspectorRouter,
37
42
  } from "@/inspector/actor";
38
- import { secureInspector } from "@/inspector/utils";
43
+ import { isInspectorEnabled, secureInspector } from "@/inspector/utils";
39
44
  import type { RunConfig } from "@/registry/run-config";
45
+ import { ConnDriverKind } from "./conn-drivers";
40
46
  import type { ActorDriver } from "./driver";
41
47
  import { InternalError } from "./errors";
42
48
  import { loggerWithoutContext } from "./log";
@@ -78,19 +84,70 @@ export function createActorRouter(
78
84
  return c.text("ok");
79
85
  });
80
86
 
87
+ // Test endpoint to force disconnect a connection non-cleanly
88
+ router.post("/.test/force-disconnect", async (c) => {
89
+ const connId = c.req.query("conn");
90
+
91
+ if (!connId) {
92
+ return c.text("Missing conn query parameter", 400);
93
+ }
94
+
95
+ const actor = await actorDriver.loadActor(c.env.actorId);
96
+ const conn = actor.__getConnForId(connId);
97
+
98
+ if (!conn) {
99
+ return c.text(`Connection not found: ${connId}`, 404);
100
+ }
101
+
102
+ // Force close the websocket/SSE connection without clean shutdown
103
+ const driverState = conn.__driverState;
104
+ if (driverState && ConnDriverKind.WEBSOCKET in driverState) {
105
+ const ws = driverState[ConnDriverKind.WEBSOCKET].websocket;
106
+
107
+ // Force close without sending close frame
108
+ (ws.raw as any).terminate();
109
+ } else if (driverState && ConnDriverKind.SSE in driverState) {
110
+ const stream = driverState[ConnDriverKind.SSE].stream;
111
+
112
+ // Force close the SSE stream
113
+ stream.abort();
114
+ }
115
+
116
+ return c.json({ success: true });
117
+ });
118
+
81
119
  router.get(PATH_CONNECT_WEBSOCKET, async (c) => {
82
120
  const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
83
121
  if (upgradeWebSocket) {
84
122
  return upgradeWebSocket(async (c) => {
85
- const encodingRaw = c.req.header(HEADER_ENCODING);
86
- const connParamsRaw = c.req.header(HEADER_CONN_PARAMS);
87
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
123
+ // Parse configuration from Sec-WebSocket-Protocol header
124
+ const protocols = c.req.header("sec-websocket-protocol");
125
+ let encodingRaw: string | undefined;
126
+ let connParamsRaw: string | undefined;
127
+ let connIdRaw: string | undefined;
128
+ let connTokenRaw: string | undefined;
129
+
130
+ if (protocols) {
131
+ const protocolList = protocols.split(",").map((p) => p.trim());
132
+ for (const protocol of protocolList) {
133
+ if (protocol.startsWith(WS_PROTOCOL_ENCODING)) {
134
+ encodingRaw = protocol.substring(WS_PROTOCOL_ENCODING.length);
135
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_PARAMS)) {
136
+ connParamsRaw = decodeURIComponent(
137
+ protocol.substring(WS_PROTOCOL_CONN_PARAMS.length),
138
+ );
139
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_ID)) {
140
+ connIdRaw = protocol.substring(WS_PROTOCOL_CONN_ID.length);
141
+ } else if (protocol.startsWith(WS_PROTOCOL_CONN_TOKEN)) {
142
+ connTokenRaw = protocol.substring(WS_PROTOCOL_CONN_TOKEN.length);
143
+ }
144
+ }
145
+ }
88
146
 
89
147
  const encoding = EncodingSchema.parse(encodingRaw);
90
148
  const connParams = connParamsRaw
91
149
  ? JSON.parse(connParamsRaw)
92
150
  : undefined;
93
- const authData = authDataRaw ? JSON.parse(authDataRaw) : undefined;
94
151
 
95
152
  return await handleWebSocketConnect(
96
153
  c.req.raw,
@@ -99,7 +156,8 @@ export function createActorRouter(
99
156
  c.env.actorId,
100
157
  encoding,
101
158
  connParams,
102
- authData,
159
+ connIdRaw,
160
+ connTokenRaw,
103
161
  );
104
162
  })(c, noopNext());
105
163
  } else {
@@ -111,41 +169,38 @@ export function createActorRouter(
111
169
  });
112
170
 
113
171
  router.get("/connect/sse", async (c) => {
114
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
115
- let authData: unknown;
116
- if (authDataRaw) {
117
- authData = JSON.parse(authDataRaw);
118
- }
119
-
120
- return handleSseConnect(c, runConfig, actorDriver, c.env.actorId, authData);
172
+ return handleSseConnect(c, runConfig, actorDriver, c.env.actorId);
121
173
  });
122
174
 
123
175
  router.post("/action/:action", async (c) => {
124
176
  const actionName = c.req.param("action");
125
177
 
126
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
127
- let authData: unknown;
128
- if (authDataRaw) {
129
- authData = JSON.parse(authDataRaw);
130
- }
178
+ return handleAction(c, runConfig, actorDriver, actionName, c.env.actorId);
179
+ });
131
180
 
132
- return handleAction(
181
+ router.post("/connections/message", async (c) => {
182
+ const connId = c.req.header(HEADER_CONN_ID);
183
+ const connToken = c.req.header(HEADER_CONN_TOKEN);
184
+ if (!connId || !connToken) {
185
+ throw new Error("Missing required parameters");
186
+ }
187
+ return handleConnectionMessage(
133
188
  c,
134
189
  runConfig,
135
190
  actorDriver,
136
- actionName,
191
+ connId,
192
+ connToken,
137
193
  c.env.actorId,
138
- authData,
139
194
  );
140
195
  });
141
196
 
142
- router.post("/connections/message", async (c) => {
197
+ router.post("/connections/close", async (c) => {
143
198
  const connId = c.req.header(HEADER_CONN_ID);
144
199
  const connToken = c.req.header(HEADER_CONN_TOKEN);
145
200
  if (!connId || !connToken) {
146
201
  throw new Error("Missing required parameters");
147
202
  }
148
- return handleConnectionMessage(
203
+ return handleConnectionClose(
149
204
  c,
150
205
  runConfig,
151
206
  actorDriver,
@@ -157,12 +212,6 @@ export function createActorRouter(
157
212
 
158
213
  // Raw HTTP endpoints - /http/*
159
214
  router.all("/raw/http/*", async (c) => {
160
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
161
- let authData: unknown;
162
- if (authDataRaw) {
163
- authData = JSON.parse(authDataRaw);
164
- }
165
-
166
215
  const actor = await actorDriver.loadActor(c.env.actorId);
167
216
 
168
217
  // TODO: This is not a clean way of doing this since `/http/` might exist mid-path
@@ -186,9 +235,7 @@ export function createActorRouter(
186
235
  });
187
236
 
188
237
  // Call the actor's onFetch handler - it will throw appropriate errors
189
- const response = await actor.handleFetch(correctedRequest, {
190
- auth: authData,
191
- });
238
+ const response = await actor.handleFetch(correctedRequest, {});
192
239
 
193
240
  // This should never happen now since handleFetch throws errors
194
241
  if (!response) {
@@ -203,16 +250,6 @@ export function createActorRouter(
203
250
  const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
204
251
  if (upgradeWebSocket) {
205
252
  return upgradeWebSocket(async (c) => {
206
- const encodingRaw = c.req.header(HEADER_ENCODING);
207
- const connParamsRaw = c.req.header(HEADER_CONN_PARAMS);
208
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
209
-
210
- const encoding = EncodingSchema.parse(encodingRaw);
211
- const connParams = connParamsRaw
212
- ? JSON.parse(connParamsRaw)
213
- : undefined;
214
- const authData = authDataRaw ? JSON.parse(authDataRaw) : undefined;
215
-
216
253
  const url = new URL(c.req.url);
217
254
  const pathWithQuery = c.req.path + url.search;
218
255
 
@@ -229,7 +266,6 @@ export function createActorRouter(
229
266
  pathWithQuery,
230
267
  actorDriver,
231
268
  c.env.actorId,
232
- authData,
233
269
  );
234
270
  })(c, noopNext());
235
271
  } else {
@@ -240,7 +276,7 @@ export function createActorRouter(
240
276
  }
241
277
  });
242
278
 
243
- if (runConfig.inspector.enabled) {
279
+ if (isInspectorEnabled(runConfig, "actor")) {
244
280
  router.route(
245
281
  "/inspect",
246
282
  new Hono<ActorInspectorRouterEnv & { Bindings: ActorRouterBindings }>()
@@ -87,7 +87,11 @@ export class Lock<T> {
87
87
  export function generateSecureToken(length = 32) {
88
88
  const array = new Uint8Array(length);
89
89
  crypto.getRandomValues(array);
90
- return btoa(String.fromCharCode(...array));
90
+ // Replace base64 chars that are not URL safe with URL-safe chars and strip padding
91
+ return btoa(String.fromCharCode(...array))
92
+ .replace(/\+/g, "-")
93
+ .replace(/\//g, "_")
94
+ .replace(/=/g, "");
91
95
  }
92
96
 
93
97
  export function generateRandomString(length = 32) {