@syntrologie/runtime-sdk 2.14.0 → 2.16.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.
Files changed (74) hide show
  1. package/README.md +1 -0
  2. package/dist/SmartCanvasApp.d.ts +4 -1
  3. package/dist/SmartCanvasElementLit.d.ts +166 -0
  4. package/dist/actions/schema.js +4 -3
  5. package/dist/actions/types.d.ts +8 -2
  6. package/dist/anchor/AnchorResolver.d.ts +1 -0
  7. package/dist/api-lit.d.ts +84 -0
  8. package/dist/api.d.ts +3 -0
  9. package/dist/apps/builtinRuntimeModules-lit.d.ts +20 -0
  10. package/dist/bootstrap-init.d.ts +2 -0
  11. package/dist/bootstrap-types.d.ts +10 -0
  12. package/dist/chunk-2IQ2PTLJ.js +871 -0
  13. package/dist/chunk-2IQ2PTLJ.js.map +7 -0
  14. package/dist/{chunk-YLLWLUQX.js → chunk-4HXPGXUC.js} +1 -16
  15. package/dist/{chunk-YLLWLUQX.js.map → chunk-4HXPGXUC.js.map} +1 -1
  16. package/dist/{chunk-IR6UOR63.js → chunk-GX7BBYX6.js} +2 -2
  17. package/dist/chunk-JMHRHAEL.js +18 -0
  18. package/dist/chunk-JMHRHAEL.js.map +7 -0
  19. package/dist/{chunk-JCDCANR7.js → chunk-NVV7IWJC.js} +1301 -1084
  20. package/dist/chunk-NVV7IWJC.js.map +7 -0
  21. package/dist/{chunk-77TNZ66J.js → chunk-XVRDKBYF.js} +3 -3
  22. package/dist/components/SyntroCanvasOverlay.d.ts +100 -0
  23. package/dist/components/SyntroDrawer.d.ts +110 -0
  24. package/dist/components/SyntroLauncher.d.ts +105 -0
  25. package/dist/components/SyntroTileCard.d.ts +74 -0
  26. package/dist/components/SyntroTileWheel.d.ts +51 -0
  27. package/dist/config/schema.js +3 -2
  28. package/dist/controllers/DecisionController.d.ts +48 -0
  29. package/dist/controllers/NotificationsController.d.ts +59 -0
  30. package/dist/controllers/RuntimeController.d.ts +52 -0
  31. package/dist/controllers/RuntimeEventsController.d.ts +42 -0
  32. package/dist/controllers/ThemeController.d.ts +110 -0
  33. package/dist/controllers/index.d.ts +13 -0
  34. package/dist/decisions/schema.js +2 -1
  35. package/dist/decisions/types.d.ts +4 -0
  36. package/dist/editorLoader.d.ts +5 -0
  37. package/dist/index-lit.d.ts +40 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +1338 -19
  40. package/dist/index.js.map +4 -4
  41. package/dist/interop/LitInReact.d.ts +27 -0
  42. package/dist/interop/ReactInLit.d.ts +42 -0
  43. package/dist/interop/index.d.ts +7 -0
  44. package/dist/metrics/sessionMetrics.d.ts +4 -0
  45. package/dist/notifications/SyntroToastStack.d.ts +43 -0
  46. package/dist/platform/PlatformAdapter.d.ts +46 -0
  47. package/dist/platform/ShopifyAdapter.d.ts +36 -0
  48. package/dist/platform/ShopifyAnchorResolver.d.ts +31 -0
  49. package/dist/platform/ShopifyAntiFlicker.d.ts +21 -0
  50. package/dist/platform/ShopifyPixelBridge.d.ts +37 -0
  51. package/dist/platform/detect.d.ts +9 -0
  52. package/dist/platform/index.d.ts +10 -0
  53. package/dist/platform/shopify-cookie-contract.d.ts +39 -0
  54. package/dist/react-compat.d.ts +114 -0
  55. package/dist/react.js +6 -4
  56. package/dist/react.js.map +1 -1
  57. package/dist/shopify-pixel-entry.d.ts +68 -0
  58. package/dist/shopify-pixel.js +77 -0
  59. package/dist/shopify-pixel.js.map +7 -0
  60. package/dist/shopify-pixel.min.js +2 -0
  61. package/dist/shopify-pixel.min.js.map +7 -0
  62. package/dist/smart-canvas.esm.js +856 -240
  63. package/dist/smart-canvas.esm.js.map +4 -4
  64. package/dist/smart-canvas.js +28769 -37080
  65. package/dist/smart-canvas.js.map +4 -4
  66. package/dist/smart-canvas.min.js +855 -240
  67. package/dist/smart-canvas.min.js.map +4 -4
  68. package/dist/theme/index.js +30 -0
  69. package/dist/theme/index.js.map +7 -0
  70. package/dist/version.d.ts +1 -1
  71. package/package.json +10 -1
  72. package/dist/chunk-JCDCANR7.js.map +0 -7
  73. /package/dist/{chunk-IR6UOR63.js.map → chunk-GX7BBYX6.js.map} +0 -0
  74. /package/dist/{chunk-77TNZ66J.js.map → chunk-XVRDKBYF.js.map} +0 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * LitInReact — A React wrapper that renders a Lit custom element.
3
+ *
4
+ * Used during the React → Lit migration to embed Lit elements within
5
+ * React-based parent components. Properties are forwarded to the
6
+ * custom element via ref + direct property assignment.
7
+ *
8
+ * Usage:
9
+ * const SyntroTileCard = createLitReactWrapper('syntro-tile-card');
10
+ * <SyntroTileCard tile={tileData} />
11
+ *
12
+ * This is a temporary bridge — delete when all React parents are migrated to Lit.
13
+ */
14
+ import { type FC } from 'react';
15
+ interface LitWrapperProps {
16
+ tagName: string;
17
+ props?: Record<string, unknown>;
18
+ className?: string;
19
+ style?: React.CSSProperties;
20
+ }
21
+ export declare const LitWrapper: FC<LitWrapperProps>;
22
+ /**
23
+ * Creates a React component that wraps a specific Lit custom element.
24
+ * All props are forwarded as element properties.
25
+ */
26
+ export declare function createLitReactWrapper(tagName: string): FC<Record<string, unknown>>;
27
+ export {};
@@ -0,0 +1,42 @@
1
+ /**
2
+ * ReactInLit — A Lit element that mounts a React component inside itself.
3
+ *
4
+ * Used during the React → Lit migration to embed React subtrees within
5
+ * Lit-based parent components. The React root is created in firstUpdated()
6
+ * and re-rendered whenever Lit reactive properties change.
7
+ *
8
+ * Usage:
9
+ * class MyLitElement extends LitElement {
10
+ * render() {
11
+ * return html`<react-bridge .component=${MyReactComponent} .props=${{ name: 'Alex' }}></react-bridge>`;
12
+ * }
13
+ * }
14
+ *
15
+ * This is a temporary bridge — delete when all React components are migrated.
16
+ */
17
+ import type { PropertyValues } from 'lit';
18
+ import { LitElement } from 'lit';
19
+ import type { ComponentType } from 'react';
20
+ export declare class ReactBridge extends LitElement {
21
+ #private;
22
+ static properties: {
23
+ component: {
24
+ attribute: boolean;
25
+ };
26
+ props: {
27
+ attribute: boolean;
28
+ };
29
+ };
30
+ component: ComponentType<Record<string, unknown>> | null;
31
+ props: Record<string, unknown>;
32
+ createRenderRoot(): this;
33
+ render(): import("lit-html").TemplateResult<1>;
34
+ firstUpdated(): void;
35
+ updated(_changed: PropertyValues): void;
36
+ disconnectedCallback(): void;
37
+ }
38
+ declare global {
39
+ interface HTMLElementTagNameMap {
40
+ 'react-bridge': ReactBridge;
41
+ }
42
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * React ↔ Lit interop bridges for incremental migration.
3
+ *
4
+ * These are temporary utilities — delete once all components are migrated to Lit.
5
+ */
6
+ export { createLitReactWrapper, LitWrapper } from './LitInReact.js';
7
+ export { ReactBridge } from './ReactInLit.js';
@@ -36,6 +36,7 @@ export interface SessionMetricTrackerOptions {
36
36
  */
37
37
  export declare class SessionMetricTracker {
38
38
  private metrics;
39
+ private subscribers;
39
40
  private experiments?;
40
41
  private attributePrefix;
41
42
  private onMetricChange?;
@@ -86,12 +87,15 @@ export declare class SessionMetricTracker {
86
87
  * Reset all metrics (clear the session).
87
88
  */
88
89
  resetAll(): void;
90
+ subscribe(callback: () => void): () => void;
91
+ destroy(): void;
89
92
  /**
90
93
  * Update the experiment client (useful if experiments client changes).
91
94
  */
92
95
  setExperiments(experiments: ExperimentClient): void;
93
96
  private updateExperimentAttributes;
94
97
  private syncAllToExperiments;
98
+ private notifySubscribers;
95
99
  private loadFromStorage;
96
100
  private saveToStorage;
97
101
  }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SyntroToastStack — Lit web component equivalent of NotificationToastStack.tsx.
3
+ *
4
+ * Renders a fixed-position stack of notification toasts with:
5
+ * - Slide-in entrance animation
6
+ * - Progress bar with hover-pause
7
+ * - Dismiss button (fires `notification-dismiss` event)
8
+ * - Click-to-open (fires `notification-click` event)
9
+ * - CSS variable theming (--sc-notification-*)
10
+ *
11
+ * Usage:
12
+ * <syntro-toast-stack
13
+ * .notifications=${activeNotifications}
14
+ * .position=${'right'}
15
+ * ></syntro-toast-stack>
16
+ *
17
+ * element.addEventListener('notification-dismiss', (e) => dismiss(e.detail.id));
18
+ * element.addEventListener('notification-click', (e) => open(e.detail.notification));
19
+ *
20
+ * Decorator-free: uses `static override properties` (tsconfig has no experimentalDecorators).
21
+ */
22
+ import { LitElement } from 'lit';
23
+ import type { ActiveNotification } from './types.js';
24
+ export declare class SyntroToastStack extends LitElement {
25
+ #private;
26
+ static properties: {
27
+ notifications: {
28
+ attribute: boolean;
29
+ };
30
+ position: {
31
+ type: StringConstructor;
32
+ };
33
+ };
34
+ notifications: ActiveNotification[];
35
+ position: 'left' | 'right';
36
+ static styles: import("lit").CSSResult;
37
+ render(): import("lit-html").TemplateResult<1>;
38
+ }
39
+ declare global {
40
+ interface HTMLElementTagNameMap {
41
+ 'syntro-toast-stack': SyntroToastStack;
42
+ }
43
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * PlatformAdapter — interface for platform-specific integrations.
3
+ *
4
+ * Each supported host platform (Shopify, WordPress, etc.) implements this
5
+ * interface so the runtime can react to platform-specific lifecycle events
6
+ * (section load/unload, theme changes, etc.) without coupling to any single
7
+ * platform's API surface.
8
+ */
9
+ export interface PlatformAdapter {
10
+ /** Human-readable platform name, e.g. "shopify" */
11
+ readonly name: string;
12
+ /**
13
+ * Synchronously detect whether the current page belongs to this platform.
14
+ * Must be safe to call in any environment (returns false when unsure).
15
+ */
16
+ detect(): boolean;
17
+ /**
18
+ * Async initialization hook. Called once after the adapter is selected.
19
+ * May wait for platform-specific readiness signals (e.g. Shopify sections).
20
+ */
21
+ onInit(): Promise<void>;
22
+ /**
23
+ * Called by the runtime when a region's DOM has been mutated.
24
+ */
25
+ onRegionMutated(regionId: string): void;
26
+ /**
27
+ * Returns a CSS-selector scope prefix for the given anchor element,
28
+ * based on the platform's section/region structure.
29
+ * Returns empty string when the anchor is not inside a known section.
30
+ */
31
+ getAnchorScope(anchor: Element): string;
32
+ /**
33
+ * Subscribe to "region is about to unload" events.
34
+ * Returns an unsubscribe function.
35
+ */
36
+ onRegionWillUnload(callback: (regionId: string) => void): () => void;
37
+ /**
38
+ * Subscribe to "region finished loading" events.
39
+ * Returns an unsubscribe function.
40
+ */
41
+ onRegionDidLoad(callback: (regionId: string) => void): () => void;
42
+ /**
43
+ * Tear down all listeners and internal state.
44
+ */
45
+ destroy(): void;
46
+ }
@@ -0,0 +1,36 @@
1
+ import type { PlatformAdapter } from './PlatformAdapter';
2
+ /**
3
+ * Shopify-specific platform adapter.
4
+ *
5
+ * Detects Shopify storefronts and hooks into Shopify's section rendering
6
+ * lifecycle events (`shopify:section:load` / `shopify:section:unload`) so
7
+ * the runtime can react to dynamic section swaps in Shopify Online Store 2.0.
8
+ */
9
+ export declare class ShopifyAdapter implements PlatformAdapter {
10
+ readonly name: "shopify";
11
+ private loadCallbacks;
12
+ private unloadCallbacks;
13
+ private abortController;
14
+ private antiFlicker;
15
+ private readonly initTimeoutMs;
16
+ constructor(options?: {
17
+ initTimeoutMs?: number;
18
+ });
19
+ detect(): boolean;
20
+ /**
21
+ * Wait for Shopify sections to be ready, then attach lifecycle listeners.
22
+ *
23
+ * Resolution order:
24
+ * 1. `.shopify-section` already in DOM -> resolve immediately
25
+ * 2. `shopify:section:load` fires -> resolve
26
+ * 3. Timeout (default 3000ms) -> resolve anyway (graceful degradation)
27
+ */
28
+ onInit(): Promise<void>;
29
+ onRegionDidLoad(callback: (regionId: string) => void): () => void;
30
+ onRegionWillUnload(callback: (regionId: string) => void): () => void;
31
+ onRegionMutated(_regionId: string): void;
32
+ getAnchorScope(anchor: Element): string;
33
+ destroy(): void;
34
+ private attachSectionListeners;
35
+ private waitForInitialSections;
36
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Theme families sharing identical DOM structures.
3
+ * Dawn family: Dawn, Sense, Craft, Ride, Refresh, Colorblock, Taste, Studio
4
+ */
5
+ export type ShopifyThemeFamily = 'dawn' | 'prestige' | 'impulse' | 'turbo' | 'trademark' | 'legacy' | 'unknown';
6
+ /**
7
+ * Detect the Shopify theme family from window.Shopify.theme.name.
8
+ */
9
+ export declare function detectThemeFamily(): ShopifyThemeFamily;
10
+ export interface AnchorValidation {
11
+ unique: boolean;
12
+ matchCount: number;
13
+ }
14
+ /**
15
+ * ShopifyAnchorResolver — provides Shopify-aware selector scoping and
16
+ * uniqueness validation for anchor configuration.
17
+ */
18
+ export declare class ShopifyAnchorResolver {
19
+ /**
20
+ * Scope a CSS selector to its containing .shopify-section wrapper.
21
+ */
22
+ scopeSelector(selector: string, element: Element): string;
23
+ /**
24
+ * Validate that a selector matches exactly one element in the document.
25
+ */
26
+ validateUniqueness(selector: string): AnchorValidation;
27
+ /**
28
+ * Get the current theme family for diagnostic/logging purposes.
29
+ */
30
+ getThemeFamily(): ShopifyThemeFamily;
31
+ }
@@ -0,0 +1,21 @@
1
+ import type { PlatformAdapter } from './PlatformAdapter';
2
+ /**
3
+ * ShopifyAntiFlicker -- prevents visible content flash during section re-renders.
4
+ *
5
+ * When Shopify's Section Rendering API replaces a section's innerHTML, our
6
+ * injected content (identified by [data-syntro-action-id]) disappears momentarily.
7
+ * This module hooks section:unload to pre-hide our content and section:load to
8
+ * fade it back in, eliminating the visible flash.
9
+ */
10
+ export declare class ShopifyAntiFlicker {
11
+ private readonly adapter;
12
+ private unsubUnload;
13
+ private unsubLoad;
14
+ private destroyed;
15
+ constructor(adapter: PlatformAdapter);
16
+ activate(): void;
17
+ destroy(): void;
18
+ private hideContentInSection;
19
+ private revealContentInSection;
20
+ private sectionSelector;
21
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ShopifyPixelBridge — storefront-side cookie writer for checkout attribution.
3
+ *
4
+ * The SDK can't run in Shopify's sandboxed checkout. This bridge writes a
5
+ * `syntro_experiments` cookie containing the telemetry context (distinct_id,
6
+ * telemetry host, public PostHog key). A Shopify Web Pixel extension running
7
+ * in the checkout sandbox reads the cookie and POSTs
8
+ * `checkout_completed` / `product_added_to_cart` events to
9
+ * `{telemetry_host}/t/{telemetry_key}/e/` — the Syntrologie PostHog proxy.
10
+ *
11
+ * Experiment attribution is NOT carried in the cookie. PostHog already tracks
12
+ * each shopper's assignments server-side via `$experiment_started` events
13
+ * fired from the storefront SDK under the same distinct_id. When the pixel's
14
+ * checkout event lands with that distinct_id, PostHog auto-attributes the
15
+ * conversion to the correct variant. The cookie only needs to ferry identity
16
+ * and destination.
17
+ *
18
+ * Max-age: 30 days (covers long shopping sessions).
19
+ * SameSite: Lax (readable by checkout on the same registrable domain).
20
+ */
21
+ export interface ShopifyPixelBridgeContext {
22
+ /** PostHog distinct_id from the storefront telemetry client. */
23
+ distinctId: string;
24
+ /** Telemetry proxy host, e.g. `https://telemetry.syntrologie.com`. No trailing slash. */
25
+ telemetryHost: string;
26
+ /** Public write-only PostHog key. */
27
+ telemetryKey: string;
28
+ }
29
+ export declare class ShopifyPixelBridge {
30
+ private context;
31
+ constructor(context: ShopifyPixelBridgeContext);
32
+ /** Update the telemetry context (e.g., after consent is granted and
33
+ * PostHog finally initializes and a real distinct_id becomes available). */
34
+ updateContext(context: ShopifyPixelBridgeContext): void;
35
+ destroy(): void;
36
+ private writeCookie;
37
+ }
@@ -0,0 +1,9 @@
1
+ import type { PlatformAdapter } from './PlatformAdapter';
2
+ /**
3
+ * Auto-detect the current host platform.
4
+ *
5
+ * Iterates registered adapters and returns the first one that detects
6
+ * its platform signals. Returns `null` if no platform is recognized
7
+ * (i.e. the page is a generic/unknown site).
8
+ */
9
+ export declare function detectPlatform(): PlatformAdapter | null;
@@ -0,0 +1,10 @@
1
+ export { detectPlatform } from './detect';
2
+ export type { PlatformAdapter } from './PlatformAdapter';
3
+ export { ShopifyAdapter } from './ShopifyAdapter';
4
+ export type { AnchorValidation, ShopifyThemeFamily } from './ShopifyAnchorResolver';
5
+ export { detectThemeFamily, ShopifyAnchorResolver } from './ShopifyAnchorResolver';
6
+ export { ShopifyAntiFlicker } from './ShopifyAntiFlicker';
7
+ export type { ShopifyPixelBridgeContext } from './ShopifyPixelBridge';
8
+ export { ShopifyPixelBridge } from './ShopifyPixelBridge';
9
+ export type { ShopifyPixelCookie } from './shopify-cookie-contract';
10
+ export { COOKIE_NAME as SHOPIFY_COOKIE_NAME } from './shopify-cookie-contract';
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Shared cookie contract between ShopifyPixelBridge (SDK) and the
3
+ * Shopify Web Pixel extension. Both sides must agree on this format.
4
+ *
5
+ * Cookie name: syntro_experiments (legacy name — kept for backward compat;
6
+ * the cookie carries telemetry identity, not experiment assignments)
7
+ * Cookie value: JSON-encoded ShopifyPixelCookie
8
+ *
9
+ * The storefront SDK writes a snapshot of its telemetry context — the
10
+ * PostHog distinct_id, the telemetry proxy host, and the public PostHog
11
+ * project key. The Web Pixel extension runs in Shopify's sandboxed
12
+ * checkout (which cannot reach the SDK), reads this cookie, and posts
13
+ * PostHog-shaped checkout events with the same distinct_id so that
14
+ * checkout events JOIN to the storefront session. PostHog already knows
15
+ * which experiments that shopper is in (via `$experiment_started` events
16
+ * fired from the storefront), so attribution happens server-side without
17
+ * the pixel having to re-transmit assignments.
18
+ *
19
+ * Naming note: the fields are `telemetry_host` / `telemetry_key` — not
20
+ * `api_host` / `api_key` — to disambiguate from the other `apiHost`
21
+ * fields in the codebase. This cookie is strictly about telemetry.
22
+ *
23
+ * Forward compat: pixel code must check `version` and reject unknown
24
+ * shapes. Bump `version` when fields are removed or semantics change.
25
+ */
26
+ export interface ShopifyPixelCookie {
27
+ /** Schema version — pixel rejects unknown versions. Bump on breaking changes. */
28
+ version: 1;
29
+ /** PostHog distinct_id from the storefront SDK, used so checkout events
30
+ * join onto the pre-checkout session. Required — without it the pixel
31
+ * would create a new anonymous user at checkout and the funnel breaks. */
32
+ distinct_id: string;
33
+ /** Telemetry proxy host (e.g., `https://telemetry.syntrologie.com`). No trailing slash. */
34
+ telemetry_host: string;
35
+ /** Public write-only PostHog key (embedded in the client). */
36
+ telemetry_key: string;
37
+ }
38
+ export declare const COOKIE_NAME = "syntro_experiments";
39
+ export declare const COOKIE_VERSION: 1;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * React compatibility layer for the Syntro SDK.
3
+ *
4
+ * Thin wrapper that calls the framework-agnostic Syntro.init() and exposes
5
+ * the result via React context. This file is the React entry point — it does
6
+ * NOT import any React UI components from the SDK (no SmartCanvasApp, no
7
+ * RuntimeProvider). The Lit bundle powers all rendering; this layer only
8
+ * provides React hooks for customers who embed the SDK in a React app.
9
+ *
10
+ * Usage:
11
+ * ```tsx
12
+ * import { SyntroProvider, useSyntro } from '@syntrologie/runtime-sdk/react';
13
+ *
14
+ * function App() {
15
+ * return (
16
+ * <SyntroProvider token="syn_xxx">
17
+ * <MyApp />
18
+ * </SyntroProvider>
19
+ * );
20
+ * }
21
+ *
22
+ * function MyComponent() {
23
+ * const { canvas, experiments, telemetry, isReady } = useSyntro();
24
+ * // ...
25
+ * }
26
+ * ```
27
+ */
28
+ import { type ReactNode } from 'react';
29
+ import type { SmartCanvasHandle } from './api';
30
+ import { type SyntroInitOptions, type SyntroInitResult } from './bootstrap';
31
+ import type { ExperimentClient } from './experiments/types';
32
+ import type { TelemetryClient } from './telemetry/types';
33
+ export interface SyntroContextValue {
34
+ /** The SmartCanvas handle */
35
+ canvas: SmartCanvasHandle | null;
36
+ /** The experiment client */
37
+ experiments: ExperimentClient | null;
38
+ /** The telemetry client */
39
+ telemetry: TelemetryClient | null;
40
+ /** Whether the SDK has finished initializing */
41
+ isReady: boolean;
42
+ /** Any error that occurred during initialization */
43
+ error: Error | null;
44
+ }
45
+ export interface SyntroProviderProps {
46
+ /**
47
+ * The Syntro token containing all credentials.
48
+ */
49
+ token: string;
50
+ /**
51
+ * Optional canvas configuration overrides.
52
+ */
53
+ canvasOptions?: SyntroInitOptions['canvas'];
54
+ /**
55
+ * Custom config fetcher to override the default experiment-based fetcher.
56
+ * Use this for local development or when bypassing the experiment server.
57
+ */
58
+ fetcher?: SyntroInitOptions['fetcher'];
59
+ /**
60
+ * Children to render.
61
+ */
62
+ children: ReactNode;
63
+ /**
64
+ * Called when initialization completes.
65
+ */
66
+ onReady?: (result: SyntroInitResult) => void;
67
+ /**
68
+ * Called when initialization fails.
69
+ */
70
+ onError?: (error: Error) => void;
71
+ }
72
+ /**
73
+ * Provider component that initializes the Syntro SDK.
74
+ *
75
+ * Calls `Syntro.init()` (framework-agnostic) in a useEffect, stores the
76
+ * result in React state, and provides it via context. Does NOT render any
77
+ * SDK UI — the Lit custom element handles all rendering.
78
+ *
79
+ * Place this at the root of your app or at the top of any subtree
80
+ * that needs access to the SDK.
81
+ */
82
+ export declare function SyntroProvider({ token, canvasOptions, fetcher, children, onReady, onError, }: SyntroProviderProps): import("react/jsx-runtime").JSX.Element;
83
+ /**
84
+ * Hook to access the Syntro SDK.
85
+ *
86
+ * Must be used within a SyntroProvider.
87
+ *
88
+ * @returns The SDK context containing canvas, experiments, telemetry, and status
89
+ */
90
+ export declare function useSyntro(): SyntroContextValue;
91
+ /**
92
+ * Hook to check if the SDK is ready.
93
+ *
94
+ * Shorthand for `useSyntro().isReady`.
95
+ */
96
+ export declare function useSyntroReady(): boolean;
97
+ /**
98
+ * Hook to access the experiment client.
99
+ *
100
+ * @returns The experiment client, or null if not configured
101
+ */
102
+ export declare function useSyntroExperiments(): ExperimentClient | null;
103
+ /**
104
+ * Hook to access the telemetry client.
105
+ *
106
+ * @returns The telemetry client, or null if not configured
107
+ */
108
+ export declare function useSyntroTelemetry(): TelemetryClient | null;
109
+ /**
110
+ * Hook to access the canvas handle.
111
+ *
112
+ * @returns The canvas handle, or null if not ready
113
+ */
114
+ export declare function useSyntroCanvas(): SmartCanvasHandle | null;
package/dist/react.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  Syntro
3
- } from "./chunk-JCDCANR7.js";
4
- import "./chunk-77TNZ66J.js";
5
- import "./chunk-IR6UOR63.js";
6
- import "./chunk-YLLWLUQX.js";
3
+ } from "./chunk-NVV7IWJC.js";
4
+ import "./chunk-2IQ2PTLJ.js";
5
+ import "./chunk-XVRDKBYF.js";
6
+ import "./chunk-GX7BBYX6.js";
7
+ import "./chunk-4HXPGXUC.js";
8
+ import "./chunk-JMHRHAEL.js";
7
9
 
8
10
  // src/react.tsx
9
11
  import { createContext, useContext, useEffect, useRef, useState } from "react";
package/dist/react.js.map CHANGED
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/react.tsx"],
4
4
  "sourcesContent": ["/**\n * React bindings for the Syntro SDK.\n *\n * Usage:\n * ```tsx\n * import { SyntroProvider, useSyntro } from '@syntrologie/runtime-sdk/react';\n *\n * function App() {\n * return (\n * <SyntroProvider token=\"syn_xxx\">\n * <MyApp />\n * </SyntroProvider>\n * );\n * }\n *\n * function MyComponent() {\n * const { canvas, experiments, telemetry, isReady } = useSyntro();\n * // ...\n * }\n * ```\n */\nimport { createContext, type ReactNode, useContext, useEffect, useRef, useState } from 'react';\n\nimport type { SmartCanvasHandle } from './api';\nimport { Syntro, type SyntroInitOptions, type SyntroInitResult } from './bootstrap';\nimport type { ExperimentClient } from './experiments/types';\nimport type { TelemetryClient } from './telemetry/types';\n\nexport interface SyntroContextValue {\n /** The SmartCanvas handle */\n canvas: SmartCanvasHandle | null;\n /** The experiment client */\n experiments: ExperimentClient | null;\n /** The telemetry client */\n telemetry: TelemetryClient | null;\n /** Whether the SDK has finished initializing */\n isReady: boolean;\n /** Any error that occurred during initialization */\n error: Error | null;\n}\n\nconst SyntroContext = createContext<SyntroContextValue | null>(null);\n\n// Global tracking to handle React Strict Mode double-mounting\nlet globalInitPromise: Promise<SyntroInitResult | undefined> | null = null;\nlet globalInitToken: string | null = null;\n\nexport interface SyntroProviderProps {\n /**\n * The Syntro token containing all credentials.\n */\n token: string;\n\n /**\n * Optional canvas configuration overrides.\n */\n canvasOptions?: SyntroInitOptions['canvas'];\n\n /**\n * Custom config fetcher to override the default experiment-based fetcher.\n * Use this for local development or when bypassing the experiment server.\n */\n fetcher?: SyntroInitOptions['fetcher'];\n\n /**\n * Children to render.\n */\n children: ReactNode;\n\n /**\n * Called when initialization completes.\n */\n onReady?: (result: SyntroInitResult) => void;\n\n /**\n * Called when initialization fails.\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Provider component that initializes the Syntro SDK.\n *\n * Place this at the root of your app or at the top of any subtree\n * that needs access to the SDK.\n */\nexport function SyntroProvider({\n token,\n canvasOptions,\n fetcher,\n children,\n onReady,\n onError,\n}: SyntroProviderProps) {\n const [state, setState] = useState<SyntroContextValue>({\n canvas: null,\n experiments: null,\n telemetry: null,\n isReady: false,\n error: null,\n });\n\n const initRef = useRef(false);\n const handleRef = useRef<SmartCanvasHandle | null>(null);\n\n useEffect(() => {\n if (!token) return;\n\n // Reuse existing init if same token (handles React Strict Mode remounts)\n if (globalInitPromise && globalInitToken === token) {\n globalInitPromise.then((result) => {\n if (!result) return;\n handleRef.current = result.canvas;\n setState({\n canvas: result.canvas,\n experiments: result.experiments ?? null,\n telemetry: result.telemetry ?? null,\n isReady: true,\n error: null,\n });\n });\n return;\n }\n\n // Prevent double init\n if (initRef.current) return;\n initRef.current = true;\n\n globalInitToken = token;\n globalInitPromise = Syntro.init({ token, canvas: canvasOptions, fetcher });\n\n globalInitPromise\n .then((result) => {\n if (!result) return;\n handleRef.current = result.canvas;\n setState({\n canvas: result.canvas,\n experiments: result.experiments ?? null,\n telemetry: result.telemetry ?? null,\n isReady: true,\n error: null,\n });\n onReady?.(result);\n })\n .catch((err) => {\n const error = err instanceof Error ? err : new Error(String(err));\n setState((prev) => ({ ...prev, error, isReady: true }));\n onError?.(error);\n // Clear global state on error so retry is possible\n globalInitPromise = null;\n globalInitToken = null;\n });\n\n // Don't destroy on unmount in dev mode - React Strict Mode will remount\n // The canvas persists and will be reused on remount\n }, [token, canvasOptions, fetcher, onError, onReady]);\n\n return <SyntroContext.Provider value={state}>{children}</SyntroContext.Provider>;\n}\n\n/**\n * Hook to access the Syntro SDK.\n *\n * Must be used within a SyntroProvider.\n *\n * @returns The SDK context containing canvas, experiments, telemetry, and status\n */\nexport function useSyntro(): SyntroContextValue {\n const context = useContext(SyntroContext);\n if (!context) {\n throw new Error('useSyntro must be used within a SyntroProvider');\n }\n return context;\n}\n\n/**\n * Hook to check if the SDK is ready.\n *\n * Shorthand for `useSyntro().isReady`.\n */\nexport function useSyntroReady(): boolean {\n return useSyntro().isReady;\n}\n\n/**\n * Hook to access the experiment client.\n *\n * @returns The experiment client, or null if not configured\n */\nexport function useSyntroExperiments(): ExperimentClient | null {\n return useSyntro().experiments;\n}\n\n/**\n * Hook to access the telemetry client.\n *\n * @returns The telemetry client, or null if not configured\n */\nexport function useSyntroTelemetry(): TelemetryClient | null {\n return useSyntro().telemetry;\n}\n\n/**\n * Hook to access the canvas handle.\n *\n * @returns The canvas handle, or null if not ready\n */\nexport function useSyntroCanvas(): SmartCanvasHandle | null {\n return useSyntro().canvas;\n}\n"],
5
- "mappings": ";;;;;;;;AAqBA,SAAS,eAA+B,YAAY,WAAW,QAAQ,gBAAgB;AAwI9E;AApHT,IAAM,gBAAgB,cAAyC,IAAI;AAGnE,IAAI,oBAAkE;AACtE,IAAI,kBAAiC;AAyC9B,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA6B;AAAA,IACrD,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,WAAW;AAAA,IACX,SAAS;AAAA,IACT,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,OAAO,KAAK;AAC5B,QAAM,YAAY,OAAiC,IAAI;AAEvD,YAAU,MAAM;AACd,QAAI,CAAC,MAAO;AAGZ,QAAI,qBAAqB,oBAAoB,OAAO;AAClD,wBAAkB,KAAK,CAAC,WAAW;AA9GzC;AA+GQ,YAAI,CAAC,OAAQ;AACb,kBAAU,UAAU,OAAO;AAC3B,iBAAS;AAAA,UACP,QAAQ,OAAO;AAAA,UACf,cAAa,YAAO,gBAAP,YAAsB;AAAA,UACnC,YAAW,YAAO,cAAP,YAAoB;AAAA,UAC/B,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AAAA,MACH,CAAC;AACD;AAAA,IACF;AAGA,QAAI,QAAQ,QAAS;AACrB,YAAQ,UAAU;AAElB,sBAAkB;AAClB,wBAAoB,OAAO,KAAK,EAAE,OAAO,QAAQ,eAAe,QAAQ,CAAC;AAEzE,sBACG,KAAK,CAAC,WAAW;AApIxB;AAqIQ,UAAI,CAAC,OAAQ;AACb,gBAAU,UAAU,OAAO;AAC3B,eAAS;AAAA,QACP,QAAQ,OAAO;AAAA,QACf,cAAa,YAAO,gBAAP,YAAsB;AAAA,QACnC,YAAW,YAAO,cAAP,YAAoB;AAAA,QAC/B,SAAS;AAAA,QACT,OAAO;AAAA,MACT,CAAC;AACD,yCAAU;AAAA,IACZ,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,eAAS,CAAC,UAAU,EAAE,GAAG,MAAM,OAAO,SAAS,KAAK,EAAE;AACtD,yCAAU;AAEV,0BAAoB;AACpB,wBAAkB;AAAA,IACpB,CAAC;AAAA,EAIL,GAAG,CAAC,OAAO,eAAe,SAAS,SAAS,OAAO,CAAC;AAEpD,SAAO,oBAAC,cAAc,UAAd,EAAuB,OAAO,OAAQ,UAAS;AACzD;AASO,SAAS,YAAgC;AAC9C,QAAM,UAAU,WAAW,aAAa;AACxC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,SAAO;AACT;AAOO,SAAS,iBAA0B;AACxC,SAAO,UAAU,EAAE;AACrB;AAOO,SAAS,uBAAgD;AAC9D,SAAO,UAAU,EAAE;AACrB;AAOO,SAAS,qBAA6C;AAC3D,SAAO,UAAU,EAAE;AACrB;AAOO,SAAS,kBAA4C;AAC1D,SAAO,UAAU,EAAE;AACrB;",
5
+ "mappings": ";;;;;;;;;;AAqBA,SAAS,eAA+B,YAAY,WAAW,QAAQ,gBAAgB;AAwI9E;AApHT,IAAM,gBAAgB,cAAyC,IAAI;AAGnE,IAAI,oBAAkE;AACtE,IAAI,kBAAiC;AAyC9B,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA6B;AAAA,IACrD,QAAQ;AAAA,IACR,aAAa;AAAA,IACb,WAAW;AAAA,IACX,SAAS;AAAA,IACT,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,OAAO,KAAK;AAC5B,QAAM,YAAY,OAAiC,IAAI;AAEvD,YAAU,MAAM;AACd,QAAI,CAAC,MAAO;AAGZ,QAAI,qBAAqB,oBAAoB,OAAO;AAClD,wBAAkB,KAAK,CAAC,WAAW;AA9GzC;AA+GQ,YAAI,CAAC,OAAQ;AACb,kBAAU,UAAU,OAAO;AAC3B,iBAAS;AAAA,UACP,QAAQ,OAAO;AAAA,UACf,cAAa,YAAO,gBAAP,YAAsB;AAAA,UACnC,YAAW,YAAO,cAAP,YAAoB;AAAA,UAC/B,SAAS;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AAAA,MACH,CAAC;AACD;AAAA,IACF;AAGA,QAAI,QAAQ,QAAS;AACrB,YAAQ,UAAU;AAElB,sBAAkB;AAClB,wBAAoB,OAAO,KAAK,EAAE,OAAO,QAAQ,eAAe,QAAQ,CAAC;AAEzE,sBACG,KAAK,CAAC,WAAW;AApIxB;AAqIQ,UAAI,CAAC,OAAQ;AACb,gBAAU,UAAU,OAAO;AAC3B,eAAS;AAAA,QACP,QAAQ,OAAO;AAAA,QACf,cAAa,YAAO,gBAAP,YAAsB;AAAA,QACnC,YAAW,YAAO,cAAP,YAAoB;AAAA,QAC/B,SAAS;AAAA,QACT,OAAO;AAAA,MACT,CAAC;AACD,yCAAU;AAAA,IACZ,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,YAAM,QAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;AAChE,eAAS,CAAC,UAAU,EAAE,GAAG,MAAM,OAAO,SAAS,KAAK,EAAE;AACtD,yCAAU;AAEV,0BAAoB;AACpB,wBAAkB;AAAA,IACpB,CAAC;AAAA,EAIL,GAAG,CAAC,OAAO,eAAe,SAAS,SAAS,OAAO,CAAC;AAEpD,SAAO,oBAAC,cAAc,UAAd,EAAuB,OAAO,OAAQ,UAAS;AACzD;AASO,SAAS,YAAgC;AAC9C,QAAM,UAAU,WAAW,aAAa;AACxC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,SAAO;AACT;AAOO,SAAS,iBAA0B;AACxC,SAAO,UAAU,EAAE;AACrB;AAOO,SAAS,uBAAgD;AAC9D,SAAO,UAAU,EAAE;AACrB;AAOO,SAAS,qBAA6C;AAC3D,SAAO,UAAU,EAAE;AACrB;AAOO,SAAS,kBAA4C;AAC1D,SAAO,UAAU,EAAE;AACrB;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * CDN-hosted Shopify Web Pixel — bundled to `dist/shopify-pixel.min.js` and
3
+ * served from `cdn.syntrologie.com/runtime-sdk/v2/latest/shopify-pixel.min.js`.
4
+ *
5
+ * Merchants install this by pasting a small loader snippet into Shopify admin
6
+ * under Settings → Customer events → Add custom pixel. The loader dynamically
7
+ * loads this file, then calls `window.__SyntroShopifyPixel.register({ analytics, browser })`
8
+ * to wire up subscribers. The indirection means we ship updates to every
9
+ * merchant's pixel by redeploying the CDN file — merchants never re-paste.
10
+ *
11
+ * The pixel's only job is to forward Shopify checkout events to the
12
+ * Syntrologie PostHog proxy with the shopper's distinct_id attached.
13
+ * Experiment attribution is handled server-side by PostHog — once the
14
+ * storefront SDK has fired `$experiment_started` for a distinct_id, PostHog
15
+ * associates all future events from that user (including checkout events
16
+ * this pixel posts) with the correct variant.
17
+ *
18
+ * Shopify custom pixels run in a "lax sandbox" iframe. Script tag injection
19
+ * works (verified by PostHog, Sentry, GTM following the same pattern). Cookie
20
+ * access is via the async `browser.cookie.get()` API which proxies to the
21
+ * top frame (merchant storefront domain), so the `syntro_experiments` cookie
22
+ * written by `ShopifyPixelBridge` on the storefront is readable here.
23
+ *
24
+ * All errors swallowed — a pixel failure must never disturb checkout.
25
+ */
26
+ interface ShopifyAnalytics {
27
+ subscribe(eventName: string, handler: (event: ShopifyPixelEvent) => void | Promise<void>): void;
28
+ }
29
+ interface ShopifyBrowserCookie {
30
+ get(name: string): Promise<string | undefined>;
31
+ }
32
+ interface ShopifyBrowser {
33
+ cookie: ShopifyBrowserCookie;
34
+ }
35
+ interface ShopifyPixelEvent {
36
+ data: {
37
+ checkout?: {
38
+ totalPrice: {
39
+ amount: string | number;
40
+ currencyCode: string;
41
+ };
42
+ order?: {
43
+ id: string;
44
+ };
45
+ };
46
+ cartLine?: {
47
+ merchandise: {
48
+ id: string;
49
+ product: {
50
+ id: string;
51
+ };
52
+ };
53
+ quantity: number;
54
+ };
55
+ };
56
+ }
57
+ export declare function register({ analytics, browser, }: {
58
+ analytics: ShopifyAnalytics;
59
+ browser: ShopifyBrowser;
60
+ }): void;
61
+ declare global {
62
+ interface Window {
63
+ __SyntroShopifyPixel?: {
64
+ register: typeof register;
65
+ };
66
+ }
67
+ }
68
+ export {};