rivetkit 2.0.5 → 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 (178) 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-5YTI25C3.cjs → chunk-3MBP4WNC.cjs} +7 -7
  5. package/dist/tsup/{chunk-5YTI25C3.cjs.map → chunk-3MBP4WNC.cjs.map} +1 -1
  6. package/dist/tsup/chunk-3Y45CIF4.cjs +3726 -0
  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-5ZOHIKWG.cjs +4071 -0
  11. package/dist/tsup/chunk-5ZOHIKWG.cjs.map +1 -0
  12. package/dist/tsup/{chunk-WADSS5X4.cjs → chunk-6EUWRXLT.cjs} +21 -7
  13. package/dist/tsup/chunk-6EUWRXLT.cjs.map +1 -0
  14. package/dist/tsup/{chunk-D7NWUCRK.cjs → chunk-6OVKCDSH.cjs} +6 -6
  15. package/dist/tsup/{chunk-D7NWUCRK.cjs.map → chunk-6OVKCDSH.cjs.map} +1 -1
  16. package/dist/tsup/{chunk-I5VTWPHW.js → chunk-7N56ZUC7.js} +3 -3
  17. package/dist/tsup/{chunk-LZIBTLEY.cjs → chunk-B3TLRM4Q.cjs} +13 -25
  18. package/dist/tsup/chunk-B3TLRM4Q.cjs.map +1 -0
  19. package/dist/tsup/chunk-BW5DPM6Z.js +4071 -0
  20. package/dist/tsup/chunk-BW5DPM6Z.js.map +1 -0
  21. package/dist/tsup/chunk-DFS77KAA.cjs +1046 -0
  22. package/dist/tsup/chunk-DFS77KAA.cjs.map +1 -0
  23. package/dist/tsup/{chunk-PG3K2LI7.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-CKA54YQN.js → chunk-GZVBFXBI.js} +3 -15
  27. package/dist/tsup/chunk-GZVBFXBI.js.map +1 -0
  28. package/dist/tsup/chunk-HPT3I7UU.js +3726 -0
  29. package/dist/tsup/chunk-HPT3I7UU.js.map +1 -0
  30. package/dist/tsup/chunk-JD54PXWP.js +1046 -0
  31. package/dist/tsup/chunk-JD54PXWP.js.map +1 -0
  32. package/dist/tsup/{chunk-PHSQJ6QI.cjs → chunk-K4ENQCC4.cjs} +3 -3
  33. package/dist/tsup/{chunk-PHSQJ6QI.cjs.map → chunk-K4ENQCC4.cjs.map} +1 -1
  34. package/dist/tsup/{chunk-WNGOBAA7.js → chunk-PUSQNDJG.js} +2 -2
  35. package/dist/tsup/{chunk-CFFKMUYH.js → chunk-RVP5RUSC.js} +20 -6
  36. package/dist/tsup/chunk-RVP5RUSC.js.map +1 -0
  37. package/dist/tsup/chunk-SAZCNSVY.cjs +259 -0
  38. package/dist/tsup/chunk-SAZCNSVY.cjs.map +1 -0
  39. package/dist/tsup/{chunk-YW6Y6VNE.js → chunk-SBKRVQS2.js} +9 -5
  40. package/dist/tsup/chunk-SBKRVQS2.js.map +1 -0
  41. package/dist/tsup/{chunk-FGFT4FVX.cjs → chunk-TZGUSEIJ.cjs} +14 -10
  42. package/dist/tsup/chunk-TZGUSEIJ.cjs.map +1 -0
  43. package/dist/tsup/chunk-YQ4XQYPM.js +259 -0
  44. package/dist/tsup/chunk-YQ4XQYPM.js.map +1 -0
  45. package/dist/tsup/client/mod.cjs +9 -9
  46. package/dist/tsup/client/mod.d.cts +7 -8
  47. package/dist/tsup/client/mod.d.ts +7 -8
  48. package/dist/tsup/client/mod.js +8 -8
  49. package/dist/tsup/common/log.cjs +3 -3
  50. package/dist/tsup/common/log.js +2 -2
  51. package/dist/tsup/common/websocket.cjs +4 -4
  52. package/dist/tsup/common/websocket.js +3 -3
  53. package/dist/tsup/{connection-BvE-Oq7t.d.ts → conn-DCSQgIlw.d.ts} +1605 -1353
  54. package/dist/tsup/{connection-DTzmWwU5.d.cts → conn-DdzHTm2E.d.cts} +1605 -1353
  55. package/dist/tsup/driver-helpers/mod.cjs +31 -5
  56. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  57. package/dist/tsup/driver-helpers/mod.d.cts +7 -8
  58. package/dist/tsup/driver-helpers/mod.d.ts +7 -8
  59. package/dist/tsup/driver-helpers/mod.js +33 -7
  60. package/dist/tsup/driver-test-suite/mod.cjs +319 -216
  61. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  62. package/dist/tsup/driver-test-suite/mod.d.cts +7 -7
  63. package/dist/tsup/driver-test-suite/mod.d.ts +7 -7
  64. package/dist/tsup/driver-test-suite/mod.js +588 -485
  65. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  66. package/dist/tsup/inspector/mod.cjs +17 -5
  67. package/dist/tsup/inspector/mod.cjs.map +1 -1
  68. package/dist/tsup/inspector/mod.d.cts +34 -7
  69. package/dist/tsup/inspector/mod.d.ts +34 -7
  70. package/dist/tsup/inspector/mod.js +20 -8
  71. package/dist/tsup/mod.cjs +10 -17
  72. package/dist/tsup/mod.cjs.map +1 -1
  73. package/dist/tsup/mod.d.cts +56 -9
  74. package/dist/tsup/mod.d.ts +56 -9
  75. package/dist/tsup/mod.js +17 -24
  76. package/dist/tsup/test/mod.cjs +11 -9
  77. package/dist/tsup/test/mod.cjs.map +1 -1
  78. package/dist/tsup/test/mod.d.cts +6 -7
  79. package/dist/tsup/test/mod.d.ts +6 -7
  80. package/dist/tsup/test/mod.js +10 -8
  81. package/dist/tsup/utils.cjs +4 -2
  82. package/dist/tsup/utils.cjs.map +1 -1
  83. package/dist/tsup/utils.d.cts +11 -1
  84. package/dist/tsup/utils.d.ts +11 -1
  85. package/dist/tsup/utils.js +3 -1
  86. package/package.json +8 -4
  87. package/src/actor/action.ts +1 -1
  88. package/src/actor/config.ts +1 -1
  89. package/src/actor/conn-drivers.ts +205 -0
  90. package/src/actor/conn-socket.ts +6 -0
  91. package/src/actor/{connection.ts → conn.ts} +78 -84
  92. package/src/actor/context.ts +1 -1
  93. package/src/actor/driver.ts +4 -43
  94. package/src/actor/instance.ts +162 -86
  95. package/src/actor/mod.ts +6 -14
  96. package/src/actor/persisted.ts +2 -5
  97. package/src/actor/protocol/old.ts +1 -1
  98. package/src/actor/router-endpoints.ts +147 -138
  99. package/src/actor/router.ts +89 -52
  100. package/src/actor/utils.ts +5 -1
  101. package/src/client/actor-conn.ts +163 -31
  102. package/src/client/actor-handle.ts +0 -1
  103. package/src/client/client.ts +2 -2
  104. package/src/client/config.ts +7 -0
  105. package/src/client/raw-utils.ts +1 -1
  106. package/src/client/utils.ts +1 -1
  107. package/src/common/actor-router-consts.ts +59 -0
  108. package/src/common/router.ts +2 -1
  109. package/src/common/versioned-data.ts +5 -5
  110. package/src/driver-helpers/mod.ts +15 -2
  111. package/src/driver-test-suite/mod.ts +11 -2
  112. package/src/driver-test-suite/test-inline-client-driver.ts +40 -22
  113. package/src/driver-test-suite/tests/actor-conn-state.ts +66 -22
  114. package/src/driver-test-suite/tests/actor-conn.ts +65 -126
  115. package/src/driver-test-suite/tests/actor-reconnect.ts +160 -0
  116. package/src/driver-test-suite/tests/actor-sleep.ts +0 -1
  117. package/src/driver-test-suite/tests/raw-websocket.ts +0 -35
  118. package/src/driver-test-suite/utils.ts +8 -3
  119. package/src/drivers/default.ts +8 -7
  120. package/src/drivers/engine/actor-driver.ts +67 -44
  121. package/src/drivers/engine/config.ts +4 -0
  122. package/src/drivers/file-system/actor.ts +0 -6
  123. package/src/drivers/file-system/global-state.ts +3 -14
  124. package/src/drivers/file-system/manager.ts +12 -8
  125. package/src/inspector/actor.ts +4 -3
  126. package/src/inspector/config.ts +10 -1
  127. package/src/inspector/mod.ts +1 -0
  128. package/src/inspector/utils.ts +23 -4
  129. package/src/manager/driver.ts +12 -2
  130. package/src/manager/gateway.ts +407 -0
  131. package/src/manager/protocol/query.ts +1 -1
  132. package/src/manager/router.ts +269 -468
  133. package/src/manager-api/actors.ts +61 -0
  134. package/src/manager-api/common.ts +4 -0
  135. package/src/mod.ts +1 -1
  136. package/src/registry/mod.ts +126 -12
  137. package/src/registry/serve.ts +8 -3
  138. package/src/remote-manager-driver/actor-http-client.ts +30 -19
  139. package/src/remote-manager-driver/actor-websocket-client.ts +45 -18
  140. package/src/remote-manager-driver/api-endpoints.ts +19 -21
  141. package/src/remote-manager-driver/api-utils.ts +10 -1
  142. package/src/remote-manager-driver/mod.ts +53 -53
  143. package/src/remote-manager-driver/ws-proxy.ts +2 -9
  144. package/src/test/mod.ts +6 -2
  145. package/src/utils.ts +21 -2
  146. package/dist/tsup/chunk-2MD57QF4.js +0 -1794
  147. package/dist/tsup/chunk-2MD57QF4.js.map +0 -1
  148. package/dist/tsup/chunk-B2QGJGZQ.js +0 -338
  149. package/dist/tsup/chunk-B2QGJGZQ.js.map +0 -1
  150. package/dist/tsup/chunk-CFFKMUYH.js.map +0 -1
  151. package/dist/tsup/chunk-CKA54YQN.js.map +0 -1
  152. package/dist/tsup/chunk-FGFT4FVX.cjs.map +0 -1
  153. package/dist/tsup/chunk-IRMBWX36.cjs +0 -1794
  154. package/dist/tsup/chunk-IRMBWX36.cjs.map +0 -1
  155. package/dist/tsup/chunk-L7QRXNWP.js +0 -6562
  156. package/dist/tsup/chunk-L7QRXNWP.js.map +0 -1
  157. package/dist/tsup/chunk-LZIBTLEY.cjs.map +0 -1
  158. package/dist/tsup/chunk-MRZS2J4X.cjs +0 -6562
  159. package/dist/tsup/chunk-MRZS2J4X.cjs.map +0 -1
  160. package/dist/tsup/chunk-RM2SVURR.cjs +0 -338
  161. package/dist/tsup/chunk-RM2SVURR.cjs.map +0 -1
  162. package/dist/tsup/chunk-WADSS5X4.cjs.map +0 -1
  163. package/dist/tsup/chunk-YW6Y6VNE.js.map +0 -1
  164. package/dist/tsup/common-CXCe7s6i.d.cts +0 -218
  165. package/dist/tsup/common-CXCe7s6i.d.ts +0 -218
  166. package/dist/tsup/router-endpoints-CctffZNL.d.cts +0 -65
  167. package/dist/tsup/router-endpoints-DFm1BglJ.d.ts +0 -65
  168. package/src/actor/generic-conn-driver.ts +0 -246
  169. package/src/common/fake-event-source.ts +0 -267
  170. package/src/manager-api/routes/actors-create.ts +0 -16
  171. package/src/manager-api/routes/actors-delete.ts +0 -4
  172. package/src/manager-api/routes/actors-get-by-id.ts +0 -7
  173. package/src/manager-api/routes/actors-get-or-create-by-id.ts +0 -29
  174. package/src/manager-api/routes/actors-get.ts +0 -7
  175. package/src/manager-api/routes/common.ts +0 -18
  176. /package/dist/tsup/{chunk-I5VTWPHW.js.map → chunk-7N56ZUC7.js.map} +0 -0
  177. /package/dist/tsup/{chunk-PG3K2LI7.js.map → chunk-E4UVJKSV.js.map} +0 -0
  178. /package/dist/tsup/{chunk-WNGOBAA7.js.map → chunk-PUSQNDJG.js.map} +0 -0
@@ -10,17 +10,26 @@ import {
10
10
  type ConnectWebSocketOpts,
11
11
  type ConnectWebSocketOutput,
12
12
  type ConnsMessageOpts,
13
- HEADER_AUTH_DATA,
14
- HEADER_CONN_ID,
15
- HEADER_CONN_PARAMS,
16
- HEADER_CONN_TOKEN,
17
- HEADER_ENCODING,
18
13
  handleAction,
14
+ handleConnectionClose,
19
15
  handleConnectionMessage,
20
16
  handleRawWebSocketHandler,
21
17
  handleSseConnect,
22
18
  handleWebSocketConnect,
23
19
  } from "@/actor/router-endpoints";
20
+ import {
21
+ HEADER_CONN_ID,
22
+ HEADER_CONN_PARAMS,
23
+ HEADER_CONN_TOKEN,
24
+ HEADER_ENCODING,
25
+ PATH_CONNECT_WEBSOCKET,
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,
32
+ } from "@/common/actor-router-consts";
24
33
  import {
25
34
  handleRouteError,
26
35
  handleRouteNotFound,
@@ -31,15 +40,13 @@ import {
31
40
  type ActorInspectorRouterEnv,
32
41
  createActorInspectorRouter,
33
42
  } from "@/inspector/actor";
34
- import { secureInspector } from "@/inspector/utils";
43
+ import { isInspectorEnabled, secureInspector } from "@/inspector/utils";
35
44
  import type { RunConfig } from "@/registry/run-config";
45
+ import { ConnDriverKind } from "./conn-drivers";
36
46
  import type { ActorDriver } from "./driver";
37
47
  import { InternalError } from "./errors";
38
48
  import { loggerWithoutContext } from "./log";
39
49
 
40
- export const PATH_CONNECT_WEBSOCKET = "/connect/websocket";
41
- export const PATH_RAW_WEBSOCKET_PREFIX = "/raw/websocket/";
42
-
43
50
  export type {
44
51
  ConnectWebSocketOpts,
45
52
  ConnectWebSocketOutput,
@@ -77,19 +84,70 @@ export function createActorRouter(
77
84
  return c.text("ok");
78
85
  });
79
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
+
80
119
  router.get(PATH_CONNECT_WEBSOCKET, async (c) => {
81
120
  const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
82
121
  if (upgradeWebSocket) {
83
122
  return upgradeWebSocket(async (c) => {
84
- const encodingRaw = c.req.header(HEADER_ENCODING);
85
- const connParamsRaw = c.req.header(HEADER_CONN_PARAMS);
86
- 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
+ }
87
146
 
88
147
  const encoding = EncodingSchema.parse(encodingRaw);
89
148
  const connParams = connParamsRaw
90
149
  ? JSON.parse(connParamsRaw)
91
150
  : undefined;
92
- const authData = authDataRaw ? JSON.parse(authDataRaw) : undefined;
93
151
 
94
152
  return await handleWebSocketConnect(
95
153
  c.req.raw,
@@ -98,7 +156,8 @@ export function createActorRouter(
98
156
  c.env.actorId,
99
157
  encoding,
100
158
  connParams,
101
- authData,
159
+ connIdRaw,
160
+ connTokenRaw,
102
161
  );
103
162
  })(c, noopNext());
104
163
  } else {
@@ -110,41 +169,38 @@ export function createActorRouter(
110
169
  });
111
170
 
112
171
  router.get("/connect/sse", async (c) => {
113
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
114
- let authData: unknown;
115
- if (authDataRaw) {
116
- authData = JSON.parse(authDataRaw);
117
- }
118
-
119
- return handleSseConnect(c, runConfig, actorDriver, c.env.actorId, authData);
172
+ return handleSseConnect(c, runConfig, actorDriver, c.env.actorId);
120
173
  });
121
174
 
122
175
  router.post("/action/:action", async (c) => {
123
176
  const actionName = c.req.param("action");
124
177
 
125
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
126
- let authData: unknown;
127
- if (authDataRaw) {
128
- authData = JSON.parse(authDataRaw);
129
- }
178
+ return handleAction(c, runConfig, actorDriver, actionName, c.env.actorId);
179
+ });
130
180
 
131
- 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(
132
188
  c,
133
189
  runConfig,
134
190
  actorDriver,
135
- actionName,
191
+ connId,
192
+ connToken,
136
193
  c.env.actorId,
137
- authData,
138
194
  );
139
195
  });
140
196
 
141
- router.post("/connections/message", async (c) => {
197
+ router.post("/connections/close", async (c) => {
142
198
  const connId = c.req.header(HEADER_CONN_ID);
143
199
  const connToken = c.req.header(HEADER_CONN_TOKEN);
144
200
  if (!connId || !connToken) {
145
201
  throw new Error("Missing required parameters");
146
202
  }
147
- return handleConnectionMessage(
203
+ return handleConnectionClose(
148
204
  c,
149
205
  runConfig,
150
206
  actorDriver,
@@ -156,12 +212,6 @@ export function createActorRouter(
156
212
 
157
213
  // Raw HTTP endpoints - /http/*
158
214
  router.all("/raw/http/*", async (c) => {
159
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
160
- let authData: unknown;
161
- if (authDataRaw) {
162
- authData = JSON.parse(authDataRaw);
163
- }
164
-
165
215
  const actor = await actorDriver.loadActor(c.env.actorId);
166
216
 
167
217
  // TODO: This is not a clean way of doing this since `/http/` might exist mid-path
@@ -185,9 +235,7 @@ export function createActorRouter(
185
235
  });
186
236
 
187
237
  // Call the actor's onFetch handler - it will throw appropriate errors
188
- const response = await actor.handleFetch(correctedRequest, {
189
- auth: authData,
190
- });
238
+ const response = await actor.handleFetch(correctedRequest, {});
191
239
 
192
240
  // This should never happen now since handleFetch throws errors
193
241
  if (!response) {
@@ -202,16 +250,6 @@ export function createActorRouter(
202
250
  const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
203
251
  if (upgradeWebSocket) {
204
252
  return upgradeWebSocket(async (c) => {
205
- const encodingRaw = c.req.header(HEADER_ENCODING);
206
- const connParamsRaw = c.req.header(HEADER_CONN_PARAMS);
207
- const authDataRaw = c.req.header(HEADER_AUTH_DATA);
208
-
209
- const encoding = EncodingSchema.parse(encodingRaw);
210
- const connParams = connParamsRaw
211
- ? JSON.parse(connParamsRaw)
212
- : undefined;
213
- const authData = authDataRaw ? JSON.parse(authDataRaw) : undefined;
214
-
215
253
  const url = new URL(c.req.url);
216
254
  const pathWithQuery = c.req.path + url.search;
217
255
 
@@ -228,7 +266,6 @@ export function createActorRouter(
228
266
  pathWithQuery,
229
267
  actorDriver,
230
268
  c.env.actorId,
231
- authData,
232
269
  );
233
270
  })(c, noopNext());
234
271
  } else {
@@ -239,7 +276,7 @@ export function createActorRouter(
239
276
  }
240
277
  });
241
278
 
242
- if (runConfig.inspector.enabled) {
279
+ if (isInspectorEnabled(runConfig, "actor")) {
243
280
  router.route(
244
281
  "/inspect",
245
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) {
@@ -5,6 +5,13 @@ import type { CloseEvent } from "ws";
5
5
  import type { AnyActorDefinition } from "@/actor/definition";
6
6
  import { inputDataToBuffer } from "@/actor/protocol/old";
7
7
  import { type Encoding, jsonStringifyCompat } from "@/actor/protocol/serde";
8
+ import {
9
+ HEADER_CONN_ID,
10
+ HEADER_CONN_PARAMS,
11
+ HEADER_CONN_TOKEN,
12
+ HEADER_ENCODING,
13
+ PATH_CONNECT_WEBSOCKET,
14
+ } from "@/common/actor-router-consts";
8
15
  import { importEventSource } from "@/common/eventsource";
9
16
  import type {
10
17
  UniversalErrorEvent,
@@ -12,15 +19,9 @@ import type {
12
19
  UniversalMessageEvent,
13
20
  } from "@/common/eventsource-interface";
14
21
  import { assertUnreachable, stringifyError } from "@/common/utils";
15
- import {
16
- HEADER_CONN_ID,
17
- HEADER_CONN_PARAMS,
18
- HEADER_CONN_TOKEN,
19
- HEADER_ENCODING,
20
- type ManagerDriver,
21
- } from "@/driver-helpers/mod";
22
+ import type { UniversalWebSocket } from "@/common/websocket-interface";
23
+ import type { ManagerDriver } from "@/driver-helpers/mod";
22
24
  import type { ActorQuery } from "@/manager/protocol/query";
23
- import { PATH_CONNECT_WEBSOCKET, type UniversalWebSocket } from "@/mod";
24
25
  import type * as protocol from "@/schemas/client-protocol/mod";
25
26
  import {
26
27
  TO_CLIENT_VERSIONED,
@@ -31,7 +32,12 @@ import {
31
32
  encodingIsBinary,
32
33
  serializeWithEncoding,
33
34
  } from "@/serde";
34
- import { bufferToArrayBuffer, getEnvUniversal, httpUserAgent } from "@/utils";
35
+ import {
36
+ bufferToArrayBuffer,
37
+ getEnvUniversal,
38
+ httpUserAgent,
39
+ promiseWithResolvers,
40
+ } from "@/utils";
35
41
  import type { ActorDefinitionActions } from "./actor-common";
36
42
  import { queryActor } from "./actor-query";
37
43
  import { ACTOR_CONNS_SYMBOL, type ClientRaw, TRANSPORT_SYMBOL } from "./client";
@@ -93,7 +99,7 @@ export class ActorConnRaw {
93
99
  /** If attempting to connect. Helpful for knowing if in a retry loop when reconnecting. */
94
100
  #connecting = false;
95
101
 
96
- // These will only be set on SSE driver
102
+ // Connection info, used for reconnection and HTTP requests
97
103
  #actorId?: string;
98
104
  #connectionId?: string;
99
105
  #connectionToken?: string;
@@ -118,7 +124,7 @@ export class ActorConnRaw {
118
124
  #keepNodeAliveInterval: NodeJS.Timeout;
119
125
 
120
126
  /** Promise used to indicate the socket has connected successfully. This will be rejected if the connection fails. */
121
- #onOpenPromise?: PromiseWithResolvers<undefined>;
127
+ #onOpenPromise?: ReturnType<typeof promiseWithResolvers<undefined>>;
122
128
 
123
129
  #client: ClientRaw;
124
130
  #driver: ManagerDriver;
@@ -176,7 +182,7 @@ export class ActorConnRaw {
176
182
  this.#actionIdCounter += 1;
177
183
 
178
184
  const { promise, resolve, reject } =
179
- Promise.withResolvers<protocol.ActionResponse>();
185
+ promiseWithResolvers<protocol.ActionResponse>();
180
186
  this.#actionsInFlight.set(actionId, { name: opts.name, resolve, reject });
181
187
 
182
188
  this.#sendMessage({
@@ -252,7 +258,7 @@ enc
252
258
  // Create promise for open
253
259
  if (this.#onOpenPromise)
254
260
  throw new Error("#onOpenPromise already defined");
255
- this.#onOpenPromise = Promise.withResolvers();
261
+ this.#onOpenPromise = promiseWithResolvers();
256
262
 
257
263
  // Connect transport
258
264
  if (this.#client[TRANSPORT_SYMBOL] === "websocket") {
@@ -276,15 +282,37 @@ enc
276
282
  this.#actorQuery,
277
283
  this.#driver,
278
284
  );
285
+
286
+ // Check if we have connection info for reconnection
287
+ const isReconnection = this.#connectionId && this.#connectionToken;
288
+ if (isReconnection) {
289
+ logger().debug({
290
+ msg: "attempting websocket reconnection",
291
+ connectionId: this.#connectionId,
292
+ });
293
+ }
294
+
279
295
  const ws = await this.#driver.openWebSocket(
280
296
  PATH_CONNECT_WEBSOCKET,
281
297
  actorId,
282
298
  this.#encoding,
283
299
  this.#params,
300
+ // Pass connection ID and token for reconnection if available
301
+ isReconnection ? this.#connectionId : undefined,
302
+ isReconnection ? this.#connectionToken : undefined,
284
303
  );
304
+ logger().debug({
305
+ msg: "transport set to new websocket",
306
+ connectionId: this.#connectionId,
307
+ readyState: ws.readyState,
308
+ messageQueueLength: this.#messageQueue.length,
309
+ });
285
310
  this.#transport = { websocket: ws };
286
311
  ws.addEventListener("open", () => {
287
- logger().debug({ msg: "websocket open" });
312
+ logger().debug({
313
+ msg: "client websocket open",
314
+ connectionId: this.#connectionId,
315
+ });
288
316
  });
289
317
  ws.addEventListener("message", async (ev) => {
290
318
  this.#handleOnMessage(ev.data);
@@ -315,6 +343,8 @@ enc
315
343
  encoding: this.#encoding,
316
344
  });
317
345
 
346
+ const isReconnection = this.#connectionId && this.#connectionToken;
347
+
318
348
  const eventSource = new EventSource("http://actor/connect/sse", {
319
349
  fetch: (input, init) => {
320
350
  return this.#driver.sendRequest(
@@ -328,6 +358,12 @@ enc
328
358
  ...(this.#params !== undefined
329
359
  ? { [HEADER_CONN_PARAMS]: JSON.stringify(this.#params) }
330
360
  : {}),
361
+ ...(isReconnection
362
+ ? {
363
+ [HEADER_CONN_ID]: this.#connectionId,
364
+ [HEADER_CONN_TOKEN]: this.#connectionToken,
365
+ }
366
+ : {}),
331
367
  },
332
368
  }),
333
369
  );
@@ -337,6 +373,9 @@ enc
337
373
  this.#transport = { sse: eventSource };
338
374
 
339
375
  eventSource.addEventListener("message", (ev: UniversalMessageEvent) => {
376
+ // Ignore pings
377
+ if (ev.type === "ping") return;
378
+
340
379
  this.#handleOnMessage(ev.data);
341
380
  });
342
381
 
@@ -350,6 +389,7 @@ enc
350
389
  logger().debug({
351
390
  msg: "socket open",
352
391
  messageQueueLength: this.#messageQueue.length,
392
+ connectionId: this.#connectionId,
353
393
  });
354
394
 
355
395
  // Resolve open promise
@@ -369,6 +409,10 @@ enc
369
409
  // If the message fails to send, the message will be re-queued
370
410
  const queue = this.#messageQueue;
371
411
  this.#messageQueue = [];
412
+ logger().debug({
413
+ msg: "flushing message queue",
414
+ queueLength: queue.length,
415
+ });
372
416
  for (const msg of queue) {
373
417
  this.#sendMessage(msg);
374
418
  }
@@ -394,7 +438,7 @@ enc
394
438
  );
395
439
 
396
440
  if (response.body.tag === "Init") {
397
- // This is only called for SSE
441
+ // Store connection info for reconnection
398
442
  this.#actorId = response.body.val.actorId;
399
443
  this.#connectionId = response.body.val.connectionId;
400
444
  this.#connectionToken = response.body.val.connectionToken;
@@ -490,26 +534,46 @@ enc
490
534
  //
491
535
  // These properties will be undefined
492
536
  const closeEvent = event as CloseEvent;
493
- if (closeEvent.wasClean) {
494
- logger().info({
495
- msg: "socket closed",
496
- code: closeEvent.code,
497
- reason: closeEvent.reason,
498
- wasClean: closeEvent.wasClean,
499
- });
500
- } else {
501
- logger().warn({
502
- msg: "socket closed",
503
- code: closeEvent.code,
504
- reason: closeEvent.reason,
505
- wasClean: closeEvent.wasClean,
537
+ const wasClean = closeEvent.wasClean;
538
+
539
+ logger().info({
540
+ msg: "socket closed",
541
+ code: closeEvent.code,
542
+ reason: closeEvent.reason,
543
+ wasClean: wasClean,
544
+ connectionId: this.#connectionId,
545
+ messageQueueLength: this.#messageQueue.length,
546
+ actionsInFlight: this.#actionsInFlight.size,
547
+ });
548
+
549
+ // Reject all in-flight actions
550
+ if (this.#actionsInFlight.size > 0) {
551
+ logger().debug({
552
+ msg: "rejecting in-flight actions after disconnect",
553
+ count: this.#actionsInFlight.size,
554
+ connectionId: this.#connectionId,
555
+ wasClean,
506
556
  });
557
+
558
+ const disconnectError = new Error(
559
+ wasClean ? "Connection closed" : "Connection lost",
560
+ );
561
+
562
+ for (const actionInfo of this.#actionsInFlight.values()) {
563
+ actionInfo.reject(disconnectError);
564
+ }
565
+ this.#actionsInFlight.clear();
507
566
  }
508
567
 
509
568
  this.#transport = undefined;
510
569
 
511
570
  // Automatically reconnect. Skip if already attempting to connect.
512
571
  if (!this.#disposed && !this.#connecting) {
572
+ logger().debug({
573
+ msg: "triggering reconnect",
574
+ connectionId: this.#connectionId,
575
+ messageQueueLength: this.#messageQueue.length,
576
+ });
513
577
  // TODO: Fetch actor to check if it's destroyed
514
578
  // TODO: Add backoff for reconnect
515
579
  // TODO: Add a way of preserving connection ID for connection state
@@ -659,9 +723,26 @@ enc
659
723
  let queueMessage = false;
660
724
  if (!this.#transport) {
661
725
  // No transport connected yet
726
+ logger().debug({ msg: "no transport, queueing message" });
662
727
  queueMessage = true;
663
728
  } else if ("websocket" in this.#transport) {
664
- if (this.#transport.websocket.readyState === 1) {
729
+ const readyState = this.#transport.websocket.readyState;
730
+ logger().debug({
731
+ msg: "websocket send attempt",
732
+ readyState,
733
+ readyStateString:
734
+ readyState === 0
735
+ ? "CONNECTING"
736
+ : readyState === 1
737
+ ? "OPEN"
738
+ : readyState === 2
739
+ ? "CLOSING"
740
+ : "CLOSED",
741
+ connectionId: this.#connectionId,
742
+ messageType: (message.body as any).tag,
743
+ actionName: (message.body as any).val?.name,
744
+ });
745
+ if (readyState === 1) {
665
746
  try {
666
747
  const messageSerialized = serializeWithEncoding(
667
748
  this.#encoding,
@@ -677,12 +758,17 @@ enc
677
758
  logger().warn({
678
759
  msg: "failed to send message, added to queue",
679
760
  error,
761
+ connectionId: this.#connectionId,
680
762
  });
681
763
 
682
764
  // Assuming the socket is disconnected and will be reconnected soon
683
765
  queueMessage = true;
684
766
  }
685
767
  } else {
768
+ logger().debug({
769
+ msg: "websocket not open, queueing message",
770
+ readyState,
771
+ });
686
772
  queueMessage = true;
687
773
  }
688
774
  } else if ("sse" in this.#transport) {
@@ -698,7 +784,13 @@ enc
698
784
 
699
785
  if (!opts?.ephemeral && queueMessage) {
700
786
  this.#messageQueue.push(message);
701
- logger().debug({ msg: "queued connection message" });
787
+ logger().debug({
788
+ msg: "queued connection message",
789
+ queueLength: this.#messageQueue.length,
790
+ connectionId: this.#connectionId,
791
+ messageType: (message.body as any).tag,
792
+ actionName: (message.body as any).val?.name,
793
+ });
702
794
  }
703
795
  }
704
796
 
@@ -777,6 +869,22 @@ enc
777
869
  return deserializeWithEncoding(this.#encoding, buffer, TO_CLIENT_VERSIONED);
778
870
  }
779
871
 
872
+ /**
873
+ * Get the actor ID (for testing purposes).
874
+ * @internal
875
+ */
876
+ get actorId(): string | undefined {
877
+ return this.#actorId;
878
+ }
879
+
880
+ /**
881
+ * Get the connection ID (for testing purposes).
882
+ * @internal
883
+ */
884
+ get connectionId(): string | undefined {
885
+ return this.#connectionId;
886
+ }
887
+
780
888
  /**
781
889
  * Disconnects from the actor.
782
890
  *
@@ -814,7 +922,7 @@ enc
814
922
  ) {
815
923
  logger().debug({ msg: "ws already closed or closing" });
816
924
  } else {
817
- const { promise, resolve } = Promise.withResolvers();
925
+ const { promise, resolve } = promiseWithResolvers();
818
926
  ws.addEventListener("close", () => {
819
927
  logger().debug({ msg: "ws closed" });
820
928
  resolve(undefined);
@@ -823,6 +931,30 @@ enc
823
931
  await promise;
824
932
  }
825
933
  } else if ("sse" in this.#transport) {
934
+ // Send close request to server for SSE connections
935
+ if (this.#connectionId && this.#connectionToken) {
936
+ try {
937
+ await sendHttpRequest({
938
+ url: "http://actor/connections/close",
939
+ method: "POST",
940
+ headers: {
941
+ [HEADER_CONN_ID]: this.#connectionId,
942
+ [HEADER_CONN_TOKEN]: this.#connectionToken,
943
+ },
944
+ encoding: this.#encoding,
945
+ skipParseResponse: true,
946
+ customFetch: this.#driver.sendRequest.bind(
947
+ this.#driver,
948
+ this.#actorId!,
949
+ ),
950
+ requestVersionedDataHandler: TO_SERVER_VERSIONED,
951
+ responseVersionedDataHandler: TO_CLIENT_VERSIONED,
952
+ });
953
+ } catch (error) {
954
+ // Ignore errors when closing - connection may already be closed
955
+ logger().warn({ msg: "failed to send close request", error });
956
+ }
957
+ }
826
958
  this.#transport.sse.close();
827
959
  } else {
828
960
  assertUnreachable(this.#transport);
@@ -4,7 +4,6 @@ import type { AnyActorDefinition } from "@/actor/definition";
4
4
  import type { Encoding } from "@/actor/protocol/serde";
5
5
  import { assertUnreachable } from "@/actor/utils";
6
6
  import { deconstructError } from "@/common/utils";
7
- import { importWebSocket } from "@/common/websocket";
8
7
  import {
9
8
  HEADER_CONN_PARAMS,
10
9
  HEADER_ENCODING,
@@ -3,7 +3,7 @@ import type { Transport } from "@/actor/protocol/old";
3
3
  import type { Encoding } from "@/actor/protocol/serde";
4
4
  import type { ManagerDriver } from "@/driver-helpers/mod";
5
5
  import type { ActorQuery } from "@/manager/protocol/query";
6
- import type { Registry } from "@/mod";
6
+ import type { Registry } from "@/registry/mod";
7
7
  import type { ActorActionFunction } from "./actor-common";
8
8
  import {
9
9
  type ActorConn,
@@ -491,7 +491,7 @@ function createActorProxy<AD extends AnyActorDefinition>(
491
491
 
492
492
  // Handle built-in Promise methods and existing properties
493
493
  if (prop === "constructor" || prop in target) {
494
- const value = Reflect.get(target, prop, receiver);
494
+ const value = Reflect.get(target, prop, target);
495
495
  // Preserve method binding
496
496
  if (typeof value === "function") {
497
497
  return value.bind(target);
@@ -14,6 +14,13 @@ export const ClientConfigSchema = z.object({
14
14
  })
15
15
  .default({}),
16
16
 
17
+ token: z
18
+ .string()
19
+ .optional()
20
+ .transform((x) => x ?? getEnvUniversal("RIVET_TOKEN")),
21
+
22
+ headers: z.record(z.string()).optional().default({}),
23
+
17
24
  /** Endpoint to connect to the Rivet engine. Can be configured via RIVET_ENGINE env var. */
18
25
  endpoint: z
19
26
  .string()
@@ -1,5 +1,5 @@
1
1
  import invariant from "invariant";
2
- import { PATH_RAW_WEBSOCKET_PREFIX } from "@/actor/router";
2
+ import { PATH_RAW_WEBSOCKET_PREFIX } from "@/common/actor-router-consts";
3
3
  import { deconstructError } from "@/common/utils";
4
4
  import { HEADER_CONN_PARAMS, type ManagerDriver } from "@/driver-helpers/mod";
5
5
  import type { ActorQuery } from "@/manager/protocol/query";
@@ -1,8 +1,8 @@
1
1
  import * as cbor from "cbor-x";
2
2
  import invariant from "invariant";
3
+ import type { Encoding } from "@/actor/protocol/serde";
3
4
  import { assertUnreachable } from "@/common/utils";
4
5
  import type { VersionedDataHandler } from "@/common/versioned-data";
5
- import type { Encoding } from "@/mod";
6
6
  import type { HttpResponseError } from "@/schemas/client-protocol/mod";
7
7
  import { HTTP_RESPONSE_ERROR_VERSIONED } from "@/schemas/client-protocol/versioned";
8
8
  import {