@snowcone-app/sdk 0.3.2 → 0.3.4
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 +27 -0
- package/dist/{chunk-D5ZRGKA5.js → chunk-IMJKV4YO.js} +24 -1
- package/dist/index.cjs +105 -6
- package/dist/index.d.cts +66 -5
- package/dist/index.d.ts +66 -5
- package/dist/index.js +81 -6
- package/dist/react.cjs +24 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{websocket-Poy8LZNA.d.cts → websocket-DTRStLRH.d.cts} +6 -2
- package/dist/{websocket-Poy8LZNA.d.ts → websocket-DTRStLRH.d.ts} +6 -2
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#288](https://github.com/snowcone-app/snowcone-monorepo/pull/288) [`7ccfe17`](https://github.com/snowcone-app/snowcone-monorepo/commit/7ccfe1724b166719e2aac4e0f5e9a2a5c2c440a4) Thanks [@kevinsproles](https://github.com/kevinsproles)! - `getProduct()` now throws an actionable error for an unknown product id/slug (naming the id, the index, and pointing to `listProducts()`) instead of a bare "Not found", so a typo'd catalog id no longer surfaces as the raw Meilisearch `document_not_found` shape.
|
|
8
|
+
|
|
9
|
+
- [#281](https://github.com/snowcone-app/snowcone-monorepo/pull/281) [`9e3d941`](https://github.com/snowcone-app/snowcone-monorepo/commit/9e3d941419b3df004feb47369692d30a4a8a2938) Thanks [@kevinsproles](https://github.com/kevinsproles)! - Coalesce duplicate realtime `connect()` calls while a grant is pending or the WebSocket is still connecting, preventing competing initial sockets and benign 1006 close noise during first render.
|
|
10
|
+
|
|
11
|
+
- [#293](https://github.com/snowcone-app/snowcone-monorepo/pull/293) [`64bcaf0`](https://github.com/snowcone-app/snowcone-monorepo/commit/64bcaf0b61dcd611a81d9a9cb013ab89dc8003f7) Thanks [@kevinsproles](https://github.com/kevinsproles)! - Stop idle canvases from continuously streaming server renders (which silently burned the realtime render budget). `serializeStateForServer` now quantizes element/artboard geometry to 0.01px so the render loop's sub-pixel float drift no longer produces a new wire state every frame, and `RealtimeMockupService` drops duplicate consecutive canvas states per placement. A naive `onChange → renderState` wiring no longer bills the integrator's realtime quota while the user is doing nothing.
|
|
12
|
+
|
|
13
|
+
- [#300](https://github.com/snowcone-app/snowcone-monorepo/pull/300) [`f80f00a`](https://github.com/snowcone-app/snowcone-monorepo/commit/f80f00a0e38631aac936bfe60de14e7d47be3e6a) Thanks [@kevinsproles](https://github.com/kevinsproles)! - `mockupUrl()` now resolves the shop id and mockup base URL by reading `process.env.NEXT_PUBLIC_*` directly instead of through an aliased `const env = process.env`. The alias defeated Next/webpack's static inlining, so in browser bundles the values came back `undefined` and the live-preview URL fell through to the `SHOP_NOT_CONFIGURED` placeholder. Consumers relying on `NEXT_PUBLIC_SNOWCONE_SHOP_ID` / `NEXT_PUBLIC_MERCH_MOCKUP_URL` env fallbacks (rather than the explicit `shop` prop / `window.snowcone`) now get the configured values.
|
|
14
|
+
|
|
15
|
+
## 0.3.3
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#268](https://github.com/snowcone-app/snowcone-monorepo/pull/268) [`dd90eba`](https://github.com/snowcone-app/snowcone-monorepo/commit/dd90eba8bbfbbaef82379cb997af84e21f7cad8f) Thanks [@kevinsproles](https://github.com/kevinsproles)! - fix(realtime): fail fast when a color/variant product is missing `variantId`
|
|
20
|
+
|
|
21
|
+
`RenderSession` now rejects a product that has a real variant axis (its
|
|
22
|
+
`options.combinations` expose 2+ distinct `variantId`s) but no valid `variantId`,
|
|
23
|
+
surfacing an immediate, actionable error at config/`connect()` time instead of
|
|
24
|
+
hanging silently for ~30s until the watchdog. Pass the catalog product's
|
|
25
|
+
`options` on `RenderProduct` to opt in. Single-variant / no-option products are
|
|
26
|
+
unaffected and still render with no `variantId`. The watchdog remains as the
|
|
27
|
+
backstop. New exports: `assertVariantSelected`, `RenderProductOptions`,
|
|
28
|
+
`RenderProductCombination`.
|
|
29
|
+
|
|
3
30
|
## 0.3.2
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
|
@@ -19,6 +19,10 @@ var RealtimeMockupService = class {
|
|
|
19
19
|
lastBlobSentAt = 0;
|
|
20
20
|
canvasBlobs = /* @__PURE__ */ new Map();
|
|
21
21
|
canvasStates = /* @__PURE__ */ new Map();
|
|
22
|
+
// Serialized JSON of the last canvas state ACTUALLY SENT per placement. Used to
|
|
23
|
+
// drop duplicate consecutive states so an idle canvas (whose onChange keeps
|
|
24
|
+
// firing identical states) doesn't stream renders and burn the realtime budget.
|
|
25
|
+
lastSentStateJson = {};
|
|
22
26
|
colors = /* @__PURE__ */ new Map();
|
|
23
27
|
lastSendTime = {};
|
|
24
28
|
throttleTimeouts = {};
|
|
@@ -39,6 +43,8 @@ var RealtimeMockupService = class {
|
|
|
39
43
|
// the WS with `?token=`, and renews ~15s before expiry via a `renew` message.
|
|
40
44
|
tokenProvider;
|
|
41
45
|
renewTimer = null;
|
|
46
|
+
isConnecting = false;
|
|
47
|
+
connectAttempt = 0;
|
|
42
48
|
setCallbacks(callbacks) {
|
|
43
49
|
this.callbacks = callbacks;
|
|
44
50
|
}
|
|
@@ -65,16 +71,21 @@ var RealtimeMockupService = class {
|
|
|
65
71
|
this.callbacks.onLog?.(message, type);
|
|
66
72
|
}
|
|
67
73
|
connect() {
|
|
68
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
74
|
+
if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING || this.isConnecting) {
|
|
69
75
|
return;
|
|
70
76
|
}
|
|
77
|
+
const attempt = ++this.connectAttempt;
|
|
78
|
+
this.isConnecting = true;
|
|
71
79
|
if (this.tokenProvider) {
|
|
72
80
|
this.tokenProvider().then((grant) => {
|
|
81
|
+
if (attempt !== this.connectAttempt || !this.isConnecting) return;
|
|
73
82
|
this.openSocket(grant.token);
|
|
74
83
|
this.scheduleRenew(grant.expiresAt);
|
|
75
84
|
}).catch((err) => {
|
|
85
|
+
if (attempt !== this.connectAttempt) return;
|
|
76
86
|
this.addLog(`Failed to obtain realtime grant: ${err}`);
|
|
77
87
|
this.status = "Disconnected";
|
|
88
|
+
this.isConnecting = false;
|
|
78
89
|
});
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
@@ -88,6 +99,7 @@ var RealtimeMockupService = class {
|
|
|
88
99
|
console.log(`[WS] connection OPENED to ${this.wsUrl}`);
|
|
89
100
|
this.addLog("WebSocket connection opened");
|
|
90
101
|
this.status = "Connected";
|
|
102
|
+
this.isConnecting = false;
|
|
91
103
|
};
|
|
92
104
|
this.ws.onmessage = (event) => {
|
|
93
105
|
try {
|
|
@@ -106,11 +118,13 @@ var RealtimeMockupService = class {
|
|
|
106
118
|
this.isConfigured = false;
|
|
107
119
|
this.configSent = false;
|
|
108
120
|
this.clearRenew();
|
|
121
|
+
this.isConnecting = false;
|
|
109
122
|
this.callbacks.onDisconnected?.();
|
|
110
123
|
};
|
|
111
124
|
this.ws.onerror = (error) => {
|
|
112
125
|
this.addLog(`WebSocket error: ${error}`);
|
|
113
126
|
this.status = "Disconnected";
|
|
127
|
+
this.isConnecting = false;
|
|
114
128
|
};
|
|
115
129
|
}
|
|
116
130
|
scheduleRenew(expiresAt) {
|
|
@@ -260,6 +274,8 @@ var RealtimeMockupService = class {
|
|
|
260
274
|
}
|
|
261
275
|
}
|
|
262
276
|
disconnect() {
|
|
277
|
+
this.connectAttempt++;
|
|
278
|
+
this.isConnecting = false;
|
|
263
279
|
this.clearRenew();
|
|
264
280
|
if (this.ws) {
|
|
265
281
|
this.ws.close();
|
|
@@ -298,6 +314,7 @@ var RealtimeMockupService = class {
|
|
|
298
314
|
this.canvasStates.clear();
|
|
299
315
|
this.colors.clear();
|
|
300
316
|
this.lastSendTime = {};
|
|
317
|
+
this.lastSentStateJson = {};
|
|
301
318
|
Object.values(this.throttleTimeouts).forEach((timeout) => clearTimeout(timeout));
|
|
302
319
|
this.throttleTimeouts = {};
|
|
303
320
|
this.requestVersion = 0;
|
|
@@ -503,6 +520,11 @@ var RealtimeMockupService = class {
|
|
|
503
520
|
console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
|
|
504
521
|
return;
|
|
505
522
|
}
|
|
523
|
+
const stateJson = JSON.stringify(state);
|
|
524
|
+
if (this.lastSentStateJson[placement] === stateJson) {
|
|
525
|
+
this.addLog(`Skipped duplicate canvas state for "${placement}" (unchanged)`, "sent");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
506
528
|
this.lastSentVersion = ++this.requestVersion;
|
|
507
529
|
this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
|
|
508
530
|
const message = JSON.stringify({
|
|
@@ -512,6 +534,7 @@ var RealtimeMockupService = class {
|
|
|
512
534
|
state
|
|
513
535
|
});
|
|
514
536
|
this.ws.send(message);
|
|
537
|
+
this.lastSentStateJson[placement] = stateJson;
|
|
515
538
|
this.lastBlobSentAt = Date.now();
|
|
516
539
|
this.addLog(`Sent canvas state for "${placement}" (${(message.length / 1024).toFixed(1)}KB, v${this.lastSentVersion})`, "sent");
|
|
517
540
|
this.callbacks.onBlobSent?.(placement);
|
package/dist/index.cjs
CHANGED
|
@@ -65,6 +65,7 @@ __export(index_exports, {
|
|
|
65
65
|
UniversalContextProvider: () => UniversalContextProvider,
|
|
66
66
|
VueAdapter: () => VueAdapter,
|
|
67
67
|
adapterRegistry: () => adapterRegistry,
|
|
68
|
+
assertVariantSelected: () => assertVariantSelected,
|
|
68
69
|
autoRegister: () => autoRegister,
|
|
69
70
|
buildMockupUrl: () => buildMockupUrl2,
|
|
70
71
|
componentRegistry: () => componentRegistry,
|
|
@@ -1796,14 +1797,12 @@ function buildMockupUrl2(options, cfg) {
|
|
|
1796
1797
|
);
|
|
1797
1798
|
}
|
|
1798
1799
|
function resolveMockupBaseUrl() {
|
|
1799
|
-
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
1800
1800
|
const winConfig = typeof window !== "undefined" && window.snowcone || {};
|
|
1801
|
-
return winConfig.mockupUrl || env?.SNOWCONE_IMAGE_URL || env?.NEXT_PUBLIC_MERCH_MOCKUP_URL || "https://cdn.snowcone.app";
|
|
1801
|
+
return winConfig.mockupUrl || typeof process !== "undefined" && process.env?.SNOWCONE_IMAGE_URL || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MERCH_MOCKUP_URL || "https://cdn.snowcone.app";
|
|
1802
1802
|
}
|
|
1803
1803
|
function resolveShop() {
|
|
1804
|
-
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
1805
1804
|
const winConfig = typeof window !== "undefined" && window.snowcone || {};
|
|
1806
|
-
return winConfig.shop || env?.SNOWCONE_SHOP_ID || env?.NEXT_PUBLIC_SNOWCONE_SHOP_ID || "SHOP_NOT_CONFIGURED";
|
|
1805
|
+
return winConfig.shop || typeof process !== "undefined" && process.env?.SNOWCONE_SHOP_ID || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_SNOWCONE_SHOP_ID || "SHOP_NOT_CONFIGURED";
|
|
1807
1806
|
}
|
|
1808
1807
|
function mockupUrl(options) {
|
|
1809
1808
|
const mockupBaseUrl = resolveMockupBaseUrl();
|
|
@@ -4836,6 +4835,10 @@ var RealtimeMockupService = class {
|
|
|
4836
4835
|
lastBlobSentAt = 0;
|
|
4837
4836
|
canvasBlobs = /* @__PURE__ */ new Map();
|
|
4838
4837
|
canvasStates = /* @__PURE__ */ new Map();
|
|
4838
|
+
// Serialized JSON of the last canvas state ACTUALLY SENT per placement. Used to
|
|
4839
|
+
// drop duplicate consecutive states so an idle canvas (whose onChange keeps
|
|
4840
|
+
// firing identical states) doesn't stream renders and burn the realtime budget.
|
|
4841
|
+
lastSentStateJson = {};
|
|
4839
4842
|
colors = /* @__PURE__ */ new Map();
|
|
4840
4843
|
lastSendTime = {};
|
|
4841
4844
|
throttleTimeouts = {};
|
|
@@ -4856,6 +4859,8 @@ var RealtimeMockupService = class {
|
|
|
4856
4859
|
// the WS with `?token=`, and renews ~15s before expiry via a `renew` message.
|
|
4857
4860
|
tokenProvider;
|
|
4858
4861
|
renewTimer = null;
|
|
4862
|
+
isConnecting = false;
|
|
4863
|
+
connectAttempt = 0;
|
|
4859
4864
|
setCallbacks(callbacks) {
|
|
4860
4865
|
this.callbacks = callbacks;
|
|
4861
4866
|
}
|
|
@@ -4882,16 +4887,21 @@ var RealtimeMockupService = class {
|
|
|
4882
4887
|
this.callbacks.onLog?.(message, type);
|
|
4883
4888
|
}
|
|
4884
4889
|
connect() {
|
|
4885
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
4890
|
+
if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING || this.isConnecting) {
|
|
4886
4891
|
return;
|
|
4887
4892
|
}
|
|
4893
|
+
const attempt = ++this.connectAttempt;
|
|
4894
|
+
this.isConnecting = true;
|
|
4888
4895
|
if (this.tokenProvider) {
|
|
4889
4896
|
this.tokenProvider().then((grant) => {
|
|
4897
|
+
if (attempt !== this.connectAttempt || !this.isConnecting) return;
|
|
4890
4898
|
this.openSocket(grant.token);
|
|
4891
4899
|
this.scheduleRenew(grant.expiresAt);
|
|
4892
4900
|
}).catch((err) => {
|
|
4901
|
+
if (attempt !== this.connectAttempt) return;
|
|
4893
4902
|
this.addLog(`Failed to obtain realtime grant: ${err}`);
|
|
4894
4903
|
this.status = "Disconnected";
|
|
4904
|
+
this.isConnecting = false;
|
|
4895
4905
|
});
|
|
4896
4906
|
return;
|
|
4897
4907
|
}
|
|
@@ -4905,6 +4915,7 @@ var RealtimeMockupService = class {
|
|
|
4905
4915
|
console.log(`[WS] connection OPENED to ${this.wsUrl}`);
|
|
4906
4916
|
this.addLog("WebSocket connection opened");
|
|
4907
4917
|
this.status = "Connected";
|
|
4918
|
+
this.isConnecting = false;
|
|
4908
4919
|
};
|
|
4909
4920
|
this.ws.onmessage = (event) => {
|
|
4910
4921
|
try {
|
|
@@ -4923,11 +4934,13 @@ var RealtimeMockupService = class {
|
|
|
4923
4934
|
this.isConfigured = false;
|
|
4924
4935
|
this.configSent = false;
|
|
4925
4936
|
this.clearRenew();
|
|
4937
|
+
this.isConnecting = false;
|
|
4926
4938
|
this.callbacks.onDisconnected?.();
|
|
4927
4939
|
};
|
|
4928
4940
|
this.ws.onerror = (error) => {
|
|
4929
4941
|
this.addLog(`WebSocket error: ${error}`);
|
|
4930
4942
|
this.status = "Disconnected";
|
|
4943
|
+
this.isConnecting = false;
|
|
4931
4944
|
};
|
|
4932
4945
|
}
|
|
4933
4946
|
scheduleRenew(expiresAt) {
|
|
@@ -5077,6 +5090,8 @@ var RealtimeMockupService = class {
|
|
|
5077
5090
|
}
|
|
5078
5091
|
}
|
|
5079
5092
|
disconnect() {
|
|
5093
|
+
this.connectAttempt++;
|
|
5094
|
+
this.isConnecting = false;
|
|
5080
5095
|
this.clearRenew();
|
|
5081
5096
|
if (this.ws) {
|
|
5082
5097
|
this.ws.close();
|
|
@@ -5115,6 +5130,7 @@ var RealtimeMockupService = class {
|
|
|
5115
5130
|
this.canvasStates.clear();
|
|
5116
5131
|
this.colors.clear();
|
|
5117
5132
|
this.lastSendTime = {};
|
|
5133
|
+
this.lastSentStateJson = {};
|
|
5118
5134
|
Object.values(this.throttleTimeouts).forEach((timeout) => clearTimeout(timeout));
|
|
5119
5135
|
this.throttleTimeouts = {};
|
|
5120
5136
|
this.requestVersion = 0;
|
|
@@ -5320,6 +5336,11 @@ var RealtimeMockupService = class {
|
|
|
5320
5336
|
console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
|
|
5321
5337
|
return;
|
|
5322
5338
|
}
|
|
5339
|
+
const stateJson = JSON.stringify(state);
|
|
5340
|
+
if (this.lastSentStateJson[placement] === stateJson) {
|
|
5341
|
+
this.addLog(`Skipped duplicate canvas state for "${placement}" (unchanged)`, "sent");
|
|
5342
|
+
return;
|
|
5343
|
+
}
|
|
5323
5344
|
this.lastSentVersion = ++this.requestVersion;
|
|
5324
5345
|
this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
|
|
5325
5346
|
const message = JSON.stringify({
|
|
@@ -5329,6 +5350,7 @@ var RealtimeMockupService = class {
|
|
|
5329
5350
|
state
|
|
5330
5351
|
});
|
|
5331
5352
|
this.ws.send(message);
|
|
5353
|
+
this.lastSentStateJson[placement] = stateJson;
|
|
5332
5354
|
this.lastBlobSentAt = Date.now();
|
|
5333
5355
|
this.addLog(`Sent canvas state for "${placement}" (${(message.length / 1024).toFixed(1)}KB, v${this.lastSentVersion})`, "sent");
|
|
5334
5356
|
this.callbacks.onBlobSent?.(placement);
|
|
@@ -5523,6 +5545,44 @@ async function fetchRealtimeGrant(grantUrl, shop, fetchImpl) {
|
|
|
5523
5545
|
}
|
|
5524
5546
|
|
|
5525
5547
|
// src/realtime/session.ts
|
|
5548
|
+
function variantIdsOf(product) {
|
|
5549
|
+
const combos = product.options?.combinations;
|
|
5550
|
+
if (!combos?.length) return [];
|
|
5551
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5552
|
+
for (const c of combos) {
|
|
5553
|
+
if (typeof c.variantId === "string" && c.variantId !== "") ids.add(c.variantId);
|
|
5554
|
+
}
|
|
5555
|
+
return [...ids];
|
|
5556
|
+
}
|
|
5557
|
+
function assertVariantSelected(product) {
|
|
5558
|
+
const variantIds = variantIdsOf(product);
|
|
5559
|
+
if (variantIds.length < 2) return;
|
|
5560
|
+
const fix = `pass product.variantId from options.combinations[].variantId (valid ids: ${variantIds.join(", ")})`;
|
|
5561
|
+
if (!product.variantId) {
|
|
5562
|
+
throw new Error(
|
|
5563
|
+
`RenderSession: product ${product.productId} requires a variantId \u2014 ${fix}`
|
|
5564
|
+
);
|
|
5565
|
+
}
|
|
5566
|
+
if (!variantIds.includes(product.variantId)) {
|
|
5567
|
+
throw new Error(
|
|
5568
|
+
`RenderSession: product ${product.productId} has an invalid variantId "${product.variantId}" \u2014 ${fix}`
|
|
5569
|
+
);
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
function warnUnknownPlacement(product, placement) {
|
|
5573
|
+
const placements = product.placements;
|
|
5574
|
+
if (!placements?.length) return null;
|
|
5575
|
+
if (placements.includes(placement)) return null;
|
|
5576
|
+
return `RenderSession: placement "${placement}" doesn't match product ${product.productId}'s placements: [${placements.join(", ")}]. Your artboard name + renderState placement arg must equal the catalog placements[].label.`;
|
|
5577
|
+
}
|
|
5578
|
+
function warnPlacementArtboardMismatch(placement, state) {
|
|
5579
|
+
const artboards = state.artboards;
|
|
5580
|
+
if (!Array.isArray(artboards) || artboards.length === 0) return null;
|
|
5581
|
+
const names = artboards.map((a) => a.name).filter((n) => typeof n === "string");
|
|
5582
|
+
if (names.length === 0) return null;
|
|
5583
|
+
if (names.includes(placement)) return null;
|
|
5584
|
+
return `RenderSession: renderState placement "${placement}" has no matching artboard in the state (artboards: [${names.join(", ")}]). The artboard name must equal the placement arg.`;
|
|
5585
|
+
}
|
|
5526
5586
|
var DEFAULT_RENDER_TIMEOUT_MS = 3e4;
|
|
5527
5587
|
var RenderSession = class {
|
|
5528
5588
|
svc;
|
|
@@ -5538,6 +5598,7 @@ var RenderSession = class {
|
|
|
5538
5598
|
if (!opts.getToken && !opts.grantUrl) {
|
|
5539
5599
|
throw new Error("RenderSession: provide `grantUrl` or `getToken` to authorize the session");
|
|
5540
5600
|
}
|
|
5601
|
+
if (opts.product) assertVariantSelected(opts.product);
|
|
5541
5602
|
this.opts = opts;
|
|
5542
5603
|
this.product = opts.product ?? null;
|
|
5543
5604
|
this.renderTimeoutMs = opts.renderTimeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
|
|
@@ -5557,6 +5618,7 @@ var RenderSession = class {
|
|
|
5557
5618
|
}
|
|
5558
5619
|
/** Set or update the product. Sends config immediately if already connected. */
|
|
5559
5620
|
setProduct(product) {
|
|
5621
|
+
assertVariantSelected(product);
|
|
5560
5622
|
this.product = product;
|
|
5561
5623
|
this.svc.sendConfig(this.toConfig(product));
|
|
5562
5624
|
}
|
|
@@ -5572,6 +5634,13 @@ var RenderSession = class {
|
|
|
5572
5634
|
);
|
|
5573
5635
|
}
|
|
5574
5636
|
const product = this.product;
|
|
5637
|
+
try {
|
|
5638
|
+
assertVariantSelected(product);
|
|
5639
|
+
} catch (err) {
|
|
5640
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5641
|
+
this.errorCb?.(message);
|
|
5642
|
+
return Promise.reject(err instanceof Error ? err : new Error(message));
|
|
5643
|
+
}
|
|
5575
5644
|
this.ready = new Promise((resolve, reject) => {
|
|
5576
5645
|
let settled = false;
|
|
5577
5646
|
this.svc.setCallbacks({
|
|
@@ -5623,6 +5692,7 @@ var RenderSession = class {
|
|
|
5623
5692
|
*/
|
|
5624
5693
|
async renderState(placement, state, throttleMs = 0) {
|
|
5625
5694
|
await this.connect();
|
|
5695
|
+
this.warnPlacement(placement, state);
|
|
5626
5696
|
this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
|
|
5627
5697
|
this.armWatchdog();
|
|
5628
5698
|
}
|
|
@@ -5635,6 +5705,7 @@ var RenderSession = class {
|
|
|
5635
5705
|
*/
|
|
5636
5706
|
async renderSavedState(placement, stateId) {
|
|
5637
5707
|
await this.connect();
|
|
5708
|
+
this.warnPlacement(placement);
|
|
5638
5709
|
this.svc.sendCanvasStateRef(placement, stateId);
|
|
5639
5710
|
this.armWatchdog();
|
|
5640
5711
|
}
|
|
@@ -5653,6 +5724,28 @@ var RenderSession = class {
|
|
|
5653
5724
|
this.svc.disconnect();
|
|
5654
5725
|
this.ready = null;
|
|
5655
5726
|
}
|
|
5727
|
+
/**
|
|
5728
|
+
* Emit loud, non-fatal warnings for the two ways a placement name silently
|
|
5729
|
+
* produces a blank mockup: (1) the name isn't one of the product's known
|
|
5730
|
+
* `placements` labels, and (2) no artboard in `state` is named after it. Each
|
|
5731
|
+
* warning goes to BOTH `console.warn` and the `onError` callback (if wired) so
|
|
5732
|
+
* a dev sees it whichever channel they watch. Never throws.
|
|
5733
|
+
*/
|
|
5734
|
+
warnPlacement(placement, state) {
|
|
5735
|
+
const warnings = [];
|
|
5736
|
+
if (this.product) {
|
|
5737
|
+
const w = warnUnknownPlacement(this.product, placement);
|
|
5738
|
+
if (w) warnings.push(w);
|
|
5739
|
+
}
|
|
5740
|
+
if (state) {
|
|
5741
|
+
const w = warnPlacementArtboardMismatch(placement, state);
|
|
5742
|
+
if (w) warnings.push(w);
|
|
5743
|
+
}
|
|
5744
|
+
for (const w of warnings) {
|
|
5745
|
+
console.warn(w);
|
|
5746
|
+
this.errorCb?.(w);
|
|
5747
|
+
}
|
|
5748
|
+
}
|
|
5656
5749
|
/**
|
|
5657
5750
|
* (Re)arm the render watchdog. Clears any prior timer and, unless disabled
|
|
5658
5751
|
* (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
|
|
@@ -5768,7 +5861,12 @@ async function getProduct(idOrSlug, config) {
|
|
|
5768
5861
|
const url = `${meilisearchHost}/indexes/${meilisearchIndex}/documents/${idOrSlug}`;
|
|
5769
5862
|
const res = await f(url, { method: "GET", headers });
|
|
5770
5863
|
if (res.status === 404)
|
|
5771
|
-
throw Object.assign(
|
|
5864
|
+
throw Object.assign(
|
|
5865
|
+
new Error(
|
|
5866
|
+
`getProduct: no catalog product "${idOrSlug}" in index "${meilisearchIndex}". Pass a valid product id/slug (browse with listProducts()).`
|
|
5867
|
+
),
|
|
5868
|
+
{ code: "NOT_FOUND", productId: idOrSlug }
|
|
5869
|
+
);
|
|
5772
5870
|
if (!res.ok) throw new Error(`getProduct failed: ${res.status}`);
|
|
5773
5871
|
const raw = await res.json();
|
|
5774
5872
|
return validateProductLoose(raw);
|
|
@@ -5831,6 +5929,7 @@ function createClient(config) {
|
|
|
5831
5929
|
UniversalContextProvider,
|
|
5832
5930
|
VueAdapter,
|
|
5833
5931
|
adapterRegistry,
|
|
5932
|
+
assertVariantSelected,
|
|
5834
5933
|
autoRegister,
|
|
5835
5934
|
buildMockupUrl,
|
|
5836
5935
|
componentRegistry,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createDevFetcher } from './dev-fetcher.cjs';
|
|
2
2
|
import { PublicDesign, PublicFill, PublicOptions } from '@snowcone-app/mockup-url';
|
|
3
|
-
import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-
|
|
4
|
-
export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-
|
|
3
|
+
import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-DTRStLRH.cjs';
|
|
4
|
+
export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-DTRStLRH.cjs';
|
|
5
5
|
|
|
6
6
|
interface CatalogProduct$2 {
|
|
7
7
|
id: string;
|
|
@@ -2584,8 +2584,7 @@ declare function fetchRealtimeGrant(grantUrl: string, shop: string, fetchImpl?:
|
|
|
2584
2584
|
* product: { productId: 'BEEB77', mockupIds: ['front'] },
|
|
2585
2585
|
* });
|
|
2586
2586
|
* session.onMockups((results) => { img.src = results[0].imageUrl; });
|
|
2587
|
-
* await session.
|
|
2588
|
-
* session.renderState('Front', serializeStateForServer(canvasState));
|
|
2587
|
+
* await session.renderState('Front', serializeStateForServer(canvasState));
|
|
2589
2588
|
*
|
|
2590
2589
|
* For the low-level escape hatch, use {@link RealtimeMockupService} directly.
|
|
2591
2590
|
*/
|
|
@@ -2604,6 +2603,29 @@ interface RenderState {
|
|
|
2604
2603
|
elements?: Array<Record<string, unknown>>;
|
|
2605
2604
|
[key: string]: unknown;
|
|
2606
2605
|
}
|
|
2606
|
+
/**
|
|
2607
|
+
* A single variant combination from the catalog product's `options.combinations`.
|
|
2608
|
+
* Each combination pins one value per variant-affecting attribute plus the
|
|
2609
|
+
* `variantId` (gvid) that selection resolves to. Pass `options` on
|
|
2610
|
+
* {@link RenderProduct} so the session can FAIL FAST when a required `variantId`
|
|
2611
|
+
* is missing instead of silently hanging until the watchdog.
|
|
2612
|
+
*/
|
|
2613
|
+
interface RenderProductCombination {
|
|
2614
|
+
/** The variant (gvid) this combination resolves to. */
|
|
2615
|
+
variantId?: string;
|
|
2616
|
+
/** Attribute selections (e.g. `{ Frame: 'Walnut', Size: '5x7' }`). */
|
|
2617
|
+
[attribute: string]: string | number | undefined;
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2620
|
+
* The catalog product's `options` block (structurally typed). Only
|
|
2621
|
+
* `combinations` is consumed by the session — to detect whether the product has
|
|
2622
|
+
* a real variant axis (multiple distinct `variantId`s) so a missing/invalid
|
|
2623
|
+
* {@link RenderProduct.variantId} can be rejected up front.
|
|
2624
|
+
*/
|
|
2625
|
+
interface RenderProductOptions {
|
|
2626
|
+
combinations?: RenderProductCombination[];
|
|
2627
|
+
[key: string]: unknown;
|
|
2628
|
+
}
|
|
2607
2629
|
/** Product the session renders against (Snowcone catalog ids). */
|
|
2608
2630
|
interface RenderProduct {
|
|
2609
2631
|
productId: string;
|
|
@@ -2618,13 +2640,44 @@ interface RenderProduct {
|
|
|
2618
2640
|
* Optional variant (gvid). Omit if the product has no variant axis. Required
|
|
2619
2641
|
* for products with a color option — it auto-fills the color placement; without
|
|
2620
2642
|
* it the render waits for a color blob that never comes.
|
|
2643
|
+
*
|
|
2644
|
+
* If you also pass {@link RenderProduct.options}, the session validates this
|
|
2645
|
+
* up front: a product whose `options.combinations` exposes more than one
|
|
2646
|
+
* variant REQUIRES a `variantId` (and it must be one of the combinations'),
|
|
2647
|
+
* else the session throws immediately rather than hanging until the watchdog.
|
|
2621
2648
|
*/
|
|
2622
2649
|
variantId?: string;
|
|
2650
|
+
/**
|
|
2651
|
+
* The catalog product's `options` block (its `options.combinations[].variantId`
|
|
2652
|
+
* are the valid variant ids). Optional, but passing it lets the session detect
|
|
2653
|
+
* a missing/invalid required `variantId` and FAIL FAST — turning the classic
|
|
2654
|
+
* 30s silent hang into an immediate, actionable error. You can pass the whole
|
|
2655
|
+
* catalog product's `options` object verbatim.
|
|
2656
|
+
*/
|
|
2657
|
+
options?: RenderProductOptions;
|
|
2658
|
+
/**
|
|
2659
|
+
* The product's valid placement labels (the catalog product's
|
|
2660
|
+
* `placements[].label`, e.g. `['Front']`). Optional, but passing it lets the
|
|
2661
|
+
* session WARN LOUDLY when a `renderState(placement, ...)` call uses a name
|
|
2662
|
+
* that isn't a real placement — turning the classic silent blank-mockup
|
|
2663
|
+
* (server waits on `incomplete_canvas_placements`) into an actionable
|
|
2664
|
+
* dev-time message. You can pass the catalog product's
|
|
2665
|
+
* `placements.map(p => p.label)` verbatim.
|
|
2666
|
+
*/
|
|
2667
|
+
placements?: string[];
|
|
2623
2668
|
/** Render width in px (default 1000). */
|
|
2624
2669
|
width?: number;
|
|
2625
2670
|
/** Per-placement settings (tiling, scale mode). */
|
|
2626
2671
|
placementSettings?: Record<string, PlacementSettings>;
|
|
2627
2672
|
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Throws immediately if the product has a variant axis (its
|
|
2675
|
+
* `options.combinations` expose 2+ distinct `variantId`s) but no valid
|
|
2676
|
+
* `variantId` was supplied. This is the fail-fast guard for the otherwise-silent
|
|
2677
|
+
* 30s render hang on color/variant products. Products with no `options` (or a
|
|
2678
|
+
* single variant) are left untouched — they render fine with no `variantId`.
|
|
2679
|
+
*/
|
|
2680
|
+
declare function assertVariantSelected(product: RenderProduct): void;
|
|
2628
2681
|
interface RenderSessionOptions {
|
|
2629
2682
|
/** Publishable shop id (= shop.id, like Stripe pk_). */
|
|
2630
2683
|
shop: string;
|
|
@@ -2712,6 +2765,14 @@ declare class RenderSession {
|
|
|
2712
2765
|
getMockups(): MockupResult[];
|
|
2713
2766
|
/** Close the WebSocket and stop auto-renew. */
|
|
2714
2767
|
close(): void;
|
|
2768
|
+
/**
|
|
2769
|
+
* Emit loud, non-fatal warnings for the two ways a placement name silently
|
|
2770
|
+
* produces a blank mockup: (1) the name isn't one of the product's known
|
|
2771
|
+
* `placements` labels, and (2) no artboard in `state` is named after it. Each
|
|
2772
|
+
* warning goes to BOTH `console.warn` and the `onError` callback (if wired) so
|
|
2773
|
+
* a dev sees it whichever channel they watch. Never throws.
|
|
2774
|
+
*/
|
|
2775
|
+
private warnPlacement;
|
|
2715
2776
|
/**
|
|
2716
2777
|
* (Re)arm the render watchdog. Clears any prior timer and, unless disabled
|
|
2717
2778
|
* (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
|
|
@@ -2819,4 +2880,4 @@ declare function getProduct(idOrSlug: string, config?: Partial<SdkConfig>): Prom
|
|
|
2819
2880
|
*/
|
|
2820
2881
|
declare function createClient(config: SdkConfig): SnowconeClient;
|
|
2821
2882
|
|
|
2822
|
-
export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, type CreateDesignStateInput, type CreatedDesignState, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createDesignState, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
|
|
2883
|
+
export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, type CreateDesignStateInput, type CreatedDesignState, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, type RenderProductCombination, type RenderProductOptions, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, assertVariantSelected, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createDesignState, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { createDevFetcher } from './dev-fetcher.js';
|
|
2
2
|
import { PublicDesign, PublicFill, PublicOptions } from '@snowcone-app/mockup-url';
|
|
3
|
-
import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-
|
|
4
|
-
export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-
|
|
3
|
+
import { O as OptionAttribute, a as OptionSelection, C as Combination, F as FrameworkAdapter, b as ComponentProps, c as ComponentDescriptor, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, g as FrameworkUtilities, P as PlacementSettings, M as MockupResult, R as RealtimeMockupService } from './websocket-DTRStLRH.js';
|
|
4
|
+
export { A as AdapterRegistry, h as ProductComponentContext, i as RealtimeMockupCallbacks, j as RealtimeMockupState, k as RenderResult, W as WebSocketConfig, l as WebSocketMessage, m as adapterRegistry, n as computeDisabledChoices, o as createComponent, p as defineComponent, q as deriveDefaultSelection, r as findBestCombination, s as getPricePreview, t as isOptionAvailable, u as resolveBestCombination } from './websocket-DTRStLRH.js';
|
|
5
5
|
|
|
6
6
|
interface CatalogProduct$2 {
|
|
7
7
|
id: string;
|
|
@@ -2584,8 +2584,7 @@ declare function fetchRealtimeGrant(grantUrl: string, shop: string, fetchImpl?:
|
|
|
2584
2584
|
* product: { productId: 'BEEB77', mockupIds: ['front'] },
|
|
2585
2585
|
* });
|
|
2586
2586
|
* session.onMockups((results) => { img.src = results[0].imageUrl; });
|
|
2587
|
-
* await session.
|
|
2588
|
-
* session.renderState('Front', serializeStateForServer(canvasState));
|
|
2587
|
+
* await session.renderState('Front', serializeStateForServer(canvasState));
|
|
2589
2588
|
*
|
|
2590
2589
|
* For the low-level escape hatch, use {@link RealtimeMockupService} directly.
|
|
2591
2590
|
*/
|
|
@@ -2604,6 +2603,29 @@ interface RenderState {
|
|
|
2604
2603
|
elements?: Array<Record<string, unknown>>;
|
|
2605
2604
|
[key: string]: unknown;
|
|
2606
2605
|
}
|
|
2606
|
+
/**
|
|
2607
|
+
* A single variant combination from the catalog product's `options.combinations`.
|
|
2608
|
+
* Each combination pins one value per variant-affecting attribute plus the
|
|
2609
|
+
* `variantId` (gvid) that selection resolves to. Pass `options` on
|
|
2610
|
+
* {@link RenderProduct} so the session can FAIL FAST when a required `variantId`
|
|
2611
|
+
* is missing instead of silently hanging until the watchdog.
|
|
2612
|
+
*/
|
|
2613
|
+
interface RenderProductCombination {
|
|
2614
|
+
/** The variant (gvid) this combination resolves to. */
|
|
2615
|
+
variantId?: string;
|
|
2616
|
+
/** Attribute selections (e.g. `{ Frame: 'Walnut', Size: '5x7' }`). */
|
|
2617
|
+
[attribute: string]: string | number | undefined;
|
|
2618
|
+
}
|
|
2619
|
+
/**
|
|
2620
|
+
* The catalog product's `options` block (structurally typed). Only
|
|
2621
|
+
* `combinations` is consumed by the session — to detect whether the product has
|
|
2622
|
+
* a real variant axis (multiple distinct `variantId`s) so a missing/invalid
|
|
2623
|
+
* {@link RenderProduct.variantId} can be rejected up front.
|
|
2624
|
+
*/
|
|
2625
|
+
interface RenderProductOptions {
|
|
2626
|
+
combinations?: RenderProductCombination[];
|
|
2627
|
+
[key: string]: unknown;
|
|
2628
|
+
}
|
|
2607
2629
|
/** Product the session renders against (Snowcone catalog ids). */
|
|
2608
2630
|
interface RenderProduct {
|
|
2609
2631
|
productId: string;
|
|
@@ -2618,13 +2640,44 @@ interface RenderProduct {
|
|
|
2618
2640
|
* Optional variant (gvid). Omit if the product has no variant axis. Required
|
|
2619
2641
|
* for products with a color option — it auto-fills the color placement; without
|
|
2620
2642
|
* it the render waits for a color blob that never comes.
|
|
2643
|
+
*
|
|
2644
|
+
* If you also pass {@link RenderProduct.options}, the session validates this
|
|
2645
|
+
* up front: a product whose `options.combinations` exposes more than one
|
|
2646
|
+
* variant REQUIRES a `variantId` (and it must be one of the combinations'),
|
|
2647
|
+
* else the session throws immediately rather than hanging until the watchdog.
|
|
2621
2648
|
*/
|
|
2622
2649
|
variantId?: string;
|
|
2650
|
+
/**
|
|
2651
|
+
* The catalog product's `options` block (its `options.combinations[].variantId`
|
|
2652
|
+
* are the valid variant ids). Optional, but passing it lets the session detect
|
|
2653
|
+
* a missing/invalid required `variantId` and FAIL FAST — turning the classic
|
|
2654
|
+
* 30s silent hang into an immediate, actionable error. You can pass the whole
|
|
2655
|
+
* catalog product's `options` object verbatim.
|
|
2656
|
+
*/
|
|
2657
|
+
options?: RenderProductOptions;
|
|
2658
|
+
/**
|
|
2659
|
+
* The product's valid placement labels (the catalog product's
|
|
2660
|
+
* `placements[].label`, e.g. `['Front']`). Optional, but passing it lets the
|
|
2661
|
+
* session WARN LOUDLY when a `renderState(placement, ...)` call uses a name
|
|
2662
|
+
* that isn't a real placement — turning the classic silent blank-mockup
|
|
2663
|
+
* (server waits on `incomplete_canvas_placements`) into an actionable
|
|
2664
|
+
* dev-time message. You can pass the catalog product's
|
|
2665
|
+
* `placements.map(p => p.label)` verbatim.
|
|
2666
|
+
*/
|
|
2667
|
+
placements?: string[];
|
|
2623
2668
|
/** Render width in px (default 1000). */
|
|
2624
2669
|
width?: number;
|
|
2625
2670
|
/** Per-placement settings (tiling, scale mode). */
|
|
2626
2671
|
placementSettings?: Record<string, PlacementSettings>;
|
|
2627
2672
|
}
|
|
2673
|
+
/**
|
|
2674
|
+
* Throws immediately if the product has a variant axis (its
|
|
2675
|
+
* `options.combinations` expose 2+ distinct `variantId`s) but no valid
|
|
2676
|
+
* `variantId` was supplied. This is the fail-fast guard for the otherwise-silent
|
|
2677
|
+
* 30s render hang on color/variant products. Products with no `options` (or a
|
|
2678
|
+
* single variant) are left untouched — they render fine with no `variantId`.
|
|
2679
|
+
*/
|
|
2680
|
+
declare function assertVariantSelected(product: RenderProduct): void;
|
|
2628
2681
|
interface RenderSessionOptions {
|
|
2629
2682
|
/** Publishable shop id (= shop.id, like Stripe pk_). */
|
|
2630
2683
|
shop: string;
|
|
@@ -2712,6 +2765,14 @@ declare class RenderSession {
|
|
|
2712
2765
|
getMockups(): MockupResult[];
|
|
2713
2766
|
/** Close the WebSocket and stop auto-renew. */
|
|
2714
2767
|
close(): void;
|
|
2768
|
+
/**
|
|
2769
|
+
* Emit loud, non-fatal warnings for the two ways a placement name silently
|
|
2770
|
+
* produces a blank mockup: (1) the name isn't one of the product's known
|
|
2771
|
+
* `placements` labels, and (2) no artboard in `state` is named after it. Each
|
|
2772
|
+
* warning goes to BOTH `console.warn` and the `onError` callback (if wired) so
|
|
2773
|
+
* a dev sees it whichever channel they watch. Never throws.
|
|
2774
|
+
*/
|
|
2775
|
+
private warnPlacement;
|
|
2715
2776
|
/**
|
|
2716
2777
|
* (Re)arm the render watchdog. Clears any prior timer and, unless disabled
|
|
2717
2778
|
* (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
|
|
@@ -2819,4 +2880,4 @@ declare function getProduct(idOrSlug: string, config?: Partial<SdkConfig>): Prom
|
|
|
2819
2880
|
*/
|
|
2820
2881
|
declare function createClient(config: SdkConfig): SnowconeClient;
|
|
2821
2882
|
|
|
2822
|
-
export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, type CreateDesignStateInput, type CreatedDesignState, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createDesignState, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
|
|
2883
|
+
export { AbstractTemplateRenderer, type AddToCartOptions, Animations, type ArtSelectorContext, type ArtSelectorDescriptor, type ArtSelectorOptions, type ArtworkData, type AspectRatio, Attributes, type BaseDescriptor, type BlendConfig, type BuildMockupUrlConfig, type CartDetail, type CatalogListResponse, type CatalogProduct$2 as CatalogProduct, ClassNames, type ColorDesignElement, ColorPickerUtils, Combination, CommonProps, ComponentContext, type ComponentDefinition, ComponentDescriptor, ComponentEventManager, ComponentFactory, ComponentLifecycle, ComponentLifecycleHooks, type ComponentMetadata, ComponentProps, ComponentRegistry, ComponentState, type ComponentTemplate, ComponentTemplates, type ContainerDescriptor, ContextBridge, type ContextComparator, type ContextConsumer, ContextInjector, type ContextProviderConfig, type ContextSubscriber, ContextSynchronizer, type CreateDesignStateInput, type CreatedDesignState, DEFAULT_ARTWORK_URL, DEFAULT_ASPECT_RATIO, DEFAULT_COLOR, DEFAULT_GRANT_BASE, DEFAULT_MOCKUP_BASE, DEFAULT_PLACEMENT_DIMENSIONS, type Descriptor, type Design, type DesignElement, type DesignGenerationContext, type Effects, Elements, type ErrorContext, type ErrorHandler, ErrorManager, type EventDefinition, EventDelegator, EventEmitter, EventHandler, type EventListener, type EventPayload, type Fill, Focus, FrameworkAdapter, FrameworkUtilities, type GetMockupUrlOptions, type ImageAlignment, type ImageDesignElement, type LifecycleHook, LifecycleManager, type LifecyclePhase, type LifecycleTransition, LitAdapter, type MaskOverride, type MockupGenerationOptions, MockupResult, type MockupService, type MockupServiceConfig, OptionAttribute, type OptionChoice, type OptionChoiceRenderData, type OptionRenderData, OptionSelection, type Placement, type PlacementDesign, PlacementSettings, type ProductArtAlignmentContext, type ProductArtAlignmentDescriptor, type ProductArtAlignmentOptions, type ProductContext, type ProductContextEvents, ProductContextManager, type ProductData, type ProductFetcher, type ProductImageDescriptor, type ProductImageOptions, ProductLoader, type ProductMockupData, type ProductOptionChoice, type ProductOptionData, type ProductOptionsDescriptor, type ProductOptionsOptions, type ProductPlacement, type ProductPriceDescriptor, type ProductPriceOptions, ProductProps, type ProductSpec, type ProductTitleDescriptor, type ProductTitleOptions, type ProductVariant, type PropDefinition, type PropSchema, type PropType, PropertyManager, REALTIME_WS_URL, type RateLimitState, type RealtimeGrant, RealtimeMockupService, type RegularArtwork, type RenderProduct, type RenderProductCombination, type RenderProductOptions, RenderSession, type RenderSessionOptions, type RenderState, type SdkConfig, type SeamlessPattern, type SignedUrlResponse, type SnowconeClient, StandardComponents, StandardEvents, StateManager, type StateManagerOptions, type StateMiddleware, type StateSelector, type StateSubscriber, type StateUpdater, Styles, SvelteAdapter, SwatchUtils, type TagName, TemplateBuilder, type TemplateNode, type TemplateNodeType, type TemplateRenderer, type TextDescriptor, type TileCount, UniversalContextProvider, type UserSelection, type ValidationError, type ValidationResult, VueAdapter, assertVariantSelected, autoRegister, buildMockupUrl, componentRegistry, createAddToCartEvent, createAddToCartHandler, createCartDetail, createClient, createContextProvider, createDesignForPlacements, createDesignState, createErrorHandler, createEventManager, createLifecycle, createLitComponent, createProductContext, createProductLoader, createPropertyManager, createStateHook, createStateStore, createSvelteComponent, createUniversalProvider, createVueComponent, describeArtSelector, describeProductArtAlignment, describeProductImage, describeProductOptions, describeProductPrice, describeProductTitle, extractProductId, fetchRealtimeGrant, filterImagePlacements, findClosestSnapPoint, findVariantForSelection, formatPrice, formatValidationErrors, getDefaultVariantId, getEffectiveAlignment, getIncompleteSelectionMessage, getMissingSelections, getMockupUrl, getOptionRenderType, getProduct, getSelectionDisplayText, getSnapPoints, getVariant, handleOptionChange, isSelectionComplete, isValidAlignment, isValidTileCount, listProducts, mintRealtimeGrant, mockupUrl, normalizeChoice, prepareOptionRenderData, registerStandardComponents, resolveMockupId, resolveVariantId, retryOperation, simulateCartOperation, toCombinations, toOptionAttributes, useFrameworkAdapter as useVueAdapter, validateAlignment, validateDesignElement, validateEffects, validateImageUrl, validateMockupOptions, validateProductSelection, validateProductSpec, validateRequiredOptions, validateTileCount, withContext, withErrorHandling, withSyncErrorHandling };
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
REALTIME_WS_URL,
|
|
6
6
|
RealtimeMockupService
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IMJKV4YO.js";
|
|
8
8
|
|
|
9
9
|
// src/validation.ts
|
|
10
10
|
import { z } from "zod";
|
|
@@ -1642,14 +1642,12 @@ function buildMockupUrl2(options, cfg) {
|
|
|
1642
1642
|
);
|
|
1643
1643
|
}
|
|
1644
1644
|
function resolveMockupBaseUrl() {
|
|
1645
|
-
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
1646
1645
|
const winConfig = typeof window !== "undefined" && window.snowcone || {};
|
|
1647
|
-
return winConfig.mockupUrl || env?.SNOWCONE_IMAGE_URL || env?.NEXT_PUBLIC_MERCH_MOCKUP_URL || "https://cdn.snowcone.app";
|
|
1646
|
+
return winConfig.mockupUrl || typeof process !== "undefined" && process.env?.SNOWCONE_IMAGE_URL || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MERCH_MOCKUP_URL || "https://cdn.snowcone.app";
|
|
1648
1647
|
}
|
|
1649
1648
|
function resolveShop() {
|
|
1650
|
-
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
1651
1649
|
const winConfig = typeof window !== "undefined" && window.snowcone || {};
|
|
1652
|
-
return winConfig.shop || env?.SNOWCONE_SHOP_ID || env?.NEXT_PUBLIC_SNOWCONE_SHOP_ID || "SHOP_NOT_CONFIGURED";
|
|
1650
|
+
return winConfig.shop || typeof process !== "undefined" && process.env?.SNOWCONE_SHOP_ID || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_SNOWCONE_SHOP_ID || "SHOP_NOT_CONFIGURED";
|
|
1653
1651
|
}
|
|
1654
1652
|
function mockupUrl(options) {
|
|
1655
1653
|
const mockupBaseUrl = resolveMockupBaseUrl();
|
|
@@ -4690,6 +4688,44 @@ async function fetchRealtimeGrant(grantUrl, shop, fetchImpl) {
|
|
|
4690
4688
|
}
|
|
4691
4689
|
|
|
4692
4690
|
// src/realtime/session.ts
|
|
4691
|
+
function variantIdsOf(product) {
|
|
4692
|
+
const combos = product.options?.combinations;
|
|
4693
|
+
if (!combos?.length) return [];
|
|
4694
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4695
|
+
for (const c of combos) {
|
|
4696
|
+
if (typeof c.variantId === "string" && c.variantId !== "") ids.add(c.variantId);
|
|
4697
|
+
}
|
|
4698
|
+
return [...ids];
|
|
4699
|
+
}
|
|
4700
|
+
function assertVariantSelected(product) {
|
|
4701
|
+
const variantIds = variantIdsOf(product);
|
|
4702
|
+
if (variantIds.length < 2) return;
|
|
4703
|
+
const fix = `pass product.variantId from options.combinations[].variantId (valid ids: ${variantIds.join(", ")})`;
|
|
4704
|
+
if (!product.variantId) {
|
|
4705
|
+
throw new Error(
|
|
4706
|
+
`RenderSession: product ${product.productId} requires a variantId \u2014 ${fix}`
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4709
|
+
if (!variantIds.includes(product.variantId)) {
|
|
4710
|
+
throw new Error(
|
|
4711
|
+
`RenderSession: product ${product.productId} has an invalid variantId "${product.variantId}" \u2014 ${fix}`
|
|
4712
|
+
);
|
|
4713
|
+
}
|
|
4714
|
+
}
|
|
4715
|
+
function warnUnknownPlacement(product, placement) {
|
|
4716
|
+
const placements = product.placements;
|
|
4717
|
+
if (!placements?.length) return null;
|
|
4718
|
+
if (placements.includes(placement)) return null;
|
|
4719
|
+
return `RenderSession: placement "${placement}" doesn't match product ${product.productId}'s placements: [${placements.join(", ")}]. Your artboard name + renderState placement arg must equal the catalog placements[].label.`;
|
|
4720
|
+
}
|
|
4721
|
+
function warnPlacementArtboardMismatch(placement, state) {
|
|
4722
|
+
const artboards = state.artboards;
|
|
4723
|
+
if (!Array.isArray(artboards) || artboards.length === 0) return null;
|
|
4724
|
+
const names = artboards.map((a) => a.name).filter((n) => typeof n === "string");
|
|
4725
|
+
if (names.length === 0) return null;
|
|
4726
|
+
if (names.includes(placement)) return null;
|
|
4727
|
+
return `RenderSession: renderState placement "${placement}" has no matching artboard in the state (artboards: [${names.join(", ")}]). The artboard name must equal the placement arg.`;
|
|
4728
|
+
}
|
|
4693
4729
|
var DEFAULT_RENDER_TIMEOUT_MS = 3e4;
|
|
4694
4730
|
var RenderSession = class {
|
|
4695
4731
|
svc;
|
|
@@ -4705,6 +4741,7 @@ var RenderSession = class {
|
|
|
4705
4741
|
if (!opts.getToken && !opts.grantUrl) {
|
|
4706
4742
|
throw new Error("RenderSession: provide `grantUrl` or `getToken` to authorize the session");
|
|
4707
4743
|
}
|
|
4744
|
+
if (opts.product) assertVariantSelected(opts.product);
|
|
4708
4745
|
this.opts = opts;
|
|
4709
4746
|
this.product = opts.product ?? null;
|
|
4710
4747
|
this.renderTimeoutMs = opts.renderTimeoutMs ?? DEFAULT_RENDER_TIMEOUT_MS;
|
|
@@ -4724,6 +4761,7 @@ var RenderSession = class {
|
|
|
4724
4761
|
}
|
|
4725
4762
|
/** Set or update the product. Sends config immediately if already connected. */
|
|
4726
4763
|
setProduct(product) {
|
|
4764
|
+
assertVariantSelected(product);
|
|
4727
4765
|
this.product = product;
|
|
4728
4766
|
this.svc.sendConfig(this.toConfig(product));
|
|
4729
4767
|
}
|
|
@@ -4739,6 +4777,13 @@ var RenderSession = class {
|
|
|
4739
4777
|
);
|
|
4740
4778
|
}
|
|
4741
4779
|
const product = this.product;
|
|
4780
|
+
try {
|
|
4781
|
+
assertVariantSelected(product);
|
|
4782
|
+
} catch (err) {
|
|
4783
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4784
|
+
this.errorCb?.(message);
|
|
4785
|
+
return Promise.reject(err instanceof Error ? err : new Error(message));
|
|
4786
|
+
}
|
|
4742
4787
|
this.ready = new Promise((resolve, reject) => {
|
|
4743
4788
|
let settled = false;
|
|
4744
4789
|
this.svc.setCallbacks({
|
|
@@ -4790,6 +4835,7 @@ var RenderSession = class {
|
|
|
4790
4835
|
*/
|
|
4791
4836
|
async renderState(placement, state, throttleMs = 0) {
|
|
4792
4837
|
await this.connect();
|
|
4838
|
+
this.warnPlacement(placement, state);
|
|
4793
4839
|
this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
|
|
4794
4840
|
this.armWatchdog();
|
|
4795
4841
|
}
|
|
@@ -4802,6 +4848,7 @@ var RenderSession = class {
|
|
|
4802
4848
|
*/
|
|
4803
4849
|
async renderSavedState(placement, stateId) {
|
|
4804
4850
|
await this.connect();
|
|
4851
|
+
this.warnPlacement(placement);
|
|
4805
4852
|
this.svc.sendCanvasStateRef(placement, stateId);
|
|
4806
4853
|
this.armWatchdog();
|
|
4807
4854
|
}
|
|
@@ -4820,6 +4867,28 @@ var RenderSession = class {
|
|
|
4820
4867
|
this.svc.disconnect();
|
|
4821
4868
|
this.ready = null;
|
|
4822
4869
|
}
|
|
4870
|
+
/**
|
|
4871
|
+
* Emit loud, non-fatal warnings for the two ways a placement name silently
|
|
4872
|
+
* produces a blank mockup: (1) the name isn't one of the product's known
|
|
4873
|
+
* `placements` labels, and (2) no artboard in `state` is named after it. Each
|
|
4874
|
+
* warning goes to BOTH `console.warn` and the `onError` callback (if wired) so
|
|
4875
|
+
* a dev sees it whichever channel they watch. Never throws.
|
|
4876
|
+
*/
|
|
4877
|
+
warnPlacement(placement, state) {
|
|
4878
|
+
const warnings = [];
|
|
4879
|
+
if (this.product) {
|
|
4880
|
+
const w = warnUnknownPlacement(this.product, placement);
|
|
4881
|
+
if (w) warnings.push(w);
|
|
4882
|
+
}
|
|
4883
|
+
if (state) {
|
|
4884
|
+
const w = warnPlacementArtboardMismatch(placement, state);
|
|
4885
|
+
if (w) warnings.push(w);
|
|
4886
|
+
}
|
|
4887
|
+
for (const w of warnings) {
|
|
4888
|
+
console.warn(w);
|
|
4889
|
+
this.errorCb?.(w);
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4823
4892
|
/**
|
|
4824
4893
|
* (Re)arm the render watchdog. Clears any prior timer and, unless disabled
|
|
4825
4894
|
* (`renderTimeoutMs <= 0`), starts a fresh one. If it fires before a mockup
|
|
@@ -4935,7 +5004,12 @@ async function getProduct(idOrSlug, config) {
|
|
|
4935
5004
|
const url = `${meilisearchHost}/indexes/${meilisearchIndex}/documents/${idOrSlug}`;
|
|
4936
5005
|
const res = await f(url, { method: "GET", headers });
|
|
4937
5006
|
if (res.status === 404)
|
|
4938
|
-
throw Object.assign(
|
|
5007
|
+
throw Object.assign(
|
|
5008
|
+
new Error(
|
|
5009
|
+
`getProduct: no catalog product "${idOrSlug}" in index "${meilisearchIndex}". Pass a valid product id/slug (browse with listProducts()).`
|
|
5010
|
+
),
|
|
5011
|
+
{ code: "NOT_FOUND", productId: idOrSlug }
|
|
5012
|
+
);
|
|
4939
5013
|
if (!res.ok) throw new Error(`getProduct failed: ${res.status}`);
|
|
4940
5014
|
const raw = await res.json();
|
|
4941
5015
|
return validateProductLoose(raw);
|
|
@@ -4997,6 +5071,7 @@ export {
|
|
|
4997
5071
|
UniversalContextProvider,
|
|
4998
5072
|
VueAdapter,
|
|
4999
5073
|
adapterRegistry,
|
|
5074
|
+
assertVariantSelected,
|
|
5000
5075
|
autoRegister,
|
|
5001
5076
|
buildMockupUrl2 as buildMockupUrl,
|
|
5002
5077
|
componentRegistry,
|
package/dist/react.cjs
CHANGED
|
@@ -51,6 +51,10 @@ var RealtimeMockupService = class {
|
|
|
51
51
|
lastBlobSentAt = 0;
|
|
52
52
|
canvasBlobs = /* @__PURE__ */ new Map();
|
|
53
53
|
canvasStates = /* @__PURE__ */ new Map();
|
|
54
|
+
// Serialized JSON of the last canvas state ACTUALLY SENT per placement. Used to
|
|
55
|
+
// drop duplicate consecutive states so an idle canvas (whose onChange keeps
|
|
56
|
+
// firing identical states) doesn't stream renders and burn the realtime budget.
|
|
57
|
+
lastSentStateJson = {};
|
|
54
58
|
colors = /* @__PURE__ */ new Map();
|
|
55
59
|
lastSendTime = {};
|
|
56
60
|
throttleTimeouts = {};
|
|
@@ -71,6 +75,8 @@ var RealtimeMockupService = class {
|
|
|
71
75
|
// the WS with `?token=`, and renews ~15s before expiry via a `renew` message.
|
|
72
76
|
tokenProvider;
|
|
73
77
|
renewTimer = null;
|
|
78
|
+
isConnecting = false;
|
|
79
|
+
connectAttempt = 0;
|
|
74
80
|
setCallbacks(callbacks) {
|
|
75
81
|
this.callbacks = callbacks;
|
|
76
82
|
}
|
|
@@ -97,16 +103,21 @@ var RealtimeMockupService = class {
|
|
|
97
103
|
this.callbacks.onLog?.(message, type);
|
|
98
104
|
}
|
|
99
105
|
connect() {
|
|
100
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
106
|
+
if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING || this.isConnecting) {
|
|
101
107
|
return;
|
|
102
108
|
}
|
|
109
|
+
const attempt = ++this.connectAttempt;
|
|
110
|
+
this.isConnecting = true;
|
|
103
111
|
if (this.tokenProvider) {
|
|
104
112
|
this.tokenProvider().then((grant) => {
|
|
113
|
+
if (attempt !== this.connectAttempt || !this.isConnecting) return;
|
|
105
114
|
this.openSocket(grant.token);
|
|
106
115
|
this.scheduleRenew(grant.expiresAt);
|
|
107
116
|
}).catch((err) => {
|
|
117
|
+
if (attempt !== this.connectAttempt) return;
|
|
108
118
|
this.addLog(`Failed to obtain realtime grant: ${err}`);
|
|
109
119
|
this.status = "Disconnected";
|
|
120
|
+
this.isConnecting = false;
|
|
110
121
|
});
|
|
111
122
|
return;
|
|
112
123
|
}
|
|
@@ -120,6 +131,7 @@ var RealtimeMockupService = class {
|
|
|
120
131
|
console.log(`[WS] connection OPENED to ${this.wsUrl}`);
|
|
121
132
|
this.addLog("WebSocket connection opened");
|
|
122
133
|
this.status = "Connected";
|
|
134
|
+
this.isConnecting = false;
|
|
123
135
|
};
|
|
124
136
|
this.ws.onmessage = (event) => {
|
|
125
137
|
try {
|
|
@@ -138,11 +150,13 @@ var RealtimeMockupService = class {
|
|
|
138
150
|
this.isConfigured = false;
|
|
139
151
|
this.configSent = false;
|
|
140
152
|
this.clearRenew();
|
|
153
|
+
this.isConnecting = false;
|
|
141
154
|
this.callbacks.onDisconnected?.();
|
|
142
155
|
};
|
|
143
156
|
this.ws.onerror = (error) => {
|
|
144
157
|
this.addLog(`WebSocket error: ${error}`);
|
|
145
158
|
this.status = "Disconnected";
|
|
159
|
+
this.isConnecting = false;
|
|
146
160
|
};
|
|
147
161
|
}
|
|
148
162
|
scheduleRenew(expiresAt) {
|
|
@@ -292,6 +306,8 @@ var RealtimeMockupService = class {
|
|
|
292
306
|
}
|
|
293
307
|
}
|
|
294
308
|
disconnect() {
|
|
309
|
+
this.connectAttempt++;
|
|
310
|
+
this.isConnecting = false;
|
|
295
311
|
this.clearRenew();
|
|
296
312
|
if (this.ws) {
|
|
297
313
|
this.ws.close();
|
|
@@ -330,6 +346,7 @@ var RealtimeMockupService = class {
|
|
|
330
346
|
this.canvasStates.clear();
|
|
331
347
|
this.colors.clear();
|
|
332
348
|
this.lastSendTime = {};
|
|
349
|
+
this.lastSentStateJson = {};
|
|
333
350
|
Object.values(this.throttleTimeouts).forEach((timeout) => clearTimeout(timeout));
|
|
334
351
|
this.throttleTimeouts = {};
|
|
335
352
|
this.requestVersion = 0;
|
|
@@ -535,6 +552,11 @@ var RealtimeMockupService = class {
|
|
|
535
552
|
console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
|
|
536
553
|
return;
|
|
537
554
|
}
|
|
555
|
+
const stateJson = JSON.stringify(state);
|
|
556
|
+
if (this.lastSentStateJson[placement] === stateJson) {
|
|
557
|
+
this.addLog(`Skipped duplicate canvas state for "${placement}" (unchanged)`, "sent");
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
538
560
|
this.lastSentVersion = ++this.requestVersion;
|
|
539
561
|
this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
|
|
540
562
|
const message = JSON.stringify({
|
|
@@ -544,6 +566,7 @@ var RealtimeMockupService = class {
|
|
|
544
566
|
state
|
|
545
567
|
});
|
|
546
568
|
this.ws.send(message);
|
|
569
|
+
this.lastSentStateJson[placement] = stateJson;
|
|
547
570
|
this.lastBlobSentAt = Date.now();
|
|
548
571
|
this.addLog(`Sent canvas state for "${placement}" (${(message.length / 1024).toFixed(1)}KB, v${this.lastSentVersion})`, "sent");
|
|
549
572
|
this.callbacks.onBlobSent?.(placement);
|
package/dist/react.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-
|
|
1
|
+
import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-DTRStLRH.cjs';
|
|
2
2
|
|
|
3
3
|
interface UseRealtimeMockupOptions {
|
|
4
4
|
wsUrl?: string;
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-
|
|
1
|
+
import { M as MockupResult, W as WebSocketConfig, P as PlacementSettings, F as FrameworkAdapter, b as ComponentProps, d as ComponentState, e as ComponentContext, f as ComponentLifecycleHooks, E as EventHandler, c as ComponentDescriptor, g as FrameworkUtilities } from './websocket-DTRStLRH.js';
|
|
2
2
|
|
|
3
3
|
interface UseRealtimeMockupOptions {
|
|
4
4
|
wsUrl?: string;
|
package/dist/react.js
CHANGED
|
@@ -17,9 +17,10 @@ declare function resolveBestCombination(selection: OptionSelection, attributes:
|
|
|
17
17
|
*/
|
|
18
18
|
declare function computeDisabledChoices(selection: OptionSelection, attributes: Record<string, OptionAttribute>, combinations: Combination[]): Record<string, string[]>;
|
|
19
19
|
/**
|
|
20
|
-
* Derives a
|
|
20
|
+
* Derives a complete default selection based on the best combination.
|
|
21
21
|
* - For attributes that affect combinations, choose the value from the best combo.
|
|
22
|
-
* - For
|
|
22
|
+
* - For every remaining attribute, fall back to a default (color pickers get
|
|
23
|
+
* black; otherwise the first choice) so the returned selection is complete.
|
|
23
24
|
*/
|
|
24
25
|
declare function deriveDefaultSelection(attributes: Record<string, OptionAttribute>, combinations: Combination[]): OptionSelection;
|
|
25
26
|
/**
|
|
@@ -306,6 +307,7 @@ declare class RealtimeMockupService {
|
|
|
306
307
|
private lastBlobSentAt;
|
|
307
308
|
private canvasBlobs;
|
|
308
309
|
private canvasStates;
|
|
310
|
+
private lastSentStateJson;
|
|
309
311
|
private colors;
|
|
310
312
|
private lastSendTime;
|
|
311
313
|
private throttleTimeouts;
|
|
@@ -316,6 +318,8 @@ declare class RealtimeMockupService {
|
|
|
316
318
|
private sendVersionInBlob;
|
|
317
319
|
private tokenProvider?;
|
|
318
320
|
private renewTimer;
|
|
321
|
+
private isConnecting;
|
|
322
|
+
private connectAttempt;
|
|
319
323
|
constructor(wsUrl?: string);
|
|
320
324
|
setCallbacks(callbacks: RealtimeMockupCallbacks): void;
|
|
321
325
|
/** Provide a grant fetcher to authorize the session (per-shop, renewable). */
|
|
@@ -17,9 +17,10 @@ declare function resolveBestCombination(selection: OptionSelection, attributes:
|
|
|
17
17
|
*/
|
|
18
18
|
declare function computeDisabledChoices(selection: OptionSelection, attributes: Record<string, OptionAttribute>, combinations: Combination[]): Record<string, string[]>;
|
|
19
19
|
/**
|
|
20
|
-
* Derives a
|
|
20
|
+
* Derives a complete default selection based on the best combination.
|
|
21
21
|
* - For attributes that affect combinations, choose the value from the best combo.
|
|
22
|
-
* - For
|
|
22
|
+
* - For every remaining attribute, fall back to a default (color pickers get
|
|
23
|
+
* black; otherwise the first choice) so the returned selection is complete.
|
|
23
24
|
*/
|
|
24
25
|
declare function deriveDefaultSelection(attributes: Record<string, OptionAttribute>, combinations: Combination[]): OptionSelection;
|
|
25
26
|
/**
|
|
@@ -306,6 +307,7 @@ declare class RealtimeMockupService {
|
|
|
306
307
|
private lastBlobSentAt;
|
|
307
308
|
private canvasBlobs;
|
|
308
309
|
private canvasStates;
|
|
310
|
+
private lastSentStateJson;
|
|
309
311
|
private colors;
|
|
310
312
|
private lastSendTime;
|
|
311
313
|
private throttleTimeouts;
|
|
@@ -316,6 +318,8 @@ declare class RealtimeMockupService {
|
|
|
316
318
|
private sendVersionInBlob;
|
|
317
319
|
private tokenProvider?;
|
|
318
320
|
private renewTimer;
|
|
321
|
+
private isConnecting;
|
|
322
|
+
private connectAttempt;
|
|
319
323
|
constructor(wsUrl?: string);
|
|
320
324
|
setCallbacks(callbacks: RealtimeMockupCallbacks): void;
|
|
321
325
|
/** Provide a grant fetcher to authorize the session (per-shop, renewable). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowcone-app/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Snowcone SDK for product mockups and print-on-demand",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"merch",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"scripts": {
|
|
82
82
|
"build": "tsup src/index.ts src/react.ts src/dev-fetcher.ts --format esm,cjs --dts --external zod --external react",
|
|
83
83
|
"clean": "rm -rf dist",
|
|
84
|
-
"typecheck": "tsc --project ./tsconfig.json --noEmit"
|
|
84
|
+
"typecheck": "tsc --project ./tsconfig.json --noEmit",
|
|
85
|
+
"test": "vitest run"
|
|
85
86
|
}
|
|
86
87
|
}
|