@syntrologie/runtime-sdk 2.15.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.
@@ -12645,7 +12645,7 @@ Please report this to https://github.com/markedjs/marked.`, e9) {
12645
12645
  }
12646
12646
 
12647
12647
  // src/version.ts
12648
- var SDK_VERSION = "2.15.0";
12648
+ var SDK_VERSION = "2.16.0";
12649
12649
 
12650
12650
  // src/types.ts
12651
12651
  var SDK_SCHEMA_VERSION = "2.0";
@@ -22204,12 +22204,8 @@ ${cssRules}
22204
22204
  if (typeof process !== "undefined" && process.env) {
22205
22205
  return process.env[name];
22206
22206
  }
22207
- try {
22208
- const meta = (0, eval)("import.meta");
22209
- if (meta?.env) {
22210
- return meta.env[name];
22211
- }
22212
- } catch {
22207
+ if (false) {
22208
+ return (void 0)[name];
22213
22209
  }
22214
22210
  return void 0;
22215
22211
  }
@@ -25849,6 +25845,250 @@ ${cssRules}
25849
25845
  return new SessionMetricTracker(options);
25850
25846
  }
25851
25847
 
25848
+ // src/platform/ShopifyAntiFlicker.ts
25849
+ var ShopifyAntiFlicker = class {
25850
+ constructor(adapter) {
25851
+ this.unsubUnload = null;
25852
+ this.unsubLoad = null;
25853
+ this.destroyed = false;
25854
+ this.adapter = adapter;
25855
+ }
25856
+ activate() {
25857
+ if (this.destroyed) return;
25858
+ this.unsubUnload = this.adapter.onRegionWillUnload((sectionId) => {
25859
+ this.hideContentInSection(sectionId);
25860
+ });
25861
+ this.unsubLoad = this.adapter.onRegionDidLoad((sectionId) => {
25862
+ this.revealContentInSection(sectionId);
25863
+ });
25864
+ }
25865
+ destroy() {
25866
+ this.destroyed = true;
25867
+ this.unsubUnload?.();
25868
+ this.unsubLoad?.();
25869
+ this.unsubUnload = null;
25870
+ this.unsubLoad = null;
25871
+ }
25872
+ hideContentInSection(sectionId) {
25873
+ const selector = this.sectionSelector(sectionId);
25874
+ const elements2 = document.querySelectorAll(`${selector} [data-syntro-action-id]`);
25875
+ for (const el of elements2) {
25876
+ el.style.opacity = "0";
25877
+ el.style.transition = "none";
25878
+ }
25879
+ }
25880
+ revealContentInSection(sectionId) {
25881
+ const selector = this.sectionSelector(sectionId);
25882
+ requestAnimationFrame(() => {
25883
+ if (this.destroyed) return;
25884
+ const elements2 = document.querySelectorAll(
25885
+ `${selector} [data-syntro-action-id]`
25886
+ );
25887
+ for (const el of elements2) {
25888
+ el.style.transition = "opacity 120ms ease-in";
25889
+ el.style.opacity = "1";
25890
+ }
25891
+ });
25892
+ }
25893
+ sectionSelector(sectionId) {
25894
+ const escaped = typeof CSS !== "undefined" ? CSS.escape(sectionId) : sectionId;
25895
+ const byId = document.getElementById(`shopify-section-${sectionId}`);
25896
+ if (byId) return `#shopify-section-${escaped}`;
25897
+ return `.shopify-section[id$="-${escaped}"]`;
25898
+ }
25899
+ };
25900
+
25901
+ // src/platform/ShopifyAdapter.ts
25902
+ var ShopifyAdapter = class {
25903
+ constructor(options) {
25904
+ this.name = "shopify";
25905
+ this.loadCallbacks = /* @__PURE__ */ new Set();
25906
+ this.unloadCallbacks = /* @__PURE__ */ new Set();
25907
+ this.abortController = null;
25908
+ this.antiFlicker = null;
25909
+ this.initTimeoutMs = options?.initTimeoutMs ?? 3e3;
25910
+ }
25911
+ // --------------------------------------------------------------------------
25912
+ // Detection
25913
+ // --------------------------------------------------------------------------
25914
+ detect() {
25915
+ if (typeof window === "undefined" || typeof document === "undefined") {
25916
+ return false;
25917
+ }
25918
+ if ("Shopify" in window && window.Shopify != null) {
25919
+ return true;
25920
+ }
25921
+ if (document.querySelector('meta[name="shopify-checkout-api-token"]')) {
25922
+ return true;
25923
+ }
25924
+ return false;
25925
+ }
25926
+ // --------------------------------------------------------------------------
25927
+ // Initialization
25928
+ // --------------------------------------------------------------------------
25929
+ /**
25930
+ * Wait for Shopify sections to be ready, then attach lifecycle listeners.
25931
+ *
25932
+ * Resolution order:
25933
+ * 1. `.shopify-section` already in DOM -> resolve immediately
25934
+ * 2. `shopify:section:load` fires -> resolve
25935
+ * 3. Timeout (default 3000ms) -> resolve anyway (graceful degradation)
25936
+ */
25937
+ async onInit() {
25938
+ this.abortController = new AbortController();
25939
+ const { signal } = this.abortController;
25940
+ this.attachSectionListeners(signal);
25941
+ this.antiFlicker = new ShopifyAntiFlicker(this);
25942
+ this.antiFlicker.activate();
25943
+ await this.waitForInitialSections(this.initTimeoutMs, signal);
25944
+ }
25945
+ // --------------------------------------------------------------------------
25946
+ // Region lifecycle subscriptions
25947
+ // --------------------------------------------------------------------------
25948
+ onRegionDidLoad(callback) {
25949
+ this.loadCallbacks.add(callback);
25950
+ return () => {
25951
+ this.loadCallbacks.delete(callback);
25952
+ };
25953
+ }
25954
+ onRegionWillUnload(callback) {
25955
+ this.unloadCallbacks.add(callback);
25956
+ return () => {
25957
+ this.unloadCallbacks.delete(callback);
25958
+ };
25959
+ }
25960
+ onRegionMutated(_regionId) {
25961
+ }
25962
+ // --------------------------------------------------------------------------
25963
+ // Anchor scope
25964
+ // --------------------------------------------------------------------------
25965
+ getAnchorScope(anchor) {
25966
+ const section = anchor.closest(".shopify-section");
25967
+ if (section?.id) {
25968
+ const escaped = typeof CSS !== "undefined" ? CSS.escape(section.id) : section.id;
25969
+ return `#${escaped} `;
25970
+ }
25971
+ return "";
25972
+ }
25973
+ // --------------------------------------------------------------------------
25974
+ // Teardown
25975
+ // --------------------------------------------------------------------------
25976
+ destroy() {
25977
+ this.antiFlicker?.destroy();
25978
+ this.antiFlicker = null;
25979
+ if (this.abortController) {
25980
+ this.abortController.abort();
25981
+ this.abortController = null;
25982
+ }
25983
+ this.loadCallbacks.clear();
25984
+ this.unloadCallbacks.clear();
25985
+ }
25986
+ // --------------------------------------------------------------------------
25987
+ // Private
25988
+ // --------------------------------------------------------------------------
25989
+ attachSectionListeners(signal) {
25990
+ document.addEventListener(
25991
+ "shopify:section:load",
25992
+ (event) => {
25993
+ const sectionId = event.detail?.sectionId;
25994
+ if (sectionId) {
25995
+ for (const cb of this.loadCallbacks) {
25996
+ try {
25997
+ cb(sectionId);
25998
+ } catch {
25999
+ }
26000
+ }
26001
+ }
26002
+ },
26003
+ { signal }
26004
+ );
26005
+ document.addEventListener(
26006
+ "shopify:section:unload",
26007
+ (event) => {
26008
+ const sectionId = event.detail?.sectionId;
26009
+ if (sectionId) {
26010
+ for (const cb of this.unloadCallbacks) {
26011
+ try {
26012
+ cb(sectionId);
26013
+ } catch {
26014
+ }
26015
+ }
26016
+ }
26017
+ },
26018
+ { signal }
26019
+ );
26020
+ }
26021
+ waitForInitialSections(timeoutMs, signal) {
26022
+ return new Promise((resolve) => {
26023
+ if (document.querySelector(".shopify-section")) {
26024
+ resolve();
26025
+ return;
26026
+ }
26027
+ let resolved = false;
26028
+ const done = () => {
26029
+ if (resolved) return;
26030
+ resolved = true;
26031
+ resolve();
26032
+ };
26033
+ document.addEventListener("shopify:section:load", () => done(), { once: true, signal });
26034
+ const timer = setTimeout(done, timeoutMs);
26035
+ signal.addEventListener(
26036
+ "abort",
26037
+ () => {
26038
+ clearTimeout(timer);
26039
+ done();
26040
+ },
26041
+ { once: true }
26042
+ );
26043
+ });
26044
+ }
26045
+ };
26046
+
26047
+ // src/platform/detect.ts
26048
+ var ADAPTERS = [() => new ShopifyAdapter()];
26049
+ function detectPlatform() {
26050
+ for (const create of ADAPTERS) {
26051
+ const adapter = create();
26052
+ if (adapter.detect()) {
26053
+ return adapter;
26054
+ }
26055
+ }
26056
+ return null;
26057
+ }
26058
+
26059
+ // src/platform/shopify-cookie-contract.ts
26060
+ var COOKIE_NAME = "syntro_experiments";
26061
+ var COOKIE_VERSION = 1;
26062
+
26063
+ // src/platform/ShopifyPixelBridge.ts
26064
+ var MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
26065
+ var ShopifyPixelBridge = class {
26066
+ constructor(context) {
26067
+ this.context = { ...context, telemetryHost: context.telemetryHost.replace(/\/$/, "") };
26068
+ this.writeCookie();
26069
+ }
26070
+ /** Update the telemetry context (e.g., after consent is granted and
26071
+ * PostHog finally initializes and a real distinct_id becomes available). */
26072
+ updateContext(context) {
26073
+ this.context = { ...context, telemetryHost: context.telemetryHost.replace(/\/$/, "") };
26074
+ this.writeCookie();
26075
+ }
26076
+ destroy() {
26077
+ document.cookie = `${COOKIE_NAME}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Lax`;
26078
+ }
26079
+ writeCookie() {
26080
+ const payload = {
26081
+ version: COOKIE_VERSION,
26082
+ distinct_id: this.context.distinctId,
26083
+ telemetry_host: this.context.telemetryHost,
26084
+ telemetry_key: this.context.telemetryKey
26085
+ };
26086
+ const value = encodeURIComponent(JSON.stringify(payload));
26087
+ const cookieString = `${COOKIE_NAME}=${value}; max-age=${MAX_AGE_SECONDS}; path=/; SameSite=Lax`;
26088
+ document.cookie = cookieString;
26089
+ }
26090
+ };
26091
+
25852
26092
  // src/context/ContextManager.ts
25853
26093
  function createDefaultContext() {
25854
26094
  const now = Date.now();
@@ -33007,6 +33247,8 @@ ${cssRules}
33007
33247
  true
33008
33248
  );
33009
33249
  }
33250
+ const platformAdapter = detectPlatform();
33251
+ let shopifyPixelBridge;
33010
33252
  const onFeatureFlagsLoaded = (allFlags) => {
33011
33253
  debug("Syntro Bootstrap", "Phase 2: PostHog feature flags loaded");
33012
33254
  const segmentFlags = extractSegmentFlags(allFlags);
@@ -33023,6 +33265,16 @@ ${cssRules}
33023
33265
  debug("Syntro Bootstrap", "Updating GrowthBook with attributes:", updatedAttrs);
33024
33266
  experiments.setAttributes?.(updatedAttrs);
33025
33267
  }
33268
+ if (shopifyPixelBridge && telemetry?.getDistinctId) {
33269
+ const distinctId = telemetry.getDistinctId();
33270
+ if (distinctId && telemetryHost && payload?.t) {
33271
+ shopifyPixelBridge.updateContext({
33272
+ distinctId,
33273
+ telemetryHost,
33274
+ telemetryKey: payload.t
33275
+ });
33276
+ }
33277
+ }
33026
33278
  };
33027
33279
  let telemetry;
33028
33280
  if (payload?.t) {
@@ -33054,6 +33306,18 @@ ${cssRules}
33054
33306
  events.setPosthogCapture((name, props) => {
33055
33307
  telemetryForCapture.track?.(name, props);
33056
33308
  });
33309
+ if (platformAdapter?.name === "shopify" && telemetryHost) {
33310
+ try {
33311
+ shopifyPixelBridge = new ShopifyPixelBridge({
33312
+ distinctId: telemetry.getDistinctId?.() ?? "",
33313
+ telemetryHost,
33314
+ telemetryKey: payload.t
33315
+ });
33316
+ debug("Syntro Bootstrap", "ShopifyPixelBridge initialized for checkout attribution");
33317
+ } catch (err) {
33318
+ warn("Syntro Bootstrap", "ShopifyPixelBridge init failed", err);
33319
+ }
33320
+ }
33057
33321
  }
33058
33322
  let sessionMetrics;
33059
33323
  if (payload?.e) {
@@ -33063,7 +33327,11 @@ ${cssRules}
33063
33327
  // undefined falls back to adapter default
33064
33328
  // Phase 1: Use browser metadata + cached segment attributes for instant evaluation
33065
33329
  attributes: phaseOneAttrs,
33066
- // Wire experiment tracking to telemetry provider
33330
+ // Wire experiment tracking to telemetry provider. PostHog registers
33331
+ // the assignment as a super-property + fires `$experiment_started`;
33332
+ // every future event from this distinct_id (including checkout events
33333
+ // posted by the Shopify Web Pixel) is then auto-attributed to the
33334
+ // variant server-side, so the pixel doesn't need to re-transmit.
33067
33335
  onExperimentViewed: telemetry?.trackExperiment ? (key, variationId, variationName) => {
33068
33336
  telemetry.trackExperiment(key, variationId, variationName);
33069
33337
  } : void 0
@@ -33255,13 +33523,23 @@ ${cssRules}
33255
33523
  }
33256
33524
  return config;
33257
33525
  } : void 0;
33526
+ if (platformAdapter) {
33527
+ debug("Syntro Bootstrap", `Detected platform: ${platformAdapter.name}`);
33528
+ try {
33529
+ await platformAdapter.onInit();
33530
+ debug("Syntro Bootstrap", `Platform adapter initialized: ${platformAdapter.name}`);
33531
+ } catch (err) {
33532
+ warn("Syntro Bootstrap", `Platform adapter init failed: ${platformAdapter.name}`, err);
33533
+ }
33534
+ }
33258
33535
  const canvas = await createSmartCanvas({
33259
33536
  ...options.canvas,
33260
33537
  fetcher: appLoadingFetcher,
33261
33538
  integrations: { experiments, telemetry },
33262
33539
  editorUrl,
33263
- runtime: runtime8
33540
+ runtime: runtime8,
33264
33541
  // Pass runtime so actions can be applied
33542
+ platformAdapter: platformAdapter ?? void 0
33265
33543
  });
33266
33544
  return { canvas, runtime: runtime8, experiments, telemetry, sessionMetrics, appLoader };
33267
33545
  }
@@ -33691,7 +33969,7 @@ ${cssRules}
33691
33969
  }
33692
33970
 
33693
33971
  // src/index-lit.ts
33694
- var RUNTIME_SDK_BUILD = true ? `${"2026-04-23T15:26:01.916Z"} (${"9d88186d214"})` : "dev";
33972
+ var RUNTIME_SDK_BUILD = true ? `${"2026-04-24T22:49:55.034Z"} (${"617ec713a54"})` : "dev";
33695
33973
  if (typeof window !== "undefined") {
33696
33974
  console.log(`[Syntro Runtime] Build: ${RUNTIME_SDK_BUILD} (Lit)`);
33697
33975
  const existing = window.SynOS;