@snowcone-app/sdk 0.2.0 → 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 +30 -0
- package/dist/index.cjs +35 -4
- package/dist/index.d.cts +42 -2
- package/dist/index.d.ts +42 -2
- package/dist/index.js +35 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
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
|
+
|
|
21
|
+
## 0.2.1
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#233](https://github.com/snowcone-app/snowcone-monorepo/pull/233) [`8b08283`](https://github.com/snowcone-app/snowcone-monorepo/commit/8b0828340ea0d2852655bffe8436f0cb71bde3aa) Thanks [@kevinsproles](https://github.com/kevinsproles)! - fix(realtime): `mintRealtimeGrant` now honors the keyless publishable path
|
|
26
|
+
|
|
27
|
+
Omitting `apiKey` (or passing `undefined`/`null`/`''`) no longer sends a
|
|
28
|
+
broken `Authorization: Bearer undefined` header that 401s. The header is now
|
|
29
|
+
sent only when a key is actually provided, so `mintRealtimeGrant({ shop })`
|
|
30
|
+
uses the publishable shop-id path exactly as documented. `apiKey` is now typed
|
|
31
|
+
as optional.
|
|
32
|
+
|
|
3
33
|
## 0.2.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/dist/index.cjs
CHANGED
|
@@ -5486,12 +5486,11 @@ ${versionToSend}
|
|
|
5486
5486
|
var DEFAULT_GRANT_BASE = "https://api.snowcone.app";
|
|
5487
5487
|
async function mintRealtimeGrant(opts) {
|
|
5488
5488
|
const f = opts.fetch ?? globalThis.fetch;
|
|
5489
|
+
const headers = { "Content-Type": "application/json" };
|
|
5490
|
+
if (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;
|
|
5489
5491
|
const res = await f(`${opts.base ?? DEFAULT_GRANT_BASE}/realtime/grant`, {
|
|
5490
5492
|
method: "POST",
|
|
5491
|
-
headers
|
|
5492
|
-
"Content-Type": "application/json",
|
|
5493
|
-
Authorization: `Bearer ${opts.apiKey}`
|
|
5494
|
-
},
|
|
5493
|
+
headers,
|
|
5495
5494
|
body: JSON.stringify({ shop: opts.shop })
|
|
5496
5495
|
});
|
|
5497
5496
|
if (!res.ok) {
|
|
@@ -5512,6 +5511,7 @@ async function fetchRealtimeGrant(grantUrl, shop, fetchImpl) {
|
|
|
5512
5511
|
}
|
|
5513
5512
|
|
|
5514
5513
|
// src/realtime/session.ts
|
|
5514
|
+
var DEFAULT_RENDER_TIMEOUT_MS = 3e4;
|
|
5515
5515
|
var RenderSession = class {
|
|
5516
5516
|
svc;
|
|
5517
5517
|
opts;
|
|
@@ -5519,6 +5519,8 @@ var RenderSession = class {
|
|
|
5519
5519
|
mockupCb = null;
|
|
5520
5520
|
errorCb = null;
|
|
5521
5521
|
ready = null;
|
|
5522
|
+
renderTimeoutMs;
|
|
5523
|
+
watchdog = null;
|
|
5522
5524
|
constructor(opts) {
|
|
5523
5525
|
if (!opts.shop) throw new Error("RenderSession: `shop` is required");
|
|
5524
5526
|
if (!opts.getToken && !opts.grantUrl) {
|
|
@@ -5526,6 +5528,7 @@ var RenderSession = class {
|
|
|
5526
5528
|
}
|
|
5527
5529
|
this.opts = opts;
|
|
5528
5530
|
this.product = opts.product ?? null;
|
|
5531
|
+
this.renderTimeoutMs = opts.renderTimeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
|
|
5529
5532
|
this.svc = new RealtimeMockupService(opts.wsUrl ?? REALTIME_WS_URL);
|
|
5530
5533
|
const getToken = opts.getToken ?? (() => fetchRealtimeGrant(opts.grantUrl, opts.shop, opts.fetch));
|
|
5531
5534
|
this.svc.setTokenProvider(getToken);
|
|
@@ -5570,9 +5573,11 @@ var RenderSession = class {
|
|
|
5570
5573
|
}
|
|
5571
5574
|
},
|
|
5572
5575
|
onMockupRendered: () => {
|
|
5576
|
+
this.clearWatchdog();
|
|
5573
5577
|
this.mockupCb?.(this.svc.getState().mockupResults);
|
|
5574
5578
|
},
|
|
5575
5579
|
onAllMockupsRendered: (results) => {
|
|
5580
|
+
this.clearWatchdog();
|
|
5576
5581
|
this.mockupCb?.(results);
|
|
5577
5582
|
},
|
|
5578
5583
|
onError: (error) => {
|
|
@@ -5607,6 +5612,7 @@ var RenderSession = class {
|
|
|
5607
5612
|
async renderState(placement, state, throttleMs = 0) {
|
|
5608
5613
|
await this.connect();
|
|
5609
5614
|
this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
|
|
5615
|
+
this.armWatchdog();
|
|
5610
5616
|
}
|
|
5611
5617
|
/**
|
|
5612
5618
|
* Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
|
|
@@ -5618,6 +5624,7 @@ var RenderSession = class {
|
|
|
5618
5624
|
async renderSavedState(placement, stateId) {
|
|
5619
5625
|
await this.connect();
|
|
5620
5626
|
this.svc.sendCanvasStateRef(placement, stateId);
|
|
5627
|
+
this.armWatchdog();
|
|
5621
5628
|
}
|
|
5622
5629
|
/** Update only the mockup ids to render (reuses the current state). */
|
|
5623
5630
|
updateMockupIds(mockupIds) {
|
|
@@ -5630,9 +5637,33 @@ var RenderSession = class {
|
|
|
5630
5637
|
}
|
|
5631
5638
|
/** Close the WebSocket and stop auto-renew. */
|
|
5632
5639
|
close() {
|
|
5640
|
+
this.clearWatchdog();
|
|
5633
5641
|
this.svc.disconnect();
|
|
5634
5642
|
this.ready = null;
|
|
5635
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
|
+
}
|
|
5636
5667
|
/** Escape hatch: the underlying low-level service. */
|
|
5637
5668
|
get service() {
|
|
5638
5669
|
return this.svc;
|
package/dist/index.d.cts
CHANGED
|
@@ -2518,14 +2518,26 @@ declare const DEFAULT_GRANT_BASE = "https://api.snowcone.app";
|
|
|
2518
2518
|
* own the target `shop`. Returns a 60s grant your browser code opens the WS
|
|
2519
2519
|
* with (hand it down via your own endpoint + {@link fetchRealtimeGrant}).
|
|
2520
2520
|
*
|
|
2521
|
+
* Omit `apiKey` (or pass `undefined`/`null`/`''`) to use the **keyless
|
|
2522
|
+
* publishable path**: no `Authorization` header is sent and the grant is
|
|
2523
|
+
* authorized by the publishable `shop` id alone. Passing a key uses the
|
|
2524
|
+
* secret (sk_) path.
|
|
2525
|
+
*
|
|
2521
2526
|
* @example
|
|
2522
2527
|
* // your backend route: POST /api/realtime/grant
|
|
2523
2528
|
* const grant = await mintRealtimeGrant({ apiKey: process.env.SNOWCONE_API_KEY!, shop });
|
|
2524
2529
|
* return Response.json(grant);
|
|
2530
|
+
*
|
|
2531
|
+
* @example
|
|
2532
|
+
* // keyless publishable path — quick trials, no API key:
|
|
2533
|
+
* const grant = await mintRealtimeGrant({ shop });
|
|
2525
2534
|
*/
|
|
2526
2535
|
declare function mintRealtimeGrant(opts: {
|
|
2527
|
-
/**
|
|
2528
|
-
|
|
2536
|
+
/**
|
|
2537
|
+
* Secret API key (sk_) with the `mockups:realtime` or `mockups` scope.
|
|
2538
|
+
* Omit (or pass `undefined`/`null`/`''`) for the keyless publishable path.
|
|
2539
|
+
*/
|
|
2540
|
+
apiKey?: string | null;
|
|
2529
2541
|
/** Target shop id (= shop.id). Must belong to the key's organization. */
|
|
2530
2542
|
shop: string;
|
|
2531
2543
|
/** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
|
|
@@ -2615,6 +2627,23 @@ interface RenderSessionOptions {
|
|
|
2615
2627
|
getToken?: () => Promise<RealtimeGrant>;
|
|
2616
2628
|
/** Override fetch (used with `grantUrl`). */
|
|
2617
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;
|
|
2618
2647
|
}
|
|
2619
2648
|
declare class RenderSession {
|
|
2620
2649
|
private readonly svc;
|
|
@@ -2623,6 +2652,8 @@ declare class RenderSession {
|
|
|
2623
2652
|
private mockupCb;
|
|
2624
2653
|
private errorCb;
|
|
2625
2654
|
private ready;
|
|
2655
|
+
private readonly renderTimeoutMs;
|
|
2656
|
+
private watchdog;
|
|
2626
2657
|
constructor(opts: RenderSessionOptions);
|
|
2627
2658
|
/** Register a callback fired whenever rendered mockup URLs update. */
|
|
2628
2659
|
onMockups(cb: (results: MockupResult[]) => void): this;
|
|
@@ -2666,6 +2697,15 @@ declare class RenderSession {
|
|
|
2666
2697
|
getMockups(): MockupResult[];
|
|
2667
2698
|
/** Close the WebSocket and stop auto-renew. */
|
|
2668
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;
|
|
2669
2709
|
/** Escape hatch: the underlying low-level service. */
|
|
2670
2710
|
get service(): RealtimeMockupService;
|
|
2671
2711
|
private toConfig;
|
package/dist/index.d.ts
CHANGED
|
@@ -2518,14 +2518,26 @@ declare const DEFAULT_GRANT_BASE = "https://api.snowcone.app";
|
|
|
2518
2518
|
* own the target `shop`. Returns a 60s grant your browser code opens the WS
|
|
2519
2519
|
* with (hand it down via your own endpoint + {@link fetchRealtimeGrant}).
|
|
2520
2520
|
*
|
|
2521
|
+
* Omit `apiKey` (or pass `undefined`/`null`/`''`) to use the **keyless
|
|
2522
|
+
* publishable path**: no `Authorization` header is sent and the grant is
|
|
2523
|
+
* authorized by the publishable `shop` id alone. Passing a key uses the
|
|
2524
|
+
* secret (sk_) path.
|
|
2525
|
+
*
|
|
2521
2526
|
* @example
|
|
2522
2527
|
* // your backend route: POST /api/realtime/grant
|
|
2523
2528
|
* const grant = await mintRealtimeGrant({ apiKey: process.env.SNOWCONE_API_KEY!, shop });
|
|
2524
2529
|
* return Response.json(grant);
|
|
2530
|
+
*
|
|
2531
|
+
* @example
|
|
2532
|
+
* // keyless publishable path — quick trials, no API key:
|
|
2533
|
+
* const grant = await mintRealtimeGrant({ shop });
|
|
2525
2534
|
*/
|
|
2526
2535
|
declare function mintRealtimeGrant(opts: {
|
|
2527
|
-
/**
|
|
2528
|
-
|
|
2536
|
+
/**
|
|
2537
|
+
* Secret API key (sk_) with the `mockups:realtime` or `mockups` scope.
|
|
2538
|
+
* Omit (or pass `undefined`/`null`/`''`) for the keyless publishable path.
|
|
2539
|
+
*/
|
|
2540
|
+
apiKey?: string | null;
|
|
2529
2541
|
/** Target shop id (= shop.id). Must belong to the key's organization. */
|
|
2530
2542
|
shop: string;
|
|
2531
2543
|
/** Backend base URL. Defaults to {@link DEFAULT_GRANT_BASE}. */
|
|
@@ -2615,6 +2627,23 @@ interface RenderSessionOptions {
|
|
|
2615
2627
|
getToken?: () => Promise<RealtimeGrant>;
|
|
2616
2628
|
/** Override fetch (used with `grantUrl`). */
|
|
2617
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;
|
|
2618
2647
|
}
|
|
2619
2648
|
declare class RenderSession {
|
|
2620
2649
|
private readonly svc;
|
|
@@ -2623,6 +2652,8 @@ declare class RenderSession {
|
|
|
2623
2652
|
private mockupCb;
|
|
2624
2653
|
private errorCb;
|
|
2625
2654
|
private ready;
|
|
2655
|
+
private readonly renderTimeoutMs;
|
|
2656
|
+
private watchdog;
|
|
2626
2657
|
constructor(opts: RenderSessionOptions);
|
|
2627
2658
|
/** Register a callback fired whenever rendered mockup URLs update. */
|
|
2628
2659
|
onMockups(cb: (results: MockupResult[]) => void): this;
|
|
@@ -2666,6 +2697,15 @@ declare class RenderSession {
|
|
|
2666
2697
|
getMockups(): MockupResult[];
|
|
2667
2698
|
/** Close the WebSocket and stop auto-renew. */
|
|
2668
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;
|
|
2669
2709
|
/** Escape hatch: the underlying low-level service. */
|
|
2670
2710
|
get service(): RealtimeMockupService;
|
|
2671
2711
|
private toConfig;
|
package/dist/index.js
CHANGED
|
@@ -4653,12 +4653,11 @@ function createSvelteComponent(descriptor, svelte) {
|
|
|
4653
4653
|
var DEFAULT_GRANT_BASE = "https://api.snowcone.app";
|
|
4654
4654
|
async function mintRealtimeGrant(opts) {
|
|
4655
4655
|
const f = opts.fetch ?? globalThis.fetch;
|
|
4656
|
+
const headers = { "Content-Type": "application/json" };
|
|
4657
|
+
if (opts.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;
|
|
4656
4658
|
const res = await f(`${opts.base ?? DEFAULT_GRANT_BASE}/realtime/grant`, {
|
|
4657
4659
|
method: "POST",
|
|
4658
|
-
headers
|
|
4659
|
-
"Content-Type": "application/json",
|
|
4660
|
-
Authorization: `Bearer ${opts.apiKey}`
|
|
4661
|
-
},
|
|
4660
|
+
headers,
|
|
4662
4661
|
body: JSON.stringify({ shop: opts.shop })
|
|
4663
4662
|
});
|
|
4664
4663
|
if (!res.ok) {
|
|
@@ -4679,6 +4678,7 @@ async function fetchRealtimeGrant(grantUrl, shop, fetchImpl) {
|
|
|
4679
4678
|
}
|
|
4680
4679
|
|
|
4681
4680
|
// src/realtime/session.ts
|
|
4681
|
+
var DEFAULT_RENDER_TIMEOUT_MS = 3e4;
|
|
4682
4682
|
var RenderSession = class {
|
|
4683
4683
|
svc;
|
|
4684
4684
|
opts;
|
|
@@ -4686,6 +4686,8 @@ var RenderSession = class {
|
|
|
4686
4686
|
mockupCb = null;
|
|
4687
4687
|
errorCb = null;
|
|
4688
4688
|
ready = null;
|
|
4689
|
+
renderTimeoutMs;
|
|
4690
|
+
watchdog = null;
|
|
4689
4691
|
constructor(opts) {
|
|
4690
4692
|
if (!opts.shop) throw new Error("RenderSession: `shop` is required");
|
|
4691
4693
|
if (!opts.getToken && !opts.grantUrl) {
|
|
@@ -4693,6 +4695,7 @@ var RenderSession = class {
|
|
|
4693
4695
|
}
|
|
4694
4696
|
this.opts = opts;
|
|
4695
4697
|
this.product = opts.product ?? null;
|
|
4698
|
+
this.renderTimeoutMs = opts.renderTimeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
|
|
4696
4699
|
this.svc = new RealtimeMockupService(opts.wsUrl ?? REALTIME_WS_URL);
|
|
4697
4700
|
const getToken = opts.getToken ?? (() => fetchRealtimeGrant(opts.grantUrl, opts.shop, opts.fetch));
|
|
4698
4701
|
this.svc.setTokenProvider(getToken);
|
|
@@ -4737,9 +4740,11 @@ var RenderSession = class {
|
|
|
4737
4740
|
}
|
|
4738
4741
|
},
|
|
4739
4742
|
onMockupRendered: () => {
|
|
4743
|
+
this.clearWatchdog();
|
|
4740
4744
|
this.mockupCb?.(this.svc.getState().mockupResults);
|
|
4741
4745
|
},
|
|
4742
4746
|
onAllMockupsRendered: (results) => {
|
|
4747
|
+
this.clearWatchdog();
|
|
4743
4748
|
this.mockupCb?.(results);
|
|
4744
4749
|
},
|
|
4745
4750
|
onError: (error) => {
|
|
@@ -4774,6 +4779,7 @@ var RenderSession = class {
|
|
|
4774
4779
|
async renderState(placement, state, throttleMs = 0) {
|
|
4775
4780
|
await this.connect();
|
|
4776
4781
|
this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
|
|
4782
|
+
this.armWatchdog();
|
|
4777
4783
|
}
|
|
4778
4784
|
/**
|
|
4779
4785
|
* Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
|
|
@@ -4785,6 +4791,7 @@ var RenderSession = class {
|
|
|
4785
4791
|
async renderSavedState(placement, stateId) {
|
|
4786
4792
|
await this.connect();
|
|
4787
4793
|
this.svc.sendCanvasStateRef(placement, stateId);
|
|
4794
|
+
this.armWatchdog();
|
|
4788
4795
|
}
|
|
4789
4796
|
/** Update only the mockup ids to render (reuses the current state). */
|
|
4790
4797
|
updateMockupIds(mockupIds) {
|
|
@@ -4797,9 +4804,33 @@ var RenderSession = class {
|
|
|
4797
4804
|
}
|
|
4798
4805
|
/** Close the WebSocket and stop auto-renew. */
|
|
4799
4806
|
close() {
|
|
4807
|
+
this.clearWatchdog();
|
|
4800
4808
|
this.svc.disconnect();
|
|
4801
4809
|
this.ready = null;
|
|
4802
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
|
+
}
|
|
4803
4834
|
/** Escape hatch: the underlying low-level service. */
|
|
4804
4835
|
get service() {
|
|
4805
4836
|
return this.svc;
|