@snowcone-app/sdk 0.1.14 → 0.2.0

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
@@ -2,8 +2,9 @@ import {
2
2
  createDevFetcher
3
3
  } from "./chunk-7VO4EL2V.js";
4
4
  import {
5
+ REALTIME_WS_URL,
5
6
  RealtimeMockupService
6
- } from "./chunk-6MV7TDTM.js";
7
+ } from "./chunk-D5ZRGKA5.js";
7
8
 
8
9
  // src/validation.ts
9
10
  import { z } from "zod";
@@ -4648,6 +4649,198 @@ function createSvelteComponent(descriptor, svelte) {
4648
4649
  };
4649
4650
  }
4650
4651
 
4652
+ // src/realtime/grant.ts
4653
+ var DEFAULT_GRANT_BASE = "https://api.snowcone.app";
4654
+ async function mintRealtimeGrant(opts) {
4655
+ const f = opts.fetch ?? globalThis.fetch;
4656
+ const res = await f(`${opts.base ?? DEFAULT_GRANT_BASE}/realtime/grant`, {
4657
+ method: "POST",
4658
+ headers: {
4659
+ "Content-Type": "application/json",
4660
+ Authorization: `Bearer ${opts.apiKey}`
4661
+ },
4662
+ body: JSON.stringify({ shop: opts.shop })
4663
+ });
4664
+ if (!res.ok) {
4665
+ const detail = await res.text().catch(() => "");
4666
+ throw new Error(`realtime grant failed: ${res.status}${detail ? ` ${detail}` : ""}`);
4667
+ }
4668
+ return await res.json();
4669
+ }
4670
+ async function fetchRealtimeGrant(grantUrl, shop, fetchImpl) {
4671
+ const f = fetchImpl ?? globalThis.fetch;
4672
+ const res = await f(grantUrl, {
4673
+ method: "POST",
4674
+ headers: { "Content-Type": "application/json" },
4675
+ body: JSON.stringify({ shop })
4676
+ });
4677
+ if (!res.ok) throw new Error(`realtime grant failed: ${res.status}`);
4678
+ return await res.json();
4679
+ }
4680
+
4681
+ // src/realtime/session.ts
4682
+ var RenderSession = class {
4683
+ svc;
4684
+ opts;
4685
+ product;
4686
+ mockupCb = null;
4687
+ errorCb = null;
4688
+ ready = null;
4689
+ constructor(opts) {
4690
+ if (!opts.shop) throw new Error("RenderSession: `shop` is required");
4691
+ if (!opts.getToken && !opts.grantUrl) {
4692
+ throw new Error("RenderSession: provide `grantUrl` or `getToken` to authorize the session");
4693
+ }
4694
+ this.opts = opts;
4695
+ this.product = opts.product ?? null;
4696
+ this.svc = new RealtimeMockupService(opts.wsUrl ?? REALTIME_WS_URL);
4697
+ const getToken = opts.getToken ?? (() => fetchRealtimeGrant(opts.grantUrl, opts.shop, opts.fetch));
4698
+ this.svc.setTokenProvider(getToken);
4699
+ }
4700
+ /** Register a callback fired whenever rendered mockup URLs update. */
4701
+ onMockups(cb) {
4702
+ this.mockupCb = cb;
4703
+ return this;
4704
+ }
4705
+ /** Register an error callback (server/transport errors). */
4706
+ onError(cb) {
4707
+ this.errorCb = cb;
4708
+ return this;
4709
+ }
4710
+ /** Set or update the product. Sends config immediately if already connected. */
4711
+ setProduct(product) {
4712
+ this.product = product;
4713
+ this.svc.sendConfig(this.toConfig(product));
4714
+ }
4715
+ /**
4716
+ * Connect, authorize, and configure. Resolves once the session is ready to
4717
+ * accept {@link RenderSession.renderState}. Idempotent.
4718
+ */
4719
+ connect() {
4720
+ if (this.ready) return this.ready;
4721
+ if (!this.product) {
4722
+ return Promise.reject(
4723
+ new Error("RenderSession: set a product (via options.product or setProduct) before connect()")
4724
+ );
4725
+ }
4726
+ const product = this.product;
4727
+ this.ready = new Promise((resolve, reject) => {
4728
+ let settled = false;
4729
+ this.svc.setCallbacks({
4730
+ onConnected: () => {
4731
+ this.svc.sendConfig(this.toConfig(product));
4732
+ },
4733
+ onConfigReceived: () => {
4734
+ if (!settled) {
4735
+ settled = true;
4736
+ resolve();
4737
+ }
4738
+ },
4739
+ onMockupRendered: () => {
4740
+ this.mockupCb?.(this.svc.getState().mockupResults);
4741
+ },
4742
+ onAllMockupsRendered: (results) => {
4743
+ this.mockupCb?.(results);
4744
+ },
4745
+ onError: (error) => {
4746
+ this.errorCb?.(error);
4747
+ if (!settled) {
4748
+ settled = true;
4749
+ reject(new Error(error));
4750
+ }
4751
+ }
4752
+ });
4753
+ this.svc.sendConfig(this.toConfig(product));
4754
+ this.svc.connect();
4755
+ });
4756
+ return this.ready;
4757
+ }
4758
+ /**
4759
+ * Render a canvas state for a placement. The server rasterizes it and fetches
4760
+ * the assets referenced inside — no pixels are uploaded. Results arrive via
4761
+ * {@link RenderSession.onMockups}. Safe to call repeatedly (e.g. on every
4762
+ * canvas edit); pass `throttleMs` to debounce during live dragging.
4763
+ *
4764
+ * `placement` is the print-area NAME (e.g. `'Front'`, the product's
4765
+ * `placements[].label`) — a different id from the product's `mockupIds`, and it
4766
+ * must match an artboard in `state`. The server renders only once it has a state
4767
+ * for EVERY required placement, so on a multi-placement product call this once
4768
+ * per placement; a missing one surfaces as an `incomplete_canvas_placements`
4769
+ * error on {@link RenderSession.onError}.
4770
+ *
4771
+ * `state` is the flattened `serializeStateForServer()` shape — NOT the
4772
+ * un-flattened shape `createDesignState` takes.
4773
+ */
4774
+ async renderState(placement, state, throttleMs = 0) {
4775
+ await this.connect();
4776
+ this.svc.sendCanvasState(placement, state, this.product?.mockupIds.length ?? 1, throttleMs);
4777
+ }
4778
+ /**
4779
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). The server resolves
4780
+ * the `stateId`, fetches the referenced assets, and renders — no pixels are
4781
+ * uploaded and you don't need to hold the canvas JSON. Ideal for re-rendering
4782
+ * a stored design (fulfillment, agents, server-to-server). Results arrive via
4783
+ * {@link RenderSession.onMockups}.
4784
+ */
4785
+ async renderSavedState(placement, stateId) {
4786
+ await this.connect();
4787
+ this.svc.sendCanvasStateRef(placement, stateId);
4788
+ }
4789
+ /** Update only the mockup ids to render (reuses the current state). */
4790
+ updateMockupIds(mockupIds) {
4791
+ if (this.product) this.product = { ...this.product, mockupIds };
4792
+ this.svc.updateMockupIds(mockupIds);
4793
+ }
4794
+ /** Current rendered results. */
4795
+ getMockups() {
4796
+ return this.svc.getState().mockupResults;
4797
+ }
4798
+ /** Close the WebSocket and stop auto-renew. */
4799
+ close() {
4800
+ this.svc.disconnect();
4801
+ this.ready = null;
4802
+ }
4803
+ /** Escape hatch: the underlying low-level service. */
4804
+ get service() {
4805
+ return this.svc;
4806
+ }
4807
+ toConfig(product) {
4808
+ return {
4809
+ productId: product.productId,
4810
+ mockupIds: product.mockupIds,
4811
+ // shop comes from the grant token server-side; sent for completeness.
4812
+ shop: this.opts.shop,
4813
+ // WebSocketConfig requires these; the server treats empty variant as "none".
4814
+ variantId: product.variantId ?? "",
4815
+ width: product.width ?? 1e3,
4816
+ ...product.placementSettings ? { placementSettings: product.placementSettings } : {}
4817
+ };
4818
+ }
4819
+ };
4820
+
4821
+ // src/realtime/design-state.ts
4822
+ var TRANSPARENT_1X1_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
4823
+ async function createDesignState(input) {
4824
+ const f = input.fetch ?? globalThis.fetch;
4825
+ const res = await f(`${input.base ?? DEFAULT_GRANT_BASE}/snowcone/design-states`, {
4826
+ method: "POST",
4827
+ headers: { "Content-Type": "application/json" },
4828
+ body: JSON.stringify({
4829
+ productId: input.productId,
4830
+ stateJson: JSON.stringify(input.state),
4831
+ previewBase64: input.previewBase64 ?? TRANSPARENT_1X1_PNG
4832
+ })
4833
+ });
4834
+ if (!res.ok) {
4835
+ const detail = await res.text().catch(() => "");
4836
+ throw new Error(`createDesignState failed: ${res.status}${detail ? ` ${detail}` : ""}`);
4837
+ }
4838
+ const body = await res.json();
4839
+ const ds = body?.designState;
4840
+ if (!ds?.id) throw new Error("createDesignState: response missing designState.id");
4841
+ return { stateId: ds.id, stateUrl: ds.stateUrl ?? "", previewUrl: ds.previewUrl ?? "" };
4842
+ }
4843
+
4651
4844
  // src/index.ts
4652
4845
  function getFetcher(config) {
4653
4846
  return config?.fetcher || globalThis.fetch.bind(globalThis);
@@ -4734,6 +4927,7 @@ export {
4734
4927
  DEFAULT_ARTWORK_URL,
4735
4928
  DEFAULT_ASPECT_RATIO,
4736
4929
  DEFAULT_COLOR,
4930
+ DEFAULT_GRANT_BASE,
4737
4931
  DEFAULT_MOCKUP_BASE,
4738
4932
  DEFAULT_PLACEMENT_DIMENSIONS,
4739
4933
  Elements,
@@ -4747,7 +4941,9 @@ export {
4747
4941
  ProductLoader,
4748
4942
  ProductProps,
4749
4943
  PropertyManager,
4944
+ REALTIME_WS_URL,
4750
4945
  RealtimeMockupService,
4946
+ RenderSession,
4751
4947
  StandardComponents,
4752
4948
  StandardEvents,
4753
4949
  StateManager,
@@ -4769,6 +4965,7 @@ export {
4769
4965
  createComponent,
4770
4966
  createContextProvider,
4771
4967
  createDesignForPlacements,
4968
+ createDesignState,
4772
4969
  createDevFetcher,
4773
4970
  createErrorHandler,
4774
4971
  createEventManager,
@@ -4791,6 +4988,7 @@ export {
4791
4988
  describeProductPrice,
4792
4989
  describeProductTitle,
4793
4990
  extractProductId,
4991
+ fetchRealtimeGrant,
4794
4992
  filterImagePlacements,
4795
4993
  findBestCombination,
4796
4994
  findClosestSnapPoint,
@@ -4814,6 +5012,7 @@ export {
4814
5012
  isValidAlignment,
4815
5013
  isValidTileCount,
4816
5014
  listProducts,
5015
+ mintRealtimeGrant,
4817
5016
  mockupUrl,
4818
5017
  normalizeChoice,
4819
5018
  prepareOptionRenderData,
package/dist/react.cjs CHANGED
@@ -30,9 +30,12 @@ module.exports = __toCommonJS(react_exports);
30
30
  // src/realtime/react.ts
31
31
  var import_react = require("react");
32
32
 
33
+ // src/realtime/constants.ts
34
+ var REALTIME_WS_URL = "wss://cdn.snowcone.app/realtime";
35
+
33
36
  // src/realtime/websocket.ts
34
37
  var RealtimeMockupService = class {
35
- constructor(wsUrl = "wss://WS_URL_NOT_CONFIGURED.invalid/realtime") {
38
+ constructor(wsUrl = REALTIME_WS_URL) {
36
39
  this.wsUrl = wsUrl;
37
40
  }
38
41
  ws = null;
@@ -501,6 +504,32 @@ var RealtimeMockupService = class {
501
504
  }
502
505
  return true;
503
506
  }
507
+ /**
508
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
509
+ * instead of inline state; the server resolves it, fetches the referenced
510
+ * assets, and renders — nothing is uploaded and the client need not hold the
511
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
512
+ */
513
+ sendCanvasStateRef(placement, stateId) {
514
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.isConfigured) {
515
+ const reason = !this.ws ? "no WebSocket" : this.ws.readyState !== WebSocket.OPEN ? `ws.readyState=${this.ws.readyState}` : "not configured";
516
+ console.log(`[WS] sendCanvasStateRef BLOCKED for "${placement}": ${reason}`);
517
+ return false;
518
+ }
519
+ this.lastSentVersion = ++this.requestVersion;
520
+ this.latestSentVersionByPlacement[placement] = this.lastSentVersion;
521
+ const message = JSON.stringify({
522
+ type: "canvas_state",
523
+ placement,
524
+ version: this.lastSentVersion,
525
+ stateId
526
+ });
527
+ this.ws.send(message);
528
+ this.lastBlobSentAt = Date.now();
529
+ this.addLog(`Sent canvas stateId "${stateId}" for "${placement}" (v${this.lastSentVersion})`, "sent");
530
+ this.callbacks.onBlobSent?.(placement);
531
+ return true;
532
+ }
504
533
  sendCanvasStateImmediately(placement, state) {
505
534
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
506
535
  console.log(`[WS] sendCanvasStateImmediately ABORTED: ws not open`);
package/dist/react.d.cts CHANGED
@@ -1,4 +1,4 @@
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';
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-Poy8LZNA.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, 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';
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-Poy8LZNA.js';
2
2
 
3
3
  interface UseRealtimeMockupOptions {
4
4
  wsUrl?: string;
package/dist/react.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  RealtimeMockupService
3
- } from "./chunk-6MV7TDTM.js";
3
+ } from "./chunk-D5ZRGKA5.js";
4
4
 
5
5
  // src/realtime/react.ts
6
6
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -356,6 +356,13 @@ declare class RealtimeMockupService {
356
356
  * Alternative to sendCanvasBlob — the server renders the PNG instead of the client.
357
357
  */
358
358
  sendCanvasState(placement: string, state: object, mockupCount?: number, baseThrottleMs?: number): boolean;
359
+ /**
360
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
361
+ * instead of inline state; the server resolves it, fetches the referenced
362
+ * assets, and renders — nothing is uploaded and the client need not hold the
363
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
364
+ */
365
+ sendCanvasStateRef(placement: string, stateId: string): boolean;
359
366
  private sendCanvasStateImmediately;
360
367
  private sendBlobImmediately;
361
368
  /**
@@ -385,4 +392,4 @@ declare class RealtimeMockupService {
385
392
  clearMockups(): void;
386
393
  }
387
394
 
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 };
395
+ 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 PlacementSettings as P, RealtimeMockupService 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, type ProductComponentContext as h, type RealtimeMockupCallbacks as i, type RealtimeMockupState as j, type RenderResult as k, type WebSocketMessage as l, adapterRegistry as m, computeDisabledChoices as n, createComponent as o, defineComponent as p, deriveDefaultSelection as q, findBestCombination as r, getPricePreview as s, isOptionAvailable as t, resolveBestCombination as u };
@@ -356,6 +356,13 @@ declare class RealtimeMockupService {
356
356
  * Alternative to sendCanvasBlob — the server renders the PNG instead of the client.
357
357
  */
358
358
  sendCanvasState(placement: string, state: object, mockupCount?: number, baseThrottleMs?: number): boolean;
359
+ /**
360
+ * Render a SAVED canvas by reference (ADR-0079 Phase 4). Sends a `stateId`
361
+ * instead of inline state; the server resolves it, fetches the referenced
362
+ * assets, and renders — nothing is uploaded and the client need not hold the
363
+ * canvas JSON. One-shot (no throttle). Results arrive like any other render.
364
+ */
365
+ sendCanvasStateRef(placement: string, stateId: string): boolean;
359
366
  private sendCanvasStateImmediately;
360
367
  private sendBlobImmediately;
361
368
  /**
@@ -385,4 +392,4 @@ declare class RealtimeMockupService {
385
392
  clearMockups(): void;
386
393
  }
387
394
 
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 };
395
+ 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 PlacementSettings as P, RealtimeMockupService 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, type ProductComponentContext as h, type RealtimeMockupCallbacks as i, type RealtimeMockupState as j, type RenderResult as k, type WebSocketMessage as l, adapterRegistry as m, computeDisabledChoices as n, createComponent as o, defineComponent as p, deriveDefaultSelection as q, findBestCombination as r, getPricePreview as s, isOptionAvailable as t, resolveBestCombination as u };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@snowcone-app/sdk",
3
- "version": "0.1.14",
4
- "description": "Merch Javascript SDK for product mockups and print-on-demand",
3
+ "version": "0.2.0",
4
+ "description": "Snowcone SDK for product mockups and print-on-demand",
5
5
  "keywords": [
6
6
  "merch",
7
7
  "mockup",
@@ -14,10 +14,13 @@
14
14
  "license": "MIT",
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "https://github.com/snowcone-app/ui-components.git",
17
+ "url": "https://github.com/snowcone-app/snowcone-monorepo.git",
18
18
  "directory": "packages/sdk"
19
19
  },
20
- "homepage": "https://snowcone.app",
20
+ "bugs": {
21
+ "url": "https://github.com/snowcone-app/snowcone-monorepo/issues"
22
+ },
23
+ "homepage": "https://developers.snowcone.app/sdk",
21
24
  "private": false,
22
25
  "type": "module",
23
26
  "main": "dist/index.cjs",