@active-reach/web-sdk 1.13.0 → 1.14.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.
@@ -1764,6 +1764,24 @@ class EcommerceTracker {
1764
1764
  variant_id: wishlist.product.variant_id
1765
1765
  });
1766
1766
  }
1767
+ // -- Back-in-stock waitlist --
1768
+ //
1769
+ // Server-side substrate: contact_events row keyed on
1770
+ // (organization_id, contact_id, event_name='product_waitlisted',
1771
+ // event_properties['product_id']). Resolved by
1772
+ // product_event_trigger_service._get_waitlisted_contacts and fanned out via
1773
+ // catalog.back_in_stock journey trigger when stock_event_handler_worker
1774
+ // detects the SKU flipping back into stock.
1775
+ productWaitlisted(waitlist) {
1776
+ this.aegis.track("product_waitlisted", {
1777
+ product_id: waitlist.product.product_id,
1778
+ sku: waitlist.product.sku ?? waitlist.product.product_id,
1779
+ variant_id: waitlist.product.variant_id,
1780
+ name: waitlist.product.name,
1781
+ price: waitlist.product.price,
1782
+ channels: waitlist.channels
1783
+ });
1784
+ }
1767
1785
  // -- Promotions --
1768
1786
  promotionViewed(promo) {
1769
1787
  this.aegis.track("promotion_viewed", { ...promo });
@@ -2362,6 +2380,8 @@ class Aegis {
2362
2380
  this._originalPushState = null;
2363
2381
  this._originalReplaceState = null;
2364
2382
  this._lastEventIds = /* @__PURE__ */ new Map();
2383
+ this._workspaceCodes = /* @__PURE__ */ new Set();
2384
+ this._runtimeWorkspace = null;
2365
2385
  }
2366
2386
  async init(writeKey, config) {
2367
2387
  if (this.initPromise) {
@@ -2543,6 +2563,137 @@ class Aegis {
2543
2563
  removePlugin(name) {
2544
2564
  this.plugins.unregister(name);
2545
2565
  }
2566
+ /**
2567
+ * Plugin-handshake P1 — ingest the workspace_code allowlist from the
2568
+ * bootstrap response. Pass `result.workspaceCodes` from `bootstrap()`
2569
+ * here so the SDK can do path-segment workspace detection on
2570
+ * customer-facing pages like `/south/rewards`, `/ghatkopar/feedback`.
2571
+ *
2572
+ * Empty array is the right answer for single-outlet tenants — SDK
2573
+ * will skip the path cascade and fall through to query param /
2574
+ * setWorkspace() / gateway origin lookup.
2575
+ *
2576
+ * Safe to call before init() — the set is consulted at event-fire time.
2577
+ */
2578
+ ingestWorkspaceCodes(codes) {
2579
+ this._workspaceCodes = new Set(
2580
+ (codes ?? []).filter((c) => typeof c === "string" && c.length > 0)
2581
+ );
2582
+ logger.debug("Workspace codes ingested", { count: this._workspaceCodes.size });
2583
+ }
2584
+ /**
2585
+ * Plugin-handshake P1 (Track E) — explicitly set the workspace for
2586
+ * subsequent events. Used by:
2587
+ * - SPAs where workspace context changes via in-app routing
2588
+ * - Mobile / RN apps that have no URL
2589
+ * - Custom integrations that resolve workspace from their own state
2590
+ *
2591
+ * Pass null to clear (or call clearWorkspace()).
2592
+ *
2593
+ * Persisted to sessionStorage so SPA reloads / page transitions on the
2594
+ * same outlet inherit it without re-calling.
2595
+ */
2596
+ setWorkspace(codeOrId) {
2597
+ this._runtimeWorkspace = codeOrId;
2598
+ if (typeof window !== "undefined" && window.sessionStorage) {
2599
+ try {
2600
+ if (codeOrId) {
2601
+ window.sessionStorage.setItem("aegis_runtime_ws", codeOrId);
2602
+ } else {
2603
+ window.sessionStorage.removeItem("aegis_runtime_ws");
2604
+ }
2605
+ } catch {
2606
+ }
2607
+ }
2608
+ logger.debug("Runtime workspace set", { value: codeOrId });
2609
+ }
2610
+ /** Symmetric helper to clear the runtime workspace. */
2611
+ clearWorkspace() {
2612
+ this.setWorkspace(null);
2613
+ }
2614
+ /**
2615
+ * Inspect the URL's first path segment and return it if it's in the
2616
+ * org's workspace_code allowlist. Skips well-known non-workspace prefixes
2617
+ * (e.g. `b` for bill short codes; `s` is the storefront app slug).
2618
+ *
2619
+ * Returns undefined when:
2620
+ * - window is undefined (SSR / Node)
2621
+ * - path is empty / root
2622
+ * - first segment isn't in `_workspaceCodes` (e.g. /products, /cart)
2623
+ *
2624
+ * The returned value is a workspace CODE (slug like "south"), not a
2625
+ * UUID. The gateway normalizes code → UUID server-side via
2626
+ * workspace_subaccounts lookup with Redis cache.
2627
+ */
2628
+ getPathWorkspaceCode() {
2629
+ if (typeof window === "undefined") return void 0;
2630
+ if (this._workspaceCodes.size === 0) return void 0;
2631
+ try {
2632
+ const segments = window.location.pathname.split("/").filter(Boolean);
2633
+ if (segments.length === 0) return void 0;
2634
+ const first = segments[0].toLowerCase();
2635
+ return this._workspaceCodes.has(first) ? first : void 0;
2636
+ } catch {
2637
+ return void 0;
2638
+ }
2639
+ }
2640
+ /**
2641
+ * Resolve the effective `workspace_id` for the next event.
2642
+ *
2643
+ * Cascade (highest precedence first):
2644
+ * 1. `this.config.workspace_id` — explicit operator config (headless
2645
+ * SDK usage, server-rendered apps, native shells).
2646
+ * 2. `?ws=` query param on the current URL — the P3 storefront URL
2647
+ * contract. The outlet picker writes this via `router.replace`.
2648
+ * 3. URL path segment (P1 Track A) — `/south/rewards` → "south" when
2649
+ * "south" is in the org's workspace_code allowlist.
2650
+ * 4. `aegis.setWorkspace()` runtime override (P1 Track E) — SPAs +
2651
+ * mobile + custom integrations.
2652
+ * 5. sessionStorage `aegis_runtime_ws` — persists setWorkspace across
2653
+ * reloads/SPA route changes.
2654
+ * 6. `undefined` — gateway falls back to `resolveByOrigin` lookup
2655
+ * against the property's allowed_origins.
2656
+ *
2657
+ * The returned value can be EITHER a UUID (from config / ?ws=) or a
2658
+ * workspace CODE slug (from path / setWorkspace). The gateway
2659
+ * normalizes slug → UUID server-side; analytics layer never needs to
2660
+ * worry about the difference.
2661
+ *
2662
+ * See docs/architecture/MULTI_OUTLET_URL_STRATEGY.md §2.3 and
2663
+ * docs/architecture/PLUGIN_HANDSHAKE_AUTOMATION.md §2.
2664
+ */
2665
+ /**
2666
+ * Public so peer SDK surfaces (AegisMessageRuntime / AegisInAppManager /
2667
+ * AegisPlacementManager / AegisWidgetManager) can plumb the same
2668
+ * resolved workspace into their own gateway POSTs. Each manager calls
2669
+ * the gateway on its own endpoint (`/v1/in_app/events`,
2670
+ * `/v1/placements/track`, `/v1/widgets/track-event`); without this,
2671
+ * those events arrive at event-ingress with no workspace_id stamped
2672
+ * → impressions/clicks fall back to the org's primary workspace and
2673
+ * cross-outlet attribution breaks.
2674
+ */
2675
+ getEffectiveWorkspaceId() {
2676
+ var _a;
2677
+ const configured = (_a = this.config) == null ? void 0 : _a.workspace_id;
2678
+ if (configured) return configured;
2679
+ if (typeof window === "undefined") return void 0;
2680
+ try {
2681
+ const ws = new URLSearchParams(window.location.search).get("ws");
2682
+ if (ws && ws.length > 0) return ws;
2683
+ } catch {
2684
+ }
2685
+ const pathWs = this.getPathWorkspaceCode();
2686
+ if (pathWs) return pathWs;
2687
+ if (this._runtimeWorkspace) return this._runtimeWorkspace;
2688
+ try {
2689
+ if (window.sessionStorage) {
2690
+ const cached = window.sessionStorage.getItem("aegis_runtime_ws");
2691
+ if (cached && cached.length > 0) return cached;
2692
+ }
2693
+ } catch {
2694
+ }
2695
+ return void 0;
2696
+ }
2546
2697
  track(eventName, properties) {
2547
2698
  if (!this.assertInitialized()) return;
2548
2699
  const messageId = generateMessageId();
@@ -2555,7 +2706,7 @@ class Aegis {
2555
2706
  anonymousId: this.identity.getAnonymousId(),
2556
2707
  userId: this.identity.getUserId() || void 0,
2557
2708
  sessionId: this.session.getSessionId(),
2558
- workspace_id: this.config.workspace_id || void 0,
2709
+ workspace_id: this.getEffectiveWorkspaceId(),
2559
2710
  context: buildContext(this.config, this.session)
2560
2711
  };
2561
2712
  this._lastEventIds.set(eventName, messageId);
@@ -2563,7 +2714,7 @@ class Aegis {
2563
2714
  }
2564
2715
  identify(userId, traits) {
2565
2716
  if (!this.assertInitialized()) return;
2566
- const wsForGovernor = this.config.workspace_id || null;
2717
+ const wsForGovernor = this.getEffectiveWorkspaceId() || null;
2567
2718
  const { sanitized: governedTraits } = this.traitGovernor.process(traits, wsForGovernor);
2568
2719
  this.identity.setUserId(userId, governedTraits);
2569
2720
  const event = {
@@ -2574,7 +2725,7 @@ class Aegis {
2574
2725
  anonymousId: this.identity.getAnonymousId(),
2575
2726
  userId,
2576
2727
  sessionId: this.session.getSessionId(),
2577
- workspace_id: this.config.workspace_id || void 0,
2728
+ workspace_id: this.getEffectiveWorkspaceId(),
2578
2729
  context: buildContext(this.config, this.session)
2579
2730
  };
2580
2731
  this.captureEvent(event);
@@ -2590,14 +2741,14 @@ class Aegis {
2590
2741
  anonymousId: this.identity.getAnonymousId(),
2591
2742
  userId: this.identity.getUserId() || void 0,
2592
2743
  sessionId: this.session.getSessionId(),
2593
- workspace_id: this.config.workspace_id || void 0,
2744
+ workspace_id: this.getEffectiveWorkspaceId(),
2594
2745
  context: buildContext(this.config, this.session)
2595
2746
  };
2596
2747
  this.captureEvent(event);
2597
2748
  }
2598
2749
  group(groupId, traits) {
2599
2750
  if (!this.assertInitialized()) return;
2600
- const wsForGovernor = this.config.workspace_id || null;
2751
+ const wsForGovernor = this.getEffectiveWorkspaceId() || null;
2601
2752
  const { sanitized: governedTraits } = this.traitGovernor.process(traits, wsForGovernor);
2602
2753
  const event = {
2603
2754
  type: "group",
@@ -2647,7 +2798,7 @@ class Aegis {
2647
2798
  anonymousId: this.identity.getAnonymousId(),
2648
2799
  userId: this.identity.getUserId() || void 0,
2649
2800
  sessionId: this.session.getSessionId(),
2650
- workspace_id: this.config.workspace_id || void 0,
2801
+ workspace_id: this.getEffectiveWorkspaceId(),
2651
2802
  context: buildContext(this.config, this.session)
2652
2803
  };
2653
2804
  this.captureEvent(event);
@@ -2959,4 +3110,4 @@ export {
2959
3110
  logger as l,
2960
3111
  murmurhash3_x86_32 as m
2961
3112
  };
2962
- //# sourceMappingURL=analytics-Mh4H4ekQ.mjs.map
3113
+ //# sourceMappingURL=analytics-DGt-CSgi.mjs.map