digital-boardgame-framework 0.5.0 → 0.6.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/dist/client/realtime.d.ts +11 -0
- package/dist/client/realtime.d.ts.map +1 -0
- package/dist/client/realtime.js +32 -0
- package/dist/client/realtime.js.map +1 -0
- package/dist/client/use-game.d.ts +8 -0
- package/dist/client/use-game.d.ts.map +1 -1
- package/dist/client/use-game.js +12 -2
- package/dist/client/use-game.js.map +1 -1
- package/dist/server/game-server.d.ts +5 -0
- package/dist/server/game-server.d.ts.map +1 -1
- package/dist/server/game-server.js +11 -0
- package/dist/server/game-server.js.map +1 -1
- package/dist/server/identity/jwt.d.ts +46 -0
- package/dist/server/identity/jwt.d.ts.map +1 -0
- package/dist/server/identity/jwt.js +134 -0
- package/dist/server/identity/jwt.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/realtime.d.ts +28 -0
- package/dist/server/realtime.d.ts.map +1 -0
- package/dist/server/realtime.js +35 -0
- package/dist/server/realtime.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface SupabaseRealtimeOpts {
|
|
2
|
+
supabaseUrl: string;
|
|
3
|
+
/** The PUBLIC anon/publishable key (safe in the client bundle). */
|
|
4
|
+
anonKey: string;
|
|
5
|
+
gameId: string;
|
|
6
|
+
/** Must match the server broadcaster's topicPrefix. Default 'game:'. */
|
|
7
|
+
topicPrefix?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Build a `subscribe` fn for `useGame({ subscribe })`. */
|
|
10
|
+
export declare function subscribeSupabaseRealtime(opts: SupabaseRealtimeOpts): (onChange: () => void) => () => void;
|
|
11
|
+
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/client/realtime.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2DAA2D;AAC3D,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,oBAAoB,GACzB,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAetC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Client helper: a `subscribe` function for useGame backed by Supabase Realtime
|
|
2
|
+
// broadcast. Subscribes to the game's "moved" channel and fires onChange — which
|
|
3
|
+
// makes useGame re-fetch the REDACTED view through the server. The broadcast
|
|
4
|
+
// carries only {turn}; it never carries game state (see server/realtime.ts).
|
|
5
|
+
//
|
|
6
|
+
// Imported via the /client/realtime subpath so games that don't use realtime
|
|
7
|
+
// don't pull @supabase/supabase-js into their bundle.
|
|
8
|
+
import { createClient } from '@supabase/supabase-js';
|
|
9
|
+
/** Build a `subscribe` fn for `useGame({ subscribe })`. */
|
|
10
|
+
export function subscribeSupabaseRealtime(opts) {
|
|
11
|
+
return (onChange) => {
|
|
12
|
+
const client = createClient(opts.supabaseUrl, opts.anonKey, {
|
|
13
|
+
auth: { persistSession: false },
|
|
14
|
+
});
|
|
15
|
+
const topic = `${opts.topicPrefix ?? 'game:'}${opts.gameId}`;
|
|
16
|
+
const channel = client
|
|
17
|
+
.channel(topic)
|
|
18
|
+
.on('broadcast', { event: 'moved' }, () => onChange())
|
|
19
|
+
.subscribe();
|
|
20
|
+
return () => {
|
|
21
|
+
try {
|
|
22
|
+
channel.unsubscribe();
|
|
23
|
+
}
|
|
24
|
+
catch { /* ignore */ }
|
|
25
|
+
try {
|
|
26
|
+
client.removeAllChannels();
|
|
27
|
+
}
|
|
28
|
+
catch { /* ignore */ }
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=realtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../src/client/realtime.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,iFAAiF;AACjF,6EAA6E;AAC7E,6EAA6E;AAC7E,EAAE;AACF,6EAA6E;AAC7E,sDAAsD;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAWrD,2DAA2D;AAC3D,MAAM,UAAU,yBAAyB,CACvC,IAA0B;IAE1B,OAAO,CAAC,QAAQ,EAAE,EAAE;QAClB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,EAAE;YAC1D,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE;SAChC,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,WAAW,IAAI,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC7D,MAAM,OAAO,GAAG,MAAM;aACnB,OAAO,CAAC,KAAK,CAAC;aACd,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;aACrD,SAAS,EAAE,CAAC;QACf,OAAO,GAAG,EAAE;YACV,IAAI,CAAC;gBAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;YACrD,IAAI,CAAC;gBAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAC5D,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -29,6 +29,14 @@ export interface GameClientApi<S, A> {
|
|
|
29
29
|
export interface UseGameOpts {
|
|
30
30
|
pollMs?: number;
|
|
31
31
|
pauseWhenHidden?: boolean;
|
|
32
|
+
/** Optional realtime push. Given a callback, set up a subscription that
|
|
33
|
+
* invokes it whenever the game changes (e.g. Supabase Realtime "moved"
|
|
34
|
+
* broadcast); return an unsubscribe fn. When provided, the hook refreshes on
|
|
35
|
+
* every push and polling drops to a slow self-healing fallback. Transport-
|
|
36
|
+
* agnostic: the framework only needs the "something changed" signal — it
|
|
37
|
+
* always re-fetches the redacted view through the server (push must NOT carry
|
|
38
|
+
* state). See `subscribeSupabaseRealtime` in /client/realtime. */
|
|
39
|
+
subscribe?: (onChange: () => void) => () => void;
|
|
32
40
|
}
|
|
33
41
|
export interface UseGameResult<S, A> {
|
|
34
42
|
view: S | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-game.d.ts","sourceRoot":"","sources":["../../src/client/use-game.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC,KAAK,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1G,YAAY,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,CAAC,UAAU,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,KAAK,GAAG,gBAAgB,GAAG,UAAU,CAAC;QACjD;qFAC6E;QAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"use-game.d.ts","sourceRoot":"","sources":["../../src/client/use-game.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC,KAAK,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1G,YAAY,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,CAAC,UAAU,EAAE;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,KAAK,GAAG,gBAAgB,GAAG,UAAU,CAAC;QACjD;qFAC6E;QAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;;uEAMmE;IACnE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,EAAE,CAAC;IACjC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,4EAA4E;IAC5E,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,YAAY,EAAE,CAAC,EAAE,CAAC;IAClB,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,KAAK,GAAG,gBAAgB,GAAG,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClG,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,GAAE,WAAgB,GAAG,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAqHtG"}
|
package/dist/client/use-game.js
CHANGED
|
@@ -50,7 +50,9 @@ export function useGame(client, opts = {}) {
|
|
|
50
50
|
useEffect(() => {
|
|
51
51
|
mounted.current = true;
|
|
52
52
|
refresh();
|
|
53
|
-
|
|
53
|
+
// With a realtime subscription, polling becomes a slow self-healing fallback
|
|
54
|
+
// (covers a dropped socket); push drives the fast updates.
|
|
55
|
+
const ms = opts.pollMs ?? (opts.subscribe ? 30_000 : 10_000);
|
|
54
56
|
let handle = null;
|
|
55
57
|
const start = () => {
|
|
56
58
|
if (handle)
|
|
@@ -80,13 +82,21 @@ export function useGame(client, opts = {}) {
|
|
|
80
82
|
if (pauseWhenHidden && typeof document !== 'undefined') {
|
|
81
83
|
document.addEventListener('visibilitychange', onVis);
|
|
82
84
|
}
|
|
85
|
+
// Realtime push: refresh on every "something changed" signal. Always
|
|
86
|
+
// re-fetch through the server (redacted) — the signal carries no state.
|
|
87
|
+
let unsubscribe;
|
|
88
|
+
if (opts.subscribe) {
|
|
89
|
+
unsubscribe = opts.subscribe(() => { refresh(); });
|
|
90
|
+
}
|
|
83
91
|
return () => {
|
|
84
92
|
mounted.current = false;
|
|
85
93
|
stop();
|
|
86
94
|
if (typeof document !== 'undefined')
|
|
87
95
|
document.removeEventListener('visibilitychange', onVis);
|
|
96
|
+
if (unsubscribe)
|
|
97
|
+
unsubscribe();
|
|
88
98
|
};
|
|
89
|
-
}, [refresh, opts.pollMs, opts.pauseWhenHidden]);
|
|
99
|
+
}, [refresh, opts.pollMs, opts.pauseWhenHidden, opts.subscribe]);
|
|
90
100
|
const submit = useCallback(async (action) => {
|
|
91
101
|
setLoading(true);
|
|
92
102
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-game.js","sourceRoot":"","sources":["../../src/client/use-game.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"use-game.js","sourceRoot":"","sources":["../../src/client/use-game.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import type { LogEntry } from '../core/log.js';
|
|
|
4
4
|
import type { SnapshotStore, BugReportRow, ReportFilter } from './store.js';
|
|
5
5
|
import type { Notifier } from './notify.js';
|
|
6
6
|
import type { ReportForwarder } from './forward.js';
|
|
7
|
+
import type { GameBroadcaster } from './realtime.js';
|
|
7
8
|
export interface GameServerOpts<S, A, P extends string> {
|
|
8
9
|
adapter: GameAdapter<S, A, P>;
|
|
9
10
|
codec: Codec<S>;
|
|
@@ -12,6 +13,10 @@ export interface GameServerOpts<S, A, P extends string> {
|
|
|
12
13
|
/** Optional sink for stored bug reports (GitHub/Slack/email/webhook).
|
|
13
14
|
* Called best-effort after the report is persisted; see {@link ReportForwarder}. */
|
|
14
15
|
forwarder?: ReportForwarder;
|
|
16
|
+
/** Optional realtime push: a tiny "game moved" ping after each move so
|
|
17
|
+
* waiting clients refresh instantly instead of polling. Called best-effort.
|
|
18
|
+
* NEVER carries state — see {@link GameBroadcaster}. */
|
|
19
|
+
broadcaster?: GameBroadcaster;
|
|
15
20
|
gameUrl: (gameId: string, token: string) => string;
|
|
16
21
|
idGen?: () => string;
|
|
17
22
|
actionEquals?: (a: A, b: A) => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-server.d.ts","sourceRoot":"","sources":["../../src/server/game-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAY,MAAM,YAAY,CAAC;AACtF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"game-server.d.ts","sourceRoot":"","sources":["../../src/server/game-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -91,6 +91,17 @@ export class GameServer {
|
|
|
91
91
|
turn: nextTurn,
|
|
92
92
|
state: this.encodeSnapshot(next),
|
|
93
93
|
});
|
|
94
|
+
// Best-effort realtime ping (turn only — never state) so waiting clients
|
|
95
|
+
// refresh instantly. A failure must not fail the move (clients still poll).
|
|
96
|
+
if (this.opts.broadcaster) {
|
|
97
|
+
try {
|
|
98
|
+
await this.opts.broadcaster.gameMoved(gameId, { turn: nextTurn });
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
// eslint-disable-next-line no-console
|
|
102
|
+
console.error('[dbf] broadcast failed:', e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
94
105
|
// Best-effort notify the new actor (if different and we have an email).
|
|
95
106
|
const newActor = this.opts.adapter.currentActor(next);
|
|
96
107
|
if (newActor && newActor !== actor && this.opts.notifier && meta.emails?.[newActor]) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"game-server.js","sourceRoot":"","sources":["../../src/server/game-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"game-server.js","sourceRoot":"","sources":["../../src/server/game-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type IdentityKind = 'anon' | 'registered';
|
|
2
|
+
/** Claims carried in an identity token. */
|
|
3
|
+
export interface IdentityClaims {
|
|
4
|
+
/** Stable player id (JWT `sub`). */
|
|
5
|
+
playerId: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
kind: IdentityKind;
|
|
8
|
+
}
|
|
9
|
+
/** JsonWebKey plus the `kid` (key id) field, which the DOM lib type omits. */
|
|
10
|
+
export type Jwk = JsonWebKey & {
|
|
11
|
+
kid?: string;
|
|
12
|
+
};
|
|
13
|
+
/** A JWK key set as published at the hub's /id/jwks endpoint. */
|
|
14
|
+
export interface Jwks {
|
|
15
|
+
keys: Jwk[];
|
|
16
|
+
}
|
|
17
|
+
/** Generate a fresh signing keypair. Run ONCE; store the private JWK as a hub
|
|
18
|
+
* secret, publish the public JWK via /id/jwks. Returns the kid too. */
|
|
19
|
+
export declare function generateIdentityKeypair(): Promise<{
|
|
20
|
+
privateJwk: Jwk;
|
|
21
|
+
publicJwk: Jwk;
|
|
22
|
+
kid: string;
|
|
23
|
+
}>;
|
|
24
|
+
/** Public JWK → a JWKS document for /id/jwks. */
|
|
25
|
+
export declare function toJwks(publicJwks: Jwk[]): Jwks;
|
|
26
|
+
/** Sign an identity token (hub side). `nowSeconds`/`ttlSeconds` injectable for
|
|
27
|
+
* testing; default ttl 1h. */
|
|
28
|
+
export declare function signIdentityToken(privateJwk: Jwk, claims: IdentityClaims, opts?: {
|
|
29
|
+
issuer?: string;
|
|
30
|
+
audience?: string;
|
|
31
|
+
ttlSeconds?: number;
|
|
32
|
+
nowSeconds?: number;
|
|
33
|
+
}): Promise<string>;
|
|
34
|
+
export interface VerifiedIdentity extends IdentityClaims {
|
|
35
|
+
issuer?: string;
|
|
36
|
+
audience?: string;
|
|
37
|
+
expiresAt: number;
|
|
38
|
+
}
|
|
39
|
+
/** Verify an identity token (game side) against the hub JWKS. Returns the
|
|
40
|
+
* identity, or null on any failure (bad signature, expired, unknown key,
|
|
41
|
+
* malformed). Never throws on untrusted input. `nowSeconds` injectable. */
|
|
42
|
+
export declare function verifyIdentityToken(token: string, jwks: Jwks, opts?: {
|
|
43
|
+
nowSeconds?: number;
|
|
44
|
+
audience?: string;
|
|
45
|
+
}): Promise<VerifiedIdentity | null>;
|
|
46
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../../src/server/identity/jwt.ts"],"names":[],"mappings":"AAWA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,CAAC;AAEjD,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,8EAA8E;AAC9E,MAAM,MAAM,GAAG,GAAG,UAAU,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,iEAAiE;AACjE,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,GAAG,EAAE,CAAC;CACb;AAsCD;wEACwE;AACxE,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,UAAU,EAAE,GAAG,CAAC;IAAC,SAAS,EAAE,GAAG,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;CAC9C,CAAC,CAQD;AAED,iDAAiD;AACjD,wBAAgB,MAAM,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAE9C;AAED;+BAC+B;AAC/B,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,GAAG,EACf,MAAM,EAAE,cAAc,EACtB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAC1F,OAAO,CAAC,MAAM,CAAC,CAkBjB;AAED,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;4EAE4E;AAC5E,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,EACV,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAyClC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Identity token (JWT) core — the trust mechanism for cross-game identity.
|
|
2
|
+
//
|
|
3
|
+
// The hub signs a short-lived identity token with its private key; every game
|
|
4
|
+
// VERIFIES it offline against the hub's published public key (JWKS) — no
|
|
5
|
+
// per-request call to the hub. This is what makes "one identity across separate
|
|
6
|
+
// origins" cheap and stateless. See docs/identity-design.md.
|
|
7
|
+
//
|
|
8
|
+
// ES256 (ECDSA P-256 + SHA-256) via WebCrypto only — no dependencies, and it
|
|
9
|
+
// runs unchanged in Cloudflare Workers/Pages Functions, Node 18+, and browsers
|
|
10
|
+
// (the verify side ships to game Functions via this package's /server entry).
|
|
11
|
+
const ALG = { name: 'ECDSA', namedCurve: 'P-256' };
|
|
12
|
+
const SIGN_ALG = { name: 'ECDSA', hash: 'SHA-256' };
|
|
13
|
+
function subtle() {
|
|
14
|
+
const c = globalThis.crypto;
|
|
15
|
+
if (!c?.subtle)
|
|
16
|
+
throw new Error('WebCrypto unavailable (need Workers, Node 18+, or a browser)');
|
|
17
|
+
return c.subtle;
|
|
18
|
+
}
|
|
19
|
+
// ---- base64url ----
|
|
20
|
+
function b64uFromBytes(bytes) {
|
|
21
|
+
let bin = '';
|
|
22
|
+
for (const b of bytes)
|
|
23
|
+
bin += String.fromCharCode(b);
|
|
24
|
+
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
25
|
+
}
|
|
26
|
+
function bytesFromB64u(s) {
|
|
27
|
+
const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/'));
|
|
28
|
+
const out = new Uint8Array(bin.length);
|
|
29
|
+
for (let i = 0; i < bin.length; i++)
|
|
30
|
+
out[i] = bin.charCodeAt(i);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function b64uFromString(s) {
|
|
34
|
+
return b64uFromBytes(new TextEncoder().encode(s));
|
|
35
|
+
}
|
|
36
|
+
function jsonFromB64u(s) {
|
|
37
|
+
return JSON.parse(new TextDecoder().decode(bytesFromB64u(s)));
|
|
38
|
+
}
|
|
39
|
+
/** Stable key id from the public JWK (so JWKS rotation works). */
|
|
40
|
+
async function thumbprint(publicJwk) {
|
|
41
|
+
// RFC 7638 thumbprint over the required EC members, lexicographically ordered.
|
|
42
|
+
const canon = JSON.stringify({ crv: publicJwk.crv, kty: publicJwk.kty, x: publicJwk.x, y: publicJwk.y });
|
|
43
|
+
const hash = await subtle().digest('SHA-256', new TextEncoder().encode(canon));
|
|
44
|
+
return b64uFromBytes(new Uint8Array(hash));
|
|
45
|
+
}
|
|
46
|
+
/** Generate a fresh signing keypair. Run ONCE; store the private JWK as a hub
|
|
47
|
+
* secret, publish the public JWK via /id/jwks. Returns the kid too. */
|
|
48
|
+
export async function generateIdentityKeypair() {
|
|
49
|
+
const pair = await subtle().generateKey(ALG, true, ['sign', 'verify']);
|
|
50
|
+
const privateJwk = await subtle().exportKey('jwk', pair.privateKey);
|
|
51
|
+
const publicJwk = await subtle().exportKey('jwk', pair.publicKey);
|
|
52
|
+
const kid = await thumbprint(publicJwk);
|
|
53
|
+
privateJwk.kid = kid;
|
|
54
|
+
publicJwk.kid = kid;
|
|
55
|
+
return { privateJwk, publicJwk, kid };
|
|
56
|
+
}
|
|
57
|
+
/** Public JWK → a JWKS document for /id/jwks. */
|
|
58
|
+
export function toJwks(publicJwks) {
|
|
59
|
+
return { keys: publicJwks.map((k) => ({ ...k, use: 'sig', alg: 'ES256' })) };
|
|
60
|
+
}
|
|
61
|
+
/** Sign an identity token (hub side). `nowSeconds`/`ttlSeconds` injectable for
|
|
62
|
+
* testing; default ttl 1h. */
|
|
63
|
+
export async function signIdentityToken(privateJwk, claims, opts = {}) {
|
|
64
|
+
const now = opts.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
65
|
+
const kid = privateJwk.kid ?? (await thumbprint(privateJwk));
|
|
66
|
+
const header = { alg: 'ES256', typ: 'JWT', kid };
|
|
67
|
+
const payload = {
|
|
68
|
+
sub: claims.playerId,
|
|
69
|
+
name: claims.displayName,
|
|
70
|
+
kind: claims.kind,
|
|
71
|
+
iat: now,
|
|
72
|
+
exp: now + (opts.ttlSeconds ?? 3600),
|
|
73
|
+
};
|
|
74
|
+
if (opts.issuer)
|
|
75
|
+
payload.iss = opts.issuer;
|
|
76
|
+
if (opts.audience)
|
|
77
|
+
payload.aud = opts.audience;
|
|
78
|
+
const key = await subtle().importKey('jwk', privateJwk, ALG, false, ['sign']);
|
|
79
|
+
const signingInput = `${b64uFromString(JSON.stringify(header))}.${b64uFromString(JSON.stringify(payload))}`;
|
|
80
|
+
const sig = await subtle().sign(SIGN_ALG, key, new TextEncoder().encode(signingInput));
|
|
81
|
+
return `${signingInput}.${b64uFromBytes(new Uint8Array(sig))}`;
|
|
82
|
+
}
|
|
83
|
+
/** Verify an identity token (game side) against the hub JWKS. Returns the
|
|
84
|
+
* identity, or null on any failure (bad signature, expired, unknown key,
|
|
85
|
+
* malformed). Never throws on untrusted input. `nowSeconds` injectable. */
|
|
86
|
+
export async function verifyIdentityToken(token, jwks, opts = {}) {
|
|
87
|
+
try {
|
|
88
|
+
const parts = token.split('.');
|
|
89
|
+
if (parts.length !== 3)
|
|
90
|
+
return null;
|
|
91
|
+
const [h, p, s] = parts;
|
|
92
|
+
const header = jsonFromB64u(h);
|
|
93
|
+
if (header.alg !== 'ES256')
|
|
94
|
+
return null;
|
|
95
|
+
const candidates = jwks.keys.filter((k) => !header.kid || k.kid === header.kid);
|
|
96
|
+
if (candidates.length === 0)
|
|
97
|
+
return null;
|
|
98
|
+
const data = new TextEncoder().encode(`${h}.${p}`);
|
|
99
|
+
const sig = bytesFromB64u(s);
|
|
100
|
+
let ok = false;
|
|
101
|
+
for (const jwk of candidates) {
|
|
102
|
+
try {
|
|
103
|
+
const key = await subtle().importKey('jwk', jwk, ALG, false, ['verify']);
|
|
104
|
+
if (await subtle().verify(SIGN_ALG, key, sig, data)) {
|
|
105
|
+
ok = true;
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch { /* try next key */ }
|
|
110
|
+
}
|
|
111
|
+
if (!ok)
|
|
112
|
+
return null;
|
|
113
|
+
const payload = jsonFromB64u(p);
|
|
114
|
+
const now = opts.nowSeconds ?? Math.floor(Date.now() / 1000);
|
|
115
|
+
if (typeof payload.exp !== 'number' || payload.exp < now)
|
|
116
|
+
return null;
|
|
117
|
+
if (opts.audience && payload.aud !== opts.audience)
|
|
118
|
+
return null;
|
|
119
|
+
if (!payload.sub || (payload.kind !== 'anon' && payload.kind !== 'registered'))
|
|
120
|
+
return null;
|
|
121
|
+
return {
|
|
122
|
+
playerId: payload.sub,
|
|
123
|
+
displayName: payload.name ?? '',
|
|
124
|
+
kind: payload.kind,
|
|
125
|
+
issuer: payload.iss,
|
|
126
|
+
audience: payload.aud,
|
|
127
|
+
expiresAt: payload.exp,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../../src/server/identity/jwt.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,8EAA8E;AAC9E,yEAAyE;AACzE,gFAAgF;AAChF,6DAA6D;AAC7D,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,8EAA8E;AAoB9E,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAW,CAAC;AAC5D,MAAM,QAAQ,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAW,CAAC;AAE7D,SAAS,MAAM;IACb,MAAM,CAAC,GAAI,UAAkC,CAAC,MAAM,CAAC;IACrD,IAAI,CAAC,CAAC,EAAE,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAChG,OAAO,CAAC,CAAC,MAAM,CAAC;AAClB,CAAC;AAED,sBAAsB;AACtB,SAAS,aAAa,CAAC,KAAiB;IACtC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AACD,SAAS,aAAa,CAAC,CAAS;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,GAAG,CAAC;AACb,CAAC;AACD,SAAS,cAAc,CAAC,CAAS;IAC/B,OAAO,aAAa,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC;AACD,SAAS,YAAY,CAAI,CAAS;IAChC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAM,CAAC;AACrE,CAAC;AAED,kEAAkE;AAClE,KAAK,UAAU,UAAU,CAAC,SAAc;IACtC,+EAA+E;IAC/E,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;IACzG,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAiB,CAAC,CAAC;IAC/F,OAAO,aAAa,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;AAC7C,CAAC;AAED;wEACwE;AACxE,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAG3C,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACvE,MAAM,UAAU,GAAQ,MAAM,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACzE,MAAM,SAAS,GAAQ,MAAM,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IACvE,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAC;IACxC,UAAU,CAAC,GAAG,GAAG,GAAG,CAAC;IACrB,SAAS,CAAC,GAAG,GAAG,GAAG,CAAC;IACpB,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;AACxC,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,MAAM,CAAC,UAAiB;IACtC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;AAC/E,CAAC;AAED;+BAC+B;AAC/B,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAe,EACf,MAAsB,EACtB,OAAyF,EAAE;IAE3F,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7D,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACjD,MAAM,OAAO,GAA4B;QACvC,GAAG,EAAE,MAAM,CAAC,QAAQ;QACpB,IAAI,EAAE,MAAM,CAAC,WAAW;QACxB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;KACrC,CAAC;IACF,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC;IAC3C,IAAI,IAAI,CAAC,QAAQ;QAAE,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC;IAE/C,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,MAAM,YAAY,GAAG,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;IAC5G,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAiB,CAAC,CAAC;IACvG,OAAO,GAAG,YAAY,IAAI,aAAa,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;AACjE,CAAC;AAQD;;4EAE4E;AAC5E,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAa,EACb,IAAU,EACV,OAAmD,EAAE;IAErD,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;QACxB,MAAM,MAAM,GAAG,YAAY,CAAiC,CAAC,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,GAAG,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;QAExC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,CAAC,CAAC;QAChF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEzC,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAiB,CAAC;QACnE,MAAM,GAAG,GAAG,aAAa,CAAC,CAAC,CAAiB,CAAC;QAC7C,IAAI,EAAE,GAAG,KAAK,CAAC;QACf,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,MAAM,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACzE,IAAI,MAAM,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC;oBAAC,EAAE,GAAG,IAAI,CAAC;oBAAC,MAAM;gBAAC,CAAC;YAC5E,CAAC;YAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAErB,MAAM,OAAO,GAAG,YAAY,CAEzB,CAAC,CAAC,CAAC;QACN,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC7D,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,GAAG,GAAG;YAAE,OAAO,IAAI,CAAC;QACtE,IAAI,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAChE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC;YAAE,OAAO,IAAI,CAAC;QAE5F,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,GAAG;YACrB,WAAW,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE;YAC/B,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,OAAO,CAAC,GAAG;YACnB,QAAQ,EAAE,OAAO,CAAC,GAAG;YACrB,SAAS,EAAE,OAAO,CAAC,GAAG;SACvB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export type { Notifier, NotifyTurnArgs } from './notify.js';
|
|
|
7
7
|
export { NoopNotifier } from './notifiers/noop-notifier.js';
|
|
8
8
|
export { ResendNotifier } from './notifiers/resend-notifier.js';
|
|
9
9
|
export type { ResendNotifierOpts } from './notifiers/resend-notifier.js';
|
|
10
|
+
export { generateIdentityKeypair, signIdentityToken, verifyIdentityToken, toJwks, } from './identity/jwt.js';
|
|
11
|
+
export type { IdentityClaims, IdentityKind, VerifiedIdentity, Jwks, Jwk, } from './identity/jwt.js';
|
|
10
12
|
export type { ReportForwarder } from './forward.js';
|
|
11
13
|
export { NoopForwarder } from './forwarders/noop-forwarder.js';
|
|
14
|
+
export type { GameBroadcaster, SupabaseBroadcasterOpts } from './realtime.js';
|
|
15
|
+
export { NoopBroadcaster, SupabaseBroadcaster } from './realtime.js';
|
|
12
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,GAC/E,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,GACjE,MAAM,YAAY,CAAC;AAOpB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EACV,cAAc,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,gBAAgB,GAC/E,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,GACjE,MAAM,YAAY,CAAC;AAOpB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,OAAO,EACL,uBAAuB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,GACxE,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,GAC1D,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,YAAY,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -8,5 +8,7 @@ export { ConflictError } from './store.js';
|
|
|
8
8
|
export { SupabaseStore } from './stores/supabase-store.js';
|
|
9
9
|
export { NoopNotifier } from './notifiers/noop-notifier.js';
|
|
10
10
|
export { ResendNotifier } from './notifiers/resend-notifier.js';
|
|
11
|
+
export { generateIdentityKeypair, signIdentityToken, verifyIdentityToken, toJwks, } from './identity/jwt.js';
|
|
11
12
|
export { NoopForwarder } from './forwarders/noop-forwarder.js';
|
|
13
|
+
export { NoopBroadcaster, SupabaseBroadcaster } from './realtime.js';
|
|
12
14
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAS9C,2EAA2E;AAC3E,4EAA4E;AAC5E,wEAAwE;AACxE,+EAA+E;AAC/E,gDAAgD;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAS9C,2EAA2E;AAC3E,4EAA4E;AAC5E,wEAAwE;AACxE,+EAA+E;AAC/E,gDAAgD;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGhE,OAAO,EACL,uBAAuB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,GACxE,MAAM,mBAAmB,CAAC;AAM3B,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAG/D,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Fan a move notification out to subscribers of a game. Best-effort: the
|
|
2
|
+
* caller swallows failures (clients still have polling as a fallback). */
|
|
3
|
+
export interface GameBroadcaster {
|
|
4
|
+
gameMoved(gameId: string, info: {
|
|
5
|
+
turn: number;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
/** Default: broadcasts nothing (clients fall back to polling). */
|
|
9
|
+
export declare class NoopBroadcaster implements GameBroadcaster {
|
|
10
|
+
gameMoved(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export interface SupabaseBroadcasterOpts {
|
|
13
|
+
supabaseUrl: string;
|
|
14
|
+
/** service-role/secret key — server-side only. */
|
|
15
|
+
serviceKey: string;
|
|
16
|
+
/** Channel topic prefix; clients subscribe to `${prefix}${gameId}`. Default 'game:'. */
|
|
17
|
+
topicPrefix?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Broadcasts via Supabase Realtime's server broadcast endpoint. The payload is
|
|
20
|
+
* only the turn number — never state. */
|
|
21
|
+
export declare class SupabaseBroadcaster implements GameBroadcaster {
|
|
22
|
+
private opts;
|
|
23
|
+
constructor(opts: SupabaseBroadcasterOpts);
|
|
24
|
+
gameMoved(gameId: string, info: {
|
|
25
|
+
turn: number;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/server/realtime.ts"],"names":[],"mappings":"AASA;2EAC2E;AAC3E,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,kEAAkE;AAClE,qBAAa,eAAgB,YAAW,eAAe;IAC/C,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;0CAC0C;AAC1C,qBAAa,mBAAoB,YAAW,eAAe;IAC7C,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,uBAAuB;IAE3C,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAavE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Realtime push — the SIGNAL half of low-latency online play.
|
|
2
|
+
//
|
|
3
|
+
// SECURITY-CRITICAL: this broadcasts only a tiny "game moved → turn N" ping. It
|
|
4
|
+
// must NEVER carry game state. Snapshot rows hold the UNREDACTED state (hidden
|
|
5
|
+
// hands, deck order); the only safe way for a client to learn the new state is
|
|
6
|
+
// to re-fetch through the server endpoint, which applies `viewFor` redaction.
|
|
7
|
+
// So: server broadcasts {turn} → client re-fetches the redacted view. Do NOT
|
|
8
|
+
// subscribe clients to the snapshots table directly (that would leak).
|
|
9
|
+
/** Default: broadcasts nothing (clients fall back to polling). */
|
|
10
|
+
export class NoopBroadcaster {
|
|
11
|
+
async gameMoved() { }
|
|
12
|
+
}
|
|
13
|
+
/** Broadcasts via Supabase Realtime's server broadcast endpoint. The payload is
|
|
14
|
+
* only the turn number — never state. */
|
|
15
|
+
export class SupabaseBroadcaster {
|
|
16
|
+
opts;
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
this.opts = opts;
|
|
19
|
+
}
|
|
20
|
+
async gameMoved(gameId, info) {
|
|
21
|
+
const topic = `${this.opts.topicPrefix ?? 'game:'}${gameId}`;
|
|
22
|
+
const res = await fetch(`${this.opts.supabaseUrl}/realtime/v1/api/broadcast`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
apikey: this.opts.serviceKey,
|
|
26
|
+
Authorization: `Bearer ${this.opts.serviceKey}`,
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({ messages: [{ topic, event: 'moved', payload: { turn: info.turn } }] }),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok)
|
|
32
|
+
throw new Error(`Supabase broadcast failed: ${res.status} ${await res.text()}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=realtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../../src/server/realtime.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,6EAA6E;AAC7E,uEAAuE;AAQvE,kEAAkE;AAClE,MAAM,OAAO,eAAe;IAC1B,KAAK,CAAC,SAAS,KAAgC,CAAC;CACjD;AAUD;0CAC0C;AAC1C,MAAM,OAAO,mBAAmB;IACV;IAApB,YAAoB,IAA6B;QAA7B,SAAI,GAAJ,IAAI,CAAyB;IAAG,CAAC;IAErD,KAAK,CAAC,SAAS,CAAC,MAAc,EAAE,IAAsB;QACpD,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,OAAO,GAAG,MAAM,EAAE,CAAC;QAC7D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,4BAA4B,EAAE;YAC5E,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU;gBAC5B,aAAa,EAAE,UAAU,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;gBAC/C,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;SAC9F,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/F,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "digital-boardgame-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Foundation library for turn-based digital boardgames: deterministic engine plumbing, async multiplayer, agent-friendly bug triage.",
|
|
5
5
|
"keywords": ["boardgame", "board-game", "multiplayer", "async-multiplayer", "turn-based", "game-framework", "supabase", "cloudflare-pages"],
|
|
6
6
|
"author": "John Champaign",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"types": "./dist/client/index.d.ts",
|
|
29
29
|
"import": "./dist/client/index.js"
|
|
30
30
|
},
|
|
31
|
+
"./client/realtime": {
|
|
32
|
+
"types": "./dist/client/realtime.d.ts",
|
|
33
|
+
"import": "./dist/client/realtime.js"
|
|
34
|
+
},
|
|
31
35
|
"./vite": {
|
|
32
36
|
"types": "./dist/vite/index.d.ts",
|
|
33
37
|
"import": "./dist/vite/index.js"
|