@snowcone-app/sdk 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#239](https://github.com/snowcone-app/snowcone-monorepo/pull/239) [`0556a8f`](https://github.com/snowcone-app/snowcone-monorepo/commit/0556a8f1fbef862dadc50463657fb716517b3264) Thanks [@kevinsproles](https://github.com/kevinsproles)! - fix(realtime): `RenderSession` now surfaces a render timeout via `onError`
8
+
9
+ A render that produces no mockup used to hang forever with no signal — the
10
+ classic trigger is a product with a color/variant axis where `variantId` is
11
+ omitted, so the server waits for a color blob that never arrives and never
12
+ errors. `RenderSession` now arms a per-render watchdog: if no mockup arrives in
13
+ time it reports an actionable timeout (with the `variantId` hint) through the
14
+ same `onError` channel as other errors, instead of silently hanging.
15
+
16
+ Configurable via the new `renderTimeoutMs` option (default `30000`; set to `0`
17
+ to disable). A successful mockup cancels the watchdog, each new render re-arms
18
+ it, and `close()` clears any pending timer. `renderState` still resolves on
19
+ send — the watchdog is a side-channel and throttle behavior is unchanged.
20
+
3
21
  ## 0.2.1
4
22
 
5
23
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -5511,6 +5511,7 @@ async function fetchRealtimeGrant(grantUrl, shop, fetchImpl) {
5511
5511
  }
5512
5512
 
5513
5513
  // src/realtime/session.ts
5514
+ var DEFAULT_RENDER_TIMEOUT_MS = 3e4;
5514
5515
  var RenderSession = class {
5515
5516
  svc;
5516
5517
  opts;
@@ -5518,6 +5519,8 @@ var RenderSession = class {
5518
5519
  mockupCb = null;
5519
5520
  errorCb = null;
5520
5521
  ready = null;
5522
+ renderTimeoutMs;
5523
+ watchdog = null;
5521
5524
  constructor(opts) {
5522
5525
  if (!opts.shop) throw new Error("RenderSession: `shop` is required");
5523
5526
  if (!opts.getToken && !opts.grantUrl) {
@@ -5525,6 +5528,7 @@ var RenderSession = class {
5525
5528
  }
5526
5529
  this.opts = opts;
5527
5530
  this.product = opts.product ?? null;
5531
+ this.renderTimeoutMs = opts.renderTimeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
5528
5532
  this.svc = new RealtimeMockupService(opts.wsUrl ?? REALTIME_WS_URL);
5529
5533
  const getToken = opts.getToken ?? (() => fetchRealtimeGrant(opts.grantUrl, opts.shop, opts.fetch));
5530
5534
  this.svc.setTokenProvider(getToken);
@@ -5569,9 +5573,11 @@ var RenderSession = class {
5569
5573
  }
5570
5574
  },
5571
5575
  onMockupRendered: () => {
5576
+ this.clearWatchdog();
5572
5577
  this.mockupCb?.(this.svc.getState().mockupResults);
5573
5578
  },
5574
5579
  onAllMockupsRendered: (results) => {
5580
+ this.clearWatchdog();
5575
5581
  this.mockupCb?.(results);
5576
5582
  },
5577
5583
  onError: (error) => {
@@ -5606,6 +5612,7 @@ var RenderSession = class {
5606
5612
  async renderState(placement, state, throttleMs = 0) {
5607
5613
  await this.connect();
5608
5614
  this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
5615
+ this.armWatchdog();
5609
5616
  }
5610
5617
  /**
5611
5618
  * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
@@ -5617,6 +5624,7 @@ var RenderSession = class {
5617
5624
  async renderSavedState(placement, stateId) {
5618
5625
  await this.connect();
5619
5626
  this.svc.sendCanvasStateRef(placement, stateId);
5627
+ this.armWatchdog();
5620
5628
  }
5621
5629
  /** Update only the mockup ids to render (reuses the current state). */
5622
5630
  updateMockupIds(mockupIds) {
@@ -5629,9 +5637,33 @@ var RenderSession = class {
5629
5637
  }
5630
5638
  /** Close the WebSocket and stop auto-renew. */
5631
5639
  close() {
5640
+ this.clearWatchdog();
5632
5641
  this.svc.disconnect();
5633
5642
  this.ready = null;
5634
5643
  }
5644
+ /**
5645
+ * (Re)arm the render watchdog. Clears any prior timer and, unless disabled
5646
+ * (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
5647
+ * arrives, it surfaces a timeout via the error callback — the same channel as
5648
+ * server/transport errors — with a hint at the most common cause.
5649
+ */
5650
+ armWatchdog() {
5651
+ this.clearWatchdog();
5652
+ if (this.renderTimeoutMs <= 0) return;
5653
+ this.watchdog = setTimeout(() => {
5654
+ this.watchdog = null;
5655
+ this.errorCb?.(
5656
+ `realtime render timed out after ${this.renderTimeoutMs}ms with no mockup \u2014 a product with a color/variant axis needs \`variantId\` in the product config (see RenderProduct.variantId)`
5657
+ );
5658
+ }, this.renderTimeoutMs);
5659
+ }
5660
+ /** Cancel the pending render watchdog, if any. */
5661
+ clearWatchdog() {
5662
+ if (this.watchdog) {
5663
+ clearTimeout(this.watchdog);
5664
+ this.watchdog = null;
5665
+ }
5666
+ }
5635
5667
  /** Escape hatch: the underlying low-level service. */
5636
5668
  get service() {
5637
5669
  return this.svc;
package/dist/index.d.cts CHANGED
@@ -2627,6 +2627,23 @@ interface RenderSessionOptions {
2627
2627
  getToken?: () => Promise<RealtimeGrant>;
2628
2628
  /** Override fetch (used with `grantUrl`). */
2629
2629
  fetch?: typeof fetch;
2630
+ /**
2631
+ * Watchdog for silent render hangs. After a render is dispatched (via
2632
+ * {@link RenderSession.renderState} or {@link RenderSession.renderSavedState}),
2633
+ * if NO mockup arrives within this many milliseconds the session surfaces a
2634
+ * timeout through {@link RenderSession.onError} — turning an otherwise-silent
2635
+ * infinite hang into an actionable error.
2636
+ *
2637
+ * The classic trigger is a product with a color/variant axis where
2638
+ * {@link RenderProduct.variantId} is omitted: the server waits for a color
2639
+ * blob that never arrives, so it neither renders nor errors. The watchdog
2640
+ * makes that visible.
2641
+ *
2642
+ * Defaults to `30000` (30s). A value of `0` (or negative) DISABLES the
2643
+ * watchdog entirely. A successful mockup always cancels the pending watchdog,
2644
+ * and each new render re-arms it.
2645
+ */
2646
+ renderTimeoutMs?: number;
2630
2647
  }
2631
2648
  declare class RenderSession {
2632
2649
  private readonly svc;
@@ -2635,6 +2652,8 @@ declare class RenderSession {
2635
2652
  private mockupCb;
2636
2653
  private errorCb;
2637
2654
  private ready;
2655
+ private readonly renderTimeoutMs;
2656
+ private watchdog;
2638
2657
  constructor(opts: RenderSessionOptions);
2639
2658
  /** Register a callback fired whenever rendered mockup URLs update. */
2640
2659
  onMockups(cb: (results: MockupResult[]) => void): this;
@@ -2678,6 +2697,15 @@ declare class RenderSession {
2678
2697
  getMockups(): MockupResult[];
2679
2698
  /** Close the WebSocket and stop auto-renew. */
2680
2699
  close(): void;
2700
+ /**
2701
+ * (Re)arm the render watchdog. Clears any prior timer and, unless disabled
2702
+ * (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
2703
+ * arrives, it surfaces a timeout via the error callback — the same channel as
2704
+ * server/transport errors — with a hint at the most common cause.
2705
+ */
2706
+ private armWatchdog;
2707
+ /** Cancel the pending render watchdog, if any. */
2708
+ private clearWatchdog;
2681
2709
  /** Escape hatch: the underlying low-level service. */
2682
2710
  get service(): RealtimeMockupService;
2683
2711
  private toConfig;
package/dist/index.d.ts CHANGED
@@ -2627,6 +2627,23 @@ interface RenderSessionOptions {
2627
2627
  getToken?: () => Promise<RealtimeGrant>;
2628
2628
  /** Override fetch (used with `grantUrl`). */
2629
2629
  fetch?: typeof fetch;
2630
+ /**
2631
+ * Watchdog for silent render hangs. After a render is dispatched (via
2632
+ * {@link RenderSession.renderState} or {@link RenderSession.renderSavedState}),
2633
+ * if NO mockup arrives within this many milliseconds the session surfaces a
2634
+ * timeout through {@link RenderSession.onError} — turning an otherwise-silent
2635
+ * infinite hang into an actionable error.
2636
+ *
2637
+ * The classic trigger is a product with a color/variant axis where
2638
+ * {@link RenderProduct.variantId} is omitted: the server waits for a color
2639
+ * blob that never arrives, so it neither renders nor errors. The watchdog
2640
+ * makes that visible.
2641
+ *
2642
+ * Defaults to `30000` (30s). A value of `0` (or negative) DISABLES the
2643
+ * watchdog entirely. A successful mockup always cancels the pending watchdog,
2644
+ * and each new render re-arms it.
2645
+ */
2646
+ renderTimeoutMs?: number;
2630
2647
  }
2631
2648
  declare class RenderSession {
2632
2649
  private readonly svc;
@@ -2635,6 +2652,8 @@ declare class RenderSession {
2635
2652
  private mockupCb;
2636
2653
  private errorCb;
2637
2654
  private ready;
2655
+ private readonly renderTimeoutMs;
2656
+ private watchdog;
2638
2657
  constructor(opts: RenderSessionOptions);
2639
2658
  /** Register a callback fired whenever rendered mockup URLs update. */
2640
2659
  onMockups(cb: (results: MockupResult[]) => void): this;
@@ -2678,6 +2697,15 @@ declare class RenderSession {
2678
2697
  getMockups(): MockupResult[];
2679
2698
  /** Close the WebSocket and stop auto-renew. */
2680
2699
  close(): void;
2700
+ /**
2701
+ * (Re)arm the render watchdog. Clears any prior timer and, unless disabled
2702
+ * (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
2703
+ * arrives, it surfaces a timeout via the error callback — the same channel as
2704
+ * server/transport errors — with a hint at the most common cause.
2705
+ */
2706
+ private armWatchdog;
2707
+ /** Cancel the pending render watchdog, if any. */
2708
+ private clearWatchdog;
2681
2709
  /** Escape hatch: the underlying low-level service. */
2682
2710
  get service(): RealtimeMockupService;
2683
2711
  private toConfig;
package/dist/index.js CHANGED
@@ -4678,6 +4678,7 @@ async function fetchRealtimeGrant(grantUrl, shop, fetchImpl) {
4678
4678
  }
4679
4679
 
4680
4680
  // src/realtime/session.ts
4681
+ var DEFAULT_RENDER_TIMEOUT_MS = 3e4;
4681
4682
  var RenderSession = class {
4682
4683
  svc;
4683
4684
  opts;
@@ -4685,6 +4686,8 @@ var RenderSession = class {
4685
4686
  mockupCb = null;
4686
4687
  errorCb = null;
4687
4688
  ready = null;
4689
+ renderTimeoutMs;
4690
+ watchdog = null;
4688
4691
  constructor(opts) {
4689
4692
  if (!opts.shop) throw new Error("RenderSession: `shop` is required");
4690
4693
  if (!opts.getToken && !opts.grantUrl) {
@@ -4692,6 +4695,7 @@ var RenderSession = class {
4692
4695
  }
4693
4696
  this.opts = opts;
4694
4697
  this.product = opts.product ?? null;
4698
+ this.renderTimeoutMs = opts.renderTimeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
4695
4699
  this.svc = new RealtimeMockupService(opts.wsUrl ?? REALTIME_WS_URL);
4696
4700
  const getToken = opts.getToken ?? (() => fetchRealtimeGrant(opts.grantUrl, opts.shop, opts.fetch));
4697
4701
  this.svc.setTokenProvider(getToken);
@@ -4736,9 +4740,11 @@ var RenderSession = class {
4736
4740
  }
4737
4741
  },
4738
4742
  onMockupRendered: () => {
4743
+ this.clearWatchdog();
4739
4744
  this.mockupCb?.(this.svc.getState().mockupResults);
4740
4745
  },
4741
4746
  onAllMockupsRendered: (results) => {
4747
+ this.clearWatchdog();
4742
4748
  this.mockupCb?.(results);
4743
4749
  },
4744
4750
  onError: (error) => {
@@ -4773,6 +4779,7 @@ var RenderSession = class {
4773
4779
  async renderState(placement, state, throttleMs = 0) {
4774
4780
  await this.connect();
4775
4781
  this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
4782
+ this.armWatchdog();
4776
4783
  }
4777
4784
  /**
4778
4785
  * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
@@ -4784,6 +4791,7 @@ var RenderSession = class {
4784
4791
  async renderSavedState(placement, stateId) {
4785
4792
  await this.connect();
4786
4793
  this.svc.sendCanvasStateRef(placement, stateId);
4794
+ this.armWatchdog();
4787
4795
  }
4788
4796
  /** Update only the mockup ids to render (reuses the current state). */
4789
4797
  updateMockupIds(mockupIds) {
@@ -4796,9 +4804,33 @@ var RenderSession = class {
4796
4804
  }
4797
4805
  /** Close the WebSocket and stop auto-renew. */
4798
4806
  close() {
4807
+ this.clearWatchdog();
4799
4808
  this.svc.disconnect();
4800
4809
  this.ready = null;
4801
4810
  }
4811
+ /**
4812
+ * (Re)arm the render watchdog. Clears any prior timer and, unless disabled
4813
+ * (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
4814
+ * arrives, it surfaces a timeout via the error callback — the same channel as
4815
+ * server/transport errors — with a hint at the most common cause.
4816
+ */
4817
+ armWatchdog() {
4818
+ this.clearWatchdog();
4819
+ if (this.renderTimeoutMs <= 0) return;
4820
+ this.watchdog = setTimeout(() => {
4821
+ this.watchdog = null;
4822
+ this.errorCb?.(
4823
+ `realtime render timed out after ${this.renderTimeoutMs}ms with no mockup \u2014 a product with a color/variant axis needs \`variantId\` in the product config (see RenderProduct.variantId)`
4824
+ );
4825
+ }, this.renderTimeoutMs);
4826
+ }
4827
+ /** Cancel the pending render watchdog, if any. */
4828
+ clearWatchdog() {
4829
+ if (this.watchdog) {
4830
+ clearTimeout(this.watchdog);
4831
+ this.watchdog = null;
4832
+ }
4833
+ }
4802
4834
  /** Escape hatch: the underlying low-level service. */
4803
4835
  get service() {
4804
4836
  return this.svc;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/sdk",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Snowcone SDK for product mockups and print-on-demand",
5
5
  "keywords": [
6
6
  "merch",