@genex-ai/embed-sdk 0.22.3 → 0.23.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.
@@ -298,7 +298,7 @@ var RETRY_FLAG = "genex:embed:retry";
298
298
  var POPOVER_DISMISSED_FLAG = "genex:guest:popover-dismissed";
299
299
  var LOCAL_AUTH_FLAG = "genex:embed:local-auth";
300
300
  var PLAYER_ID_KEY = "genex:player";
301
- var SDK_VERSION = "0.22.3";
301
+ var SDK_VERSION = "0.23.0";
302
302
  var config2 = null;
303
303
  var state = "pending";
304
304
  var user = null;
@@ -548,12 +548,18 @@ function stateFetch(path, init = {}) {
548
548
  keepalive: init.body !== void 0 && init.body.length <= KEEPALIVE_MAX_BYTES ? true : void 0
549
549
  });
550
550
  }
551
+ async function refusedAsStaging(res) {
552
+ if (res.status !== 403) return false;
553
+ const body = await res.json().catch(() => null);
554
+ return body?.error === "staging_no_save";
555
+ }
551
556
  async function decodeSave(res) {
552
557
  if (res.status === 409) {
553
558
  const body2 = await res.json().catch(() => ({}));
554
559
  return { saved: false, conflict: true, version: body2.version };
555
560
  }
556
561
  if (!res.ok) {
562
+ if (await refusedAsStaging(res)) return { saved: false, staging: true };
557
563
  emit("error", { error: new Error(`genex state save failed (${res.status})`) });
558
564
  return { saved: false };
559
565
  }
@@ -566,7 +572,10 @@ async function loadPlayerState() {
566
572
  return { data: hasPendingPlayerSave ? pendingPlayerSave : null, version: 0, guest: true };
567
573
  }
568
574
  const res = await stateFetch("/state/me");
569
- if (!res.ok) throw new Error(`genex load player state failed (${res.status})`);
575
+ if (!res.ok) {
576
+ if (await refusedAsStaging(res)) return { data: null, version: 0, staging: true };
577
+ throw new Error(`genex load player state failed (${res.status})`);
578
+ }
570
579
  const body = await res.json();
571
580
  return { data: body.data, version: body.version };
572
581
  }
@@ -589,7 +598,10 @@ async function loadWorldState() {
589
598
  await ensurePlayer();
590
599
  if (state === "guest") return { data: null, version: 0, guest: true };
591
600
  const res = await stateFetch("/state");
592
- if (!res.ok) throw new Error(`genex load world state failed (${res.status})`);
601
+ if (!res.ok) {
602
+ if (await refusedAsStaging(res)) return { data: null, version: 0, staging: true };
603
+ throw new Error(`genex load world state failed (${res.status})`);
604
+ }
593
605
  const body = await res.json();
594
606
  lastWorldVersion = body.version;
595
607
  return { data: body.data, version: body.version };
package/dist/index.d.ts CHANGED
@@ -222,11 +222,20 @@ interface PlayerStateResult {
222
222
  version: number;
223
223
  /** True when the current identity is a guest (server storage untouched). */
224
224
  guest?: boolean;
225
+ /**
226
+ * True when the build is running on the PREVIEW channel — the owner's draft
227
+ * workspace, any `preview--<slug>` URL. Server saves are off there by design,
228
+ * so nothing was read: keep progress in localStorage and say "preview build",
229
+ * never "offline".
230
+ */
231
+ staging?: boolean;
225
232
  }
226
233
  interface WorldStateResult {
227
234
  data: unknown;
228
235
  version: number;
229
236
  guest?: boolean;
237
+ /** True on the preview channel — see PlayerStateResult.staging. */
238
+ staging?: boolean;
230
239
  }
231
240
  interface SaveStateResult {
232
241
  /** True when the write landed on the server. */
@@ -239,6 +248,13 @@ interface SaveStateResult {
239
248
  guest?: boolean;
240
249
  /** True when the value was queued to auto-flush if this guest signs in mid-game. */
241
250
  queued?: boolean;
251
+ /**
252
+ * True when the build is running on the PREVIEW channel (the owner's draft
253
+ * workspace, `preview--<slug>`): the server refused the write by policy, not
254
+ * by failure — nothing is queued, no `error` event fires. Keep the value in
255
+ * localStorage and tell the player it is a preview build.
256
+ */
257
+ staging?: boolean;
242
258
  }
243
259
  interface SubmitScoreResult {
244
260
  submitted: boolean;
@@ -355,14 +371,19 @@ declare function on(event: EmbedEvent, cb: (ctx?: EventContext) => void): () =>
355
371
  * Load THIS player's own save slot (per-player, per-game — another player can
356
372
  * never read or clobber it). Resolves { data: null, version: 0 } when they
357
373
  * never saved. Guests get their queued pending save (if any) so a round-trip
358
- * works mid-session; the server is untouched. Await-safe from boot: resolves
359
- * after identity exists, rejects only when the session is blocked.
374
+ * works mid-session; the server is untouched. On the preview channel (the
375
+ * owner's draft workspace) resolves { data: null, version: 0, staging: true }
376
+ * — server saves are off there by design, and that is not an error. Await-safe
377
+ * from boot: resolves after identity exists, rejects only when the session is
378
+ * blocked.
360
379
  */
361
380
  declare function loadPlayerState(): Promise<PlayerStateResult>;
362
381
  /**
363
382
  * Save THIS player's own slot (any JSON ≤ 256KB; last-write-wins on their own
364
383
  * row). Guests: the value is QUEUED in memory and auto-flushed if they sign in
365
384
  * mid-game ({ saved: false, guest: true, queued: true }) — no extra game code.
385
+ * On the preview channel: { saved: false, staging: true }, nothing queued
386
+ * (nothing would ever flush it) and no `error` event — keep it in localStorage.
366
387
  * Pass { ifVersion } (from the last load/save) to detect same-account
367
388
  * two-device races: a losing write resolves { conflict: true, version } —
368
389
  * reload, merge, retry. Small saves survive tab close (fetch keepalive).
@@ -374,7 +395,8 @@ declare function savePlayerState(data: unknown, opts?: {
374
395
  * Load the game's SHARED world slot (one blob per game — every player reads
375
396
  * the same world). Guests resolve { data: null, guest: true } — the server
376
397
  * refuses guest reads; in multiplayer, guests receive the live world through
377
- * the room instead.
398
+ * the room instead. The preview channel resolves { data: null, version: 0,
399
+ * staging: true } for the same reason the player slot does.
378
400
  */
379
401
  declare function loadWorldState(): Promise<WorldStateResult>;
380
402
  /**
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  waitForAuth,
29
29
  waitForGeneration,
30
30
  waitForPlayer
31
- } from "./chunk-IFI2NYBC.js";
31
+ } from "./chunk-CBR3BK66.js";
32
32
  import "./chunk-Q476GJHI.js";
33
33
  export {
34
34
  __resetForTests,
package/dist/sentry.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  _stashTicketFromUrl,
3
3
  getUser,
4
4
  on
5
- } from "./chunk-IFI2NYBC.js";
5
+ } from "./chunk-CBR3BK66.js";
6
6
  import "./chunk-Q476GJHI.js";
7
7
 
8
8
  // src/sentry.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/embed-sdk",
3
- "version": "0.22.3",
3
+ "version": "0.23.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",