@flow-industries/id 0.18.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 (59) 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 +2 -1
  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 +1 -1
  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.js +2 -2
  27. package/dist/sdk/react/hooks.d.ts +8 -4
  28. package/dist/sdk/react/hooks.js +1 -10
  29. package/dist/sdk/react/profile-button.js +2 -2
  30. package/dist/sdk/server.js +2 -1
  31. package/dist/sdk/session-core.d.ts +1 -6
  32. package/dist/sdk/session-core.js +7 -4
  33. package/dist/sdk/session-route.d.ts +0 -20
  34. package/dist/sdk/session-route.js +25 -9
  35. package/dist/sdk/start/graceful-shutdown.d.ts +8 -0
  36. package/dist/sdk/start/graceful-shutdown.js +81 -0
  37. package/dist/sdk/start/index.d.ts +2 -0
  38. package/dist/sdk/start/index.js +3 -2
  39. package/dist/sdk/token-expiry.js +3 -1
  40. package/dist/sdk/types/cosmetics.d.ts +162 -0
  41. package/dist/sdk/types/cosmetics.js +78 -0
  42. package/dist/sdk/types/game.d.ts +58 -0
  43. package/dist/sdk/types/game.js +8 -0
  44. package/dist/sdk/types/index.d.ts +7 -3
  45. package/dist/sdk/types/index.js +2 -0
  46. package/dist/sdk/types/messenger.d.ts +1 -1
  47. package/dist/sdk/types/protocol.d.ts +6 -5
  48. package/dist/sdk/types/protocol.js +10 -4
  49. package/dist/sdk/types/room-events.d.ts +49 -1
  50. package/dist/sdk/types/rooms.d.ts +53 -3
  51. package/dist/sdk/types/rooms.js +0 -1
  52. package/dist/sdk/types/sdk.d.ts +2 -1
  53. package/dist/sdk/types/server.d.ts +18 -0
  54. package/dist/sdk/verify.js +1 -0
  55. package/dist/sdk/wagmi/index.d.ts +7 -3
  56. package/dist/sdk/wagmi/index.js +19 -32
  57. package/package.json +11 -8
  58. package/dist/sdk/client/refresh-store.d.ts +0 -22
  59. package/dist/sdk/client/refresh-store.js +0 -66
@@ -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";
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";
9
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";
10
- export type { ResolvedFlowSession, ResolveSessionOptions, SessionRouteOptions, SessionRouteResponse, SessionRouteSession, } from "./server";
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. */
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { Chain, Hex, PrepareTransactionRequestParameters, SendTransactionParameters, SignTypedDataParameters, Transport, WalletClient } from "viem";
3
3
  import type { SendCallsParameters } from "viem/actions";
4
+ import type { RawValue } from "../json";
4
5
  import type { AdditionalSession, FlowCredential, FlowUser, WebAuthnSignature } from "./auth";
5
6
  import type { ConnectCapabilities, ConnectResponse } from "./protocol";
6
7
  import type { RoomsApi } from "./rooms";
@@ -262,7 +263,7 @@ export type FlowWidgetHandle = {
262
263
  * Sends a widget-specific message (e.g. `{ type: "xp-refresh" }`), pinned to
263
264
  * the Flow ID origin.
264
265
  */
265
- post: (message: unknown) => void;
266
+ post: (message: RawValue) => void;
266
267
  /**
267
268
  * Recolors the mounted widget in place, for the same reason the pill exposes
268
269
  * it: a remount reloads the frame and flashes it out of the host's layout.
@@ -60,3 +60,21 @@ export type SessionRouteResponse = {
60
60
  */
61
61
  additionalSessions?: AdditionalSession[];
62
62
  };
63
+ export type GracefulShutdownSignal = "SIGTERM" | "SIGINT";
64
+ export type GracefulShutdownServer = {
65
+ /** Stop accepting connections; `true` also closes active connections. */
66
+ stop(closeActiveConnections?: boolean): Promise<void> | void;
67
+ };
68
+ export type GracefulShutdownOptions = {
69
+ /**
70
+ * Total time allowed for active HTTP requests and `onShutdown` to finish.
71
+ * Defaults to 20 seconds, leaving Kubernetes headroom around the app drain.
72
+ */
73
+ timeoutMs?: number;
74
+ /**
75
+ * Consumer cleanup started after the server stops accepting connections.
76
+ * Close WebSockets, flush presence, and finish other process-owned state here.
77
+ */
78
+ onShutdown?: (signal: GracefulShutdownSignal) => Promise<void> | void;
79
+ };
80
+ export type GracefulShutdownCleanup = () => void;
@@ -33,5 +33,6 @@ export async function verifyFlowJWT(token, opts) {
33
33
  });
34
34
  if (!payload.sub)
35
35
  throw new Error("JWT missing sub");
36
+ /* SAFETY: jwtVerify already checked the signature, issuer and audience against this issuer's own keys, and `sub` is checked above. */
36
37
  return payload;
37
38
  }
@@ -1,3 +1,4 @@
1
+ import type { RawValue } from "../json";
1
2
  import type { FlowConnectorParameters } from "../types";
2
3
  /**
3
4
  * Wagmi connector adapter for Flow. Drop into your wagmi config with
@@ -17,9 +18,12 @@ import type { FlowConnectorParameters } from "../types";
17
18
  * wagmi state stays in sync when login/logout happens via the SDK rather
18
19
  * than through wagmi's own `connect()`.
19
20
  */
20
- export declare function flowConnector(parameters?: FlowConnectorParameters): import("@wagmi/core").CreateConnectorFn<{
21
- request: (...args: unknown[]) => Promise<unknown>;
22
- }, Record<string, unknown>, Record<string, unknown>>;
21
+ /** The minimal EIP-1193 surface wagmi needs from this connector. */
22
+ type FlowProvider = {
23
+ request: (...args: RawValue[]) => Promise<RawValue>;
24
+ };
25
+ export declare function flowConnector(parameters?: FlowConnectorParameters): import("@wagmi/core").CreateConnectorFn<FlowProvider, Record<string, unknown>, Record<string, unknown>>;
23
26
  export declare namespace flowConnector {
24
27
  var type: "flow";
25
28
  }
29
+ export {};
@@ -5,33 +5,14 @@ flowConnector.type = "flow";
5
5
  function loginOptionsFromCapabilities(capabilities) {
6
6
  const signUp = capabilities?.type === "sign-up" || capabilities?.createAccount;
7
7
  const signIn = capabilities?.type === "sign-in" || capabilities?.signIn;
8
+ const headless = !signUp &&
9
+ (capabilities?.signInHeadless || capabilities?.type === "sign-in");
8
10
  return {
9
- ...(signUp ? { signUp: true } : {}),
10
- ...(!signUp && signIn ? { signIn: true } : {}),
11
- ...(!signUp &&
12
- (capabilities?.signInHeadless || capabilities?.type === "sign-in")
13
- ? { signInHeadless: true }
14
- : {}),
11
+ signUp: signUp ? true : undefined,
12
+ signIn: !signUp && signIn ? true : undefined,
13
+ signInHeadless: headless ? true : undefined,
15
14
  };
16
15
  }
17
- /**
18
- * Wagmi connector adapter for Flow. Drop into your wagmi config with
19
- * `connectors: [flowConnector()]` — no arguments needed in the common case.
20
- *
21
- * Resolution order for the underlying Flow instance:
22
- * 1. `parameters.flow` (explicit override — useful for tests)
23
- * 2. The Flow singleton set by a prior `createFlow()` call
24
- * 3. Lazily created on first connector use (also sets the singleton)
25
- *
26
- * This means wagmi-only apps get a working Flow without ever calling
27
- * `createFlow`, and apps mixing wagmi + direct SDK calls automatically
28
- * share state because they all resolve to the same singleton.
29
- *
30
- * The `setup()` method subscribes to the Flow store and emits wagmi
31
- * `connect`/`disconnect` events whenever the address changes — that's how
32
- * wagmi state stays in sync when login/logout happens via the SDK rather
33
- * than through wagmi's own `connect()`.
34
- */
35
16
  export function flowConnector(parameters = {}) {
36
17
  let flow = parameters.flow ?? null;
37
18
  return createConnector((config) => {
@@ -64,6 +45,8 @@ export function flowConnector(parameters = {}) {
64
45
  return;
65
46
  lastAddress = next;
66
47
  if (next) {
48
+ /* SAFETY: wagmi types `accounts` as a readonly tuple of its own `Address` brand;
49
+ `next` is the checksummed address this connector just resolved. */
67
50
  config.emitter.emit("connect", {
68
51
  accounts: [next],
69
52
  chainId: config.chains[0]?.id ?? 1,
@@ -76,6 +59,7 @@ export function flowConnector(parameters = {}) {
76
59
  },
77
60
  async connect(params = {}) {
78
61
  const f = getFlow();
62
+ // SAFETY: wagmi passes its own connect options; WagmiConnectParams names the subset read.
79
63
  const connectParams = params;
80
64
  const isReconnecting = connectParams.isReconnecting;
81
65
  // Wagmi calls connect({ isReconnecting: true }) on mount when it
@@ -135,15 +119,18 @@ export function flowConnector(parameters = {}) {
135
119
  },
136
120
  async getClient({ chainId } = {}) {
137
121
  const f = getFlow();
138
- return f.walletClient({ ...(chainId ? { chainId } : {}) });
122
+ /* SAFETY: wagmi types getClient against viem's Client generic over its own chain
123
+ config; the Flow wallet client implements that surface. */
124
+ return f.walletClient(chainId ? { chainId } : {});
139
125
  },
140
- async getProvider({ chainId } = {}) {
141
- const client = await this.getClient({
142
- ...(chainId ? { chainId } : {}),
143
- });
144
- return {
145
- request: client.request,
146
- };
126
+ async getProvider(options = {}) {
127
+ const client = await this.getClient?.({ chainId: options.chainId });
128
+ /* SAFETY: the wallet client viem builds always carries `request`; wagmi's Client type
129
+ does not name it, which is the only reason this is stated rather than inferred. */
130
+ const request = Object.entries(Object(client)).find(([k]) => k === "request")?.[1];
131
+ /* SAFETY: the wallet client viem builds always carries `request`; wagmi's Client type
132
+ does not name it, which is the only reason this is read by name. */
133
+ return { request: request };
147
134
  },
148
135
  onAccountsChanged() { },
149
136
  onChainChanged(chain) {