@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.
@@ -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
- enterBlocked();
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 || state !== "pending" || redeeming) return;
170
+ if (!config) return;
128
171
  const data = event.data;
129
- if (!data || data.type !== "genex:embed:ticket" || data.v !== PROTOCOL_VERSION) return;
130
- if (typeof data.ticket !== "string" || !data.ticket) return;
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 (state !== "pending" || redeeming) return;
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 ticket = readTicketFromFragment(w);
150
- if (ticket) {
151
- try {
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
- showOverlay("redirecting");
159
- await redirectToAuthorize(w);
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("genex_ticket")) return;
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 readTicketFromFragment(w) {
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
- return new URLSearchParams(hash.slice(1)).get("genex_ticket");
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 url = `${origin}/play/authorize?returnTo=${encodeURIComponent(returnTo)}&slug=${encodeURIComponent(config.slug)}`;
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 (the gate overlay blocks interaction, not execution); await
28
- * waitForAuth() before anything identity-dependent.
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
- * navigates away to the dashboard sign-in — by design, there is no anonymous
32
- * play.
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
- /** Current auth state. Synchronous; starts 'pending', ends 'authenticated' or 'blocked'. */
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
- /** The signed-in user — non-null only once getAuthState() === 'authenticated'. */
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 — which REQUIRES it; there is no
47
- * anonymous save path). undefined unless authenticated.
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 waitForAuth()` — the relay rejects tokenless joins. Read it
57
- * fresh at every connect() call (tokens rotate ~every 10 minutes).
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 user once authenticated; rejects if the session
65
- * ends up blocked. THE gate for connect() and /state calls.
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 a genex ticket, move it out of the URL (so no
75
- * recorder/logger ever sees it) into a stash the standalone flow reads.
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
- } from "./chunk-KXUDNW6P.js";
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-KXUDNW6P.js";
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.2.0",
4
- "description": "Signed-in identity for genex games — embedded (dashboard iframe postMessage handshake) or standalone (sign-in redirect). No anonymous play.",
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",