rivetkit 2.0.3 → 2.0.5
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/README.md +11 -0
- package/dist/schemas/actor-persist/v1.ts +21 -24
- package/dist/schemas/client-protocol/v1.ts +6 -0
- package/dist/tsup/actor/errors.cjs +10 -2
- package/dist/tsup/actor/errors.cjs.map +1 -1
- package/dist/tsup/actor/errors.d.cts +17 -4
- package/dist/tsup/actor/errors.d.ts +17 -4
- package/dist/tsup/actor/errors.js +11 -3
- package/dist/tsup/{chunk-4NSUQZ2H.js → chunk-2MD57QF4.js} +119 -115
- package/dist/tsup/chunk-2MD57QF4.js.map +1 -0
- package/dist/tsup/{chunk-GIR3AFFI.cjs → chunk-5QGQK44L.cjs} +103 -44
- package/dist/tsup/chunk-5QGQK44L.cjs.map +1 -0
- package/dist/tsup/chunk-5YTI25C3.cjs +250 -0
- package/dist/tsup/chunk-5YTI25C3.cjs.map +1 -0
- package/dist/tsup/chunk-B2QGJGZQ.js +338 -0
- package/dist/tsup/chunk-B2QGJGZQ.js.map +1 -0
- package/dist/tsup/{chunk-3H7O2A7I.js → chunk-CFFKMUYH.js} +61 -22
- package/dist/tsup/chunk-CFFKMUYH.js.map +1 -0
- package/dist/tsup/{chunk-FLMTTN27.js → chunk-CKA54YQN.js} +15 -8
- package/dist/tsup/chunk-CKA54YQN.js.map +1 -0
- package/dist/tsup/chunk-D7NWUCRK.cjs +20 -0
- package/dist/tsup/chunk-D7NWUCRK.cjs.map +1 -0
- package/dist/tsup/{chunk-FCCPJNMA.cjs → chunk-FGFT4FVX.cjs} +12 -27
- package/dist/tsup/chunk-FGFT4FVX.cjs.map +1 -0
- package/dist/tsup/chunk-I5VTWPHW.js +20 -0
- package/dist/tsup/chunk-I5VTWPHW.js.map +1 -0
- package/dist/tsup/{chunk-6WKQDDUD.cjs → chunk-IRMBWX36.cjs} +146 -142
- package/dist/tsup/chunk-IRMBWX36.cjs.map +1 -0
- package/dist/tsup/chunk-L7QRXNWP.js +6562 -0
- package/dist/tsup/chunk-L7QRXNWP.js.map +1 -0
- package/dist/tsup/{chunk-R2OPSKIV.cjs → chunk-LZIBTLEY.cjs} +20 -13
- package/dist/tsup/chunk-LZIBTLEY.cjs.map +1 -0
- package/dist/tsup/chunk-MRZS2J4X.cjs +6562 -0
- package/dist/tsup/chunk-MRZS2J4X.cjs.map +1 -0
- package/dist/tsup/{chunk-PO4VLDWA.js → chunk-PG3K2LI7.js} +3 -5
- package/dist/tsup/chunk-PG3K2LI7.js.map +1 -0
- package/dist/tsup/{chunk-TZJKSBUQ.cjs → chunk-PHSQJ6QI.cjs} +3 -5
- package/dist/tsup/chunk-PHSQJ6QI.cjs.map +1 -0
- package/dist/tsup/chunk-RM2SVURR.cjs +338 -0
- package/dist/tsup/chunk-RM2SVURR.cjs.map +1 -0
- package/dist/tsup/{chunk-OGAPU3UG.cjs → chunk-WADSS5X4.cjs} +66 -27
- package/dist/tsup/chunk-WADSS5X4.cjs.map +1 -0
- package/dist/tsup/chunk-WNGOBAA7.js +250 -0
- package/dist/tsup/chunk-WNGOBAA7.js.map +1 -0
- package/dist/tsup/{chunk-INGJP237.js → chunk-YPZFLUO6.js} +103 -44
- package/dist/tsup/chunk-YPZFLUO6.js.map +1 -0
- package/dist/tsup/{chunk-6PDXBYI5.js → chunk-YW6Y6VNE.js} +8 -23
- package/dist/tsup/chunk-YW6Y6VNE.js.map +1 -0
- package/dist/tsup/client/mod.cjs +10 -10
- package/dist/tsup/client/mod.d.cts +7 -13
- package/dist/tsup/client/mod.d.ts +7 -13
- package/dist/tsup/client/mod.js +9 -9
- package/dist/tsup/common/log.cjs +12 -4
- package/dist/tsup/common/log.cjs.map +1 -1
- package/dist/tsup/common/log.d.cts +23 -17
- package/dist/tsup/common/log.d.ts +23 -17
- package/dist/tsup/common/log.js +15 -7
- package/dist/tsup/common/websocket.cjs +5 -5
- package/dist/tsup/common/websocket.js +4 -4
- package/dist/tsup/{common-CpqORuCq.d.cts → common-CXCe7s6i.d.cts} +2 -2
- package/dist/tsup/{common-CpqORuCq.d.ts → common-CXCe7s6i.d.ts} +2 -2
- package/dist/tsup/{connection-BwUMoe6n.d.ts → connection-BvE-Oq7t.d.ts} +215 -234
- package/dist/tsup/{connection-BR_Ve4ku.d.cts → connection-DTzmWwU5.d.cts} +215 -234
- package/dist/tsup/driver-helpers/mod.cjs +6 -9
- package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
- package/dist/tsup/driver-helpers/mod.d.cts +5 -6
- package/dist/tsup/driver-helpers/mod.d.ts +5 -6
- package/dist/tsup/driver-helpers/mod.js +6 -9
- package/dist/tsup/driver-test-suite/mod.cjs +615 -1357
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +12 -6
- package/dist/tsup/driver-test-suite/mod.d.ts +12 -6
- package/dist/tsup/driver-test-suite/mod.js +1334 -2076
- package/dist/tsup/driver-test-suite/mod.js.map +1 -1
- package/dist/tsup/inspector/mod.cjs +6 -8
- package/dist/tsup/inspector/mod.cjs.map +1 -1
- package/dist/tsup/inspector/mod.d.cts +3 -3
- package/dist/tsup/inspector/mod.d.ts +3 -3
- package/dist/tsup/inspector/mod.js +8 -10
- package/dist/tsup/mod.cjs +9 -15
- package/dist/tsup/mod.cjs.map +1 -1
- package/dist/tsup/mod.d.cts +47 -42
- package/dist/tsup/mod.d.ts +47 -42
- package/dist/tsup/mod.js +10 -16
- package/dist/tsup/{router-endpoints-DAbqVFx2.d.ts → router-endpoints-CctffZNL.d.cts} +2 -3
- package/dist/tsup/{router-endpoints-AYkXG8Tl.d.cts → router-endpoints-DFm1BglJ.d.ts} +2 -3
- package/dist/tsup/test/mod.cjs +10 -14
- package/dist/tsup/test/mod.cjs.map +1 -1
- package/dist/tsup/test/mod.d.cts +4 -5
- package/dist/tsup/test/mod.d.ts +4 -5
- package/dist/tsup/test/mod.js +9 -13
- package/dist/tsup/{utils-CT0cv4jd.d.ts → utils-fwx3o3K9.d.cts} +1 -0
- package/dist/tsup/{utils-CT0cv4jd.d.cts → utils-fwx3o3K9.d.ts} +1 -0
- package/dist/tsup/utils.cjs +5 -3
- package/dist/tsup/utils.cjs.map +1 -1
- package/dist/tsup/utils.d.cts +19 -2
- package/dist/tsup/utils.d.ts +19 -2
- package/dist/tsup/utils.js +4 -2
- package/package.json +6 -6
- package/src/actor/action.ts +1 -5
- package/src/actor/config.ts +27 -295
- package/src/actor/connection.ts +9 -12
- package/src/actor/context.ts +1 -4
- package/src/actor/definition.ts +7 -11
- package/src/actor/errors.ts +98 -36
- package/src/actor/generic-conn-driver.ts +28 -16
- package/src/actor/instance.ts +177 -133
- package/src/actor/log.ts +4 -13
- package/src/actor/mod.ts +0 -5
- package/src/actor/protocol/old.ts +42 -26
- package/src/actor/protocol/serde.ts +1 -1
- package/src/actor/router-endpoints.ts +47 -39
- package/src/actor/router.ts +22 -19
- package/src/actor/unstable-react.ts +1 -1
- package/src/actor/utils.ts +6 -2
- package/src/client/actor-common.ts +1 -1
- package/src/client/actor-conn.ts +152 -91
- package/src/client/actor-handle.ts +85 -25
- package/src/client/actor-query.ts +65 -0
- package/src/client/client.ts +29 -98
- package/src/client/config.ts +44 -0
- package/src/client/errors.ts +1 -0
- package/src/client/log.ts +2 -4
- package/src/client/mod.ts +16 -12
- package/src/client/raw-utils.ts +82 -25
- package/src/client/utils.ts +5 -3
- package/src/common/fake-event-source.ts +10 -9
- package/src/common/inline-websocket-adapter2.ts +39 -30
- package/src/common/log.ts +176 -101
- package/src/common/logfmt.ts +21 -30
- package/src/common/router.ts +12 -19
- package/src/common/utils.ts +27 -13
- package/src/common/websocket.ts +0 -1
- package/src/driver-helpers/mod.ts +1 -1
- package/src/driver-test-suite/log.ts +1 -3
- package/src/driver-test-suite/mod.ts +87 -61
- package/src/driver-test-suite/test-inline-client-driver.ts +441 -255
- package/src/driver-test-suite/tests/actor-error-handling.ts +4 -12
- package/src/driver-test-suite/tests/actor-handle.ts +33 -0
- package/src/driver-test-suite/tests/actor-inspector.ts +2 -1
- package/src/driver-test-suite/tests/manager-driver.ts +5 -3
- package/src/driver-test-suite/tests/raw-http-direct-registry.ts +227 -226
- package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +393 -392
- package/src/driver-test-suite/tests/request-access.ts +112 -126
- package/src/driver-test-suite/utils.ts +10 -6
- package/src/drivers/default.ts +7 -4
- package/src/drivers/engine/actor-driver.ts +22 -13
- package/src/drivers/engine/config.ts +2 -10
- package/src/drivers/engine/kv.ts +1 -1
- package/src/drivers/engine/log.ts +1 -3
- package/src/drivers/engine/mod.ts +2 -3
- package/src/drivers/file-system/actor.ts +1 -1
- package/src/drivers/file-system/global-state.ts +36 -21
- package/src/drivers/file-system/log.ts +1 -3
- package/src/drivers/file-system/manager.ts +33 -15
- package/src/inspector/config.ts +9 -4
- package/src/inspector/log.ts +1 -1
- package/src/inspector/manager.ts +2 -2
- package/src/inspector/utils.ts +1 -1
- package/src/manager/driver.ts +10 -2
- package/src/manager/hono-websocket-adapter.ts +21 -12
- package/src/manager/log.ts +2 -4
- package/src/manager/mod.ts +1 -1
- package/src/manager/router.ts +378 -1390
- package/src/manager-api/routes/actors-create.ts +16 -0
- package/src/manager-api/routes/actors-delete.ts +4 -0
- package/src/manager-api/routes/actors-get-by-id.ts +7 -0
- package/src/manager-api/routes/actors-get-or-create-by-id.ts +29 -0
- package/src/manager-api/routes/actors-get.ts +7 -0
- package/src/manager-api/routes/common.ts +18 -0
- package/src/mod.ts +0 -2
- package/src/registry/config.ts +1 -1
- package/src/registry/log.ts +2 -4
- package/src/registry/mod.ts +63 -34
- package/src/registry/run-config.ts +39 -26
- package/src/registry/serve.ts +4 -5
- package/src/remote-manager-driver/actor-http-client.ts +74 -0
- package/src/remote-manager-driver/actor-websocket-client.ts +64 -0
- package/src/remote-manager-driver/api-endpoints.ts +79 -0
- package/src/remote-manager-driver/api-utils.ts +46 -0
- package/src/remote-manager-driver/log.ts +5 -0
- package/src/remote-manager-driver/mod.ts +275 -0
- package/src/{drivers/engine → remote-manager-driver}/ws-proxy.ts +24 -14
- package/src/serde.ts +8 -2
- package/src/test/log.ts +1 -3
- package/src/test/mod.ts +17 -16
- package/src/utils.ts +53 -0
- package/dist/tsup/chunk-2CRLFV6Z.cjs +0 -202
- package/dist/tsup/chunk-2CRLFV6Z.cjs.map +0 -1
- package/dist/tsup/chunk-3H7O2A7I.js.map +0 -1
- package/dist/tsup/chunk-42I3OZ3Q.js +0 -15
- package/dist/tsup/chunk-42I3OZ3Q.js.map +0 -1
- package/dist/tsup/chunk-4NSUQZ2H.js.map +0 -1
- package/dist/tsup/chunk-6PDXBYI5.js.map +0 -1
- package/dist/tsup/chunk-6WKQDDUD.cjs.map +0 -1
- package/dist/tsup/chunk-CTBOSFUH.cjs +0 -116
- package/dist/tsup/chunk-CTBOSFUH.cjs.map +0 -1
- package/dist/tsup/chunk-EGVZZFE2.js +0 -2857
- package/dist/tsup/chunk-EGVZZFE2.js.map +0 -1
- package/dist/tsup/chunk-FCCPJNMA.cjs.map +0 -1
- package/dist/tsup/chunk-FLMTTN27.js.map +0 -1
- package/dist/tsup/chunk-GIR3AFFI.cjs.map +0 -1
- package/dist/tsup/chunk-INGJP237.js.map +0 -1
- package/dist/tsup/chunk-KJCJLKRM.js +0 -116
- package/dist/tsup/chunk-KJCJLKRM.js.map +0 -1
- package/dist/tsup/chunk-KUPQZYUQ.cjs +0 -15
- package/dist/tsup/chunk-KUPQZYUQ.cjs.map +0 -1
- package/dist/tsup/chunk-O2MBYIXO.cjs +0 -2857
- package/dist/tsup/chunk-O2MBYIXO.cjs.map +0 -1
- package/dist/tsup/chunk-OGAPU3UG.cjs.map +0 -1
- package/dist/tsup/chunk-OV6AYD4S.js +0 -4406
- package/dist/tsup/chunk-OV6AYD4S.js.map +0 -1
- package/dist/tsup/chunk-PO4VLDWA.js.map +0 -1
- package/dist/tsup/chunk-R2OPSKIV.cjs.map +0 -1
- package/dist/tsup/chunk-TZJKSBUQ.cjs.map +0 -1
- package/dist/tsup/chunk-UBUC5C3G.cjs +0 -189
- package/dist/tsup/chunk-UBUC5C3G.cjs.map +0 -1
- package/dist/tsup/chunk-UIM22YJL.cjs +0 -4406
- package/dist/tsup/chunk-UIM22YJL.cjs.map +0 -1
- package/dist/tsup/chunk-URVFQMYI.cjs +0 -230
- package/dist/tsup/chunk-URVFQMYI.cjs.map +0 -1
- package/dist/tsup/chunk-UVUPOS46.js +0 -230
- package/dist/tsup/chunk-UVUPOS46.js.map +0 -1
- package/dist/tsup/chunk-VRRHBNJC.js +0 -189
- package/dist/tsup/chunk-VRRHBNJC.js.map +0 -1
- package/dist/tsup/chunk-XFSS33EQ.js +0 -202
- package/dist/tsup/chunk-XFSS33EQ.js.map +0 -1
- package/src/client/http-client-driver.ts +0 -326
- package/src/driver-test-suite/tests/actor-auth.ts +0 -591
- package/src/drivers/engine/api-endpoints.ts +0 -128
- package/src/drivers/engine/api-utils.ts +0 -70
- package/src/drivers/engine/manager-driver.ts +0 -391
- package/src/inline-client-driver/log.ts +0 -7
- package/src/inline-client-driver/mod.ts +0 -385
- package/src/manager/auth.ts +0 -121
- /package/src/{drivers/engine → actor}/keys.test.ts +0 -0
- /package/src/{drivers/engine → actor}/keys.ts +0 -0
package/src/manager/router.ts
CHANGED
|
@@ -1,127 +1,52 @@
|
|
|
1
1
|
import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
|
|
2
2
|
import * as cbor from "cbor-x";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type MiddlewareHandler,
|
|
7
|
-
} from "hono";
|
|
8
|
-
import { cors } from "hono/cors";
|
|
9
|
-
import { streamSSE } from "hono/streaming";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { cors as corsMiddleware } from "hono/cors";
|
|
5
|
+
import { createMiddleware } from "hono/factory";
|
|
10
6
|
import type { WSContext } from "hono/ws";
|
|
11
7
|
import invariant from "invariant";
|
|
12
|
-
import type { CloseEvent, MessageEvent, WebSocket } from "ws";
|
|
13
8
|
import { z } from "zod";
|
|
14
|
-
import * as errors from "@/actor/errors";
|
|
15
|
-
import type { Transport } from "@/actor/protocol/old";
|
|
16
|
-
import type { Encoding } from "@/actor/protocol/serde";
|
|
17
|
-
import {
|
|
18
|
-
PATH_CONNECT_WEBSOCKET,
|
|
19
|
-
PATH_RAW_WEBSOCKET_PREFIX,
|
|
20
|
-
} from "@/actor/router";
|
|
21
9
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
HEADER_CONN_PARAMS,
|
|
30
|
-
HEADER_CONN_TOKEN,
|
|
31
|
-
HEADER_ENCODING,
|
|
32
|
-
} from "@/actor/router-endpoints";
|
|
33
|
-
import type { ClientDriver } from "@/client/client";
|
|
10
|
+
ActorNotFound,
|
|
11
|
+
FeatureNotImplemented,
|
|
12
|
+
MissingActorHeader,
|
|
13
|
+
Unsupported,
|
|
14
|
+
WebSocketsNotEnabled,
|
|
15
|
+
} from "@/actor/errors";
|
|
16
|
+
import type { Encoding } from "@/client/mod";
|
|
34
17
|
import {
|
|
35
18
|
handleRouteError,
|
|
36
19
|
handleRouteNotFound,
|
|
37
20
|
loggerMiddleware,
|
|
38
21
|
} from "@/common/router";
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} from "@/
|
|
22
|
+
import { deconstructError, noopNext } from "@/common/utils";
|
|
23
|
+
import { HEADER_ACTOR_ID } from "@/driver-helpers/mod";
|
|
24
|
+
import type {
|
|
25
|
+
TestInlineDriverCallRequest,
|
|
26
|
+
TestInlineDriverCallResponse,
|
|
27
|
+
} from "@/driver-test-suite/test-inline-client-driver";
|
|
45
28
|
import { createManagerInspectorRouter } from "@/inspector/manager";
|
|
46
29
|
import { secureInspector } from "@/inspector/utils";
|
|
47
|
-
import
|
|
30
|
+
import {
|
|
31
|
+
type ActorsCreateRequest,
|
|
32
|
+
ActorsCreateRequestSchema,
|
|
33
|
+
ActorsCreateResponseSchema,
|
|
34
|
+
} from "@/manager-api/routes/actors-create";
|
|
35
|
+
import { ActorsDeleteResponseSchema } from "@/manager-api/routes/actors-delete";
|
|
36
|
+
import { ActorsGetResponseSchema } from "@/manager-api/routes/actors-get";
|
|
37
|
+
import { ActorsGetByIdResponseSchema } from "@/manager-api/routes/actors-get-by-id";
|
|
38
|
+
import {
|
|
39
|
+
type ActorsGetOrCreateByIdRequest,
|
|
40
|
+
ActorsGetOrCreateByIdRequestSchema,
|
|
41
|
+
ActorsGetOrCreateByIdResponseSchema,
|
|
42
|
+
} from "@/manager-api/routes/actors-get-or-create-by-id";
|
|
43
|
+
import { RivetIdSchema } from "@/manager-api/routes/common";
|
|
44
|
+
import type { UniversalWebSocket, UpgradeWebSocketArgs } from "@/mod";
|
|
48
45
|
import type { RegistryConfig } from "@/registry/config";
|
|
49
46
|
import type { RunConfig } from "@/registry/run-config";
|
|
50
|
-
import
|
|
51
|
-
import {
|
|
52
|
-
HTTP_RESOLVE_RESPONSE_VERSIONED,
|
|
53
|
-
TO_CLIENT_VERSIONED,
|
|
54
|
-
} from "@/schemas/client-protocol/versioned";
|
|
55
|
-
import { serializeWithEncoding } from "@/serde";
|
|
56
|
-
import { bufferToArrayBuffer } from "@/utils";
|
|
57
|
-
import { authenticateEndpoint } from "./auth";
|
|
47
|
+
import { stringifyError } from "@/utils";
|
|
58
48
|
import type { ManagerDriver } from "./driver";
|
|
59
49
|
import { logger } from "./log";
|
|
60
|
-
import type { ActorQuery } from "./protocol/query";
|
|
61
|
-
import {
|
|
62
|
-
ActorQuerySchema,
|
|
63
|
-
ConnectRequestSchema,
|
|
64
|
-
ConnectWebSocketRequestSchema,
|
|
65
|
-
ConnMessageRequestSchema,
|
|
66
|
-
ResolveRequestSchema,
|
|
67
|
-
} from "./protocol/query";
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Parse WebSocket protocol headers for query and connection parameters
|
|
71
|
-
*/
|
|
72
|
-
function parseWebSocketProtocols(protocols: string | undefined): {
|
|
73
|
-
queryRaw: string | undefined;
|
|
74
|
-
encodingRaw: string | undefined;
|
|
75
|
-
connParamsRaw: string | undefined;
|
|
76
|
-
} {
|
|
77
|
-
let queryRaw: string | undefined;
|
|
78
|
-
let encodingRaw: string | undefined;
|
|
79
|
-
let connParamsRaw: string | undefined;
|
|
80
|
-
|
|
81
|
-
if (protocols) {
|
|
82
|
-
const protocolList = protocols.split(",").map((p) => p.trim());
|
|
83
|
-
for (const protocol of protocolList) {
|
|
84
|
-
if (protocol.startsWith("query.")) {
|
|
85
|
-
queryRaw = decodeURIComponent(protocol.substring("query.".length));
|
|
86
|
-
} else if (protocol.startsWith("encoding.")) {
|
|
87
|
-
encodingRaw = protocol.substring("encoding.".length);
|
|
88
|
-
} else if (protocol.startsWith("conn_params.")) {
|
|
89
|
-
connParamsRaw = decodeURIComponent(
|
|
90
|
-
protocol.substring("conn_params.".length),
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return { queryRaw, encodingRaw, connParamsRaw };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const OPENAPI_ENCODING = z.string().openapi({
|
|
100
|
-
description: "The encoding format to use for the response (json, cbor)",
|
|
101
|
-
example: "json",
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const OPENAPI_ACTOR_QUERY = z.string().openapi({
|
|
105
|
-
description: "Actor query information",
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const OPENAPI_CONN_PARAMS = z.string().openapi({
|
|
109
|
-
description: "Connection parameters",
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
const OPENAPI_ACTOR_ID = z.string().openapi({
|
|
113
|
-
description: "Actor ID (used in some endpoints)",
|
|
114
|
-
example: "actor-123456",
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const OPENAPI_CONN_ID = z.string().openapi({
|
|
118
|
-
description: "Connection ID",
|
|
119
|
-
example: "conn-123456",
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
const OPENAPI_CONN_TOKEN = z.string().openapi({
|
|
123
|
-
description: "Connection token",
|
|
124
|
-
});
|
|
125
50
|
|
|
126
51
|
function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
|
|
127
52
|
return {
|
|
@@ -144,17 +69,9 @@ function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
|
|
|
144
69
|
};
|
|
145
70
|
}
|
|
146
71
|
|
|
147
|
-
/**
|
|
148
|
-
* Only use `validateBody` to `true` if you need to export OpenAPI JSON.
|
|
149
|
-
*
|
|
150
|
-
* If left enabled for production, this will cause errors. We disable JSON validation since:
|
|
151
|
-
* - It prevents us from proxying requests, since validating the body requires consuming the body so we can't forward the body
|
|
152
|
-
* - We validate all types at the actor router layer since most requests are proxied
|
|
153
|
-
*/
|
|
154
72
|
export function createManagerRouter(
|
|
155
73
|
registryConfig: RegistryConfig,
|
|
156
74
|
runConfig: RunConfig,
|
|
157
|
-
inlineClientDriver: ClientDriver,
|
|
158
75
|
managerDriver: ManagerDriver,
|
|
159
76
|
validateBody: boolean,
|
|
160
77
|
): { router: Hono; openapi: OpenAPIHono } {
|
|
@@ -164,443 +81,323 @@ export function createManagerRouter(
|
|
|
164
81
|
|
|
165
82
|
router.use("*", loggerMiddleware(logger()));
|
|
166
83
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
path.endsWith("/actors/inspect")
|
|
180
|
-
) {
|
|
181
|
-
return next();
|
|
84
|
+
const cors = runConfig.cors
|
|
85
|
+
? corsMiddleware(runConfig.cors)
|
|
86
|
+
: createMiddleware((_c, next) => next());
|
|
87
|
+
|
|
88
|
+
// Actor proxy middleware - intercept requests with x-rivet-target=actor
|
|
89
|
+
router.use("*", cors, async (c, next) => {
|
|
90
|
+
const target = c.req.header("x-rivet-target");
|
|
91
|
+
const actorId = c.req.header("x-rivet-actor");
|
|
92
|
+
|
|
93
|
+
if (target === "actor") {
|
|
94
|
+
if (!actorId) {
|
|
95
|
+
throw new MissingActorHeader();
|
|
182
96
|
}
|
|
183
97
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (inspectorOrigin !== undefined) {
|
|
191
|
-
if (typeof inspectorOrigin === "function") {
|
|
192
|
-
const allowed = inspectorOrigin(origin, c);
|
|
193
|
-
if (allowed) return allowed;
|
|
194
|
-
// Proceed to next CORS config if none provided
|
|
195
|
-
} else if (Array.isArray(inspectorOrigin)) {
|
|
196
|
-
return inspectorOrigin.includes(origin) ? origin : undefined;
|
|
197
|
-
} else {
|
|
198
|
-
return inspectorOrigin;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
98
|
+
logger().debug({
|
|
99
|
+
msg: "proxying request to actor",
|
|
100
|
+
actorId,
|
|
101
|
+
path: c.req.path,
|
|
102
|
+
method: c.req.method,
|
|
103
|
+
});
|
|
201
104
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
}
|
|
105
|
+
// Handle WebSocket upgrade
|
|
106
|
+
if (c.req.header("upgrade") === "websocket") {
|
|
107
|
+
const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
|
|
108
|
+
if (!upgradeWebSocket) {
|
|
109
|
+
throw new WebSocketsNotEnabled();
|
|
110
|
+
}
|
|
210
111
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
112
|
+
// For WebSocket, use the driver's proxyWebSocket method
|
|
113
|
+
// Extract any additional headers that might be needed
|
|
114
|
+
const encoding =
|
|
115
|
+
c.req.header("X-RivetKit-Encoding") ||
|
|
116
|
+
c.req.header("x-rivet-encoding") ||
|
|
117
|
+
"json";
|
|
118
|
+
const connParams =
|
|
119
|
+
c.req.header("X-RivetKit-Conn-Params") ||
|
|
120
|
+
c.req.header("x-rivet-conn-params");
|
|
121
|
+
const authData =
|
|
122
|
+
c.req.header("X-RivetKit-Auth-Data") ||
|
|
123
|
+
c.req.header("x-rivet-auth-data");
|
|
124
|
+
|
|
125
|
+
// Include query string if present
|
|
126
|
+
const pathWithQuery = c.req.url.includes("?")
|
|
127
|
+
? c.req.path + c.req.url.substring(c.req.url.indexOf("?"))
|
|
128
|
+
: c.req.path;
|
|
129
|
+
|
|
130
|
+
return await managerDriver.proxyWebSocket(
|
|
131
|
+
c,
|
|
132
|
+
pathWithQuery,
|
|
133
|
+
actorId,
|
|
134
|
+
encoding as any, // Will be validated by driver
|
|
135
|
+
connParams ? JSON.parse(connParams) : undefined,
|
|
136
|
+
authData ? JSON.parse(authData) : undefined,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
221
139
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
140
|
+
// Handle regular HTTP requests
|
|
141
|
+
// Preserve all headers except the routing headers
|
|
142
|
+
const proxyHeaders = new Headers(c.req.raw.headers);
|
|
143
|
+
proxyHeaders.delete("x-rivet-target");
|
|
144
|
+
proxyHeaders.delete("x-rivet-actor");
|
|
228
145
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
146
|
+
// Build the proxy request with the actor URL format
|
|
147
|
+
const url = new URL(c.req.url);
|
|
148
|
+
const proxyUrl = new URL(`http://actor${url.pathname}${url.search}`);
|
|
149
|
+
|
|
150
|
+
const proxyRequest = new Request(proxyUrl, {
|
|
151
|
+
method: c.req.raw.method,
|
|
152
|
+
headers: proxyHeaders,
|
|
153
|
+
body: c.req.raw.body,
|
|
154
|
+
signal: c.req.raw.signal,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return await managerDriver.proxyRequest(c, proxyRequest, actorId);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return next();
|
|
161
|
+
});
|
|
245
162
|
|
|
246
163
|
// GET /
|
|
247
|
-
router.get("/", (c
|
|
164
|
+
router.get("/", cors, (c) => {
|
|
248
165
|
return c.text(
|
|
249
|
-
"This is
|
|
166
|
+
"This is a RivetKit server.\n\nLearn more at https://rivetkit.org",
|
|
250
167
|
);
|
|
251
168
|
});
|
|
252
169
|
|
|
253
|
-
//
|
|
170
|
+
// GET /actors/by-id
|
|
254
171
|
{
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
172
|
+
const route = createRoute({
|
|
173
|
+
middleware: [cors],
|
|
174
|
+
method: "get",
|
|
175
|
+
path: "/actors/by-id",
|
|
176
|
+
request: {
|
|
177
|
+
query: z.object({
|
|
178
|
+
name: z.string(),
|
|
179
|
+
key: z.string(),
|
|
259
180
|
}),
|
|
260
|
-
}
|
|
261
|
-
|
|
181
|
+
},
|
|
182
|
+
responses: buildOpenApiResponses(
|
|
183
|
+
ActorsGetByIdResponseSchema,
|
|
184
|
+
validateBody,
|
|
185
|
+
),
|
|
186
|
+
});
|
|
262
187
|
|
|
263
|
-
|
|
264
|
-
.
|
|
265
|
-
i: z.string().openapi({
|
|
266
|
-
example: "actor-123",
|
|
267
|
-
}),
|
|
268
|
-
})
|
|
269
|
-
.openapi("ResolveResponse");
|
|
188
|
+
router.openapi(route, async (c) => {
|
|
189
|
+
const { name, key } = c.req.valid("query");
|
|
270
190
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
191
|
+
// Get actor by key from the driver
|
|
192
|
+
const actorOutput = await managerDriver.getWithKey({
|
|
193
|
+
c,
|
|
194
|
+
name,
|
|
195
|
+
key: [key], // Convert string to ActorKey array
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return c.json({
|
|
199
|
+
actor_id: actorOutput?.actorId || null,
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// PUT /actors/by-id
|
|
205
|
+
{
|
|
206
|
+
const route = createRoute({
|
|
207
|
+
cors: [cors],
|
|
208
|
+
method: "put",
|
|
209
|
+
path: "/actors/by-id",
|
|
274
210
|
request: {
|
|
275
211
|
body: {
|
|
276
212
|
content: validateBody
|
|
277
213
|
? {
|
|
278
214
|
"application/json": {
|
|
279
|
-
schema:
|
|
215
|
+
schema: ActorsGetOrCreateByIdRequestSchema,
|
|
280
216
|
},
|
|
281
217
|
}
|
|
282
218
|
: {},
|
|
283
219
|
},
|
|
284
|
-
headers: z.object({
|
|
285
|
-
[HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY,
|
|
286
|
-
}),
|
|
287
220
|
},
|
|
288
|
-
responses: buildOpenApiResponses(
|
|
221
|
+
responses: buildOpenApiResponses(
|
|
222
|
+
ActorsGetOrCreateByIdResponseSchema,
|
|
223
|
+
validateBody,
|
|
224
|
+
),
|
|
289
225
|
});
|
|
290
226
|
|
|
291
|
-
router.openapi(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
227
|
+
router.openapi(route, async (c) => {
|
|
228
|
+
const body = validateBody
|
|
229
|
+
? await c.req.json<ActorsGetOrCreateByIdRequest>()
|
|
230
|
+
: await c.req.json();
|
|
295
231
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
router.use("*", (c, next) => {
|
|
300
|
-
if (c.req.path.endsWith("/actors/connect/websocket")) {
|
|
301
|
-
return handleWebSocketConnectRequest(
|
|
302
|
-
c,
|
|
303
|
-
registryConfig,
|
|
304
|
-
runConfig,
|
|
305
|
-
managerDriver,
|
|
306
|
-
);
|
|
232
|
+
// Parse and validate the request body if validation is enabled
|
|
233
|
+
if (validateBody) {
|
|
234
|
+
ActorsGetOrCreateByIdRequestSchema.parse(body);
|
|
307
235
|
}
|
|
308
236
|
|
|
309
|
-
|
|
310
|
-
|
|
237
|
+
// Check if actor already exists
|
|
238
|
+
const existingActor = await managerDriver.getWithKey({
|
|
239
|
+
c,
|
|
240
|
+
name: body.name,
|
|
241
|
+
key: [body.key], // Convert string to ActorKey array
|
|
242
|
+
});
|
|
311
243
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
244
|
+
if (existingActor) {
|
|
245
|
+
return c.json({
|
|
246
|
+
actor_id: existingActor.actorId,
|
|
247
|
+
created: false,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Create new actor
|
|
252
|
+
const newActor = await managerDriver.getOrCreateWithKey({
|
|
253
|
+
c,
|
|
254
|
+
name: body.name,
|
|
255
|
+
key: [body.key], // Convert string to ActorKey array
|
|
256
|
+
input: body.input
|
|
257
|
+
? cbor.decode(Buffer.from(body.input, "base64"))
|
|
258
|
+
: undefined,
|
|
259
|
+
region: undefined, // Not provided in the request schema
|
|
260
|
+
});
|
|
322
261
|
|
|
323
|
-
|
|
324
|
-
|
|
262
|
+
return c.json({
|
|
263
|
+
actor_id: newActor.actorId,
|
|
264
|
+
created: true,
|
|
265
|
+
});
|
|
325
266
|
});
|
|
326
267
|
}
|
|
327
268
|
|
|
328
|
-
// GET /actors/
|
|
269
|
+
// GET /actors/{actor_id}
|
|
329
270
|
{
|
|
330
|
-
const
|
|
271
|
+
const route = createRoute({
|
|
272
|
+
middleware: [cors],
|
|
331
273
|
method: "get",
|
|
332
|
-
path: "/actors/
|
|
274
|
+
path: "/actors/{actor_id}",
|
|
333
275
|
request: {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
[HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY,
|
|
337
|
-
[HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(),
|
|
276
|
+
params: z.object({
|
|
277
|
+
actor_id: RivetIdSchema,
|
|
338
278
|
}),
|
|
339
279
|
},
|
|
340
|
-
responses:
|
|
341
|
-
200: {
|
|
342
|
-
description: "SSE stream",
|
|
343
|
-
content: {
|
|
344
|
-
"text/event-stream": {
|
|
345
|
-
schema: z.unknown(),
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
},
|
|
280
|
+
responses: buildOpenApiResponses(ActorsGetResponseSchema, validateBody),
|
|
350
281
|
});
|
|
351
282
|
|
|
352
|
-
router.openapi(
|
|
353
|
-
|
|
354
|
-
);
|
|
355
|
-
}
|
|
283
|
+
router.openapi(route, async (c) => {
|
|
284
|
+
const { actor_id } = c.req.valid("param");
|
|
356
285
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
name: "action",
|
|
364
|
-
in: "path",
|
|
365
|
-
},
|
|
366
|
-
example: "myAction",
|
|
367
|
-
}),
|
|
368
|
-
})
|
|
369
|
-
.openapi("ActionParams");
|
|
286
|
+
// Get actor by ID from the driver
|
|
287
|
+
const actorOutput = await managerDriver.getForId({
|
|
288
|
+
c,
|
|
289
|
+
name: "", // TODO: The API doesn't provide the name, this may need to be resolved
|
|
290
|
+
actorId: actor_id,
|
|
291
|
+
});
|
|
370
292
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
example: { getForId: { actorId: "actor-123" } },
|
|
375
|
-
}),
|
|
376
|
-
body: z
|
|
377
|
-
.any()
|
|
378
|
-
.optional()
|
|
379
|
-
.openapi({
|
|
380
|
-
example: { param1: "value1", param2: 123 },
|
|
381
|
-
}),
|
|
382
|
-
})
|
|
383
|
-
.openapi("ActionRequest");
|
|
293
|
+
if (!actorOutput) {
|
|
294
|
+
throw new ActorNotFound(actor_id);
|
|
295
|
+
}
|
|
384
296
|
|
|
385
|
-
|
|
297
|
+
// Transform ActorOutput to match ActorSchema
|
|
298
|
+
// Note: Some fields are not available from the driver and need defaults
|
|
299
|
+
const actor = {
|
|
300
|
+
actor_id: actorOutput.actorId,
|
|
301
|
+
name: actorOutput.name,
|
|
302
|
+
key: actorOutput.key,
|
|
303
|
+
namespace_id: "default", // Assert default namespace
|
|
304
|
+
runner_name_selector: "rivetkit", // Assert rivetkit runner
|
|
305
|
+
create_ts: Date.now(), // Not available from driver
|
|
306
|
+
connectable_ts: null,
|
|
307
|
+
destroy_ts: null,
|
|
308
|
+
sleep_ts: null,
|
|
309
|
+
start_ts: null,
|
|
310
|
+
};
|
|
386
311
|
|
|
387
|
-
|
|
388
|
-
method: "post",
|
|
389
|
-
path: "/actors/actions/{action}",
|
|
390
|
-
request: {
|
|
391
|
-
params: ActionParamsSchema,
|
|
392
|
-
body: {
|
|
393
|
-
content: validateBody
|
|
394
|
-
? {
|
|
395
|
-
"application/json": {
|
|
396
|
-
schema: ActionRequestSchema,
|
|
397
|
-
},
|
|
398
|
-
}
|
|
399
|
-
: {},
|
|
400
|
-
},
|
|
401
|
-
headers: z.object({
|
|
402
|
-
[HEADER_ENCODING]: OPENAPI_ENCODING,
|
|
403
|
-
[HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(),
|
|
404
|
-
}),
|
|
405
|
-
},
|
|
406
|
-
responses: buildOpenApiResponses(ActionResponseSchema, validateBody),
|
|
312
|
+
return c.json({ actor });
|
|
407
313
|
});
|
|
408
|
-
|
|
409
|
-
router.openapi(actionRoute, (c) =>
|
|
410
|
-
handleActionRequest(c, registryConfig, runConfig, managerDriver),
|
|
411
|
-
);
|
|
412
314
|
}
|
|
413
315
|
|
|
414
|
-
// POST /actors
|
|
316
|
+
// POST /actors
|
|
415
317
|
{
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
message: z.any().openapi({
|
|
419
|
-
example: { type: "message", content: "Hello, actor!" },
|
|
420
|
-
}),
|
|
421
|
-
})
|
|
422
|
-
.openapi("ConnectionMessageRequest");
|
|
423
|
-
|
|
424
|
-
const ConnectionMessageResponseSchema = z
|
|
425
|
-
.any()
|
|
426
|
-
.openapi("ConnectionMessageResponse");
|
|
427
|
-
|
|
428
|
-
const messageRoute = createRoute({
|
|
318
|
+
const route = createRoute({
|
|
319
|
+
middleware: [cors],
|
|
429
320
|
method: "post",
|
|
430
|
-
path: "/actors
|
|
321
|
+
path: "/actors",
|
|
431
322
|
request: {
|
|
432
323
|
body: {
|
|
433
324
|
content: validateBody
|
|
434
325
|
? {
|
|
435
326
|
"application/json": {
|
|
436
|
-
schema:
|
|
327
|
+
schema: ActorsCreateRequestSchema,
|
|
437
328
|
},
|
|
438
329
|
}
|
|
439
330
|
: {},
|
|
440
331
|
},
|
|
441
|
-
headers: z.object({
|
|
442
|
-
[HEADER_ACTOR_ID]: OPENAPI_ACTOR_ID,
|
|
443
|
-
[HEADER_CONN_ID]: OPENAPI_CONN_ID,
|
|
444
|
-
[HEADER_ENCODING]: OPENAPI_ENCODING,
|
|
445
|
-
[HEADER_CONN_TOKEN]: OPENAPI_CONN_TOKEN,
|
|
446
|
-
}),
|
|
447
332
|
},
|
|
448
333
|
responses: buildOpenApiResponses(
|
|
449
|
-
|
|
334
|
+
ActorsCreateResponseSchema,
|
|
450
335
|
validateBody,
|
|
451
336
|
),
|
|
452
337
|
});
|
|
453
338
|
|
|
454
|
-
router.openapi(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
339
|
+
router.openapi(route, async (c) => {
|
|
340
|
+
const body = validateBody
|
|
341
|
+
? await c.req.json<ActorsCreateRequest>()
|
|
342
|
+
: await c.req.json();
|
|
458
343
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
description: "Raw request body (can be any content type)",
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
const RawHttpResponseSchema = z.any().openapi({
|
|
466
|
-
description: "Raw response from actor's onFetch handler",
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
// Define common route config
|
|
470
|
-
const rawHttpRouteConfig = {
|
|
471
|
-
path: "/actors/raw/http/*",
|
|
472
|
-
request: {
|
|
473
|
-
headers: z.object({
|
|
474
|
-
[HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY.optional(),
|
|
475
|
-
[HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(),
|
|
476
|
-
}),
|
|
477
|
-
body: {
|
|
478
|
-
content: {
|
|
479
|
-
"*/*": {
|
|
480
|
-
schema: RawHttpRequestBodySchema,
|
|
481
|
-
},
|
|
482
|
-
},
|
|
483
|
-
},
|
|
484
|
-
},
|
|
485
|
-
responses: {
|
|
486
|
-
200: {
|
|
487
|
-
description: "Success - response from actor's onFetch handler",
|
|
488
|
-
content: {
|
|
489
|
-
"*/*": {
|
|
490
|
-
schema: RawHttpResponseSchema,
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
},
|
|
494
|
-
404: {
|
|
495
|
-
description: "Actor does not have an onFetch handler",
|
|
496
|
-
},
|
|
497
|
-
500: {
|
|
498
|
-
description: "Internal server error or invalid response from actor",
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
// Create routes for each HTTP method
|
|
504
|
-
const httpMethods = [
|
|
505
|
-
"get",
|
|
506
|
-
"post",
|
|
507
|
-
"put",
|
|
508
|
-
"delete",
|
|
509
|
-
"patch",
|
|
510
|
-
"head",
|
|
511
|
-
"options",
|
|
512
|
-
] as const;
|
|
513
|
-
for (const method of httpMethods) {
|
|
514
|
-
const route = createRoute({
|
|
515
|
-
method,
|
|
516
|
-
...rawHttpRouteConfig,
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
router.openapi(route, async (c) => {
|
|
520
|
-
return handleRawHttpRequest(
|
|
521
|
-
c,
|
|
522
|
-
registryConfig,
|
|
523
|
-
runConfig,
|
|
524
|
-
managerDriver,
|
|
525
|
-
);
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// Raw WebSocket endpoint - /actors/raw/websocket/*
|
|
531
|
-
{
|
|
532
|
-
// HACK: WebSockets don't work with mounts, so we need to dynamically match the trailing path
|
|
533
|
-
router.use("*", async (c, next) => {
|
|
534
|
-
if (c.req.path.includes("/raw/websocket/")) {
|
|
535
|
-
return handleRawWebSocketRequest(
|
|
536
|
-
c,
|
|
537
|
-
registryConfig,
|
|
538
|
-
runConfig,
|
|
539
|
-
managerDriver,
|
|
540
|
-
);
|
|
344
|
+
// Parse and validate the request body if validation is enabled
|
|
345
|
+
if (validateBody) {
|
|
346
|
+
ActorsCreateRequestSchema.parse(body);
|
|
541
347
|
}
|
|
542
348
|
|
|
543
|
-
|
|
544
|
-
|
|
349
|
+
// Create actor using the driver
|
|
350
|
+
const actorOutput = await managerDriver.createActor({
|
|
351
|
+
c,
|
|
352
|
+
name: body.name,
|
|
353
|
+
key: [body.key || crypto.randomUUID()], // Generate key if not provided, convert to ActorKey array
|
|
354
|
+
input: body.input
|
|
355
|
+
? cbor.decode(Buffer.from(body.input, "base64"))
|
|
356
|
+
: undefined,
|
|
357
|
+
region: undefined, // Not provided in the request schema
|
|
358
|
+
});
|
|
545
359
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
description: "Actor does not have an onWebSocket handler",
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
});
|
|
360
|
+
// Transform ActorOutput to match ActorSchema
|
|
361
|
+
const actor = {
|
|
362
|
+
actor_id: actorOutput.actorId,
|
|
363
|
+
name: actorOutput.name,
|
|
364
|
+
key: actorOutput.key,
|
|
365
|
+
namespace_id: "default", // Assert default namespace
|
|
366
|
+
runner_name_selector: "rivetkit", // Assert rivetkit runner
|
|
367
|
+
create_ts: Date.now(),
|
|
368
|
+
connectable_ts: null,
|
|
369
|
+
destroy_ts: null,
|
|
370
|
+
sleep_ts: null,
|
|
371
|
+
start_ts: null,
|
|
372
|
+
};
|
|
563
373
|
|
|
564
|
-
|
|
565
|
-
throw new Error("Should be unreachable");
|
|
374
|
+
return c.json({ actor });
|
|
566
375
|
});
|
|
567
376
|
}
|
|
568
377
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
secureInspector(runConfig),
|
|
593
|
-
async (c, next) => {
|
|
594
|
-
const inspector = managerDriver.inspector;
|
|
595
|
-
invariant(inspector, "inspector not supported on this platform");
|
|
596
|
-
|
|
597
|
-
c.set("inspector", inspector);
|
|
598
|
-
await next();
|
|
599
|
-
},
|
|
600
|
-
)
|
|
601
|
-
.route("/", createManagerInspectorRouter()),
|
|
602
|
-
);
|
|
603
|
-
}
|
|
378
|
+
// TODO:
|
|
379
|
+
// // DELETE /actors/{actor_id}
|
|
380
|
+
// {
|
|
381
|
+
// const route = createRoute({
|
|
382
|
+
// middleware: [cors],
|
|
383
|
+
// method: "delete",
|
|
384
|
+
// path: "/actors/{actor_id}",
|
|
385
|
+
// request: {
|
|
386
|
+
// params: z.object({
|
|
387
|
+
// actor_id: RivetIdSchema,
|
|
388
|
+
// }),
|
|
389
|
+
// },
|
|
390
|
+
// responses: buildOpenApiResponses(
|
|
391
|
+
// ActorsDeleteResponseSchema,
|
|
392
|
+
// validateBody,
|
|
393
|
+
// ),
|
|
394
|
+
// });
|
|
395
|
+
//
|
|
396
|
+
// router.openapi(route, async (c) => {
|
|
397
|
+
// const { actor_id } = c.req.valid("param");
|
|
398
|
+
//
|
|
399
|
+
// });
|
|
400
|
+
// }
|
|
604
401
|
|
|
605
402
|
if (registryConfig.test.enabled) {
|
|
606
403
|
// Add HTTP endpoint to test the inline client
|
|
@@ -612,7 +409,8 @@ export function createManagerRouter(
|
|
|
612
409
|
const { encoding, transport, method, args }: TestInlineDriverCallRequest =
|
|
613
410
|
cbor.decode(new Uint8Array(buffer));
|
|
614
411
|
|
|
615
|
-
logger().debug(
|
|
412
|
+
logger().debug({
|
|
413
|
+
msg: "received inline request",
|
|
616
414
|
encoding,
|
|
617
415
|
transport,
|
|
618
416
|
method,
|
|
@@ -622,9 +420,7 @@ export function createManagerRouter(
|
|
|
622
420
|
// Forward inline driver request
|
|
623
421
|
let response: TestInlineDriverCallResponse<unknown>;
|
|
624
422
|
try {
|
|
625
|
-
const output = await ((
|
|
626
|
-
...args,
|
|
627
|
-
);
|
|
423
|
+
const output = await ((managerDriver as any)[method] as any)(...args);
|
|
628
424
|
response = { ok: output };
|
|
629
425
|
} catch (rawErr) {
|
|
630
426
|
const err = deconstructError(rawErr, logger(), {}, true);
|
|
@@ -634,144 +430,84 @@ export function createManagerRouter(
|
|
|
634
430
|
return c.body(cbor.encode(response));
|
|
635
431
|
});
|
|
636
432
|
|
|
637
|
-
router.get(".test/inline-driver/connect-websocket", async (c) => {
|
|
433
|
+
router.get(".test/inline-driver/connect-websocket/*", async (c) => {
|
|
638
434
|
const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
|
|
639
435
|
invariant(upgradeWebSocket, "websockets not supported on this platform");
|
|
640
436
|
|
|
641
437
|
return upgradeWebSocket(async (c: any) => {
|
|
642
438
|
const {
|
|
643
|
-
|
|
439
|
+
path,
|
|
440
|
+
actorId,
|
|
644
441
|
params: paramsRaw,
|
|
645
442
|
encodingKind,
|
|
646
443
|
} = c.req.query() as {
|
|
647
|
-
|
|
444
|
+
path: string;
|
|
445
|
+
actorId: string;
|
|
648
446
|
params?: string;
|
|
649
447
|
encodingKind: Encoding;
|
|
650
448
|
};
|
|
651
|
-
const actorQuery = JSON.parse(actorQueryRaw);
|
|
652
449
|
const params =
|
|
653
450
|
paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined;
|
|
654
451
|
|
|
655
|
-
logger().debug(
|
|
656
|
-
|
|
452
|
+
logger().debug({
|
|
453
|
+
msg: "received test inline driver websocket",
|
|
454
|
+
actorId,
|
|
657
455
|
params,
|
|
658
456
|
encodingKind,
|
|
457
|
+
path: path,
|
|
659
458
|
});
|
|
660
459
|
|
|
661
460
|
// Connect to the actor using the inline client driver - this returns a Promise<WebSocket>
|
|
662
|
-
const clientWsPromise =
|
|
663
|
-
undefined,
|
|
664
|
-
actorQuery,
|
|
665
|
-
encodingKind,
|
|
666
|
-
params,
|
|
667
|
-
undefined,
|
|
668
|
-
);
|
|
669
|
-
|
|
670
|
-
return await createTestWebSocketProxy(clientWsPromise, "standard");
|
|
671
|
-
})(c, noopNext());
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
router.get(".test/inline-driver/raw-websocket", async (c) => {
|
|
675
|
-
const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
|
|
676
|
-
invariant(upgradeWebSocket, "websockets not supported on this platform");
|
|
677
|
-
|
|
678
|
-
return upgradeWebSocket(async (c: any) => {
|
|
679
|
-
const {
|
|
680
|
-
actorQuery: actorQueryRaw,
|
|
681
|
-
params: paramsRaw,
|
|
682
|
-
encodingKind,
|
|
683
|
-
path,
|
|
684
|
-
protocols: protocolsRaw,
|
|
685
|
-
} = c.req.query() as {
|
|
686
|
-
actorQuery: string;
|
|
687
|
-
params?: string;
|
|
688
|
-
encodingKind: Encoding;
|
|
689
|
-
path: string;
|
|
690
|
-
protocols?: string;
|
|
691
|
-
};
|
|
692
|
-
const actorQuery = JSON.parse(actorQueryRaw);
|
|
693
|
-
const params =
|
|
694
|
-
paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined;
|
|
695
|
-
const protocols =
|
|
696
|
-
protocolsRaw !== undefined ? JSON.parse(protocolsRaw) : undefined;
|
|
697
|
-
|
|
698
|
-
logger().debug("received test inline driver raw websocket", {
|
|
699
|
-
actorQuery,
|
|
700
|
-
params,
|
|
701
|
-
encodingKind,
|
|
461
|
+
const clientWsPromise = managerDriver.openWebSocket(
|
|
702
462
|
path,
|
|
703
|
-
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
// Connect to the actor using the inline client driver - this returns a Promise<WebSocket>
|
|
707
|
-
logger().debug("calling inlineClientDriver.rawWebSocket");
|
|
708
|
-
const clientWsPromise = inlineClientDriver.rawWebSocket(
|
|
709
|
-
undefined,
|
|
710
|
-
actorQuery,
|
|
463
|
+
actorId,
|
|
711
464
|
encodingKind,
|
|
712
465
|
params,
|
|
713
|
-
path,
|
|
714
|
-
protocols,
|
|
715
|
-
undefined,
|
|
716
466
|
);
|
|
717
467
|
|
|
718
|
-
|
|
719
|
-
return await createTestWebSocketProxy(clientWsPromise, "raw");
|
|
468
|
+
return await createTestWebSocketProxy(clientWsPromise, "standard");
|
|
720
469
|
})(c, noopNext());
|
|
721
470
|
});
|
|
722
471
|
|
|
723
|
-
|
|
724
|
-
router.all(".test/inline-driver/raw-http/*", async (c) => {
|
|
472
|
+
router.all(".test/inline-driver/send-request/*", async (c) => {
|
|
725
473
|
// Extract parameters from headers
|
|
726
|
-
const
|
|
727
|
-
const paramsHeader = c.req.header(HEADER_CONN_PARAMS);
|
|
728
|
-
const encodingHeader = c.req.header(HEADER_ENCODING);
|
|
474
|
+
const actorId = c.req.header(HEADER_ACTOR_ID);
|
|
729
475
|
|
|
730
|
-
if (!
|
|
476
|
+
if (!actorId) {
|
|
731
477
|
return c.text("Missing required headers", 400);
|
|
732
478
|
}
|
|
733
479
|
|
|
734
|
-
|
|
735
|
-
const params = paramsHeader ? JSON.parse(paramsHeader) : undefined;
|
|
736
|
-
const encoding = encodingHeader as Encoding;
|
|
737
|
-
|
|
738
|
-
// Extract the path after /raw-http/
|
|
739
|
-
const fullPath = c.req.path;
|
|
480
|
+
// Extract the path after /send-request/
|
|
740
481
|
const pathOnly =
|
|
741
|
-
|
|
482
|
+
c.req.path.split("/.test/inline-driver/send-request/")[1] || "";
|
|
742
483
|
|
|
743
484
|
// Include query string
|
|
744
485
|
const url = new URL(c.req.url);
|
|
745
486
|
const pathWithQuery = pathOnly + url.search;
|
|
746
487
|
|
|
747
|
-
logger().debug(
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
encoding,
|
|
488
|
+
logger().debug({
|
|
489
|
+
msg: "received test inline driver raw http",
|
|
490
|
+
actorId,
|
|
751
491
|
path: pathWithQuery,
|
|
752
492
|
method: c.req.method,
|
|
753
493
|
});
|
|
754
494
|
|
|
755
495
|
try {
|
|
756
496
|
// Forward the request using the inline client driver
|
|
757
|
-
const response = await
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
encoding,
|
|
761
|
-
params,
|
|
762
|
-
pathWithQuery,
|
|
763
|
-
{
|
|
497
|
+
const response = await managerDriver.sendRequest(
|
|
498
|
+
actorId,
|
|
499
|
+
new Request(`http://actor/${pathWithQuery}`, {
|
|
764
500
|
method: c.req.method,
|
|
765
501
|
headers: c.req.raw.headers,
|
|
766
502
|
body: c.req.raw.body,
|
|
767
|
-
},
|
|
768
|
-
undefined,
|
|
503
|
+
}),
|
|
769
504
|
);
|
|
770
505
|
|
|
771
506
|
// Return the response directly
|
|
772
507
|
return response;
|
|
773
508
|
} catch (error) {
|
|
774
|
-
logger().error(
|
|
509
|
+
logger().error({
|
|
510
|
+
msg: "error in test inline raw http",
|
|
775
511
|
error: stringifyError(error),
|
|
776
512
|
});
|
|
777
513
|
|
|
@@ -796,128 +532,56 @@ export function createManagerRouter(
|
|
|
796
532
|
router as unknown as Hono,
|
|
797
533
|
);
|
|
798
534
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
//
|
|
803
|
-
// If using `app.fetch` directly in a non-hono router, paths
|
|
804
|
-
// might not be truncated so they'll come to this router as
|
|
805
|
-
// `/registry/*`. If mounted correctly in Hono, requests will
|
|
806
|
-
// come in at the root as `/*`.
|
|
807
|
-
const mountedRouter = new Hono();
|
|
808
|
-
mountedRouter.route("/", router);
|
|
809
|
-
mountedRouter.route("/registry", router);
|
|
810
|
-
|
|
811
|
-
// IMPORTANT: These must be on `mountedRouter` instead of `router` or else they will not be called.
|
|
812
|
-
mountedRouter.notFound(handleRouteNotFound);
|
|
813
|
-
mountedRouter.onError(handleRouteError.bind(undefined, {}));
|
|
814
|
-
|
|
815
|
-
return { router: mountedRouter, openapi: router };
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
export interface TestInlineDriverCallRequest {
|
|
819
|
-
encoding: Encoding;
|
|
820
|
-
transport: Transport;
|
|
821
|
-
method: string;
|
|
822
|
-
args: unknown[];
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
export type TestInlineDriverCallResponse<T> =
|
|
826
|
-
| {
|
|
827
|
-
ok: T;
|
|
828
|
-
}
|
|
829
|
-
| {
|
|
830
|
-
err: DeconstructedError;
|
|
831
|
-
};
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* Query the manager driver to get or create a actor based on the provided query
|
|
835
|
-
*/
|
|
836
|
-
export async function queryActor(
|
|
837
|
-
c: HonoContext,
|
|
838
|
-
query: ActorQuery,
|
|
839
|
-
driver: ManagerDriver,
|
|
840
|
-
): Promise<{ actorId: string }> {
|
|
841
|
-
logger().debug("querying actor", { query });
|
|
842
|
-
let actorOutput: { actorId: string };
|
|
843
|
-
if ("getForId" in query) {
|
|
844
|
-
const output = await driver.getForId({
|
|
845
|
-
c,
|
|
846
|
-
name: query.getForId.name,
|
|
847
|
-
actorId: query.getForId.actorId,
|
|
848
|
-
});
|
|
849
|
-
if (!output) throw new errors.ActorNotFound(query.getForId.actorId);
|
|
850
|
-
actorOutput = output;
|
|
851
|
-
} else if ("getForKey" in query) {
|
|
852
|
-
const existingActor = await driver.getWithKey({
|
|
853
|
-
c,
|
|
854
|
-
name: query.getForKey.name,
|
|
855
|
-
key: query.getForKey.key,
|
|
856
|
-
});
|
|
857
|
-
if (!existingActor) {
|
|
858
|
-
throw new errors.ActorNotFound(
|
|
859
|
-
`${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`,
|
|
860
|
-
);
|
|
535
|
+
if (runConfig.inspector?.enabled) {
|
|
536
|
+
if (!managerDriver.inspector) {
|
|
537
|
+
throw new Unsupported("inspector");
|
|
861
538
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
};
|
|
874
|
-
} else if ("create" in query) {
|
|
875
|
-
const createOutput = await driver.createActor({
|
|
876
|
-
c,
|
|
877
|
-
name: query.create.name,
|
|
878
|
-
key: query.create.key,
|
|
879
|
-
input: query.create.input,
|
|
880
|
-
region: query.create.region,
|
|
881
|
-
});
|
|
882
|
-
actorOutput = {
|
|
883
|
-
actorId: createOutput.actorId,
|
|
884
|
-
};
|
|
885
|
-
} else {
|
|
886
|
-
throw new errors.InvalidRequest("Invalid query format");
|
|
539
|
+
router.route(
|
|
540
|
+
"/inspect",
|
|
541
|
+
new Hono<{ Variables: { inspector: any } }>()
|
|
542
|
+
.use(corsMiddleware(runConfig.inspector.cors))
|
|
543
|
+
.use(secureInspector(runConfig))
|
|
544
|
+
.use((c, next) => {
|
|
545
|
+
c.set("inspector", managerDriver.inspector!);
|
|
546
|
+
return next();
|
|
547
|
+
})
|
|
548
|
+
.route("/", createManagerInspectorRouter()),
|
|
549
|
+
);
|
|
887
550
|
}
|
|
888
551
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
return { actorId: actorOutput.actorId };
|
|
893
|
-
}
|
|
552
|
+
// Error handling
|
|
553
|
+
router.notFound(handleRouteNotFound);
|
|
554
|
+
router.onError(handleRouteError);
|
|
894
555
|
|
|
556
|
+
return { router: router as Hono, openapi: router };
|
|
557
|
+
}
|
|
895
558
|
/**
|
|
896
559
|
* Creates a WebSocket proxy for test endpoints that forwards messages between server and client WebSockets
|
|
897
560
|
*/
|
|
898
561
|
async function createTestWebSocketProxy(
|
|
899
|
-
clientWsPromise: Promise<
|
|
562
|
+
clientWsPromise: Promise<UniversalWebSocket>,
|
|
900
563
|
connectionType: string,
|
|
901
564
|
): Promise<UpgradeWebSocketArgs> {
|
|
902
565
|
// Store a reference to the resolved WebSocket
|
|
903
|
-
let clientWs:
|
|
566
|
+
let clientWs: UniversalWebSocket | null = null;
|
|
904
567
|
try {
|
|
905
568
|
// Resolve the client WebSocket promise
|
|
906
|
-
logger().debug("awaiting client websocket promise");
|
|
569
|
+
logger().debug({ msg: "awaiting client websocket promise" });
|
|
907
570
|
const ws = await clientWsPromise;
|
|
908
571
|
clientWs = ws;
|
|
909
|
-
logger().debug(
|
|
572
|
+
logger().debug({
|
|
573
|
+
msg: "client websocket promise resolved",
|
|
910
574
|
constructor: ws?.constructor.name,
|
|
911
575
|
});
|
|
912
576
|
|
|
913
577
|
// Wait for ws to open
|
|
914
578
|
await new Promise<void>((resolve, reject) => {
|
|
915
579
|
const onOpen = () => {
|
|
916
|
-
logger().debug("test websocket connection opened");
|
|
580
|
+
logger().debug({ msg: "test websocket connection opened" });
|
|
917
581
|
resolve();
|
|
918
582
|
};
|
|
919
583
|
const onError = (error: any) => {
|
|
920
|
-
logger().error("test websocket connection failed",
|
|
584
|
+
logger().error({ msg: "test websocket connection failed", error });
|
|
921
585
|
reject(
|
|
922
586
|
new Error(`Failed to open WebSocket: ${error.message || error}`),
|
|
923
587
|
);
|
|
@@ -926,10 +590,10 @@ async function createTestWebSocketProxy(
|
|
|
926
590
|
ws.addEventListener("error", onError);
|
|
927
591
|
});
|
|
928
592
|
} catch (error) {
|
|
929
|
-
logger().error(
|
|
930
|
-
`failed to establish client ${connectionType} websocket connection`,
|
|
931
|
-
|
|
932
|
-
);
|
|
593
|
+
logger().error({
|
|
594
|
+
msg: `failed to establish client ${connectionType} websocket connection`,
|
|
595
|
+
error,
|
|
596
|
+
});
|
|
933
597
|
return {
|
|
934
598
|
onOpen: (_evt, serverWs) => {
|
|
935
599
|
serverWs.close(1011, "Failed to establish connection");
|
|
@@ -943,10 +607,13 @@ async function createTestWebSocketProxy(
|
|
|
943
607
|
// Create WebSocket proxy handlers to relay messages between client and server
|
|
944
608
|
return {
|
|
945
609
|
onOpen: (_evt: any, serverWs: WSContext) => {
|
|
946
|
-
logger().debug(
|
|
610
|
+
logger().debug({
|
|
611
|
+
msg: `test ${connectionType} websocket connection opened`,
|
|
612
|
+
});
|
|
947
613
|
|
|
948
614
|
// Check WebSocket type
|
|
949
|
-
logger().debug(
|
|
615
|
+
logger().debug({
|
|
616
|
+
msg: "clientWs info",
|
|
950
617
|
constructor: clientWs.constructor.name,
|
|
951
618
|
hasAddEventListener: typeof clientWs.addEventListener === "function",
|
|
952
619
|
readyState: clientWs.readyState,
|
|
@@ -954,19 +621,17 @@ async function createTestWebSocketProxy(
|
|
|
954
621
|
|
|
955
622
|
// Add message handler to forward messages from client to server
|
|
956
623
|
clientWs.addEventListener("message", (clientEvt: MessageEvent) => {
|
|
957
|
-
logger().debug(
|
|
958
|
-
`test ${connectionType} websocket connection message from client`,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
},
|
|
969
|
-
);
|
|
624
|
+
logger().debug({
|
|
625
|
+
msg: `test ${connectionType} websocket connection message from client`,
|
|
626
|
+
dataType: typeof clientEvt.data,
|
|
627
|
+
isBlob: clientEvt.data instanceof Blob,
|
|
628
|
+
isArrayBuffer: clientEvt.data instanceof ArrayBuffer,
|
|
629
|
+
dataConstructor: clientEvt.data?.constructor?.name,
|
|
630
|
+
dataStr:
|
|
631
|
+
typeof clientEvt.data === "string"
|
|
632
|
+
? clientEvt.data.substring(0, 100)
|
|
633
|
+
: undefined,
|
|
634
|
+
});
|
|
970
635
|
|
|
971
636
|
if (serverWs.readyState === 1) {
|
|
972
637
|
// OPEN
|
|
@@ -975,21 +640,21 @@ async function createTestWebSocketProxy(
|
|
|
975
640
|
clientEvt.data
|
|
976
641
|
.arrayBuffer()
|
|
977
642
|
.then((buffer) => {
|
|
978
|
-
logger().debug(
|
|
979
|
-
"converted client blob to arraybuffer, sending to server",
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
},
|
|
983
|
-
);
|
|
643
|
+
logger().debug({
|
|
644
|
+
msg: "converted client blob to arraybuffer, sending to server",
|
|
645
|
+
bufferSize: buffer.byteLength,
|
|
646
|
+
});
|
|
984
647
|
serverWs.send(buffer as any);
|
|
985
648
|
})
|
|
986
649
|
.catch((error) => {
|
|
987
|
-
logger().error(
|
|
650
|
+
logger().error({
|
|
651
|
+
msg: "failed to convert blob to arraybuffer",
|
|
988
652
|
error,
|
|
989
653
|
});
|
|
990
654
|
});
|
|
991
655
|
} else {
|
|
992
|
-
logger().debug(
|
|
656
|
+
logger().debug({
|
|
657
|
+
msg: "sending client data directly to server",
|
|
993
658
|
dataType: typeof clientEvt.data,
|
|
994
659
|
dataLength:
|
|
995
660
|
typeof clientEvt.data === "string"
|
|
@@ -1002,8 +667,10 @@ async function createTestWebSocketProxy(
|
|
|
1002
667
|
});
|
|
1003
668
|
|
|
1004
669
|
// Add close handler to close server when client closes
|
|
1005
|
-
clientWs.addEventListener("close", (clientEvt:
|
|
1006
|
-
logger().debug(
|
|
670
|
+
clientWs.addEventListener("close", (clientEvt: any) => {
|
|
671
|
+
logger().debug({
|
|
672
|
+
msg: `test ${connectionType} websocket connection closed`,
|
|
673
|
+
});
|
|
1007
674
|
|
|
1008
675
|
if (serverWs.readyState !== 3) {
|
|
1009
676
|
// Not CLOSED
|
|
@@ -1013,7 +680,9 @@ async function createTestWebSocketProxy(
|
|
|
1013
680
|
|
|
1014
681
|
// Add error handler
|
|
1015
682
|
clientWs.addEventListener("error", () => {
|
|
1016
|
-
logger().debug(
|
|
683
|
+
logger().debug({
|
|
684
|
+
msg: `test ${connectionType} websocket connection error`,
|
|
685
|
+
});
|
|
1017
686
|
|
|
1018
687
|
if (serverWs.readyState !== 3) {
|
|
1019
688
|
// Not CLOSED
|
|
@@ -1022,7 +691,8 @@ async function createTestWebSocketProxy(
|
|
|
1022
691
|
});
|
|
1023
692
|
},
|
|
1024
693
|
onMessage: (evt: { data: any }) => {
|
|
1025
|
-
logger().debug(
|
|
694
|
+
logger().debug({
|
|
695
|
+
msg: "received message from server",
|
|
1026
696
|
dataType: typeof evt.data,
|
|
1027
697
|
isBlob: evt.data instanceof Blob,
|
|
1028
698
|
isArrayBuffer: evt.data instanceof ArrayBuffer,
|
|
@@ -1039,18 +709,21 @@ async function createTestWebSocketProxy(
|
|
|
1039
709
|
evt.data
|
|
1040
710
|
.arrayBuffer()
|
|
1041
711
|
.then((buffer) => {
|
|
1042
|
-
logger().debug(
|
|
712
|
+
logger().debug({
|
|
713
|
+
msg: "converted blob to arraybuffer, sending",
|
|
1043
714
|
bufferSize: buffer.byteLength,
|
|
1044
715
|
});
|
|
1045
716
|
clientWs.send(buffer);
|
|
1046
717
|
})
|
|
1047
718
|
.catch((error) => {
|
|
1048
|
-
logger().error(
|
|
719
|
+
logger().error({
|
|
720
|
+
msg: "failed to convert blob to arraybuffer",
|
|
1049
721
|
error,
|
|
1050
722
|
});
|
|
1051
723
|
});
|
|
1052
724
|
} else {
|
|
1053
|
-
logger().debug(
|
|
725
|
+
logger().debug({
|
|
726
|
+
msg: "sending data directly",
|
|
1054
727
|
dataType: typeof evt.data,
|
|
1055
728
|
dataLength:
|
|
1056
729
|
typeof evt.data === "string" ? evt.data.length : undefined,
|
|
@@ -1067,7 +740,8 @@ async function createTestWebSocketProxy(
|
|
|
1067
740
|
},
|
|
1068
741
|
serverWs: WSContext,
|
|
1069
742
|
) => {
|
|
1070
|
-
logger().debug(
|
|
743
|
+
logger().debug({
|
|
744
|
+
msg: `server ${connectionType} websocket closed`,
|
|
1071
745
|
wasClean: event.wasClean,
|
|
1072
746
|
code: event.code,
|
|
1073
747
|
reason: event.reason,
|
|
@@ -1088,7 +762,10 @@ async function createTestWebSocketProxy(
|
|
|
1088
762
|
}
|
|
1089
763
|
},
|
|
1090
764
|
onError: (error: unknown) => {
|
|
1091
|
-
logger().error(
|
|
765
|
+
logger().error({
|
|
766
|
+
msg: `error in server ${connectionType} websocket`,
|
|
767
|
+
error,
|
|
768
|
+
});
|
|
1092
769
|
|
|
1093
770
|
// Close the client websocket on error
|
|
1094
771
|
if (
|
|
@@ -1101,692 +778,3 @@ async function createTestWebSocketProxy(
|
|
|
1101
778
|
},
|
|
1102
779
|
};
|
|
1103
780
|
}
|
|
1104
|
-
|
|
1105
|
-
/**
|
|
1106
|
-
* Handle SSE connection request
|
|
1107
|
-
*/
|
|
1108
|
-
async function handleSseConnectRequest(
|
|
1109
|
-
c: HonoContext,
|
|
1110
|
-
registryConfig: RegistryConfig,
|
|
1111
|
-
_runConfig: RunConfig,
|
|
1112
|
-
driver: ManagerDriver,
|
|
1113
|
-
): Promise<Response> {
|
|
1114
|
-
let encoding: Encoding | undefined;
|
|
1115
|
-
try {
|
|
1116
|
-
encoding = getRequestEncoding(c.req);
|
|
1117
|
-
logger().debug("sse connection request received", { encoding });
|
|
1118
|
-
|
|
1119
|
-
const params = ConnectRequestSchema.safeParse({
|
|
1120
|
-
query: getRequestQuery(c),
|
|
1121
|
-
encoding: c.req.header(HEADER_ENCODING),
|
|
1122
|
-
connParams: c.req.header(HEADER_CONN_PARAMS),
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
|
-
if (!params.success) {
|
|
1126
|
-
logger().error("invalid connection parameters", {
|
|
1127
|
-
error: params.error,
|
|
1128
|
-
});
|
|
1129
|
-
throw new errors.InvalidRequest(params.error);
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
const query = params.data.query;
|
|
1133
|
-
|
|
1134
|
-
// Parse connection parameters for authentication
|
|
1135
|
-
const connParams = params.data.connParams
|
|
1136
|
-
? JSON.parse(params.data.connParams)
|
|
1137
|
-
: undefined;
|
|
1138
|
-
|
|
1139
|
-
// Authenticate the request
|
|
1140
|
-
const authData = await authenticateEndpoint(
|
|
1141
|
-
c,
|
|
1142
|
-
driver,
|
|
1143
|
-
registryConfig,
|
|
1144
|
-
query,
|
|
1145
|
-
["connect"],
|
|
1146
|
-
connParams,
|
|
1147
|
-
);
|
|
1148
|
-
|
|
1149
|
-
// Get the actor ID
|
|
1150
|
-
const { actorId } = await queryActor(c, query, driver);
|
|
1151
|
-
invariant(actorId, "Missing actor ID");
|
|
1152
|
-
logger().debug("sse connection to actor", { actorId });
|
|
1153
|
-
|
|
1154
|
-
// Handle based on mode
|
|
1155
|
-
logger().debug("using custom proxy mode for sse connection");
|
|
1156
|
-
const url = new URL("http://actor/connect/sse");
|
|
1157
|
-
|
|
1158
|
-
// Always build fresh request to prevent forwarding unwanted headers
|
|
1159
|
-
const proxyRequestHeaderes = new Headers();
|
|
1160
|
-
proxyRequestHeaderes.set(HEADER_ENCODING, params.data.encoding);
|
|
1161
|
-
if (params.data.connParams) {
|
|
1162
|
-
proxyRequestHeaderes.set(HEADER_CONN_PARAMS, params.data.connParams);
|
|
1163
|
-
}
|
|
1164
|
-
if (authData) {
|
|
1165
|
-
proxyRequestHeaderes.set(HEADER_AUTH_DATA, JSON.stringify(authData));
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
const proxyRequest = new Request(url, { headers: proxyRequestHeaderes });
|
|
1169
|
-
|
|
1170
|
-
return await driver.proxyRequest(c, proxyRequest, actorId);
|
|
1171
|
-
} catch (error) {
|
|
1172
|
-
// If we receive an error during setup, we send the error and close the socket immediately
|
|
1173
|
-
//
|
|
1174
|
-
// We have to return the error over SSE since SSE clients cannot read vanilla HTTP responses
|
|
1175
|
-
|
|
1176
|
-
const { code, message, metadata } = deconstructError(error, logger(), {
|
|
1177
|
-
sseEvent: "setup",
|
|
1178
|
-
});
|
|
1179
|
-
|
|
1180
|
-
return streamSSE(c, async (stream) => {
|
|
1181
|
-
try {
|
|
1182
|
-
if (encoding) {
|
|
1183
|
-
// Serialize and send the connection error
|
|
1184
|
-
const errorMsg: protocol.ToClient = {
|
|
1185
|
-
body: {
|
|
1186
|
-
tag: "Error",
|
|
1187
|
-
val: {
|
|
1188
|
-
code,
|
|
1189
|
-
message,
|
|
1190
|
-
metadata: bufferToArrayBuffer(cbor.encode(metadata)),
|
|
1191
|
-
actionId: null,
|
|
1192
|
-
},
|
|
1193
|
-
},
|
|
1194
|
-
};
|
|
1195
|
-
|
|
1196
|
-
// Send the error message to the client
|
|
1197
|
-
const serialized = serializeWithEncoding(
|
|
1198
|
-
encoding,
|
|
1199
|
-
errorMsg,
|
|
1200
|
-
TO_CLIENT_VERSIONED,
|
|
1201
|
-
);
|
|
1202
|
-
await stream.writeSSE({
|
|
1203
|
-
data:
|
|
1204
|
-
typeof serialized === "string"
|
|
1205
|
-
? serialized
|
|
1206
|
-
: Buffer.from(serialized).toString("base64"),
|
|
1207
|
-
});
|
|
1208
|
-
} else {
|
|
1209
|
-
// We don't know the encoding, send an error and close
|
|
1210
|
-
await stream.writeSSE({
|
|
1211
|
-
data: code,
|
|
1212
|
-
event: "error",
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
|
-
} catch (serializeError) {
|
|
1216
|
-
logger().error("failed to send error to sse client", {
|
|
1217
|
-
error: serializeError,
|
|
1218
|
-
});
|
|
1219
|
-
await stream.writeSSE({
|
|
1220
|
-
data: "internal error during error handling",
|
|
1221
|
-
event: "error",
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// Stream will exit completely once function exits
|
|
1226
|
-
});
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
/**
|
|
1231
|
-
* Handle WebSocket connection request
|
|
1232
|
-
*/
|
|
1233
|
-
async function handleWebSocketConnectRequest(
|
|
1234
|
-
c: HonoContext,
|
|
1235
|
-
registryConfig: RegistryConfig,
|
|
1236
|
-
runConfig: RunConfig,
|
|
1237
|
-
driver: ManagerDriver,
|
|
1238
|
-
): Promise<Response> {
|
|
1239
|
-
const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
|
|
1240
|
-
if (!upgradeWebSocket) {
|
|
1241
|
-
return c.text(
|
|
1242
|
-
"WebSockets are not enabled for this driver. Use SSE instead.",
|
|
1243
|
-
400,
|
|
1244
|
-
);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
let encoding: Encoding | undefined;
|
|
1248
|
-
try {
|
|
1249
|
-
logger().debug("websocket connection request received");
|
|
1250
|
-
|
|
1251
|
-
// Parse configuration from Sec-WebSocket-Protocol header
|
|
1252
|
-
//
|
|
1253
|
-
// We use this instead of query parameters since this is more secure than
|
|
1254
|
-
// query parameters. Query parameters often get logged.
|
|
1255
|
-
//
|
|
1256
|
-
// Browsers don't support using headers, so this is the only way to
|
|
1257
|
-
// pass data securely.
|
|
1258
|
-
const protocols = c.req.header("sec-websocket-protocol");
|
|
1259
|
-
const { queryRaw, encodingRaw, connParamsRaw } =
|
|
1260
|
-
parseWebSocketProtocols(protocols);
|
|
1261
|
-
|
|
1262
|
-
// Parse query
|
|
1263
|
-
let queryUnvalidated: unknown;
|
|
1264
|
-
try {
|
|
1265
|
-
queryUnvalidated = JSON.parse(queryRaw!);
|
|
1266
|
-
} catch (error) {
|
|
1267
|
-
logger().error("invalid query json", { error });
|
|
1268
|
-
throw new errors.InvalidQueryJSON(error);
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// Parse conn params
|
|
1272
|
-
let connParamsUnvalidated: unknown = null;
|
|
1273
|
-
try {
|
|
1274
|
-
if (connParamsRaw) {
|
|
1275
|
-
connParamsUnvalidated = JSON.parse(connParamsRaw!);
|
|
1276
|
-
}
|
|
1277
|
-
} catch (error) {
|
|
1278
|
-
logger().error("invalid conn params", { error });
|
|
1279
|
-
throw new errors.InvalidParams(
|
|
1280
|
-
`Invalid params JSON: ${stringifyError(error)}`,
|
|
1281
|
-
);
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
// We can't use the standard headers with WebSockets
|
|
1285
|
-
//
|
|
1286
|
-
// All other information will be sent over the socket itself, since that data needs to be E2EE
|
|
1287
|
-
const params = ConnectWebSocketRequestSchema.safeParse({
|
|
1288
|
-
query: queryUnvalidated,
|
|
1289
|
-
encoding: encodingRaw,
|
|
1290
|
-
connParams: connParamsUnvalidated,
|
|
1291
|
-
});
|
|
1292
|
-
if (!params.success) {
|
|
1293
|
-
logger().error("invalid connection parameters", {
|
|
1294
|
-
error: params.error,
|
|
1295
|
-
});
|
|
1296
|
-
throw new errors.InvalidRequest(params.error);
|
|
1297
|
-
}
|
|
1298
|
-
encoding = params.data.encoding;
|
|
1299
|
-
|
|
1300
|
-
// Authenticate endpoint
|
|
1301
|
-
const authData = await authenticateEndpoint(
|
|
1302
|
-
c,
|
|
1303
|
-
driver,
|
|
1304
|
-
registryConfig,
|
|
1305
|
-
params.data.query,
|
|
1306
|
-
["connect"],
|
|
1307
|
-
connParamsRaw,
|
|
1308
|
-
);
|
|
1309
|
-
|
|
1310
|
-
// Get the actor ID
|
|
1311
|
-
const { actorId } = await queryActor(c, params.data.query, driver);
|
|
1312
|
-
logger().debug("found actor for websocket connection", {
|
|
1313
|
-
actorId,
|
|
1314
|
-
});
|
|
1315
|
-
invariant(actorId, "missing actor id");
|
|
1316
|
-
|
|
1317
|
-
// Proxy the WebSocket connection to the actor
|
|
1318
|
-
//
|
|
1319
|
-
// The proxyWebSocket handler will:
|
|
1320
|
-
// 1. Validate the WebSocket upgrade request
|
|
1321
|
-
// 2. Forward the request to the actor with the appropriate path
|
|
1322
|
-
// 3. Handle the WebSocket pair and proxy messages between client and actor
|
|
1323
|
-
return await driver.proxyWebSocket(
|
|
1324
|
-
c,
|
|
1325
|
-
PATH_CONNECT_WEBSOCKET,
|
|
1326
|
-
actorId,
|
|
1327
|
-
params.data.encoding,
|
|
1328
|
-
params.data.connParams,
|
|
1329
|
-
authData,
|
|
1330
|
-
);
|
|
1331
|
-
} catch (error) {
|
|
1332
|
-
// If we receive an error during setup, we send the error and close the socket immediately
|
|
1333
|
-
//
|
|
1334
|
-
// We have to return the error over WS since WebSocket clients cannot read vanilla HTTP responses
|
|
1335
|
-
|
|
1336
|
-
const { code, message, metadata } = deconstructError(error, logger(), {
|
|
1337
|
-
wsEvent: "setup",
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
return await upgradeWebSocket(() => ({
|
|
1341
|
-
onOpen: (_evt: unknown, ws: WSContext) => {
|
|
1342
|
-
if (encoding) {
|
|
1343
|
-
try {
|
|
1344
|
-
// Serialize and send the connection error
|
|
1345
|
-
const errorMsg: protocol.ToClient = {
|
|
1346
|
-
body: {
|
|
1347
|
-
tag: "Error",
|
|
1348
|
-
val: {
|
|
1349
|
-
code,
|
|
1350
|
-
message,
|
|
1351
|
-
metadata: bufferToArrayBuffer(cbor.encode(metadata)),
|
|
1352
|
-
actionId: null,
|
|
1353
|
-
},
|
|
1354
|
-
},
|
|
1355
|
-
};
|
|
1356
|
-
|
|
1357
|
-
// Send the error message to the client
|
|
1358
|
-
const serialized = serializeWithEncoding(
|
|
1359
|
-
encoding,
|
|
1360
|
-
errorMsg,
|
|
1361
|
-
TO_CLIENT_VERSIONED,
|
|
1362
|
-
);
|
|
1363
|
-
ws.send(serialized);
|
|
1364
|
-
|
|
1365
|
-
// Close the connection with an error code
|
|
1366
|
-
ws.close(1011, code);
|
|
1367
|
-
} catch (serializeError) {
|
|
1368
|
-
logger().error("failed to send error to websocket client", {
|
|
1369
|
-
error: serializeError,
|
|
1370
|
-
});
|
|
1371
|
-
ws.close(1011, "internal error during error handling");
|
|
1372
|
-
}
|
|
1373
|
-
} else {
|
|
1374
|
-
// We don't know the encoding so we send what we can
|
|
1375
|
-
ws.close(1011, code);
|
|
1376
|
-
}
|
|
1377
|
-
},
|
|
1378
|
-
}))(c, noopNext());
|
|
1379
|
-
}
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
/**
|
|
1383
|
-
* Handle a connection message request to a actor
|
|
1384
|
-
*
|
|
1385
|
-
* There is no authentication handler on this request since the connection
|
|
1386
|
-
* token is used to authenticate the message.
|
|
1387
|
-
*/
|
|
1388
|
-
async function handleMessageRequest(
|
|
1389
|
-
c: HonoContext,
|
|
1390
|
-
_registryConfig: RegistryConfig,
|
|
1391
|
-
_runConfig: RunConfig,
|
|
1392
|
-
driver: ManagerDriver,
|
|
1393
|
-
): Promise<Response> {
|
|
1394
|
-
logger().debug("connection message request received");
|
|
1395
|
-
try {
|
|
1396
|
-
const params = ConnMessageRequestSchema.safeParse({
|
|
1397
|
-
actorId: c.req.header(HEADER_ACTOR_ID),
|
|
1398
|
-
connId: c.req.header(HEADER_CONN_ID),
|
|
1399
|
-
encoding: c.req.header(HEADER_ENCODING),
|
|
1400
|
-
connToken: c.req.header(HEADER_CONN_TOKEN),
|
|
1401
|
-
});
|
|
1402
|
-
if (!params.success) {
|
|
1403
|
-
logger().error("invalid connection parameters", {
|
|
1404
|
-
error: params.error,
|
|
1405
|
-
});
|
|
1406
|
-
throw new errors.InvalidRequest(params.error);
|
|
1407
|
-
}
|
|
1408
|
-
const { actorId, connId, encoding, connToken } = params.data;
|
|
1409
|
-
|
|
1410
|
-
// TODO: This endpoint can be used to exhause resources (DoS attack) on an actor if you know the actor ID:
|
|
1411
|
-
// 1. Get the actor ID (usually this is reasonably secure, but we don't assume actor ID is sensitive)
|
|
1412
|
-
// 2. Spam messages to the actor (the conn token can be invalid)
|
|
1413
|
-
// 3. The actor will be exhausted processing messages — even if the token is invalid
|
|
1414
|
-
//
|
|
1415
|
-
// The solution is we need to move the authorization of the connection token to this request handler
|
|
1416
|
-
// AND include the actor ID in the connection token so we can verify that it has permission to send
|
|
1417
|
-
// a message to that actor. This would require changing the token to a JWT so we can include a secure
|
|
1418
|
-
// payload, but this requires managing a private key & managing key rotations.
|
|
1419
|
-
//
|
|
1420
|
-
// All other solutions (e.g. include the actor name as a header or include the actor name in the actor ID)
|
|
1421
|
-
// have exploits that allow the caller to send messages to arbitrary actors.
|
|
1422
|
-
//
|
|
1423
|
-
// Currently, we assume this is not a critical problem because requests will likely get rate
|
|
1424
|
-
// limited before enough messages are passed to the actor to exhaust resources.
|
|
1425
|
-
|
|
1426
|
-
const url = new URL("http://actor/connections/message");
|
|
1427
|
-
|
|
1428
|
-
// Always build fresh request to prevent forwarding unwanted headers
|
|
1429
|
-
const proxyRequestHeaders = new Headers();
|
|
1430
|
-
proxyRequestHeaders.set(HEADER_ENCODING, encoding);
|
|
1431
|
-
proxyRequestHeaders.set(HEADER_CONN_ID, connId);
|
|
1432
|
-
proxyRequestHeaders.set(HEADER_CONN_TOKEN, connToken);
|
|
1433
|
-
|
|
1434
|
-
const proxyRequest = new Request(url, {
|
|
1435
|
-
method: "POST",
|
|
1436
|
-
body: c.req.raw.body,
|
|
1437
|
-
duplex: "half",
|
|
1438
|
-
headers: proxyRequestHeaders,
|
|
1439
|
-
});
|
|
1440
|
-
|
|
1441
|
-
return await driver.proxyRequest(c, proxyRequest, actorId);
|
|
1442
|
-
} catch (error) {
|
|
1443
|
-
logger().error("error proxying connection message", { error });
|
|
1444
|
-
|
|
1445
|
-
// Use ProxyError if it's not already an ActorError
|
|
1446
|
-
if (!errors.ActorError.isActorError(error)) {
|
|
1447
|
-
throw new errors.ProxyError("connection message", error);
|
|
1448
|
-
} else {
|
|
1449
|
-
throw error;
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
/**
|
|
1455
|
-
* Handle an action request to a actor
|
|
1456
|
-
*/
|
|
1457
|
-
async function handleActionRequest(
|
|
1458
|
-
c: HonoContext,
|
|
1459
|
-
registryConfig: RegistryConfig,
|
|
1460
|
-
_runConfig: RunConfig,
|
|
1461
|
-
driver: ManagerDriver,
|
|
1462
|
-
): Promise<Response> {
|
|
1463
|
-
try {
|
|
1464
|
-
const actionName = c.req.param("action");
|
|
1465
|
-
logger().debug("action call received", { actionName });
|
|
1466
|
-
|
|
1467
|
-
const params = ConnectRequestSchema.safeParse({
|
|
1468
|
-
query: getRequestQuery(c),
|
|
1469
|
-
encoding: c.req.header(HEADER_ENCODING),
|
|
1470
|
-
connParams: c.req.header(HEADER_CONN_PARAMS),
|
|
1471
|
-
});
|
|
1472
|
-
|
|
1473
|
-
if (!params.success) {
|
|
1474
|
-
logger().error("invalid connection parameters", {
|
|
1475
|
-
error: params.error,
|
|
1476
|
-
});
|
|
1477
|
-
throw new errors.InvalidRequest(params.error);
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
// Parse connection parameters for authentication
|
|
1481
|
-
const connParams = params.data.connParams
|
|
1482
|
-
? JSON.parse(params.data.connParams)
|
|
1483
|
-
: undefined;
|
|
1484
|
-
|
|
1485
|
-
// Authenticate the request
|
|
1486
|
-
const authData = await authenticateEndpoint(
|
|
1487
|
-
c,
|
|
1488
|
-
driver,
|
|
1489
|
-
registryConfig,
|
|
1490
|
-
params.data.query,
|
|
1491
|
-
["action"],
|
|
1492
|
-
connParams,
|
|
1493
|
-
);
|
|
1494
|
-
|
|
1495
|
-
// Get the actor ID
|
|
1496
|
-
const { actorId } = await queryActor(c, params.data.query, driver);
|
|
1497
|
-
logger().debug("found actor for action", { actorId });
|
|
1498
|
-
invariant(actorId, "Missing actor ID");
|
|
1499
|
-
|
|
1500
|
-
const url = new URL(
|
|
1501
|
-
`http://actor/action/${encodeURIComponent(actionName)}`,
|
|
1502
|
-
);
|
|
1503
|
-
|
|
1504
|
-
// Always build fresh request to prevent forwarding unwanted headers
|
|
1505
|
-
const proxyRequestHeaders = new Headers();
|
|
1506
|
-
proxyRequestHeaders.set(HEADER_ENCODING, params.data.encoding);
|
|
1507
|
-
if (params.data.connParams) {
|
|
1508
|
-
proxyRequestHeaders.set(HEADER_CONN_PARAMS, params.data.connParams);
|
|
1509
|
-
}
|
|
1510
|
-
if (authData) {
|
|
1511
|
-
proxyRequestHeaders.set(HEADER_AUTH_DATA, JSON.stringify(authData));
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
const proxyRequest = new Request(url, {
|
|
1515
|
-
method: "POST",
|
|
1516
|
-
body: c.req.raw.body,
|
|
1517
|
-
headers: proxyRequestHeaders,
|
|
1518
|
-
});
|
|
1519
|
-
|
|
1520
|
-
return await driver.proxyRequest(c, proxyRequest, actorId);
|
|
1521
|
-
} catch (error) {
|
|
1522
|
-
logger().error("error in action handler", { error: stringifyError(error) });
|
|
1523
|
-
|
|
1524
|
-
// Use ProxyError if it's not already an ActorError
|
|
1525
|
-
if (!errors.ActorError.isActorError(error)) {
|
|
1526
|
-
throw new errors.ProxyError("Action call", error);
|
|
1527
|
-
} else {
|
|
1528
|
-
throw error;
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
/**
|
|
1534
|
-
* Handle the resolve request to get a actor ID from a query
|
|
1535
|
-
*/
|
|
1536
|
-
async function handleResolveRequest(
|
|
1537
|
-
c: HonoContext,
|
|
1538
|
-
registryConfig: RegistryConfig,
|
|
1539
|
-
driver: ManagerDriver,
|
|
1540
|
-
): Promise<Response> {
|
|
1541
|
-
const encoding = getRequestEncoding(c.req);
|
|
1542
|
-
logger().debug("resolve request encoding", { encoding });
|
|
1543
|
-
|
|
1544
|
-
const params = ResolveRequestSchema.safeParse({
|
|
1545
|
-
query: getRequestQuery(c),
|
|
1546
|
-
connParams: c.req.header(HEADER_CONN_PARAMS),
|
|
1547
|
-
});
|
|
1548
|
-
if (!params.success) {
|
|
1549
|
-
logger().error("invalid connection parameters", {
|
|
1550
|
-
error: params.error,
|
|
1551
|
-
});
|
|
1552
|
-
throw new errors.InvalidRequest(params.error);
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
// Parse connection parameters for authentication
|
|
1556
|
-
const connParams = params.data.connParams
|
|
1557
|
-
? JSON.parse(params.data.connParams)
|
|
1558
|
-
: undefined;
|
|
1559
|
-
|
|
1560
|
-
const query = params.data.query;
|
|
1561
|
-
|
|
1562
|
-
// Authenticate the request
|
|
1563
|
-
await authenticateEndpoint(c, driver, registryConfig, query, [], connParams);
|
|
1564
|
-
|
|
1565
|
-
// Get the actor ID
|
|
1566
|
-
const { actorId } = await queryActor(c, query, driver);
|
|
1567
|
-
logger().debug("resolved actor", { actorId });
|
|
1568
|
-
invariant(actorId, "Missing actor ID");
|
|
1569
|
-
|
|
1570
|
-
// Format response according to protocol
|
|
1571
|
-
const response: protocol.HttpResolveResponse = {
|
|
1572
|
-
actorId,
|
|
1573
|
-
};
|
|
1574
|
-
const serialized = serializeWithEncoding(
|
|
1575
|
-
encoding,
|
|
1576
|
-
response,
|
|
1577
|
-
HTTP_RESOLVE_RESPONSE_VERSIONED,
|
|
1578
|
-
);
|
|
1579
|
-
return c.body(serialized);
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
/**
|
|
1583
|
-
* Handle raw HTTP requests to an actor
|
|
1584
|
-
*/
|
|
1585
|
-
async function handleRawHttpRequest(
|
|
1586
|
-
c: HonoContext,
|
|
1587
|
-
registryConfig: RegistryConfig,
|
|
1588
|
-
_runConfig: RunConfig,
|
|
1589
|
-
driver: ManagerDriver,
|
|
1590
|
-
): Promise<Response> {
|
|
1591
|
-
try {
|
|
1592
|
-
const subpath = c.req.path.split("/raw/http/")[1] || "";
|
|
1593
|
-
logger().debug("raw http request received", { subpath });
|
|
1594
|
-
|
|
1595
|
-
// Get actor query from header (consistent with other endpoints)
|
|
1596
|
-
const queryHeader = c.req.header(HEADER_ACTOR_QUERY);
|
|
1597
|
-
if (!queryHeader) {
|
|
1598
|
-
throw new errors.InvalidRequest("Missing actor query header");
|
|
1599
|
-
}
|
|
1600
|
-
const query: ActorQuery = JSON.parse(queryHeader);
|
|
1601
|
-
|
|
1602
|
-
// Parse connection parameters for authentication
|
|
1603
|
-
const connParamsHeader = c.req.header(HEADER_CONN_PARAMS);
|
|
1604
|
-
const connParams = connParamsHeader
|
|
1605
|
-
? JSON.parse(connParamsHeader)
|
|
1606
|
-
: undefined;
|
|
1607
|
-
|
|
1608
|
-
// Authenticate the request
|
|
1609
|
-
const authData = await authenticateEndpoint(
|
|
1610
|
-
c,
|
|
1611
|
-
driver,
|
|
1612
|
-
registryConfig,
|
|
1613
|
-
query,
|
|
1614
|
-
["action"],
|
|
1615
|
-
connParams,
|
|
1616
|
-
);
|
|
1617
|
-
|
|
1618
|
-
// Get the actor ID
|
|
1619
|
-
const { actorId } = await queryActor(c, query, driver);
|
|
1620
|
-
logger().debug("found actor for raw http", { actorId });
|
|
1621
|
-
invariant(actorId, "Missing actor ID");
|
|
1622
|
-
|
|
1623
|
-
// Preserve the original URL's query parameters
|
|
1624
|
-
const originalUrl = new URL(c.req.url);
|
|
1625
|
-
const url = new URL(
|
|
1626
|
-
`http://actor/raw/http/${subpath}${originalUrl.search}`,
|
|
1627
|
-
);
|
|
1628
|
-
|
|
1629
|
-
// Forward the request to the actor
|
|
1630
|
-
|
|
1631
|
-
logger().debug("rewriting http url", {
|
|
1632
|
-
from: c.req.url,
|
|
1633
|
-
to: url,
|
|
1634
|
-
});
|
|
1635
|
-
|
|
1636
|
-
const proxyRequestHeaders = new Headers(c.req.raw.headers);
|
|
1637
|
-
if (connParams) {
|
|
1638
|
-
proxyRequestHeaders.set(HEADER_CONN_PARAMS, JSON.stringify(connParams));
|
|
1639
|
-
}
|
|
1640
|
-
if (authData) {
|
|
1641
|
-
proxyRequestHeaders.set(HEADER_AUTH_DATA, JSON.stringify(authData));
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
const proxyRequest = new Request(url, {
|
|
1645
|
-
method: c.req.method,
|
|
1646
|
-
headers: proxyRequestHeaders,
|
|
1647
|
-
body: c.req.raw.body,
|
|
1648
|
-
});
|
|
1649
|
-
|
|
1650
|
-
return await driver.proxyRequest(c, proxyRequest, actorId);
|
|
1651
|
-
} catch (error) {
|
|
1652
|
-
logger().error("error in raw http handler", {
|
|
1653
|
-
error: stringifyError(error),
|
|
1654
|
-
});
|
|
1655
|
-
|
|
1656
|
-
// Use ProxyError if it's not already an ActorError
|
|
1657
|
-
if (!errors.ActorError.isActorError(error)) {
|
|
1658
|
-
throw new errors.ProxyError("Raw HTTP request", error);
|
|
1659
|
-
} else {
|
|
1660
|
-
throw error;
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
/**
|
|
1666
|
-
* Handle raw WebSocket requests to an actor
|
|
1667
|
-
*/
|
|
1668
|
-
async function handleRawWebSocketRequest(
|
|
1669
|
-
c: HonoContext,
|
|
1670
|
-
registryConfig: RegistryConfig,
|
|
1671
|
-
runConfig: RunConfig,
|
|
1672
|
-
driver: ManagerDriver,
|
|
1673
|
-
): Promise<Response> {
|
|
1674
|
-
const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
|
|
1675
|
-
if (!upgradeWebSocket) {
|
|
1676
|
-
return c.text("WebSockets are not enabled for this driver.", 400);
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
try {
|
|
1680
|
-
const subpath = c.req.path.split("/raw/websocket/")[1] || "";
|
|
1681
|
-
logger().debug("raw websocket request received", { subpath });
|
|
1682
|
-
|
|
1683
|
-
// Parse protocols from Sec-WebSocket-Protocol header
|
|
1684
|
-
const protocols = c.req.header("sec-websocket-protocol");
|
|
1685
|
-
const {
|
|
1686
|
-
queryRaw: queryFromProtocol,
|
|
1687
|
-
connParamsRaw: connParamsFromProtocol,
|
|
1688
|
-
} = parseWebSocketProtocols(protocols);
|
|
1689
|
-
|
|
1690
|
-
if (!queryFromProtocol) {
|
|
1691
|
-
throw new errors.InvalidRequest("Missing query in WebSocket protocol");
|
|
1692
|
-
}
|
|
1693
|
-
const query = JSON.parse(queryFromProtocol);
|
|
1694
|
-
|
|
1695
|
-
// Parse connection parameters from protocol
|
|
1696
|
-
let connParams: unknown;
|
|
1697
|
-
if (connParamsFromProtocol) {
|
|
1698
|
-
connParams = JSON.parse(connParamsFromProtocol);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
// Authenticate the request
|
|
1702
|
-
const authData = await authenticateEndpoint(
|
|
1703
|
-
c,
|
|
1704
|
-
driver,
|
|
1705
|
-
registryConfig,
|
|
1706
|
-
query,
|
|
1707
|
-
["action"],
|
|
1708
|
-
connParams,
|
|
1709
|
-
);
|
|
1710
|
-
|
|
1711
|
-
// Get the actor ID
|
|
1712
|
-
const { actorId } = await queryActor(c, query, driver);
|
|
1713
|
-
logger().debug("found actor for raw websocket", { actorId });
|
|
1714
|
-
invariant(actorId, "Missing actor ID");
|
|
1715
|
-
|
|
1716
|
-
logger().debug("using custom proxy mode for raw websocket");
|
|
1717
|
-
|
|
1718
|
-
// Preserve the original URL's query parameters
|
|
1719
|
-
const originalUrl = new URL(c.req.url);
|
|
1720
|
-
const proxyPath = `${PATH_RAW_WEBSOCKET_PREFIX}${subpath}${originalUrl.search}`;
|
|
1721
|
-
|
|
1722
|
-
logger().debug("manager router proxyWebSocket", {
|
|
1723
|
-
originalUrl: c.req.url,
|
|
1724
|
-
subpath,
|
|
1725
|
-
search: originalUrl.search,
|
|
1726
|
-
proxyPath,
|
|
1727
|
-
});
|
|
1728
|
-
|
|
1729
|
-
// For raw WebSocket, we need to use proxyWebSocket instead of proxyRequest
|
|
1730
|
-
return await driver.proxyWebSocket(
|
|
1731
|
-
c,
|
|
1732
|
-
proxyPath,
|
|
1733
|
-
actorId,
|
|
1734
|
-
"json", // Default encoding for raw WebSocket
|
|
1735
|
-
connParams,
|
|
1736
|
-
authData,
|
|
1737
|
-
);
|
|
1738
|
-
} catch (error) {
|
|
1739
|
-
// If we receive an error during setup, we send the error and close the socket immediately
|
|
1740
|
-
//
|
|
1741
|
-
// We have to return the error over WS since WebSocket clients cannot read vanilla HTTP responses
|
|
1742
|
-
|
|
1743
|
-
const { code } = deconstructError(error, logger(), {
|
|
1744
|
-
wsEvent: "setup",
|
|
1745
|
-
});
|
|
1746
|
-
|
|
1747
|
-
return await upgradeWebSocket(() => ({
|
|
1748
|
-
onOpen: (_evt: unknown, ws: WSContext) => {
|
|
1749
|
-
// Close with message so we can see the error on the client
|
|
1750
|
-
ws.close(1011, code);
|
|
1751
|
-
},
|
|
1752
|
-
}))(c, noopNext());
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
function universalActorProxy({
|
|
1757
|
-
registryConfig,
|
|
1758
|
-
runConfig,
|
|
1759
|
-
driver,
|
|
1760
|
-
}: {
|
|
1761
|
-
registryConfig: RegistryConfig;
|
|
1762
|
-
runConfig: RunConfig;
|
|
1763
|
-
driver: ManagerDriver;
|
|
1764
|
-
}): MiddlewareHandler {
|
|
1765
|
-
return async (c, _next) => {
|
|
1766
|
-
if (c.req.header("upgrade") === "websocket") {
|
|
1767
|
-
return handleRawWebSocketRequest(c, registryConfig, runConfig, driver);
|
|
1768
|
-
} else {
|
|
1769
|
-
const queryHeader = c.req.header(HEADER_ACTOR_QUERY);
|
|
1770
|
-
if (!queryHeader) {
|
|
1771
|
-
throw new errors.InvalidRequest("Missing actor query header");
|
|
1772
|
-
}
|
|
1773
|
-
const query = ActorQuerySchema.parse(JSON.parse(queryHeader));
|
|
1774
|
-
|
|
1775
|
-
const { actorId } = await queryActor(c, query, driver);
|
|
1776
|
-
|
|
1777
|
-
const url = new URL(c.req.url);
|
|
1778
|
-
url.hostname = "actor";
|
|
1779
|
-
url.pathname = url.pathname
|
|
1780
|
-
.replace(new RegExp(`^${runConfig.basePath}`, ""), "")
|
|
1781
|
-
.replace(/^\/?registry\/actors/, "")
|
|
1782
|
-
.replace(/^\/?actors/, ""); // Remove /registry prefix if present
|
|
1783
|
-
|
|
1784
|
-
const proxyRequest = new Request(url, {
|
|
1785
|
-
method: c.req.method,
|
|
1786
|
-
headers: c.req.raw.headers,
|
|
1787
|
-
body: c.req.raw.body,
|
|
1788
|
-
});
|
|
1789
|
-
return await driver.proxyRequest(c, proxyRequest, actorId);
|
|
1790
|
-
}
|
|
1791
|
-
};
|
|
1792
|
-
}
|