@flow-industries/id 0.21.2 → 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.
Files changed (47) hide show
  1. package/README.md +52 -0
  2. package/contracts/v1/openapi.json +818 -0
  3. package/contracts/v1/reports.json +188 -0
  4. package/contracts/v1/sdk-exports.json +1165 -0
  5. package/dist/sdk/client/create-flow.js +2 -5
  6. package/dist/sdk/client/dialog-host.js +11 -6
  7. package/dist/sdk/client/flow-widget.js +3 -3
  8. package/dist/sdk/client/focus-session.d.ts +18 -0
  9. package/dist/sdk/client/focus-session.js +140 -0
  10. package/dist/sdk/client/focus.d.ts +11 -0
  11. package/dist/sdk/client/focus.js +30 -0
  12. package/dist/sdk/client/iframe-host.d.ts +2 -1
  13. package/dist/sdk/client/iframe-host.js +11 -1
  14. package/dist/sdk/client/index.d.ts +4 -1
  15. package/dist/sdk/client/index.js +2 -0
  16. package/dist/sdk/client/json-api.d.ts +1 -1
  17. package/dist/sdk/client/json-api.js +3 -1
  18. package/dist/sdk/client/open-profile.js +10 -7
  19. package/dist/sdk/client/profile-button.js +16 -9
  20. package/dist/sdk/client/reports.d.ts +9 -0
  21. package/dist/sdk/client/reports.js +30 -0
  22. package/dist/sdk/client/rooms.js +7 -26
  23. package/dist/sdk/client/study-search.d.ts +20 -0
  24. package/dist/sdk/client/study-search.js +116 -0
  25. package/dist/sdk/contracts/http.d.ts +155 -0
  26. package/dist/sdk/contracts/http.js +91 -0
  27. package/dist/sdk/contracts/reports.d.ts +130 -0
  28. package/dist/sdk/contracts/reports.js +67 -0
  29. package/dist/sdk/dialog/remote/Messenger.d.ts +3 -0
  30. package/dist/sdk/dialog/remote/Messenger.js +9 -3
  31. package/dist/sdk/react/use-cooldown.d.ts +1 -0
  32. package/dist/sdk/react/use-cooldown.js +12 -0
  33. package/dist/sdk/types/auth.d.ts +12 -0
  34. package/dist/sdk/types/dialog.d.ts +25 -0
  35. package/dist/sdk/types/focus.d.ts +109 -0
  36. package/dist/sdk/types/focus.js +0 -0
  37. package/dist/sdk/types/index.d.ts +8 -3
  38. package/dist/sdk/types/landing.d.ts +9 -0
  39. package/dist/sdk/types/landing.js +0 -0
  40. package/dist/sdk/types/otp.d.ts +20 -0
  41. package/dist/sdk/types/otp.js +0 -0
  42. package/dist/sdk/types/reports.d.ts +7 -0
  43. package/dist/sdk/types/reports.js +0 -0
  44. package/dist/sdk/types/settings.d.ts +162 -0
  45. package/dist/sdk/types/settings.js +0 -0
  46. package/dist/sdk/types/xp.d.ts +8 -0
  47. package/package.json +21 -9
@@ -0,0 +1,116 @@
1
+ export function normalizeSearchText(text) {
2
+ return text
3
+ .normalize("NFD")
4
+ .replace(/\p{M}+/gu, "")
5
+ .toLowerCase()
6
+ .replace(/\s+/g, " ")
7
+ .trim();
8
+ }
9
+ /** 4 exact, 3 prefix, 2 a later word's prefix, 1 substring, 0 no match.
10
+ * Both sides are normalized here so callers can pass raw labels. */
11
+ export function matchScore(query, candidate) {
12
+ const normalizedQuery = normalizeSearchText(query);
13
+ const normalizedCandidate = normalizeSearchText(candidate);
14
+ if (!normalizedQuery || !normalizedCandidate)
15
+ return 0;
16
+ if (normalizedCandidate === normalizedQuery)
17
+ return 4;
18
+ if (normalizedCandidate.startsWith(normalizedQuery))
19
+ return 3;
20
+ if (normalizedCandidate.includes(` ${normalizedQuery}`))
21
+ return 2;
22
+ if (normalizedCandidate.includes(normalizedQuery))
23
+ return 1;
24
+ return 0;
25
+ }
26
+ export function catalogFieldIndex(catalog) {
27
+ const index = new Map();
28
+ for (const category of catalog.categories) {
29
+ for (const field of category.fields) {
30
+ index.set(field.slug, { category, field });
31
+ }
32
+ }
33
+ return index;
34
+ }
35
+ /** Live subjects newest-used first; auth stamps `lastUsedAt` at creation, so
36
+ * a subject never studied sorts by when it was added. */
37
+ export function recentSubjects(subjects, limit = 5) {
38
+ return [...subjects]
39
+ .sort((a, b) => b.lastUsedAt.localeCompare(a.lastUsedAt))
40
+ .slice(0, limit);
41
+ }
42
+ /** Catalog fields the user already keeps under the field's own name are
43
+ * hidden from results — the subject hit covers them and picking the field
44
+ * again would only re-create the same subject. A renamed subject ("Math for
45
+ * my test" under Algebra) leaves the field visible for a second subject. */
46
+ export function fieldsCoveredBySubjects(subjects, index) {
47
+ const covered = new Set();
48
+ for (const subject of subjects) {
49
+ if (!subject.field)
50
+ continue;
51
+ const ref = index.get(subject.field);
52
+ if (!ref)
53
+ continue;
54
+ if (normalizeSearchText(subject.name) === normalizeSearchText(ref.field.label)) {
55
+ covered.add(subject.field);
56
+ }
57
+ }
58
+ return covered;
59
+ }
60
+ function bestAliasMatch(query, field) {
61
+ let best = null;
62
+ for (const alias of field.aliases ?? []) {
63
+ const score = matchScore(query, alias);
64
+ if (score > (best?.score ?? 0))
65
+ best = { alias, score };
66
+ }
67
+ return best;
68
+ }
69
+ export function searchStudy(query, options) {
70
+ const { catalog, subjects, limit = 12 } = options;
71
+ if (!normalizeSearchText(query))
72
+ return [];
73
+ const index = catalogFieldIndex(catalog);
74
+ const covered = fieldsCoveredBySubjects(subjects, index);
75
+ const hits = [];
76
+ for (const subject of subjects) {
77
+ const ref = subject.field ? (index.get(subject.field) ?? null) : null;
78
+ let score = matchScore(query, subject.name);
79
+ if (ref) {
80
+ score = Math.max(score, matchScore(query, ref.field.label));
81
+ const alias = bestAliasMatch(query, ref.field);
82
+ if (alias)
83
+ score = Math.max(score, alias.score);
84
+ }
85
+ if (score > 0) {
86
+ hits.push({ kind: "subject", id: subject.id, subject, ref, score });
87
+ }
88
+ }
89
+ for (const ref of index.values()) {
90
+ if (covered.has(ref.field.slug))
91
+ continue;
92
+ const labelScore = matchScore(query, ref.field.label);
93
+ const alias = bestAliasMatch(query, ref.field);
94
+ const score = Math.max(labelScore, alias?.score ?? 0);
95
+ if (score === 0)
96
+ continue;
97
+ hits.push({
98
+ kind: "field",
99
+ id: ref.field.slug,
100
+ ref,
101
+ alias: alias && alias.score > labelScore ? alias.alias : null,
102
+ score,
103
+ });
104
+ }
105
+ hits.sort((a, b) => {
106
+ if (b.score !== a.score)
107
+ return b.score - a.score;
108
+ if (a.kind !== b.kind)
109
+ return a.kind === "subject" ? -1 : 1;
110
+ return hitLabel(a).localeCompare(hitLabel(b));
111
+ });
112
+ return hits.slice(0, limit);
113
+ }
114
+ export function hitLabel(hit) {
115
+ return hit.kind === "subject" ? hit.subject.name : hit.ref.field.label;
116
+ }
@@ -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
+ };
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ /** Game server → auth: a reportable XP event. */
3
+ export const xpEventSchema = z.object({
4
+ sub: z.string(),
5
+ source: z.string(),
6
+ durationSeconds: z.number().finite().optional(),
7
+ idempotencyKey: z.string().optional(),
8
+ });
9
+ /** Talk and the game report a full snapshot of every active room they hold. */
10
+ export const presenceReportSchema = z.object({
11
+ channel: z.enum(["game", "voice", "text"]),
12
+ rooms: z.array(z.object({
13
+ roomId: z.string(),
14
+ occupants: z.array(z.looseObject({ sub: z.string() })),
15
+ })),
16
+ });
17
+ const POSITION_VALUE_LIMIT = 1_000_000;
18
+ const WORLD_ID_MAX_LENGTH = 64;
19
+ const positionValue = z
20
+ .number()
21
+ .min(-POSITION_VALUE_LIMIT)
22
+ .max(POSITION_VALUE_LIMIT);
23
+ /** The game server checkpoints each tracked player's last grounded footing. */
24
+ export const positionReportSchema = z.object({
25
+ positions: z.array(z.object({
26
+ roomId: z.string(),
27
+ sub: z.string(),
28
+ x: positionValue,
29
+ y: positionValue,
30
+ z: positionValue,
31
+ yaw: positionValue,
32
+ worldId: z.string().max(WORLD_ID_MAX_LENGTH),
33
+ })),
34
+ });
35
+ export const actionStartSchema = z.object({
36
+ sub: z.string(),
37
+ sessionId: z.string(),
38
+ source: z.string(),
39
+ showTimer: z.boolean().optional(),
40
+ });
41
+ export const actionFinishSchema = z.object({
42
+ sessionId: z.string(),
43
+ });
44
+ export const actionEventSchema = z.object({
45
+ sessionId: z.string(),
46
+ kind: z.enum(["pause", "resume", "focus_lost", "focus_gained"]),
47
+ });
48
+ export const stateTimeSchema = z.object({
49
+ sub: z.string(),
50
+ day: z.string(),
51
+ states: z.record(z.string(), z.number()),
52
+ });
53
+ export const xpDevSchema = z.object({
54
+ sub: z.string(),
55
+ op: z.enum(["add", "set_total", "set_level"]),
56
+ value: z.number().finite(),
57
+ });
58
+ export const reportSchemas = {
59
+ xpEvent: xpEventSchema,
60
+ presenceReport: presenceReportSchema,
61
+ positionReport: positionReportSchema,
62
+ actionStart: actionStartSchema,
63
+ actionEvent: actionEventSchema,
64
+ actionFinish: actionFinishSchema,
65
+ stateTime: stateTimeSchema,
66
+ xpDev: xpDevSchema,
67
+ };
@@ -12,6 +12,9 @@ export declare function from(messenger: Messenger): Messenger;
12
12
  * isolation that prevents arbitrary pages from injecting messages.
13
13
  */
14
14
  export declare function fromWindow(w: Window, options?: FromWindowOptions): Messenger;
15
+ export declare class BridgeDestroyedError extends Error {
16
+ constructor();
17
+ }
15
18
  /**
16
19
  * Combines two Messengers into a bidirectional bridge: `from` is read-side
17
20
  * (inbound messages), `to` is write-side (outbound). Adds a "ready"
@@ -87,6 +87,12 @@ export function fromWindow(w, options = {}) {
87
87
  },
88
88
  });
89
89
  }
90
+ export class BridgeDestroyedError extends Error {
91
+ constructor() {
92
+ super("Message bridge destroyed before the peer was ready");
93
+ this.name = "BridgeDestroyedError";
94
+ }
95
+ }
90
96
  /**
91
97
  * Combines two Messengers into a bidirectional bridge: `from` is read-side
92
98
  * (inbound messages), `to` is write-side (outbound). Adds a "ready"
@@ -109,19 +115,19 @@ export function bridge(parameters) {
109
115
  from_.destroy();
110
116
  to.destroy();
111
117
  if (pending)
112
- rejectReady();
118
+ rejectReady(new BridgeDestroyedError());
113
119
  },
114
120
  on(topic, listener, id) {
115
121
  return from_.on(topic, listener, id);
116
122
  },
117
123
  async send(topic, payload, targetOrigin) {
118
- pending = true;
124
+ pending = waitForReady;
119
125
  if (waitForReady)
120
126
  await readyPromise.finally(() => (pending = false));
121
127
  return to.send(topic, payload, targetOrigin);
122
128
  },
123
129
  async sendAsync(topic, payload, targetOrigin) {
124
- pending = true;
130
+ pending = waitForReady;
125
131
  if (waitForReady)
126
132
  await readyPromise.finally(() => (pending = false));
127
133
  return to.sendAsync(topic, payload, targetOrigin);
@@ -0,0 +1 @@
1
+ export declare function useCooldown(initial: number): [number, (next: number) => void];
@@ -0,0 +1,12 @@
1
+ import { useEffect, useState } from "react";
2
+ export function useCooldown(initial) {
3
+ const [cooldown, setCooldown] = useState(initial);
4
+ const cooldownActive = cooldown > 0;
5
+ useEffect(() => {
6
+ if (!cooldownActive)
7
+ return;
8
+ const timer = setInterval(() => setCooldown((c) => (c <= 1 ? 0 : c - 1)), 1000);
9
+ return () => clearInterval(timer);
10
+ }, [cooldownActive]);
11
+ return [cooldown, setCooldown];
12
+ }
@@ -160,3 +160,15 @@ export type VerifiedFlowJWT = JWTPayload & {
160
160
  */
161
161
  guest?: boolean;
162
162
  };
163
+ export type PasskeyEnrollment = {
164
+ userId: string;
165
+ username: string;
166
+ credential: FlowCredential;
167
+ };
168
+ export type AccountRegistration = {
169
+ username: string;
170
+ email: string;
171
+ subscribedToNews: boolean;
172
+ walletAddress: string | null;
173
+ credential: FlowCredential | null;
174
+ };
@@ -1,3 +1,4 @@
1
+ import type { RpcRequest } from "./protocol";
1
2
  /**
2
3
  * Async view state for a dialog panel — the three states every fetched
3
4
  * section renders (spinner / retry row / content), so "empty" stays
@@ -89,3 +90,27 @@ export type DialogState = {
89
90
  customFeatures?: DialogCustomFeatures;
90
91
  customLabels?: DialogCustomLabels;
91
92
  };
93
+ export type SignUpProps = {
94
+ request: RpcRequest;
95
+ rpId: string;
96
+ accessKeyHash?: string | undefined;
97
+ guestToken?: string | undefined;
98
+ onHome: () => void;
99
+ onSignIn: () => void;
100
+ };
101
+ export type SignInWithEmailProps = {
102
+ request: RpcRequest;
103
+ onBack: () => void;
104
+ onHome: () => void;
105
+ };
106
+ /** Cookie-authoritative identity for account data operations. */
107
+ export type AccountDataIdentity = {
108
+ id: string;
109
+ username: string;
110
+ isGuest: boolean;
111
+ };
112
+ /** Validated export file and any collections capped by the server. */
113
+ export type AccountDataDownload = {
114
+ blob: Blob;
115
+ truncated: string[];
116
+ };