@flow-industries/id 0.17.0 → 0.19.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 +19 -0
- package/dist/sdk/client/access-key.d.ts +1 -1
- package/dist/sdk/client/access-key.js +20 -6
- package/dist/sdk/client/create-flow.js +19 -11
- package/dist/sdk/client/dialog-host.js +8 -2
- package/dist/sdk/client/flow-widget.js +13 -2
- package/dist/sdk/client/idb.d.ts +2 -2
- package/dist/sdk/client/idb.js +17 -2
- package/dist/sdk/client/profile-button.js +18 -3
- package/dist/sdk/client/rooms.js +19 -4
- package/dist/sdk/client/session.js +8 -5
- package/dist/sdk/client/signing.js +22 -7
- package/dist/sdk/client/store.js +4 -5
- package/dist/sdk/cookies.d.ts +3 -1
- package/dist/sdk/cookies.js +11 -9
- package/dist/sdk/dialog/remote/Messenger.d.ts +0 -5
- package/dist/sdk/dialog/remote/Messenger.js +26 -14
- package/dist/sdk/driver-error.d.ts +8 -0
- package/dist/sdk/driver-error.js +17 -0
- package/dist/sdk/hex.d.ts +6 -0
- package/dist/sdk/hex.js +6 -0
- package/dist/sdk/id-host.d.ts +1 -1
- package/dist/sdk/id-host.js +4 -5
- package/dist/sdk/json.d.ts +17 -0
- package/dist/sdk/json.js +5 -0
- package/dist/sdk/react/flow-widget.d.ts +4 -0
- package/dist/sdk/react/flow-widget.js +19 -4
- package/dist/sdk/react/hooks.d.ts +8 -4
- package/dist/sdk/react/hooks.js +1 -10
- package/dist/sdk/react/profile-button.d.ts +5 -0
- package/dist/sdk/react/profile-button.js +20 -4
- package/dist/sdk/server.js +2 -1
- package/dist/sdk/session-core.d.ts +1 -6
- package/dist/sdk/session-core.js +7 -4
- package/dist/sdk/session-route.d.ts +0 -20
- package/dist/sdk/session-route.js +25 -9
- package/dist/sdk/start/graceful-shutdown.d.ts +8 -0
- package/dist/sdk/start/graceful-shutdown.js +81 -0
- package/dist/sdk/start/index.d.ts +2 -0
- package/dist/sdk/start/index.js +3 -2
- package/dist/sdk/token-expiry.js +3 -1
- package/dist/sdk/types/cosmetics.d.ts +162 -0
- package/dist/sdk/types/cosmetics.js +78 -0
- package/dist/sdk/types/game.d.ts +58 -0
- package/dist/sdk/types/game.js +8 -0
- package/dist/sdk/types/index.d.ts +8 -4
- package/dist/sdk/types/index.js +2 -0
- package/dist/sdk/types/messenger.d.ts +1 -1
- package/dist/sdk/types/protocol.d.ts +6 -5
- package/dist/sdk/types/protocol.js +10 -4
- package/dist/sdk/types/room-events.d.ts +49 -1
- package/dist/sdk/types/rooms.d.ts +53 -3
- package/dist/sdk/types/rooms.js +0 -1
- package/dist/sdk/types/sdk.d.ts +22 -5
- package/dist/sdk/types/server.d.ts +18 -0
- package/dist/sdk/verify.js +1 -0
- package/dist/sdk/wagmi/index.d.ts +7 -3
- package/dist/sdk/wagmi/index.js +19 -32
- package/package.json +11 -8
- package/dist/sdk/client/refresh-store.d.ts +0 -22
- package/dist/sdk/client/refresh-store.js +0 -66
package/dist/sdk/start/index.js
CHANGED
|
@@ -24,13 +24,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
24
24
|
import { useMemo } from "react";
|
|
25
25
|
import { createFlow, createStaticFlow, getFlow } from "../client";
|
|
26
26
|
import { FlowIdProvider } from "../react";
|
|
27
|
+
export { DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_MS, installGracefulShutdown, } from "./graceful-shutdown";
|
|
27
28
|
/**
|
|
28
29
|
* The `beforeLoad` body: during SSR resolve the session server-side (via the
|
|
29
30
|
* app's `getFlowSession` server fn, which appends rotated Set-Cookie values);
|
|
30
31
|
* in the browser snapshot the live singleton instead — no network.
|
|
31
32
|
*/
|
|
32
33
|
export async function flowSessionContext(getSession) {
|
|
33
|
-
if (
|
|
34
|
+
if (!("window" in globalThis))
|
|
34
35
|
return { session: await getSession() };
|
|
35
36
|
const flow = getFlow();
|
|
36
37
|
const session = flow?.user && flow.jwt
|
|
@@ -45,7 +46,7 @@ export async function flowSessionContext(getSession) {
|
|
|
45
46
|
* is fresh.
|
|
46
47
|
*/
|
|
47
48
|
export function FlowRoot({ session, autoGuest, host, additionalAudiences, onAdditionalSessions, children, }) {
|
|
48
|
-
const flow = useMemo(() =>
|
|
49
|
+
const flow = useMemo(() => !("window" in globalThis)
|
|
49
50
|
? createStaticFlow(session, { host })
|
|
50
51
|
: createFlow({
|
|
51
52
|
initialState: session,
|
package/dist/sdk/token-expiry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isNum, isRecord } from "./json";
|
|
1
2
|
// Refresh a little before the JWT's `exp` so a token handed out to a caller
|
|
2
3
|
// is still valid by the time it reaches the relying party, absorbing request
|
|
3
4
|
// latency and minor client/server clock skew.
|
|
@@ -10,7 +11,8 @@ export function jwtExp(token) {
|
|
|
10
11
|
try {
|
|
11
12
|
const padded = (parts[1] ?? "").replace(/-/g, "+").replace(/_/g, "/");
|
|
12
13
|
const payload = JSON.parse(atob(padded));
|
|
13
|
-
|
|
14
|
+
const exp = isRecord(payload) ? payload.exp : undefined;
|
|
15
|
+
return isNum(exp) ? exp : null;
|
|
14
16
|
}
|
|
15
17
|
catch {
|
|
16
18
|
return null;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cosmetic items, the space they occupy on the model, and the equipped set a
|
|
3
|
+
* game server receives on `/api/session/verify`.
|
|
4
|
+
*
|
|
5
|
+
* Two region vocabularies live here and must never be collapsed into one:
|
|
6
|
+
* {@link EquipRegion} is *item vs item* (can these be worn together), while
|
|
7
|
+
* {@link BodyRegion} is *cosmetic vs body* (what geometry must stop drawing so
|
|
8
|
+
* nothing pokes through). TF2 keeps them separate — equip regions and
|
|
9
|
+
* bodygroups — and conflating them is what produces the clipping bugs.
|
|
10
|
+
*
|
|
11
|
+
* {@link CosmeticMaterial} is a third vocabulary and belongs to neither: it
|
|
12
|
+
* names a *surface* of the model rather than a space on it, and it is what a
|
|
13
|
+
* colour is applied to.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* A space on the model an item occupies. Two items occupying the same region,
|
|
17
|
+
* or overlapping ones, cannot be equipped together.
|
|
18
|
+
*
|
|
19
|
+
* A deliberately small subset of TF2's 68: enough to express the composite
|
|
20
|
+
* case (`whole_head` swallowing the pieces of a head) that makes the rule
|
|
21
|
+
* non-trivial, without inventing regions no item claims yet.
|
|
22
|
+
*/
|
|
23
|
+
export type EquipRegion = "whole_head" | "hat" | "hair" | "face" | "glasses" | "ears" | "torso" | "arms" | "back" | "legs" | "feet";
|
|
24
|
+
/**
|
|
25
|
+
* Every body area a cosmetic may suppress — the bodygroup half's whole
|
|
26
|
+
* vocabulary, head down.
|
|
27
|
+
*
|
|
28
|
+
* SOURCE OF TRUTH: the `regions` keys of
|
|
29
|
+
* `game/games/arena/player/luna_bodygroups.tres`, which is authored against
|
|
30
|
+
* the shipped model. This is a mirror, kept as a constant so auth can resolve
|
|
31
|
+
* a loadout without reaching into the game repo at runtime.
|
|
32
|
+
*
|
|
33
|
+
* WHEN THE MODEL IS RE-CUT: update this list to the new `regions` keys, then
|
|
34
|
+
* fix whatever `hides` in the catalog no longer resolves. `cosmetics.test.ts`
|
|
35
|
+
* fails on the first entry that names a region absent here, and the type below
|
|
36
|
+
* makes the same mistake a typecheck error — which is the whole point of
|
|
37
|
+
* mirroring the names into one constant. AUTH-210 is what happens without it:
|
|
38
|
+
* the catalog was written from a nine-region proposal in
|
|
39
|
+
* `docs/internal/cosmetics/bodygroups.md` section 6, the fifteen-region cut
|
|
40
|
+
* that actually shipped shared not one name with it, and every cosmetic
|
|
41
|
+
* silently suppressed nothing.
|
|
42
|
+
*/
|
|
43
|
+
export declare const BODY_REGIONS: readonly ["head", "ears", "hair", "body_neck", "body_chest", "body_arm_upper_l", "body_arm_upper_r", "body_arm_lower_l", "body_arm_lower_r", "body_hand_l", "body_hand_r", "body_pelvis", "body_leg_upper_l", "body_leg_upper_r", "body_leg_lower_l", "body_leg_lower_r", "body_foot_l", "body_foot_r"];
|
|
44
|
+
/**
|
|
45
|
+
* A body area a cosmetic suppresses so the body cannot clip through it — the
|
|
46
|
+
* bodygroup half. Derived from {@link BODY_REGIONS} rather than written out a
|
|
47
|
+
* second time, so the list and the type cannot disagree.
|
|
48
|
+
*/
|
|
49
|
+
export type BodyRegion = (typeof BODY_REGIONS)[number];
|
|
50
|
+
/**
|
|
51
|
+
* Every material the shipped model set exposes, body and cosmetic alike — the
|
|
52
|
+
* surfaces a colour can be applied to.
|
|
53
|
+
*
|
|
54
|
+
* SOURCE OF TRUTH: the glTF material names inside
|
|
55
|
+
* `game/games/arena/resources/models/LUNA12.7.glb` (`skin`, `brownhair`,
|
|
56
|
+
* `tshirt`, `shorts`) and the per-cosmetic GLBs beside it
|
|
57
|
+
* (`cosmetics/LUNA_boots.glb` names `Shoes`, `cosmetics/LUNA_glasses.glb`
|
|
58
|
+
* names `outline`). `Shoes` is capitalised because the model capitalises it: a
|
|
59
|
+
* tidied-up copy here would name a material no renderer can find, and the
|
|
60
|
+
* paint would silently land nowhere.
|
|
61
|
+
*
|
|
62
|
+
* WHEN THE MODEL IS RE-CUT: update this list to the new material names, then
|
|
63
|
+
* fix whatever `paintable` in the catalog no longer resolves. This is the same
|
|
64
|
+
* mirror-and-guard {@link BODY_REGIONS} is, kept for the same reason —
|
|
65
|
+
* AUTH-210 is what a hardcoded copy with no test looks like a month later.
|
|
66
|
+
*/
|
|
67
|
+
export declare const COSMETIC_MATERIALS: readonly ["skin", "brownhair", "tshirt", "shorts", "Shoes", "outline"];
|
|
68
|
+
/**
|
|
69
|
+
* A material a cosmetic may expose for recolouring. Derived from
|
|
70
|
+
* {@link COSMETIC_MATERIALS} rather than written out a second time, so the
|
|
71
|
+
* list and the type cannot disagree.
|
|
72
|
+
*/
|
|
73
|
+
export type CosmeticMaterial = (typeof COSMETIC_MATERIALS)[number];
|
|
74
|
+
/**
|
|
75
|
+
* One equipment slot. The slot name IS the key inside the `appearance` blob,
|
|
76
|
+
* so equipped state needs no table and no migration of its own.
|
|
77
|
+
*
|
|
78
|
+
* The vocabulary is complete rather than "the slots something is sold for":
|
|
79
|
+
* `hat` has no product today and is still a slot, because the wire format, the
|
|
80
|
+
* game's whitelist and the region rules all speak in slots, and a vocabulary
|
|
81
|
+
* that shrank whenever a slot emptied would churn the contract every time
|
|
82
|
+
* content lands.
|
|
83
|
+
*/
|
|
84
|
+
export type CosmeticSlot = "hat" | "face" | "top" | "bottom" | "feet";
|
|
85
|
+
/** One item in the catalog. */
|
|
86
|
+
export interface CosmeticItem {
|
|
87
|
+
id: string;
|
|
88
|
+
slot: CosmeticSlot;
|
|
89
|
+
name: string;
|
|
90
|
+
/**
|
|
91
|
+
* ALWAYS plural, even for a single region, and the only shape there is: TF2
|
|
92
|
+
* carried a singular `equip_region` key alongside the plural one, and items
|
|
93
|
+
* that declared several regions through the singular form silently escaped
|
|
94
|
+
* enforcement (Source-1-Games#4678). A singular field is never added here.
|
|
95
|
+
*/
|
|
96
|
+
regions: readonly EquipRegion[];
|
|
97
|
+
/**
|
|
98
|
+
* Body regions this item suppresses. Required rather than optional: a
|
|
99
|
+
* cosmetic that declares nothing is a content bug, not a default, so an
|
|
100
|
+
* author must write `[]` deliberately (an accessory that covers no skin)
|
|
101
|
+
* instead of forgetting the field on a cosmetic that does.
|
|
102
|
+
*/
|
|
103
|
+
hides: readonly BodyRegion[];
|
|
104
|
+
/**
|
|
105
|
+
* The materials on this item a player may recolour. Plural for the reason
|
|
106
|
+
* {@link CosmeticItem.regions} is, and required for the reason
|
|
107
|
+
* {@link CosmeticItem.hides} is: `[]` is an item stating that nothing on it
|
|
108
|
+
* takes paint, while a missing field is an author who never considered the
|
|
109
|
+
* question. The fault is silence, not emptiness.
|
|
110
|
+
*/
|
|
111
|
+
paintable: readonly CosmeticMaterial[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* One painted surface on an equipped item, as `/api/session/verify` hands it
|
|
115
|
+
* to a renderer: recolour this material on this item to this colour.
|
|
116
|
+
*
|
|
117
|
+
* A LIST per item rather than a `material -> colour` map, because a map is
|
|
118
|
+
* exactly the shape that cannot grow: GAME-178 paints a masked region of an
|
|
119
|
+
* item instead of its whole surface, which arrives as another field here and a
|
|
120
|
+
* SECOND entry naming the same material.
|
|
121
|
+
*/
|
|
122
|
+
export interface CosmeticPaint {
|
|
123
|
+
material: CosmeticMaterial;
|
|
124
|
+
color: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* One recolourable surface the equipped set exposes: which worn item, which of
|
|
128
|
+
* its materials, and the `appearance` key holding the colour.
|
|
129
|
+
*/
|
|
130
|
+
export interface PaintSlot {
|
|
131
|
+
key: string;
|
|
132
|
+
slot: CosmeticSlot;
|
|
133
|
+
item: CosmeticItem;
|
|
134
|
+
material: CosmeticMaterial;
|
|
135
|
+
}
|
|
136
|
+
/** One equipped item as `/api/session/verify` hands it to a game server. */
|
|
137
|
+
export interface EquippedCosmetic {
|
|
138
|
+
id: string;
|
|
139
|
+
name: string;
|
|
140
|
+
regions: readonly EquipRegion[];
|
|
141
|
+
hides: readonly BodyRegion[];
|
|
142
|
+
/** The colours to apply, one entry per material the player has painted. */
|
|
143
|
+
paint: readonly CosmeticPaint[];
|
|
144
|
+
}
|
|
145
|
+
/** The verified equipped set, keyed by slot. An absent slot is empty. */
|
|
146
|
+
export type EquippedCosmetics = Partial<Record<CosmeticSlot, EquippedCosmetic>>;
|
|
147
|
+
/** The two items that cannot be worn together, and the region they fight over. */
|
|
148
|
+
export interface EquipConflict {
|
|
149
|
+
region: EquipRegion;
|
|
150
|
+
items: [string, string];
|
|
151
|
+
slots: [CosmeticSlot, CosmeticSlot];
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Why a settings write was refused by its surface's cross-field rule. Returned
|
|
155
|
+
* from the one write path and rendered as the 409 body, so a client can mark
|
|
156
|
+
* the offending keys instead of guessing which of them lost.
|
|
157
|
+
*/
|
|
158
|
+
export interface SettingsConflict {
|
|
159
|
+
code: string;
|
|
160
|
+
message: string;
|
|
161
|
+
keys: string[];
|
|
162
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cosmetic items, the space they occupy on the model, and the equipped set a
|
|
3
|
+
* game server receives on `/api/session/verify`.
|
|
4
|
+
*
|
|
5
|
+
* Two region vocabularies live here and must never be collapsed into one:
|
|
6
|
+
* {@link EquipRegion} is *item vs item* (can these be worn together), while
|
|
7
|
+
* {@link BodyRegion} is *cosmetic vs body* (what geometry must stop drawing so
|
|
8
|
+
* nothing pokes through). TF2 keeps them separate — equip regions and
|
|
9
|
+
* bodygroups — and conflating them is what produces the clipping bugs.
|
|
10
|
+
*
|
|
11
|
+
* {@link CosmeticMaterial} is a third vocabulary and belongs to neither: it
|
|
12
|
+
* names a *surface* of the model rather than a space on it, and it is what a
|
|
13
|
+
* colour is applied to.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Every body area a cosmetic may suppress — the bodygroup half's whole
|
|
17
|
+
* vocabulary, head down.
|
|
18
|
+
*
|
|
19
|
+
* SOURCE OF TRUTH: the `regions` keys of
|
|
20
|
+
* `game/games/arena/player/luna_bodygroups.tres`, which is authored against
|
|
21
|
+
* the shipped model. This is a mirror, kept as a constant so auth can resolve
|
|
22
|
+
* a loadout without reaching into the game repo at runtime.
|
|
23
|
+
*
|
|
24
|
+
* WHEN THE MODEL IS RE-CUT: update this list to the new `regions` keys, then
|
|
25
|
+
* fix whatever `hides` in the catalog no longer resolves. `cosmetics.test.ts`
|
|
26
|
+
* fails on the first entry that names a region absent here, and the type below
|
|
27
|
+
* makes the same mistake a typecheck error — which is the whole point of
|
|
28
|
+
* mirroring the names into one constant. AUTH-210 is what happens without it:
|
|
29
|
+
* the catalog was written from a nine-region proposal in
|
|
30
|
+
* `docs/internal/cosmetics/bodygroups.md` section 6, the fifteen-region cut
|
|
31
|
+
* that actually shipped shared not one name with it, and every cosmetic
|
|
32
|
+
* silently suppressed nothing.
|
|
33
|
+
*/
|
|
34
|
+
export const BODY_REGIONS = [
|
|
35
|
+
"head",
|
|
36
|
+
"ears",
|
|
37
|
+
"hair",
|
|
38
|
+
"body_neck",
|
|
39
|
+
"body_chest",
|
|
40
|
+
"body_arm_upper_l",
|
|
41
|
+
"body_arm_upper_r",
|
|
42
|
+
"body_arm_lower_l",
|
|
43
|
+
"body_arm_lower_r",
|
|
44
|
+
"body_hand_l",
|
|
45
|
+
"body_hand_r",
|
|
46
|
+
"body_pelvis",
|
|
47
|
+
"body_leg_upper_l",
|
|
48
|
+
"body_leg_upper_r",
|
|
49
|
+
"body_leg_lower_l",
|
|
50
|
+
"body_leg_lower_r",
|
|
51
|
+
"body_foot_l",
|
|
52
|
+
"body_foot_r",
|
|
53
|
+
];
|
|
54
|
+
/**
|
|
55
|
+
* Every material the shipped model set exposes, body and cosmetic alike — the
|
|
56
|
+
* surfaces a colour can be applied to.
|
|
57
|
+
*
|
|
58
|
+
* SOURCE OF TRUTH: the glTF material names inside
|
|
59
|
+
* `game/games/arena/resources/models/LUNA12.7.glb` (`skin`, `brownhair`,
|
|
60
|
+
* `tshirt`, `shorts`) and the per-cosmetic GLBs beside it
|
|
61
|
+
* (`cosmetics/LUNA_boots.glb` names `Shoes`, `cosmetics/LUNA_glasses.glb`
|
|
62
|
+
* names `outline`). `Shoes` is capitalised because the model capitalises it: a
|
|
63
|
+
* tidied-up copy here would name a material no renderer can find, and the
|
|
64
|
+
* paint would silently land nowhere.
|
|
65
|
+
*
|
|
66
|
+
* WHEN THE MODEL IS RE-CUT: update this list to the new material names, then
|
|
67
|
+
* fix whatever `paintable` in the catalog no longer resolves. This is the same
|
|
68
|
+
* mirror-and-guard {@link BODY_REGIONS} is, kept for the same reason —
|
|
69
|
+
* AUTH-210 is what a hardcoded copy with no test looks like a month later.
|
|
70
|
+
*/
|
|
71
|
+
export const COSMETIC_MATERIALS = [
|
|
72
|
+
"skin",
|
|
73
|
+
"brownhair",
|
|
74
|
+
"tshirt",
|
|
75
|
+
"shorts",
|
|
76
|
+
"Shoes",
|
|
77
|
+
"outline",
|
|
78
|
+
];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { RawRecord } from "../json";
|
|
2
|
+
/**
|
|
3
|
+
* Game-server lifecycle telemetry. The same record shape is POSTed to the
|
|
4
|
+
* OpenObserve `game_events` stream (ops, TTL'd) and to this server's durable
|
|
5
|
+
* ingest (`POST /api/game/events`), so one emitter feeds both and a new event
|
|
6
|
+
* kind needs no wire change here.
|
|
7
|
+
*/
|
|
8
|
+
/** One lifecycle record as the game server reports it. `sessionId` pairs a
|
|
9
|
+
* player's join with their leave, which is what makes time-present a self-join
|
|
10
|
+
* instead of a guess about reused peer ids. Anything not named here rides in
|
|
11
|
+
* `detail`. */
|
|
12
|
+
export interface GameEventReport {
|
|
13
|
+
/** The reporter's idempotency key. Resending a batch with stable ids is a
|
|
14
|
+
* no-op; omit it and the server mints one, making the retry a duplicate. */
|
|
15
|
+
id?: string;
|
|
16
|
+
event: string;
|
|
17
|
+
ts: string;
|
|
18
|
+
serverId: string;
|
|
19
|
+
sessionId?: string;
|
|
20
|
+
roomId?: string;
|
|
21
|
+
roomSlug?: string;
|
|
22
|
+
sub?: string;
|
|
23
|
+
reason?: string;
|
|
24
|
+
detail?: RawRecord;
|
|
25
|
+
}
|
|
26
|
+
/** A player's stay in a room, reconstructed by pairing the join and leave rows
|
|
27
|
+
* that share a `sessionId`. A same-server shutdown closes a missing leave;
|
|
28
|
+
* `leftAt` remains null only while the session is open or when neither closing
|
|
29
|
+
* record survived. */
|
|
30
|
+
export interface GamePlayerSession {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
userId: string;
|
|
33
|
+
roomId: string | null;
|
|
34
|
+
roomSlug: string | null;
|
|
35
|
+
joinedAt: string;
|
|
36
|
+
leftAt: string | null;
|
|
37
|
+
reason: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare const GAME_LIFECYCLE_EVENT_NAMES: readonly ["game.server.boot", "game.server.shutdown", "game.room.open", "game.room.close", "game.player.join", "game.player.leave"];
|
|
40
|
+
export type GameLifecycleEventName = (typeof GAME_LIFECYCLE_EVENT_NAMES)[number];
|
|
41
|
+
/** Product-safe projection of a durable lifecycle record. Raw `detail` is
|
|
42
|
+
* intentionally absent from this service-to-service contract. */
|
|
43
|
+
export interface GameLifecycleFeedEvent {
|
|
44
|
+
id: string;
|
|
45
|
+
event: GameLifecycleEventName;
|
|
46
|
+
occurredAt: string;
|
|
47
|
+
serverId: string;
|
|
48
|
+
sessionId: string | null;
|
|
49
|
+
roomId: string | null;
|
|
50
|
+
roomSlug: string | null;
|
|
51
|
+
userId: string | null;
|
|
52
|
+
reason: string | null;
|
|
53
|
+
}
|
|
54
|
+
export interface GameLifecycleFeedPage {
|
|
55
|
+
events: GameLifecycleFeedEvent[];
|
|
56
|
+
nextCursor: string;
|
|
57
|
+
hasMore: boolean;
|
|
58
|
+
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
export type { AccountSession, AccountSessionsResponse, AdditionalSession, AuthConfig, AuthResponse, AuthResponseWithWebAuthn, FlowCredential, FlowUser, PasskeyPluginOptions, RevokeAllSessionsResponse, RevokeSessionResponse, SecurityActivityEntry, SecurityActivityResponse, SessionUser, VerifiedFlowJWT, VerifyOptions, WebAuthnSignature, } from "./auth";
|
|
2
|
+
export type { BodyRegion, CosmeticItem, CosmeticMaterial, CosmeticPaint, CosmeticSlot, EquipConflict, EquippedCosmetic, EquippedCosmetics, EquipRegion, PaintSlot, SettingsConflict, } from "./cosmetics";
|
|
3
|
+
export { BODY_REGIONS, COSMETIC_MATERIALS } from "./cosmetics";
|
|
2
4
|
export type { BoundaryError, DialogCustomFeatures, DialogCustomLabels, DialogError, DialogReferrer, DialogState, Loadable, ProfileData, ProfileIdentity, } from "./dialog";
|
|
3
5
|
export type { AuthErrorCode, AuthEventName, AuthEventRecord, AuthMode, AuthOutcome, BeaconBody, FunnelStep, } from "./events";
|
|
6
|
+
export type { GameEventReport, GameLifecycleEventName, GameLifecycleFeedEvent, GameLifecycleFeedPage, GamePlayerSession, } from "./game";
|
|
7
|
+
export { GAME_LIFECYCLE_EVENT_NAMES } from "./game";
|
|
4
8
|
export type { Bridge, BridgeParameters, FlowAccount, FlowRemote, FlowRemoteConfig, FromWindowOptions, MessageResponse, Messenger, OneOf, Payload, QueuedRequest, ReadyOptions, RemoteFlowState, RemoteState, Schema, Storage, Topic, WithReady, } from "./messenger";
|
|
5
9
|
export type { Call, ConnectCapabilities, ConnectRequest, ConnectResponse, GuestRequest, GuestResponse, MethodName, MethodParams, MethodResult, RestoreResponse, RpcRequest, SendCallsParams, SendCallsRequest, SendCallsResponse, SendTransactionParams, SendTransactionRequest, SendTransactionResponse, SignMessageRequest, SignMessageResponse, SignOutRequest, SignOutResponse, SignTypedDataRequest, SignTypedDataResponse, TransactionArgs, TypedData, TypedDataDomain, TypedDataField, } from "./protocol";
|
|
6
10
|
export { getConnectCapabilities, isPersonalSignParams, isSendCallsParams, isSendTransactionParams, } from "./protocol";
|
|
7
|
-
export type { CreateRoomEventInput, RoomEventAttendance, RoomEventFrequency, RoomEventInstance, RoomEventInterest, RoomEventInterestState, RoomEventList, RoomEventOccurrence, RoomEventRecurrence, RoomEventRoomRef, RoomEventSeries, RoomEventStatus, SetInterestInput, UpdateRoomEventInput, } from "./room-events";
|
|
8
|
-
export type { CreateRoomInput, GlobalRole, ModerationSubject, MyRooms, RoleChangeVerdict, RoomChannel, RoomDetail, RoomList, RoomMemberEntry, RoomMembers, RoomOccupancy, RoomOwner, RoomPresenceEntry, RoomPresenceSnapshot, RoomRestrictionKind, RoomRole, RoomSummary, RoomSurfaceSettings, RoomsApi, RoomVisibility, StaffEntry, UpdateRoomInput, VerifyRoomContext, Viewer, } from "./rooms";
|
|
9
|
-
export type { AccessKeyOptions, AccessKeyPreparation, Address, CreateDialogHostOptions, CreateFlowOptions, DialogHost, DialogOpenOptions, FinalizeAccessKeyParams, Flow, FlowConnectorParameters, FlowCookieNames, FlowIdProviderProps, FlowSessionState, FlowState, FlowWidgetHandle, FlowWidgetName, FlowWidgetProps, Listener, LoginOptions, MountFlowWidgetOptions, MountProfileOptions, PrepareArgsWithAuth, PrepareTransactionRequestPhase, ProfileButtonHandle, ProfileButtonProps, ProfilePosition, ResolveAccountParams, ResolvedAccessKeyOptions, RootCredential, RunLoginParams, RunLoginResult, SendCallsArgs, SendTransactionArgs, Session, SigningContext, SignMessageArgs, SignTypedDataArgs, Store, StoredAccessKey, WagmiConnectCapabilities, WagmiConnectParams, } from "./sdk";
|
|
10
|
-
export type { ResolvedFlowSession, ResolveSessionOptions, SessionRouteOptions, SessionRouteResponse, SessionRouteSession, } from "./server";
|
|
11
|
+
export type { CreateRoomEventInput, RoomEventArchive, RoomEventAttendance, RoomEventFrequency, RoomEventInstance, RoomEventInterest, RoomEventInterestState, RoomEventList, RoomEventOccurrence, RoomEventRecurrence, RoomEventRoomRef, RoomEventRun, RoomEventSeries, RoomEventStatus, SetInterestInput, UpdateRoomEventInput, } from "./room-events";
|
|
12
|
+
export type { CreateRoomInput, GlobalRole, ModerationSubject, MyRooms, PlayerPositionReport, RoleChangeVerdict, RoomCapabilities, RoomCapability, RoomChannel, RoomDetail, RoomList, RoomMemberEntry, RoomMembers, RoomOccupancy, RoomOwner, RoomPlayerPosition, RoomPresenceEntry, RoomPresenceSnapshot, RoomRestrictionKind, RoomRole, RoomSummary, RoomSurfaceSettings, RoomsApi, RoomVisibility, StaffEntry, UpdateRoomInput, VerifyRoomContext, Viewer, } from "./rooms";
|
|
13
|
+
export type { AccessKeyOptions, AccessKeyPreparation, Address, ColorScheme, CreateDialogHostOptions, CreateFlowOptions, DialogHost, DialogOpenOptions, FinalizeAccessKeyParams, Flow, FlowConnectorParameters, FlowCookieNames, FlowIdProviderProps, FlowSessionState, FlowState, FlowWidgetHandle, FlowWidgetName, FlowWidgetProps, Listener, LoginOptions, MountFlowWidgetOptions, MountProfileOptions, PrepareArgsWithAuth, PrepareTransactionRequestPhase, ProfileButtonHandle, ProfileButtonProps, ProfilePosition, ResolveAccountParams, ResolvedAccessKeyOptions, RootCredential, RunLoginParams, RunLoginResult, SendCallsArgs, SendTransactionArgs, Session, SigningContext, SignMessageArgs, SignTypedDataArgs, Store, StoredAccessKey, WagmiConnectCapabilities, WagmiConnectParams, } from "./sdk";
|
|
14
|
+
export type { GracefulShutdownCleanup, GracefulShutdownOptions, GracefulShutdownServer, GracefulShutdownSignal, ResolvedFlowSession, ResolveSessionOptions, SessionRouteOptions, SessionRouteResponse, SessionRouteSession, } from "./server";
|
|
11
15
|
export type { CoinAsset, IdentifiedTx, TxApprove, TxConvert, TxSend, TxSwap, } from "./tx";
|
|
12
16
|
export type { ActionDayContext, ActionEventKind, ActionSessionState, ActiveActionResponse, LevelProgress, PublicProfile, XpGrantResult, XpRecentGrant, XpSummary, } from "./xp";
|
package/dist/sdk/types/index.js
CHANGED
|
@@ -166,7 +166,7 @@ export type RemoteState = {
|
|
|
166
166
|
export type Storage = {
|
|
167
167
|
getItem: <value>(name: string) => Promise<value | null> | value | null;
|
|
168
168
|
removeItem: (name: string) => Promise<void> | void;
|
|
169
|
-
setItem: (name: string, value:
|
|
169
|
+
setItem: <value>(name: string, value: value) => Promise<void> | void;
|
|
170
170
|
sizeLimit: number;
|
|
171
171
|
storages?: readonly Storage[];
|
|
172
172
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RawRecord, RawValue } from "../json";
|
|
1
2
|
import type { AdditionalSession, FlowCredential, FlowUser, WebAuthnSignature } from "./auth";
|
|
2
3
|
import type { Address } from "./sdk";
|
|
3
4
|
export type MethodName = "wallet_connect" | "personal_sign" | "eth_signTypedData" | "eth_sendTransaction" | "wallet_sendCalls" | "wallet_guest" | "wallet_signout";
|
|
@@ -51,7 +52,7 @@ export type TypedData = {
|
|
|
51
52
|
domain: TypedDataDomain;
|
|
52
53
|
types: Record<string, readonly TypedDataField[]>;
|
|
53
54
|
primaryType: string;
|
|
54
|
-
message:
|
|
55
|
+
message: RawRecord;
|
|
55
56
|
};
|
|
56
57
|
export type SignTypedDataRequest = [address: string, typedData: TypedData];
|
|
57
58
|
export type SignTypedDataResponse = `0x${string}`;
|
|
@@ -69,7 +70,7 @@ export type SendCallsRequest = [
|
|
|
69
70
|
{
|
|
70
71
|
calls: readonly Call[];
|
|
71
72
|
chainId?: string;
|
|
72
|
-
capabilities?:
|
|
73
|
+
capabilities?: RawRecord;
|
|
73
74
|
}
|
|
74
75
|
];
|
|
75
76
|
export type SendCallsResponse = {
|
|
@@ -123,9 +124,9 @@ export type MethodResult = {
|
|
|
123
124
|
export type RpcRequest = {
|
|
124
125
|
id: string | number;
|
|
125
126
|
method: string;
|
|
126
|
-
params?: readonly
|
|
127
|
+
params?: readonly RawValue[];
|
|
127
128
|
jsonrpc?: string;
|
|
128
|
-
[key: string]:
|
|
129
|
+
[key: string]: RawValue | readonly RawValue[];
|
|
129
130
|
};
|
|
130
131
|
export type SendTransactionParams = {
|
|
131
132
|
to?: string;
|
|
@@ -140,7 +141,7 @@ export type SendCallsParams = {
|
|
|
140
141
|
data?: string;
|
|
141
142
|
}[];
|
|
142
143
|
chainId?: string;
|
|
143
|
-
capabilities?:
|
|
144
|
+
capabilities?: RawRecord;
|
|
144
145
|
};
|
|
145
146
|
/**
|
|
146
147
|
* Unwraps the connect capabilities from a wallet_connect RPC request (they ride
|
|
@@ -4,22 +4,28 @@
|
|
|
4
4
|
* accessKeyHash, guestToken), so the cast lives here rather than being repeated.
|
|
5
5
|
*/
|
|
6
6
|
export function getConnectCapabilities(request) {
|
|
7
|
-
|
|
7
|
+
const first = request?.params?.[0];
|
|
8
|
+
const found = Object.entries(Object(first)).find(([k]) => k === "capabilities")?.[1];
|
|
9
|
+
/* SAFETY: the field is read off this app's own wallet_connect params, whose capabilities
|
|
10
|
+
block is defined by ConnectCapabilities; a missing one answers undefined. */
|
|
11
|
+
return found;
|
|
8
12
|
}
|
|
9
13
|
export function isPersonalSignParams(params) {
|
|
10
14
|
if (!params || params.length < 2)
|
|
11
15
|
return false;
|
|
12
|
-
return
|
|
16
|
+
return String(params[0]) === params[0] && String(params[1]) === params[1];
|
|
13
17
|
}
|
|
14
18
|
export function isSendTransactionParams(params) {
|
|
15
19
|
if (!params || params.length < 1)
|
|
16
20
|
return false;
|
|
17
21
|
const p = params[0];
|
|
18
|
-
return p != null &&
|
|
22
|
+
return p != null && Object(p) === p;
|
|
19
23
|
}
|
|
20
24
|
export function isSendCallsParams(params) {
|
|
21
25
|
if (!params || params.length < 1)
|
|
22
26
|
return false;
|
|
23
27
|
const p = params[0];
|
|
24
|
-
|
|
28
|
+
if (p == null || Object(p) !== p)
|
|
29
|
+
return false;
|
|
30
|
+
return Array.isArray(Object.entries(p).find(([k]) => k === "calls")?.[1]);
|
|
25
31
|
}
|
|
@@ -66,12 +66,50 @@ export interface RoomEventInstance extends RoomEventInterest {
|
|
|
66
66
|
/** Written once the instance completes — the history view reads these rather
|
|
67
67
|
* than scanning presence. Null while it has not finished. */
|
|
68
68
|
attendance: RoomEventAttendance | null;
|
|
69
|
+
/** When the instance actually ran. Null until something has happened to it.
|
|
70
|
+
* This — not `attendance` — is the authoritative answer to "did this run, and
|
|
71
|
+
* when". */
|
|
72
|
+
run: RoomEventRun | null;
|
|
69
73
|
}
|
|
70
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* The authoritative run window, stamped by the scheduler at the instant of each
|
|
76
|
+
* transition.
|
|
77
|
+
*
|
|
78
|
+
* `activatedAt` is null on an occurrence the worker slept through and caught up
|
|
79
|
+
* in one step (scheduled straight to completed): nothing observed it start, so
|
|
80
|
+
* nothing claims to know. Rows that completed before this was recorded are null
|
|
81
|
+
* on all three and cannot be backfilled — the instants were never captured.
|
|
82
|
+
*/
|
|
83
|
+
export interface RoomEventRun {
|
|
84
|
+
activatedAt: string | null;
|
|
85
|
+
completedAt: string | null;
|
|
86
|
+
cancelledAt: string | null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* When players were in the room, derived from game lifecycle edges and merged
|
|
90
|
+
* with presence samples where reporting gaps remain.
|
|
91
|
+
*
|
|
92
|
+
* **This is not the run window.** These instants are the first and last player
|
|
93
|
+
* *seen*; an occurrence that ran for two hours with one player who left after
|
|
94
|
+
* fifteen minutes reports fifteen minutes here, and one nobody attended reports
|
|
95
|
+
* null for both while still having genuinely run. Read {@link RoomEventRun} for
|
|
96
|
+
* whether and when it ran.
|
|
97
|
+
*/
|
|
71
98
|
export interface RoomEventAttendance {
|
|
72
99
|
attendeeCount: number;
|
|
73
100
|
peakConcurrent: number;
|
|
101
|
+
/** First player seen in the room. */
|
|
102
|
+
firstPlayerSeenAt: string | null;
|
|
103
|
+
/** Last player seen in the room. */
|
|
104
|
+
lastPlayerSeenAt: string | null;
|
|
105
|
+
/**
|
|
106
|
+
* @deprecated Misread as the run window once already, which is why the pair
|
|
107
|
+
* above exists. Same values, kept so existing consumers keep working; read
|
|
108
|
+
* `firstPlayerSeenAt`/`lastPlayerSeenAt`, or {@link RoomEventRun} if what you
|
|
109
|
+
* want is when the event ran.
|
|
110
|
+
*/
|
|
74
111
|
startedAt: string | null;
|
|
112
|
+
/** @deprecated See {@link RoomEventAttendance.startedAt}. */
|
|
75
113
|
endedAt: string | null;
|
|
76
114
|
}
|
|
77
115
|
/** An instance standing on its own in a listing, carrying enough of its series
|
|
@@ -85,6 +123,11 @@ export interface RoomEventOccurrence extends RoomEventInstance {
|
|
|
85
123
|
timezone: string;
|
|
86
124
|
/** True when the parent series repeats, so a card can badge it as recurring. */
|
|
87
125
|
recurring: boolean;
|
|
126
|
+
/** The series' rule, denormalized so a card can say *how* it repeats without
|
|
127
|
+
* a second read. Null for a one-off, and then `recurring` is false too — the
|
|
128
|
+
* flag stays because it is what a caller branches on, the rule is what it
|
|
129
|
+
* renders. */
|
|
130
|
+
recurrence: RoomEventRecurrence | null;
|
|
88
131
|
createdBy: string | null;
|
|
89
132
|
}
|
|
90
133
|
/** The authored series, with the instances it has materialized so far. */
|
|
@@ -110,6 +153,11 @@ export interface RoomEventSeries {
|
|
|
110
153
|
export interface RoomEventList {
|
|
111
154
|
events: RoomEventOccurrence[];
|
|
112
155
|
}
|
|
156
|
+
/** The archive listing: finished and cancelled occurrences, newest first, with
|
|
157
|
+
* the keyset cursor for the next page (`null` once the history is exhausted). */
|
|
158
|
+
export interface RoomEventArchive extends RoomEventList {
|
|
159
|
+
nextCursor: string | null;
|
|
160
|
+
}
|
|
113
161
|
export interface CreateRoomEventInput {
|
|
114
162
|
title: string;
|
|
115
163
|
description?: string | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RawRecord } from "../json";
|
|
1
2
|
/** Room registry types shared by the server routes and the client SDK. */
|
|
2
3
|
export type RoomVisibility = "public" | "unlisted" | "private";
|
|
3
4
|
/**
|
|
@@ -109,11 +110,52 @@ export interface RoomPresenceSnapshot {
|
|
|
109
110
|
slug: string;
|
|
110
111
|
channels: Record<RoomChannel, RoomPresenceEntry[]>;
|
|
111
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Where a player was last standing in one room, as handed back on the verify
|
|
115
|
+
* room context. A hint for the game server's placement pass, not a command:
|
|
116
|
+
* the world is hand-authored and changes between deploys, so the coordinate is
|
|
117
|
+
* re-validated against the live physics space before anyone is put on it.
|
|
118
|
+
* `worldId` records which build of the world produced it; `at` is how fresh it
|
|
119
|
+
* is. Feet position in metres, `yaw` in radians.
|
|
120
|
+
*/
|
|
121
|
+
export interface RoomPlayerPosition {
|
|
122
|
+
x: number;
|
|
123
|
+
y: number;
|
|
124
|
+
z: number;
|
|
125
|
+
yaw: number;
|
|
126
|
+
worldId: string;
|
|
127
|
+
at: string;
|
|
128
|
+
}
|
|
129
|
+
/** One entry of `POST /api/rooms/position/report`, keyed by the Flow `sub`. */
|
|
130
|
+
export interface PlayerPositionReport {
|
|
131
|
+
roomId: string;
|
|
132
|
+
sub: string;
|
|
133
|
+
x: number;
|
|
134
|
+
y: number;
|
|
135
|
+
z: number;
|
|
136
|
+
yaw: number;
|
|
137
|
+
worldId: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* One server-resolved permission over an admitted room session, keyed by the
|
|
141
|
+
* Talk operation it authorizes (see `src/rooms/capabilities.ts` for the
|
|
142
|
+
* derivation). Resolved in admission and re-resolved on every restriction
|
|
143
|
+
* recheck — the client's copy is explanatory only and never the enforcement
|
|
144
|
+
* boundary.
|
|
145
|
+
*/
|
|
146
|
+
export type RoomCapability = "history" | "reply" | "react" | "mention" | "attach" | "pin" | "moderate" | "search";
|
|
147
|
+
export type RoomCapabilities = Record<RoomCapability, boolean>;
|
|
112
148
|
/**
|
|
113
149
|
* Room admission context returned by POST /api/session/verify when the caller
|
|
114
150
|
* passes a `room` slug: whether the verified subject may enter, and with which
|
|
115
|
-
* role and per-room
|
|
116
|
-
*
|
|
151
|
+
* role and per-room settings (the admission-facing surfaces merged flat —
|
|
152
|
+
* see `src/rooms/admission.ts`). `settings` is delivered only through this
|
|
153
|
+
* server-to-server path — surface blobs may hold secret-ish values, and
|
|
154
|
+
* `lastPosition` is scoped to the room being joined so it never answers "where
|
|
155
|
+
* is this user" across the fleet. `capabilities` is the resolved permission
|
|
156
|
+
* set for the session (all-false when `ok` is false — no session exists);
|
|
157
|
+
* `mutedUntil` carries a live mute's expiry (null for permanent or none) so
|
|
158
|
+
* consumers can show feedback until it lapses.
|
|
117
159
|
*/
|
|
118
160
|
export interface VerifyRoomContext {
|
|
119
161
|
ok: boolean;
|
|
@@ -123,7 +165,10 @@ export interface VerifyRoomContext {
|
|
|
123
165
|
visibility: RoomVisibility | null;
|
|
124
166
|
role: RoomRole | null;
|
|
125
167
|
muted: boolean;
|
|
168
|
+
mutedUntil: string | null;
|
|
169
|
+
capabilities: RoomCapabilities;
|
|
126
170
|
settings: Record<string, number | boolean | string>;
|
|
171
|
+
lastPosition: RoomPlayerPosition | null;
|
|
127
172
|
}
|
|
128
173
|
export interface RoomSurfaceSettings {
|
|
129
174
|
surface: string;
|
|
@@ -147,7 +192,7 @@ export interface RoomsApi {
|
|
|
147
192
|
members(slug: string): Promise<RoomMembers>;
|
|
148
193
|
presence(slug: string): Promise<RoomPresenceSnapshot>;
|
|
149
194
|
settings(slug: string, surface: string): Promise<RoomSurfaceSettings>;
|
|
150
|
-
saveSettings(slug: string, surface: string, settings:
|
|
195
|
+
saveSettings(slug: string, surface: string, settings: RawRecord): Promise<RoomSurfaceSettings>;
|
|
151
196
|
transfer(slug: string, userId: string): Promise<void>;
|
|
152
197
|
claim(slug: string): Promise<void>;
|
|
153
198
|
setRole(slug: string, userId: string, role: "moderator" | "member"): Promise<void>;
|
|
@@ -162,4 +207,9 @@ export interface RoomsApi {
|
|
|
162
207
|
expiresInSeconds?: number;
|
|
163
208
|
}): Promise<void>;
|
|
164
209
|
unmute(slug: string, userId: string): Promise<void>;
|
|
210
|
+
timeout(slug: string, userId: string, options: {
|
|
211
|
+
seconds: number;
|
|
212
|
+
reason?: string;
|
|
213
|
+
}): Promise<void>;
|
|
214
|
+
removeTimeout(slug: string, userId: string): Promise<void>;
|
|
165
215
|
}
|
package/dist/sdk/types/rooms.js
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/** Room registry types shared by the server routes and the client SDK. */
|