@genex-ai/embed-sdk 0.16.0 → 0.17.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.
@@ -341,7 +341,7 @@ var RETRY_FLAG = "genex:embed:retry";
341
341
  var POPOVER_DISMISSED_FLAG = "genex:guest:popover-dismissed";
342
342
  var LOCAL_AUTH_FLAG = "genex:embed:local-auth";
343
343
  var PLAYER_ID_KEY = "genex:player";
344
- var SDK_VERSION = "0.16.0";
344
+ var SDK_VERSION = "0.17.0";
345
345
  var config = null;
346
346
  var state = "pending";
347
347
  var user = null;
@@ -366,6 +366,8 @@ var nativeReauthTimeoutMs = 3e4;
366
366
  var heartbeatIntervalMs = 3e4;
367
367
  var handshakeTimer;
368
368
  var refreshTimer;
369
+ var unattendedWaiterTimer;
370
+ var earlyWaiterWarned = false;
369
371
  var messageHandler;
370
372
  var listeners = /* @__PURE__ */ new Map();
371
373
  var authWaiters = [];
@@ -382,8 +384,16 @@ function win() {
382
384
  function doc() {
383
385
  return globalThis.document;
384
386
  }
385
- function doFetch(input, init) {
386
- return globalThis.fetch(input, init);
387
+ function doFetch(input, init, timeoutMs) {
388
+ let signal;
389
+ if (timeoutMs !== void 0) {
390
+ try {
391
+ const ctor = globalThis.AbortSignal;
392
+ if (ctor && typeof ctor.timeout === "function") signal = ctor.timeout(timeoutMs);
393
+ } catch {
394
+ }
395
+ }
396
+ return globalThis.fetch(input, signal ? { ...init, signal } : init);
387
397
  }
388
398
  function readRetryFlag() {
389
399
  try {
@@ -449,6 +459,10 @@ function initEmbed(cfg2) {
449
459
  if (!w) return;
450
460
  if (initialized) return;
451
461
  initialized = true;
462
+ if (unattendedWaiterTimer !== void 0) {
463
+ clearTimeout(unattendedWaiterTimer);
464
+ unattendedWaiterTimer = void 0;
465
+ }
452
466
  config = {
453
467
  slug: cfg2.slug,
454
468
  apiUrl: cfg2.apiUrl.replace(/\/$/, ""),
@@ -506,6 +520,7 @@ function getColyseusUrls() {
506
520
  function waitForAuth() {
507
521
  if (state === "authenticated" && user) return Promise.resolve({ user });
508
522
  if (state === "blocked") return Promise.reject(new Error("genex embed auth: blocked"));
523
+ noteWaiterBeforeInit("waitForAuth");
509
524
  return new Promise((resolve, reject) => {
510
525
  authWaiters.push({ resolve, reject });
511
526
  });
@@ -515,10 +530,29 @@ function waitForPlayer() {
515
530
  return Promise.resolve({ user, guest: state === "guest" });
516
531
  }
517
532
  if (state === "blocked") return Promise.reject(new Error("genex embed auth: blocked"));
533
+ noteWaiterBeforeInit("waitForPlayer");
518
534
  return new Promise((resolve, reject) => {
519
535
  playerWaiters.push({ resolve, reject });
520
536
  });
521
537
  }
538
+ function noteWaiterBeforeInit(fn) {
539
+ if (initialized) return;
540
+ if (!earlyWaiterWarned) {
541
+ earlyWaiterWarned = true;
542
+ try {
543
+ console.error(
544
+ `[genex] ${fn}() called before initEmbed() \u2014 identity can never resolve and the game will sit black. Call initEmbed({ slug, apiUrl, dashboardOrigins }) first (genex-threejs-embed-auth), and draw a frame before you await identity.`
545
+ );
546
+ } catch {
547
+ }
548
+ }
549
+ if (unattendedWaiterTimer === void 0) {
550
+ unattendedWaiterTimer = setTimeout(() => {
551
+ unattendedWaiterTimer = void 0;
552
+ if (!initialized) enterBlocked("no-identity");
553
+ }, handshakeTimeoutMs);
554
+ }
555
+ }
522
556
  function on(event, cb) {
523
557
  let set = listeners.get(event);
524
558
  if (!set) {
@@ -841,16 +875,25 @@ function postToParent(message, targetOrigin) {
841
875
  async function startStandaloneFlow(w) {
842
876
  const frag = readFragment(w);
843
877
  if (frag.ticket) {
878
+ armStandaloneWatchdog();
844
879
  await redeemTicket(frag.ticket);
845
880
  return;
846
881
  }
847
882
  if (frag.guest) {
883
+ armStandaloneWatchdog();
848
884
  await requestGuestSession();
849
885
  return;
850
886
  }
851
887
  showOverlay("redirecting");
852
888
  await redirectToAuthorize(w, { guestOk: true });
853
889
  }
890
+ function armStandaloneWatchdog() {
891
+ if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
892
+ handshakeTimer = setTimeout(() => {
893
+ handshakeTimer = void 0;
894
+ if (state === "pending") enterBlocked("no-identity");
895
+ }, handshakeTimeoutMs);
896
+ }
854
897
  var stashedTicketHash = null;
855
898
  function _stashTicketFromUrl() {
856
899
  const w = win();
@@ -929,11 +972,15 @@ async function redeemTicket(ticket) {
929
972
  const epoch = ++identityEpoch;
930
973
  redeeming = true;
931
974
  try {
932
- const res = await doFetch(`${config.apiUrl}/api/embed/session`, {
933
- method: "POST",
934
- headers: { "Content-Type": "application/json" },
935
- body: JSON.stringify({ ticket })
936
- });
975
+ const res = await doFetch(
976
+ `${config.apiUrl}/api/embed/session`,
977
+ {
978
+ method: "POST",
979
+ headers: { "Content-Type": "application/json" },
980
+ body: JSON.stringify({ ticket })
981
+ },
982
+ handshakeTimeoutMs
983
+ );
937
984
  if (epoch !== identityEpoch) return;
938
985
  if (!res.ok) {
939
986
  emit("error", { error: new Error(`ticket redemption failed (${res.status})`) });
@@ -946,6 +993,8 @@ async function redeemTicket(ticket) {
946
993
  user = body.user;
947
994
  colyseusUrls = body.colyseus?.urls;
948
995
  state = "authenticated";
996
+ if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
997
+ handshakeTimer = void 0;
949
998
  clearRetryFlag();
950
999
  removeOverlay();
951
1000
  removeGuestPopover();
@@ -984,7 +1033,7 @@ async function requestGuestSession() {
984
1033
  // guest id and the token is unchanged — this just lets the play event
985
1034
  // key on something that survives the tab.
986
1035
  body: JSON.stringify({ slug: config.slug, playerId: playerId() })
987
- });
1036
+ }, handshakeTimeoutMs);
988
1037
  if (epoch !== identityEpoch) return;
989
1038
  if (!res.ok) {
990
1039
  emit("error", { error: new Error(`guest session failed (${res.status})`) });
@@ -1076,6 +1125,8 @@ function enterLocalTestMode(w) {
1076
1125
  }
1077
1126
  async function handleRedeemFailure() {
1078
1127
  const w = win();
1128
+ if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
1129
+ handshakeTimer = void 0;
1079
1130
  if (inNativeMode()) {
1080
1131
  clearNativeReauth();
1081
1132
  postNativeError("ticket-rejected");
@@ -1469,8 +1520,11 @@ function __resetForTests(overrides) {
1469
1520
  messageHandler = void 0;
1470
1521
  if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
1471
1522
  if (refreshTimer !== void 0) clearTimeout(refreshTimer);
1523
+ if (unattendedWaiterTimer !== void 0) clearTimeout(unattendedWaiterTimer);
1472
1524
  handshakeTimer = void 0;
1473
1525
  refreshTimer = void 0;
1526
+ unattendedWaiterTimer = void 0;
1527
+ earlyWaiterWarned = false;
1474
1528
  stopNativeTimers();
1475
1529
  if (w) delete w[NATIVE_RECEIVER];
1476
1530
  nativeEntry = null;
package/dist/index.d.ts CHANGED
@@ -258,7 +258,9 @@ declare function getColyseusUrls(): string[];
258
258
  * session ends up blocked. THE gate for /state saves and any account-bound
259
259
  * feature. In a guest session this stays PENDING (it is not rejected —
260
260
  * a live upgrade resolves it), so never gate scene boot or connect() on it:
261
- * use waitForPlayer() for those.
261
+ * use waitForPlayer() for those. Call initEmbed() FIRST: parked before it,
262
+ * this logs one console line and, unattended, ends `blocked` after the
263
+ * handshake budget instead of hanging.
262
264
  */
263
265
  declare function waitForAuth(): Promise<{
264
266
  user: EmbedUser;
@@ -266,7 +268,10 @@ declare function waitForAuth(): Promise<{
266
268
  /**
267
269
  * Resolves with the current player — guest OR signed-in — as soon as either
268
270
  * identity exists; rejects only if the session ends up blocked. THE gate for
269
- * multiplayer connect() and player-name UI.
271
+ * multiplayer connect() and player-name UI. Call initEmbed() FIRST — and draw
272
+ * a frame before awaiting this: parked before init, it logs one console line
273
+ * and, unattended, ends `blocked` after the handshake budget instead of
274
+ * leaving the page black.
270
275
  */
271
276
  declare function waitForPlayer(): Promise<{
272
277
  user: EmbedUser;
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  submitScore,
22
22
  waitForAuth,
23
23
  waitForPlayer
24
- } from "./chunk-ALBFRX3L.js";
24
+ } from "./chunk-DNLXNEPL.js";
25
25
  export {
26
26
  __resetForTests,
27
27
  _stashTicketFromUrl,
package/dist/sentry.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  _stashTicketFromUrl,
3
3
  getUser,
4
4
  on
5
- } from "./chunk-ALBFRX3L.js";
5
+ } from "./chunk-DNLXNEPL.js";
6
6
 
7
7
  // src/sentry.ts
8
8
  import * as Sentry from "@sentry/browser";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/embed-sdk",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Player identity + durable game state for genex games \u2014 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",