@syntrologie/runtime-sdk 2.18.0 → 2.19.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
@@ -9590,6 +9590,10 @@ var HealthReporter = class {
9590
9590
  __publicField(this, "timer", null);
9591
9591
  __publicField(this, "pagehideHandler", null);
9592
9592
  __publicField(this, "runtime", null);
9593
+ /** Cleanup function for adapters that registered side effects (e.g. the
9594
+ * web-vitals long-task observer). Called from `stop()` so SPA re-init
9595
+ * via `initHealthReporter` doesn't accumulate observers. */
9596
+ __publicField(this, "adapterCleanup", null);
9593
9597
  var _a3;
9594
9598
  this.resource = config.resource;
9595
9599
  this.flushIntervalMs = (_a3 = config.flushIntervalMs) != null ? _a3 : 6e4;
@@ -9602,6 +9606,15 @@ var HealthReporter = class {
9602
9606
  bindRuntime(binding) {
9603
9607
  this.runtime = binding;
9604
9608
  }
9609
+ /**
9610
+ * Register a cleanup function to run on `stop()`. Used by adapters
9611
+ * (e.g. webVitalsAdapter) that need to release per-bind resources
9612
+ * like PerformanceObservers. Replaces any prior cleanup — only one
9613
+ * adapter cleanup is tracked.
9614
+ */
9615
+ attachAdapterCleanup(cleanup) {
9616
+ this.adapterCleanup = cleanup;
9617
+ }
9605
9618
  increment(signal, by = 1) {
9606
9619
  var _a3;
9607
9620
  this.counters.set(signal, ((_a3 = this.counters.get(signal)) != null ? _a3 : 0) + by);
@@ -9697,6 +9710,13 @@ var HealthReporter = class {
9697
9710
  window.removeEventListener("pagehide", this.pagehideHandler);
9698
9711
  this.pagehideHandler = null;
9699
9712
  }
9713
+ if (this.adapterCleanup !== null) {
9714
+ try {
9715
+ this.adapterCleanup();
9716
+ } catch {
9717
+ }
9718
+ this.adapterCleanup = null;
9719
+ }
9700
9720
  }
9701
9721
  };
9702
9722
  function percentile(sortedAsc, q2) {
@@ -9801,8 +9821,89 @@ function createOtlpEmitter(cfg) {
9801
9821
  };
9802
9822
  }
9803
9823
 
9824
+ // src/observability/sdkScriptUrl.ts
9825
+ function detect() {
9826
+ var _a3;
9827
+ if (typeof document === "undefined") return void 0;
9828
+ const cs = document.currentScript;
9829
+ if (cs && cs.src) return cs.src;
9830
+ try {
9831
+ const scripts = Array.from(document.scripts);
9832
+ for (let i = scripts.length - 1; i >= 0; i -= 1) {
9833
+ const src = (_a3 = scripts[i]) == null ? void 0 : _a3.src;
9834
+ if (typeof src === "string" && (src.includes("runtime-sdk") || src.includes("smart-canvas"))) {
9835
+ return src;
9836
+ }
9837
+ }
9838
+ } catch {
9839
+ }
9840
+ return void 0;
9841
+ }
9842
+ var SDK_SCRIPT_URL = detect();
9843
+ function getSdkScriptUrlPrefix() {
9844
+ if (!SDK_SCRIPT_URL) return void 0;
9845
+ const lastSlash = SDK_SCRIPT_URL.lastIndexOf("/");
9846
+ if (lastSlash === -1) return void 0;
9847
+ return SDK_SCRIPT_URL.substring(0, lastSlash + 1);
9848
+ }
9849
+
9850
+ // src/observability/webVitalsAdapter.ts
9851
+ import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";
9852
+ var VITALS_BOUND_FLAG = "__syntro_vitals_bound";
9853
+ var VITALS_CURRENT_REPORTER = "__syntro_vitals_current_reporter";
9854
+ function bindWebVitals(reporter, options) {
9855
+ if (typeof window === "undefined") return () => {
9856
+ };
9857
+ const w2 = window;
9858
+ w2[VITALS_CURRENT_REPORTER] = reporter;
9859
+ let longTaskObserver = null;
9860
+ const prefix = options == null ? void 0 : options.attributedScriptUrlPrefix;
9861
+ if (prefix && "PerformanceObserver" in window) {
9862
+ try {
9863
+ longTaskObserver = new PerformanceObserver((list) => {
9864
+ const current = w2[VITALS_CURRENT_REPORTER];
9865
+ if (!current) return;
9866
+ for (const entry of list.getEntries()) {
9867
+ const attribution = entry.attribution;
9868
+ if (!attribution) continue;
9869
+ const matched = attribution.some((a) => {
9870
+ var _a3, _b;
9871
+ const src = (_b = (_a3 = a.containerSrc) != null ? _a3 : a.name) != null ? _b : "";
9872
+ return src.startsWith(prefix);
9873
+ });
9874
+ if (matched) {
9875
+ current.increment("longtasks_self_count");
9876
+ current.increment("longtasks_self_blocking_ms", Math.round(entry.duration));
9877
+ }
9878
+ }
9879
+ });
9880
+ longTaskObserver.observe({ type: "longtask", buffered: true });
9881
+ } catch {
9882
+ longTaskObserver = null;
9883
+ }
9884
+ }
9885
+ if (!w2[VITALS_BOUND_FLAG]) {
9886
+ w2[VITALS_BOUND_FLAG] = true;
9887
+ const recordMs = (signal) => (metric) => {
9888
+ const current = w2[VITALS_CURRENT_REPORTER];
9889
+ current == null ? void 0 : current.recordHistogram(signal, metric.value);
9890
+ };
9891
+ onLCP(recordMs("lcp_ms"));
9892
+ onINP(recordMs("inp_ms"));
9893
+ onFCP(recordMs("fcp_ms"));
9894
+ onTTFB(recordMs("ttfb_ms"));
9895
+ onCLS((metric) => {
9896
+ const current = w2[VITALS_CURRENT_REPORTER];
9897
+ current == null ? void 0 : current.recordHistogram("cls_score", metric.value);
9898
+ });
9899
+ }
9900
+ return () => {
9901
+ longTaskObserver == null ? void 0 : longTaskObserver.disconnect();
9902
+ };
9903
+ }
9904
+
9804
9905
  // src/version.ts
9805
- var SDK_VERSION = "2.18.0";
9906
+ var SDK_VERSION = "2.19.0";
9806
9907
 
9807
9908
  // src/types.ts
9808
9909
  var SDK_SCHEMA_VERSION = "2.0";
@@ -18200,7 +18301,7 @@ function createTelemetryClient(provider, config) {
18200
18301
 
18201
18302
  // src/bootstrap-runtime.ts
18202
18303
  async function _initCore(options) {
18203
- var _a3, _b, _c, _d, _e2, _f, _g, _h, _i, _j, _k, _l, _m;
18304
+ var _a3, _b, _c, _d, _e2, _f, _g, _h, _i, _j, _k, _l, _m, _n;
18204
18305
  initLogger();
18205
18306
  debug("Syntro Bootstrap", "====== INIT ======");
18206
18307
  debug("Syntro Bootstrap", "Options:", {
@@ -18267,7 +18368,7 @@ async function _initCore(options) {
18267
18368
  if (payload != null && payload.obd !== true && telemetryHost) {
18268
18369
  try {
18269
18370
  const otlpEndpoint = `${telemetryHost.replace(/\/$/, "")}/sdk-otel/v1/logs`;
18270
- initHealthReporter({
18371
+ const reporter = initHealthReporter({
18271
18372
  resource: {
18272
18373
  serviceName: "syntro-runtime-sdk",
18273
18374
  serviceVersion: SDK_VERSION,
@@ -18278,14 +18379,17 @@ async function _initCore(options) {
18278
18379
  },
18279
18380
  emit: createOtlpEmitter({ endpoint: otlpEndpoint })
18280
18381
  });
18382
+ const attributedScriptUrlPrefix = (_c = getSdkScriptUrlPrefix()) != null ? _c : "https://cdn.syntrologie.com/runtime-sdk/";
18383
+ const cleanup = bindWebVitals(reporter, { attributedScriptUrlPrefix });
18384
+ reporter.attachAdapterCleanup(cleanup);
18281
18385
  } catch {
18282
18386
  }
18283
18387
  }
18284
- const editorUrl = getEnvVar("NEXT_PUBLIC_SYNTRO_EDITOR_URL") || getEnvVar("VITE_SYNTRO_EDITOR_URL") || ((_c = options.canvas) == null ? void 0 : _c.editorUrl);
18388
+ const editorUrl = getEnvVar("NEXT_PUBLIC_SYNTRO_EDITOR_URL") || getEnvVar("VITE_SYNTRO_EDITOR_URL") || ((_d = options.canvas) == null ? void 0 : _d.editorUrl);
18285
18389
  const geoHost = (payload == null ? void 0 : payload.g) || getEnvVar("NEXT_PUBLIC_SYNTRO_GEO_HOST") || getEnvVar("VITE_SYNTRO_GEO_HOST") || GEO_DEFAULT_HOST;
18286
18390
  const cachedSegmentAttrs = loadCachedSegmentAttributes();
18287
18391
  const browserMetadata = collectBrowserMetadata();
18288
- const appSignalsInit = (_d = options.appSignalsInit) != null ? _d : {};
18392
+ const appSignalsInit = (_e2 = options.appSignalsInit) != null ? _e2 : {};
18289
18393
  const phaseOneAttrs = { ...browserMetadata, ...cachedSegmentAttrs, ...appSignalsInit };
18290
18394
  debug("Syntro Bootstrap", "Phase 1: Browser metadata:", browserMetadata);
18291
18395
  debug("Syntro Bootstrap", "Phase 1: Cached segment attributes:", cachedSegmentAttrs);
@@ -18429,7 +18533,7 @@ async function _initCore(options) {
18429
18533
  });
18430
18534
  console.log(`[Syntro Bootstrap] Telemetry client created (${provider}) with EventBus wiring`);
18431
18535
  if (Object.keys(appSignalsInit).length > 0) {
18432
- (_e2 = telemetry.track) == null ? void 0 : _e2.call(telemetry, "app_signals_init", appSignalsInit);
18536
+ (_f = telemetry.track) == null ? void 0 : _f.call(telemetry, "app_signals_init", appSignalsInit);
18433
18537
  debug("Syntro Bootstrap", "Tracked app signals event:", appSignalsInit);
18434
18538
  }
18435
18539
  const telemetryForCapture = telemetry;
@@ -18487,7 +18591,7 @@ async function _initCore(options) {
18487
18591
  if ((platformAdapter == null ? void 0 : platformAdapter.name) === "shopify" && telemetryHost) {
18488
18592
  try {
18489
18593
  shopifyPixelBridge = new ShopifyPixelBridge({
18490
- distinctId: (_g = (_f = telemetry.getDistinctId) == null ? void 0 : _f.call(telemetry)) != null ? _g : "",
18594
+ distinctId: (_h = (_g = telemetry.getDistinctId) == null ? void 0 : _g.call(telemetry)) != null ? _h : "",
18491
18595
  telemetryHost,
18492
18596
  telemetryKey: payload.t
18493
18597
  });
@@ -18575,7 +18679,7 @@ async function _initCore(options) {
18575
18679
  debug("Syntro Bootstrap", "auto-load of adaptive-mcp failed (non-fatal):", err);
18576
18680
  });
18577
18681
  }
18578
- const registeredApps = (_j = (_i = (_h = runtime5.apps).list) == null ? void 0 : _i.call(_h)) != null ? _j : [];
18682
+ const registeredApps = (_k = (_j = (_i = runtime5.apps).list) == null ? void 0 : _j.call(_i)) != null ? _k : [];
18579
18683
  console.log(
18580
18684
  `[DIAG] Activation loop: ${registeredApps.length} apps in registry:`,
18581
18685
  registeredApps.map((a) => `${a.manifest.id}(${a.state})`).join(", ")
@@ -18606,13 +18710,13 @@ async function _initCore(options) {
18606
18710
  if (experiments && Object.keys(geoData).length > 0) {
18607
18711
  const mergedAttrs = { ...browserMetadata, ...geoData, ...appSignalsInit };
18608
18712
  debug("Syntro Bootstrap", "Merging geo data into GrowthBook attributes:", geoData);
18609
- (_k = experiments.setAttributes) == null ? void 0 : _k.call(experiments, mergedAttrs);
18713
+ (_l = experiments.setAttributes) == null ? void 0 : _l.call(experiments, mergedAttrs);
18610
18714
  }
18611
18715
  const mcpHost = await mcpPromise;
18612
18716
  if (mcpHost && experiments) {
18613
18717
  browserMetadata.surface_type = "mcp-app";
18614
18718
  browserMetadata.surface_host = mcpHost.name;
18615
- (_l = experiments.setAttributes) == null ? void 0 : _l.call(experiments, { ...browserMetadata, ...geoData });
18719
+ (_m = experiments.setAttributes) == null ? void 0 : _m.call(experiments, { ...browserMetadata, ...geoData });
18616
18720
  runtime5.context.setSurfaceType("mcp-app");
18617
18721
  runtime5.context.setSurfaceHost(mcpHost.name);
18618
18722
  debug("Syntro Bootstrap", "MCP App detected:", mcpHost.name);
@@ -18621,7 +18725,7 @@ async function _initCore(options) {
18621
18725
  if (options.fetcher) {
18622
18726
  baseFetcher = options.fetcher;
18623
18727
  } else if (payload == null ? void 0 : payload.f) {
18624
- const configFetcher = createConfigFetcher(payload.f, (_m = payload.o) != null ? _m : {});
18728
+ const configFetcher = createConfigFetcher(payload.f, (_n = payload.o) != null ? _n : {});
18625
18729
  baseFetcher = async () => {
18626
18730
  var _a4;
18627
18731
  const result = await configFetcher.fetch();