@genex-ai/embed-sdk 0.2.0 → 0.3.1

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,7 @@
1
1
  // src/index.ts
2
2
  var PROTOCOL_VERSION = 1;
3
3
  var RETRY_FLAG = "genex:embed:retry";
4
+ var POPOVER_DISMISSED_FLAG = "genex:guest:popover-dismissed";
4
5
  var config = null;
5
6
  var state = "pending";
6
7
  var user = null;
@@ -9,6 +10,7 @@ var colyseusUrl;
9
10
  var parentOrigin = null;
10
11
  var initialized = false;
11
12
  var redeeming = false;
13
+ var requestingGuest = false;
12
14
  var handshakeTimeoutMs = 1e4;
13
15
  var refreshDelayMs = 10 * 6e4;
14
16
  var refreshRetryMs = 6e4;
@@ -17,7 +19,9 @@ var refreshTimer;
17
19
  var messageHandler;
18
20
  var listeners = /* @__PURE__ */ new Map();
19
21
  var authWaiters = [];
22
+ var playerWaiters = [];
20
23
  var overlayEl = null;
24
+ var popoverEl = null;
21
25
  function win() {
22
26
  return globalThis.window;
23
27
  }
@@ -46,6 +50,19 @@ function clearRetryFlag() {
46
50
  } catch {
47
51
  }
48
52
  }
53
+ function readPopoverDismissed() {
54
+ try {
55
+ return win()?.sessionStorage?.getItem(POPOVER_DISMISSED_FLAG) === "1";
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+ function writePopoverDismissed() {
61
+ try {
62
+ win()?.sessionStorage?.setItem(POPOVER_DISMISSED_FLAG, "1");
63
+ } catch {
64
+ }
65
+ }
49
66
  function initEmbed(cfg) {
50
67
  const w = win();
51
68
  if (!w) return;
@@ -92,6 +109,15 @@ function waitForAuth() {
92
109
  authWaiters.push({ resolve, reject });
93
110
  });
94
111
  }
112
+ function waitForPlayer() {
113
+ if ((state === "authenticated" || state === "guest") && user) {
114
+ return Promise.resolve({ user, guest: state === "guest" });
115
+ }
116
+ if (state === "blocked") return Promise.reject(new Error("genex embed auth: blocked"));
117
+ return new Promise((resolve, reject) => {
118
+ playerWaiters.push({ resolve, reject });
119
+ });
120
+ }
95
121
  function on(event, cb) {
96
122
  let set = listeners.get(event);
97
123
  if (!set) {
@@ -120,21 +146,33 @@ function startEmbeddedHandshake(w) {
120
146
  w.addEventListener("message", messageHandler);
121
147
  w.parent.postMessage({ type: "genex:embed:ready", v: PROTOCOL_VERSION }, "*");
122
148
  handshakeTimer = setTimeout(() => {
123
- enterBlocked();
149
+ void requestGuestSession();
124
150
  }, handshakeTimeoutMs);
125
151
  }
152
+ function acceptsTicket() {
153
+ return (state === "pending" || state === "guest") && !redeeming;
154
+ }
126
155
  async function handleParentMessage(event) {
127
- if (!config || state !== "pending" || redeeming) return;
156
+ if (!config) return;
128
157
  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;
158
+ if (!data || data.v !== PROTOCOL_VERSION) return;
159
+ const isTicket = data.type === "genex:embed:ticket";
160
+ const isGuest = data.type === "genex:embed:guest";
161
+ if (!isTicket && !isGuest) return;
162
+ if (isTicket && (!acceptsTicket() || typeof data.ticket !== "string" || !data.ticket)) return;
163
+ if (isGuest && (state !== "pending" || redeeming || requestingGuest)) return;
131
164
  if (!config.dashboardOrigins.includes(event.origin)) {
132
165
  const fresh = await fetchDashboardOrigins();
133
- if (state !== "pending" || redeeming) return;
166
+ if (isTicket && !acceptsTicket()) return;
167
+ if (isGuest && (state !== "pending" || redeeming || requestingGuest)) return;
134
168
  if (!fresh.includes(event.origin)) return;
135
169
  }
136
170
  if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
137
171
  parentOrigin = event.origin;
172
+ if (isGuest) {
173
+ await requestGuestSession();
174
+ return;
175
+ }
138
176
  await redeemTicket(data.ticket);
139
177
  }
140
178
  function postToParent(message, targetOrigin) {
@@ -146,41 +184,52 @@ function postToParent(message, targetOrigin) {
146
184
  }
147
185
  }
148
186
  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);
187
+ const frag = readFragment(w);
188
+ if (frag.ticket) {
189
+ await redeemTicket(frag.ticket);
190
+ return;
191
+ }
192
+ if (frag.guest) {
193
+ await requestGuestSession();
156
194
  return;
157
195
  }
158
196
  showOverlay("redirecting");
159
- await redirectToAuthorize(w);
197
+ await redirectToAuthorize(w, { guestOk: true });
160
198
  }
161
199
  var stashedTicketHash = null;
162
200
  function _stashTicketFromUrl() {
163
201
  const w = win();
164
202
  if (!w) return;
165
203
  const hash = w.location.hash;
166
- if (!hash || !hash.includes("genex_ticket")) return;
204
+ if (!hash || !hash.includes("genex_")) return;
167
205
  stashedTicketHash = hash;
168
206
  try {
169
207
  w.history.replaceState(null, "", w.location.pathname + w.location.search);
170
208
  } catch {
171
209
  }
172
210
  }
173
- function readTicketFromFragment(w) {
211
+ function readFragment(w) {
174
212
  const hash = stashedTicketHash ?? w.location.hash;
175
213
  stashedTicketHash = null;
176
- if (!hash || hash.length < 2) return null;
214
+ if (!hash || hash.length < 2) return { ticket: null, guest: false };
215
+ let ticket = null;
216
+ let guest = false;
177
217
  try {
178
- return new URLSearchParams(hash.slice(1)).get("genex_ticket");
218
+ const params = new URLSearchParams(hash.slice(1));
219
+ ticket = params.get("genex_ticket");
220
+ guest = params.get("genex_guest") === "1";
179
221
  } catch {
180
- return null;
222
+ return { ticket: null, guest: false };
223
+ }
224
+ if (ticket || guest) {
225
+ try {
226
+ w.history.replaceState(null, "", w.location.pathname + w.location.search);
227
+ } catch {
228
+ }
181
229
  }
230
+ return { ticket, guest };
182
231
  }
183
- async function redirectToAuthorize(w) {
232
+ async function redirectToAuthorize(w, opts) {
184
233
  if (!config) return;
185
234
  const origins = await fetchDashboardOrigins();
186
235
  const origin = origins[0] ?? config.dashboardOrigins[0];
@@ -189,7 +238,8 @@ async function redirectToAuthorize(w) {
189
238
  return;
190
239
  }
191
240
  const returnTo = w.location.origin + w.location.pathname;
192
- const url = `${origin}/play/authorize?returnTo=${encodeURIComponent(returnTo)}&slug=${encodeURIComponent(config.slug)}`;
241
+ const guestParam = opts?.guestOk ? "&guest=ok" : "";
242
+ const url = `${origin}/play/authorize?returnTo=${encodeURIComponent(returnTo)}&slug=${encodeURIComponent(config.slug)}${guestParam}`;
193
243
  w.location.replace(url);
194
244
  }
195
245
  async function fetchDashboardOrigins() {
@@ -206,7 +256,8 @@ async function fetchDashboardOrigins() {
206
256
  return config.dashboardOrigins;
207
257
  }
208
258
  async function redeemTicket(ticket) {
209
- if (!config || redeeming || state !== "pending") return;
259
+ if (!config || redeeming || state !== "pending" && state !== "guest") return;
260
+ const upgradingFromGuest = state === "guest";
210
261
  redeeming = true;
211
262
  try {
212
263
  const res = await doFetch(`${config.apiUrl}/api/embed/session`, {
@@ -216,7 +267,7 @@ async function redeemTicket(ticket) {
216
267
  });
217
268
  if (!res.ok) {
218
269
  emit("error", { error: new Error(`ticket redemption failed (${res.status})`) });
219
- await handleRedeemFailure();
270
+ if (!upgradingFromGuest) await handleRedeemFailure();
220
271
  return;
221
272
  }
222
273
  const body = await res.json();
@@ -227,26 +278,75 @@ async function redeemTicket(ticket) {
227
278
  state = "authenticated";
228
279
  clearRetryFlag();
229
280
  removeOverlay();
281
+ removeGuestPopover();
230
282
  scheduleRefresh();
231
283
  if (isEmbedded() && parentOrigin) {
232
284
  postToParent({ type: "genex:embed:authenticated", v: PROTOCOL_VERSION }, parentOrigin);
233
285
  }
234
286
  const ctx = { user };
235
287
  for (const waiter of authWaiters.splice(0)) waiter.resolve(ctx);
288
+ for (const waiter of playerWaiters.splice(0)) waiter.resolve({ user, guest: false });
236
289
  emit("authenticated", ctx);
237
290
  } catch (error) {
238
291
  emit("error", { error });
239
- await handleRedeemFailure();
292
+ if (!upgradingFromGuest) await handleRedeemFailure();
240
293
  } finally {
241
294
  redeeming = false;
242
295
  }
243
296
  }
297
+ async function requestGuestSession() {
298
+ if (!config || requestingGuest || redeeming) return;
299
+ if (state !== "pending" && state !== "guest") return;
300
+ requestingGuest = true;
301
+ try {
302
+ const res = await doFetch(`${config.apiUrl}/api/embed/guest-session`, {
303
+ method: "POST",
304
+ headers: { "Content-Type": "application/json" },
305
+ body: JSON.stringify({ slug: config.slug })
306
+ });
307
+ if (!res.ok) {
308
+ emit("error", { error: new Error(`guest session failed (${res.status})`) });
309
+ enterBlocked();
310
+ return;
311
+ }
312
+ const body = await res.json();
313
+ embedToken = body.embedToken;
314
+ user = body.user;
315
+ colyseusUrl = body.colyseus?.url;
316
+ enterGuest();
317
+ } catch (error) {
318
+ emit("error", { error });
319
+ enterBlocked();
320
+ } finally {
321
+ requestingGuest = false;
322
+ }
323
+ }
324
+ function enterGuest() {
325
+ const firstEntry = state !== "guest";
326
+ state = "guest";
327
+ if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
328
+ removeOverlay();
329
+ scheduleRefresh();
330
+ if (isEmbedded()) {
331
+ postToParent(
332
+ { type: "genex:embed:authenticated", v: PROTOCOL_VERSION, guest: true },
333
+ parentOrigin ?? "*"
334
+ );
335
+ } else if (firstEntry && !readPopoverDismissed()) {
336
+ showGuestPopover();
337
+ }
338
+ if (user) {
339
+ const ctx = { user };
340
+ for (const waiter of playerWaiters.splice(0)) waiter.resolve({ user, guest: true });
341
+ emit("guest", ctx);
342
+ }
343
+ }
244
344
  async function handleRedeemFailure() {
245
345
  const w = win();
246
346
  if (w && !isEmbedded() && !readRetryFlag()) {
247
347
  writeRetryFlag();
248
348
  showOverlay("redirecting");
249
- await redirectToAuthorize(w);
349
+ await redirectToAuthorize(w, { guestOk: true });
250
350
  return;
251
351
  }
252
352
  enterBlocked();
@@ -258,7 +358,7 @@ function scheduleRefresh() {
258
358
  }, refreshDelayMs);
259
359
  }
260
360
  async function refreshToken() {
261
- if (!config || state !== "authenticated" || !embedToken) return;
361
+ if (!config || state !== "authenticated" && state !== "guest" || !embedToken) return;
262
362
  let status;
263
363
  try {
264
364
  const res = await doFetch(`${config.apiUrl}/api/embed/session/refresh`, {
@@ -279,6 +379,10 @@ async function refreshToken() {
279
379
  status = void 0;
280
380
  }
281
381
  if (status === 401) {
382
+ if (state === "guest") {
383
+ void requestGuestSession();
384
+ return;
385
+ }
282
386
  emit("error", { error: new Error("embed session expired") });
283
387
  enterBlocked();
284
388
  return;
@@ -295,17 +399,21 @@ function enterBlocked() {
295
399
  embedToken = void 0;
296
400
  if (handshakeTimer !== void 0) clearTimeout(handshakeTimer);
297
401
  if (refreshTimer !== void 0) clearTimeout(refreshTimer);
402
+ removeGuestPopover();
298
403
  showOverlay(isEmbedded() ? "blocked-embedded" : "blocked-standalone");
299
404
  if (isEmbedded()) {
300
405
  postToParent({ type: "genex:embed:blocked", v: PROTOCOL_VERSION }, parentOrigin ?? "*");
301
406
  }
302
407
  const err = new Error("genex embed auth: blocked");
303
408
  for (const waiter of authWaiters.splice(0)) waiter.reject(err);
409
+ for (const waiter of playerWaiters.splice(0)) waiter.reject(err);
304
410
  emit("blocked");
305
411
  }
306
412
  var OVERLAY_TEXT = {
307
413
  connecting: { title: "Connecting to Genex\u2026" },
308
- redirecting: { title: "Signing you in\u2026" },
414
+ // Neutral on purpose: the automatic bounce precedes GUEST play as often as
415
+ // sign-in — "signing you in" would be wrong for most visitors.
416
+ redirecting: { title: "Loading\u2026" },
309
417
  "blocked-standalone": { title: "Sign in to play", button: "Sign in" },
310
418
  "blocked-embedded": { title: "Sign in on the Genex dashboard to continue" }
311
419
  };
@@ -359,6 +467,56 @@ function buildOverlay(d) {
359
467
  }
360
468
  };
361
469
  }
470
+ function showGuestPopover() {
471
+ const d = doc();
472
+ if (!d?.createElement || popoverEl) return;
473
+ try {
474
+ popoverEl = buildGuestPopover(d);
475
+ } catch {
476
+ }
477
+ }
478
+ function removeGuestPopover() {
479
+ try {
480
+ const root = popoverEl?.root;
481
+ root?.remove?.();
482
+ } catch {
483
+ }
484
+ popoverEl = null;
485
+ }
486
+ function buildGuestPopover(d) {
487
+ const root = d.createElement("div");
488
+ root.setAttribute("data-genex-guest-popover", "");
489
+ 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";
490
+ const text = d.createElement("span");
491
+ text.textContent = user?.name ? `Playing as ${user.name} \u2014 sign in to save progress` : "Sign in to save progress";
492
+ root.appendChild(text);
493
+ const signIn = d.createElement("button");
494
+ signIn.textContent = "Sign in";
495
+ signIn.style.cssText = "font-size:13px;font-weight:600;padding:6px 16px;border-radius:999px;border:none;cursor:pointer;background:#fff;color:#111";
496
+ signIn.addEventListener("click", () => {
497
+ const w = win();
498
+ if (!w) return;
499
+ signIn.disabled = true;
500
+ clearRetryFlag();
501
+ void redirectToAuthorize(w);
502
+ });
503
+ root.appendChild(signIn);
504
+ const dismiss = d.createElement("button");
505
+ dismiss.textContent = "\xD7";
506
+ dismiss.setAttribute("aria-label", "Dismiss");
507
+ 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)";
508
+ dismiss.addEventListener("click", () => {
509
+ writePopoverDismissed();
510
+ removeGuestPopover();
511
+ });
512
+ root.appendChild(dismiss);
513
+ const mount = () => {
514
+ if (d.body && !root.isConnected) d.body.appendChild(root);
515
+ };
516
+ if (d.body) mount();
517
+ else d.addEventListener?.("DOMContentLoaded", mount);
518
+ return { root };
519
+ }
362
520
  function __resetForTests(overrides) {
363
521
  const w = win();
364
522
  if (w && messageHandler) w.removeEventListener("message", messageHandler);
@@ -368,6 +526,7 @@ function __resetForTests(overrides) {
368
526
  handshakeTimer = void 0;
369
527
  refreshTimer = void 0;
370
528
  removeOverlay();
529
+ removeGuestPopover();
371
530
  config = null;
372
531
  state = "pending";
373
532
  user = null;
@@ -376,8 +535,11 @@ function __resetForTests(overrides) {
376
535
  parentOrigin = null;
377
536
  initialized = false;
378
537
  redeeming = false;
538
+ requestingGuest = false;
379
539
  listeners.clear();
380
540
  authWaiters = [];
541
+ playerWaiters = [];
542
+ stashedTicketHash = null;
381
543
  handshakeTimeoutMs = overrides?.handshakeTimeoutMs ?? 1e4;
382
544
  refreshDelayMs = overrides?.refreshDelayMs ?? 10 * 6e4;
383
545
  refreshRetryMs = overrides?.refreshRetryMs ?? 6e4;
@@ -391,6 +553,7 @@ export {
391
553
  getEmbedToken,
392
554
  getColyseusAuth,
393
555
  waitForAuth,
556
+ waitForPlayer,
394
557
  on,
395
558
  _stashTicketFromUrl,
396
559
  __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
- * 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.
31
+ * Standalone (not in an iframe) with no return-trip fragment present, this
32
+ * bounces once through the dashboard's /play/authorize and comes straight
33
+ * back — signed-in visitors arrive authenticated (zero clicks, any game),
34
+ * everyone else arrives as a guest (published games; no login wall).
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-S7JLPCNE.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-S7JLPCNE.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.1",
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",