@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.
Files changed (61) hide show
  1. package/README.md +19 -0
  2. package/dist/sdk/client/access-key.d.ts +1 -1
  3. package/dist/sdk/client/access-key.js +20 -6
  4. package/dist/sdk/client/create-flow.js +19 -11
  5. package/dist/sdk/client/dialog-host.js +8 -2
  6. package/dist/sdk/client/flow-widget.js +13 -2
  7. package/dist/sdk/client/idb.d.ts +2 -2
  8. package/dist/sdk/client/idb.js +17 -2
  9. package/dist/sdk/client/profile-button.js +18 -3
  10. package/dist/sdk/client/rooms.js +19 -4
  11. package/dist/sdk/client/session.js +8 -5
  12. package/dist/sdk/client/signing.js +22 -7
  13. package/dist/sdk/client/store.js +4 -5
  14. package/dist/sdk/cookies.d.ts +3 -1
  15. package/dist/sdk/cookies.js +11 -9
  16. package/dist/sdk/dialog/remote/Messenger.d.ts +0 -5
  17. package/dist/sdk/dialog/remote/Messenger.js +26 -14
  18. package/dist/sdk/driver-error.d.ts +8 -0
  19. package/dist/sdk/driver-error.js +17 -0
  20. package/dist/sdk/hex.d.ts +6 -0
  21. package/dist/sdk/hex.js +6 -0
  22. package/dist/sdk/id-host.d.ts +1 -1
  23. package/dist/sdk/id-host.js +4 -5
  24. package/dist/sdk/json.d.ts +17 -0
  25. package/dist/sdk/json.js +5 -0
  26. package/dist/sdk/react/flow-widget.d.ts +4 -0
  27. package/dist/sdk/react/flow-widget.js +19 -4
  28. package/dist/sdk/react/hooks.d.ts +8 -4
  29. package/dist/sdk/react/hooks.js +1 -10
  30. package/dist/sdk/react/profile-button.d.ts +5 -0
  31. package/dist/sdk/react/profile-button.js +20 -4
  32. package/dist/sdk/server.js +2 -1
  33. package/dist/sdk/session-core.d.ts +1 -6
  34. package/dist/sdk/session-core.js +7 -4
  35. package/dist/sdk/session-route.d.ts +0 -20
  36. package/dist/sdk/session-route.js +25 -9
  37. package/dist/sdk/start/graceful-shutdown.d.ts +8 -0
  38. package/dist/sdk/start/graceful-shutdown.js +81 -0
  39. package/dist/sdk/start/index.d.ts +2 -0
  40. package/dist/sdk/start/index.js +3 -2
  41. package/dist/sdk/token-expiry.js +3 -1
  42. package/dist/sdk/types/cosmetics.d.ts +162 -0
  43. package/dist/sdk/types/cosmetics.js +78 -0
  44. package/dist/sdk/types/game.d.ts +58 -0
  45. package/dist/sdk/types/game.js +8 -0
  46. package/dist/sdk/types/index.d.ts +8 -4
  47. package/dist/sdk/types/index.js +2 -0
  48. package/dist/sdk/types/messenger.d.ts +1 -1
  49. package/dist/sdk/types/protocol.d.ts +6 -5
  50. package/dist/sdk/types/protocol.js +10 -4
  51. package/dist/sdk/types/room-events.d.ts +49 -1
  52. package/dist/sdk/types/rooms.d.ts +53 -3
  53. package/dist/sdk/types/rooms.js +0 -1
  54. package/dist/sdk/types/sdk.d.ts +22 -5
  55. package/dist/sdk/types/server.d.ts +18 -0
  56. package/dist/sdk/verify.js +1 -0
  57. package/dist/sdk/wagmi/index.d.ts +7 -3
  58. package/dist/sdk/wagmi/index.js +19 -32
  59. package/package.json +11 -8
  60. package/dist/sdk/client/refresh-store.d.ts +0 -22
  61. package/dist/sdk/client/refresh-store.js +0 -66
@@ -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 (typeof window === "undefined")
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(() => typeof window === "undefined"
49
+ const flow = useMemo(() => !("window" in globalThis)
49
50
  ? createStaticFlow(session, { host })
50
51
  : createFlow({
51
52
  initialState: session,
@@ -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
- return typeof payload.exp === "number" ? payload.exp : null;
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
+ }
@@ -0,0 +1,8 @@
1
+ export const GAME_LIFECYCLE_EVENT_NAMES = [
2
+ "game.server.boot",
3
+ "game.server.shutdown",
4
+ "game.room.open",
5
+ "game.room.close",
6
+ "game.player.join",
7
+ "game.player.leave",
8
+ ];
@@ -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";
@@ -1 +1,3 @@
1
+ export { BODY_REGIONS, COSMETIC_MATERIALS } from "./cosmetics";
2
+ export { GAME_LIFECYCLE_EVENT_NAMES } from "./game";
1
3
  export { getConnectCapabilities, isPersonalSignParams, isSendCallsParams, isSendTransactionParams, } from "./protocol";
@@ -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: unknown) => Promise<void> | void;
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: Record<string, unknown>;
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?: Record<string, unknown>;
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 unknown[];
127
+ params?: readonly RawValue[];
127
128
  jsonrpc?: string;
128
- [key: string]: unknown;
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?: Record<string, unknown>;
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
- return request?.params?.[0]?.capabilities;
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 typeof params[0] === "string" && typeof params[1] === "string";
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 && typeof p === "object";
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
- return p != null && typeof p === "object" && Array.isArray(p.calls);
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
- /** What actually happened, as observed from room presence while it ran. */
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 game settings. `settings` is delivered only through this
116
- * server-to-server path — surface blobs may hold secret-ish values.
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: Record<string, unknown>): Promise<RoomSurfaceSettings>;
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
  }
@@ -1 +0,0 @@
1
- /** Room registry types shared by the server routes and the client SDK. */