@flow-industries/id 0.22.0 → 0.22.1

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.
@@ -1,4 +1,4 @@
1
- import { bridgeToWindow, makeIframe } from "./iframe-host";
1
+ import { bridgeToWindow, makeIframe, sendBridgeNotification, } from "./iframe-host";
2
2
  import { setOverlayFocus } from "./overlay-focus";
3
3
  const HIDDEN_STYLE = {
4
4
  position: "fixed",
@@ -106,7 +106,7 @@ export function createDialogHost(options) {
106
106
  return;
107
107
  initSent = true;
108
108
  // SAFETY: the payload map types this topic; the dialog reads exactly these fields.
109
- messenger?.send("__internal", {
109
+ sendBridgeNotification(messenger, "__internal", {
110
110
  type: "init",
111
111
  mode: "iframe",
112
112
  referrer: opts?.referrer ?? { title: document.title },
@@ -124,7 +124,7 @@ export function createDialogHost(options) {
124
124
  // widget, so every dialog appears and disappears identically.
125
125
  void (
126
126
  // SAFETY: the payload map types this topic; the dialog reads exactly these fields.
127
- messenger?.send("__internal", { type: "dialog-shown" }));
127
+ sendBridgeNotification(messenger, "__internal", { type: "dialog-shown" }));
128
128
  }
129
129
  function hide() {
130
130
  if (!iframe)
@@ -137,7 +137,7 @@ export function createDialogHost(options) {
137
137
  setOverlayFocus(iframe, false);
138
138
  void (
139
139
  // SAFETY: the payload map types this topic; the dialog reads exactly these fields.
140
- messenger?.send("__internal", { type: "dialog-hidden" }));
140
+ sendBridgeNotification(messenger, "__internal", { type: "dialog-hidden" }));
141
141
  }
142
142
  function open(opts) {
143
143
  if (opts?.mode === "popup") {
@@ -177,9 +177,14 @@ export function createDialogHost(options) {
177
177
  return new Promise((resolve, reject) => {
178
178
  pending.set(id, { resolve, reject });
179
179
  // SAFETY: the rpc-requests payload shape.
180
- messenger.send("rpc-requests", [
180
+ messenger
181
+ .send("rpc-requests", [
181
182
  { request: rpcRequest, status: "pending" },
182
- ]);
183
+ ])
184
+ .catch((error) => {
185
+ pending.delete(id);
186
+ reject(error);
187
+ });
183
188
  });
184
189
  }
185
190
  /** Opens the dialog UI and dispatches an interactive RPC (login, sign, etc.). */
@@ -1,6 +1,6 @@
1
1
  import { resolveIdHost } from "../id-host";
2
2
  import { getFlow, requireFlow } from "./create-flow";
3
- import { answerTokenRequest, bridgeToWindow, makeIframe } from "./iframe-host";
3
+ import { answerTokenRequest, bridgeToWindow, makeIframe, sendBridgeNotification, } from "./iframe-host";
4
4
  /**
5
5
  * The inline widgets an embedder may mount, mapped to the dialog routes that
6
6
  * serve them. Callers name a widget rather than passing a route so the dialog's
@@ -65,7 +65,7 @@ export function createFlowWidget(options) {
65
65
  // confirms where the frame is; what the handshake is really for is telling
66
66
  // the dialog which origin it is embedded by, which is what it later asks for
67
67
  // a token.
68
- void bridge.send("__internal", {
68
+ sendBridgeNotification(bridge, "__internal", {
69
69
  type: "init",
70
70
  mode: "iframe",
71
71
  referrer: { title: document.title },
@@ -81,7 +81,7 @@ export function createFlowWidget(options) {
81
81
  if (next === theme)
82
82
  return;
83
83
  theme = next;
84
- void bridge.send("__internal", {
84
+ sendBridgeNotification(bridge, "__internal", {
85
85
  type: "set-theme",
86
86
  theme: { colorScheme: next },
87
87
  });
@@ -1,5 +1,5 @@
1
1
  import * as Messenger from "../dialog/remote/Messenger";
2
- import type { Flow, ProfileIdentity } from "../types";
2
+ import type { Flow, Payload, ProfileIdentity, Topic } from "../types";
3
3
  /**
4
4
  * Permissions the dialog iframe needs to run WebAuthn passkey ceremonies and
5
5
  * copy recovery values. Shared by every Flow iframe host (the dialog host and
@@ -48,3 +48,4 @@ export declare function hostIdentity(flow: Flow): ProfileIdentity | null;
48
48
  * out — instead of leaving the request to time out.
49
49
  */
50
50
  export declare function answerTokenRequest(bridge: Messenger.Bridge, flow: Flow, id: string): Promise<void>;
51
+ export declare function sendBridgeNotification<T extends Topic>(bridge: Messenger.Bridge | null | undefined, topic: T, payload: Payload<T>): void;
@@ -71,5 +71,15 @@ export function hostIdentity(flow) {
71
71
  */
72
72
  export async function answerTokenRequest(bridge, flow, id) {
73
73
  const token = await flow.getToken().catch(() => null);
74
- void bridge.send("__internal", { type: "profile-token", id, token });
74
+ sendBridgeNotification(bridge, "__internal", {
75
+ type: "profile-token",
76
+ id,
77
+ token,
78
+ });
79
+ }
80
+ export function sendBridgeNotification(bridge, topic, payload) {
81
+ void bridge?.send(topic, payload).catch((error) => {
82
+ if (!(error instanceof Messenger.BridgeDestroyedError))
83
+ throw error;
84
+ });
75
85
  }
@@ -9,6 +9,8 @@ export { actionHeadline, anchorFor, catalogFieldIndex, createFocusApi, elapsedSe
9
9
  export { METHODS } from "./methods";
10
10
  export { closeProfile, openProfile } from "./open-profile";
11
11
  export { createProfileButton } from "./profile-button";
12
+ export type { ReportOperation, ReportRequest, ReportResponse } from "./reports";
13
+ export { createReportsApi, ReportRequestError } from "./reports";
12
14
  export { createRoomsApi, RoomsRequestError } from "./rooms";
13
15
  export { createStaticFlow } from "./static-flow";
14
16
  export { createStudyApi, StudyRequestError } from "./study";
@@ -8,6 +8,7 @@ export { actionHeadline, anchorFor, catalogFieldIndex, createFocusApi, elapsedSe
8
8
  export { METHODS } from "./methods";
9
9
  export { closeProfile, openProfile } from "./open-profile";
10
10
  export { createProfileButton } from "./profile-button";
11
+ export { createReportsApi, ReportRequestError } from "./reports";
11
12
  export { createRoomsApi, RoomsRequestError } from "./rooms";
12
13
  export { createStaticFlow } from "./static-flow";
13
14
  export { createStudyApi, StudyRequestError } from "./study";
@@ -1,7 +1,7 @@
1
1
  import { resolveIdHost } from "../id-host";
2
2
  import { isValidUsername } from "../usernames";
3
3
  import { getFlow, requireFlow } from "./create-flow";
4
- import { answerTokenRequest, bridgeToWindow, hostIdentity, makeIframe, OVERLAY_STYLE, } from "./iframe-host";
4
+ import { answerTokenRequest, bridgeToWindow, hostIdentity, makeIframe, OVERLAY_STYLE, sendBridgeNotification, } from "./iframe-host";
5
5
  import { setOverlayFocus } from "./overlay-focus";
6
6
  // One overlay per page, kept for the page's lifetime. The alternative — a
7
7
  // handle per call site — would put a second fullscreen iframe over the host the
@@ -47,7 +47,7 @@ function createViewer(options) {
47
47
  // `/dialog/` and `init` asks it to land on the profile. Sending the route
48
48
  // here rather than as a follow-up message keeps the sign-in root from ever
49
49
  // mounting, even for the frame it takes to navigate away from it.
50
- void opened.send("__internal", {
50
+ sendBridgeNotification(opened, "__internal", {
51
51
  type: "init",
52
52
  mode: "iframe",
53
53
  referrer: { title: document.title },
@@ -90,11 +90,14 @@ function createViewer(options) {
90
90
  // re-pointed in place. Tearing it down and rebuilding would reload the
91
91
  // dialog and lose the open animation on every profile after the first.
92
92
  if (mounted)
93
- void live.send("__internal", { type: "profile-view", username });
93
+ sendBridgeNotification(live, "__internal", {
94
+ type: "profile-view",
95
+ username,
96
+ });
94
97
  // Pushed on every open rather than tracked with a subscription: it is only
95
98
  // read while a profile is on screen, and sending it here means the "this is
96
99
  // you" link is decided from the session as it stands at that moment.
97
- void live.send("__internal", {
100
+ sendBridgeNotification(live, "__internal", {
98
101
  type: "profile-identity",
99
102
  identity: hostIdentity(flow),
100
103
  });
@@ -102,7 +105,7 @@ function createViewer(options) {
102
105
  onClose = nextOnClose;
103
106
  visible = true;
104
107
  applyHitTesting();
105
- void live.send("__internal", { type: "dialog-shown" });
108
+ sendBridgeNotification(live, "__internal", { type: "dialog-shown" });
106
109
  }
107
110
  // Visibility is hit-testing plus a message, and nothing else. The overlay
108
111
  // iframe is NEVER set to `display: none` — that pauses the iframe's
@@ -116,7 +119,7 @@ function createViewer(options) {
116
119
  visible = false;
117
120
  window.removeEventListener("keydown", onKeyDown);
118
121
  applyHitTesting();
119
- void bridge.send("__internal", { type: "dialog-hidden" });
122
+ sendBridgeNotification(bridge, "__internal", { type: "dialog-hidden" });
120
123
  const notifyClose = onClose;
121
124
  onClose = undefined;
122
125
  notifyClose?.();
@@ -131,7 +134,7 @@ function createViewer(options) {
131
134
  if (next === theme)
132
135
  return;
133
136
  theme = next;
134
- void bridge?.send("__internal", {
137
+ sendBridgeNotification(bridge, "__internal", {
135
138
  type: "set-theme",
136
139
  theme: { colorScheme: next },
137
140
  });
@@ -1,6 +1,6 @@
1
1
  import { resolveIdHost } from "../id-host";
2
2
  import { getFlow, requireFlow } from "./create-flow";
3
- import { answerTokenRequest, bridgeToWindow, hostIdentity, makeIframe, OVERLAY_STYLE, } from "./iframe-host";
3
+ import { answerTokenRequest, bridgeToWindow, hostIdentity, makeIframe, OVERLAY_STYLE, sendBridgeNotification, } from "./iframe-host";
4
4
  import { setOverlayFocus } from "./overlay-focus";
5
5
  const POSITION_STYLE = {
6
6
  "top-right": { top: "0", right: "0" },
@@ -62,7 +62,7 @@ export function createProfileButton(options) {
62
62
  const bridge = bridgeToWindow(frame.contentWindow, {
63
63
  targetOrigin: hostOrigin,
64
64
  });
65
- bridge.send("__internal", {
65
+ sendBridgeNotification(bridge, "__internal", {
66
66
  type: "init",
67
67
  mode: "iframe",
68
68
  referrer: { title: document.title },
@@ -130,7 +130,7 @@ export function createProfileButton(options) {
130
130
  void answerTokenRequest(dialogBridge, flow, payload.id);
131
131
  }
132
132
  });
133
- void dialogBridge.send("__internal", {
133
+ sendBridgeNotification(dialogBridge, "__internal", {
134
134
  type: "profile-identity",
135
135
  identity: hostIdentity(flow),
136
136
  });
@@ -147,14 +147,18 @@ export function createProfileButton(options) {
147
147
  return;
148
148
  dialog.style.pointerEvents = "auto";
149
149
  setOverlayFocus(dialog, true);
150
- void dialogBridge?.send("__internal", { type: "dialog-shown" });
150
+ sendBridgeNotification(dialogBridge, "__internal", {
151
+ type: "dialog-shown",
152
+ });
151
153
  }
152
154
  function hideDialog() {
153
155
  if (!dialog)
154
156
  return;
155
157
  dialog.style.pointerEvents = "none";
156
158
  setOverlayFocus(dialog, false);
157
- void dialogBridge?.send("__internal", { type: "dialog-hidden" });
159
+ sendBridgeNotification(dialogBridge, "__internal", {
160
+ type: "dialog-hidden",
161
+ });
158
162
  }
159
163
  // ----- keep both iframes' identity in sync with the Flow session -----
160
164
  // flow.subscribe fires on any store change (jwt/credential/address too), so
@@ -166,9 +170,12 @@ export function createProfileButton(options) {
166
170
  if (key === lastIdentityKey)
167
171
  return;
168
172
  lastIdentityKey = key;
169
- void pillBridge.send("__internal", { type: "profile-identity", identity });
173
+ sendBridgeNotification(pillBridge, "__internal", {
174
+ type: "profile-identity",
175
+ identity,
176
+ });
170
177
  if (dialogBridge)
171
- void dialogBridge.send("__internal", {
178
+ sendBridgeNotification(dialogBridge, "__internal", {
172
179
  type: "profile-identity",
173
180
  identity,
174
181
  });
@@ -180,11 +187,11 @@ export function createProfileButton(options) {
180
187
  if (next === theme)
181
188
  return;
182
189
  theme = next;
183
- void pillBridge.send("__internal", {
190
+ sendBridgeNotification(pillBridge, "__internal", {
184
191
  type: "set-theme",
185
192
  theme: { colorScheme: next },
186
193
  });
187
- void dialogBridge?.send("__internal", {
194
+ sendBridgeNotification(dialogBridge, "__internal", {
188
195
  type: "set-theme",
189
196
  theme: { colorScheme: next },
190
197
  });
@@ -0,0 +1,9 @@
1
+ import type { ReportsApi, ReportTransport } from "../types/reports";
2
+ export type { ReportOperation, ReportRequest, ReportResponse, } from "../types/reports";
3
+ export declare class ReportRequestError extends Error {
4
+ readonly status: number;
5
+ readonly response: string;
6
+ constructor(status: number, response: string);
7
+ }
8
+ /** Typed service reports. Keep the reporting bearer on trusted servers only. */
9
+ export declare function createReportsApi(host: string, token: string, transport?: ReportTransport): ReportsApi;
@@ -0,0 +1,30 @@
1
+ import { httpOperations } from "../contracts/http";
2
+ export class ReportRequestError extends Error {
3
+ status;
4
+ response;
5
+ constructor(status, response) {
6
+ super(`Flow report failed (${status})`);
7
+ this.status = status;
8
+ this.response = response;
9
+ this.name = "ReportRequestError";
10
+ }
11
+ }
12
+ /** Typed service reports. Keep the reporting bearer on trusted servers only. */
13
+ export function createReportsApi(host, token, transport = fetch) {
14
+ return async function report(operation, body) {
15
+ const contract = httpOperations[operation];
16
+ const request = contract.request.parse(body);
17
+ const response = await transport(new URL(contract.path, host), {
18
+ method: "POST",
19
+ headers: {
20
+ authorization: `Bearer ${token}`,
21
+ "content-type": "application/json",
22
+ },
23
+ body: JSON.stringify(request),
24
+ });
25
+ if (!response.ok)
26
+ throw new ReportRequestError(response.status, await response.text());
27
+ // SAFETY: the selected operation owns both this response schema and the mapped response type.
28
+ return contract.response.parse(await response.json());
29
+ };
30
+ }
@@ -0,0 +1,155 @@
1
+ import { z } from "zod";
2
+ export declare const httpOperations: {
3
+ readonly startAction: {
4
+ readonly path: "/api/actions/start";
5
+ readonly request: z.ZodObject<{
6
+ sub: z.ZodString;
7
+ sessionId: z.ZodString;
8
+ source: z.ZodString;
9
+ showTimer: z.ZodOptional<z.ZodBoolean>;
10
+ }, z.core.$strip>;
11
+ readonly response: z.ZodObject<{
12
+ ok: z.ZodLiteral<true>;
13
+ }, z.core.$strip>;
14
+ };
15
+ readonly eventAction: {
16
+ readonly path: "/api/actions/event";
17
+ readonly request: z.ZodObject<{
18
+ sessionId: z.ZodString;
19
+ kind: z.ZodEnum<{
20
+ pause: "pause";
21
+ resume: "resume";
22
+ focus_lost: "focus_lost";
23
+ focus_gained: "focus_gained";
24
+ }>;
25
+ }, z.core.$strip>;
26
+ readonly response: z.ZodObject<{
27
+ ok: z.ZodLiteral<true>;
28
+ recorded: z.ZodBoolean;
29
+ }, z.core.$strip>;
30
+ };
31
+ readonly finishAction: {
32
+ readonly path: "/api/actions/finish";
33
+ readonly request: z.ZodObject<{
34
+ sessionId: z.ZodString;
35
+ }, z.core.$strip>;
36
+ readonly response: z.ZodObject<{
37
+ ok: z.ZodLiteral<true>;
38
+ xpGranted: z.ZodNullable<z.ZodObject<{
39
+ granted: z.ZodBoolean;
40
+ amount: z.ZodNumber;
41
+ totalXp: z.ZodNumber;
42
+ flowScore: z.ZodNumber;
43
+ level: z.ZodNumber;
44
+ tier: z.ZodNumber;
45
+ xpIntoLevel: z.ZodNumber;
46
+ xpForNextLevel: z.ZodNumber;
47
+ }, z.core.$strip>>;
48
+ }, z.core.$strip>;
49
+ };
50
+ readonly reportStateTime: {
51
+ readonly path: "/api/actions/state-time";
52
+ readonly request: z.ZodObject<{
53
+ sub: z.ZodString;
54
+ day: z.ZodString;
55
+ states: z.ZodRecord<z.ZodString, z.ZodNumber>;
56
+ }, z.core.$strip>;
57
+ readonly response: z.ZodObject<{
58
+ ok: z.ZodLiteral<true>;
59
+ }, z.core.$strip>;
60
+ };
61
+ readonly reportXp: {
62
+ readonly path: "/api/xp/events";
63
+ readonly request: z.ZodObject<{
64
+ sub: z.ZodString;
65
+ source: z.ZodString;
66
+ durationSeconds: z.ZodOptional<z.ZodNumber>;
67
+ idempotencyKey: z.ZodOptional<z.ZodString>;
68
+ }, z.core.$strip>;
69
+ readonly response: z.ZodObject<{
70
+ granted: z.ZodBoolean;
71
+ amount: z.ZodNumber;
72
+ totalXp: z.ZodNumber;
73
+ flowScore: z.ZodNumber;
74
+ level: z.ZodNumber;
75
+ tier: z.ZodNumber;
76
+ xpIntoLevel: z.ZodNumber;
77
+ xpForNextLevel: z.ZodNumber;
78
+ }, z.core.$strip>;
79
+ };
80
+ readonly reportPresence: {
81
+ readonly path: "/api/rooms/presence/report";
82
+ readonly request: z.ZodObject<{
83
+ channel: z.ZodEnum<{
84
+ game: "game";
85
+ voice: "voice";
86
+ text: "text";
87
+ }>;
88
+ rooms: z.ZodArray<z.ZodObject<{
89
+ roomId: z.ZodString;
90
+ occupants: z.ZodArray<z.ZodObject<{
91
+ sub: z.ZodString;
92
+ }, z.core.$loose>>;
93
+ }, z.core.$strip>>;
94
+ }, z.core.$strip>;
95
+ readonly response: z.ZodObject<{
96
+ ok: z.ZodLiteral<true>;
97
+ }, z.core.$strip>;
98
+ };
99
+ readonly reportPosition: {
100
+ readonly path: "/api/rooms/position/report";
101
+ readonly request: z.ZodObject<{
102
+ positions: z.ZodArray<z.ZodObject<{
103
+ roomId: z.ZodString;
104
+ sub: z.ZodString;
105
+ x: z.ZodNumber;
106
+ y: z.ZodNumber;
107
+ z: z.ZodNumber;
108
+ yaw: z.ZodNumber;
109
+ worldId: z.ZodString;
110
+ }, z.core.$strip>>;
111
+ }, z.core.$strip>;
112
+ readonly response: z.ZodObject<{
113
+ ok: z.ZodLiteral<true>;
114
+ stored: z.ZodNumber;
115
+ }, z.core.$strip>;
116
+ };
117
+ };
118
+ export declare const jwtClaimsSchema: z.ZodObject<{
119
+ sub: z.ZodString;
120
+ aud: z.ZodString;
121
+ iss: z.ZodString;
122
+ iat: z.ZodNumber;
123
+ exp: z.ZodNumber;
124
+ guest: z.ZodBoolean;
125
+ username: z.ZodOptional<z.ZodString>;
126
+ }, z.core.$loose>;
127
+ export declare const sessionResponseSchema: z.ZodObject<{
128
+ jwt: z.ZodString;
129
+ refreshToken: z.ZodString;
130
+ user: z.ZodObject<{
131
+ id: z.ZodString;
132
+ username: z.ZodString;
133
+ isGuest: z.ZodOptional<z.ZodBoolean>;
134
+ }, z.core.$loose>;
135
+ credential: z.ZodNullable<z.ZodObject<{
136
+ id: z.ZodString;
137
+ publicKey: z.ZodString;
138
+ }, z.core.$strip>>;
139
+ additionalSessions: z.ZodOptional<z.ZodArray<z.ZodObject<{
140
+ audience: z.ZodString;
141
+ jwt: z.ZodString;
142
+ refreshToken: z.ZodString;
143
+ }, z.core.$strip>>>;
144
+ }, z.core.$loose>;
145
+ export declare const jwksSchema: z.ZodObject<{
146
+ keys: z.ZodArray<z.ZodObject<{
147
+ kid: z.ZodString;
148
+ kty: z.ZodLiteral<"OKP">;
149
+ crv: z.ZodLiteral<"Ed25519">;
150
+ x: z.ZodString;
151
+ alg: z.ZodOptional<z.ZodLiteral<"EdDSA">>;
152
+ use: z.ZodOptional<z.ZodLiteral<"sig">>;
153
+ d: z.ZodOptional<z.ZodNever>;
154
+ }, z.core.$strip>>;
155
+ }, z.core.$strip>;
@@ -0,0 +1,91 @@
1
+ import { z } from "zod";
2
+ import { actionEventSchema, actionFinishSchema, actionStartSchema, positionReportSchema, presenceReportSchema, stateTimeSchema, xpEventSchema, } from "./reports";
3
+ const okResponseSchema = z.object({ ok: z.literal(true) });
4
+ const xpGrantResponseSchema = z.object({
5
+ granted: z.boolean(),
6
+ amount: z.number(),
7
+ totalXp: z.number(),
8
+ flowScore: z.number(),
9
+ level: z.number(),
10
+ tier: z.number(),
11
+ xpIntoLevel: z.number(),
12
+ xpForNextLevel: z.number(),
13
+ });
14
+ export const httpOperations = {
15
+ startAction: {
16
+ path: "/api/actions/start",
17
+ request: actionStartSchema,
18
+ response: okResponseSchema,
19
+ },
20
+ eventAction: {
21
+ path: "/api/actions/event",
22
+ request: actionEventSchema,
23
+ response: okResponseSchema.extend({ recorded: z.boolean() }),
24
+ },
25
+ finishAction: {
26
+ path: "/api/actions/finish",
27
+ request: actionFinishSchema,
28
+ response: okResponseSchema.extend({
29
+ xpGranted: xpGrantResponseSchema.nullable(),
30
+ }),
31
+ },
32
+ reportStateTime: {
33
+ path: "/api/actions/state-time",
34
+ request: stateTimeSchema,
35
+ response: okResponseSchema,
36
+ },
37
+ reportXp: {
38
+ path: "/api/xp/events",
39
+ request: xpEventSchema,
40
+ response: xpGrantResponseSchema,
41
+ },
42
+ reportPresence: {
43
+ path: "/api/rooms/presence/report",
44
+ request: presenceReportSchema,
45
+ response: okResponseSchema,
46
+ },
47
+ reportPosition: {
48
+ path: "/api/rooms/position/report",
49
+ request: positionReportSchema,
50
+ response: okResponseSchema.extend({ stored: z.number() }),
51
+ },
52
+ };
53
+ export const jwtClaimsSchema = z.looseObject({
54
+ sub: z.string().min(1),
55
+ aud: z.string().min(1),
56
+ iss: z.string().min(1),
57
+ iat: z.number().int(),
58
+ exp: z.number().int(),
59
+ guest: z.boolean(),
60
+ username: z.string().optional(),
61
+ });
62
+ export const sessionResponseSchema = z.looseObject({
63
+ jwt: z.string().min(1),
64
+ refreshToken: z.string().min(1),
65
+ user: z.looseObject({
66
+ id: z.string(),
67
+ username: z.string(),
68
+ isGuest: z.boolean().optional(),
69
+ }),
70
+ credential: z.object({ id: z.string(), publicKey: z.string() }).nullable(),
71
+ additionalSessions: z
72
+ .array(z.object({
73
+ audience: z.string(),
74
+ jwt: z.string(),
75
+ refreshToken: z.string(),
76
+ }))
77
+ .optional(),
78
+ });
79
+ export const jwksSchema = z.object({
80
+ keys: z
81
+ .array(z.object({
82
+ kid: z.string().min(1),
83
+ kty: z.literal("OKP"),
84
+ crv: z.literal("Ed25519"),
85
+ x: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
86
+ alg: z.literal("EdDSA").optional(),
87
+ use: z.literal("sig").optional(),
88
+ d: z.never().optional(),
89
+ }))
90
+ .min(1),
91
+ });
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ /** Game server → auth: a reportable XP event. */
3
+ export declare const xpEventSchema: z.ZodObject<{
4
+ sub: z.ZodString;
5
+ source: z.ZodString;
6
+ durationSeconds: z.ZodOptional<z.ZodNumber>;
7
+ idempotencyKey: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strip>;
9
+ /** Talk and the game report a full snapshot of every active room they hold. */
10
+ export declare const presenceReportSchema: z.ZodObject<{
11
+ channel: z.ZodEnum<{
12
+ game: "game";
13
+ voice: "voice";
14
+ text: "text";
15
+ }>;
16
+ rooms: z.ZodArray<z.ZodObject<{
17
+ roomId: z.ZodString;
18
+ occupants: z.ZodArray<z.ZodObject<{
19
+ sub: z.ZodString;
20
+ }, z.core.$loose>>;
21
+ }, z.core.$strip>>;
22
+ }, z.core.$strip>;
23
+ /** The game server checkpoints each tracked player's last grounded footing. */
24
+ export declare const positionReportSchema: z.ZodObject<{
25
+ positions: z.ZodArray<z.ZodObject<{
26
+ roomId: z.ZodString;
27
+ sub: z.ZodString;
28
+ x: z.ZodNumber;
29
+ y: z.ZodNumber;
30
+ z: z.ZodNumber;
31
+ yaw: z.ZodNumber;
32
+ worldId: z.ZodString;
33
+ }, z.core.$strip>>;
34
+ }, z.core.$strip>;
35
+ export declare const actionStartSchema: z.ZodObject<{
36
+ sub: z.ZodString;
37
+ sessionId: z.ZodString;
38
+ source: z.ZodString;
39
+ showTimer: z.ZodOptional<z.ZodBoolean>;
40
+ }, z.core.$strip>;
41
+ export declare const actionFinishSchema: z.ZodObject<{
42
+ sessionId: z.ZodString;
43
+ }, z.core.$strip>;
44
+ export declare const actionEventSchema: z.ZodObject<{
45
+ sessionId: z.ZodString;
46
+ kind: z.ZodEnum<{
47
+ pause: "pause";
48
+ resume: "resume";
49
+ focus_lost: "focus_lost";
50
+ focus_gained: "focus_gained";
51
+ }>;
52
+ }, z.core.$strip>;
53
+ export declare const stateTimeSchema: z.ZodObject<{
54
+ sub: z.ZodString;
55
+ day: z.ZodString;
56
+ states: z.ZodRecord<z.ZodString, z.ZodNumber>;
57
+ }, z.core.$strip>;
58
+ export declare const xpDevSchema: z.ZodObject<{
59
+ sub: z.ZodString;
60
+ op: z.ZodEnum<{
61
+ add: "add";
62
+ set_total: "set_total";
63
+ set_level: "set_level";
64
+ }>;
65
+ value: z.ZodNumber;
66
+ }, z.core.$strip>;
67
+ export declare const reportSchemas: {
68
+ xpEvent: z.ZodObject<{
69
+ sub: z.ZodString;
70
+ source: z.ZodString;
71
+ durationSeconds: z.ZodOptional<z.ZodNumber>;
72
+ idempotencyKey: z.ZodOptional<z.ZodString>;
73
+ }, z.core.$strip>;
74
+ presenceReport: z.ZodObject<{
75
+ channel: z.ZodEnum<{
76
+ game: "game";
77
+ voice: "voice";
78
+ text: "text";
79
+ }>;
80
+ rooms: z.ZodArray<z.ZodObject<{
81
+ roomId: z.ZodString;
82
+ occupants: z.ZodArray<z.ZodObject<{
83
+ sub: z.ZodString;
84
+ }, z.core.$loose>>;
85
+ }, z.core.$strip>>;
86
+ }, z.core.$strip>;
87
+ positionReport: z.ZodObject<{
88
+ positions: z.ZodArray<z.ZodObject<{
89
+ roomId: z.ZodString;
90
+ sub: z.ZodString;
91
+ x: z.ZodNumber;
92
+ y: z.ZodNumber;
93
+ z: z.ZodNumber;
94
+ yaw: z.ZodNumber;
95
+ worldId: z.ZodString;
96
+ }, z.core.$strip>>;
97
+ }, z.core.$strip>;
98
+ actionStart: z.ZodObject<{
99
+ sub: z.ZodString;
100
+ sessionId: z.ZodString;
101
+ source: z.ZodString;
102
+ showTimer: z.ZodOptional<z.ZodBoolean>;
103
+ }, z.core.$strip>;
104
+ actionEvent: z.ZodObject<{
105
+ sessionId: z.ZodString;
106
+ kind: z.ZodEnum<{
107
+ pause: "pause";
108
+ resume: "resume";
109
+ focus_lost: "focus_lost";
110
+ focus_gained: "focus_gained";
111
+ }>;
112
+ }, z.core.$strip>;
113
+ actionFinish: z.ZodObject<{
114
+ sessionId: z.ZodString;
115
+ }, z.core.$strip>;
116
+ stateTime: z.ZodObject<{
117
+ sub: z.ZodString;
118
+ day: z.ZodString;
119
+ states: z.ZodRecord<z.ZodString, z.ZodNumber>;
120
+ }, z.core.$strip>;
121
+ xpDev: z.ZodObject<{
122
+ sub: z.ZodString;
123
+ op: z.ZodEnum<{
124
+ add: "add";
125
+ set_total: "set_total";
126
+ set_level: "set_level";
127
+ }>;
128
+ value: z.ZodNumber;
129
+ }, z.core.$strip>;
130
+ };