@snowcone-app/sdk 0.1.11 → 0.1.13
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/README.md +99 -63
- package/dist/{chunk-UJFJ7REN.js → chunk-6MV7TDTM.js} +186 -18
- package/dist/index.cjs +756 -170
- package/dist/index.d.cts +130 -29
- package/dist/index.d.ts +130 -29
- package/dist/index.js +561 -144
- package/dist/react.cjs +203 -20
- package/dist/react.d.cts +13 -1
- package/dist/react.d.ts +13 -1
- package/dist/react.js +18 -3
- package/dist/{websocket-B8_XAwWx.d.ts → websocket-Dum3OooZ.d.cts} +55 -3
- package/dist/{websocket-B8_XAwWx.d.cts → websocket-Dum3OooZ.d.ts} +55 -3
- package/package.json +21 -11
- package/dist/chunk-HOYSZQET.js +0 -476
- package/dist/chunk-IIUCW2O4.js +0 -457
- package/dist/websocket-GXMYofWp.d.cts +0 -330
- package/dist/websocket-GXMYofWp.d.ts +0 -330
package/dist/react.cjs
CHANGED
|
@@ -45,7 +45,9 @@ var RealtimeMockupService = class {
|
|
|
45
45
|
logs = [];
|
|
46
46
|
lastError = null;
|
|
47
47
|
callbacks = {};
|
|
48
|
+
lastBlobSentAt = 0;
|
|
48
49
|
canvasBlobs = /* @__PURE__ */ new Map();
|
|
50
|
+
canvasStates = /* @__PURE__ */ new Map();
|
|
49
51
|
colors = /* @__PURE__ */ new Map();
|
|
50
52
|
lastSendTime = {};
|
|
51
53
|
throttleTimeouts = {};
|
|
@@ -55,11 +57,24 @@ var RealtimeMockupService = class {
|
|
|
55
57
|
lastSentVersion = 0;
|
|
56
58
|
// Track latest sent version per placement to detect stale responses
|
|
57
59
|
latestSentVersionByPlacement = {};
|
|
60
|
+
// Track latest accepted (displayed) version per placement — only drop results
|
|
61
|
+
// older than what we've already shown, not older than what we've sent.
|
|
62
|
+
// This prevents the "version racing" problem during drag where sent versions
|
|
63
|
+
// advance faster than the server can render.
|
|
64
|
+
latestAcceptedVersionByPlacement = {};
|
|
58
65
|
// Feature flag: server now supports version in blob message
|
|
59
66
|
sendVersionInBlob = true;
|
|
67
|
+
// Session-grant auth: when set, connect() fetches a short-lived token, opens
|
|
68
|
+
// the WS with `?token=`, and renews ~15s before expiry via a `renew` message.
|
|
69
|
+
tokenProvider;
|
|
70
|
+
renewTimer = null;
|
|
60
71
|
setCallbacks(callbacks) {
|
|
61
72
|
this.callbacks = callbacks;
|
|
62
73
|
}
|
|
74
|
+
/** Provide a grant fetcher to authorize the session (per-shop, renewable). */
|
|
75
|
+
setTokenProvider(fn) {
|
|
76
|
+
this.tokenProvider = fn;
|
|
77
|
+
}
|
|
63
78
|
getState() {
|
|
64
79
|
return {
|
|
65
80
|
isConnected: this.ws?.readyState === WebSocket.OPEN,
|
|
@@ -82,9 +97,24 @@ var RealtimeMockupService = class {
|
|
|
82
97
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
83
98
|
return;
|
|
84
99
|
}
|
|
100
|
+
if (this.tokenProvider) {
|
|
101
|
+
this.tokenProvider().then((grant) => {
|
|
102
|
+
this.openSocket(grant.token);
|
|
103
|
+
this.scheduleRenew(grant.expiresAt);
|
|
104
|
+
}).catch((err) => {
|
|
105
|
+
this.addLog(`Failed to obtain realtime grant: ${err}`);
|
|
106
|
+
this.status = "Disconnected";
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.openSocket();
|
|
111
|
+
}
|
|
112
|
+
openSocket(token) {
|
|
113
|
+
const url = token ? `${this.wsUrl}${this.wsUrl.includes("?") ? "&" : "?"}token=${encodeURIComponent(token)}` : this.wsUrl;
|
|
85
114
|
this.addLog(`Connecting to ${this.wsUrl}...`);
|
|
86
|
-
this.ws = new WebSocket(
|
|
115
|
+
this.ws = new WebSocket(url);
|
|
87
116
|
this.ws.onopen = () => {
|
|
117
|
+
console.log(`[WS] connection OPENED to ${this.wsUrl}`);
|
|
88
118
|
this.addLog("WebSocket connection opened");
|
|
89
119
|
this.status = "Connected";
|
|
90
120
|
};
|
|
@@ -98,11 +128,13 @@ var RealtimeMockupService = class {
|
|
|
98
128
|
}
|
|
99
129
|
};
|
|
100
130
|
this.ws.onclose = (event) => {
|
|
131
|
+
console.log(`[WS] connection CLOSED (code: ${event.code}, reason: "${event.reason}", wasClean: ${event.wasClean})`);
|
|
101
132
|
this.addLog(`WebSocket connection closed (code: ${event.code})`);
|
|
102
133
|
this.status = "Disconnected";
|
|
103
134
|
this.sessionId = null;
|
|
104
135
|
this.isConfigured = false;
|
|
105
136
|
this.configSent = false;
|
|
137
|
+
this.clearRenew();
|
|
106
138
|
this.callbacks.onDisconnected?.();
|
|
107
139
|
};
|
|
108
140
|
this.ws.onerror = (error) => {
|
|
@@ -110,6 +142,29 @@ var RealtimeMockupService = class {
|
|
|
110
142
|
this.status = "Disconnected";
|
|
111
143
|
};
|
|
112
144
|
}
|
|
145
|
+
scheduleRenew(expiresAt) {
|
|
146
|
+
this.clearRenew();
|
|
147
|
+
if (!this.tokenProvider) return;
|
|
148
|
+
const ms = Math.max(1e3, expiresAt * 1e3 - Date.now() - 15e3);
|
|
149
|
+
this.renewTimer = setTimeout(() => void this.renew(), ms);
|
|
150
|
+
}
|
|
151
|
+
clearRenew() {
|
|
152
|
+
if (this.renewTimer) {
|
|
153
|
+
clearTimeout(this.renewTimer);
|
|
154
|
+
this.renewTimer = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async renew() {
|
|
158
|
+
if (!this.tokenProvider || this.ws?.readyState !== WebSocket.OPEN) return;
|
|
159
|
+
try {
|
|
160
|
+
const grant = await this.tokenProvider();
|
|
161
|
+
this.ws.send(JSON.stringify({ type: "renew", token: grant.token }));
|
|
162
|
+
this.addLog("\u{1F504} Renewed realtime session token", "sent");
|
|
163
|
+
this.scheduleRenew(grant.expiresAt);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
this.addLog(`Failed to renew realtime grant: ${err}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
113
168
|
handleMessage(data) {
|
|
114
169
|
switch (data.type) {
|
|
115
170
|
case "connected":
|
|
@@ -161,15 +216,18 @@ var RealtimeMockupService = class {
|
|
|
161
216
|
this.addLog("\u{1F3A8} Mockup rendering has started...");
|
|
162
217
|
break;
|
|
163
218
|
case "mockup_rendered":
|
|
219
|
+
console.log(`[WS] mockup_rendered received: mockupId=${data.mockupId} hasImageUrl=${!!data.imageUrl} v=${data.requestVersion} placement="${data.placement}" renderMs=${data.renderMs}`);
|
|
164
220
|
if (data.imageUrl && data.mockupId) {
|
|
165
221
|
const responseVersion = data.requestVersion;
|
|
166
222
|
const responsePlacement = data.placement;
|
|
167
223
|
if (responseVersion !== void 0 && responsePlacement) {
|
|
168
|
-
const
|
|
169
|
-
if (
|
|
170
|
-
|
|
224
|
+
const lastAccepted = this.latestAcceptedVersionByPlacement[responsePlacement];
|
|
225
|
+
if (lastAccepted !== void 0 && responseVersion < lastAccepted) {
|
|
226
|
+
console.log(`[WS] STALE mockup dropped: v${responseVersion} for "${responsePlacement}" (already displayed: v${lastAccepted}, latest sent: v${this.latestSentVersionByPlacement[responsePlacement]})`);
|
|
227
|
+
this.addLog(`\u23ED\uFE0F Ignoring stale mockup v${responseVersion} for "${responsePlacement}" (displayed: v${lastAccepted})`);
|
|
171
228
|
break;
|
|
172
229
|
}
|
|
230
|
+
this.latestAcceptedVersionByPlacement[responsePlacement] = responseVersion;
|
|
173
231
|
}
|
|
174
232
|
const mockupResult = {
|
|
175
233
|
mockupId: data.mockupId,
|
|
@@ -177,7 +235,10 @@ var RealtimeMockupService = class {
|
|
|
177
235
|
renderUrl: data.renderUrl || data.imageUrl,
|
|
178
236
|
imageSize: data.imageSize || 0,
|
|
179
237
|
requestVersion: responseVersion,
|
|
180
|
-
placement: responsePlacement
|
|
238
|
+
placement: responsePlacement,
|
|
239
|
+
renderMs: data.renderMs,
|
|
240
|
+
blobToRenderMs: data.blobToRenderMs,
|
|
241
|
+
canvasRenderTiming: data.canvasRenderTiming
|
|
181
242
|
};
|
|
182
243
|
const existingIndex = this.mockupResults.findIndex((m) => m.mockupId === data.mockupId);
|
|
183
244
|
if (existingIndex >= 0) {
|
|
@@ -196,14 +257,16 @@ var RealtimeMockupService = class {
|
|
|
196
257
|
}
|
|
197
258
|
break;
|
|
198
259
|
case "all_mockups_rendered":
|
|
260
|
+
console.log(`[WS] all_mockups_rendered received: ${data.mockups?.length ?? 0} mockups`);
|
|
199
261
|
if (data.mockups) {
|
|
200
262
|
const freshMockups = data.mockups.filter((mockup) => {
|
|
201
263
|
if (mockup.requestVersion !== void 0 && mockup.placement) {
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
204
|
-
this.addLog(`\u23ED\uFE0F Filtering stale mockup v${mockup.requestVersion} for "${mockup.placement}" (
|
|
264
|
+
const lastAccepted = this.latestAcceptedVersionByPlacement[mockup.placement];
|
|
265
|
+
if (lastAccepted !== void 0 && mockup.requestVersion < lastAccepted) {
|
|
266
|
+
this.addLog(`\u23ED\uFE0F Filtering stale mockup v${mockup.requestVersion} for "${mockup.placement}" (displayed: v${lastAccepted})`);
|
|
205
267
|
return false;
|
|
206
268
|
}
|
|
269
|
+
this.latestAcceptedVersionByPlacement[mockup.placement] = mockup.requestVersion;
|
|
207
270
|
}
|
|
208
271
|
return true;
|
|
209
272
|
});
|
|
@@ -226,6 +289,7 @@ var RealtimeMockupService = class {
|
|
|
226
289
|
}
|
|
227
290
|
}
|
|
228
291
|
disconnect() {
|
|
292
|
+
this.clearRenew();
|
|
229
293
|
if (this.ws) {
|
|
230
294
|
this.ws.close();
|
|
231
295
|
this.ws = null;
|
|
@@ -260,6 +324,7 @@ var RealtimeMockupService = class {
|
|
|
260
324
|
this.isConfigured = false;
|
|
261
325
|
this.mockupResults = [];
|
|
262
326
|
this.canvasBlobs.clear();
|
|
327
|
+
this.canvasStates.clear();
|
|
263
328
|
this.colors.clear();
|
|
264
329
|
this.lastSendTime = {};
|
|
265
330
|
Object.values(this.throttleTimeouts).forEach((timeout) => clearTimeout(timeout));
|
|
@@ -267,6 +332,7 @@ var RealtimeMockupService = class {
|
|
|
267
332
|
this.requestVersion = 0;
|
|
268
333
|
this.lastSentVersion = 0;
|
|
269
334
|
this.latestSentVersionByPlacement = {};
|
|
335
|
+
this.latestAcceptedVersionByPlacement = {};
|
|
270
336
|
this.addLog("\u{1F9F9} Cleared all cached canvas/color data for new product");
|
|
271
337
|
}
|
|
272
338
|
}
|
|
@@ -302,6 +368,30 @@ var RealtimeMockupService = class {
|
|
|
302
368
|
this.addLog(`\u{1F3AF} Updating mockupIds to: [${mockupIds.join(", ")}]`);
|
|
303
369
|
return this.sendConfig(updatedConfig);
|
|
304
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Update render width without changing other config.
|
|
373
|
+
* Used for low-res preview during rapid edits (e.g., 600 while dragging, 1200 on release).
|
|
374
|
+
* Preserves blobs since product/variant don't change.
|
|
375
|
+
*/
|
|
376
|
+
updateWidth(width) {
|
|
377
|
+
if (!this.config) {
|
|
378
|
+
this.addLog("Cannot update width: no config set");
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
382
|
+
this.addLog("Cannot update width: WebSocket not connected");
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
if (this.config.width === width) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
const updatedConfig = {
|
|
389
|
+
...this.config,
|
|
390
|
+
width
|
|
391
|
+
};
|
|
392
|
+
this.addLog(`\u{1F4D0} Updating render width: ${this.config.width} \u2192 ${width}`);
|
|
393
|
+
return this.sendConfig(updatedConfig);
|
|
394
|
+
}
|
|
305
395
|
/**
|
|
306
396
|
* Update placementSettings without changing other config.
|
|
307
397
|
* Used to override scaleMode when canvas editor is active.
|
|
@@ -324,6 +414,8 @@ var RealtimeMockupService = class {
|
|
|
324
414
|
sendCanvasBlob(placement, blob, mockupCount = 1, baseThrottleMs = 1e3, notifyCallback = true) {
|
|
325
415
|
this.canvasBlobs.set(placement, blob);
|
|
326
416
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.isConfigured) {
|
|
417
|
+
const reason = !this.ws ? "no WebSocket" : this.ws.readyState !== WebSocket.OPEN ? `ws.readyState=${this.ws.readyState}` : "not configured";
|
|
418
|
+
console.log(`[WS] sendCanvasBlob BLOCKED for "${placement}" (${blob.size}B): ${reason} (cached for later)`);
|
|
327
419
|
return false;
|
|
328
420
|
}
|
|
329
421
|
if (baseThrottleMs <= 0) {
|
|
@@ -331,16 +423,25 @@ var RealtimeMockupService = class {
|
|
|
331
423
|
this.lastSendTime[placement] = Date.now();
|
|
332
424
|
return true;
|
|
333
425
|
}
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
426
|
+
const debounceMs = baseThrottleMs * mockupCount;
|
|
427
|
+
const lastSendTime = this.lastSendTime[placement];
|
|
428
|
+
const timeSinceLastSend = lastSendTime ? Date.now() - lastSendTime : 0;
|
|
429
|
+
const isActiveInteraction = lastSendTime && timeSinceLastSend < debounceMs * 3;
|
|
430
|
+
if (isActiveInteraction && timeSinceLastSend >= debounceMs) {
|
|
431
|
+
if (this.throttleTimeouts[placement]) {
|
|
432
|
+
clearTimeout(this.throttleTimeouts[placement]);
|
|
433
|
+
delete this.throttleTimeouts[placement];
|
|
434
|
+
}
|
|
435
|
+
const latestBlob = this.canvasBlobs.get(placement);
|
|
436
|
+
if (latestBlob && this.ws?.readyState === WebSocket.OPEN && this.isConfigured) {
|
|
437
|
+
this.sendBlobImmediately(placement, latestBlob, notifyCallback);
|
|
438
|
+
this.lastSendTime[placement] = Date.now();
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
if (this.throttleTimeouts[placement]) {
|
|
442
|
+
clearTimeout(this.throttleTimeouts[placement]);
|
|
443
|
+
}
|
|
444
|
+
const delayTime = isActiveInteraction ? debounceMs - timeSinceLastSend : debounceMs;
|
|
344
445
|
this.throttleTimeouts[placement] = setTimeout(() => {
|
|
345
446
|
const latestBlob = this.canvasBlobs.get(placement);
|
|
346
447
|
if (latestBlob && this.ws?.readyState === WebSocket.OPEN && this.isConfigured) {
|
|
@@ -352,6 +453,72 @@ var RealtimeMockupService = class {
|
|
|
352
453
|
}
|
|
353
454
|
return true;
|
|
354
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Send canvas state JSON for server-side rendering.
|
|
458
|
+
* Alternative to sendCanvasBlob — the server renders the PNG instead of the client.
|
|
459
|
+
*/
|
|
460
|
+
sendCanvasState(placement, state, mockupCount = 1, baseThrottleMs = 1e3) {
|
|
461
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.isConfigured) {
|
|
462
|
+
const reason = !this.ws ? "no WebSocket" : this.ws.readyState !== WebSocket.OPEN ? `ws.readyState=${this.ws.readyState}` : "not configured";
|
|
463
|
+
console.log(`[WS] sendCanvasState BLOCKED for "${placement}": ${reason}`);
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
this.canvasStates.set(placement, state);
|
|
467
|
+
if (baseThrottleMs <= 0) {
|
|
468
|
+
this.sendCanvasStateImmediately(placement, state);
|
|
469
|
+
this.lastSendTime[placement] = Date.now();
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
const debounceMs = baseThrottleMs * mockupCount;
|
|
473
|
+
const lastSendTime = this.lastSendTime[placement];
|
|
474
|
+
const timeSinceLastSend = lastSendTime ? Date.now() - lastSendTime : 0;
|
|
475
|
+
const isActiveInteraction = lastSendTime && timeSinceLastSend < debounceMs * 3;
|
|
476
|
+
if (isActiveInteraction && timeSinceLastSend >= debounceMs) {
|
|
477
|
+
if (this.throttleTimeouts[placement]) {
|
|
478
|
+
clearTimeout(this.throttleTimeouts[placement]);
|
|
479
|
+
delete this.throttleTimeouts[placement];
|
|
480
|
+
}
|
|
481
|
+
const latestState = this.canvasStates.get(placement);
|
|
482
|
+
if (latestState && this.ws?.readyState === WebSocket.OPEN && this.isConfigured) {
|
|
483
|
+
console.log(`[WS] sendCanvasState "${placement}": max-wait flush (${timeSinceLastSend}ms since last)`);
|
|
484
|
+
this.sendCanvasStateImmediately(placement, latestState);
|
|
485
|
+
this.lastSendTime[placement] = Date.now();
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
if (this.throttleTimeouts[placement]) {
|
|
489
|
+
clearTimeout(this.throttleTimeouts[placement]);
|
|
490
|
+
}
|
|
491
|
+
const delayTime = isActiveInteraction ? debounceMs - timeSinceLastSend : debounceMs;
|
|
492
|
+
this.throttleTimeouts[placement] = setTimeout(() => {
|
|
493
|
+
const latestState = this.canvasStates.get(placement);
|
|
494
|
+
if (latestState && this.ws?.readyState === WebSocket.OPEN && this.isConfigured) {
|
|
495
|
+
console.log(`[WS] sendCanvasState "${placement}": debounce firing (${debounceMs}ms)`);
|
|
496
|
+
this.sendCanvasStateImmediately(placement, latestState);
|
|
497
|
+
this.lastSendTime[placement] = Date.now();
|
|
498
|
+
}
|
|
499
|
+
delete this.throttleTimeouts[placement];
|
|
500
|
+
}, delayTime);
|
|
501
|
+
}
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
sendCanvasStateImmediately(placement, state) {
|
|
505
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
506
|
+
console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
this.lastSentVersion = ++this.requestVersion;
|
|
510
|
+
this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
|
|
511
|
+
const message = JSON.stringify({
|
|
512
|
+
type: "canvas_state",
|
|
513
|
+
placement,
|
|
514
|
+
version: this.lastSentVersion,
|
|
515
|
+
state
|
|
516
|
+
});
|
|
517
|
+
this.ws.send(message);
|
|
518
|
+
this.lastBlobSentAt = Date.now();
|
|
519
|
+
this.addLog(`Sent canvas state for "${placement}" (${(message.length / 1024).toFixed(1)}KB, v${this.lastSentVersion})`, "sent");
|
|
520
|
+
this.callbacks.onBlobSent?.(placement);
|
|
521
|
+
}
|
|
355
522
|
sendBlobImmediately(placement, blob, notifyCallback = true) {
|
|
356
523
|
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
357
524
|
this.lastSentVersion = ++this.requestVersion;
|
|
@@ -377,6 +544,7 @@ ${versionToSend}
|
|
|
377
544
|
combined.set(imageBytes, headerBytes.length);
|
|
378
545
|
}
|
|
379
546
|
this.ws.send(combined.buffer);
|
|
547
|
+
this.lastBlobSentAt = Date.now();
|
|
380
548
|
this.addLog(`Sent canvas blob for placement "${placement}" (${imageBytes.length} bytes, v${versionToSend})`, "sent");
|
|
381
549
|
if (notifyCallback) {
|
|
382
550
|
this.callbacks.onBlobSent?.(placement);
|
|
@@ -515,6 +683,8 @@ ${versionToSend}
|
|
|
515
683
|
// src/realtime/react.ts
|
|
516
684
|
function useRealtimeMockup(options = {}) {
|
|
517
685
|
const serviceRef = (0, import_react.useRef)(null);
|
|
686
|
+
const getTokenRef = (0, import_react.useRef)(options.getToken);
|
|
687
|
+
getTokenRef.current = options.getToken;
|
|
518
688
|
const [state, setState] = (0, import_react.useState)({
|
|
519
689
|
isConnected: false,
|
|
520
690
|
sessionId: null,
|
|
@@ -547,11 +717,13 @@ function useRealtimeMockup(options = {}) {
|
|
|
547
717
|
options.onBlobSent?.(placement);
|
|
548
718
|
},
|
|
549
719
|
onMockupRendered: (result) => {
|
|
550
|
-
|
|
720
|
+
console.log(`[useRealtimeMockup] onMockupRendered: mockupId=${result.mockupId} hasImageUrl=${!!result.imageUrl}`);
|
|
551
721
|
options.onMockupRendered?.(result);
|
|
552
722
|
},
|
|
553
723
|
onAllMockupsRendered: (results) => {
|
|
554
|
-
|
|
724
|
+
const newState = service.getState();
|
|
725
|
+
console.log(`[useRealtimeMockup] onAllMockupsRendered: ${results.length} results, state.mockupResults=${newState.mockupResults.length}`);
|
|
726
|
+
setState(newState);
|
|
555
727
|
options.onAllMockupsRendered?.(results);
|
|
556
728
|
},
|
|
557
729
|
onError: (error) => {
|
|
@@ -561,6 +733,9 @@ function useRealtimeMockup(options = {}) {
|
|
|
561
733
|
// REMOVED: onLog callback was causing setState on every log message,
|
|
562
734
|
// which triggered re-renders during canvas drag operations
|
|
563
735
|
});
|
|
736
|
+
if (getTokenRef.current) {
|
|
737
|
+
service.setTokenProvider(() => getTokenRef.current());
|
|
738
|
+
}
|
|
564
739
|
return () => {
|
|
565
740
|
service.disconnect();
|
|
566
741
|
};
|
|
@@ -580,9 +755,15 @@ function useRealtimeMockup(options = {}) {
|
|
|
580
755
|
const updatePlacementSettings = (0, import_react.useCallback)((settings) => {
|
|
581
756
|
return serviceRef.current?.updatePlacementSettings(settings) || false;
|
|
582
757
|
}, []);
|
|
758
|
+
const updateWidth = (0, import_react.useCallback)((width) => {
|
|
759
|
+
return serviceRef.current?.updateWidth(width) || false;
|
|
760
|
+
}, []);
|
|
583
761
|
const sendCanvasBlob = (0, import_react.useCallback)((placement, blob, mockupCount, baseThrottleMs) => {
|
|
584
762
|
return serviceRef.current?.sendCanvasBlob(placement, blob, mockupCount, baseThrottleMs) || false;
|
|
585
763
|
}, []);
|
|
764
|
+
const sendCanvasState = (0, import_react.useCallback)((placement, state2, mockupCount, baseThrottleMs) => {
|
|
765
|
+
return serviceRef.current?.sendCanvasState(placement, state2, mockupCount, baseThrottleMs) || false;
|
|
766
|
+
}, []);
|
|
586
767
|
const sendColorBlob = (0, import_react.useCallback)((placement, hexColor) => {
|
|
587
768
|
return serviceRef.current?.sendColorBlob(placement, hexColor) || false;
|
|
588
769
|
}, []);
|
|
@@ -616,7 +797,9 @@ function useRealtimeMockup(options = {}) {
|
|
|
616
797
|
sendConfig,
|
|
617
798
|
updateMockupIds,
|
|
618
799
|
updatePlacementSettings,
|
|
800
|
+
updateWidth,
|
|
619
801
|
sendCanvasBlob,
|
|
802
|
+
sendCanvasState,
|
|
620
803
|
sendColorBlob,
|
|
621
804
|
createEmptyCanvasBlob,
|
|
622
805
|
sendInitialEmptyCanvases,
|
package/dist/react.d.cts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { M as MockupResult, W as WebSocketConfig, u 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, u 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-Dum3OooZ.cjs';
|
|
2
2
|
|
|
3
3
|
interface UseRealtimeMockupOptions {
|
|
4
4
|
wsUrl?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Optional grant fetcher. When provided, the session authorizes via a
|
|
7
|
+
* short-lived token (opened on the WS URL, renewed before expiry) instead of
|
|
8
|
+
* a static seal. Identity may change between renders — it's read via a ref,
|
|
9
|
+
* so it never re-opens the socket.
|
|
10
|
+
*/
|
|
11
|
+
getToken?: () => Promise<{
|
|
12
|
+
token: string;
|
|
13
|
+
expiresAt: number;
|
|
14
|
+
}>;
|
|
5
15
|
onConnected?: (sessionId: string) => void;
|
|
6
16
|
onDisconnected?: () => void;
|
|
7
17
|
onConfigReceived?: () => void;
|
|
@@ -19,7 +29,9 @@ declare function useRealtimeMockup(options?: UseRealtimeMockupOptions): {
|
|
|
19
29
|
sendConfig: (config: WebSocketConfig) => boolean;
|
|
20
30
|
updateMockupIds: (mockupIds: string[]) => boolean;
|
|
21
31
|
updatePlacementSettings: (settings: Record<string, PlacementSettings>) => boolean;
|
|
32
|
+
updateWidth: (width: number) => boolean;
|
|
22
33
|
sendCanvasBlob: (placement: string, blob: Blob, mockupCount?: number, baseThrottleMs?: number) => boolean;
|
|
34
|
+
sendCanvasState: (placement: string, state: object, mockupCount?: number, baseThrottleMs?: number) => boolean;
|
|
23
35
|
sendColorBlob: (placement: string, hexColor: string) => boolean;
|
|
24
36
|
createEmptyCanvasBlob: (width?: number, height?: number) => Promise<Blob>;
|
|
25
37
|
sendInitialEmptyCanvases: (placements: Array<{
|
package/dist/react.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { M as MockupResult, W as WebSocketConfig, u 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, u 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-Dum3OooZ.js';
|
|
2
2
|
|
|
3
3
|
interface UseRealtimeMockupOptions {
|
|
4
4
|
wsUrl?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Optional grant fetcher. When provided, the session authorizes via a
|
|
7
|
+
* short-lived token (opened on the WS URL, renewed before expiry) instead of
|
|
8
|
+
* a static seal. Identity may change between renders — it's read via a ref,
|
|
9
|
+
* so it never re-opens the socket.
|
|
10
|
+
*/
|
|
11
|
+
getToken?: () => Promise<{
|
|
12
|
+
token: string;
|
|
13
|
+
expiresAt: number;
|
|
14
|
+
}>;
|
|
5
15
|
onConnected?: (sessionId: string) => void;
|
|
6
16
|
onDisconnected?: () => void;
|
|
7
17
|
onConfigReceived?: () => void;
|
|
@@ -19,7 +29,9 @@ declare function useRealtimeMockup(options?: UseRealtimeMockupOptions): {
|
|
|
19
29
|
sendConfig: (config: WebSocketConfig) => boolean;
|
|
20
30
|
updateMockupIds: (mockupIds: string[]) => boolean;
|
|
21
31
|
updatePlacementSettings: (settings: Record<string, PlacementSettings>) => boolean;
|
|
32
|
+
updateWidth: (width: number) => boolean;
|
|
22
33
|
sendCanvasBlob: (placement: string, blob: Blob, mockupCount?: number, baseThrottleMs?: number) => boolean;
|
|
34
|
+
sendCanvasState: (placement: string, state: object, mockupCount?: number, baseThrottleMs?: number) => boolean;
|
|
23
35
|
sendColorBlob: (placement: string, hexColor: string) => boolean;
|
|
24
36
|
createEmptyCanvasBlob: (width?: number, height?: number) => Promise<Blob>;
|
|
25
37
|
sendInitialEmptyCanvases: (placements: Array<{
|
package/dist/react.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RealtimeMockupService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-6MV7TDTM.js";
|
|
4
4
|
|
|
5
5
|
// src/realtime/react.ts
|
|
6
6
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
7
|
function useRealtimeMockup(options = {}) {
|
|
8
8
|
const serviceRef = useRef(null);
|
|
9
|
+
const getTokenRef = useRef(options.getToken);
|
|
10
|
+
getTokenRef.current = options.getToken;
|
|
9
11
|
const [state, setState] = useState({
|
|
10
12
|
isConnected: false,
|
|
11
13
|
sessionId: null,
|
|
@@ -38,11 +40,13 @@ function useRealtimeMockup(options = {}) {
|
|
|
38
40
|
options.onBlobSent?.(placement);
|
|
39
41
|
},
|
|
40
42
|
onMockupRendered: (result) => {
|
|
41
|
-
|
|
43
|
+
console.log(`[useRealtimeMockup] onMockupRendered: mockupId=${result.mockupId} hasImageUrl=${!!result.imageUrl}`);
|
|
42
44
|
options.onMockupRendered?.(result);
|
|
43
45
|
},
|
|
44
46
|
onAllMockupsRendered: (results) => {
|
|
45
|
-
|
|
47
|
+
const newState = service.getState();
|
|
48
|
+
console.log(`[useRealtimeMockup] onAllMockupsRendered: ${results.length} results, state.mockupResults=${newState.mockupResults.length}`);
|
|
49
|
+
setState(newState);
|
|
46
50
|
options.onAllMockupsRendered?.(results);
|
|
47
51
|
},
|
|
48
52
|
onError: (error) => {
|
|
@@ -52,6 +56,9 @@ function useRealtimeMockup(options = {}) {
|
|
|
52
56
|
// REMOVED: onLog callback was causing setState on every log message,
|
|
53
57
|
// which triggered re-renders during canvas drag operations
|
|
54
58
|
});
|
|
59
|
+
if (getTokenRef.current) {
|
|
60
|
+
service.setTokenProvider(() => getTokenRef.current());
|
|
61
|
+
}
|
|
55
62
|
return () => {
|
|
56
63
|
service.disconnect();
|
|
57
64
|
};
|
|
@@ -71,9 +78,15 @@ function useRealtimeMockup(options = {}) {
|
|
|
71
78
|
const updatePlacementSettings = useCallback((settings) => {
|
|
72
79
|
return serviceRef.current?.updatePlacementSettings(settings) || false;
|
|
73
80
|
}, []);
|
|
81
|
+
const updateWidth = useCallback((width) => {
|
|
82
|
+
return serviceRef.current?.updateWidth(width) || false;
|
|
83
|
+
}, []);
|
|
74
84
|
const sendCanvasBlob = useCallback((placement, blob, mockupCount, baseThrottleMs) => {
|
|
75
85
|
return serviceRef.current?.sendCanvasBlob(placement, blob, mockupCount, baseThrottleMs) || false;
|
|
76
86
|
}, []);
|
|
87
|
+
const sendCanvasState = useCallback((placement, state2, mockupCount, baseThrottleMs) => {
|
|
88
|
+
return serviceRef.current?.sendCanvasState(placement, state2, mockupCount, baseThrottleMs) || false;
|
|
89
|
+
}, []);
|
|
77
90
|
const sendColorBlob = useCallback((placement, hexColor) => {
|
|
78
91
|
return serviceRef.current?.sendColorBlob(placement, hexColor) || false;
|
|
79
92
|
}, []);
|
|
@@ -107,7 +120,9 @@ function useRealtimeMockup(options = {}) {
|
|
|
107
120
|
sendConfig,
|
|
108
121
|
updateMockupIds,
|
|
109
122
|
updatePlacementSettings,
|
|
123
|
+
updateWidth,
|
|
110
124
|
sendCanvasBlob,
|
|
125
|
+
sendCanvasState,
|
|
111
126
|
sendColorBlob,
|
|
112
127
|
createEmptyCanvasBlob,
|
|
113
128
|
sendInitialEmptyCanvases,
|
|
@@ -212,8 +212,13 @@ interface WebSocketConfig {
|
|
|
212
212
|
productId: string;
|
|
213
213
|
mockupIds: string[];
|
|
214
214
|
variantId: string;
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
shop: string;
|
|
216
|
+
/**
|
|
217
|
+
* Legacy URL seal. Only sent on the unauthenticated (shim) path. When a
|
|
218
|
+
* tokenProvider is set, the session is authorized by the grant token on the
|
|
219
|
+
* WS URL and the server self-signs the render, so no seal is needed.
|
|
220
|
+
*/
|
|
221
|
+
seal?: string;
|
|
217
222
|
width: number;
|
|
218
223
|
/** Aspect ratio for mockup rendering (e.g. '2:3' for portrait). Server default: '16:9' */
|
|
219
224
|
ar?: string;
|
|
@@ -228,6 +233,12 @@ interface MockupResult {
|
|
|
228
233
|
requestVersion?: number;
|
|
229
234
|
/** The placement this mockup corresponds to */
|
|
230
235
|
placement?: string;
|
|
236
|
+
/** Server-side PIXI render time in ms */
|
|
237
|
+
renderMs?: number;
|
|
238
|
+
/** Time from blob received to render start in ms */
|
|
239
|
+
blobToRenderMs?: number;
|
|
240
|
+
/** Server-side canvas render timing breakdown (present when canvas_state was used) */
|
|
241
|
+
canvasRenderTiming?: any;
|
|
231
242
|
}
|
|
232
243
|
interface WebSocketMessage {
|
|
233
244
|
type: string;
|
|
@@ -242,6 +253,21 @@ interface WebSocketMessage {
|
|
|
242
253
|
missingPlacements?: string[];
|
|
243
254
|
/** The request version this response corresponds to (for stale detection) */
|
|
244
255
|
requestVersion?: number;
|
|
256
|
+
/** Server-side PIXI render time in ms */
|
|
257
|
+
renderMs?: number;
|
|
258
|
+
/** Time from blob received to render start in ms */
|
|
259
|
+
blobToRenderMs?: number;
|
|
260
|
+
/** Server-side canvas render timing (only present when canvas_state was used) */
|
|
261
|
+
canvasRenderTiming?: {
|
|
262
|
+
poolAcquireMs: number;
|
|
263
|
+
imageFetchMs: number;
|
|
264
|
+
fontLoadMs: number;
|
|
265
|
+
canvasRenderMs: number;
|
|
266
|
+
blobConvertMs: number;
|
|
267
|
+
base64Ms: number;
|
|
268
|
+
totalWorkerMs: number;
|
|
269
|
+
totalMs: number;
|
|
270
|
+
};
|
|
245
271
|
}
|
|
246
272
|
interface RealtimeMockupState {
|
|
247
273
|
isConnected: boolean;
|
|
@@ -277,19 +303,33 @@ declare class RealtimeMockupService {
|
|
|
277
303
|
private logs;
|
|
278
304
|
private lastError;
|
|
279
305
|
private callbacks;
|
|
306
|
+
private lastBlobSentAt;
|
|
280
307
|
private canvasBlobs;
|
|
308
|
+
private canvasStates;
|
|
281
309
|
private colors;
|
|
282
310
|
private lastSendTime;
|
|
283
311
|
private throttleTimeouts;
|
|
284
312
|
private requestVersion;
|
|
285
313
|
private lastSentVersion;
|
|
286
314
|
private latestSentVersionByPlacement;
|
|
315
|
+
private latestAcceptedVersionByPlacement;
|
|
287
316
|
private sendVersionInBlob;
|
|
317
|
+
private tokenProvider?;
|
|
318
|
+
private renewTimer;
|
|
288
319
|
constructor(wsUrl?: string);
|
|
289
320
|
setCallbacks(callbacks: RealtimeMockupCallbacks): void;
|
|
321
|
+
/** Provide a grant fetcher to authorize the session (per-shop, renewable). */
|
|
322
|
+
setTokenProvider(fn: () => Promise<{
|
|
323
|
+
token: string;
|
|
324
|
+
expiresAt: number;
|
|
325
|
+
}>): void;
|
|
290
326
|
getState(): RealtimeMockupState;
|
|
291
327
|
private addLog;
|
|
292
328
|
connect(): void;
|
|
329
|
+
private openSocket;
|
|
330
|
+
private scheduleRenew;
|
|
331
|
+
private clearRenew;
|
|
332
|
+
private renew;
|
|
293
333
|
private handleMessage;
|
|
294
334
|
disconnect(): void;
|
|
295
335
|
sendConfig(config: WebSocketConfig): boolean;
|
|
@@ -299,12 +339,24 @@ declare class RealtimeMockupService {
|
|
|
299
339
|
* This is the preferred method for priority-based rendering.
|
|
300
340
|
*/
|
|
301
341
|
updateMockupIds(mockupIds: string[]): boolean;
|
|
342
|
+
/**
|
|
343
|
+
* Update render width without changing other config.
|
|
344
|
+
* Used for low-res preview during rapid edits (e.g., 600 while dragging, 1200 on release).
|
|
345
|
+
* Preserves blobs since product/variant don't change.
|
|
346
|
+
*/
|
|
347
|
+
updateWidth(width: number): boolean;
|
|
302
348
|
/**
|
|
303
349
|
* Update placementSettings without changing other config.
|
|
304
350
|
* Used to override scaleMode when canvas editor is active.
|
|
305
351
|
*/
|
|
306
352
|
updatePlacementSettings(placementSettings: Record<string, PlacementSettings>): boolean;
|
|
307
353
|
sendCanvasBlob(placement: string, blob: Blob, mockupCount?: number, baseThrottleMs?: number, notifyCallback?: boolean): boolean;
|
|
354
|
+
/**
|
|
355
|
+
* Send canvas state JSON for server-side rendering.
|
|
356
|
+
* Alternative to sendCanvasBlob — the server renders the PNG instead of the client.
|
|
357
|
+
*/
|
|
358
|
+
sendCanvasState(placement: string, state: object, mockupCount?: number, baseThrottleMs?: number): boolean;
|
|
359
|
+
private sendCanvasStateImmediately;
|
|
308
360
|
private sendBlobImmediately;
|
|
309
361
|
/**
|
|
310
362
|
* Enable sending version number in blob messages.
|
|
@@ -333,4 +385,4 @@ declare class RealtimeMockupService {
|
|
|
333
385
|
clearMockups(): void;
|
|
334
386
|
}
|
|
335
387
|
|
|
336
|
-
export { AdapterRegistry as A, type Combination as C, type EventHandler as E, type FrameworkAdapter as F, type MockupResult as M, type OptionAttribute as O, type ProductComponentContext as P, type
|
|
388
|
+
export { AdapterRegistry as A, type Combination as C, type EventHandler as E, type FrameworkAdapter as F, type MockupResult as M, type OptionAttribute as O, type ProductComponentContext as P, type RealtimeMockupCallbacks as R, type WebSocketConfig as W, type OptionSelection as a, type ComponentProps as b, type ComponentDescriptor as c, type ComponentState as d, type ComponentContext as e, type ComponentLifecycleHooks as f, type FrameworkUtilities as g, RealtimeMockupService as h, type RealtimeMockupState as i, type RenderResult as j, type WebSocketMessage as k, adapterRegistry as l, computeDisabledChoices as m, createComponent as n, defineComponent as o, deriveDefaultSelection as p, findBestCombination as q, getPricePreview as r, isOptionAvailable as s, resolveBestCombination as t, type PlacementSettings as u };
|