dynamodb-reactive 0.6.2 → 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 +61 -6
- package/dist/cloudflare.cjs +197 -17
- package/dist/cloudflare.cjs.map +1 -1
- package/dist/cloudflare.d.cts +135 -2
- package/dist/cloudflare.d.ts +135 -2
- package/dist/cloudflare.js +191 -18
- 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
|
|
@@ -183,5 +185,136 @@ interface ReactiveRoutesOptions {
|
|
|
183
185
|
/** Serves the ticket, socket, reactive and notify routes; `undefined` for any other path. */
|
|
184
186
|
declare function handleReactiveRoutes(request: Request, options: ReactiveRoutesOptions): Promise<Response | undefined>;
|
|
185
187
|
|
|
186
|
-
|
|
187
|
-
|
|
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
|
};
|
|
@@ -510,31 +515,31 @@ async function handleReactiveRoutes(request, options) {
|
|
|
510
515
|
});
|
|
511
516
|
}
|
|
512
517
|
if (url.pathname === routes.notify) {
|
|
513
|
-
if (request.method !== "POST") return
|
|
514
|
-
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);
|
|
515
520
|
if (!sameSecret(bearerToken(request), options.notifySecret)) {
|
|
516
|
-
return
|
|
521
|
+
return error2("Unauthorized", 401);
|
|
517
522
|
}
|
|
518
523
|
let body2;
|
|
519
524
|
try {
|
|
520
525
|
body2 = await request.json();
|
|
521
526
|
} catch {
|
|
522
|
-
return
|
|
527
|
+
return error2("Invalid JSON", 400);
|
|
523
528
|
}
|
|
524
529
|
const { path, patches } = body2 ?? {};
|
|
525
530
|
if (typeof path !== "string" || patches !== void 0 && !Array.isArray(patches)) {
|
|
526
|
-
return
|
|
531
|
+
return error2("Expected { path, patches? }", 400);
|
|
527
532
|
}
|
|
528
533
|
const client = hubClient(options.hub());
|
|
529
534
|
if (patches) await client.publish(path, patches);
|
|
530
535
|
else await client.notify(path);
|
|
531
536
|
return json({ type: "ok" });
|
|
532
537
|
}
|
|
533
|
-
if (!secret) return withCors(
|
|
538
|
+
if (!secret) return withCors(error2("Reactive hub is not configured", 503), corsOrigin);
|
|
534
539
|
if (url.pathname === routes.ticket) {
|
|
535
|
-
if (request.method !== "POST") return withCors(
|
|
540
|
+
if (request.method !== "POST") return withCors(error2("Method not allowed", 405), corsOrigin);
|
|
536
541
|
const principal2 = await options.authenticate(request);
|
|
537
|
-
if (!principal2) return withCors(
|
|
542
|
+
if (!principal2) return withCors(error2("Unauthorized", 401), corsOrigin);
|
|
538
543
|
const socketUrl = new URL(routes.socket, url);
|
|
539
544
|
socketUrl.protocol = "wss:";
|
|
540
545
|
return withCors(
|
|
@@ -551,7 +556,7 @@ async function handleReactiveRoutes(request, options) {
|
|
|
551
556
|
}
|
|
552
557
|
if (url.pathname === routes.socket) {
|
|
553
558
|
if (request.headers.get("upgrade") !== "websocket") {
|
|
554
|
-
return
|
|
559
|
+
return error2("Expected a WebSocket upgrade", 426);
|
|
555
560
|
}
|
|
556
561
|
const principal2 = await verifySignedClusterTicket(url.searchParams.get("token") ?? "", secret);
|
|
557
562
|
if (!principal2) return new Response("Unauthorized", { status: 401 });
|
|
@@ -559,19 +564,187 @@ async function handleReactiveRoutes(request, options) {
|
|
|
559
564
|
forwarded.headers.set(HUB_PRINCIPAL_HEADER, principal2);
|
|
560
565
|
return options.hub().fetch(forwarded);
|
|
561
566
|
}
|
|
562
|
-
if (request.method !== "POST") return withCors(
|
|
567
|
+
if (request.method !== "POST") return withCors(error2("Method not allowed", 405), corsOrigin);
|
|
563
568
|
const principal = await verifySignedClusterTicket(bearerToken(request), secret);
|
|
564
|
-
if (!principal) return withCors(
|
|
569
|
+
if (!principal) return withCors(error2("Unauthorized", 401), corsOrigin);
|
|
565
570
|
let body;
|
|
566
571
|
try {
|
|
567
572
|
body = await request.json();
|
|
568
573
|
} catch {
|
|
569
|
-
return withCors(
|
|
574
|
+
return withCors(error2("Invalid JSON", 400), corsOrigin);
|
|
570
575
|
}
|
|
571
|
-
if (!isHubRequest(body)) return withCors(
|
|
576
|
+
if (!isHubRequest(body)) return withCors(error2("Unsupported reactive request", 400), corsOrigin);
|
|
572
577
|
return withCors(await hubClient(options.hub()).request(body, principal), corsOrigin);
|
|
573
578
|
}
|
|
574
579
|
|
|
575
|
-
|
|
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 };
|
|
576
749
|
//# sourceMappingURL=cloudflare.js.map
|
|
577
750
|
//# sourceMappingURL=cloudflare.js.map
|