@genex-ai/embed-sdk 0.2.0 → 0.3.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/dist/{chunk-KXUDNW6P.js → chunk-IB4PTSH5.js} +205 -25
- package/dist/index.d.ts +42 -18
- package/dist/index.js +5 -3
- package/dist/sentry.js +5 -1
- package/package.json +2 -2
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
var PROTOCOL_VERSION = 1;
|
|
3
3
|
var RETRY_FLAG = "genex:embed:retry";
|
|
4
|
+
var PREFER_AUTH_FLAG = "genex:prefer-auth";
|
|
5
|
+
var POPOVER_DISMISSED_FLAG = "genex:guest:popover-dismissed";
|
|
4
6
|
var config = null;
|
|
5
7
|
var state = "pending";
|
|
6
8
|
var user = null;
|
|
@@ -9,6 +11,7 @@ var colyseusUrl;
|
|
|
9
11
|
var parentOrigin = null;
|
|
10
12
|
var initialized = false;
|
|
11
13
|
var redeeming = false;
|
|
14
|
+
var requestingGuest = false;
|
|
12
15
|
var handshakeTimeoutMs = 1e4;
|
|
13
16
|
var refreshDelayMs = 10 * 6e4;
|
|
14
17
|
var refreshRetryMs = 6e4;
|
|
@@ -17,7 +20,9 @@ var refreshTimer;
|
|
|
17
20
|
var messageHandler;
|
|
18
21
|
var listeners = /* @__PURE__ */ new Map();
|
|
19
22
|
var authWaiters = [];
|
|
23
|
+
var playerWaiters = [];
|
|
20
24
|
var overlayEl = null;
|
|
25
|
+
var popoverEl = null;
|
|
21
26
|
function win() {
|
|
22
27
|
return globalThis.window;
|
|
23
28
|
}
|
|
@@ -46,6 +51,32 @@ function clearRetryFlag() {
|
|
|
46
51
|
} catch {
|
|
47
52
|
}
|
|
48
53
|
}
|
|
54
|
+
function readPreferAuth() {
|
|
55
|
+
try {
|
|
56
|
+
return win()?.localStorage?.getItem(PREFER_AUTH_FLAG) === "1";
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function writePreferAuth() {
|
|
62
|
+
try {
|
|
63
|
+
win()?.localStorage?.setItem(PREFER_AUTH_FLAG, "1");
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function readPopoverDismissed() {
|
|
68
|
+
try {
|
|
69
|
+
return win()?.sessionStorage?.getItem(POPOVER_DISMISSED_FLAG) === "1";
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function writePopoverDismissed() {
|
|
75
|
+
try {
|
|
76
|
+
win()?.sessionStorage?.setItem(POPOVER_DISMISSED_FLAG, "1");
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
49
80
|
function initEmbed(cfg) {
|
|
50
81
|
const w = win();
|
|
51
82
|
if (!w) return;
|
|
@@ -92,6 +123,15 @@ function waitForAuth() {
|
|
|
92
123
|
authWaiters.push({ resolve, reject });
|
|
93
124
|
});
|
|
94
125
|
}
|
|
126
|
+
function waitForPlayer() {
|
|
127
|
+
if ((state === "authenticated" || state === "guest") && user) {
|
|
128
|
+
return Promise.resolve({ user, guest: state === "guest" });
|
|
129
|
+
}
|
|
130
|
+
if (state === "blocked") return Promise.reject(new Error("genex embed auth: blocked"));
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
playerWaiters.push({ resolve, reject });
|
|
133
|
+
});
|
|
134
|
+
}
|
|
95
135
|
function on(event, cb) {
|
|
96
136
|
let set = listeners.get(event);
|
|
97
137
|
if (!set) {
|
|
@@ -120,21 +160,33 @@ function startEmbeddedHandshake(w) {
|
|
|
120
160
|
w.addEventListener("message", messageHandler);
|
|
121
161
|
w.parent.postMessage({ type: "genex:embed:ready", v: PROTOCOL_VERSION }, "*");
|
|
122
162
|
handshakeTimer = setTimeout(() => {
|
|
123
|
-
|
|
163
|
+
void requestGuestSession();
|
|
124
164
|
}, handshakeTimeoutMs);
|
|
125
165
|
}
|
|
166
|
+
function acceptsTicket() {
|
|
167
|
+
return (state === "pending" || state === "guest") && !redeeming;
|
|
168
|
+
}
|
|
126
169
|
async function handleParentMessage(event) {
|
|
127
|
-
if (!config
|
|
170
|
+
if (!config) return;
|
|
128
171
|
const data = event.data;
|
|
129
|
-
if (!data || data.
|
|
130
|
-
|
|
172
|
+
if (!data || data.v !== PROTOCOL_VERSION) return;
|
|
173
|
+
const isTicket = data.type === "genex:embed:ticket";
|
|
174
|
+
const isGuest = data.type === "genex:embed:guest";
|
|
175
|
+
if (!isTicket && !isGuest) return;
|
|
176
|
+
if (isTicket && (!acceptsTicket() || typeof data.ticket !== "string" || !data.ticket)) return;
|
|
177
|
+
if (isGuest && (state !== "pending" || redeeming || requestingGuest)) return;
|
|
131
178
|
if (!config.dashboardOrigins.includes(event.origin)) {
|
|
132
179
|
const fresh = await fetchDashboardOrigins();
|
|
133
|
-
if (
|
|
180
|
+
if (isTicket && !acceptsTicket()) return;
|
|
181
|
+
if (isGuest && (state !== "pending" || redeeming || requestingGuest)) return;
|
|
134
182
|
if (!fresh.includes(event.origin)) return;
|
|
135
183
|
}
|
|
136
184
|
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
137
185
|
parentOrigin = event.origin;
|
|
186
|
+
if (isGuest) {
|
|
187
|
+
await requestGuestSession();
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
138
190
|
await redeemTicket(data.ticket);
|
|
139
191
|
}
|
|
140
192
|
function postToParent(message, targetOrigin) {
|
|
@@ -146,41 +198,56 @@ function postToParent(message, targetOrigin) {
|
|
|
146
198
|
}
|
|
147
199
|
}
|
|
148
200
|
async function startStandaloneFlow(w) {
|
|
149
|
-
const
|
|
150
|
-
if (ticket) {
|
|
151
|
-
|
|
152
|
-
w.history.replaceState(null, "", w.location.pathname + w.location.search);
|
|
153
|
-
} catch {
|
|
154
|
-
}
|
|
155
|
-
await redeemTicket(ticket);
|
|
201
|
+
const frag = readFragment(w);
|
|
202
|
+
if (frag.ticket) {
|
|
203
|
+
await redeemTicket(frag.ticket);
|
|
156
204
|
return;
|
|
157
205
|
}
|
|
158
|
-
|
|
159
|
-
|
|
206
|
+
if (frag.guest) {
|
|
207
|
+
await requestGuestSession();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (readPreferAuth()) {
|
|
211
|
+
showOverlay("redirecting");
|
|
212
|
+
await redirectToAuthorize(w, { guestOk: true });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
await requestGuestSession();
|
|
160
216
|
}
|
|
161
217
|
var stashedTicketHash = null;
|
|
162
218
|
function _stashTicketFromUrl() {
|
|
163
219
|
const w = win();
|
|
164
220
|
if (!w) return;
|
|
165
221
|
const hash = w.location.hash;
|
|
166
|
-
if (!hash || !hash.includes("
|
|
222
|
+
if (!hash || !hash.includes("genex_")) return;
|
|
167
223
|
stashedTicketHash = hash;
|
|
168
224
|
try {
|
|
169
225
|
w.history.replaceState(null, "", w.location.pathname + w.location.search);
|
|
170
226
|
} catch {
|
|
171
227
|
}
|
|
172
228
|
}
|
|
173
|
-
function
|
|
229
|
+
function readFragment(w) {
|
|
174
230
|
const hash = stashedTicketHash ?? w.location.hash;
|
|
175
231
|
stashedTicketHash = null;
|
|
176
|
-
if (!hash || hash.length < 2) return null;
|
|
232
|
+
if (!hash || hash.length < 2) return { ticket: null, guest: false };
|
|
233
|
+
let ticket = null;
|
|
234
|
+
let guest = false;
|
|
177
235
|
try {
|
|
178
|
-
|
|
236
|
+
const params = new URLSearchParams(hash.slice(1));
|
|
237
|
+
ticket = params.get("genex_ticket");
|
|
238
|
+
guest = params.get("genex_guest") === "1";
|
|
179
239
|
} catch {
|
|
180
|
-
return null;
|
|
240
|
+
return { ticket: null, guest: false };
|
|
181
241
|
}
|
|
242
|
+
if (ticket || guest) {
|
|
243
|
+
try {
|
|
244
|
+
w.history.replaceState(null, "", w.location.pathname + w.location.search);
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return { ticket, guest };
|
|
182
249
|
}
|
|
183
|
-
async function redirectToAuthorize(w) {
|
|
250
|
+
async function redirectToAuthorize(w, opts) {
|
|
184
251
|
if (!config) return;
|
|
185
252
|
const origins = await fetchDashboardOrigins();
|
|
186
253
|
const origin = origins[0] ?? config.dashboardOrigins[0];
|
|
@@ -189,7 +256,8 @@ async function redirectToAuthorize(w) {
|
|
|
189
256
|
return;
|
|
190
257
|
}
|
|
191
258
|
const returnTo = w.location.origin + w.location.pathname;
|
|
192
|
-
const
|
|
259
|
+
const guestParam = opts?.guestOk ? "&guest=ok" : "";
|
|
260
|
+
const url = `${origin}/play/authorize?returnTo=${encodeURIComponent(returnTo)}&slug=${encodeURIComponent(config.slug)}${guestParam}`;
|
|
193
261
|
w.location.replace(url);
|
|
194
262
|
}
|
|
195
263
|
async function fetchDashboardOrigins() {
|
|
@@ -206,7 +274,8 @@ async function fetchDashboardOrigins() {
|
|
|
206
274
|
return config.dashboardOrigins;
|
|
207
275
|
}
|
|
208
276
|
async function redeemTicket(ticket) {
|
|
209
|
-
if (!config || redeeming || state !== "pending") return;
|
|
277
|
+
if (!config || redeeming || state !== "pending" && state !== "guest") return;
|
|
278
|
+
const upgradingFromGuest = state === "guest";
|
|
210
279
|
redeeming = true;
|
|
211
280
|
try {
|
|
212
281
|
const res = await doFetch(`${config.apiUrl}/api/embed/session`, {
|
|
@@ -216,7 +285,7 @@ async function redeemTicket(ticket) {
|
|
|
216
285
|
});
|
|
217
286
|
if (!res.ok) {
|
|
218
287
|
emit("error", { error: new Error(`ticket redemption failed (${res.status})`) });
|
|
219
|
-
await handleRedeemFailure();
|
|
288
|
+
if (!upgradingFromGuest) await handleRedeemFailure();
|
|
220
289
|
return;
|
|
221
290
|
}
|
|
222
291
|
const body = await res.json();
|
|
@@ -226,21 +295,71 @@ async function redeemTicket(ticket) {
|
|
|
226
295
|
void colyseusUrl;
|
|
227
296
|
state = "authenticated";
|
|
228
297
|
clearRetryFlag();
|
|
298
|
+
writePreferAuth();
|
|
229
299
|
removeOverlay();
|
|
300
|
+
removeGuestPopover();
|
|
230
301
|
scheduleRefresh();
|
|
231
302
|
if (isEmbedded() && parentOrigin) {
|
|
232
303
|
postToParent({ type: "genex:embed:authenticated", v: PROTOCOL_VERSION }, parentOrigin);
|
|
233
304
|
}
|
|
234
305
|
const ctx = { user };
|
|
235
306
|
for (const waiter of authWaiters.splice(0)) waiter.resolve(ctx);
|
|
307
|
+
for (const waiter of playerWaiters.splice(0)) waiter.resolve({ user, guest: false });
|
|
236
308
|
emit("authenticated", ctx);
|
|
237
309
|
} catch (error) {
|
|
238
310
|
emit("error", { error });
|
|
239
|
-
await handleRedeemFailure();
|
|
311
|
+
if (!upgradingFromGuest) await handleRedeemFailure();
|
|
240
312
|
} finally {
|
|
241
313
|
redeeming = false;
|
|
242
314
|
}
|
|
243
315
|
}
|
|
316
|
+
async function requestGuestSession() {
|
|
317
|
+
if (!config || requestingGuest || redeeming) return;
|
|
318
|
+
if (state !== "pending" && state !== "guest") return;
|
|
319
|
+
requestingGuest = true;
|
|
320
|
+
try {
|
|
321
|
+
const res = await doFetch(`${config.apiUrl}/api/embed/guest-session`, {
|
|
322
|
+
method: "POST",
|
|
323
|
+
headers: { "Content-Type": "application/json" },
|
|
324
|
+
body: JSON.stringify({ slug: config.slug })
|
|
325
|
+
});
|
|
326
|
+
if (!res.ok) {
|
|
327
|
+
emit("error", { error: new Error(`guest session failed (${res.status})`) });
|
|
328
|
+
enterBlocked();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const body = await res.json();
|
|
332
|
+
embedToken = body.embedToken;
|
|
333
|
+
user = body.user;
|
|
334
|
+
colyseusUrl = body.colyseus?.url;
|
|
335
|
+
enterGuest();
|
|
336
|
+
} catch (error) {
|
|
337
|
+
emit("error", { error });
|
|
338
|
+
enterBlocked();
|
|
339
|
+
} finally {
|
|
340
|
+
requestingGuest = false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function enterGuest() {
|
|
344
|
+
const firstEntry = state !== "guest";
|
|
345
|
+
state = "guest";
|
|
346
|
+
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
347
|
+
removeOverlay();
|
|
348
|
+
scheduleRefresh();
|
|
349
|
+
if (isEmbedded()) {
|
|
350
|
+
postToParent(
|
|
351
|
+
{ type: "genex:embed:authenticated", v: PROTOCOL_VERSION, guest: true },
|
|
352
|
+
parentOrigin ?? "*"
|
|
353
|
+
);
|
|
354
|
+
} else if (firstEntry && !readPopoverDismissed()) {
|
|
355
|
+
showGuestPopover();
|
|
356
|
+
}
|
|
357
|
+
if (user) {
|
|
358
|
+
const ctx = { user };
|
|
359
|
+
for (const waiter of playerWaiters.splice(0)) waiter.resolve({ user, guest: true });
|
|
360
|
+
emit("guest", ctx);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
244
363
|
async function handleRedeemFailure() {
|
|
245
364
|
const w = win();
|
|
246
365
|
if (w && !isEmbedded() && !readRetryFlag()) {
|
|
@@ -258,7 +377,7 @@ function scheduleRefresh() {
|
|
|
258
377
|
}, refreshDelayMs);
|
|
259
378
|
}
|
|
260
379
|
async function refreshToken() {
|
|
261
|
-
if (!config || state !== "authenticated" || !embedToken) return;
|
|
380
|
+
if (!config || state !== "authenticated" && state !== "guest" || !embedToken) return;
|
|
262
381
|
let status;
|
|
263
382
|
try {
|
|
264
383
|
const res = await doFetch(`${config.apiUrl}/api/embed/session/refresh`, {
|
|
@@ -279,6 +398,10 @@ async function refreshToken() {
|
|
|
279
398
|
status = void 0;
|
|
280
399
|
}
|
|
281
400
|
if (status === 401) {
|
|
401
|
+
if (state === "guest") {
|
|
402
|
+
void requestGuestSession();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
282
405
|
emit("error", { error: new Error("embed session expired") });
|
|
283
406
|
enterBlocked();
|
|
284
407
|
return;
|
|
@@ -295,12 +418,14 @@ function enterBlocked() {
|
|
|
295
418
|
embedToken = void 0;
|
|
296
419
|
if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
|
|
297
420
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
421
|
+
removeGuestPopover();
|
|
298
422
|
showOverlay(isEmbedded() ? "blocked-embedded" : "blocked-standalone");
|
|
299
423
|
if (isEmbedded()) {
|
|
300
424
|
postToParent({ type: "genex:embed:blocked", v: PROTOCOL_VERSION }, parentOrigin ?? "*");
|
|
301
425
|
}
|
|
302
426
|
const err = new Error("genex embed auth: blocked");
|
|
303
427
|
for (const waiter of authWaiters.splice(0)) waiter.reject(err);
|
|
428
|
+
for (const waiter of playerWaiters.splice(0)) waiter.reject(err);
|
|
304
429
|
emit("blocked");
|
|
305
430
|
}
|
|
306
431
|
var OVERLAY_TEXT = {
|
|
@@ -359,6 +484,56 @@ function buildOverlay(d) {
|
|
|
359
484
|
}
|
|
360
485
|
};
|
|
361
486
|
}
|
|
487
|
+
function showGuestPopover() {
|
|
488
|
+
const d = doc();
|
|
489
|
+
if (!d?.createElement || popoverEl) return;
|
|
490
|
+
try {
|
|
491
|
+
popoverEl = buildGuestPopover(d);
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function removeGuestPopover() {
|
|
496
|
+
try {
|
|
497
|
+
const root = popoverEl?.root;
|
|
498
|
+
root?.remove?.();
|
|
499
|
+
} catch {
|
|
500
|
+
}
|
|
501
|
+
popoverEl = null;
|
|
502
|
+
}
|
|
503
|
+
function buildGuestPopover(d) {
|
|
504
|
+
const root = d.createElement("div");
|
|
505
|
+
root.setAttribute("data-genex-guest-popover", "");
|
|
506
|
+
root.style.cssText = "position:fixed;top:12px;right:12px;z-index:2147483646;display:flex;align-items:center;gap:12px;padding:10px 12px 10px 16px;border-radius:12px;background:rgba(8,10,20,0.85);color:#fff;font-family:system-ui,-apple-system,sans-serif;font-size:13px;box-shadow:0 4px 24px rgba(0,0,0,0.4);pointer-events:auto;user-select:none";
|
|
507
|
+
const text = d.createElement("span");
|
|
508
|
+
text.textContent = user?.name ? `Playing as ${user.name} \u2014 sign in to save progress` : "Sign in to save progress";
|
|
509
|
+
root.appendChild(text);
|
|
510
|
+
const signIn = d.createElement("button");
|
|
511
|
+
signIn.textContent = "Sign in";
|
|
512
|
+
signIn.style.cssText = "font-size:13px;font-weight:600;padding:6px 16px;border-radius:999px;border:none;cursor:pointer;background:#fff;color:#111";
|
|
513
|
+
signIn.addEventListener("click", () => {
|
|
514
|
+
const w = win();
|
|
515
|
+
if (!w) return;
|
|
516
|
+
signIn.disabled = true;
|
|
517
|
+
clearRetryFlag();
|
|
518
|
+
void redirectToAuthorize(w);
|
|
519
|
+
});
|
|
520
|
+
root.appendChild(signIn);
|
|
521
|
+
const dismiss = d.createElement("button");
|
|
522
|
+
dismiss.textContent = "\xD7";
|
|
523
|
+
dismiss.setAttribute("aria-label", "Dismiss");
|
|
524
|
+
dismiss.style.cssText = "font-size:16px;line-height:1;padding:6px 8px;border:none;cursor:pointer;background:transparent;color:rgba(255,255,255,0.6)";
|
|
525
|
+
dismiss.addEventListener("click", () => {
|
|
526
|
+
writePopoverDismissed();
|
|
527
|
+
removeGuestPopover();
|
|
528
|
+
});
|
|
529
|
+
root.appendChild(dismiss);
|
|
530
|
+
const mount = () => {
|
|
531
|
+
if (d.body && !root.isConnected) d.body.appendChild(root);
|
|
532
|
+
};
|
|
533
|
+
if (d.body) mount();
|
|
534
|
+
else d.addEventListener?.("DOMContentLoaded", mount);
|
|
535
|
+
return { root };
|
|
536
|
+
}
|
|
362
537
|
function __resetForTests(overrides) {
|
|
363
538
|
const w = win();
|
|
364
539
|
if (w && messageHandler) w.removeEventListener("message", messageHandler);
|
|
@@ -368,6 +543,7 @@ function __resetForTests(overrides) {
|
|
|
368
543
|
handshakeTimer = void 0;
|
|
369
544
|
refreshTimer = void 0;
|
|
370
545
|
removeOverlay();
|
|
546
|
+
removeGuestPopover();
|
|
371
547
|
config = null;
|
|
372
548
|
state = "pending";
|
|
373
549
|
user = null;
|
|
@@ -376,8 +552,11 @@ function __resetForTests(overrides) {
|
|
|
376
552
|
parentOrigin = null;
|
|
377
553
|
initialized = false;
|
|
378
554
|
redeeming = false;
|
|
555
|
+
requestingGuest = false;
|
|
379
556
|
listeners.clear();
|
|
380
557
|
authWaiters = [];
|
|
558
|
+
playerWaiters = [];
|
|
559
|
+
stashedTicketHash = null;
|
|
381
560
|
handshakeTimeoutMs = overrides?.handshakeTimeoutMs ?? 1e4;
|
|
382
561
|
refreshDelayMs = overrides?.refreshDelayMs ?? 10 * 6e4;
|
|
383
562
|
refreshRetryMs = overrides?.refreshRetryMs ?? 6e4;
|
|
@@ -391,6 +570,7 @@ export {
|
|
|
391
570
|
getEmbedToken,
|
|
392
571
|
getColyseusAuth,
|
|
393
572
|
waitForAuth,
|
|
573
|
+
waitForPlayer,
|
|
394
574
|
on,
|
|
395
575
|
_stashTicketFromUrl,
|
|
396
576
|
__resetForTests
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ interface EmbedUser {
|
|
|
15
15
|
name: string;
|
|
16
16
|
image?: string;
|
|
17
17
|
}
|
|
18
|
-
type AuthState = 'pending' | 'authenticated' | 'blocked';
|
|
19
|
-
type EmbedEvent = 'authenticated' | 'blocked' | 'error';
|
|
18
|
+
type AuthState = 'pending' | 'authenticated' | 'guest' | 'blocked';
|
|
19
|
+
type EmbedEvent = 'authenticated' | 'guest' | 'blocked' | 'error';
|
|
20
20
|
interface EventContext {
|
|
21
21
|
user?: EmbedUser;
|
|
22
22
|
error?: unknown;
|
|
@@ -24,12 +24,14 @@ interface EventContext {
|
|
|
24
24
|
/**
|
|
25
25
|
* Initialize embed auth. Call ONCE, first thing in the boot sequence — before
|
|
26
26
|
* connect() and before any /state call. Scene/asset boot may proceed
|
|
27
|
-
* immediately (
|
|
28
|
-
* waitForAuth()
|
|
27
|
+
* immediately; await waitForPlayer() for anything that needs a player
|
|
28
|
+
* identity (multiplayer connect, name UI) and waitForAuth() for anything that
|
|
29
|
+
* needs a signed-in ACCOUNT (/state saves).
|
|
29
30
|
*
|
|
30
31
|
* Standalone (not in an iframe) with no return-trip ticket present, this
|
|
31
|
-
*
|
|
32
|
-
*
|
|
32
|
+
* mints a guest session in place (published games) — sign-in happens on the
|
|
33
|
+
* guest popover's own button, or automatically for returning account holders
|
|
34
|
+
* (prefer-auth hint).
|
|
33
35
|
*/
|
|
34
36
|
declare function initEmbed(cfg: EmbedConfig): void;
|
|
35
37
|
/**
|
|
@@ -37,14 +39,23 @@ declare function initEmbed(cfg: EmbedConfig): void;
|
|
|
37
39
|
* "is authenticated". True even before the handshake resolves.
|
|
38
40
|
*/
|
|
39
41
|
declare function isEmbedded(): boolean;
|
|
40
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Current auth state. Synchronous; starts 'pending', ends 'authenticated',
|
|
44
|
+
* 'guest' (accountless guest play) or 'blocked'. A 'guest' session may still
|
|
45
|
+
* upgrade to 'authenticated' live (dashboard sign-in mid-play).
|
|
46
|
+
*/
|
|
41
47
|
declare function getAuthState(): AuthState;
|
|
42
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The current player — non-null once 'authenticated' OR 'guest'. Guests have
|
|
50
|
+
* an `id` prefixed `guest:` and a server-assigned name like "Guest-1234".
|
|
51
|
+
*/
|
|
43
52
|
declare function getUser(): EmbedUser | null;
|
|
44
53
|
/**
|
|
45
54
|
* The raw scoped embedToken, for Authorization: Bearer calls the game makes
|
|
46
|
-
* itself (GET/PUT /api/projects/:slug/state
|
|
47
|
-
*
|
|
55
|
+
* itself (GET/PUT /api/projects/:slug/state). undefined unless authenticated
|
|
56
|
+
* or guest. In GUEST state this is the guest token: the relay accepts it, but
|
|
57
|
+
* /state rejects it with 403 { error: "guest_no_save" } — gate saves on
|
|
58
|
+
* waitForAuth(), not on this being defined.
|
|
48
59
|
*
|
|
49
60
|
* NEVER log this value or pass it to crash-reporting/analytics breadcrumb
|
|
50
61
|
* capture. It is bounded (15 minutes, one project, one scope), but third-party
|
|
@@ -53,26 +64,39 @@ declare function getUser(): EmbedUser | null;
|
|
|
53
64
|
declare function getEmbedToken(): string | undefined;
|
|
54
65
|
/**
|
|
55
66
|
* The credential for multiplayer: pass as `connect({ ..., auth: getColyseusAuth() })`
|
|
56
|
-
* AFTER `await
|
|
57
|
-
* fresh at every connect() call (tokens rotate
|
|
58
|
-
* NEVER log this value.
|
|
67
|
+
* AFTER `await waitForPlayer()` — the relay rejects tokenless joins, but
|
|
68
|
+
* accepts guest tokens. Read it fresh at every connect() call (tokens rotate
|
|
69
|
+
* ~every 10 minutes). NEVER log this value.
|
|
59
70
|
*/
|
|
60
71
|
declare function getColyseusAuth(): {
|
|
61
72
|
embedToken: string;
|
|
62
73
|
} | undefined;
|
|
63
74
|
/**
|
|
64
|
-
* Resolves with the signed-in
|
|
65
|
-
* ends up blocked. THE gate for
|
|
75
|
+
* Resolves with the signed-in ACCOUNT once authenticated; rejects if the
|
|
76
|
+
* session ends up blocked. THE gate for /state saves and any account-bound
|
|
77
|
+
* feature. In a guest session this stays PENDING (it is not rejected —
|
|
78
|
+
* a live upgrade resolves it), so never gate scene boot or connect() on it:
|
|
79
|
+
* use waitForPlayer() for those.
|
|
66
80
|
*/
|
|
67
81
|
declare function waitForAuth(): Promise<{
|
|
68
82
|
user: EmbedUser;
|
|
69
83
|
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Resolves with the current player — guest OR signed-in — as soon as either
|
|
86
|
+
* identity exists; rejects only if the session ends up blocked. THE gate for
|
|
87
|
+
* multiplayer connect() and player-name UI.
|
|
88
|
+
*/
|
|
89
|
+
declare function waitForPlayer(): Promise<{
|
|
90
|
+
user: EmbedUser;
|
|
91
|
+
guest: boolean;
|
|
92
|
+
}>;
|
|
70
93
|
/** Subscribe to auth lifecycle events. Returns an unsubscribe function. */
|
|
71
94
|
declare function on(event: EmbedEvent, cb: (ctx?: EventContext) => void): () => void;
|
|
72
95
|
/**
|
|
73
96
|
* @internal — for ./sentry.ts ONLY, not public API (games never call this).
|
|
74
|
-
* If the URL fragment carries
|
|
75
|
-
*
|
|
97
|
+
* If the URL fragment carries any genex marker (`genex_ticket` — a secret —
|
|
98
|
+
* or the inert `genex_guest`), move it out of the URL (so no recorder/logger
|
|
99
|
+
* ever sees it) into a stash the standalone flow reads.
|
|
76
100
|
*/
|
|
77
101
|
declare function _stashTicketFromUrl(): void;
|
|
78
102
|
declare function __resetForTests(overrides?: {
|
|
@@ -81,4 +105,4 @@ declare function __resetForTests(overrides?: {
|
|
|
81
105
|
refreshRetryMs?: number;
|
|
82
106
|
}): void;
|
|
83
107
|
|
|
84
|
-
export { type AuthState, type EmbedConfig, type EmbedEvent, type EmbedUser, __resetForTests, _stashTicketFromUrl, getAuthState, getColyseusAuth, getEmbedToken, getUser, initEmbed, isEmbedded, on, waitForAuth };
|
|
108
|
+
export { type AuthState, type EmbedConfig, type EmbedEvent, type EmbedUser, __resetForTests, _stashTicketFromUrl, getAuthState, getColyseusAuth, getEmbedToken, getUser, initEmbed, isEmbedded, on, waitForAuth, waitForPlayer };
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
initEmbed,
|
|
9
9
|
isEmbedded,
|
|
10
10
|
on,
|
|
11
|
-
waitForAuth
|
|
12
|
-
|
|
11
|
+
waitForAuth,
|
|
12
|
+
waitForPlayer
|
|
13
|
+
} from "./chunk-IB4PTSH5.js";
|
|
13
14
|
export {
|
|
14
15
|
__resetForTests,
|
|
15
16
|
_stashTicketFromUrl,
|
|
@@ -20,5 +21,6 @@ export {
|
|
|
20
21
|
initEmbed,
|
|
21
22
|
isEmbedded,
|
|
22
23
|
on,
|
|
23
|
-
waitForAuth
|
|
24
|
+
waitForAuth,
|
|
25
|
+
waitForPlayer
|
|
24
26
|
};
|
package/dist/sentry.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
_stashTicketFromUrl,
|
|
3
3
|
getUser,
|
|
4
4
|
on
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-IB4PTSH5.js";
|
|
6
6
|
|
|
7
7
|
// src/sentry.ts
|
|
8
8
|
import * as Sentry from "@sentry/browser";
|
|
@@ -94,6 +94,10 @@ function initGameSentry(opts) {
|
|
|
94
94
|
const u = ctx?.user ?? getUser();
|
|
95
95
|
if (u) Sentry.setUser({ id: u.id, username: u.name });
|
|
96
96
|
});
|
|
97
|
+
on("guest", (ctx) => {
|
|
98
|
+
const u = ctx?.user ?? getUser();
|
|
99
|
+
if (u) Sentry.setUser({ id: u.id, username: u.name });
|
|
100
|
+
});
|
|
97
101
|
on("blocked", () => Sentry.setUser(null));
|
|
98
102
|
on("error", (ctx) => {
|
|
99
103
|
if (ctx?.error) Sentry.captureException(ctx.error);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/embed-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Player identity for genex games — signed-in (dashboard iframe postMessage handshake or sign-in redirect) or guest (accountless play for published games, sign in to save).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|