@genex-ai/embed-sdk 0.6.0 → 0.7.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-5VYRFOFN.js → chunk-BWJ2FVCH.js} +109 -17
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/sentry.js +1 -1
- package/package.json +1 -1
|
@@ -12,9 +12,11 @@ var parentOrigin = null;
|
|
|
12
12
|
var initialized = false;
|
|
13
13
|
var redeeming = false;
|
|
14
14
|
var requestingGuest = false;
|
|
15
|
+
var identityEpoch = 0;
|
|
15
16
|
var handshakeTimeoutMs = 1e4;
|
|
16
17
|
var refreshDelayMs = 10 * 6e4;
|
|
17
18
|
var refreshRetryMs = 6e4;
|
|
19
|
+
var overlayTextDelayMs = 500;
|
|
18
20
|
var handshakeTimer;
|
|
19
21
|
var refreshTimer;
|
|
20
22
|
var messageHandler;
|
|
@@ -419,6 +421,7 @@ async function fetchDashboardOrigins() {
|
|
|
419
421
|
async function redeemTicket(ticket) {
|
|
420
422
|
if (!config || localTestMode || redeeming || state !== "pending" && state !== "guest") return;
|
|
421
423
|
const upgradingFromGuest = state === "guest";
|
|
424
|
+
const epoch = ++identityEpoch;
|
|
422
425
|
redeeming = true;
|
|
423
426
|
try {
|
|
424
427
|
const res = await doFetch(`${config.apiUrl}/api/embed/session`, {
|
|
@@ -426,12 +429,14 @@ async function redeemTicket(ticket) {
|
|
|
426
429
|
headers: { "Content-Type": "application/json" },
|
|
427
430
|
body: JSON.stringify({ ticket })
|
|
428
431
|
});
|
|
432
|
+
if (epoch !== identityEpoch) return;
|
|
429
433
|
if (!res.ok) {
|
|
430
434
|
emit("error", { error: new Error(`ticket redemption failed (${res.status})`) });
|
|
431
435
|
if (!upgradingFromGuest) await handleRedeemFailure();
|
|
432
436
|
return;
|
|
433
437
|
}
|
|
434
438
|
const body = await res.json();
|
|
439
|
+
if (epoch !== identityEpoch) return;
|
|
435
440
|
embedToken = body.embedToken;
|
|
436
441
|
user = body.user;
|
|
437
442
|
colyseusUrls = body.colyseus?.urls;
|
|
@@ -449,6 +454,7 @@ async function redeemTicket(ticket) {
|
|
|
449
454
|
for (const waiter of playerWaiters.splice(0)) waiter.resolve({ user, guest: false });
|
|
450
455
|
emit("authenticated", ctx);
|
|
451
456
|
} catch (error) {
|
|
457
|
+
if (epoch !== identityEpoch) return;
|
|
452
458
|
emit("error", { error });
|
|
453
459
|
if (!upgradingFromGuest) await handleRedeemFailure();
|
|
454
460
|
} finally {
|
|
@@ -458,6 +464,7 @@ async function redeemTicket(ticket) {
|
|
|
458
464
|
async function requestGuestSession() {
|
|
459
465
|
if (!config || localTestMode || requestingGuest || redeeming) return;
|
|
460
466
|
if (state !== "pending" && state !== "guest") return;
|
|
467
|
+
const epoch = ++identityEpoch;
|
|
461
468
|
requestingGuest = true;
|
|
462
469
|
try {
|
|
463
470
|
const res = await doFetch(`${config.apiUrl}/api/embed/guest-session`, {
|
|
@@ -465,17 +472,20 @@ async function requestGuestSession() {
|
|
|
465
472
|
headers: { "Content-Type": "application/json" },
|
|
466
473
|
body: JSON.stringify({ slug: config.slug })
|
|
467
474
|
});
|
|
475
|
+
if (epoch !== identityEpoch) return;
|
|
468
476
|
if (!res.ok) {
|
|
469
477
|
emit("error", { error: new Error(`guest session failed (${res.status})`) });
|
|
470
478
|
enterBlocked();
|
|
471
479
|
return;
|
|
472
480
|
}
|
|
473
481
|
const body = await res.json();
|
|
482
|
+
if (epoch !== identityEpoch) return;
|
|
474
483
|
embedToken = body.embedToken;
|
|
475
484
|
user = body.user;
|
|
476
485
|
colyseusUrls = body.colyseus?.urls;
|
|
477
486
|
enterGuest();
|
|
478
487
|
} catch (error) {
|
|
488
|
+
if (epoch !== identityEpoch) return;
|
|
479
489
|
emit("error", { error });
|
|
480
490
|
enterBlocked();
|
|
481
491
|
} finally {
|
|
@@ -553,18 +563,22 @@ function scheduleRefresh() {
|
|
|
553
563
|
}
|
|
554
564
|
async function refreshToken() {
|
|
555
565
|
if (!config || state !== "authenticated" && state !== "guest" || !embedToken) return;
|
|
566
|
+
const epoch = identityEpoch;
|
|
567
|
+
const tokenAtStart = embedToken;
|
|
556
568
|
let status;
|
|
557
569
|
try {
|
|
558
570
|
const res = await doFetch(`${config.apiUrl}/api/embed/session/refresh`, {
|
|
559
571
|
method: "POST",
|
|
560
|
-
headers: { Authorization: `Bearer ${
|
|
572
|
+
headers: { Authorization: `Bearer ${tokenAtStart}` },
|
|
561
573
|
// Best-effort completion if the tab starts unloading mid-refresh; zero
|
|
562
574
|
// security consequence either way (worst case the token just expires).
|
|
563
575
|
keepalive: true
|
|
564
576
|
});
|
|
565
577
|
status = res.status;
|
|
578
|
+
if (epoch !== identityEpoch || embedToken !== tokenAtStart) return;
|
|
566
579
|
if (res.ok) {
|
|
567
580
|
const body = await res.json();
|
|
581
|
+
if (epoch !== identityEpoch || embedToken !== tokenAtStart) return;
|
|
568
582
|
embedToken = body.embedToken;
|
|
569
583
|
scheduleRefresh();
|
|
570
584
|
return;
|
|
@@ -572,6 +586,7 @@ async function refreshToken() {
|
|
|
572
586
|
} catch {
|
|
573
587
|
status = void 0;
|
|
574
588
|
}
|
|
589
|
+
if (epoch !== identityEpoch || embedToken !== tokenAtStart) return;
|
|
575
590
|
if (status === 401) {
|
|
576
591
|
if (state === "guest") {
|
|
577
592
|
void requestGuestSession();
|
|
@@ -588,6 +603,7 @@ async function refreshToken() {
|
|
|
588
603
|
}
|
|
589
604
|
function enterBlocked() {
|
|
590
605
|
if (state === "blocked") return;
|
|
606
|
+
identityEpoch++;
|
|
591
607
|
state = "blocked";
|
|
592
608
|
user = null;
|
|
593
609
|
embedToken = void 0;
|
|
@@ -604,13 +620,36 @@ function enterBlocked() {
|
|
|
604
620
|
emit("blocked");
|
|
605
621
|
}
|
|
606
622
|
var OVERLAY_TEXT = {
|
|
607
|
-
|
|
608
|
-
//
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
623
|
+
// Delayed (text grace period): most connects finish before the text ever
|
|
624
|
+
// shows — a sub-second text flash reads as a glitch.
|
|
625
|
+
connecting: { title: "Connecting\u2026" },
|
|
626
|
+
// Same copy as `connecting` so the whole standalone bounce (gate ->
|
|
627
|
+
// /play/authorize -> gate) reads as ONE continuous screen. Still neutral:
|
|
628
|
+
// the automatic bounce precedes GUEST play as often as sign-in.
|
|
629
|
+
redirecting: { title: "Connecting\u2026" },
|
|
630
|
+
// Terminal states need the visitor to act — text shows immediately.
|
|
631
|
+
"blocked-standalone": { title: "Sign in to play", button: "Sign in", instant: true },
|
|
632
|
+
"blocked-embedded": { title: "Sign in on the Genex dashboard to continue", instant: true }
|
|
613
633
|
};
|
|
634
|
+
var OVERLAY_FONT_URL = "https://cdn.genex.technology/fonts/Geist-variable.woff2";
|
|
635
|
+
var OVERLAY_FONT_STACK = "'Geist',system-ui,-apple-system,sans-serif";
|
|
636
|
+
var overlayFontRequested = false;
|
|
637
|
+
function ensureOverlayFont(d) {
|
|
638
|
+
if (overlayFontRequested) return;
|
|
639
|
+
overlayFontRequested = true;
|
|
640
|
+
try {
|
|
641
|
+
const FF = win()?.FontFace;
|
|
642
|
+
const fonts = d.fonts;
|
|
643
|
+
if (!FF || !fonts?.add) return;
|
|
644
|
+
const face = new FF("Geist", `url(${OVERLAY_FONT_URL}) format('woff2')`, {
|
|
645
|
+
weight: "100 900",
|
|
646
|
+
display: "swap"
|
|
647
|
+
});
|
|
648
|
+
face.load().then((loaded) => fonts.add(loaded)).catch(() => {
|
|
649
|
+
});
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
}
|
|
614
653
|
function showOverlay(kind) {
|
|
615
654
|
const d = doc();
|
|
616
655
|
if (!d?.createElement) return;
|
|
@@ -620,23 +659,45 @@ function showOverlay(kind) {
|
|
|
620
659
|
} catch {
|
|
621
660
|
}
|
|
622
661
|
}
|
|
623
|
-
function removeOverlay() {
|
|
662
|
+
function removeOverlay(instant = false) {
|
|
624
663
|
try {
|
|
625
|
-
const
|
|
626
|
-
|
|
664
|
+
const el = overlayEl;
|
|
665
|
+
overlayEl = null;
|
|
666
|
+
if (!el) return;
|
|
667
|
+
el.clearTextTimer();
|
|
668
|
+
const root = el.root;
|
|
669
|
+
if (instant || !root.style) {
|
|
670
|
+
root.remove?.();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
root.style.pointerEvents = "none";
|
|
674
|
+
root.style.transition = "opacity 180ms ease";
|
|
675
|
+
root.style.opacity = "0";
|
|
676
|
+
setTimeout(() => {
|
|
677
|
+
try {
|
|
678
|
+
root.remove?.();
|
|
679
|
+
} catch {
|
|
680
|
+
}
|
|
681
|
+
}, 220);
|
|
627
682
|
} catch {
|
|
628
683
|
}
|
|
629
|
-
overlayEl = null;
|
|
630
684
|
}
|
|
631
685
|
function buildOverlay(d) {
|
|
686
|
+
ensureOverlayFont(d);
|
|
632
687
|
const root = d.createElement("div");
|
|
633
688
|
root.setAttribute("data-genex-embed-overlay", "");
|
|
634
|
-
root.style.cssText =
|
|
689
|
+
root.style.cssText = `position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:16px;background:#000;color:#fff;font-family:${OVERLAY_FONT_STACK};text-align:center;pointer-events:auto;user-select:none`;
|
|
635
690
|
const title = d.createElement("div");
|
|
636
|
-
title.style.cssText = "font-size:18px;font-weight:600;padding:0 24px";
|
|
691
|
+
title.style.cssText = "font-size:18px;font-weight:600;padding:0 24px;opacity:0";
|
|
637
692
|
root.appendChild(title);
|
|
638
693
|
const button = d.createElement("button");
|
|
639
|
-
button.style.cssText = "font-size:15px;font-weight:600;padding:10px 28px;border-radius:
|
|
694
|
+
button.style.cssText = "font-size:15px;font-weight:600;padding:10px 28px;border-radius:9px;border:none;cursor:pointer;background:#4b60bf;color:#fff;display:none";
|
|
695
|
+
button.addEventListener("mouseenter", () => {
|
|
696
|
+
button.style.background = "#3f51a8";
|
|
697
|
+
});
|
|
698
|
+
button.addEventListener("mouseleave", () => {
|
|
699
|
+
button.style.background = "#4b60bf";
|
|
700
|
+
});
|
|
640
701
|
button.addEventListener("click", () => {
|
|
641
702
|
const w = win();
|
|
642
703
|
if (!w) return;
|
|
@@ -650,11 +711,32 @@ function buildOverlay(d) {
|
|
|
650
711
|
};
|
|
651
712
|
if (d.body) mount();
|
|
652
713
|
else d.addEventListener?.("DOMContentLoaded", mount);
|
|
714
|
+
let textTimer;
|
|
715
|
+
const clearTextTimer = () => {
|
|
716
|
+
if (textTimer !== void 0) {
|
|
717
|
+
clearTimeout(textTimer);
|
|
718
|
+
textTimer = void 0;
|
|
719
|
+
}
|
|
720
|
+
};
|
|
653
721
|
return {
|
|
654
722
|
root,
|
|
723
|
+
clearTextTimer,
|
|
655
724
|
setContent(kind) {
|
|
656
725
|
const text = OVERLAY_TEXT[kind];
|
|
726
|
+
clearTextTimer();
|
|
657
727
|
title.textContent = text.title;
|
|
728
|
+
if (text.instant || overlayTextDelayMs <= 0) {
|
|
729
|
+
title.style.transition = "";
|
|
730
|
+
title.style.opacity = "1";
|
|
731
|
+
} else {
|
|
732
|
+
title.style.transition = "";
|
|
733
|
+
title.style.opacity = "0";
|
|
734
|
+
textTimer = setTimeout(() => {
|
|
735
|
+
textTimer = void 0;
|
|
736
|
+
title.style.transition = "opacity 200ms ease";
|
|
737
|
+
title.style.opacity = "1";
|
|
738
|
+
}, overlayTextDelayMs);
|
|
739
|
+
}
|
|
658
740
|
button.style.display = text.button ? "inline-block" : "none";
|
|
659
741
|
if (text.button) button.textContent = text.button;
|
|
660
742
|
mount();
|
|
@@ -678,15 +760,22 @@ function removeGuestPopover() {
|
|
|
678
760
|
popoverEl = null;
|
|
679
761
|
}
|
|
680
762
|
function buildGuestPopover(d) {
|
|
763
|
+
ensureOverlayFont(d);
|
|
681
764
|
const root = d.createElement("div");
|
|
682
765
|
root.setAttribute("data-genex-guest-popover", "");
|
|
683
|
-
root.style.cssText =
|
|
766
|
+
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(0,0,0,0.85);color:#fff;font-family:${OVERLAY_FONT_STACK};font-size:13px;box-shadow:0 4px 24px rgba(0,0,0,0.4);pointer-events:auto;user-select:none`;
|
|
684
767
|
const text = d.createElement("span");
|
|
685
768
|
text.textContent = user?.name ? `Playing as ${user.name} \u2014 sign in to save progress` : "Sign in to save progress";
|
|
686
769
|
root.appendChild(text);
|
|
687
770
|
const signIn = d.createElement("button");
|
|
688
771
|
signIn.textContent = "Sign in";
|
|
689
|
-
signIn.style.cssText = "font-size:13px;font-weight:600;padding:6px 16px;border-radius:
|
|
772
|
+
signIn.style.cssText = "font-size:13px;font-weight:600;padding:6px 16px;border-radius:9px;border:none;cursor:pointer;background:#4b60bf;color:#fff";
|
|
773
|
+
signIn.addEventListener("mouseenter", () => {
|
|
774
|
+
signIn.style.background = "#3f51a8";
|
|
775
|
+
});
|
|
776
|
+
signIn.addEventListener("mouseleave", () => {
|
|
777
|
+
signIn.style.background = "#4b60bf";
|
|
778
|
+
});
|
|
690
779
|
signIn.addEventListener("click", () => {
|
|
691
780
|
const w = win();
|
|
692
781
|
if (!w) return;
|
|
@@ -719,8 +808,9 @@ function __resetForTests(overrides) {
|
|
|
719
808
|
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
720
809
|
handshakeTimer = void 0;
|
|
721
810
|
refreshTimer = void 0;
|
|
722
|
-
removeOverlay();
|
|
811
|
+
removeOverlay(true);
|
|
723
812
|
removeGuestPopover();
|
|
813
|
+
overlayFontRequested = false;
|
|
724
814
|
config = null;
|
|
725
815
|
state = "pending";
|
|
726
816
|
user = null;
|
|
@@ -731,6 +821,7 @@ function __resetForTests(overrides) {
|
|
|
731
821
|
initialized = false;
|
|
732
822
|
redeeming = false;
|
|
733
823
|
requestingGuest = false;
|
|
824
|
+
identityEpoch++;
|
|
734
825
|
listeners.clear();
|
|
735
826
|
authWaiters = [];
|
|
736
827
|
playerWaiters = [];
|
|
@@ -741,6 +832,7 @@ function __resetForTests(overrides) {
|
|
|
741
832
|
handshakeTimeoutMs = overrides?.handshakeTimeoutMs ?? 1e4;
|
|
742
833
|
refreshDelayMs = overrides?.refreshDelayMs ?? 10 * 6e4;
|
|
743
834
|
refreshRetryMs = overrides?.refreshRetryMs ?? 6e4;
|
|
835
|
+
overlayTextDelayMs = overrides?.overlayTextDelayMs ?? 500;
|
|
744
836
|
}
|
|
745
837
|
|
|
746
838
|
export {
|
package/dist/index.d.ts
CHANGED
|
@@ -116,7 +116,7 @@ declare function getUser(): EmbedUser | null;
|
|
|
116
116
|
*/
|
|
117
117
|
declare function getEmbedToken(): string | undefined;
|
|
118
118
|
/**
|
|
119
|
-
* The credential for multiplayer: pass as `connect({ ..., auth: getColyseusAuth() })`
|
|
119
|
+
* The credential for multiplayer: pass as `connect({ ..., auth: () => getColyseusAuth() })`
|
|
120
120
|
* AFTER `await waitForPlayer()` — the relay rejects tokenless joins, but
|
|
121
121
|
* accepts guest tokens. Read it fresh at every connect() call (tokens rotate
|
|
122
122
|
* ~every 10 minutes). NEVER log this value.
|
|
@@ -221,6 +221,7 @@ declare function __resetForTests(overrides?: {
|
|
|
221
221
|
handshakeTimeoutMs?: number;
|
|
222
222
|
refreshDelayMs?: number;
|
|
223
223
|
refreshRetryMs?: number;
|
|
224
|
+
overlayTextDelayMs?: number;
|
|
224
225
|
}): void;
|
|
225
226
|
|
|
226
227
|
export { type AuthState, type EmbedConfig, type EmbedEvent, type EmbedUser, type Leaderboard, type LeaderboardEntry, type PlayerStateResult, type SaveStateResult, type SubmitScoreResult, type WorldStateResult, __resetForTests, _stashTicketFromUrl, getAuthState, getColyseusAuth, getColyseusUrls, getEmbedToken, getLeaderboard, getUser, initEmbed, isEmbedded, loadPlayerState, loadWorldState, on, savePlayerState, saveWorldState, submitScore, waitForAuth, waitForPlayer };
|
package/dist/index.js
CHANGED
package/dist/sentry.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/embed-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Player identity + durable game state for genex games — signed-in or guest play, per-player save slots, shared world state, and soft-trust leaderboards.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|