@snowcone-app/sdk 0.1.12 → 0.1.14

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/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-7VO4EL2V.js";
4
4
  import {
5
5
  RealtimeMockupService
6
- } from "./chunk-UJFJ7REN.js";
6
+ } from "./chunk-6MV7TDTM.js";
7
7
 
8
8
  // src/validation.ts
9
9
  import { z } from "zod";
@@ -46,10 +46,19 @@ var CatalogProductSchema = z.object({
46
46
  ).optional(),
47
47
  placements: z.array(
48
48
  z.object({
49
+ // The one additive multi-placement field (rev 5 P1): a URL-safe slug
50
+ // used as the public param key (`asset.front`). Optional — older catalog
51
+ // docs predate it.
52
+ key: z.string().optional(),
49
53
  label: z.string(),
50
- type: z.enum(["image", "color"]),
51
- width: z.number().int(),
52
- height: z.number().int(),
54
+ // Live data has placements with `type: null` (and ingestion may omit it
55
+ // entirely), not just "image"/"color". The previous strict enum threw on
56
+ // real catalog docs. Accept image | color | null | absent.
57
+ type: z.enum(["image", "color"]).nullable().optional(),
58
+ // Live data carries width/height of 0 (and `.int()` rejected nothing of
59
+ // import here). Drop `.int()` and allow nullable so 0 / null / float pass.
60
+ width: z.number().nullable().optional(),
61
+ height: z.number().nullable().optional(),
53
62
  defaultScaleMode: z.enum(["fill", "fit"]).optional(),
54
63
  fitMarginTop: z.number().int().optional(),
55
64
  fitMarginRight: z.number().int().optional(),
@@ -70,7 +79,7 @@ var SignatureCache = class {
70
79
  memoryCache = /* @__PURE__ */ new Map();
71
80
  maxMemoryEntries;
72
81
  maxLocalStorageEntries;
73
- localStorageKey = "merchify_signature_cache";
82
+ localStorageKey = "snowcone_signature_cache";
74
83
  localStorageAvailable;
75
84
  constructor(maxMemoryEntries = 500, maxLocalStorageEntries = 100) {
76
85
  this.maxMemoryEntries = maxMemoryEntries;
@@ -167,8 +176,8 @@ var SignatureCache = class {
167
176
  };
168
177
 
169
178
  // src/mockup/service.ts
170
- var DEFAULT_IMAGE_URL = typeof process !== "undefined" && process.env?.MERCHIFY_IMAGE_URL || "https://i.snowcone.app";
171
- var DEFAULT_SIGNER_URL = typeof process !== "undefined" && process.env?.MERCHIFY_SIGNER_URL || "https://s.snowcone.app/sign";
179
+ var DEFAULT_IMAGE_URL = typeof process !== "undefined" && process.env?.SNOWCONE_IMAGE_URL || "https://cdn.snowcone.app";
180
+ var DEFAULT_SIGNER_URL = typeof process !== "undefined" && process.env?.SNOWCONE_SIGNER_URL || "https://s.snowcone.app/sign";
172
181
  var RATE_LIMIT_PER_MINUTE = 450;
173
182
  var RATE_LIMIT_PER_SECOND = 20;
174
183
  var MockupServiceImpl = class {
@@ -176,11 +185,11 @@ var MockupServiceImpl = class {
176
185
  cache;
177
186
  rateLimitState;
178
187
  fetch;
179
- constructor(config2, customFetch) {
188
+ constructor(config, customFetch) {
180
189
  this.config = {
181
- imageUrl: config2.imageUrl || DEFAULT_IMAGE_URL,
182
- signerUrl: config2.signerUrl || DEFAULT_SIGNER_URL,
183
- accountId: config2.accountId || typeof process !== "undefined" && process.env?.MERCHIFY_ACCOUNT_ID || ""
190
+ imageUrl: config.imageUrl || DEFAULT_IMAGE_URL,
191
+ signerUrl: config.signerUrl || DEFAULT_SIGNER_URL,
192
+ shop: config.shop || typeof process !== "undefined" && process.env?.SNOWCONE_SHOP_ID || ""
184
193
  };
185
194
  this.cache = new SignatureCache();
186
195
  this.fetch = customFetch || fetch.bind(globalThis);
@@ -193,12 +202,21 @@ var MockupServiceImpl = class {
193
202
  if (!options.design || !options.product) {
194
203
  throw new Error("Missing required options: design and product are required");
195
204
  }
196
- if (!this.config.accountId) {
197
- throw new Error("Account ID is required for mockup generation");
205
+ if (!this.config.shop) {
206
+ throw new Error("Shop ID is required for mockup generation");
207
+ }
208
+ for (const element of options.design) {
209
+ if (element.type === "image" && element.imageUrl) {
210
+ if (element.imageUrl.startsWith("blob:")) {
211
+ throw new Error(
212
+ `[snowcone-sdk] Cannot generate mockup: design element for placement "${element.placement}" uses a blob URL ("${element.imageUrl.slice(0, 60)}..."). Blob URLs are local to this browser tab and cannot be downloaded by the mockup rendering server. Upload the image to a publicly accessible URL or convert it to a data URL before requesting a mockup.`
213
+ );
214
+ }
215
+ }
198
216
  }
199
217
  this.checkRateLimits();
200
218
  const relativeUrl = this.buildMockupUrlWithParams(options);
201
- const cacheKey = `${relativeUrl}:${this.config.accountId}`;
219
+ const cacheKey = `${relativeUrl}:${this.config.shop}`;
202
220
  const cachedUrl = this.cache.get(cacheKey);
203
221
  if (cachedUrl) {
204
222
  return cachedUrl;
@@ -283,17 +301,12 @@ var MockupServiceImpl = class {
283
301
  }
284
302
  }
285
303
  async getSignedUrl(relativeUrl) {
286
- const urlWithAccountId = relativeUrl + (relativeUrl.includes("?") ? "&" : "?") + `accountId=${encodeURIComponent(this.config.accountId)}`;
304
+ const urlWithAccountId = relativeUrl + (relativeUrl.includes("?") ? "&" : "?") + `shop=${encodeURIComponent(this.config.shop)}`;
287
305
  const cacheKey = urlWithAccountId;
288
306
  const cachedUrl = this.cache.get(cacheKey);
289
307
  if (cachedUrl) {
290
308
  return cachedUrl;
291
309
  }
292
- if (this.config.signerUrl?.includes("localhost") || this.config.signerUrl === "mock") {
293
- const signedUrl = this.config.imageUrl + urlWithAccountId + "&sig=bypass-sig-for-k6-load-test";
294
- this.cache.set(cacheKey, signedUrl);
295
- return signedUrl;
296
- }
297
310
  const signerUrl = new URL(this.config.signerUrl);
298
311
  signerUrl.searchParams.set("url", urlWithAccountId);
299
312
  try {
@@ -340,6 +353,456 @@ var MockupServiceImpl = class {
340
353
  }
341
354
  };
342
355
 
356
+ // ../mockup-url/src/index.ts
357
+ var PARAMS = {
358
+ shop: "shop",
359
+ asset: "asset",
360
+ assets: "assets",
361
+ placement: "placement",
362
+ variant: "variant",
363
+ mockup: "mockup",
364
+ width: "width",
365
+ grain: "grain",
366
+ aspect: "aspect",
367
+ /** Live preview/calibration mask overrides (not cached). */
368
+ maskOverrides: "maskOverrides",
369
+ /**
370
+ * Render-only option picks (the `affectsCombinations:false` attributes), as a
371
+ * base64 JSON map `{ attributeName: choiceLabel }`. rendercenter already parses
372
+ * this and resolves per-choice underlays from it; the builder emits it so the
373
+ * edge resolver can forward render-only picks (e.g. a cap's Crown/Strap color)
374
+ * without changing rendercenter's grammar.
375
+ */
376
+ optionSelections: "optionSelections",
377
+ signature: "signature",
378
+ seal: "seal"
379
+ };
380
+ function asSimpleAsset(design) {
381
+ if (design.length !== 1) return null;
382
+ const el = design[0];
383
+ if (el.type === "color" || el.hex) return null;
384
+ if (!el.imageUrl) return null;
385
+ if (el.position || el.size || el.tiles != null) return null;
386
+ if (el.alignment && el.alignment !== "center") return null;
387
+ return { imageUrl: el.imageUrl, placement: el.placement };
388
+ }
389
+ var ALLOWED_WIDTHS = [
390
+ 400,
391
+ 600,
392
+ 800,
393
+ 1e3,
394
+ 1200,
395
+ 1400,
396
+ 1600,
397
+ 1800,
398
+ 2e3,
399
+ 2500,
400
+ 3e3,
401
+ 4e3
402
+ ];
403
+ function normalizeWidth(width) {
404
+ for (let i = ALLOWED_WIDTHS.length - 1; i >= 0; i--) {
405
+ if (ALLOWED_WIDTHS[i] <= width) return ALLOWED_WIDTHS[i];
406
+ }
407
+ return ALLOWED_WIDTHS[0];
408
+ }
409
+ function normalizeDesignElements(design) {
410
+ return design.map((element) => {
411
+ const normalized = {};
412
+ const keys = Object.keys(element).sort();
413
+ for (const key of keys) {
414
+ if (key === "width" || key === "height" || key === "type") continue;
415
+ if (key === "alignment" && element[key] === "center") {
416
+ continue;
417
+ }
418
+ const value = element[key];
419
+ if (value !== void 0 && value !== null) normalized[key] = value;
420
+ }
421
+ return normalized;
422
+ });
423
+ }
424
+ function buildMockupUrl(options, cfg) {
425
+ for (const element of options.design) {
426
+ if (element.type === "image" && element.imageUrl?.startsWith("blob:")) {
427
+ throw new Error(
428
+ `[snowcone] Cannot generate mockup: design element for placement "${element.placement}" uses a blob URL ("${element.imageUrl.slice(0, 60)}..."). Blob URLs are local to this browser tab and cannot be downloaded by the mockup rendering server. Upload the image to a publicly accessible URL or convert it to a data URL before requesting a mockup.`
429
+ );
430
+ }
431
+ }
432
+ const width = normalizeWidth(options.width);
433
+ const base = cfg.mockupBaseUrl.replace(/\/+$/, "");
434
+ const path = `/${encodeURIComponent(options.productId)}`;
435
+ let queryString = `${PARAMS.shop}=${encodeURIComponent(cfg.shop)}`;
436
+ const simple = asSimpleAsset(options.design);
437
+ if (simple) {
438
+ queryString += `&${PARAMS.asset}=${encodeURIComponent(simple.imageUrl)}`;
439
+ if (simple.placement) {
440
+ queryString += `&${PARAMS.placement}=${encodeURIComponent(simple.placement)}`;
441
+ }
442
+ } else {
443
+ const encodedAssets = encodeURIComponent(
444
+ btoa(JSON.stringify(normalizeDesignElements(options.design)))
445
+ );
446
+ queryString += `&${PARAMS.assets}=${encodedAssets}`;
447
+ }
448
+ queryString += `&${PARAMS.variant}=${encodeURIComponent(options.variantId)}`;
449
+ queryString += `&${PARAMS.mockup}=${encodeURIComponent(options.mockupId)}`;
450
+ queryString += `&${PARAMS.width}=${width}`;
451
+ if (options.optionSelections && Object.keys(options.optionSelections).length > 0) {
452
+ const sorted = {};
453
+ for (const k of Object.keys(options.optionSelections).sort()) {
454
+ sorted[k] = options.optionSelections[k];
455
+ }
456
+ const optionSelectionsBase64 = btoa(JSON.stringify(sorted));
457
+ queryString += `&${PARAMS.optionSelections}=${encodeURIComponent(
458
+ optionSelectionsBase64
459
+ )}`;
460
+ }
461
+ if (options.effects?.grain) {
462
+ queryString += `&${PARAMS.grain}=${options.effects.grain}`;
463
+ }
464
+ if (options.aspect && options.aspect !== "16:9") {
465
+ queryString += `&${PARAMS.aspect}=${encodeURIComponent(options.aspect)}`;
466
+ }
467
+ if (options.maskOverrides && options.maskOverrides.length > 0) {
468
+ const maskOverridesBase64 = btoa(JSON.stringify(options.maskOverrides));
469
+ queryString += `&${PARAMS.maskOverrides}=${encodeURIComponent(maskOverridesBase64)}`;
470
+ }
471
+ return `${base}${path}?${queryString}`;
472
+ }
473
+ var PARAM_PREFIX = {
474
+ asset: "asset.",
475
+ color: "color.",
476
+ tile: "tile.",
477
+ align: "align.",
478
+ opt: "opt."
479
+ };
480
+ var DEFAULT_PUBLIC_BASE_URL = "https://img.snowcone.app";
481
+ function buildPublicMockupUrl(options, cfg = {}) {
482
+ const base = (cfg.baseUrl ?? DEFAULT_PUBLIC_BASE_URL).replace(/\/+$/, "");
483
+ const path = `/${encodeURIComponent(options.productCode)}`;
484
+ const enc = encodeURIComponent;
485
+ const parts = [];
486
+ parts.push(`${PARAMS.shop}=${enc(options.shop)}`);
487
+ if (options.design) {
488
+ for (const key of Object.keys(options.design).sort()) {
489
+ const fill = options.design[key];
490
+ const k = enc(key);
491
+ if (typeof fill === "string") {
492
+ parts.push(`${PARAM_PREFIX.asset}${k}=${enc(fill)}`);
493
+ } else if ("color" in fill) {
494
+ parts.push(`${PARAM_PREFIX.color}${k}=${enc(fill.color)}`);
495
+ } else {
496
+ parts.push(`${PARAM_PREFIX.asset}${k}=${enc(fill.src)}`);
497
+ if (fill.tile !== void 0) {
498
+ parts.push(`${PARAM_PREFIX.tile}${k}=${fill.tile}`);
499
+ }
500
+ if (fill.align !== void 0) {
501
+ parts.push(`${PARAM_PREFIX.align}${k}=${enc(fill.align)}`);
502
+ }
503
+ }
504
+ }
505
+ } else if (options.assetUrl !== void 0) {
506
+ parts.push(`${PARAMS.asset}=${enc(options.assetUrl)}`);
507
+ }
508
+ if (options.placement !== void 0) {
509
+ parts.push(`${PARAMS.placement}=${enc(options.placement)}`);
510
+ }
511
+ if (options.options) {
512
+ for (const attr of Object.keys(options.options).sort()) {
513
+ parts.push(`${PARAM_PREFIX.opt}${enc(attr)}=${enc(options.options[attr])}`);
514
+ }
515
+ }
516
+ if (options.variant !== void 0) {
517
+ parts.push(`${PARAMS.variant}=${enc(options.variant)}`);
518
+ }
519
+ if (options.mockup !== void 0) {
520
+ parts.push(`${PARAMS.mockup}=${enc(options.mockup)}`);
521
+ }
522
+ if (options.width !== void 0) parts.push(`${PARAMS.width}=${options.width}`);
523
+ if (options.aspect !== void 0 && options.aspect !== "16:9") {
524
+ parts.push(`${PARAMS.aspect}=${enc(options.aspect)}`);
525
+ }
526
+ return `${base}${path}?${parts.join("&")}`;
527
+ }
528
+ function canonicalForSig(url) {
529
+ const hashIdx = url.indexOf("#");
530
+ const noHash = hashIdx === -1 ? url : url.slice(0, hashIdx);
531
+ const qIdx = noHash.indexOf("?");
532
+ const beforeQuery = qIdx === -1 ? noHash : noHash.slice(0, qIdx);
533
+ const rawQuery = qIdx === -1 ? "" : noHash.slice(qIdx + 1);
534
+ let pathname;
535
+ const schemeMatch = beforeQuery.match(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//);
536
+ if (schemeMatch) {
537
+ const afterScheme = beforeQuery.slice(schemeMatch[0].length);
538
+ const slashIdx = afterScheme.indexOf("/");
539
+ pathname = slashIdx === -1 ? "/" : afterScheme.slice(slashIdx);
540
+ } else {
541
+ pathname = beforeQuery;
542
+ }
543
+ const pairs = [];
544
+ if (rawQuery.length > 0) {
545
+ for (const segment of rawQuery.split("&")) {
546
+ if (segment.length === 0) continue;
547
+ const eq = segment.indexOf("=");
548
+ const rawKey = eq === -1 ? segment : segment.slice(0, eq);
549
+ const rawValue = eq === -1 ? "" : segment.slice(eq + 1);
550
+ const key = decodeURIComponent(rawKey);
551
+ if (key === "signature") continue;
552
+ pairs.push({ key, raw: rawValue });
553
+ }
554
+ }
555
+ pairs.sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0);
556
+ const canonicalQuery = pairs.map((p) => `${p.key}=${p.raw}`).join("&");
557
+ return `${pathname}?${canonicalQuery}`;
558
+ }
559
+
560
+ // src/mockup/hmac.ts
561
+ var K = new Uint32Array([
562
+ 1116352408,
563
+ 1899447441,
564
+ 3049323471,
565
+ 3921009573,
566
+ 961987163,
567
+ 1508970993,
568
+ 2453635748,
569
+ 2870763221,
570
+ 3624381080,
571
+ 310598401,
572
+ 607225278,
573
+ 1426881987,
574
+ 1925078388,
575
+ 2162078206,
576
+ 2614888103,
577
+ 3248222580,
578
+ 3835390401,
579
+ 4022224774,
580
+ 264347078,
581
+ 604807628,
582
+ 770255983,
583
+ 1249150122,
584
+ 1555081692,
585
+ 1996064986,
586
+ 2554220882,
587
+ 2821834349,
588
+ 2952996808,
589
+ 3210313671,
590
+ 3336571891,
591
+ 3584528711,
592
+ 113926993,
593
+ 338241895,
594
+ 666307205,
595
+ 773529912,
596
+ 1294757372,
597
+ 1396182291,
598
+ 1695183700,
599
+ 1986661051,
600
+ 2177026350,
601
+ 2456956037,
602
+ 2730485921,
603
+ 2820302411,
604
+ 3259730800,
605
+ 3345764771,
606
+ 3516065817,
607
+ 3600352804,
608
+ 4094571909,
609
+ 275423344,
610
+ 430227734,
611
+ 506948616,
612
+ 659060556,
613
+ 883997877,
614
+ 958139571,
615
+ 1322822218,
616
+ 1537002063,
617
+ 1747873779,
618
+ 1955562222,
619
+ 2024104815,
620
+ 2227730452,
621
+ 2361852424,
622
+ 2428436474,
623
+ 2756734187,
624
+ 3204031479,
625
+ 3329325298
626
+ ]);
627
+ function rotr(x, n) {
628
+ return x >>> n | x << 32 - n;
629
+ }
630
+ function sha256(bytes) {
631
+ const h = new Uint32Array([
632
+ 1779033703,
633
+ 3144134277,
634
+ 1013904242,
635
+ 2773480762,
636
+ 1359893119,
637
+ 2600822924,
638
+ 528734635,
639
+ 1541459225
640
+ ]);
641
+ const bitLen = bytes.length * 8;
642
+ const withOne = bytes.length + 1;
643
+ const total = withOne + (56 - withOne % 64 + 64) % 64 + 8;
644
+ const msg = new Uint8Array(total);
645
+ msg.set(bytes);
646
+ msg[bytes.length] = 128;
647
+ const hi = Math.floor(bitLen / 4294967296);
648
+ const lo = bitLen >>> 0;
649
+ msg[total - 8] = hi >>> 24 & 255;
650
+ msg[total - 7] = hi >>> 16 & 255;
651
+ msg[total - 6] = hi >>> 8 & 255;
652
+ msg[total - 5] = hi & 255;
653
+ msg[total - 4] = lo >>> 24 & 255;
654
+ msg[total - 3] = lo >>> 16 & 255;
655
+ msg[total - 2] = lo >>> 8 & 255;
656
+ msg[total - 1] = lo & 255;
657
+ const w = new Uint32Array(64);
658
+ for (let off = 0; off < total; off += 64) {
659
+ for (let i = 0; i < 16; i++) {
660
+ const j = off + i * 4;
661
+ w[i] = (msg[j] << 24 | msg[j + 1] << 16 | msg[j + 2] << 8 | msg[j + 3]) >>> 0;
662
+ }
663
+ for (let i = 16; i < 64; i++) {
664
+ const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
665
+ const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
666
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
667
+ }
668
+ let a = h[0];
669
+ let b = h[1];
670
+ let c = h[2];
671
+ let d = h[3];
672
+ let e = h[4];
673
+ let f = h[5];
674
+ let g = h[6];
675
+ let hh = h[7];
676
+ for (let i = 0; i < 64; i++) {
677
+ const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
678
+ const ch = e & f ^ ~e & g;
679
+ const t1 = hh + S1 + ch + K[i] + w[i] >>> 0;
680
+ const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
681
+ const maj = a & b ^ a & c ^ b & c;
682
+ const t2 = S0 + maj >>> 0;
683
+ hh = g;
684
+ g = f;
685
+ f = e;
686
+ e = d + t1 >>> 0;
687
+ d = c;
688
+ c = b;
689
+ b = a;
690
+ a = t1 + t2 >>> 0;
691
+ }
692
+ h[0] = h[0] + a >>> 0;
693
+ h[1] = h[1] + b >>> 0;
694
+ h[2] = h[2] + c >>> 0;
695
+ h[3] = h[3] + d >>> 0;
696
+ h[4] = h[4] + e >>> 0;
697
+ h[5] = h[5] + f >>> 0;
698
+ h[6] = h[6] + g >>> 0;
699
+ h[7] = h[7] + hh >>> 0;
700
+ }
701
+ const out = new Uint8Array(32);
702
+ for (let i = 0; i < 8; i++) {
703
+ out[i * 4] = h[i] >>> 24 & 255;
704
+ out[i * 4 + 1] = h[i] >>> 16 & 255;
705
+ out[i * 4 + 2] = h[i] >>> 8 & 255;
706
+ out[i * 4 + 3] = h[i] & 255;
707
+ }
708
+ return out;
709
+ }
710
+ function utf8(str) {
711
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(str);
712
+ const bytes = [];
713
+ for (let i = 0; i < str.length; i++) {
714
+ let c = str.charCodeAt(i);
715
+ if (c < 128) bytes.push(c);
716
+ else if (c < 2048) {
717
+ bytes.push(192 | c >> 6, 128 | c & 63);
718
+ } else if (c >= 55296 && c <= 56319) {
719
+ const c2 = str.charCodeAt(++i);
720
+ c = 65536 + ((c & 1023) << 10) + (c2 & 1023);
721
+ bytes.push(
722
+ 240 | c >> 18,
723
+ 128 | c >> 12 & 63,
724
+ 128 | c >> 6 & 63,
725
+ 128 | c & 63
726
+ );
727
+ } else {
728
+ bytes.push(
729
+ 224 | c >> 12,
730
+ 128 | c >> 6 & 63,
731
+ 128 | c & 63
732
+ );
733
+ }
734
+ }
735
+ return new Uint8Array(bytes);
736
+ }
737
+ function toHex(bytes) {
738
+ let hex = "";
739
+ for (let i = 0; i < bytes.length; i++) {
740
+ hex += bytes[i].toString(16).padStart(2, "0");
741
+ }
742
+ return hex;
743
+ }
744
+ function hmacSha256Hex(secret, message) {
745
+ const blockSize = 64;
746
+ let key = utf8(secret);
747
+ if (key.length > blockSize) key = sha256(key);
748
+ if (key.length < blockSize) {
749
+ const padded = new Uint8Array(blockSize);
750
+ padded.set(key);
751
+ key = padded;
752
+ }
753
+ const oKeyPad = new Uint8Array(blockSize);
754
+ const iKeyPad = new Uint8Array(blockSize);
755
+ for (let i = 0; i < blockSize; i++) {
756
+ oKeyPad[i] = key[i] ^ 92;
757
+ iKeyPad[i] = key[i] ^ 54;
758
+ }
759
+ const msg = utf8(message);
760
+ const inner = new Uint8Array(blockSize + msg.length);
761
+ inner.set(iKeyPad);
762
+ inner.set(msg, blockSize);
763
+ const innerHash = sha256(inner);
764
+ const outer = new Uint8Array(blockSize + innerHash.length);
765
+ outer.set(oKeyPad);
766
+ outer.set(innerHash, blockSize);
767
+ return toHex(sha256(outer));
768
+ }
769
+
770
+ // src/mockup/getMockupUrl.ts
771
+ var DEFAULT_MOCKUP_BASE = "https://img.snowcone.app";
772
+ function getMockupUrl(a, b, c) {
773
+ let productCode;
774
+ let opts;
775
+ let legacyAsset;
776
+ if (typeof b === "string") {
777
+ legacyAsset = a;
778
+ productCode = b;
779
+ opts = c ?? {};
780
+ } else {
781
+ productCode = a;
782
+ opts = b;
783
+ }
784
+ const base = (opts.base ?? DEFAULT_MOCKUP_BASE).replace(/\/+$/, "");
785
+ const assetUrl = opts.asset ?? legacyAsset;
786
+ const url = buildPublicMockupUrl(
787
+ {
788
+ productCode,
789
+ shop: opts.shop,
790
+ ...opts.design ? { design: opts.design } : {},
791
+ ...assetUrl !== void 0 ? { assetUrl } : {},
792
+ ...opts.options ? { options: opts.options } : {},
793
+ ...opts.width !== void 0 ? { width: opts.width } : {},
794
+ ...opts.aspect !== void 0 ? { aspect: opts.aspect } : {},
795
+ ...opts.mockup ?? opts.view ? { mockup: opts.mockup ?? opts.view } : {},
796
+ ...opts.variant !== void 0 ? { variant: opts.variant } : {},
797
+ ...opts.placement !== void 0 ? { placement: opts.placement } : {}
798
+ },
799
+ { baseUrl: base }
800
+ );
801
+ if (!opts.secret) return url;
802
+ const signature = hmacSha256Hex(opts.secret, canonicalForSig(url)).slice(0, 16);
803
+ return `${url}&signature=${signature}`;
804
+ }
805
+
343
806
  // src/state/optionSelection.ts
344
807
  function resolveBestCombination(selection, attributes, combinations) {
345
808
  const relevant = Object.fromEntries(
@@ -1010,7 +1473,7 @@ function resolveUrlFromDOM(relativeUrl) {
1010
1473
  const filename = pathParts[pathParts.length - 1];
1011
1474
  if (!filename) return null;
1012
1475
  const filenameWithoutExt = filename.replace(/\.[^.]+$/, "");
1013
- const images = document.querySelectorAll("img");
1476
+ const images = Array.from(document.querySelectorAll("img"));
1014
1477
  for (const img of images) {
1015
1478
  const src = img.getAttribute("src");
1016
1479
  if (!src) continue;
@@ -1022,6 +1485,9 @@ function resolveUrlFromDOM(relativeUrl) {
1022
1485
  }
1023
1486
  function normalizeImageUrl(url) {
1024
1487
  if (!url) return null;
1488
+ if (url.startsWith("blob:")) {
1489
+ return null;
1490
+ }
1025
1491
  if (url.startsWith("http://") || url.startsWith("https://")) {
1026
1492
  return url;
1027
1493
  }
@@ -1040,18 +1506,18 @@ function normalizeImageUrl(url) {
1040
1506
  const absoluteUrl = new URL(url, window.location.origin).href;
1041
1507
  if (absoluteUrl.includes("localhost") || absoluteUrl.includes("127.0.0.1")) {
1042
1508
  console.warn(
1043
- `[merchify-sdk] Artwork URL "${absoluteUrl}" uses localhost. This will work in your browser but won't work for mockup generation. Deploy your app or use publicly accessible URLs for production mockups.`
1509
+ `[snowcone-sdk] Artwork URL "${absoluteUrl}" uses localhost. This will work in your browser but won't work for mockup generation. Deploy your app or use publicly accessible URLs for production mockups.`
1044
1510
  );
1045
1511
  }
1046
1512
  return absoluteUrl;
1047
1513
  } catch (e) {
1048
- console.warn(`[merchify-sdk] Failed to normalize URL "${url}":`, e);
1514
+ console.warn(`[snowcone-sdk] Failed to normalize URL "${url}":`, e);
1049
1515
  return url;
1050
1516
  }
1051
1517
  }
1052
1518
  if (url.startsWith("/") || url.startsWith(".")) {
1053
1519
  console.warn(
1054
- `[merchify-sdk] Relative URL "${url}" detected during SSR. URL will be resolved on the client. Consider using absolute URLs for SSR compatibility.`
1520
+ `[snowcone-sdk] Relative URL "${url}" detected during SSR. URL will be resolved on the client. Consider using absolute URLs for SSR compatibility.`
1055
1521
  );
1056
1522
  }
1057
1523
  return url;
@@ -1154,95 +1620,39 @@ function filterImagePlacements(placements) {
1154
1620
  }
1155
1621
 
1156
1622
  // src/mockup/urlGenerator.ts
1157
- var cachedMockupConfig = null;
1158
- function resolveDefaultConfig() {
1159
- const DEFAULT_MOCKUP_URL = "https://MOCKUP_URL_NOT_CONFIGURED.invalid";
1160
- const DEFAULT_ACCOUNT_ID = "ACCOUNT_ID_NOT_CONFIGURED";
1161
- const winConfig = typeof window !== "undefined" && window.merchify || {};
1162
- const env = typeof process !== "undefined" ? process.env : void 0;
1163
- const envEndpoint = env?.CATALOG_API_BASE_URL;
1164
- const envImageUrl = env?.MERCHIFY_IMAGE_URL || env?.NEXT_PUBLIC_MERCH_MOCKUP_URL;
1165
- const envSignerUrl = env?.MERCHIFY_SIGNER_URL || env?.NEXT_PUBLIC_MERCH_SIGNER_URL;
1166
- const envAccountId = env?.MERCHIFY_ACCOUNT_ID || env?.NEXT_PUBLIC_MERCH_ACCOUNT_ID;
1167
- return {
1168
- endpoint: winConfig.endpoint || envEndpoint || void 0,
1169
- mockupUrl: winConfig.mockupUrl || envImageUrl || DEFAULT_MOCKUP_URL,
1170
- signerUrl: winConfig.signerUrl || envSignerUrl || void 0,
1171
- accountId: winConfig.accountId || envAccountId || DEFAULT_ACCOUNT_ID,
1172
- mode: winConfig.mode || "mock"
1173
- };
1623
+ var maxWidthCache = /* @__PURE__ */ new Map();
1624
+ function buildMockupUrl2(options, cfg) {
1625
+ const { ar, ...rest } = options;
1626
+ return buildMockupUrl(
1627
+ { ...rest, aspect: ar },
1628
+ { mockupBaseUrl: cfg.mockupBaseUrl, shop: cfg.shop }
1629
+ );
1174
1630
  }
1175
- var localhostWarningShown = false;
1176
- function config(overrides) {
1177
- if (overrides) {
1178
- const base = resolveDefaultConfig();
1179
- cachedMockupConfig = { ...base, ...overrides };
1180
- } else if (!cachedMockupConfig) {
1181
- cachedMockupConfig = resolveDefaultConfig();
1182
- }
1183
- const hostname = typeof window !== "undefined" ? window.location.hostname : "";
1184
- const isLocalDev = hostname === "localhost" || hostname === "127.0.0.1" || hostname.includes("192.168.") || hostname.endsWith(".local") || hostname.endsWith(".lvh.me") || hostname === "lvh.me";
1185
- if (!localhostWarningShown && typeof window !== "undefined" && !isLocalDev && cachedMockupConfig?.mockupUrl?.includes("localhost")) {
1186
- console.error(
1187
- `[@snowcone-app/sdk] WARNING: Using localhost mockup URL in production environment.
1188
- mockupUrl: ${cachedMockupConfig.mockupUrl}
1189
- This was likely caused by building with .env.local present.
1190
- Redeploy with "pnpm run deploy" to fix.`
1191
- );
1192
- localhostWarningShown = true;
1193
- }
1194
- return cachedMockupConfig;
1631
+ function resolveMockupBaseUrl() {
1632
+ const env = typeof process !== "undefined" ? process.env : void 0;
1633
+ const winConfig = typeof window !== "undefined" && window.snowcone || {};
1634
+ return winConfig.mockupUrl || env?.SNOWCONE_IMAGE_URL || env?.NEXT_PUBLIC_MERCH_MOCKUP_URL || "https://cdn.snowcone.app";
1195
1635
  }
1196
- var resolveMockupConfig = config;
1197
- function normalizeDesignElements(design) {
1198
- return design.map((element) => {
1199
- const normalized = {};
1200
- const keys = Object.keys(element).sort();
1201
- for (const key of keys) {
1202
- if (key === "width" || key === "height" || key === "type") {
1203
- continue;
1204
- }
1205
- if (key === "alignment" && element[key] === "center") {
1206
- continue;
1207
- }
1208
- const value = element[key];
1209
- if (value !== void 0 && value !== null) {
1210
- normalized[key] = value;
1211
- }
1212
- }
1213
- return normalized;
1214
- });
1636
+ function resolveShop() {
1637
+ const env = typeof process !== "undefined" ? process.env : void 0;
1638
+ const winConfig = typeof window !== "undefined" && window.snowcone || {};
1639
+ return winConfig.shop || env?.SNOWCONE_SHOP_ID || env?.NEXT_PUBLIC_SNOWCONE_SHOP_ID || "SHOP_NOT_CONFIGURED";
1215
1640
  }
1216
1641
  function mockupUrl(options) {
1217
- const mockupConfig = config();
1218
- const mockupBaseUrl = mockupConfig.mockupUrl || "https://MOCKUP_URL_NOT_CONFIGURED.invalid";
1219
- const accountId = mockupConfig.accountId || "ACCOUNT_ID_NOT_CONFIGURED";
1220
- const normalizedDesign = normalizeDesignElements(options.design);
1221
- const designBase64 = btoa(JSON.stringify(normalizedDesign));
1222
- const encodedDesign = encodeURIComponent(designBase64);
1223
- let queryString = `productId=${encodeURIComponent(options.productId)}`;
1224
- queryString += `&mockupId=${encodeURIComponent(options.mockupId)}`;
1225
- queryString += `&variantId=${encodeURIComponent(options.variantId)}`;
1226
- queryString += `&design=${encodedDesign}`;
1227
- queryString += `&width=${options.width}`;
1228
- queryString += `&accountId=${encodeURIComponent(accountId)}`;
1229
- if (options.effects?.grain) {
1230
- queryString += `&grain=${options.effects.grain}`;
1231
- }
1232
- if (options.ar && options.ar !== "16:9") {
1233
- queryString += `&ar=${encodeURIComponent(options.ar)}`;
1234
- }
1235
- if (options.maskOverrides && options.maskOverrides.length > 0) {
1236
- const maskOverridesBase64 = btoa(JSON.stringify(options.maskOverrides));
1237
- queryString += `&maskOverrides=${encodeURIComponent(maskOverridesBase64)}`;
1238
- }
1239
- if (mockupConfig.mode === "mock") {
1240
- const mockSignature = "bypass-sig-for-k6-load-test";
1241
- queryString += `&sig=${encodeURIComponent(mockSignature)}`;
1642
+ const mockupBaseUrl = resolveMockupBaseUrl();
1643
+ const shop = resolveShop();
1644
+ const encodedDesign = encodeURIComponent(
1645
+ btoa(JSON.stringify(normalizeDesignElements(options.design)))
1646
+ );
1647
+ let width = normalizeWidth(options.width);
1648
+ const cacheKey = `${options.productId}:${options.mockupId}:${options.variantId}:${encodedDesign}`;
1649
+ const cachedWidth = maxWidthCache.get(cacheKey);
1650
+ if (cachedWidth !== void 0 && cachedWidth >= width) {
1651
+ width = cachedWidth;
1242
1652
  } else {
1243
- console.warn("Live mode signature generation not implemented in mockupUrl");
1653
+ maxWidthCache.set(cacheKey, width);
1244
1654
  }
1245
- return `${mockupBaseUrl}/mockup?${queryString}`;
1655
+ return buildMockupUrl2({ ...options, width }, { mockupBaseUrl, shop });
1246
1656
  }
1247
1657
  function getVariant(selection, product) {
1248
1658
  const combination = findBestCombination(
@@ -1408,6 +1818,12 @@ function validateImageUrl(url) {
1408
1818
  message: "Image URL is required",
1409
1819
  value: url
1410
1820
  });
1821
+ } else if (url.startsWith("blob:")) {
1822
+ errors.push({
1823
+ field: "imageUrl",
1824
+ message: "Blob URLs (blob:...) cannot be used for mockup generation \u2014 they are local to the browser and not accessible by the rendering server. Upload the image to a public URL first, or export the canvas to a data URL.",
1825
+ value: url
1826
+ });
1411
1827
  } else if (
1412
1828
  // Allow absolute URLs
1413
1829
  !url.startsWith("http://") && !url.startsWith("https://") && // Allow data URLs
@@ -2406,11 +2822,11 @@ var UniversalContextProvider = class extends EventEmitter {
2406
2822
  config;
2407
2823
  consumers = /* @__PURE__ */ new Set();
2408
2824
  initialized = false;
2409
- constructor(config2 = {}) {
2825
+ constructor(config = {}) {
2410
2826
  super();
2411
- this.config = config2;
2827
+ this.config = config;
2412
2828
  this.contextManager = new ProductContextManager();
2413
- this.loader = new ProductLoader(this.contextManager, config2.fetcher);
2829
+ this.loader = new ProductLoader(this.contextManager, config.fetcher);
2414
2830
  this.setupSubscriptions();
2415
2831
  }
2416
2832
  /**
@@ -2528,7 +2944,7 @@ var ContextInjector = class {
2528
2944
  static forVue(provider) {
2529
2945
  return {
2530
2946
  install(app) {
2531
- app.provide("merchifyContext", provider);
2947
+ app.provide("snowconeContext", provider);
2532
2948
  },
2533
2949
  inject() {
2534
2950
  return provider;
@@ -2556,7 +2972,7 @@ var ContextInjector = class {
2556
2972
  // Attach to element
2557
2973
  attach(element) {
2558
2974
  element.__contextProvider = provider;
2559
- element.addEventListener("merchify:request-context", (event) => {
2975
+ element.addEventListener("snowcone:request-context", (event) => {
2560
2976
  event.detail.context = provider.getContext();
2561
2977
  });
2562
2978
  },
@@ -2574,8 +2990,8 @@ var ContextInjector = class {
2574
2990
  };
2575
2991
  }
2576
2992
  };
2577
- function createUniversalProvider(config2) {
2578
- return new UniversalContextProvider(config2);
2993
+ function createUniversalProvider(config) {
2994
+ return new UniversalContextProvider(config);
2579
2995
  }
2580
2996
  function withContext(Base) {
2581
2997
  return class extends Base {
@@ -3580,8 +3996,8 @@ var StandardComponents = {
3580
3996
  Product: {
3581
3997
  metadata: {
3582
3998
  name: "Product",
3583
- tagName: "merchify-product",
3584
- displayName: "MerchifyProduct",
3999
+ tagName: "snowcone-product",
4000
+ displayName: "SnowconeProduct",
3585
4001
  description: "Product context provider component",
3586
4002
  props: {
3587
4003
  productId: { type: "string" },
@@ -3607,8 +4023,8 @@ var StandardComponents = {
3607
4023
  ProductOptions: {
3608
4024
  metadata: {
3609
4025
  name: "ProductOptions",
3610
- tagName: "merchify-product-options",
3611
- displayName: "MerchifyProductOptions",
4026
+ tagName: "snowcone-product-options",
4027
+ displayName: "SnowconeProductOptions",
3612
4028
  description: "Product options selection component",
3613
4029
  props: {
3614
4030
  attributes: { type: "object" },
@@ -3628,8 +4044,8 @@ var StandardComponents = {
3628
4044
  ProductPrice: {
3629
4045
  metadata: {
3630
4046
  name: "ProductPrice",
3631
- tagName: "merchify-product-price",
3632
- displayName: "MerchifyProductPrice",
4047
+ tagName: "snowcone-product-price",
4048
+ displayName: "SnowconeProductPrice",
3633
4049
  description: "Product price display component",
3634
4050
  props: {
3635
4051
  price: { type: "number" },
@@ -3645,8 +4061,8 @@ var StandardComponents = {
3645
4061
  ProductImage: {
3646
4062
  metadata: {
3647
4063
  name: "ProductImage",
3648
- tagName: "merchify-product-image",
3649
- displayName: "MerchifyProductImage",
4064
+ tagName: "snowcone-product-image",
4065
+ displayName: "SnowconeProductImage",
3650
4066
  description: "Product mockup image component",
3651
4067
  props: {
3652
4068
  productId: { type: "string" },
@@ -3665,8 +4081,8 @@ var StandardComponents = {
3665
4081
  AddToCart: {
3666
4082
  metadata: {
3667
4083
  name: "AddToCart",
3668
- tagName: "merchify-add-to-cart",
3669
- displayName: "MerchifyAddToCart",
4084
+ tagName: "snowcone-add-to-cart",
4085
+ displayName: "SnowconeAddToCart",
3670
4086
  description: "Add to cart button component",
3671
4087
  props: {
3672
4088
  text: { type: "string", default: "Add to Cart" },
@@ -4233,8 +4649,8 @@ function createSvelteComponent(descriptor, svelte) {
4233
4649
  }
4234
4650
 
4235
4651
  // src/index.ts
4236
- function getFetcher(config2) {
4237
- return config2?.fetcher || globalThis.fetch.bind(globalThis);
4652
+ function getFetcher(config) {
4653
+ return config?.fetcher || globalThis.fetch.bind(globalThis);
4238
4654
  }
4239
4655
  function validateProductLoose(product) {
4240
4656
  try {
@@ -4243,11 +4659,11 @@ function validateProductLoose(product) {
4243
4659
  return product;
4244
4660
  }
4245
4661
  }
4246
- async function listProducts(config2) {
4247
- const f = getFetcher(config2);
4248
- const meilisearchHost = config2?.meilisearch?.host || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_HOST || "https://ms-e5d999b2eaca-15654.sfo.meilisearch.io";
4249
- const meilisearchApiKey = config2?.meilisearch?.apiKey || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_API_KEY || "eee819b849798ad9091228c486ec05d0931e5292";
4250
- const meilisearchIndex = config2?.meilisearch?.index || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_INDEX || "merchify";
4662
+ async function listProducts(config) {
4663
+ const f = getFetcher(config);
4664
+ const meilisearchHost = config?.meilisearch?.host || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_HOST || "https://search.snowcone.app";
4665
+ const meilisearchApiKey = config?.meilisearch?.apiKey || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_API_KEY || "eee819b849798ad9091228c486ec05d0931e5292";
4666
+ const meilisearchIndex = config?.meilisearch?.index || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_INDEX || "snowcone";
4251
4667
  const headers = {
4252
4668
  "Content-Type": "application/json"
4253
4669
  };
@@ -4260,7 +4676,7 @@ async function listProducts(config2) {
4260
4676
  headers,
4261
4677
  body: JSON.stringify({
4262
4678
  q: "",
4263
- limit: config2?.limit || 1e3,
4679
+ limit: config?.limit || 1e3,
4264
4680
  filter: "mockups IS NOT EMPTY"
4265
4681
  })
4266
4682
  });
@@ -4269,11 +4685,11 @@ async function listProducts(config2) {
4269
4685
  const items = (data.hits || []).map((p) => validateProductLoose(p));
4270
4686
  return { items, total: data.estimatedTotalHits || items.length };
4271
4687
  }
4272
- async function getProduct(idOrSlug, config2) {
4273
- const f = getFetcher(config2);
4274
- const meilisearchHost = config2?.meilisearch?.host || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_HOST || "https://ms-e5d999b2eaca-15654.sfo.meilisearch.io";
4275
- const meilisearchApiKey = config2?.meilisearch?.apiKey || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_API_KEY || "eee819b849798ad9091228c486ec05d0931e5292";
4276
- const meilisearchIndex = config2?.meilisearch?.index || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_INDEX || "merchify";
4688
+ async function getProduct(idOrSlug, config) {
4689
+ const f = getFetcher(config);
4690
+ const meilisearchHost = config?.meilisearch?.host || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_HOST || "https://search.snowcone.app";
4691
+ const meilisearchApiKey = config?.meilisearch?.apiKey || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_API_KEY || "eee819b849798ad9091228c486ec05d0931e5292";
4692
+ const meilisearchIndex = config?.meilisearch?.index || typeof process !== "undefined" && process.env?.NEXT_PUBLIC_MEILISEARCH_INDEX || "snowcone";
4277
4693
  const headers = {
4278
4694
  "Content-Type": "application/json"
4279
4695
  };
@@ -4288,13 +4704,13 @@ async function getProduct(idOrSlug, config2) {
4288
4704
  const raw = await res.json();
4289
4705
  return validateProductLoose(raw);
4290
4706
  }
4291
- function createClient(config2) {
4292
- const fetcher = getFetcher(config2);
4293
- const mockupService = new MockupServiceImpl(config2.mockup || {}, fetcher);
4707
+ function createClient(config) {
4708
+ const fetcher = getFetcher(config);
4709
+ const mockupService = new MockupServiceImpl(config.mockup || {}, fetcher);
4294
4710
  return {
4295
4711
  catalog: {
4296
- listProducts: (overrides) => listProducts({ ...config2, ...overrides }),
4297
- getProduct: (idOrSlug, overrides) => getProduct(idOrSlug, { ...config2, ...overrides })
4712
+ listProducts: (overrides) => listProducts({ ...config, ...overrides }),
4713
+ getProduct: (idOrSlug, overrides) => getProduct(idOrSlug, { ...config, ...overrides })
4298
4714
  },
4299
4715
  mockup: mockupService
4300
4716
  };
@@ -4318,6 +4734,7 @@ export {
4318
4734
  DEFAULT_ARTWORK_URL,
4319
4735
  DEFAULT_ASPECT_RATIO,
4320
4736
  DEFAULT_COLOR,
4737
+ DEFAULT_MOCKUP_BASE,
4321
4738
  DEFAULT_PLACEMENT_DIMENSIONS,
4322
4739
  Elements,
4323
4740
  ErrorManager,
@@ -4342,9 +4759,9 @@ export {
4342
4759
  VueAdapter,
4343
4760
  adapterRegistry,
4344
4761
  autoRegister,
4762
+ buildMockupUrl2 as buildMockupUrl,
4345
4763
  componentRegistry,
4346
4764
  computeDisabledChoices,
4347
- config,
4348
4765
  createAddToCartEvent,
4349
4766
  createAddToCartHandler,
4350
4767
  createCartDetail,
@@ -4384,6 +4801,7 @@ export {
4384
4801
  getEffectiveAlignment,
4385
4802
  getIncompleteSelectionMessage,
4386
4803
  getMissingSelections,
4804
+ getMockupUrl,
4387
4805
  getOptionRenderType,
4388
4806
  getPricePreview,
4389
4807
  getProduct,
@@ -4401,7 +4819,6 @@ export {
4401
4819
  prepareOptionRenderData,
4402
4820
  registerStandardComponents,
4403
4821
  resolveBestCombination,
4404
- resolveMockupConfig,
4405
4822
  resolveMockupId,
4406
4823
  resolveVariantId,
4407
4824
  retryOperation,