@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.
- package/README.md +52 -0
- package/contracts/v1/openapi.json +818 -0
- package/contracts/v1/reports.json +188 -0
- package/contracts/v1/sdk-exports.json +1165 -0
- package/dist/sdk/client/dialog-host.js +11 -6
- package/dist/sdk/client/flow-widget.js +3 -3
- package/dist/sdk/client/iframe-host.d.ts +2 -1
- package/dist/sdk/client/iframe-host.js +11 -1
- package/dist/sdk/client/index.d.ts +2 -0
- package/dist/sdk/client/index.js +1 -0
- package/dist/sdk/client/open-profile.js +10 -7
- package/dist/sdk/client/profile-button.js +16 -9
- package/dist/sdk/client/reports.d.ts +9 -0
- package/dist/sdk/client/reports.js +30 -0
- package/dist/sdk/contracts/http.d.ts +155 -0
- package/dist/sdk/contracts/http.js +91 -0
- package/dist/sdk/contracts/reports.d.ts +130 -0
- package/dist/sdk/contracts/reports.js +67 -0
- package/dist/sdk/dialog/remote/Messenger.d.ts +3 -0
- package/dist/sdk/dialog/remote/Messenger.js +9 -3
- package/dist/sdk/types/index.d.ts +1 -0
- package/dist/sdk/types/reports.d.ts +7 -0
- package/dist/sdk/types/reports.js +0 -0
- package/package.json +16 -8
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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";
|
package/dist/sdk/client/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
+
sendBridgeNotification(pillBridge, "__internal", {
|
|
174
|
+
type: "profile-identity",
|
|
175
|
+
identity,
|
|
176
|
+
});
|
|
170
177
|
if (dialogBridge)
|
|
171
|
-
|
|
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
|
-
|
|
190
|
+
sendBridgeNotification(pillBridge, "__internal", {
|
|
184
191
|
type: "set-theme",
|
|
185
192
|
theme: { colorScheme: next },
|
|
186
193
|
});
|
|
187
|
-
|
|
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
|
+
};
|