rivetkit 2.0.3 → 2.0.4
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-6PDXBYI5.js → chunk-3F2YSRJL.js} +8 -23
- package/dist/tsup/chunk-3F2YSRJL.js.map +1 -0
- package/dist/tsup/chunk-4CXBCT26.cjs +250 -0
- package/dist/tsup/chunk-4CXBCT26.cjs.map +1 -0
- package/dist/tsup/chunk-4R73YDN3.cjs +20 -0
- package/dist/tsup/chunk-4R73YDN3.cjs.map +1 -0
- package/dist/tsup/{chunk-OGAPU3UG.cjs → chunk-6LJT3QRL.cjs} +39 -25
- package/dist/tsup/chunk-6LJT3QRL.cjs.map +1 -0
- package/dist/tsup/{chunk-6WKQDDUD.cjs → chunk-GICQ3YCU.cjs} +143 -141
- package/dist/tsup/chunk-GICQ3YCU.cjs.map +1 -0
- package/dist/tsup/{chunk-FLMTTN27.js → chunk-H26RP6GD.js} +15 -8
- package/dist/tsup/chunk-H26RP6GD.js.map +1 -0
- package/dist/tsup/chunk-HI3HWJRC.js +20 -0
- package/dist/tsup/chunk-HI3HWJRC.js.map +1 -0
- package/dist/tsup/{chunk-4NSUQZ2H.js → chunk-HLLF4B4Q.js} +116 -114
- package/dist/tsup/chunk-HLLF4B4Q.js.map +1 -0
- package/dist/tsup/{chunk-FCCPJNMA.cjs → chunk-IH6CKNDW.cjs} +12 -27
- package/dist/tsup/chunk-IH6CKNDW.cjs.map +1 -0
- package/dist/tsup/chunk-LV2S3OU3.js +250 -0
- package/dist/tsup/chunk-LV2S3OU3.js.map +1 -0
- package/dist/tsup/{chunk-R2OPSKIV.cjs → chunk-LWNKVZG5.cjs} +20 -13
- package/dist/tsup/chunk-LWNKVZG5.cjs.map +1 -0
- package/dist/tsup/{chunk-INGJP237.js → chunk-NFU2BBT5.js} +102 -43
- package/dist/tsup/chunk-NFU2BBT5.js.map +1 -0
- package/dist/tsup/{chunk-3H7O2A7I.js → chunk-PQY7KKTL.js} +33 -19
- package/dist/tsup/chunk-PQY7KKTL.js.map +1 -0
- package/dist/tsup/{chunk-PO4VLDWA.js → chunk-QK72M5JB.js} +3 -5
- package/dist/tsup/chunk-QK72M5JB.js.map +1 -0
- package/dist/tsup/{chunk-TZJKSBUQ.cjs → chunk-QNNXFOQV.cjs} +3 -5
- package/dist/tsup/chunk-QNNXFOQV.cjs.map +1 -0
- package/dist/tsup/{chunk-GIR3AFFI.cjs → chunk-SBHHJ6QS.cjs} +102 -43
- package/dist/tsup/chunk-SBHHJ6QS.cjs.map +1 -0
- package/dist/tsup/chunk-TQ62L3X7.js +325 -0
- package/dist/tsup/chunk-TQ62L3X7.js.map +1 -0
- package/dist/tsup/chunk-VO7ZRVVD.cjs +6293 -0
- package/dist/tsup/chunk-VO7ZRVVD.cjs.map +1 -0
- package/dist/tsup/chunk-WHBPJNGW.cjs +325 -0
- package/dist/tsup/chunk-WHBPJNGW.cjs.map +1 -0
- package/dist/tsup/chunk-XJQHKJ4P.js +6293 -0
- package/dist/tsup/chunk-XJQHKJ4P.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-BI-6UIBJ.d.ts} +196 -226
- package/dist/tsup/{connection-BR_Ve4ku.d.cts → connection-Dyd4NLGW.d.cts} +196 -226
- 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 +155 -1363
- package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
- package/dist/tsup/driver-test-suite/mod.d.cts +11 -5
- package/dist/tsup/driver-test-suite/mod.d.ts +11 -5
- package/dist/tsup/driver-test-suite/mod.js +876 -2084
- 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-BTe_Rsdn.d.cts} +2 -3
- package/dist/tsup/{router-endpoints-AYkXG8Tl.d.cts → router-endpoints-CBSrKHmo.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 +3 -3
- package/dist/tsup/utils.d.cts +1 -1
- package/dist/tsup/utils.d.ts +1 -1
- package/dist/tsup/utils.js +2 -2
- package/package.json +4 -4
- 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 +97 -35
- 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 +41 -38
- package/src/actor/router.ts +20 -18
- 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 +86 -60
- package/src/driver-test-suite/tests/actor-handle.ts +33 -0
- 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 +13 -10
- 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 +33 -20
- package/src/drivers/file-system/log.ts +1 -3
- package/src/drivers/file-system/manager.ts +31 -8
- 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 +277 -1657
- 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 +57 -24
- package/src/registry/run-config.ts +31 -33
- package/src/registry/serve.ts +4 -5
- package/src/remote-manager-driver/actor-http-client.ts +72 -0
- package/src/remote-manager-driver/actor-websocket-client.ts +63 -0
- package/src/remote-manager-driver/api-endpoints.ts +79 -0
- package/src/remote-manager-driver/api-utils.ts +43 -0
- package/src/remote-manager-driver/log.ts +5 -0
- package/src/remote-manager-driver/mod.ts +274 -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/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/test-inline-client-driver.ts +0 -402
- 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,41 @@
|
|
|
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";
|
|
10
|
-
import type { WSContext } from "hono/ws";
|
|
11
|
-
import invariant from "invariant";
|
|
12
|
-
import type { CloseEvent, MessageEvent, WebSocket } from "ws";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { cors as corsMiddleware } from "hono/cors";
|
|
5
|
+
import { createMiddleware } from "hono/factory";
|
|
13
6
|
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
7
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
HEADER_CONN_ID,
|
|
29
|
-
HEADER_CONN_PARAMS,
|
|
30
|
-
HEADER_CONN_TOKEN,
|
|
31
|
-
HEADER_ENCODING,
|
|
32
|
-
} from "@/actor/router-endpoints";
|
|
33
|
-
import type { ClientDriver } from "@/client/client";
|
|
8
|
+
ActorNotFound,
|
|
9
|
+
FeatureNotImplemented,
|
|
10
|
+
MissingActorHeader,
|
|
11
|
+
Unsupported,
|
|
12
|
+
WebSocketsNotEnabled,
|
|
13
|
+
} from "@/actor/errors";
|
|
34
14
|
import {
|
|
35
15
|
handleRouteError,
|
|
36
16
|
handleRouteNotFound,
|
|
37
17
|
loggerMiddleware,
|
|
38
18
|
} from "@/common/router";
|
|
39
|
-
import {
|
|
40
|
-
type DeconstructedError,
|
|
41
|
-
deconstructError,
|
|
42
|
-
noopNext,
|
|
43
|
-
stringifyError,
|
|
44
|
-
} from "@/common/utils";
|
|
45
19
|
import { createManagerInspectorRouter } from "@/inspector/manager";
|
|
46
20
|
import { secureInspector } from "@/inspector/utils";
|
|
47
|
-
import
|
|
21
|
+
import {
|
|
22
|
+
type ActorsCreateRequest,
|
|
23
|
+
ActorsCreateRequestSchema,
|
|
24
|
+
ActorsCreateResponseSchema,
|
|
25
|
+
} from "@/manager-api/routes/actors-create";
|
|
26
|
+
import { ActorsDeleteResponseSchema } from "@/manager-api/routes/actors-delete";
|
|
27
|
+
import { ActorsGetResponseSchema } from "@/manager-api/routes/actors-get";
|
|
28
|
+
import { ActorsGetByIdResponseSchema } from "@/manager-api/routes/actors-get-by-id";
|
|
29
|
+
import {
|
|
30
|
+
type ActorsGetOrCreateByIdRequest,
|
|
31
|
+
ActorsGetOrCreateByIdRequestSchema,
|
|
32
|
+
ActorsGetOrCreateByIdResponseSchema,
|
|
33
|
+
} from "@/manager-api/routes/actors-get-or-create-by-id";
|
|
34
|
+
import { RivetIdSchema } from "@/manager-api/routes/common";
|
|
48
35
|
import type { RegistryConfig } from "@/registry/config";
|
|
49
36
|
import type { RunConfig } from "@/registry/run-config";
|
|
50
|
-
import type * as protocol from "@/schemas/client-protocol/mod";
|
|
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";
|
|
58
37
|
import type { ManagerDriver } from "./driver";
|
|
59
38
|
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
39
|
|
|
126
40
|
function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
|
|
127
41
|
return {
|
|
@@ -144,17 +58,9 @@ function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
|
|
|
144
58
|
};
|
|
145
59
|
}
|
|
146
60
|
|
|
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
61
|
export function createManagerRouter(
|
|
155
62
|
registryConfig: RegistryConfig,
|
|
156
63
|
runConfig: RunConfig,
|
|
157
|
-
inlineClientDriver: ClientDriver,
|
|
158
64
|
managerDriver: ManagerDriver,
|
|
159
65
|
validateBody: boolean,
|
|
160
66
|
): { router: Hono; openapi: OpenAPIHono } {
|
|
@@ -164,1629 +70,343 @@ export function createManagerRouter(
|
|
|
164
70
|
|
|
165
71
|
router.use("*", loggerMiddleware(logger()));
|
|
166
72
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
path.endsWith("/actors/inspect")
|
|
180
|
-
) {
|
|
181
|
-
return next();
|
|
73
|
+
const cors = runConfig.cors
|
|
74
|
+
? corsMiddleware(runConfig.cors)
|
|
75
|
+
: createMiddleware((_c, next) => next());
|
|
76
|
+
|
|
77
|
+
// Actor proxy middleware - intercept requests with x-rivet-target=actor
|
|
78
|
+
router.use("*", cors, async (c, next) => {
|
|
79
|
+
const target = c.req.header("x-rivet-target");
|
|
80
|
+
const actorId = c.req.header("x-rivet-actor");
|
|
81
|
+
|
|
82
|
+
if (target === "actor") {
|
|
83
|
+
if (!actorId) {
|
|
84
|
+
throw new MissingActorHeader();
|
|
182
85
|
}
|
|
183
86
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
87
|
+
logger().debug({
|
|
88
|
+
msg: "proxying request to actor",
|
|
89
|
+
actorId,
|
|
90
|
+
path: c.req.path,
|
|
91
|
+
method: c.req.method,
|
|
92
|
+
});
|
|
189
93
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
return inspectorOrigin.includes(origin) ? origin : undefined;
|
|
197
|
-
} else {
|
|
198
|
-
return inspectorOrigin;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
94
|
+
// Handle WebSocket upgrade
|
|
95
|
+
if (c.req.header("upgrade") === "websocket") {
|
|
96
|
+
const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
|
|
97
|
+
if (!upgradeWebSocket) {
|
|
98
|
+
throw new WebSocketsNotEnabled();
|
|
99
|
+
}
|
|
201
100
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
101
|
+
// For WebSocket, use the driver's proxyWebSocket method
|
|
102
|
+
// Extract any additional headers that might be needed
|
|
103
|
+
const encoding =
|
|
104
|
+
c.req.header("X-RivetKit-Encoding") ||
|
|
105
|
+
c.req.header("x-rivet-encoding") ||
|
|
106
|
+
"json";
|
|
107
|
+
const connParams =
|
|
108
|
+
c.req.header("X-RivetKit-Conn-Params") ||
|
|
109
|
+
c.req.header("x-rivet-conn-params");
|
|
110
|
+
const authData =
|
|
111
|
+
c.req.header("X-RivetKit-Auth-Data") ||
|
|
112
|
+
c.req.header("x-rivet-auth-data");
|
|
113
|
+
|
|
114
|
+
// Include query string if present
|
|
115
|
+
const pathWithQuery = c.req.url.includes("?")
|
|
116
|
+
? c.req.path + c.req.url.substring(c.req.url.indexOf("?"))
|
|
117
|
+
: c.req.path;
|
|
118
|
+
|
|
119
|
+
return await managerDriver.proxyWebSocket(
|
|
120
|
+
c,
|
|
121
|
+
pathWithQuery,
|
|
122
|
+
actorId,
|
|
123
|
+
encoding as any, // Will be validated by driver
|
|
124
|
+
connParams ? JSON.parse(connParams) : undefined,
|
|
125
|
+
authData ? JSON.parse(authData) : undefined,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
210
128
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (typeof inspectorMethods === "function") {
|
|
217
|
-
return inspectorMethods(origin, c);
|
|
218
|
-
}
|
|
219
|
-
return inspectorMethods;
|
|
220
|
-
}
|
|
129
|
+
// Handle regular HTTP requests
|
|
130
|
+
// Preserve all headers except the routing headers
|
|
131
|
+
const proxyHeaders = new Headers(c.req.raw.headers);
|
|
132
|
+
proxyHeaders.delete("x-rivet-target");
|
|
133
|
+
proxyHeaders.delete("x-rivet-actor");
|
|
221
134
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
return runConfig.cors.allowMethods;
|
|
227
|
-
}
|
|
135
|
+
// Build the proxy request with the actor URL format
|
|
136
|
+
const url = new URL(c.req.url);
|
|
137
|
+
const proxyUrl = new URL(`http://actor${url.pathname}${url.search}`);
|
|
228
138
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
runConfig.inspector?.cors?.credentials ??
|
|
241
|
-
true,
|
|
242
|
-
})(c, next);
|
|
243
|
-
});
|
|
244
|
-
}
|
|
139
|
+
const proxyRequest = new Request(proxyUrl, {
|
|
140
|
+
method: c.req.method,
|
|
141
|
+
headers: proxyHeaders,
|
|
142
|
+
body: c.req.raw.body,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
return await managerDriver.proxyRequest(c, proxyRequest, actorId);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return next();
|
|
149
|
+
});
|
|
245
150
|
|
|
246
151
|
// GET /
|
|
247
|
-
router.get("/", (c
|
|
152
|
+
router.get("/", cors, (c) => {
|
|
248
153
|
return c.text(
|
|
249
|
-
"This is
|
|
154
|
+
"This is a RivetKit server.\n\nLearn more at https://rivetkit.org",
|
|
250
155
|
);
|
|
251
156
|
});
|
|
252
157
|
|
|
253
|
-
//
|
|
158
|
+
// GET /actors/by-id
|
|
254
159
|
{
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
160
|
+
const route = createRoute({
|
|
161
|
+
middleware: [cors],
|
|
162
|
+
method: "get",
|
|
163
|
+
path: "/actors/by-id",
|
|
164
|
+
request: {
|
|
165
|
+
query: z.object({
|
|
166
|
+
name: z.string(),
|
|
167
|
+
key: z.string(),
|
|
259
168
|
}),
|
|
260
|
-
}
|
|
261
|
-
|
|
169
|
+
},
|
|
170
|
+
responses: buildOpenApiResponses(
|
|
171
|
+
ActorsGetByIdResponseSchema,
|
|
172
|
+
validateBody,
|
|
173
|
+
),
|
|
174
|
+
});
|
|
262
175
|
|
|
263
|
-
|
|
264
|
-
.
|
|
265
|
-
i: z.string().openapi({
|
|
266
|
-
example: "actor-123",
|
|
267
|
-
}),
|
|
268
|
-
})
|
|
269
|
-
.openapi("ResolveResponse");
|
|
176
|
+
router.openapi(route, async (c) => {
|
|
177
|
+
const { name, key } = c.req.valid("query");
|
|
270
178
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
179
|
+
// Get actor by key from the driver
|
|
180
|
+
const actorOutput = await managerDriver.getWithKey({
|
|
181
|
+
c,
|
|
182
|
+
name,
|
|
183
|
+
key: [key], // Convert string to ActorKey array
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return c.json({
|
|
187
|
+
actor_id: actorOutput?.actorId || null,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// PUT /actors/by-id
|
|
193
|
+
{
|
|
194
|
+
const route = createRoute({
|
|
195
|
+
cors: [cors],
|
|
196
|
+
method: "put",
|
|
197
|
+
path: "/actors/by-id",
|
|
274
198
|
request: {
|
|
275
199
|
body: {
|
|
276
200
|
content: validateBody
|
|
277
201
|
? {
|
|
278
202
|
"application/json": {
|
|
279
|
-
schema:
|
|
203
|
+
schema: ActorsGetOrCreateByIdRequestSchema,
|
|
280
204
|
},
|
|
281
205
|
}
|
|
282
206
|
: {},
|
|
283
207
|
},
|
|
284
|
-
headers: z.object({
|
|
285
|
-
[HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY,
|
|
286
|
-
}),
|
|
287
208
|
},
|
|
288
|
-
responses: buildOpenApiResponses(
|
|
209
|
+
responses: buildOpenApiResponses(
|
|
210
|
+
ActorsGetOrCreateByIdResponseSchema,
|
|
211
|
+
validateBody,
|
|
212
|
+
),
|
|
289
213
|
});
|
|
290
214
|
|
|
291
|
-
router.openapi(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
215
|
+
router.openapi(route, async (c) => {
|
|
216
|
+
const body = validateBody
|
|
217
|
+
? await c.req.json<ActorsGetOrCreateByIdRequest>()
|
|
218
|
+
: await c.req.json();
|
|
295
219
|
|
|
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
|
-
);
|
|
220
|
+
// Parse and validate the request body if validation is enabled
|
|
221
|
+
if (validateBody) {
|
|
222
|
+
ActorsGetOrCreateByIdRequestSchema.parse(body);
|
|
307
223
|
}
|
|
308
224
|
|
|
309
|
-
|
|
310
|
-
|
|
225
|
+
// Check if actor already exists
|
|
226
|
+
const existingActor = await managerDriver.getWithKey({
|
|
227
|
+
c,
|
|
228
|
+
name: body.name,
|
|
229
|
+
key: [body.key], // Convert string to ActorKey array
|
|
230
|
+
});
|
|
311
231
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
description: "WebSocket upgrade",
|
|
319
|
-
},
|
|
320
|
-
},
|
|
321
|
-
});
|
|
232
|
+
if (existingActor) {
|
|
233
|
+
return c.json({
|
|
234
|
+
actor_id: existingActor.actorId,
|
|
235
|
+
created: false,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
322
238
|
|
|
323
|
-
|
|
324
|
-
|
|
239
|
+
// Create new actor
|
|
240
|
+
const newActor = await managerDriver.getOrCreateWithKey({
|
|
241
|
+
c,
|
|
242
|
+
name: body.name,
|
|
243
|
+
key: [body.key], // Convert string to ActorKey array
|
|
244
|
+
input: body.input
|
|
245
|
+
? cbor.decode(Buffer.from(body.input, "base64"))
|
|
246
|
+
: undefined,
|
|
247
|
+
region: undefined, // Not provided in the request schema
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return c.json({
|
|
251
|
+
actor_id: newActor.actorId,
|
|
252
|
+
created: true,
|
|
253
|
+
});
|
|
325
254
|
});
|
|
326
255
|
}
|
|
327
256
|
|
|
328
|
-
// GET /actors/
|
|
257
|
+
// GET /actors/{actor_id}
|
|
329
258
|
{
|
|
330
|
-
const
|
|
259
|
+
const route = createRoute({
|
|
260
|
+
middleware: [cors],
|
|
331
261
|
method: "get",
|
|
332
|
-
path: "/actors/
|
|
262
|
+
path: "/actors/{actor_id}",
|
|
333
263
|
request: {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
[HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY,
|
|
337
|
-
[HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(),
|
|
264
|
+
params: z.object({
|
|
265
|
+
actor_id: RivetIdSchema,
|
|
338
266
|
}),
|
|
339
267
|
},
|
|
340
|
-
responses:
|
|
341
|
-
200: {
|
|
342
|
-
description: "SSE stream",
|
|
343
|
-
content: {
|
|
344
|
-
"text/event-stream": {
|
|
345
|
-
schema: z.unknown(),
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
|
-
},
|
|
268
|
+
responses: buildOpenApiResponses(ActorsGetResponseSchema, validateBody),
|
|
350
269
|
});
|
|
351
270
|
|
|
352
|
-
router.openapi(
|
|
353
|
-
|
|
354
|
-
);
|
|
355
|
-
}
|
|
271
|
+
router.openapi(route, async (c) => {
|
|
272
|
+
const { actor_id } = c.req.valid("param");
|
|
356
273
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
name: "action",
|
|
364
|
-
in: "path",
|
|
365
|
-
},
|
|
366
|
-
example: "myAction",
|
|
367
|
-
}),
|
|
368
|
-
})
|
|
369
|
-
.openapi("ActionParams");
|
|
274
|
+
// Get actor by ID from the driver
|
|
275
|
+
const actorOutput = await managerDriver.getForId({
|
|
276
|
+
c,
|
|
277
|
+
name: "", // TODO: The API doesn't provide the name, this may need to be resolved
|
|
278
|
+
actorId: actor_id,
|
|
279
|
+
});
|
|
370
280
|
|
|
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");
|
|
281
|
+
if (!actorOutput) {
|
|
282
|
+
throw new ActorNotFound(actor_id);
|
|
283
|
+
}
|
|
384
284
|
|
|
385
|
-
|
|
285
|
+
// Transform ActorOutput to match ActorSchema
|
|
286
|
+
// Note: Some fields are not available from the driver and need defaults
|
|
287
|
+
const actor = {
|
|
288
|
+
actor_id: actorOutput.actorId,
|
|
289
|
+
name: actorOutput.name,
|
|
290
|
+
key: actorOutput.key,
|
|
291
|
+
namespace_id: "", // Not available from driver
|
|
292
|
+
runner_name_selector: "", // Not available from driver
|
|
293
|
+
create_ts: Date.now(), // Not available from driver
|
|
294
|
+
connectable_ts: null,
|
|
295
|
+
destroy_ts: null,
|
|
296
|
+
sleep_ts: null,
|
|
297
|
+
start_ts: null,
|
|
298
|
+
};
|
|
386
299
|
|
|
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),
|
|
300
|
+
return c.json({ actor });
|
|
407
301
|
});
|
|
408
|
-
|
|
409
|
-
router.openapi(actionRoute, (c) =>
|
|
410
|
-
handleActionRequest(c, registryConfig, runConfig, managerDriver),
|
|
411
|
-
);
|
|
412
302
|
}
|
|
413
303
|
|
|
414
|
-
// POST /actors
|
|
304
|
+
// POST /actors
|
|
415
305
|
{
|
|
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({
|
|
306
|
+
const route = createRoute({
|
|
307
|
+
middleware: [cors],
|
|
429
308
|
method: "post",
|
|
430
|
-
path: "/actors
|
|
309
|
+
path: "/actors",
|
|
431
310
|
request: {
|
|
432
311
|
body: {
|
|
433
312
|
content: validateBody
|
|
434
313
|
? {
|
|
435
314
|
"application/json": {
|
|
436
|
-
schema:
|
|
315
|
+
schema: ActorsCreateRequestSchema,
|
|
437
316
|
},
|
|
438
317
|
}
|
|
439
318
|
: {},
|
|
440
319
|
},
|
|
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
320
|
},
|
|
448
321
|
responses: buildOpenApiResponses(
|
|
449
|
-
|
|
322
|
+
ActorsCreateResponseSchema,
|
|
450
323
|
validateBody,
|
|
451
324
|
),
|
|
452
325
|
});
|
|
453
326
|
|
|
454
|
-
router.openapi(
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// Raw HTTP endpoints - /actors/raw/http/*
|
|
460
|
-
{
|
|
461
|
-
const RawHttpRequestBodySchema = z.any().optional().openapi({
|
|
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
|
-
);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return next();
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
// This route is a noop, just used to generate docs
|
|
547
|
-
const rawWebSocketRoute = createRoute({
|
|
548
|
-
method: "get",
|
|
549
|
-
path: "/actors/raw/websocket/*",
|
|
550
|
-
request: {},
|
|
551
|
-
responses: {
|
|
552
|
-
101: {
|
|
553
|
-
description: "WebSocket upgrade successful",
|
|
554
|
-
},
|
|
555
|
-
400: {
|
|
556
|
-
description: "WebSockets not enabled or invalid request",
|
|
557
|
-
},
|
|
558
|
-
404: {
|
|
559
|
-
description: "Actor does not have an onWebSocket handler",
|
|
560
|
-
},
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
router.openapi(rawWebSocketRoute, () => {
|
|
565
|
-
throw new Error("Should be unreachable");
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (runConfig.inspector?.enabled) {
|
|
570
|
-
router.route(
|
|
571
|
-
"/actors/inspect",
|
|
572
|
-
new Hono()
|
|
573
|
-
.use(
|
|
574
|
-
cors(runConfig.inspector.cors),
|
|
575
|
-
secureInspector(runConfig),
|
|
576
|
-
universalActorProxy({
|
|
577
|
-
registryConfig,
|
|
578
|
-
runConfig,
|
|
579
|
-
driver: managerDriver,
|
|
580
|
-
}),
|
|
581
|
-
)
|
|
582
|
-
.all("/", (c) =>
|
|
583
|
-
// this should be handled by the actor proxy, but just in case
|
|
584
|
-
c.text("Unreachable.", 404),
|
|
585
|
-
),
|
|
586
|
-
);
|
|
587
|
-
router.route(
|
|
588
|
-
"/inspect",
|
|
589
|
-
new Hono()
|
|
590
|
-
.use(
|
|
591
|
-
cors(runConfig.inspector.cors),
|
|
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
|
-
}
|
|
604
|
-
|
|
605
|
-
if (registryConfig.test.enabled) {
|
|
606
|
-
// Add HTTP endpoint to test the inline client
|
|
607
|
-
//
|
|
608
|
-
// We have to do this in a router since this needs to run in the same server as the RivetKit registry. Some test contexts to not run in the same server.
|
|
609
|
-
router.post(".test/inline-driver/call", async (c) => {
|
|
610
|
-
// TODO: use openapi instead
|
|
611
|
-
const buffer = await c.req.arrayBuffer();
|
|
612
|
-
const { encoding, transport, method, args }: TestInlineDriverCallRequest =
|
|
613
|
-
cbor.decode(new Uint8Array(buffer));
|
|
614
|
-
|
|
615
|
-
logger().debug("received inline request", {
|
|
616
|
-
encoding,
|
|
617
|
-
transport,
|
|
618
|
-
method,
|
|
619
|
-
args,
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
// Forward inline driver request
|
|
623
|
-
let response: TestInlineDriverCallResponse<unknown>;
|
|
624
|
-
try {
|
|
625
|
-
const output = await ((inlineClientDriver as any)[method] as any)(
|
|
626
|
-
...args,
|
|
627
|
-
);
|
|
628
|
-
response = { ok: output };
|
|
629
|
-
} catch (rawErr) {
|
|
630
|
-
const err = deconstructError(rawErr, logger(), {}, true);
|
|
631
|
-
response = { err };
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
return c.body(cbor.encode(response));
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
router.get(".test/inline-driver/connect-websocket", async (c) => {
|
|
638
|
-
const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
|
|
639
|
-
invariant(upgradeWebSocket, "websockets not supported on this platform");
|
|
640
|
-
|
|
641
|
-
return upgradeWebSocket(async (c: any) => {
|
|
642
|
-
const {
|
|
643
|
-
actorQuery: actorQueryRaw,
|
|
644
|
-
params: paramsRaw,
|
|
645
|
-
encodingKind,
|
|
646
|
-
} = c.req.query() as {
|
|
647
|
-
actorQuery: string;
|
|
648
|
-
params?: string;
|
|
649
|
-
encodingKind: Encoding;
|
|
650
|
-
};
|
|
651
|
-
const actorQuery = JSON.parse(actorQueryRaw);
|
|
652
|
-
const params =
|
|
653
|
-
paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined;
|
|
654
|
-
|
|
655
|
-
logger().debug("received test inline driver websocket", {
|
|
656
|
-
actorQuery,
|
|
657
|
-
params,
|
|
658
|
-
encodingKind,
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
// Connect to the actor using the inline client driver - this returns a Promise<WebSocket>
|
|
662
|
-
const clientWsPromise = inlineClientDriver.connectWebSocket(
|
|
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,
|
|
702
|
-
path,
|
|
703
|
-
protocols,
|
|
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,
|
|
711
|
-
encodingKind,
|
|
712
|
-
params,
|
|
713
|
-
path,
|
|
714
|
-
protocols,
|
|
715
|
-
undefined,
|
|
716
|
-
);
|
|
717
|
-
|
|
718
|
-
logger().debug("calling createTestWebSocketProxy");
|
|
719
|
-
return await createTestWebSocketProxy(clientWsPromise, "raw");
|
|
720
|
-
})(c, noopNext());
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
// Raw HTTP endpoint for test inline driver
|
|
724
|
-
router.all(".test/inline-driver/raw-http/*", async (c) => {
|
|
725
|
-
// Extract parameters from headers
|
|
726
|
-
const actorQueryHeader = c.req.header(HEADER_ACTOR_QUERY);
|
|
727
|
-
const paramsHeader = c.req.header(HEADER_CONN_PARAMS);
|
|
728
|
-
const encodingHeader = c.req.header(HEADER_ENCODING);
|
|
327
|
+
router.openapi(route, async (c) => {
|
|
328
|
+
const body = validateBody
|
|
329
|
+
? await c.req.json<ActorsCreateRequest>()
|
|
330
|
+
: await c.req.json();
|
|
729
331
|
|
|
730
|
-
if
|
|
731
|
-
|
|
332
|
+
// Parse and validate the request body if validation is enabled
|
|
333
|
+
if (validateBody) {
|
|
334
|
+
ActorsCreateRequestSchema.parse(body);
|
|
732
335
|
}
|
|
733
336
|
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
// Include query string
|
|
744
|
-
const url = new URL(c.req.url);
|
|
745
|
-
const pathWithQuery = pathOnly + url.search;
|
|
746
|
-
|
|
747
|
-
logger().debug("received test inline driver raw http", {
|
|
748
|
-
actorQuery,
|
|
749
|
-
params,
|
|
750
|
-
encoding,
|
|
751
|
-
path: pathWithQuery,
|
|
752
|
-
method: c.req.method,
|
|
337
|
+
// Create actor using the driver
|
|
338
|
+
const actorOutput = await managerDriver.createActor({
|
|
339
|
+
c,
|
|
340
|
+
name: body.name,
|
|
341
|
+
key: [body.key || crypto.randomUUID()], // Generate key if not provided, convert to ActorKey array
|
|
342
|
+
input: body.input
|
|
343
|
+
? cbor.decode(Buffer.from(body.input, "base64"))
|
|
344
|
+
: undefined,
|
|
345
|
+
region: undefined, // Not provided in the request schema
|
|
753
346
|
});
|
|
754
347
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
undefined,
|
|
769
|
-
);
|
|
770
|
-
|
|
771
|
-
// Return the response directly
|
|
772
|
-
return response;
|
|
773
|
-
} catch (error) {
|
|
774
|
-
logger().error("error in test inline raw http", {
|
|
775
|
-
error: stringifyError(error),
|
|
776
|
-
});
|
|
348
|
+
// Transform ActorOutput to match ActorSchema
|
|
349
|
+
const actor = {
|
|
350
|
+
actor_id: actorOutput.actorId,
|
|
351
|
+
name: actorOutput.name,
|
|
352
|
+
key: actorOutput.key,
|
|
353
|
+
namespace_id: "", // Not available from driver
|
|
354
|
+
runner_name_selector: body.runner_name_selector,
|
|
355
|
+
create_ts: Date.now(),
|
|
356
|
+
connectable_ts: null,
|
|
357
|
+
destroy_ts: null,
|
|
358
|
+
sleep_ts: null,
|
|
359
|
+
start_ts: null,
|
|
360
|
+
};
|
|
777
361
|
|
|
778
|
-
|
|
779
|
-
const err = deconstructError(error, logger(), {}, true);
|
|
780
|
-
return c.json(
|
|
781
|
-
{
|
|
782
|
-
error: {
|
|
783
|
-
code: err.code,
|
|
784
|
-
message: err.message,
|
|
785
|
-
metadata: err.metadata,
|
|
786
|
-
},
|
|
787
|
-
},
|
|
788
|
-
err.statusCode,
|
|
789
|
-
);
|
|
790
|
-
}
|
|
362
|
+
return c.json({ actor });
|
|
791
363
|
});
|
|
792
364
|
}
|
|
793
365
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
//
|
|
366
|
+
// TODO:
|
|
367
|
+
// // DELETE /actors/{actor_id}
|
|
368
|
+
// {
|
|
369
|
+
// const route = createRoute({
|
|
370
|
+
// middleware: [cors],
|
|
371
|
+
// method: "delete",
|
|
372
|
+
// path: "/actors/{actor_id}",
|
|
373
|
+
// request: {
|
|
374
|
+
// params: z.object({
|
|
375
|
+
// actor_id: RivetIdSchema,
|
|
376
|
+
// }),
|
|
377
|
+
// },
|
|
378
|
+
// responses: buildOpenApiResponses(
|
|
379
|
+
// ActorsDeleteResponseSchema,
|
|
380
|
+
// validateBody,
|
|
381
|
+
// ),
|
|
382
|
+
// });
|
|
800
383
|
//
|
|
801
|
-
//
|
|
384
|
+
// router.openapi(route, async (c) => {
|
|
385
|
+
// const { actor_id } = c.req.valid("param");
|
|
802
386
|
//
|
|
803
|
-
//
|
|
804
|
-
//
|
|
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
|
-
);
|
|
861
|
-
}
|
|
862
|
-
actorOutput = existingActor;
|
|
863
|
-
} else if ("getOrCreateForKey" in query) {
|
|
864
|
-
const getOrCreateOutput = await driver.getOrCreateWithKey({
|
|
865
|
-
c,
|
|
866
|
-
name: query.getOrCreateForKey.name,
|
|
867
|
-
key: query.getOrCreateForKey.key,
|
|
868
|
-
input: query.getOrCreateForKey.input,
|
|
869
|
-
region: query.getOrCreateForKey.region,
|
|
870
|
-
});
|
|
871
|
-
actorOutput = {
|
|
872
|
-
actorId: getOrCreateOutput.actorId,
|
|
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");
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
logger().debug("actor query result", {
|
|
890
|
-
actorId: actorOutput.actorId,
|
|
891
|
-
});
|
|
892
|
-
return { actorId: actorOutput.actorId };
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
/**
|
|
896
|
-
* Creates a WebSocket proxy for test endpoints that forwards messages between server and client WebSockets
|
|
897
|
-
*/
|
|
898
|
-
async function createTestWebSocketProxy(
|
|
899
|
-
clientWsPromise: Promise<WebSocket>,
|
|
900
|
-
connectionType: string,
|
|
901
|
-
): Promise<UpgradeWebSocketArgs> {
|
|
902
|
-
// Store a reference to the resolved WebSocket
|
|
903
|
-
let clientWs: WebSocket | null = null;
|
|
904
|
-
try {
|
|
905
|
-
// Resolve the client WebSocket promise
|
|
906
|
-
logger().debug("awaiting client websocket promise");
|
|
907
|
-
const ws = await clientWsPromise;
|
|
908
|
-
clientWs = ws;
|
|
909
|
-
logger().debug("client websocket promise resolved", {
|
|
910
|
-
constructor: ws?.constructor.name,
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
// Wait for ws to open
|
|
914
|
-
await new Promise<void>((resolve, reject) => {
|
|
915
|
-
const onOpen = () => {
|
|
916
|
-
logger().debug("test websocket connection opened");
|
|
917
|
-
resolve();
|
|
918
|
-
};
|
|
919
|
-
const onError = (error: any) => {
|
|
920
|
-
logger().error("test websocket connection failed", { error });
|
|
921
|
-
reject(
|
|
922
|
-
new Error(`Failed to open WebSocket: ${error.message || error}`),
|
|
923
|
-
);
|
|
924
|
-
};
|
|
925
|
-
ws.addEventListener("open", onOpen);
|
|
926
|
-
ws.addEventListener("error", onError);
|
|
927
|
-
});
|
|
928
|
-
} catch (error) {
|
|
929
|
-
logger().error(
|
|
930
|
-
`failed to establish client ${connectionType} websocket connection`,
|
|
931
|
-
{ error },
|
|
932
|
-
);
|
|
933
|
-
return {
|
|
934
|
-
onOpen: (_evt, serverWs) => {
|
|
935
|
-
serverWs.close(1011, "Failed to establish connection");
|
|
936
|
-
},
|
|
937
|
-
onMessage: () => {},
|
|
938
|
-
onError: () => {},
|
|
939
|
-
onClose: () => {},
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// Create WebSocket proxy handlers to relay messages between client and server
|
|
944
|
-
return {
|
|
945
|
-
onOpen: (_evt: any, serverWs: WSContext) => {
|
|
946
|
-
logger().debug(`test ${connectionType} websocket connection opened`);
|
|
947
|
-
|
|
948
|
-
// Check WebSocket type
|
|
949
|
-
logger().debug("clientWs info", {
|
|
950
|
-
constructor: clientWs.constructor.name,
|
|
951
|
-
hasAddEventListener: typeof clientWs.addEventListener === "function",
|
|
952
|
-
readyState: clientWs.readyState,
|
|
953
|
-
});
|
|
954
|
-
|
|
955
|
-
// Add message handler to forward messages from client to server
|
|
956
|
-
clientWs.addEventListener("message", (clientEvt: MessageEvent) => {
|
|
957
|
-
logger().debug(
|
|
958
|
-
`test ${connectionType} websocket connection message from client`,
|
|
959
|
-
{
|
|
960
|
-
dataType: typeof clientEvt.data,
|
|
961
|
-
isBlob: clientEvt.data instanceof Blob,
|
|
962
|
-
isArrayBuffer: clientEvt.data instanceof ArrayBuffer,
|
|
963
|
-
dataConstructor: clientEvt.data?.constructor?.name,
|
|
964
|
-
dataStr:
|
|
965
|
-
typeof clientEvt.data === "string"
|
|
966
|
-
? clientEvt.data.substring(0, 100)
|
|
967
|
-
: undefined,
|
|
968
|
-
},
|
|
969
|
-
);
|
|
970
|
-
|
|
971
|
-
if (serverWs.readyState === 1) {
|
|
972
|
-
// OPEN
|
|
973
|
-
// Handle Blob data
|
|
974
|
-
if (clientEvt.data instanceof Blob) {
|
|
975
|
-
clientEvt.data
|
|
976
|
-
.arrayBuffer()
|
|
977
|
-
.then((buffer) => {
|
|
978
|
-
logger().debug(
|
|
979
|
-
"converted client blob to arraybuffer, sending to server",
|
|
980
|
-
{
|
|
981
|
-
bufferSize: buffer.byteLength,
|
|
982
|
-
},
|
|
983
|
-
);
|
|
984
|
-
serverWs.send(buffer as any);
|
|
985
|
-
})
|
|
986
|
-
.catch((error) => {
|
|
987
|
-
logger().error("failed to convert blob to arraybuffer", {
|
|
988
|
-
error,
|
|
989
|
-
});
|
|
990
|
-
});
|
|
991
|
-
} else {
|
|
992
|
-
logger().debug("sending client data directly to server", {
|
|
993
|
-
dataType: typeof clientEvt.data,
|
|
994
|
-
dataLength:
|
|
995
|
-
typeof clientEvt.data === "string"
|
|
996
|
-
? clientEvt.data.length
|
|
997
|
-
: undefined,
|
|
998
|
-
});
|
|
999
|
-
serverWs.send(clientEvt.data as any);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
// Add close handler to close server when client closes
|
|
1005
|
-
clientWs.addEventListener("close", (clientEvt: CloseEvent) => {
|
|
1006
|
-
logger().debug(`test ${connectionType} websocket connection closed`);
|
|
1007
|
-
|
|
1008
|
-
if (serverWs.readyState !== 3) {
|
|
1009
|
-
// Not CLOSED
|
|
1010
|
-
serverWs.close(clientEvt.code, clientEvt.reason);
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
|
|
1014
|
-
// Add error handler
|
|
1015
|
-
clientWs.addEventListener("error", () => {
|
|
1016
|
-
logger().debug(`test ${connectionType} websocket connection error`);
|
|
1017
|
-
|
|
1018
|
-
if (serverWs.readyState !== 3) {
|
|
1019
|
-
// Not CLOSED
|
|
1020
|
-
serverWs.close(1011, "Error in client websocket");
|
|
1021
|
-
}
|
|
1022
|
-
});
|
|
1023
|
-
},
|
|
1024
|
-
onMessage: (evt: { data: any }) => {
|
|
1025
|
-
logger().debug("received message from server", {
|
|
1026
|
-
dataType: typeof evt.data,
|
|
1027
|
-
isBlob: evt.data instanceof Blob,
|
|
1028
|
-
isArrayBuffer: evt.data instanceof ArrayBuffer,
|
|
1029
|
-
dataConstructor: evt.data?.constructor?.name,
|
|
1030
|
-
dataStr:
|
|
1031
|
-
typeof evt.data === "string" ? evt.data.substring(0, 100) : undefined,
|
|
1032
|
-
});
|
|
1033
|
-
|
|
1034
|
-
// Forward messages from server websocket to client websocket
|
|
1035
|
-
if (clientWs.readyState === 1) {
|
|
1036
|
-
// OPEN
|
|
1037
|
-
// Handle Blob data
|
|
1038
|
-
if (evt.data instanceof Blob) {
|
|
1039
|
-
evt.data
|
|
1040
|
-
.arrayBuffer()
|
|
1041
|
-
.then((buffer) => {
|
|
1042
|
-
logger().debug("converted blob to arraybuffer, sending", {
|
|
1043
|
-
bufferSize: buffer.byteLength,
|
|
1044
|
-
});
|
|
1045
|
-
clientWs.send(buffer);
|
|
1046
|
-
})
|
|
1047
|
-
.catch((error) => {
|
|
1048
|
-
logger().error("failed to convert blob to arraybuffer", {
|
|
1049
|
-
error,
|
|
1050
|
-
});
|
|
1051
|
-
});
|
|
1052
|
-
} else {
|
|
1053
|
-
logger().debug("sending data directly", {
|
|
1054
|
-
dataType: typeof evt.data,
|
|
1055
|
-
dataLength:
|
|
1056
|
-
typeof evt.data === "string" ? evt.data.length : undefined,
|
|
1057
|
-
});
|
|
1058
|
-
clientWs.send(evt.data);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
},
|
|
1062
|
-
onClose: (
|
|
1063
|
-
event: {
|
|
1064
|
-
wasClean: boolean;
|
|
1065
|
-
code: number;
|
|
1066
|
-
reason: string;
|
|
1067
|
-
},
|
|
1068
|
-
serverWs: WSContext,
|
|
1069
|
-
) => {
|
|
1070
|
-
logger().debug(`server ${connectionType} websocket closed`, {
|
|
1071
|
-
wasClean: event.wasClean,
|
|
1072
|
-
code: event.code,
|
|
1073
|
-
reason: event.reason,
|
|
1074
|
-
});
|
|
1075
|
-
|
|
1076
|
-
// HACK: Close socket in order to fix bug with Cloudflare leaving WS in closing state
|
|
1077
|
-
// https://github.com/cloudflare/workerd/issues/2569
|
|
1078
|
-
serverWs.close(1000, "hack_force_close");
|
|
1079
|
-
|
|
1080
|
-
// Close the client websocket when the server websocket closes
|
|
1081
|
-
if (
|
|
1082
|
-
clientWs &&
|
|
1083
|
-
clientWs.readyState !== clientWs.CLOSED &&
|
|
1084
|
-
clientWs.readyState !== clientWs.CLOSING
|
|
1085
|
-
) {
|
|
1086
|
-
// Don't pass code/message since this may affect how close events are triggered
|
|
1087
|
-
clientWs.close(1000, event.reason);
|
|
1088
|
-
}
|
|
1089
|
-
},
|
|
1090
|
-
onError: (error: unknown) => {
|
|
1091
|
-
logger().error(`error in server ${connectionType} websocket`, { error });
|
|
1092
|
-
|
|
1093
|
-
// Close the client websocket on error
|
|
1094
|
-
if (
|
|
1095
|
-
clientWs &&
|
|
1096
|
-
clientWs.readyState !== clientWs.CLOSED &&
|
|
1097
|
-
clientWs.readyState !== clientWs.CLOSING
|
|
1098
|
-
) {
|
|
1099
|
-
clientWs.close(1011, "Error in server websocket");
|
|
1100
|
-
}
|
|
1101
|
-
},
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
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
|
-
}
|
|
387
|
+
// });
|
|
388
|
+
// }
|
|
1664
389
|
|
|
1665
|
-
|
|
1666
|
-
|
|
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);
|
|
390
|
+
if (runConfig.inspector?.enabled) {
|
|
391
|
+
if (!managerDriver.inspector) {
|
|
392
|
+
throw new Unsupported("inspector");
|
|
1699
393
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
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,
|
|
394
|
+
router.route(
|
|
395
|
+
"/inspect",
|
|
396
|
+
new Hono<{ Variables: { inspector: any } }>()
|
|
397
|
+
.use(corsMiddleware(runConfig.inspector.cors))
|
|
398
|
+
.use(secureInspector(runConfig))
|
|
399
|
+
.use((c, next) => {
|
|
400
|
+
c.set("inspector", managerDriver.inspector!);
|
|
401
|
+
return next();
|
|
402
|
+
})
|
|
403
|
+
.route("/", createManagerInspectorRouter()),
|
|
1737
404
|
);
|
|
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
405
|
}
|
|
1754
|
-
}
|
|
1755
406
|
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
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);
|
|
407
|
+
// Error handling
|
|
408
|
+
router.notFound(handleRouteNotFound);
|
|
409
|
+
router.onError(handleRouteError);
|
|
1776
410
|
|
|
1777
|
-
|
|
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
|
-
};
|
|
411
|
+
return { router: router as Hono, openapi: router };
|
|
1792
412
|
}
|