@flow-industries/id 0.22.0 → 0.23.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.
- package/README.md +69 -8
- 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/browser-contract.d.ts +30 -0
- package/dist/sdk/browser-contract.js +19 -0
- package/dist/sdk/browser-session-route.d.ts +8 -0
- package/dist/sdk/browser-session-route.js +196 -0
- package/dist/sdk/client/create-flow.js +176 -166
- package/dist/sdk/client/dialog-host.js +11 -6
- package/dist/sdk/client/flow-widget.js +4 -4
- package/dist/sdk/client/idb.d.ts +1 -0
- package/dist/sdk/client/idb.js +24 -0
- 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 +19 -8
- package/dist/sdk/client/profile-button.d.ts +1 -13
- package/dist/sdk/client/profile-button.js +114 -170
- package/dist/sdk/client/reports.d.ts +9 -0
- package/dist/sdk/client/reports.js +30 -0
- package/dist/sdk/client/session.d.ts +1 -10
- package/dist/sdk/client/session.js +0 -37
- package/dist/sdk/client/static-flow.js +1 -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/react/hooks.d.ts +1 -1
- package/dist/sdk/react/profile-button.d.ts +1 -12
- package/dist/sdk/react/profile-button.js +1 -12
- package/dist/sdk/session-route.js +101 -4
- package/dist/sdk/types/dialog.d.ts +0 -3
- package/dist/sdk/types/index.d.ts +2 -1
- package/dist/sdk/types/landing.d.ts +1 -0
- package/dist/sdk/types/reports.d.ts +7 -0
- package/dist/sdk/types/reports.js +0 -0
- package/dist/sdk/types/sdk.d.ts +7 -21
- package/dist/sdk/types/server.d.ts +6 -0
- package/dist/sdk/wagmi/index.js +0 -3
- package/package.json +18 -9
|
@@ -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 =
|
|
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 =
|
|
130
|
+
pending = waitForReady;
|
|
125
131
|
if (waitForReady)
|
|
126
132
|
await readyPromise.finally(() => (pending = false));
|
|
127
133
|
return to.sendAsync(topic, payload, targetOrigin);
|
|
@@ -28,7 +28,7 @@ export declare function useFlowId(): {
|
|
|
28
28
|
address: `0x${string}` | null;
|
|
29
29
|
isAuthenticated: boolean;
|
|
30
30
|
isGuest: boolean;
|
|
31
|
-
login: (options?: import("../types").LoginOptions) => Promise<
|
|
31
|
+
login: (options?: import("../types").LoginOptions) => Promise<never>;
|
|
32
32
|
logout: () => Promise<void>;
|
|
33
33
|
restore: () => Promise<boolean>;
|
|
34
34
|
ensureGuest: () => Promise<boolean>;
|
|
@@ -1,14 +1,3 @@
|
|
|
1
1
|
import type { ProfileButtonProps } from "../types";
|
|
2
|
-
/**
|
|
3
|
-
* Drops the universal Flow ID profile button into a React tree. Renders a host
|
|
4
|
-
* element (style it via `className` for placement/padding — e.g. top-right) that
|
|
5
|
-
* the pill iframe mounts into; the iframe itself is sized to its content and
|
|
6
|
-
* expands to the profile dialog on click. Resolves the Flow instance from a
|
|
7
|
-
* `<FlowIdProvider>` or the createFlow() singleton.
|
|
8
|
-
*
|
|
9
|
-
* A changing `theme` recolors the mounted pill over its open bridge rather than
|
|
10
|
-
* remounting it: `theme` is deliberately absent from the mount effect's deps,
|
|
11
|
-
* because tearing the iframe down on a light/dark toggle reloads the widget and
|
|
12
|
-
* drops the pill out of the host's layout mid-toggle.
|
|
13
|
-
*/
|
|
2
|
+
/** Mount the native account button with access to the current Flow session. */
|
|
14
3
|
export declare function ProfileButton({ className, host, theme }: ProfileButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,18 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { createProfileButton } from "../client/profile-button";
|
|
4
4
|
import { useFlow } from "./hooks";
|
|
5
|
-
/**
|
|
6
|
-
* Drops the universal Flow ID profile button into a React tree. Renders a host
|
|
7
|
-
* element (style it via `className` for placement/padding — e.g. top-right) that
|
|
8
|
-
* the pill iframe mounts into; the iframe itself is sized to its content and
|
|
9
|
-
* expands to the profile dialog on click. Resolves the Flow instance from a
|
|
10
|
-
* `<FlowIdProvider>` or the createFlow() singleton.
|
|
11
|
-
*
|
|
12
|
-
* A changing `theme` recolors the mounted pill over its open bridge rather than
|
|
13
|
-
* remounting it: `theme` is deliberately absent from the mount effect's deps,
|
|
14
|
-
* because tearing the iframe down on a light/dark toggle reloads the widget and
|
|
15
|
-
* drops the pill out of the host's layout mid-toggle.
|
|
16
|
-
*/
|
|
5
|
+
/** Mount the native account button with access to the current Flow session. */
|
|
17
6
|
export function ProfileButton({ className, host, theme }) {
|
|
18
7
|
const flow = useFlow();
|
|
19
8
|
const ref = useRef(null);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { browserCompletionSchema } from "./browser-contract";
|
|
3
|
+
import { finishBrowserSession, startBrowserSession, } from "./browser-session-route";
|
|
4
|
+
import { clearCookieString } from "./cookies";
|
|
2
5
|
/**
|
|
3
6
|
* The first-party session route every consumer app serves from its own
|
|
4
7
|
* origin — the only path between a browser and the refresh token. The
|
|
@@ -21,13 +24,20 @@ import { z } from "zod";
|
|
|
21
24
|
*/
|
|
22
25
|
import { cookieNamesFor, parseCookieHeader } from "./cookies";
|
|
23
26
|
import { DEFAULT_SESSION_PATH, defaultAudience, resolveIdHost, } from "./id-host";
|
|
24
|
-
import { claimsToUser, clearingCookies, isJwtVerificationFailure, resolveSessionCore, sessionCookies, } from "./session-core";
|
|
27
|
+
import { claimsToUser, clearingCookies, isJwtVerificationFailure, refreshSessionCore, resolveSessionCore, sessionCookies, } from "./session-core";
|
|
25
28
|
import { verifyFlowJWT } from "./verify";
|
|
26
29
|
/** The body the browser SDK posts to its own app's session route. */
|
|
27
30
|
const installBodySchema = z.object({
|
|
28
31
|
refreshToken: z.string().min(1).optional(),
|
|
29
32
|
jwt: z.string().min(1).optional(),
|
|
30
33
|
mint: z.array(z.string()).optional(),
|
|
34
|
+
action: z.enum(["login", "account", "guest"]).optional(),
|
|
35
|
+
returnTo: z.string().optional(),
|
|
36
|
+
signUp: z.boolean().optional(),
|
|
37
|
+
accessKeyHash: z
|
|
38
|
+
.string()
|
|
39
|
+
.regex(/^0x[a-fA-F0-9]{64}$/)
|
|
40
|
+
.optional(),
|
|
31
41
|
});
|
|
32
42
|
const additionalBodySchema = z.object({
|
|
33
43
|
additionalSessions: z
|
|
@@ -72,17 +82,100 @@ function isCrossSite(request, audience) {
|
|
|
72
82
|
export async function handleSessionRequest(request, opts = {}) {
|
|
73
83
|
const audience = opts.audience ?? defaultAudience(new URL(request.url).origin);
|
|
74
84
|
const issuerUrl = resolveIdHost(opts.issuerUrl, audience);
|
|
85
|
+
const callback = new URL(request.url);
|
|
86
|
+
if (request.method === "GET" && callback.searchParams.has("flow_state")) {
|
|
87
|
+
return finishBrowserSession(request, audience, issuerUrl);
|
|
88
|
+
}
|
|
75
89
|
if (isCrossSite(request, audience)) {
|
|
76
90
|
return json({ state: null }, 403);
|
|
77
91
|
}
|
|
78
92
|
switch (request.method) {
|
|
79
93
|
case "GET": {
|
|
94
|
+
if (new URL(request.url).searchParams.get("refresh") === "1") {
|
|
95
|
+
const token = parseCookieHeader(request.headers.get("Cookie"))[cookieNamesFor(audience).refresh];
|
|
96
|
+
if (!token)
|
|
97
|
+
return json({ state: null }, 200);
|
|
98
|
+
const result = await refreshSessionCore(issuerUrl, audience, token);
|
|
99
|
+
if (result === "dead")
|
|
100
|
+
return json({ state: null }, 200, clearingCookies(audience));
|
|
101
|
+
if (!result)
|
|
102
|
+
return json({ state: null }, 502);
|
|
103
|
+
return json({ state: result.session }, 200, result.setCookies);
|
|
104
|
+
}
|
|
80
105
|
const { session, setCookies } = await resolveSessionCore(request.headers.get("Cookie"), audience, issuerUrl);
|
|
81
|
-
|
|
106
|
+
const completionName = `${cookieNamesFor(audience).refresh}.completion`;
|
|
107
|
+
const raw = parseCookieHeader(request.headers.get("Cookie"))[completionName];
|
|
108
|
+
let loginCompletion;
|
|
109
|
+
if (raw) {
|
|
110
|
+
setCookies.push(clearCookieString(completionName, {
|
|
111
|
+
secure: audience.startsWith("https:"),
|
|
112
|
+
}));
|
|
113
|
+
try {
|
|
114
|
+
const parsed = browserCompletionSchema.safeParse(JSON.parse(decodeURIComponent(atob(raw))));
|
|
115
|
+
if (parsed.success &&
|
|
116
|
+
parsed.data.expires > Date.now() &&
|
|
117
|
+
parsed.data.userId === session?.user.id)
|
|
118
|
+
loginCompletion = parsed.data;
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
121
|
+
}
|
|
122
|
+
return json({ state: session, loginCompletion }, 200, setCookies);
|
|
82
123
|
}
|
|
83
124
|
case "POST": {
|
|
84
125
|
const parsed = installBodySchema.safeParse(await request.json().catch(() => null));
|
|
85
126
|
const body = parsed.success ? parsed.data : null;
|
|
127
|
+
if (body?.action === "login" || body?.action === "account") {
|
|
128
|
+
try {
|
|
129
|
+
return await startBrowserSession(request, audience, issuerUrl, {
|
|
130
|
+
...body,
|
|
131
|
+
action: body.action,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return json({ state: null }, 502);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (body?.action === "guest") {
|
|
139
|
+
const existing = await resolveSessionCore(request.headers.get("Cookie"), audience, issuerUrl);
|
|
140
|
+
if (existing.session)
|
|
141
|
+
return json({ state: existing.session }, 200, existing.setCookies);
|
|
142
|
+
const previousRefresh = parseCookieHeader(request.headers.get("Cookie"))[cookieNamesFor(audience).refresh];
|
|
143
|
+
if (previousRefresh && existing.setCookies.length === 0)
|
|
144
|
+
return json({ state: null }, 502);
|
|
145
|
+
try {
|
|
146
|
+
const headers = new Headers({ "Content-Type": "application/json" });
|
|
147
|
+
for (const name of [
|
|
148
|
+
"cf-connecting-ip",
|
|
149
|
+
"x-forwarded-for",
|
|
150
|
+
"user-agent",
|
|
151
|
+
]) {
|
|
152
|
+
const value = request.headers.get(name);
|
|
153
|
+
if (value)
|
|
154
|
+
headers.set(name, value);
|
|
155
|
+
}
|
|
156
|
+
const result = await fetch(`${issuerUrl}/api/auth/passkey/guest`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers,
|
|
159
|
+
body: JSON.stringify({ audience }),
|
|
160
|
+
});
|
|
161
|
+
if (!result.ok)
|
|
162
|
+
return json({ state: null }, result.status);
|
|
163
|
+
// SAFETY: the configured issuer returns this session token pair, verified below.
|
|
164
|
+
const data = (await result.json());
|
|
165
|
+
const claims = await verifyFlowJWT(data.jwt, { audience, issuerUrl });
|
|
166
|
+
return json({
|
|
167
|
+
state: {
|
|
168
|
+
user: claimsToUser(claims),
|
|
169
|
+
jwt: data.jwt,
|
|
170
|
+
credential: null,
|
|
171
|
+
address: null,
|
|
172
|
+
},
|
|
173
|
+
}, 200, sessionCookies(audience, data.jwt, data.refreshToken));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return json({ state: null }, 502);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
86
179
|
// Reconcile mint: the browser asks its own server to obtain additional-
|
|
87
180
|
// audience sessions (e.g. talk.flow.game) authorized by the PRIMARY
|
|
88
181
|
// refresh cookie, for an embed that booted with no session of its own.
|
|
@@ -137,12 +230,16 @@ export async function handleSessionRequest(request, opts = {}) {
|
|
|
137
230
|
// an unreachable issuer just leaves the lineage to expire on its own.
|
|
138
231
|
if (token) {
|
|
139
232
|
try {
|
|
140
|
-
await fetch(`${issuerUrl}/api/session/revoke`, {
|
|
233
|
+
const response = await fetch(`${issuerUrl}/api/session/revoke`, {
|
|
141
234
|
method: "POST",
|
|
142
235
|
headers: { Authorization: `Bearer ${token}` },
|
|
143
236
|
});
|
|
237
|
+
if (!response.ok)
|
|
238
|
+
return json({ state: null }, 502);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return json({ state: null }, 502);
|
|
144
242
|
}
|
|
145
|
-
catch { }
|
|
146
243
|
}
|
|
147
244
|
const headers = new Headers({ "Cache-Control": "no-store" });
|
|
148
245
|
for (const cookie of clearingCookies(audience)) {
|