@rivetkit/cloudflare-workers 0.9.1 → 0.9.2

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.
@@ -1,7 +1,6 @@
1
1
  import type { Encoding } from "@rivetkit/core";
2
2
  import {
3
3
  type ActorOutput,
4
- type ConnRoutingHandler,
5
4
  type CreateInput,
6
5
  type GetForIdInput,
7
6
  type GetOrCreateWithKeyInput,
@@ -13,6 +12,7 @@ import {
13
12
  type ManagerDriver,
14
13
  } from "@rivetkit/core/driver-helpers";
15
14
  import { ActorAlreadyExists, InternalError } from "@rivetkit/core/errors";
15
+ import type { Context as HonoContext } from "hono";
16
16
  import { getCloudflareAmbientEnv } from "./handler";
17
17
  import { logger } from "./log";
18
18
  import type { Bindings } from "./mod";
@@ -48,149 +48,156 @@ const STANDARD_WEBSOCKET_HEADERS = [
48
48
  ];
49
49
 
50
50
  export class CloudflareActorsManagerDriver implements ManagerDriver {
51
- connRoutingHandler: ConnRoutingHandler;
52
-
53
- constructor() {
54
- this.connRoutingHandler = {
55
- custom: {
56
- sendRequest: async (actorId, actorRequest): Promise<Response> => {
57
- const env = getCloudflareAmbientEnv();
58
-
59
- logger().debug("sending request to durable object", {
60
- actorId,
61
- method: actorRequest.method,
62
- url: actorRequest.url,
63
- });
64
-
65
- const id = env.ACTOR_DO.idFromString(actorId);
66
- const stub = env.ACTOR_DO.get(id);
67
-
68
- return await stub.fetch(actorRequest);
69
- },
70
-
71
- openWebSocket: async (
72
- actorId,
73
- encodingKind: Encoding,
74
- params: unknown,
75
- ): Promise<WebSocket> => {
76
- const env = getCloudflareAmbientEnv();
77
-
78
- logger().debug("opening websocket to durable object", { actorId });
79
-
80
- // Make a fetch request to the Durable Object with WebSocket upgrade
81
- const id = env.ACTOR_DO.idFromString(actorId);
82
- const stub = env.ACTOR_DO.get(id);
83
-
84
- const headers: Record<string, string> = {
85
- Upgrade: "websocket",
86
- Connection: "Upgrade",
87
- [HEADER_EXPOSE_INTERNAL_ERROR]: "true",
88
- [HEADER_ENCODING]: encodingKind,
89
- };
90
- if (params) {
91
- headers[HEADER_CONN_PARAMS] = JSON.stringify(params);
92
- }
93
- // HACK: See packages/platforms/cloudflare-workers/src/websocket.ts
94
- headers["sec-websocket-protocol"] = "rivetkit";
95
-
96
- const response = await stub.fetch("http://actor/connect/websocket", {
97
- headers,
98
- });
99
- const webSocket = response.webSocket;
100
-
101
- if (!webSocket) {
102
- throw new InternalError(
103
- "missing websocket connection in response from DO",
104
- );
105
- }
106
-
107
- logger().debug("durable object websocket connection open", {
108
- actorId,
109
- });
110
-
111
- webSocket.accept();
112
-
113
- // TODO: Is this still needed?
114
- // HACK: Cloudflare does not call onopen automatically, so we need
115
- // to call this on the next tick
116
- setTimeout(() => {
117
- (webSocket as any).onopen?.(new Event("open"));
118
- }, 0);
119
-
120
- return webSocket as unknown as WebSocket;
121
- },
122
-
123
- proxyRequest: async (c, actorRequest, actorId): Promise<Response> => {
124
- logger().debug("forwarding request to durable object", {
125
- actorId,
126
- method: actorRequest.method,
127
- url: actorRequest.url,
128
- });
129
-
130
- const id = c.env.ACTOR_DO.idFromString(actorId);
131
- const stub = c.env.ACTOR_DO.get(id);
132
-
133
- return await stub.fetch(actorRequest);
134
- },
135
- proxyWebSocket: async (
136
- c,
137
- path,
138
- actorId,
139
- encoding,
140
- params,
141
- authData,
142
- ) => {
143
- logger().debug("forwarding websocket to durable object", {
144
- actorId,
145
- path,
146
- });
147
-
148
- // Validate upgrade
149
- const upgradeHeader = c.req.header("Upgrade");
150
- if (!upgradeHeader || upgradeHeader !== "websocket") {
151
- return new Response("Expected Upgrade: websocket", {
152
- status: 426,
153
- });
154
- }
155
-
156
- // TODO: strip headers
157
- const newUrl = new URL(`http://actor${path}`);
158
- const actorRequest = new Request(newUrl, c.req.raw);
159
-
160
- // Always build fresh request to prevent forwarding unwanted headers
161
- // HACK: Since we can't build a new request, we need to remove
162
- // non-standard headers manually
163
- const headerKeys: string[] = [];
164
- actorRequest.headers.forEach((v, k) => headerKeys.push(k));
165
- for (const k of headerKeys) {
166
- if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) {
167
- actorRequest.headers.delete(k);
168
- }
169
- }
170
-
171
- // Add RivetKit headers
172
- actorRequest.headers.set(HEADER_EXPOSE_INTERNAL_ERROR, "true");
173
- actorRequest.headers.set(HEADER_ENCODING, encoding);
174
- if (params) {
175
- actorRequest.headers.set(
176
- HEADER_CONN_PARAMS,
177
- JSON.stringify(params),
178
- );
179
- }
180
- if (authData) {
181
- actorRequest.headers.set(
182
- HEADER_AUTH_DATA,
183
- JSON.stringify(authData),
184
- );
185
- }
186
-
187
- const id = c.env.ACTOR_DO.idFromString(actorId);
188
- const stub = c.env.ACTOR_DO.get(id);
189
-
190
- return await stub.fetch(actorRequest);
191
- },
192
- },
51
+ async sendRequest(actorId: string, actorRequest: Request): Promise<Response> {
52
+ const env = getCloudflareAmbientEnv();
53
+
54
+ logger().debug("sending request to durable object", {
55
+ actorId,
56
+ method: actorRequest.method,
57
+ url: actorRequest.url,
58
+ });
59
+
60
+ const id = env.ACTOR_DO.idFromString(actorId);
61
+ const stub = env.ACTOR_DO.get(id);
62
+
63
+ return await stub.fetch(actorRequest);
64
+ }
65
+
66
+ async openWebSocket(
67
+ path: string,
68
+ actorId: string,
69
+ encoding: Encoding,
70
+ params: unknown,
71
+ ): Promise<WebSocket> {
72
+ const env = getCloudflareAmbientEnv();
73
+
74
+ logger().debug("opening websocket to durable object", { actorId, path });
75
+
76
+ // Make a fetch request to the Durable Object with WebSocket upgrade
77
+ const id = env.ACTOR_DO.idFromString(actorId);
78
+ const stub = env.ACTOR_DO.get(id);
79
+
80
+ const headers: Record<string, string> = {
81
+ Upgrade: "websocket",
82
+ Connection: "Upgrade",
83
+ [HEADER_EXPOSE_INTERNAL_ERROR]: "true",
84
+ [HEADER_ENCODING]: encoding,
193
85
  };
86
+ if (params) {
87
+ headers[HEADER_CONN_PARAMS] = JSON.stringify(params);
88
+ }
89
+ // HACK: See packages/drivers/cloudflare-workers/src/websocket.ts
90
+ headers["sec-websocket-protocol"] = "rivetkit";
91
+
92
+ // Use the path parameter to determine the URL
93
+ const url = `http://actor${path}`;
94
+
95
+ logger().debug("rewriting websocket url", {
96
+ from: path,
97
+ to: url,
98
+ });
99
+
100
+ const response = await stub.fetch(url, {
101
+ headers,
102
+ });
103
+ const webSocket = response.webSocket;
104
+
105
+ if (!webSocket) {
106
+ throw new InternalError(
107
+ "missing websocket connection in response from DO",
108
+ );
109
+ }
110
+
111
+ logger().debug("durable object websocket connection open", {
112
+ actorId,
113
+ });
114
+
115
+ webSocket.accept();
116
+
117
+ // TODO: Is this still needed?
118
+ // HACK: Cloudflare does not call onopen automatically, so we need
119
+ // to call this on the next tick
120
+ setTimeout(() => {
121
+ const event = new Event("open");
122
+ (webSocket as any).onopen?.(event);
123
+ (webSocket as any).dispatchEvent(event);
124
+ }, 0);
125
+
126
+ return webSocket as unknown as WebSocket;
127
+ }
128
+
129
+ async proxyRequest(
130
+ c: HonoContext<{ Bindings: Bindings }>,
131
+ actorRequest: Request,
132
+ actorId: string,
133
+ ): Promise<Response> {
134
+ logger().debug("forwarding request to durable object", {
135
+ actorId,
136
+ method: actorRequest.method,
137
+ url: actorRequest.url,
138
+ });
139
+
140
+ const id = c.env.ACTOR_DO.idFromString(actorId);
141
+ const stub = c.env.ACTOR_DO.get(id);
142
+
143
+ return await stub.fetch(actorRequest);
144
+ }
145
+
146
+ async proxyWebSocket(
147
+ c: HonoContext<{ Bindings: Bindings }>,
148
+ path: string,
149
+ actorId: string,
150
+ encoding: Encoding,
151
+ params: unknown,
152
+ authData: unknown,
153
+ ): Promise<Response> {
154
+ logger().debug("forwarding websocket to durable object", {
155
+ actorId,
156
+ path,
157
+ });
158
+
159
+ // Validate upgrade
160
+ const upgradeHeader = c.req.header("Upgrade");
161
+ if (!upgradeHeader || upgradeHeader !== "websocket") {
162
+ return new Response("Expected Upgrade: websocket", {
163
+ status: 426,
164
+ });
165
+ }
166
+
167
+ // TODO: strip headers
168
+ const newUrl = new URL(`http://actor${path}`);
169
+ const actorRequest = new Request(newUrl, c.req.raw);
170
+
171
+ logger().debug("rewriting websocket url", {
172
+ from: c.req.url,
173
+ to: actorRequest.url,
174
+ });
175
+
176
+ // Always build fresh request to prevent forwarding unwanted headers
177
+ // HACK: Since we can't build a new request, we need to remove
178
+ // non-standard headers manually
179
+ const headerKeys: string[] = [];
180
+ actorRequest.headers.forEach((v, k) => headerKeys.push(k));
181
+ for (const k of headerKeys) {
182
+ if (!STANDARD_WEBSOCKET_HEADERS.includes(k)) {
183
+ actorRequest.headers.delete(k);
184
+ }
185
+ }
186
+
187
+ // Add RivetKit headers
188
+ actorRequest.headers.set(HEADER_EXPOSE_INTERNAL_ERROR, "true");
189
+ actorRequest.headers.set(HEADER_ENCODING, encoding);
190
+ if (params) {
191
+ actorRequest.headers.set(HEADER_CONN_PARAMS, JSON.stringify(params));
192
+ }
193
+ if (authData) {
194
+ actorRequest.headers.set(HEADER_AUTH_DATA, JSON.stringify(authData));
195
+ }
196
+
197
+ const id = c.env.ACTOR_DO.idFromString(actorId);
198
+ const stub = c.env.ACTOR_DO.get(id);
199
+
200
+ return await stub.fetch(actorRequest);
194
201
  }
195
202
 
196
203
  async getForId({