dynamodb-reactive 0.6.1 → 0.7.0
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 +62 -5
- package/dist/cloudflare.cjs +200 -18
- package/dist/cloudflare.cjs.map +1 -1
- package/dist/cloudflare.d.cts +137 -2
- package/dist/cloudflare.d.ts +137 -2
- package/dist/cloudflare.js +194 -19
- package/dist/cloudflare.js.map +1 -1
- package/dist/cluster.d.cts +1 -1
- package/dist/cluster.d.ts +1 -1
- package/dist/hub-cli.js +121 -0
- package/dist/{protocol-C4Syt_Bl.d.ts → protocol-xs_OqPqe.d.ts} +1 -1
- package/package.json +4 -1
package/dist/cloudflare.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { SubscribeRequest, SnapshotResponse, UnsubscribeRequest, JsonPatch } from './cluster.js';
|
|
2
2
|
import { HubSocket, HubPaths } from './hub.js';
|
|
3
|
+
import { ClusterTicket } from './cluster.js';
|
|
4
|
+
import './client-BPAct-Dz.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Cloudflare Durable Object shell for the reactive hub. It is structural: it
|
|
@@ -140,6 +142,8 @@ interface RemoteHubOptions {
|
|
|
140
142
|
url: string;
|
|
141
143
|
/** The Worker's `notifySecret`. Keep it server-side. */
|
|
142
144
|
secret: string;
|
|
145
|
+
/** Aborts a notify that takes longer than this. Default 3000 ms. */
|
|
146
|
+
timeoutMs?: number;
|
|
143
147
|
/** Injectable for tests. */
|
|
144
148
|
fetch?: typeof fetch;
|
|
145
149
|
}
|
|
@@ -181,5 +185,136 @@ interface ReactiveRoutesOptions {
|
|
|
181
185
|
/** Serves the ticket, socket, reactive and notify routes; `undefined` for any other path. */
|
|
182
186
|
declare function handleReactiveRoutes(request: Request, options: ReactiveRoutesOptions): Promise<Response | undefined>;
|
|
183
187
|
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
/** Routes a `defineHub` Worker serves. */
|
|
189
|
+
declare const HUB_ROUTES: {
|
|
190
|
+
/** `POST` with the notify secret and `?principal=`: a ticket for a machine client. */
|
|
191
|
+
readonly ticket: "/api/hub/token";
|
|
192
|
+
/** WebSocket upgrade with a signed ticket in `?token=`. */
|
|
193
|
+
readonly socket: "/api/hub/socket";
|
|
194
|
+
/** Reactive HTTP subscribe and unsubscribe, with a signed ticket as the bearer. */
|
|
195
|
+
readonly reactive: "/api/hub/reactive";
|
|
196
|
+
/** `POST { path, patches? }` with the notify secret, after the data behind a path changed. */
|
|
197
|
+
readonly notify: "/api/hub/notify";
|
|
198
|
+
/** `GET` with the notify secret: `{ principals }`, the tracked principals holding a live socket. */
|
|
199
|
+
readonly presence: "/api/hub/presence";
|
|
200
|
+
};
|
|
201
|
+
/** Names of the Worker secrets and variables a `defineHub` Worker reads. */
|
|
202
|
+
declare const HUB_ENV: {
|
|
203
|
+
readonly TICKET_SECRET: "REACTIVE_TICKET_SECRET";
|
|
204
|
+
readonly NOTIFY_SECRET: "HUB_NOTIFY_SECRET";
|
|
205
|
+
/** Comma-separated browser origins allowed to call the hub cross-origin. */
|
|
206
|
+
readonly ALLOWED_ORIGINS: "ALLOWED_ORIGINS";
|
|
207
|
+
};
|
|
208
|
+
/** The Durable Object class name and binding a `defineHub` Worker uses. */
|
|
209
|
+
declare const HUB_DURABLE_OBJECT: {
|
|
210
|
+
readonly className: "ReactiveHub";
|
|
211
|
+
readonly binding: "REACTIVE_HUB";
|
|
212
|
+
};
|
|
213
|
+
/** The secrets and variables a `defineHub` Worker reads. */
|
|
214
|
+
interface HubWorkerEnv {
|
|
215
|
+
REACTIVE_TICKET_SECRET?: string;
|
|
216
|
+
HUB_NOTIFY_SECRET?: string;
|
|
217
|
+
ALLOWED_ORIGINS?: string;
|
|
218
|
+
REACTIVE_HUB: {
|
|
219
|
+
idFromName(name: string): unknown;
|
|
220
|
+
get(id: unknown): HubStub;
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/** What a hub's path rules can read from the Durable Object. */
|
|
224
|
+
interface HubContext {
|
|
225
|
+
/** Whether `principal` holds a live socket. Needs `presence`. */
|
|
226
|
+
present(principal: string): boolean;
|
|
227
|
+
/** Every tracked principal holding a live socket, sorted. Needs `presence`. */
|
|
228
|
+
presentPrincipals(): string[];
|
|
229
|
+
}
|
|
230
|
+
interface HubConfig {
|
|
231
|
+
/** Path rules: who may watch each path and its snapshot. */
|
|
232
|
+
paths: (hub: HubContext) => HubPaths;
|
|
233
|
+
/**
|
|
234
|
+
* Principals whose live socket counts as presence, such as machine workers.
|
|
235
|
+
* They must ping more often than `timeoutMs`; `connectClusterWorker` does.
|
|
236
|
+
*/
|
|
237
|
+
presence?: {
|
|
238
|
+
principals: readonly string[] | ((principal: string) => boolean);
|
|
239
|
+
/** @default 90000 */
|
|
240
|
+
timeoutMs?: number;
|
|
241
|
+
/** Paths notified when a principal comes online or goes offline, such as a status page. */
|
|
242
|
+
notify?: readonly string[];
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Machine principals that may get a ticket from `HUB_ROUTES.ticket` by presenting
|
|
246
|
+
* the notify secret. Browsers get tickets from the app instead (`mintHubTicket`).
|
|
247
|
+
*/
|
|
248
|
+
machinePrincipal?: (principal: string) => boolean;
|
|
249
|
+
}
|
|
250
|
+
/** The Durable Object a `defineHub` Worker exports as `ReactiveHub`. */
|
|
251
|
+
interface HubDurableObject {
|
|
252
|
+
fetch(request: Request): Promise<Response>;
|
|
253
|
+
webSocketMessage(socket: DurableObjectWebSocket, message: string | ArrayBuffer): Promise<void>;
|
|
254
|
+
webSocketClose(socket: DurableObjectWebSocket, code: number): Promise<void>;
|
|
255
|
+
webSocketError(socket: DurableObjectWebSocket): Promise<void>;
|
|
256
|
+
alarm(): Promise<void>;
|
|
257
|
+
}
|
|
258
|
+
/** What `defineHub` returns: export `ReactiveHub` by that name and `worker` as the default. */
|
|
259
|
+
interface DefinedHub {
|
|
260
|
+
ReactiveHub: new (ctx: DurableObjectStateLike, env: unknown) => HubDurableObject;
|
|
261
|
+
worker: {
|
|
262
|
+
fetch(request: Request, env: HubWorkerEnv): Promise<Response>;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* A complete hub Worker: the Durable Object class and the Worker handler, with
|
|
267
|
+
* the ticket, socket, reactive, notify and presence routes. The application
|
|
268
|
+
* supplies only its path rules.
|
|
269
|
+
*
|
|
270
|
+
* ```ts
|
|
271
|
+
* const hub = defineHub({ paths: () => ({ 'status': { authorize: () => true, snapshot: () => ({}) } }) });
|
|
272
|
+
* export const ReactiveHub = hub.ReactiveHub;
|
|
273
|
+
* export default hub.worker;
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* Deploy it with `dynamodb-reactive-hub deploy`, which writes the matching
|
|
277
|
+
* Wrangler config and puts the secrets.
|
|
278
|
+
*/
|
|
279
|
+
declare function defineHub(config: HubConfig): DefinedHub;
|
|
280
|
+
/** The hub's two secrets, derived from one root secret the application already holds. */
|
|
281
|
+
interface HubSecrets {
|
|
282
|
+
/** `REACTIVE_TICKET_SECRET`: signs and verifies tickets. */
|
|
283
|
+
ticket: string;
|
|
284
|
+
/** `HUB_NOTIFY_SECRET`: notify, presence and machine ticket routes. */
|
|
285
|
+
notify: string;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Derives the hub secrets as HMAC-SHA256 hex of `root` under `<name>-ticket` and
|
|
289
|
+
* `<name>-notify`, where `name` is the hub Worker's name. Everything that already
|
|
290
|
+
* holds `root` derives them itself; `dynamodb-reactive-hub deploy` puts the same
|
|
291
|
+
* values on the Worker, so no hub secret has to be distributed.
|
|
292
|
+
*/
|
|
293
|
+
declare function deriveHubSecrets(root: string, name: string): Promise<HubSecrets>;
|
|
294
|
+
interface MintHubTicketOptions {
|
|
295
|
+
/** The hub's origin, such as `https://my-hub.example.workers.dev`. */
|
|
296
|
+
origin: string;
|
|
297
|
+
/** The hub's ticket secret. */
|
|
298
|
+
secret: string;
|
|
299
|
+
principal: string;
|
|
300
|
+
/** @default 60 */
|
|
301
|
+
lifetimeSeconds?: number;
|
|
302
|
+
}
|
|
303
|
+
/** A browser ticket for the hub, minted by the app that knows the signed-in user. */
|
|
304
|
+
declare function mintHubTicket(options: MintHubTicketOptions): Promise<ClusterTicket & {
|
|
305
|
+
httpUrl: string;
|
|
306
|
+
}>;
|
|
307
|
+
/**
|
|
308
|
+
* The tracked principals holding a live socket, from `HUB_ROUTES.presence`, or
|
|
309
|
+
* undefined when the hub cannot be reached.
|
|
310
|
+
*/
|
|
311
|
+
declare function fetchHubPresence(options: {
|
|
312
|
+
origin: string;
|
|
313
|
+
secret: string;
|
|
314
|
+
/** @default 3000 */
|
|
315
|
+
timeoutMs?: number;
|
|
316
|
+
fetch?: typeof fetch;
|
|
317
|
+
}): Promise<string[] | undefined>;
|
|
318
|
+
|
|
319
|
+
export { HEARTBEAT_TIMEOUT_CLOSE_CODE, HUB_DURABLE_OBJECT, HUB_ENV, HUB_PRINCIPAL_HEADER, HUB_ROUTES, createDurableObjectHub, defineHub, deriveHubSecrets, fetchHubPresence, handleReactiveRoutes, hubClient, mintHubTicket, remoteHub };
|
|
320
|
+
export type { DefinedHub, DurableObjectHub, DurableObjectHubOptions, DurableObjectStateLike, DurableObjectWebSocket, HubClientFrame, HubConfig, HubContext, HubDurableObject, HubPresenceOptions, HubSecrets, HubStub, HubWorkerEnv, MintHubTicketOptions, ReactiveRoutes, ReactiveRoutesOptions, RemoteHubOptions };
|
package/dist/cloudflare.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// ../client/src/log.ts
|
|
2
|
+
function error(...values) {
|
|
3
|
+
console.error(...values);
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
// ../client/src/cluster.ts
|
|
2
7
|
var DEFAULT_TICKET_LIFETIME_SECONDS = 60;
|
|
3
8
|
function isRecord(value) {
|
|
@@ -261,7 +266,7 @@ var HEARTBEAT_TIMEOUT_CLOSE_CODE = 4001;
|
|
|
261
266
|
function json(value, status = 200) {
|
|
262
267
|
return Response.json(value, { status, headers: { "cache-control": "no-store" } });
|
|
263
268
|
}
|
|
264
|
-
function
|
|
269
|
+
function error2(message, status) {
|
|
265
270
|
return json({ type: "error", message }, status);
|
|
266
271
|
}
|
|
267
272
|
function workersGlobal(name) {
|
|
@@ -333,7 +338,7 @@ function createDurableObjectHub(ctx, options) {
|
|
|
333
338
|
};
|
|
334
339
|
const accept = async (request) => {
|
|
335
340
|
const principal = request.headers.get(HUB_PRINCIPAL_HEADER);
|
|
336
|
-
if (!principal) return
|
|
341
|
+
if (!principal) return error2("Unauthorized", 401);
|
|
337
342
|
const Pair = options.WebSocketPair ?? workersGlobal("WebSocketPair");
|
|
338
343
|
if (!Pair) throw new Error("WebSocketPair is not available in this runtime");
|
|
339
344
|
const pair = new Pair();
|
|
@@ -368,10 +373,10 @@ function createDurableObjectHub(ctx, options) {
|
|
|
368
373
|
await hub.publish(action.path, action.patches);
|
|
369
374
|
return json({ type: "ok" });
|
|
370
375
|
default:
|
|
371
|
-
return
|
|
376
|
+
return error2("Unknown hub action", 400);
|
|
372
377
|
}
|
|
373
378
|
} catch (cause) {
|
|
374
|
-
if (cause instanceof ReactiveHubError) return
|
|
379
|
+
if (cause instanceof ReactiveHubError) return error2(cause.message, 400);
|
|
375
380
|
throw cause;
|
|
376
381
|
}
|
|
377
382
|
};
|
|
@@ -453,12 +458,14 @@ function hubClient(stub) {
|
|
|
453
458
|
publish: async (path, patches) => ok(await post({ action: "publish", path, patches }))
|
|
454
459
|
};
|
|
455
460
|
}
|
|
461
|
+
var DEFAULT_REMOTE_HUB_TIMEOUT_MS = 3e3;
|
|
456
462
|
function remoteHub(options) {
|
|
457
463
|
const send2 = async (body) => {
|
|
458
464
|
const response = await (options.fetch ?? fetch)(options.url, {
|
|
459
465
|
method: "POST",
|
|
460
466
|
headers: { authorization: `Bearer ${options.secret}`, "content-type": "application/json" },
|
|
461
|
-
body: JSON.stringify(body)
|
|
467
|
+
body: JSON.stringify(body),
|
|
468
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_REMOTE_HUB_TIMEOUT_MS)
|
|
462
469
|
});
|
|
463
470
|
if (!response.ok) throw new Error(`Hub notify failed: HTTP ${response.status}`);
|
|
464
471
|
};
|
|
@@ -508,31 +515,31 @@ async function handleReactiveRoutes(request, options) {
|
|
|
508
515
|
});
|
|
509
516
|
}
|
|
510
517
|
if (url.pathname === routes.notify) {
|
|
511
|
-
if (request.method !== "POST") return
|
|
512
|
-
if (!options.notifySecret) return
|
|
518
|
+
if (request.method !== "POST") return error2("Method not allowed", 405);
|
|
519
|
+
if (!options.notifySecret) return error2("Hub notify is not configured", 503);
|
|
513
520
|
if (!sameSecret(bearerToken(request), options.notifySecret)) {
|
|
514
|
-
return
|
|
521
|
+
return error2("Unauthorized", 401);
|
|
515
522
|
}
|
|
516
523
|
let body2;
|
|
517
524
|
try {
|
|
518
525
|
body2 = await request.json();
|
|
519
526
|
} catch {
|
|
520
|
-
return
|
|
527
|
+
return error2("Invalid JSON", 400);
|
|
521
528
|
}
|
|
522
529
|
const { path, patches } = body2 ?? {};
|
|
523
530
|
if (typeof path !== "string" || patches !== void 0 && !Array.isArray(patches)) {
|
|
524
|
-
return
|
|
531
|
+
return error2("Expected { path, patches? }", 400);
|
|
525
532
|
}
|
|
526
533
|
const client = hubClient(options.hub());
|
|
527
534
|
if (patches) await client.publish(path, patches);
|
|
528
535
|
else await client.notify(path);
|
|
529
536
|
return json({ type: "ok" });
|
|
530
537
|
}
|
|
531
|
-
if (!secret) return withCors(
|
|
538
|
+
if (!secret) return withCors(error2("Reactive hub is not configured", 503), corsOrigin);
|
|
532
539
|
if (url.pathname === routes.ticket) {
|
|
533
|
-
if (request.method !== "POST") return withCors(
|
|
540
|
+
if (request.method !== "POST") return withCors(error2("Method not allowed", 405), corsOrigin);
|
|
534
541
|
const principal2 = await options.authenticate(request);
|
|
535
|
-
if (!principal2) return withCors(
|
|
542
|
+
if (!principal2) return withCors(error2("Unauthorized", 401), corsOrigin);
|
|
536
543
|
const socketUrl = new URL(routes.socket, url);
|
|
537
544
|
socketUrl.protocol = "wss:";
|
|
538
545
|
return withCors(
|
|
@@ -549,7 +556,7 @@ async function handleReactiveRoutes(request, options) {
|
|
|
549
556
|
}
|
|
550
557
|
if (url.pathname === routes.socket) {
|
|
551
558
|
if (request.headers.get("upgrade") !== "websocket") {
|
|
552
|
-
return
|
|
559
|
+
return error2("Expected a WebSocket upgrade", 426);
|
|
553
560
|
}
|
|
554
561
|
const principal2 = await verifySignedClusterTicket(url.searchParams.get("token") ?? "", secret);
|
|
555
562
|
if (!principal2) return new Response("Unauthorized", { status: 401 });
|
|
@@ -557,19 +564,187 @@ async function handleReactiveRoutes(request, options) {
|
|
|
557
564
|
forwarded.headers.set(HUB_PRINCIPAL_HEADER, principal2);
|
|
558
565
|
return options.hub().fetch(forwarded);
|
|
559
566
|
}
|
|
560
|
-
if (request.method !== "POST") return withCors(
|
|
567
|
+
if (request.method !== "POST") return withCors(error2("Method not allowed", 405), corsOrigin);
|
|
561
568
|
const principal = await verifySignedClusterTicket(bearerToken(request), secret);
|
|
562
|
-
if (!principal) return withCors(
|
|
569
|
+
if (!principal) return withCors(error2("Unauthorized", 401), corsOrigin);
|
|
563
570
|
let body;
|
|
564
571
|
try {
|
|
565
572
|
body = await request.json();
|
|
566
573
|
} catch {
|
|
567
|
-
return withCors(
|
|
574
|
+
return withCors(error2("Invalid JSON", 400), corsOrigin);
|
|
568
575
|
}
|
|
569
|
-
if (!isHubRequest(body)) return withCors(
|
|
576
|
+
if (!isHubRequest(body)) return withCors(error2("Unsupported reactive request", 400), corsOrigin);
|
|
570
577
|
return withCors(await hubClient(options.hub()).request(body, principal), corsOrigin);
|
|
571
578
|
}
|
|
572
579
|
|
|
573
|
-
|
|
580
|
+
// ../client/src/cloudflare-hub.ts
|
|
581
|
+
var HUB_ROUTES = {
|
|
582
|
+
/** `POST` with the notify secret and `?principal=`: a ticket for a machine client. */
|
|
583
|
+
ticket: "/api/hub/token",
|
|
584
|
+
/** WebSocket upgrade with a signed ticket in `?token=`. */
|
|
585
|
+
socket: "/api/hub/socket",
|
|
586
|
+
/** Reactive HTTP subscribe and unsubscribe, with a signed ticket as the bearer. */
|
|
587
|
+
reactive: "/api/hub/reactive",
|
|
588
|
+
/** `POST { path, patches? }` with the notify secret, after the data behind a path changed. */
|
|
589
|
+
notify: "/api/hub/notify",
|
|
590
|
+
/** `GET` with the notify secret: `{ principals }`, the tracked principals holding a live socket. */
|
|
591
|
+
presence: "/api/hub/presence"
|
|
592
|
+
};
|
|
593
|
+
var HUB_ENV = {
|
|
594
|
+
TICKET_SECRET: "REACTIVE_TICKET_SECRET",
|
|
595
|
+
NOTIFY_SECRET: "HUB_NOTIFY_SECRET",
|
|
596
|
+
/** Comma-separated browser origins allowed to call the hub cross-origin. */
|
|
597
|
+
ALLOWED_ORIGINS: "ALLOWED_ORIGINS"
|
|
598
|
+
};
|
|
599
|
+
var HUB_DURABLE_OBJECT = { className: "ReactiveHub", binding: "REACTIVE_HUB" };
|
|
600
|
+
var HUB_INSTANCE = "hub";
|
|
601
|
+
var PRESENCE_ACTION_URL = "https://reactive-hub/presence";
|
|
602
|
+
function sameSecret2(given, expected) {
|
|
603
|
+
const left = new TextEncoder().encode(given);
|
|
604
|
+
const right = new TextEncoder().encode(expected);
|
|
605
|
+
let difference = left.length ^ right.length;
|
|
606
|
+
for (let index = 0; index < right.length; index++) {
|
|
607
|
+
difference |= (left[index] ?? 0) ^ right[index];
|
|
608
|
+
}
|
|
609
|
+
return difference === 0;
|
|
610
|
+
}
|
|
611
|
+
function parseOrigins(value) {
|
|
612
|
+
return (value ?? "").split(",").map((origin) => origin.trim().replace(/\/+$/u, "")).filter(Boolean);
|
|
613
|
+
}
|
|
614
|
+
function defineHub(config) {
|
|
615
|
+
class ReactiveHub {
|
|
616
|
+
#ctx;
|
|
617
|
+
#reactive;
|
|
618
|
+
constructor(ctx, _env) {
|
|
619
|
+
this.#ctx = ctx;
|
|
620
|
+
const context = {
|
|
621
|
+
present: (principal) => this.#reactive.present(principal),
|
|
622
|
+
presentPrincipals: () => this.#presentPrincipals()
|
|
623
|
+
};
|
|
624
|
+
const presence = config.presence;
|
|
625
|
+
this.#reactive = createDurableObjectHub(ctx, {
|
|
626
|
+
paths: config.paths(context),
|
|
627
|
+
...presence ? {
|
|
628
|
+
presence: {
|
|
629
|
+
principals: presence.principals,
|
|
630
|
+
...presence.timeoutMs === void 0 ? {} : { timeoutMs: presence.timeoutMs },
|
|
631
|
+
onChange: async () => {
|
|
632
|
+
for (const path of presence.notify ?? []) {
|
|
633
|
+
await this.#reactive.hub.notify(path);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
} : {}
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
#presentPrincipals() {
|
|
641
|
+
const principals = /* @__PURE__ */ new Set();
|
|
642
|
+
for (const socket of this.#ctx.getWebSockets()) {
|
|
643
|
+
const principal = this.#reactive.principalOf(socket);
|
|
644
|
+
if (this.#reactive.present(principal)) principals.add(principal);
|
|
645
|
+
}
|
|
646
|
+
return [...principals].sort();
|
|
647
|
+
}
|
|
648
|
+
async fetch(request) {
|
|
649
|
+
if (request.url === PRESENCE_ACTION_URL) {
|
|
650
|
+
return Response.json(
|
|
651
|
+
{ principals: this.#presentPrincipals() },
|
|
652
|
+
{ headers: { "cache-control": "no-store" } }
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
return this.#reactive.fetch(request);
|
|
656
|
+
}
|
|
657
|
+
webSocketMessage(socket, message) {
|
|
658
|
+
return this.#reactive.webSocketMessage(socket, message);
|
|
659
|
+
}
|
|
660
|
+
webSocketClose(socket, code) {
|
|
661
|
+
return this.#reactive.webSocketClose(socket, code);
|
|
662
|
+
}
|
|
663
|
+
webSocketError(socket) {
|
|
664
|
+
return this.#reactive.webSocketError(socket);
|
|
665
|
+
}
|
|
666
|
+
alarm() {
|
|
667
|
+
return this.#reactive.alarm();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const worker = {
|
|
671
|
+
async fetch(request, env) {
|
|
672
|
+
const stub = () => env.REACTIVE_HUB.get(env.REACTIVE_HUB.idFromName(HUB_INSTANCE));
|
|
673
|
+
const notifySecret = env.HUB_NOTIFY_SECRET;
|
|
674
|
+
const authorized = () => Boolean(notifySecret) && sameSecret2(bearerToken(request), notifySecret);
|
|
675
|
+
try {
|
|
676
|
+
if (new URL(request.url).pathname === HUB_ROUTES.presence) {
|
|
677
|
+
if (request.method !== "GET") return new Response(null, { status: 405 });
|
|
678
|
+
if (!notifySecret) return new Response(null, { status: 503 });
|
|
679
|
+
if (!authorized()) return new Response(null, { status: 401 });
|
|
680
|
+
return await stub().fetch(new Request(PRESENCE_ACTION_URL));
|
|
681
|
+
}
|
|
682
|
+
const response = await handleReactiveRoutes(request, {
|
|
683
|
+
routes: HUB_ROUTES,
|
|
684
|
+
secret: env.REACTIVE_TICKET_SECRET,
|
|
685
|
+
notifySecret,
|
|
686
|
+
allowedOrigins: parseOrigins(env.ALLOWED_ORIGINS),
|
|
687
|
+
hub: stub,
|
|
688
|
+
authenticate: (incoming) => {
|
|
689
|
+
const principal = new URL(incoming.url).searchParams.get("principal") ?? "";
|
|
690
|
+
return authorized() && config.machinePrincipal?.(principal) ? principal : void 0;
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
return response ?? new Response("Not found", { status: 404 });
|
|
694
|
+
} catch (cause) {
|
|
695
|
+
error("hub request failed", request.method, new URL(request.url).pathname, cause);
|
|
696
|
+
return Response.json({ error: "Hub error" }, { status: 500 });
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
return { ReactiveHub, worker };
|
|
701
|
+
}
|
|
702
|
+
async function hmacHex(key, label) {
|
|
703
|
+
const encoder2 = new TextEncoder();
|
|
704
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
705
|
+
"raw",
|
|
706
|
+
encoder2.encode(key),
|
|
707
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
708
|
+
false,
|
|
709
|
+
["sign"]
|
|
710
|
+
);
|
|
711
|
+
const signature = new Uint8Array(
|
|
712
|
+
await crypto.subtle.sign("HMAC", cryptoKey, encoder2.encode(label))
|
|
713
|
+
);
|
|
714
|
+
return Array.from(signature, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
715
|
+
}
|
|
716
|
+
async function deriveHubSecrets(root, name) {
|
|
717
|
+
const [ticket, notify] = await Promise.all([
|
|
718
|
+
hmacHex(root, `${name}-ticket`),
|
|
719
|
+
hmacHex(root, `${name}-notify`)
|
|
720
|
+
]);
|
|
721
|
+
return { ticket, notify };
|
|
722
|
+
}
|
|
723
|
+
async function mintHubTicket(options) {
|
|
724
|
+
const socketUrl = new URL(HUB_ROUTES.socket, options.origin);
|
|
725
|
+
socketUrl.protocol = "wss:";
|
|
726
|
+
const ticket = await createSignedClusterTicket({
|
|
727
|
+
secret: options.secret,
|
|
728
|
+
principal: options.principal,
|
|
729
|
+
url: socketUrl.toString(),
|
|
730
|
+
...options.lifetimeSeconds === void 0 ? {} : { lifetimeSeconds: options.lifetimeSeconds }
|
|
731
|
+
});
|
|
732
|
+
return { ...ticket, httpUrl: new URL(HUB_ROUTES.reactive, options.origin).toString() };
|
|
733
|
+
}
|
|
734
|
+
async function fetchHubPresence(options) {
|
|
735
|
+
try {
|
|
736
|
+
const response = await (options.fetch ?? fetch)(new URL(HUB_ROUTES.presence, options.origin), {
|
|
737
|
+
headers: { authorization: `Bearer ${options.secret}` },
|
|
738
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 3e3)
|
|
739
|
+
});
|
|
740
|
+
if (!response.ok) return void 0;
|
|
741
|
+
const { principals } = await response.json();
|
|
742
|
+
return Array.isArray(principals) && principals.every((value) => typeof value === "string") ? principals : void 0;
|
|
743
|
+
} catch {
|
|
744
|
+
return void 0;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
export { HEARTBEAT_TIMEOUT_CLOSE_CODE, HUB_DURABLE_OBJECT, HUB_ENV, HUB_PRINCIPAL_HEADER, HUB_ROUTES, createDurableObjectHub, defineHub, deriveHubSecrets, fetchHubPresence, handleReactiveRoutes, hubClient, mintHubTicket, remoteHub };
|
|
574
749
|
//# sourceMappingURL=cloudflare.js.map
|
|
575
750
|
//# sourceMappingURL=cloudflare.js.map
|