@active-reach/web-sdk 1.15.0 → 1.20.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 (64) hide show
  1. package/README.md +35 -54
  2. package/dist/aegis-sw.js +1 -1
  3. package/dist/aegis.min.js +1 -1
  4. package/dist/aegis.min.js.map +1 -1
  5. package/dist/{analytics-CjLItVo2.mjs → analytics-DblhjFhs.mjs} +66 -3
  6. package/dist/analytics-DblhjFhs.mjs.map +1 -0
  7. package/dist/cdn.d.ts +5 -2
  8. package/dist/cdn.d.ts.map +1 -1
  9. package/dist/chat/AegisChat.d.ts +138 -0
  10. package/dist/chat/AegisChat.d.ts.map +1 -0
  11. package/dist/chat/index.d.ts +3 -0
  12. package/dist/chat/index.d.ts.map +1 -0
  13. package/dist/core/analytics.d.ts +37 -2
  14. package/dist/core/analytics.d.ts.map +1 -1
  15. package/dist/ecommerce/index.d.ts +32 -14
  16. package/dist/ecommerce/index.d.ts.map +1 -1
  17. package/dist/inapp/AegisInAppManager.d.ts +323 -7
  18. package/dist/inapp/AegisInAppManager.d.ts.map +1 -1
  19. package/dist/inapp/devicePairing.d.ts +70 -0
  20. package/dist/inapp/devicePairing.d.ts.map +1 -0
  21. package/dist/inapp/index.d.ts +2 -0
  22. package/dist/inapp/index.d.ts.map +1 -1
  23. package/dist/inapp/renderPreview.d.ts.map +1 -1
  24. package/dist/inapp/renderers/active-web-chat.d.ts +38 -0
  25. package/dist/inapp/renderers/active-web-chat.d.ts.map +1 -0
  26. package/dist/inapp/renderers/carousel-cards.d.ts.map +1 -1
  27. package/dist/inapp/renderers/coachmark-tour.d.ts.map +1 -1
  28. package/dist/inapp/renderers/games.d.ts +27 -0
  29. package/dist/inapp/renderers/games.d.ts.map +1 -0
  30. package/dist/inapp/renderers/index.d.ts +8 -3
  31. package/dist/inapp/renderers/index.d.ts.map +1 -1
  32. package/dist/inapp/renderers/product-recommendation.d.ts +4 -4
  33. package/dist/inapp/renderers/product-recommendation.d.ts.map +1 -1
  34. package/dist/inapp/renderers/progress-bar.d.ts +14 -0
  35. package/dist/inapp/renderers/progress-bar.d.ts.map +1 -1
  36. package/dist/inapp/renderers/sticky-bar.d.ts.map +1 -1
  37. package/dist/inapp/renderers/stories.d.ts +31 -0
  38. package/dist/inapp/renderers/stories.d.ts.map +1 -0
  39. package/dist/inapp/renderers/types.d.ts +73 -2
  40. package/dist/inapp/renderers/types.d.ts.map +1 -1
  41. package/dist/inapp/renderers/video.d.ts +20 -0
  42. package/dist/inapp/renderers/video.d.ts.map +1 -0
  43. package/dist/index.d.ts +5 -3
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +8576 -3169
  46. package/dist/index.js.map +1 -1
  47. package/dist/loyalty/AegisLoyaltyManager.d.ts +120 -0
  48. package/dist/loyalty/AegisLoyaltyManager.d.ts.map +1 -0
  49. package/dist/loyalty/index.d.ts +3 -0
  50. package/dist/loyalty/index.d.ts.map +1 -0
  51. package/dist/push/AegisWebPush.d.ts.map +1 -1
  52. package/dist/push/AegisWebPush.js +13 -2
  53. package/dist/push/AegisWebPush.js.map +1 -1
  54. package/dist/react.js +1 -1
  55. package/dist/runtime/AegisMessageRuntime.d.ts +78 -1
  56. package/dist/runtime/AegisMessageRuntime.d.ts.map +1 -1
  57. package/dist/triggers/ContactScoresFetcher.d.ts +6 -0
  58. package/dist/triggers/ContactScoresFetcher.d.ts.map +1 -1
  59. package/dist/triggers/IntentRuleEvaluator.d.ts +29 -4
  60. package/dist/triggers/IntentRuleEvaluator.d.ts.map +1 -1
  61. package/package.json +7 -6
  62. package/dist/analytics-CjLItVo2.mjs.map +0 -1
  63. package/dist/placements/AegisPlacementManager.d.ts +0 -97
  64. package/dist/placements/AegisPlacementManager.d.ts.map +0 -1
@@ -0,0 +1,120 @@
1
+ /**
2
+ * AegisLoyaltyManager — public SDK surface for the closed-loop store
3
+ * credit + gift cards feature.
4
+ *
5
+ * Phase 7 of the Active Loyalty — Store Credit + Gift Cards rollout.
6
+ * Tracker: docs/architecture/ACTIVE_LOYALTY_STORE_CREDIT_GIFT_CARDS_TRACKER.md
7
+ *
8
+ * Public API (call via `runtime.loyalty.storeCredit.*` / `runtime.loyalty.giftCards.*`):
9
+ *
10
+ * await runtime.loyalty.storeCredit.listActive(phone)
11
+ * await runtime.loyalty.storeCredit.startReload({ phone, workspaceId, amountPaise })
12
+ * await runtime.loyalty.giftCards.startPurchase({ ... })
13
+ * await runtime.loyalty.giftCards.claim({ giftCardId, phone })
14
+ * await runtime.loyalty.giftCards.listReceived(phone)
15
+ *
16
+ * Closed-loop constraints surfaced through the API:
17
+ * - workspaceId is required on every reload / gift card purchase
18
+ * (C1 — single-workspace redemption).
19
+ * - listActive returns ONE row per workspace; clients render one
20
+ * card per workspace, never pooled.
21
+ * - claim() requires the recipient phone to authenticate as the
22
+ * directed recipient (C3 — no bearer codes).
23
+ *
24
+ * Network shape — all calls hit the storefront-facing commerce router
25
+ * via the configured apiHost / same-origin.
26
+ */
27
+ export interface StoreCreditBalance {
28
+ workspaceId: string;
29
+ balancePaise: number;
30
+ expiresAt: string | null;
31
+ lastActivityAt: string | null;
32
+ }
33
+ export interface ReloadCheckoutResult {
34
+ contactId: string;
35
+ checkoutUrl: string;
36
+ provider: string;
37
+ paymentLinkId: string;
38
+ amountPaise: number;
39
+ }
40
+ export interface GiftCardPurchaseResult {
41
+ senderContactId: string;
42
+ giftCardId: string;
43
+ checkoutUrl: string;
44
+ provider: string;
45
+ paymentLinkId: string;
46
+ amountPaise: number;
47
+ }
48
+ export interface GiftCard {
49
+ id: string;
50
+ workspaceId: string;
51
+ senderContactId: string;
52
+ amountPaise: number;
53
+ status: string;
54
+ personalMessage: string | null;
55
+ deliveredAt: string | null;
56
+ claimedAt: string | null;
57
+ expiresAt: string | null;
58
+ createdAt: string | null;
59
+ }
60
+ export interface GiftCardClaimResult {
61
+ recipientContactId: string;
62
+ giftCardId: string;
63
+ amountCreditedPaise: number;
64
+ newBalancePaise: number;
65
+ }
66
+ export interface AegisLoyaltyManagerConfig {
67
+ /** Storefront slug — `{slug}.actii.me/...` */
68
+ slug: string;
69
+ /** Optional explicit API host. When null, uses same-origin. */
70
+ apiHost?: string;
71
+ /** When true, attaches credentials so cookies flow (anon-id, csrf). */
72
+ credentials?: 'include' | 'same-origin' | 'omit';
73
+ /** Optional debug logger. */
74
+ debugMode?: boolean;
75
+ }
76
+ declare class HttpClient {
77
+ private config;
78
+ constructor(config: AegisLoyaltyManagerConfig);
79
+ private url;
80
+ get<T>(path: string): Promise<T>;
81
+ post<T>(path: string, body: Record<string, unknown>): Promise<T>;
82
+ }
83
+ declare class StoreCreditClient {
84
+ private http;
85
+ constructor(http: HttpClient);
86
+ listActive(phone: string): Promise<StoreCreditBalance[]>;
87
+ startReload(opts: {
88
+ phone: string;
89
+ workspaceId: string;
90
+ amountPaise: number;
91
+ firstName?: string;
92
+ email?: string;
93
+ callbackUrl?: string;
94
+ }): Promise<ReloadCheckoutResult>;
95
+ }
96
+ declare class GiftCardsClient {
97
+ private http;
98
+ constructor(http: HttpClient);
99
+ startPurchase(opts: {
100
+ senderPhone: string;
101
+ workspaceId: string;
102
+ recipientPhone: string;
103
+ recipientName?: string;
104
+ amountPaise: number;
105
+ personalMessage?: string;
106
+ callbackUrl?: string;
107
+ }): Promise<GiftCardPurchaseResult>;
108
+ claim(opts: {
109
+ giftCardId: string;
110
+ phone: string;
111
+ }): Promise<GiftCardClaimResult>;
112
+ listReceived(phone: string): Promise<GiftCard[]>;
113
+ }
114
+ export declare class AegisLoyaltyManager {
115
+ readonly storeCredit: StoreCreditClient;
116
+ readonly giftCards: GiftCardsClient;
117
+ constructor(config: AegisLoyaltyManagerConfig);
118
+ }
119
+ export {};
120
+ //# sourceMappingURL=AegisLoyaltyManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AegisLoyaltyManager.d.ts","sourceRoot":"","sources":["../../src/loyalty/AegisLoyaltyManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { AegisLoyaltyManager } from './AegisLoyaltyManager';
2
+ export type { AegisLoyaltyManagerConfig, StoreCreditBalance, ReloadCheckoutResult, GiftCard, GiftCardPurchaseResult, GiftCardClaimResult, } from './AegisLoyaltyManager';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/loyalty/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EACV,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,QAAQ,EACR,sBAAsB,EACtB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"AegisWebPush.d.ts","sourceRoot":"","sources":["../../src/push/AegisWebPush.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AegisWebPush.d.ts","sourceRoot":"","sources":["../../src/push/AegisWebPush.ts"],"names":[],"mappings":"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"}
@@ -319,6 +319,11 @@ class AegisWebPush {
319
319
  case "push.clicked": {
320
320
  this.trackPushEvent("push.clicked", {
321
321
  campaign_id: data.campaign_id,
322
+ // Attribution echoed by the SW → forwarded to the engagement
323
+ // event so it rolls up per-journey / per-template / per-outlet.
324
+ journey_execution_id: data.journey_execution_id,
325
+ lifecycle_fork_id: data.lifecycle_fork_id,
326
+ location_id: data.location_id,
322
327
  action_url: data.action_url,
323
328
  action_type: data.action_type
324
329
  });
@@ -327,7 +332,10 @@ class AegisWebPush {
327
332
  }
328
333
  case "push.dismissed":
329
334
  this.trackPushEvent("push.dismissed", {
330
- campaign_id: data.campaign_id
335
+ campaign_id: data.campaign_id,
336
+ journey_execution_id: data.journey_execution_id,
337
+ lifecycle_fork_id: data.lifecycle_fork_id,
338
+ location_id: data.location_id
331
339
  });
332
340
  this.emit("push-dismissed", {
333
341
  campaign_id: data.campaign_id
@@ -335,7 +343,10 @@ class AegisWebPush {
335
343
  break;
336
344
  case "push.delivered":
337
345
  this.trackPushEvent("push.delivered", {
338
- campaign_id: data.campaign_id
346
+ campaign_id: data.campaign_id,
347
+ journey_execution_id: data.journey_execution_id,
348
+ lifecycle_fork_id: data.lifecycle_fork_id,
349
+ location_id: data.location_id
339
350
  });
340
351
  this.emit("push-shown", {
341
352
  campaign_id: data.campaign_id,
@@ -1 +1 @@
1
- {"version":3,"file":"AegisWebPush.js","sources":["../../src/push/AegisWebPush.ts"],"sourcesContent":["export interface AegisAPIClient {\n post(\n endpoint: string,\n payload: Record<string, unknown>,\n headers?: Record<string, string>\n ): Promise<unknown>;\n}\n\nexport interface ContactIdentity {\n contactId?: string;\n shopifyCustomerId?: string;\n email?: string;\n}\n\nexport interface AegisWebPushConfig {\n vapidPublicKey: string;\n // SubscriptionProperty binding — supplied by the embedding host (e.g.\n // the storefront SSR via the `sdk.property_id` field on /store/{slug}).\n // Required: every subscribe call sends it back as `X-Property-Id` so\n // the backend pins the row to the right origin.\n propertyId: string;\n // Stable per-browser cookie id (e.g. aegis_fpc). Used as the\n // device-fingerprint salt so a fresh subscribe after VAPID rotation\n // upserts onto the same `web_push_subscriptions` row.\n firstPartyCookieId: string;\n autoPrompt?: boolean;\n promptDelay?: number;\n serviceWorkerPath?: string;\n serviceWorkerScope?: string;\n // Engagement-event reporting credentials. When BOTH `writeKey` and\n // `organizationId` are provided AND the caller does NOT pass an\n // explicit `eventTracker` to the constructor, AegisWebPush auto-wires\n // a built-in tracker that POSTs push lifecycle events\n // (push.delivered / push.dismissed / push.clicked) to\n // `/v1/push/engagement` — closing the long-standing gap where shown\n // + dismissed signals (only the device-side SDK can report them)\n // never reached `aegis.delivery_events`. See backend dual-route at\n // `apps/event-ingress/src/services/event-switchboard.ts`\n // (SDK_ENGAGEMENT_EVENT_TO_DELIVERY_MAPPING).\n //\n // Callers using `AegisMessageRuntime` should keep passing the\n // runtime's analytics adapter as `eventTracker` — these fields are\n // only the standalone-init fallback.\n writeKey?: string;\n organizationId?: string;\n}\n\nexport interface PushEventTracker {\n track(eventName: string, properties: Record<string, unknown>): void;\n}\n\n/**\n * Payload for the `push-tap` lifecycle event. CANCELLABLE — return `false`\n * (or call `evt.preventDefault()`) from a handler to suppress the SDK's\n * default `window.location.href` fallback navigation. Use this to dispatch\n * an in-place state change in a SPA host instead of a hard reload.\n *\n * `action_type` + `action_payload` are author-controlled fields baked into\n * the push template's `data` block:\n *\n * ```json\n * {\n * \"title\": \"Your cart is waiting\",\n * \"body\": \"...\",\n * \"action_url\": \"https://shop.actii.me/?step=cart\",\n * \"data\": {\n * \"action_type\": \"open_cart\",\n * \"action_payload\": { \"step\": \"cart\" }\n * }\n * }\n * ```\n */\nexport interface PushTapEvent {\n /** Author-controlled action discriminator (e.g. `open_cart`,\n * `open_payment_link`, `open_order`). When unset, the SDK runs its\n * default fallback navigation to `action_url`. */\n action_type?: string;\n /** Optional structured args the host SPA forwards to its handler. */\n action_payload?: Record<string, unknown>;\n /** Sanitized destination URL — used as the SDK's default fallback nav\n * target when no host handler cancels. */\n action_url: string;\n /** Action-button id when the user clicked a specific button on a\n * multi-button notification; `'default'` for body taps. */\n action: string;\n /** Source campaign id from the push payload — useful for SPA routing\n * (e.g. open_cart from a cart-recovery campaign vs from a generic\n * catch-all template). */\n campaign_id?: string;\n /** Mutate via `evt.preventDefault()` to signal the SDK to skip its\n * default hard-navigation. Identical semantics to DOM CustomEvent. */\n preventDefault: () => void;\n defaultPrevented: boolean;\n}\n\n/** Payload for `push-shown` — fires after `showNotification` resolves on\n * the SW side. The `degraded` flag is true when the SW had to fall back\n * to its minimal `{title, body}` retry path (broken icon URL etc.). */\nexport interface PushShownEvent {\n campaign_id?: string;\n degraded?: boolean;\n}\n\n/** Payload for `push-dismissed` — fires when the user closes the\n * notification without clicking. */\nexport interface PushDismissEvent {\n campaign_id?: string;\n}\n\n/** Payload for `error` — non-fatal SDK errors (subscribe failures,\n * resubscribe persist failures, host-handler throws). Wire into Sentry\n * / Datadog RUM. */\nexport interface PushErrorEvent {\n message: string;\n error: unknown;\n context: Record<string, unknown>;\n}\n\n/** Map of lifecycle event name → handler signature. Mirrors 1.8.0\n * in-app lifecycle for consistency. */\nexport interface WebPushLifecycleEventMap {\n 'push-tap': (evt: PushTapEvent) => void | false;\n 'push-shown': (evt: PushShownEvent) => void;\n 'push-dismissed': (evt: PushDismissEvent) => void;\n 'error': (evt: PushErrorEvent) => void;\n}\n\n/**\n * AegisWebPush — Web Push notification manager.\n *\n * Handles service worker registration, push subscription via VAPID,\n * token registration with the backend, and push event tracking.\n */\nexport class AegisWebPush {\n private apiClient: AegisAPIClient;\n private eventTracker: PushEventTracker | null;\n private swRegistration: ServiceWorkerRegistration | null = null;\n private config: AegisWebPushConfig;\n private initialized = false;\n\n // ── Lifecycle event bus (1.8.5) ────────────────────────────────────────\n // Typed handler registry, parity with AegisInAppManager (1.8.0).\n // Hosts register via the public `on*` methods below; calling the\n // returned unsubscribe fn removes the handler. Multiple subscribers per\n // event are supported. Cancellable events (`push-tap`) suppress the\n // SDK's default behaviour when a handler returns `false` or calls\n // `evt.preventDefault()`.\n //\n // Industry parity: matches CleverTap `notificationCallback`, OneSignal\n // `addEventListener('notificationOpened')`, MoEngage\n // `onSelfHandledClick`, WebEngage `notificationCallback`. The SW posts\n // a `push.clicked` message; this class translates that into a typed\n // PushTapEvent with cancellable default-nav behaviour.\n private hooks = new Map<string, Set<(...args: unknown[]) => void | false | undefined>>();\n\n constructor(\n apiClient: AegisAPIClient,\n config: AegisWebPushConfig,\n eventTracker?: PushEventTracker\n ) {\n this.apiClient = apiClient;\n this.config = {\n serviceWorkerPath: '/aegis-sw.js',\n serviceWorkerScope: '/',\n autoPrompt: false,\n promptDelay: 5000,\n ...config,\n };\n // Explicit eventTracker wins (typical AegisMessageRuntime flow).\n // Otherwise, if both `writeKey` and `organizationId` are configured,\n // wire the built-in tracker that POSTs to `/v1/push/engagement` so\n // standalone push installs don't silently drop lifecycle events.\n if (eventTracker) {\n this.eventTracker = eventTracker;\n } else if (this.config.writeKey && this.config.organizationId) {\n this.eventTracker = this.createBuiltinEventTracker(\n this.config.writeKey,\n this.config.organizationId,\n );\n } else {\n this.eventTracker = null;\n }\n }\n\n /** Built-in fallback that POSTs push lifecycle events to\n * `/v1/push/engagement`. The gateway forwards to event-ingress with\n * the canonical `push.<event>` event_type, which the\n * event-switchboard's SDK_ENGAGEMENT_EVENT_TO_DELIVERY_MAPPING\n * dual-routes onto `aegis.delivery_events` as\n * `channel='push', status='shown'|'dismissed'|...`. Best-effort —\n * failures are warned but never thrown (push SW callbacks shouldn't\n * reject because the analytics POST failed). */\n private createBuiltinEventTracker(\n writeKey: string,\n organizationId: string,\n ): PushEventTracker {\n const apiClient = this.apiClient;\n const propertyId = this.config.propertyId;\n const resolveContactId = () => this.resolveContactId();\n return {\n track(eventName: string, properties: Record<string, unknown>): void {\n const contactId =\n (properties.contact_id as string | undefined) ?? resolveContactId();\n const body = {\n campaign_id: (properties.campaign_id as string | undefined) || '',\n message_id: (properties.message_id as string | undefined) || '',\n event_type: eventName, // e.g. 'push.delivered' — gateway tolerates either form\n user_id: (properties.user_id as string | undefined) || contactId,\n contact_id: contactId,\n anonymous_id: (properties.anonymous_id as string | undefined) || '',\n platform: (properties.platform as string | undefined) || 'web',\n property_id: propertyId,\n metadata: (properties.metadata as Record<string, unknown> | undefined) ?? {},\n };\n const headers = {\n 'X-Aegis-Write-Key': writeKey,\n 'X-Organization-ID': organizationId,\n 'X-Property-Id': propertyId,\n };\n void Promise.resolve(\n apiClient.post('/v1/push/engagement', body, headers),\n ).catch((err) => {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn('[Active Reach WebPush] engagement report failed:', err);\n }\n });\n },\n };\n }\n\n // ── Public lifecycle hook API ──────────────────────────────────────────\n\n /** Generic typed subscribe — useful when the host wants to dispatch\n * multiple events through one wrapper. For single-event subscriptions\n * prefer the `on*` sugar methods below. */\n on<E extends keyof WebPushLifecycleEventMap>(\n event: E,\n handler: WebPushLifecycleEventMap[E],\n ): () => void {\n return this.register(event, handler as never);\n }\n\n /** Subscribe to push notification taps. CANCELLABLE — return `false`\n * (or call `evt.preventDefault()`) to suppress the SDK's default\n * `window.location.href = action_url` fallback. Use this to wire a\n * SPA router into the typed `action_type` payload (e.g. open the\n * cart drawer in-place instead of a full reload). */\n onPushTap(\n handler: (evt: PushTapEvent) => void | false,\n ): () => void {\n return this.register('push-tap', handler);\n }\n\n /** Subscribe to push impressions — fires after the OS notification\n * has actually rendered. `degraded=true` when the SW fell back to\n * the minimal `{title, body}` path (broken icon, etc.). */\n onPushShown(\n handler: (evt: PushShownEvent) => void,\n ): () => void {\n return this.register('push-shown', handler);\n }\n\n /** Subscribe to push dismissals (notification closed without a tap). */\n onPushDismiss(\n handler: (evt: PushDismissEvent) => void,\n ): () => void {\n return this.register('push-dismissed', handler);\n }\n\n /** Subscribe to non-fatal SDK errors. Wire into Sentry / Datadog. */\n onError(\n handler: (evt: PushErrorEvent) => void,\n ): () => void {\n return this.register('error', handler);\n }\n\n private register<T>(\n eventName: string,\n handler: (payload: T) => void | false | undefined,\n ): () => void {\n if (!this.hooks.has(eventName)) this.hooks.set(eventName, new Set());\n this.hooks.get(eventName)!.add(handler as (...args: unknown[]) => void | false | undefined);\n return () => {\n this.hooks.get(eventName)?.delete(handler as (...args: unknown[]) => void | false | undefined);\n };\n }\n\n private emit<T>(eventName: string, payload: T): boolean {\n const handlers = this.hooks.get(eventName);\n if (!handlers || handlers.size === 0) return true;\n let proceed = true;\n for (const handler of handlers) {\n try {\n const result = (handler as (p: T) => void | false | undefined)(payload);\n if (result === false) proceed = false;\n } catch (err) {\n this.emitError(err, { event: eventName });\n }\n }\n return proceed;\n }\n\n private emitError(err: unknown, context?: Record<string, unknown>): void {\n const handlers = this.hooks.get('error');\n if (!handlers || handlers.size === 0) {\n console.warn(\n '[Active Reach WebPush] error:',\n err instanceof Error ? err.message : String(err),\n context ?? {}\n );\n return;\n }\n for (const handler of handlers) {\n try {\n (handler as (e: PushErrorEvent) => void)({\n message: err instanceof Error ? err.message : String(err),\n error: err,\n context: context ?? {},\n });\n } catch {\n // already in error path — don't loop\n }\n }\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n if (!('serviceWorker' in navigator) || !('PushManager' in window)) {\n console.warn('[Active Reach WebPush] Push notifications not supported in this browser');\n return;\n }\n\n try {\n this.swRegistration = await navigator.serviceWorker.register(\n this.config.serviceWorkerPath!,\n { scope: this.config.serviceWorkerScope }\n );\n await navigator.serviceWorker.ready;\n this.initialized = true;\n\n // Listen for messages from the service worker (click/dismiss tracking,\n // and pushsubscriptionchange relay — see handleSWMessage).\n navigator.serviceWorker.addEventListener('message', (event) => {\n this.handleSWMessage(event.data);\n });\n\n // Heal endpoint drift on every page load.\n //\n // Background: when the SW updates with skipWaiting+clients.claim\n // (added in 1.7.3), some browsers — Samsung Internet most\n // aggressively, occasionally Chrome too — silently regenerate the\n // push subscription mid-flight. The SW catches the\n // pushsubscriptionchange event and resubscribes locally, but\n // historically the new endpoint never made it back to the\n // backend. Cell-plane kept FCM-pushing to the dead endpoint;\n // FCM 201s on receipt regardless of whether the token is alive,\n // so the silent failure was invisible until users complained.\n //\n // Fix: whenever permission is already granted at init time,\n // re-run subscribeToPush. The pushManager.subscribe() call is\n // idempotent — it returns the existing subscription if the\n // endpoint is still valid, otherwise mints a new one — and we\n // POST the result to /v1/web_push/subscriptions which UPSERTs\n // on (property_id, device_fingerprint). One extra round-trip\n // per page load; cheap, deterministic, never need to debug\n // stale-endpoint mysteries again.\n if (typeof window !== 'undefined' && Notification.permission === 'granted') {\n this.subscribeToPush().catch((err) => {\n console.warn('[Active Reach WebPush] auto-resync subscribeToPush failed:', err);\n });\n }\n\n if (this.config.autoPrompt) {\n setTimeout(() => this.requestPermission(), this.config.promptDelay);\n }\n } catch (err) {\n console.error('[Active Reach WebPush] Service worker registration failed:', err);\n }\n }\n\n async requestPermission(): Promise<boolean> {\n const permission = await Notification.requestPermission();\n\n if (permission === 'granted') {\n await this.subscribeToPush();\n return true;\n }\n\n this.trackPushEvent('push.permission_denied', {});\n return false;\n }\n\n async identify(params: ContactIdentity): Promise<void> {\n // Persist the resolved contact_id so getStableFingerprint /\n // resolveContactId see it on the next subscribe call. Then re-run\n // the subscribe flow — the backend UPSERT on\n // (property_id, device_fingerprint) updates the existing row with\n // the new contact_id in place.\n try {\n if (params.contactId) {\n window.localStorage.setItem('aegis_contact_id', params.contactId);\n }\n } catch {\n // storage blocked — proceed anyway; subscribe() still works\n }\n\n const subscription = await this.swRegistration?.pushManager.getSubscription();\n if (!subscription) return;\n\n const subscriptionJSON = subscription.toJSON();\n const fingerprint = await this.getStableFingerprint();\n await this.retryApiCall(() =>\n this.apiClient.post(\n '/v1/web_push/subscriptions',\n {\n contact_id: params.contactId ?? this.resolveContactId(),\n endpoint: subscriptionJSON.endpoint!,\n p256dh: subscriptionJSON.keys!.p256dh!,\n auth: subscriptionJSON.keys!.auth!,\n device_fingerprint: fingerprint,\n user_agent: navigator.userAgent,\n },\n { 'X-Property-Id': this.config.propertyId }\n )\n );\n }\n\n async logout(): Promise<void> {\n const subscription = await this.swRegistration?.pushManager.getSubscription();\n if (!subscription) return;\n const subscriptionJSON = subscription.toJSON();\n const fingerprint = await this.getStableFingerprint();\n try {\n await this.apiClient.post(\n '/v1/web_push/unsubscribe',\n {\n contact_id: this.resolveContactId(),\n device_fingerprint: fingerprint,\n endpoint: subscriptionJSON.endpoint!,\n },\n { 'X-Property-Id': this.config.propertyId }\n );\n } finally {\n try {\n window.localStorage.removeItem('aegis_contact_id');\n } catch {\n // ignore\n }\n }\n }\n\n // ---------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------\n\n private async subscribeToPush(): Promise<void> {\n if (!this.swRegistration) return;\n\n const subscription = await this.swRegistration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: this.urlBase64ToUint8Array(this.config.vapidPublicKey),\n });\n\n const subscriptionJSON = subscription.toJSON();\n const fingerprint = await this.getStableFingerprint();\n const contactId = this.resolveContactId();\n\n await this.retryApiCall(() =>\n this.apiClient.post(\n '/v1/web_push/subscriptions',\n {\n contact_id: contactId,\n endpoint: subscriptionJSON.endpoint!,\n p256dh: subscriptionJSON.keys!.p256dh!,\n auth: subscriptionJSON.keys!.auth!,\n device_fingerprint: fingerprint,\n user_agent: navigator.userAgent,\n },\n { 'X-Property-Id': this.config.propertyId }\n )\n );\n\n this.trackPushEvent('push.subscribed', {\n browser: this.getBrowserInfo(),\n property_id: this.config.propertyId,\n });\n }\n\n /**\n * Stable SHA-256 device fingerprint over (first_party_cookie_id + UA stable\n * parts). Used as the UPSERT key on `(property_id, device_fingerprint)` —\n * re-subscribes after VAPID rotation or permission reset update the same\n * row instead of leaving dangling duplicates.\n */\n private async getStableFingerprint(): Promise<string> {\n const cookieId = this.config.firstPartyCookieId;\n const ua = navigator.userAgent || '';\n const platform = navigator.platform || '';\n const language = navigator.language || '';\n const input = `${cookieId}|${ua}|${platform}|${language}`;\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const bytes = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n }\n // Fallback only reachable in environments without Web Crypto —\n // push-capable browsers all have it, so this is defensive.\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n return `fnv-${h.toString(16)}`;\n }\n\n /**\n * Resolve the current contact_id. AegisWebPush doesn't own identity, so\n * we look in a few well-known localStorage keys that the identity layer\n * of the SDK writes. If no known contact is set, we use the first-party\n * cookie id — the backend upgrades the row when identify() is called.\n */\n private resolveContactId(): string {\n try {\n const fromStorage =\n window.localStorage.getItem('aegis_contact_id') ||\n window.localStorage.getItem('aegis_user_id');\n if (fromStorage) return fromStorage;\n } catch {\n // storage blocked — fall through\n }\n return this.config.firstPartyCookieId;\n }\n\n /**\n * Retry an API call with exponential backoff (max 3 retries: 1s, 2s, 4s).\n */\n private async retryApiCall(fn: () => Promise<unknown>, maxRetries = 3): Promise<void> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n await fn();\n return;\n } catch (err) {\n if (attempt === maxRetries) {\n console.error('[Active Reach WebPush] API call failed after retries:', err);\n return;\n }\n const delay = 1000 * Math.pow(2, attempt);\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n }\n\n /**\n * Handle messages forwarded from the service worker (push click, dismiss).\n */\n private handleSWMessage(data: Record<string, unknown>): void {\n if (!data || typeof data.type !== 'string') return;\n\n switch (data.type) {\n case 'push.clicked': {\n this.trackPushEvent('push.clicked', {\n campaign_id: data.campaign_id,\n action_url: data.action_url,\n action_type: data.action_type,\n });\n this.dispatchPushTap(data);\n break;\n }\n case 'push.dismissed':\n this.trackPushEvent('push.dismissed', {\n campaign_id: data.campaign_id,\n });\n this.emit('push-dismissed', {\n campaign_id: data.campaign_id as string | undefined,\n });\n break;\n case 'push.delivered':\n this.trackPushEvent('push.delivered', {\n campaign_id: data.campaign_id,\n });\n this.emit('push-shown', {\n campaign_id: data.campaign_id as string | undefined,\n degraded: data.degraded === true ? true : undefined,\n });\n break;\n case 'push.resubscribed':\n // SW caught a pushsubscriptionchange event and minted a new\n // push subscription. The browser side now has a fresh\n // endpoint; cell-plane still has the old one. Push the\n // updated payload up so the UPSERT keeps the row alive.\n // Without this, FCM pushes go to a dead endpoint with no\n // visible failure (FCM 201s regardless of token validity).\n void this.persistResubscription(\n data.subscription as PushSubscriptionJSON | undefined\n );\n break;\n }\n }\n\n /**\n * Translate a SW `push.clicked` postMessage into a typed PushTapEvent,\n * dispatch through the lifecycle hook bus, and fall back to a hard\n * navigation if no host handler claimed it.\n *\n * The fallback path matters: if a tenant ships a push template with\n * an unknown `action_type` (no SPA handler registered) OR pushes the\n * SDK before the host wires up its `onPushTap` subscriber, we still\n * want the user to land somewhere — `action_url` is always set by the\n * SW's notificationclick handler (it falls back to '/').\n */\n private dispatchPushTap(data: Record<string, unknown>): void {\n if (typeof window === 'undefined') return;\n const actionUrl = (data.action_url as string) || '/';\n let defaultPrevented = false;\n const evt: PushTapEvent = {\n action_type: data.action_type as string | undefined,\n action_payload: data.action_payload as Record<string, unknown> | undefined,\n action_url: actionUrl,\n action: (data.action as string) || 'default',\n campaign_id: data.campaign_id as string | undefined,\n preventDefault: () => {\n defaultPrevented = true;\n evt.defaultPrevented = true;\n },\n defaultPrevented: false,\n };\n const proceed = this.emit('push-tap', evt);\n if (proceed && !defaultPrevented) {\n // No subscriber claimed this tap (or the subscriber returned\n // void / didn't preventDefault). Default to a hard nav so the\n // user always lands somewhere actionable. SPAs that want\n // in-place handling MUST return false from their handler.\n try {\n window.location.href = actionUrl;\n } catch {\n // navigation blocked (sandboxed iframe, etc.) — host can\n // still handle via the hook\n }\n }\n }\n\n private async persistResubscription(\n subscription: PushSubscriptionJSON | undefined\n ): Promise<void> {\n if (!subscription || !subscription.endpoint || !subscription.keys) return;\n try {\n const fingerprint = await this.getStableFingerprint();\n await this.retryApiCall(() =>\n this.apiClient.post(\n '/v1/web_push/subscriptions',\n {\n contact_id: this.resolveContactId(),\n endpoint: subscription.endpoint!,\n p256dh: subscription.keys!.p256dh!,\n auth: subscription.keys!.auth!,\n device_fingerprint: fingerprint,\n user_agent: navigator.userAgent,\n },\n { 'X-Property-Id': this.config.propertyId }\n )\n );\n this.trackPushEvent('push.resubscribed', {});\n } catch (err) {\n console.warn('[Active Reach WebPush] persistResubscription failed:', err);\n }\n }\n\n private trackPushEvent(eventName: string, properties: Record<string, unknown>): void {\n this.eventTracker?.track(eventName, {\n ...properties,\n platform: 'web',\n browser: this.getBrowserInfo(),\n });\n }\n\n private getBrowserInfo(): string {\n const ua = navigator.userAgent;\n if (ua.includes('Edg')) return 'edge';\n if (ua.includes('Chrome')) return 'chrome';\n if (ua.includes('Firefox')) return 'firefox';\n if (ua.includes('Safari')) return 'safari';\n return 'unknown';\n }\n\n private urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');\n const rawData = atob(base64);\n const arr = new Uint8Array(rawData.length);\n for (let i = 0; i < rawData.length; ++i) {\n arr[i] = rawData.charCodeAt(i);\n }\n return arr;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AegisWebPush.js","sources":["../../src/push/AegisWebPush.ts"],"sourcesContent":["export interface AegisAPIClient {\n post(\n endpoint: string,\n payload: Record<string, unknown>,\n headers?: Record<string, string>\n ): Promise<unknown>;\n}\n\nexport interface ContactIdentity {\n contactId?: string;\n shopifyCustomerId?: string;\n email?: string;\n}\n\nexport interface AegisWebPushConfig {\n vapidPublicKey: string;\n // SubscriptionProperty binding — supplied by the embedding host (e.g.\n // the storefront SSR via the `sdk.property_id` field on /store/{slug}).\n // Required: every subscribe call sends it back as `X-Property-Id` so\n // the backend pins the row to the right origin.\n propertyId: string;\n // Stable per-browser cookie id (e.g. aegis_fpc). Used as the\n // device-fingerprint salt so a fresh subscribe after VAPID rotation\n // upserts onto the same `web_push_subscriptions` row.\n firstPartyCookieId: string;\n autoPrompt?: boolean;\n promptDelay?: number;\n serviceWorkerPath?: string;\n serviceWorkerScope?: string;\n // Engagement-event reporting credentials. When BOTH `writeKey` and\n // `organizationId` are provided AND the caller does NOT pass an\n // explicit `eventTracker` to the constructor, AegisWebPush auto-wires\n // a built-in tracker that POSTs push lifecycle events\n // (push.delivered / push.dismissed / push.clicked) to\n // `/v1/push/engagement` — closing the long-standing gap where shown\n // + dismissed signals (only the device-side SDK can report them)\n // never reached `aegis.delivery_events`. See backend dual-route at\n // `apps/event-ingress/src/services/event-switchboard.ts`\n // (SDK_ENGAGEMENT_EVENT_TO_DELIVERY_MAPPING).\n //\n // Callers using `AegisMessageRuntime` should keep passing the\n // runtime's analytics adapter as `eventTracker` — these fields are\n // only the standalone-init fallback.\n writeKey?: string;\n organizationId?: string;\n}\n\nexport interface PushEventTracker {\n track(eventName: string, properties: Record<string, unknown>): void;\n}\n\n/**\n * Payload for the `push-tap` lifecycle event. CANCELLABLE — return `false`\n * (or call `evt.preventDefault()`) from a handler to suppress the SDK's\n * default `window.location.href` fallback navigation. Use this to dispatch\n * an in-place state change in a SPA host instead of a hard reload.\n *\n * `action_type` + `action_payload` are author-controlled fields baked into\n * the push template's `data` block:\n *\n * ```json\n * {\n * \"title\": \"Your cart is waiting\",\n * \"body\": \"...\",\n * \"action_url\": \"https://shop.actii.me/?step=cart\",\n * \"data\": {\n * \"action_type\": \"open_cart\",\n * \"action_payload\": { \"step\": \"cart\" }\n * }\n * }\n * ```\n */\nexport interface PushTapEvent {\n /** Author-controlled action discriminator (e.g. `open_cart`,\n * `open_payment_link`, `open_order`). When unset, the SDK runs its\n * default fallback navigation to `action_url`. */\n action_type?: string;\n /** Optional structured args the host SPA forwards to its handler. */\n action_payload?: Record<string, unknown>;\n /** Sanitized destination URL — used as the SDK's default fallback nav\n * target when no host handler cancels. */\n action_url: string;\n /** Action-button id when the user clicked a specific button on a\n * multi-button notification; `'default'` for body taps. */\n action: string;\n /** Source campaign id from the push payload — useful for SPA routing\n * (e.g. open_cart from a cart-recovery campaign vs from a generic\n * catch-all template). */\n campaign_id?: string;\n /** Mutate via `evt.preventDefault()` to signal the SDK to skip its\n * default hard-navigation. Identical semantics to DOM CustomEvent. */\n preventDefault: () => void;\n defaultPrevented: boolean;\n}\n\n/** Payload for `push-shown` — fires after `showNotification` resolves on\n * the SW side. The `degraded` flag is true when the SW had to fall back\n * to its minimal `{title, body}` retry path (broken icon URL etc.). */\nexport interface PushShownEvent {\n campaign_id?: string;\n degraded?: boolean;\n}\n\n/** Payload for `push-dismissed` — fires when the user closes the\n * notification without clicking. */\nexport interface PushDismissEvent {\n campaign_id?: string;\n}\n\n/** Payload for `error` — non-fatal SDK errors (subscribe failures,\n * resubscribe persist failures, host-handler throws). Wire into Sentry\n * / Datadog RUM. */\nexport interface PushErrorEvent {\n message: string;\n error: unknown;\n context: Record<string, unknown>;\n}\n\n/** Map of lifecycle event name → handler signature. Mirrors 1.8.0\n * in-app lifecycle for consistency. */\nexport interface WebPushLifecycleEventMap {\n 'push-tap': (evt: PushTapEvent) => void | false;\n 'push-shown': (evt: PushShownEvent) => void;\n 'push-dismissed': (evt: PushDismissEvent) => void;\n 'error': (evt: PushErrorEvent) => void;\n}\n\n/**\n * AegisWebPush — Web Push notification manager.\n *\n * Handles service worker registration, push subscription via VAPID,\n * token registration with the backend, and push event tracking.\n */\nexport class AegisWebPush {\n private apiClient: AegisAPIClient;\n private eventTracker: PushEventTracker | null;\n private swRegistration: ServiceWorkerRegistration | null = null;\n private config: AegisWebPushConfig;\n private initialized = false;\n\n // ── Lifecycle event bus (1.8.5) ────────────────────────────────────────\n // Typed handler registry, parity with AegisInAppManager (1.8.0).\n // Hosts register via the public `on*` methods below; calling the\n // returned unsubscribe fn removes the handler. Multiple subscribers per\n // event are supported. Cancellable events (`push-tap`) suppress the\n // SDK's default behaviour when a handler returns `false` or calls\n // `evt.preventDefault()`.\n //\n // Industry parity: matches CleverTap `notificationCallback`, OneSignal\n // `addEventListener('notificationOpened')`, MoEngage\n // `onSelfHandledClick`, WebEngage `notificationCallback`. The SW posts\n // a `push.clicked` message; this class translates that into a typed\n // PushTapEvent with cancellable default-nav behaviour.\n private hooks = new Map<string, Set<(...args: unknown[]) => void | false | undefined>>();\n\n constructor(\n apiClient: AegisAPIClient,\n config: AegisWebPushConfig,\n eventTracker?: PushEventTracker\n ) {\n this.apiClient = apiClient;\n this.config = {\n serviceWorkerPath: '/aegis-sw.js',\n serviceWorkerScope: '/',\n autoPrompt: false,\n promptDelay: 5000,\n ...config,\n };\n // Explicit eventTracker wins (typical AegisMessageRuntime flow).\n // Otherwise, if both `writeKey` and `organizationId` are configured,\n // wire the built-in tracker that POSTs to `/v1/push/engagement` so\n // standalone push installs don't silently drop lifecycle events.\n if (eventTracker) {\n this.eventTracker = eventTracker;\n } else if (this.config.writeKey && this.config.organizationId) {\n this.eventTracker = this.createBuiltinEventTracker(\n this.config.writeKey,\n this.config.organizationId,\n );\n } else {\n this.eventTracker = null;\n }\n }\n\n /** Built-in fallback that POSTs push lifecycle events to\n * `/v1/push/engagement`. The gateway forwards to event-ingress with\n * the canonical `push.<event>` event_type, which the\n * event-switchboard's SDK_ENGAGEMENT_EVENT_TO_DELIVERY_MAPPING\n * dual-routes onto `aegis.delivery_events` as\n * `channel='push', status='shown'|'dismissed'|...`. Best-effort —\n * failures are warned but never thrown (push SW callbacks shouldn't\n * reject because the analytics POST failed). */\n private createBuiltinEventTracker(\n writeKey: string,\n organizationId: string,\n ): PushEventTracker {\n const apiClient = this.apiClient;\n const propertyId = this.config.propertyId;\n const resolveContactId = () => this.resolveContactId();\n return {\n track(eventName: string, properties: Record<string, unknown>): void {\n const contactId =\n (properties.contact_id as string | undefined) ?? resolveContactId();\n const body = {\n campaign_id: (properties.campaign_id as string | undefined) || '',\n message_id: (properties.message_id as string | undefined) || '',\n event_type: eventName, // e.g. 'push.delivered' — gateway tolerates either form\n user_id: (properties.user_id as string | undefined) || contactId,\n contact_id: contactId,\n anonymous_id: (properties.anonymous_id as string | undefined) || '',\n platform: (properties.platform as string | undefined) || 'web',\n property_id: propertyId,\n metadata: (properties.metadata as Record<string, unknown> | undefined) ?? {},\n };\n const headers = {\n 'X-Aegis-Write-Key': writeKey,\n 'X-Organization-ID': organizationId,\n 'X-Property-Id': propertyId,\n };\n void Promise.resolve(\n apiClient.post('/v1/push/engagement', body, headers),\n ).catch((err) => {\n if (typeof console !== 'undefined' && console.warn) {\n console.warn('[Active Reach WebPush] engagement report failed:', err);\n }\n });\n },\n };\n }\n\n // ── Public lifecycle hook API ──────────────────────────────────────────\n\n /** Generic typed subscribe — useful when the host wants to dispatch\n * multiple events through one wrapper. For single-event subscriptions\n * prefer the `on*` sugar methods below. */\n on<E extends keyof WebPushLifecycleEventMap>(\n event: E,\n handler: WebPushLifecycleEventMap[E],\n ): () => void {\n return this.register(event, handler as never);\n }\n\n /** Subscribe to push notification taps. CANCELLABLE — return `false`\n * (or call `evt.preventDefault()`) to suppress the SDK's default\n * `window.location.href = action_url` fallback. Use this to wire a\n * SPA router into the typed `action_type` payload (e.g. open the\n * cart drawer in-place instead of a full reload). */\n onPushTap(\n handler: (evt: PushTapEvent) => void | false,\n ): () => void {\n return this.register('push-tap', handler);\n }\n\n /** Subscribe to push impressions — fires after the OS notification\n * has actually rendered. `degraded=true` when the SW fell back to\n * the minimal `{title, body}` path (broken icon, etc.). */\n onPushShown(\n handler: (evt: PushShownEvent) => void,\n ): () => void {\n return this.register('push-shown', handler);\n }\n\n /** Subscribe to push dismissals (notification closed without a tap). */\n onPushDismiss(\n handler: (evt: PushDismissEvent) => void,\n ): () => void {\n return this.register('push-dismissed', handler);\n }\n\n /** Subscribe to non-fatal SDK errors. Wire into Sentry / Datadog. */\n onError(\n handler: (evt: PushErrorEvent) => void,\n ): () => void {\n return this.register('error', handler);\n }\n\n private register<T>(\n eventName: string,\n handler: (payload: T) => void | false | undefined,\n ): () => void {\n if (!this.hooks.has(eventName)) this.hooks.set(eventName, new Set());\n this.hooks.get(eventName)!.add(handler as (...args: unknown[]) => void | false | undefined);\n return () => {\n this.hooks.get(eventName)?.delete(handler as (...args: unknown[]) => void | false | undefined);\n };\n }\n\n private emit<T>(eventName: string, payload: T): boolean {\n const handlers = this.hooks.get(eventName);\n if (!handlers || handlers.size === 0) return true;\n let proceed = true;\n for (const handler of handlers) {\n try {\n const result = (handler as (p: T) => void | false | undefined)(payload);\n if (result === false) proceed = false;\n } catch (err) {\n this.emitError(err, { event: eventName });\n }\n }\n return proceed;\n }\n\n private emitError(err: unknown, context?: Record<string, unknown>): void {\n const handlers = this.hooks.get('error');\n if (!handlers || handlers.size === 0) {\n console.warn(\n '[Active Reach WebPush] error:',\n err instanceof Error ? err.message : String(err),\n context ?? {}\n );\n return;\n }\n for (const handler of handlers) {\n try {\n (handler as (e: PushErrorEvent) => void)({\n message: err instanceof Error ? err.message : String(err),\n error: err,\n context: context ?? {},\n });\n } catch {\n // already in error path — don't loop\n }\n }\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n if (!('serviceWorker' in navigator) || !('PushManager' in window)) {\n console.warn('[Active Reach WebPush] Push notifications not supported in this browser');\n return;\n }\n\n try {\n this.swRegistration = await navigator.serviceWorker.register(\n this.config.serviceWorkerPath!,\n { scope: this.config.serviceWorkerScope }\n );\n await navigator.serviceWorker.ready;\n this.initialized = true;\n\n // Listen for messages from the service worker (click/dismiss tracking,\n // and pushsubscriptionchange relay — see handleSWMessage).\n navigator.serviceWorker.addEventListener('message', (event) => {\n this.handleSWMessage(event.data);\n });\n\n // Heal endpoint drift on every page load.\n //\n // Background: when the SW updates with skipWaiting+clients.claim\n // (added in 1.7.3), some browsers — Samsung Internet most\n // aggressively, occasionally Chrome too — silently regenerate the\n // push subscription mid-flight. The SW catches the\n // pushsubscriptionchange event and resubscribes locally, but\n // historically the new endpoint never made it back to the\n // backend. Cell-plane kept FCM-pushing to the dead endpoint;\n // FCM 201s on receipt regardless of whether the token is alive,\n // so the silent failure was invisible until users complained.\n //\n // Fix: whenever permission is already granted at init time,\n // re-run subscribeToPush. The pushManager.subscribe() call is\n // idempotent — it returns the existing subscription if the\n // endpoint is still valid, otherwise mints a new one — and we\n // POST the result to /v1/web_push/subscriptions which UPSERTs\n // on (property_id, device_fingerprint). One extra round-trip\n // per page load; cheap, deterministic, never need to debug\n // stale-endpoint mysteries again.\n if (typeof window !== 'undefined' && Notification.permission === 'granted') {\n this.subscribeToPush().catch((err) => {\n console.warn('[Active Reach WebPush] auto-resync subscribeToPush failed:', err);\n });\n }\n\n if (this.config.autoPrompt) {\n setTimeout(() => this.requestPermission(), this.config.promptDelay);\n }\n } catch (err) {\n console.error('[Active Reach WebPush] Service worker registration failed:', err);\n }\n }\n\n async requestPermission(): Promise<boolean> {\n const permission = await Notification.requestPermission();\n\n if (permission === 'granted') {\n await this.subscribeToPush();\n return true;\n }\n\n this.trackPushEvent('push.permission_denied', {});\n return false;\n }\n\n async identify(params: ContactIdentity): Promise<void> {\n // Persist the resolved contact_id so getStableFingerprint /\n // resolveContactId see it on the next subscribe call. Then re-run\n // the subscribe flow — the backend UPSERT on\n // (property_id, device_fingerprint) updates the existing row with\n // the new contact_id in place.\n try {\n if (params.contactId) {\n window.localStorage.setItem('aegis_contact_id', params.contactId);\n }\n } catch {\n // storage blocked — proceed anyway; subscribe() still works\n }\n\n const subscription = await this.swRegistration?.pushManager.getSubscription();\n if (!subscription) return;\n\n const subscriptionJSON = subscription.toJSON();\n const fingerprint = await this.getStableFingerprint();\n await this.retryApiCall(() =>\n this.apiClient.post(\n '/v1/web_push/subscriptions',\n {\n contact_id: params.contactId ?? this.resolveContactId(),\n endpoint: subscriptionJSON.endpoint!,\n p256dh: subscriptionJSON.keys!.p256dh!,\n auth: subscriptionJSON.keys!.auth!,\n device_fingerprint: fingerprint,\n user_agent: navigator.userAgent,\n },\n { 'X-Property-Id': this.config.propertyId }\n )\n );\n }\n\n async logout(): Promise<void> {\n const subscription = await this.swRegistration?.pushManager.getSubscription();\n if (!subscription) return;\n const subscriptionJSON = subscription.toJSON();\n const fingerprint = await this.getStableFingerprint();\n try {\n await this.apiClient.post(\n '/v1/web_push/unsubscribe',\n {\n contact_id: this.resolveContactId(),\n device_fingerprint: fingerprint,\n endpoint: subscriptionJSON.endpoint!,\n },\n { 'X-Property-Id': this.config.propertyId }\n );\n } finally {\n try {\n window.localStorage.removeItem('aegis_contact_id');\n } catch {\n // ignore\n }\n }\n }\n\n // ---------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------\n\n private async subscribeToPush(): Promise<void> {\n if (!this.swRegistration) return;\n\n const subscription = await this.swRegistration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: this.urlBase64ToUint8Array(this.config.vapidPublicKey),\n });\n\n const subscriptionJSON = subscription.toJSON();\n const fingerprint = await this.getStableFingerprint();\n const contactId = this.resolveContactId();\n\n await this.retryApiCall(() =>\n this.apiClient.post(\n '/v1/web_push/subscriptions',\n {\n contact_id: contactId,\n endpoint: subscriptionJSON.endpoint!,\n p256dh: subscriptionJSON.keys!.p256dh!,\n auth: subscriptionJSON.keys!.auth!,\n device_fingerprint: fingerprint,\n user_agent: navigator.userAgent,\n },\n { 'X-Property-Id': this.config.propertyId }\n )\n );\n\n this.trackPushEvent('push.subscribed', {\n browser: this.getBrowserInfo(),\n property_id: this.config.propertyId,\n });\n }\n\n /**\n * Stable SHA-256 device fingerprint over (first_party_cookie_id + UA stable\n * parts). Used as the UPSERT key on `(property_id, device_fingerprint)` —\n * re-subscribes after VAPID rotation or permission reset update the same\n * row instead of leaving dangling duplicates.\n */\n private async getStableFingerprint(): Promise<string> {\n const cookieId = this.config.firstPartyCookieId;\n const ua = navigator.userAgent || '';\n const platform = navigator.platform || '';\n const language = navigator.language || '';\n const input = `${cookieId}|${ua}|${platform}|${language}`;\n if (typeof crypto !== 'undefined' && crypto.subtle) {\n const bytes = new TextEncoder().encode(input);\n const hash = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(hash))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n }\n // Fallback only reachable in environments without Web Crypto —\n // push-capable browsers all have it, so this is defensive.\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = (h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24))) >>> 0;\n }\n return `fnv-${h.toString(16)}`;\n }\n\n /**\n * Resolve the current contact_id. AegisWebPush doesn't own identity, so\n * we look in a few well-known localStorage keys that the identity layer\n * of the SDK writes. If no known contact is set, we use the first-party\n * cookie id — the backend upgrades the row when identify() is called.\n */\n private resolveContactId(): string {\n try {\n const fromStorage =\n window.localStorage.getItem('aegis_contact_id') ||\n window.localStorage.getItem('aegis_user_id');\n if (fromStorage) return fromStorage;\n } catch {\n // storage blocked — fall through\n }\n return this.config.firstPartyCookieId;\n }\n\n /**\n * Retry an API call with exponential backoff (max 3 retries: 1s, 2s, 4s).\n */\n private async retryApiCall(fn: () => Promise<unknown>, maxRetries = 3): Promise<void> {\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n await fn();\n return;\n } catch (err) {\n if (attempt === maxRetries) {\n console.error('[Active Reach WebPush] API call failed after retries:', err);\n return;\n }\n const delay = 1000 * Math.pow(2, attempt);\n await new Promise((r) => setTimeout(r, delay));\n }\n }\n }\n\n /**\n * Handle messages forwarded from the service worker (push click, dismiss).\n */\n private handleSWMessage(data: Record<string, unknown>): void {\n if (!data || typeof data.type !== 'string') return;\n\n switch (data.type) {\n case 'push.clicked': {\n this.trackPushEvent('push.clicked', {\n campaign_id: data.campaign_id,\n // Attribution echoed by the SW → forwarded to the engagement\n // event so it rolls up per-journey / per-template / per-outlet.\n journey_execution_id: data.journey_execution_id,\n lifecycle_fork_id: data.lifecycle_fork_id,\n location_id: data.location_id,\n action_url: data.action_url,\n action_type: data.action_type,\n });\n this.dispatchPushTap(data);\n break;\n }\n case 'push.dismissed':\n this.trackPushEvent('push.dismissed', {\n campaign_id: data.campaign_id,\n journey_execution_id: data.journey_execution_id,\n lifecycle_fork_id: data.lifecycle_fork_id,\n location_id: data.location_id,\n });\n this.emit('push-dismissed', {\n campaign_id: data.campaign_id as string | undefined,\n });\n break;\n case 'push.delivered':\n this.trackPushEvent('push.delivered', {\n campaign_id: data.campaign_id,\n journey_execution_id: data.journey_execution_id,\n lifecycle_fork_id: data.lifecycle_fork_id,\n location_id: data.location_id,\n });\n this.emit('push-shown', {\n campaign_id: data.campaign_id as string | undefined,\n degraded: data.degraded === true ? true : undefined,\n });\n break;\n case 'push.resubscribed':\n // SW caught a pushsubscriptionchange event and minted a new\n // push subscription. The browser side now has a fresh\n // endpoint; cell-plane still has the old one. Push the\n // updated payload up so the UPSERT keeps the row alive.\n // Without this, FCM pushes go to a dead endpoint with no\n // visible failure (FCM 201s regardless of token validity).\n void this.persistResubscription(\n data.subscription as PushSubscriptionJSON | undefined\n );\n break;\n }\n }\n\n /**\n * Translate a SW `push.clicked` postMessage into a typed PushTapEvent,\n * dispatch through the lifecycle hook bus, and fall back to a hard\n * navigation if no host handler claimed it.\n *\n * The fallback path matters: if a tenant ships a push template with\n * an unknown `action_type` (no SPA handler registered) OR pushes the\n * SDK before the host wires up its `onPushTap` subscriber, we still\n * want the user to land somewhere — `action_url` is always set by the\n * SW's notificationclick handler (it falls back to '/').\n */\n private dispatchPushTap(data: Record<string, unknown>): void {\n if (typeof window === 'undefined') return;\n const actionUrl = (data.action_url as string) || '/';\n let defaultPrevented = false;\n const evt: PushTapEvent = {\n action_type: data.action_type as string | undefined,\n action_payload: data.action_payload as Record<string, unknown> | undefined,\n action_url: actionUrl,\n action: (data.action as string) || 'default',\n campaign_id: data.campaign_id as string | undefined,\n preventDefault: () => {\n defaultPrevented = true;\n evt.defaultPrevented = true;\n },\n defaultPrevented: false,\n };\n const proceed = this.emit('push-tap', evt);\n if (proceed && !defaultPrevented) {\n // No subscriber claimed this tap (or the subscriber returned\n // void / didn't preventDefault). Default to a hard nav so the\n // user always lands somewhere actionable. SPAs that want\n // in-place handling MUST return false from their handler.\n try {\n window.location.href = actionUrl;\n } catch {\n // navigation blocked (sandboxed iframe, etc.) — host can\n // still handle via the hook\n }\n }\n }\n\n private async persistResubscription(\n subscription: PushSubscriptionJSON | undefined\n ): Promise<void> {\n if (!subscription || !subscription.endpoint || !subscription.keys) return;\n try {\n const fingerprint = await this.getStableFingerprint();\n await this.retryApiCall(() =>\n this.apiClient.post(\n '/v1/web_push/subscriptions',\n {\n contact_id: this.resolveContactId(),\n endpoint: subscription.endpoint!,\n p256dh: subscription.keys!.p256dh!,\n auth: subscription.keys!.auth!,\n device_fingerprint: fingerprint,\n user_agent: navigator.userAgent,\n },\n { 'X-Property-Id': this.config.propertyId }\n )\n );\n this.trackPushEvent('push.resubscribed', {});\n } catch (err) {\n console.warn('[Active Reach WebPush] persistResubscription failed:', err);\n }\n }\n\n private trackPushEvent(eventName: string, properties: Record<string, unknown>): void {\n this.eventTracker?.track(eventName, {\n ...properties,\n platform: 'web',\n browser: this.getBrowserInfo(),\n });\n }\n\n private getBrowserInfo(): string {\n const ua = navigator.userAgent;\n if (ua.includes('Edg')) return 'edge';\n if (ua.includes('Chrome')) return 'chrome';\n if (ua.includes('Firefox')) return 'firefox';\n if (ua.includes('Safari')) return 'safari';\n return 'unknown';\n }\n\n private urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');\n const rawData = atob(base64);\n const arr = new Uint8Array(rawData.length);\n for (let i = 0; i < rawData.length; ++i) {\n arr[i] = rawData.charCodeAt(i);\n }\n return arr;\n }\n}\n"],"names":[],"mappings":"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"}
package/dist/react.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { createContext, useState, useEffect, useContext } from "react";
2
- import { A as Aegis } from "./analytics-CjLItVo2.mjs";
2
+ import { A as Aegis } from "./analytics-DblhjFhs.mjs";
3
3
  const AegisContext = createContext({
4
4
  aegis: null,
5
5
  isReady: false
@@ -22,10 +22,12 @@
22
22
  * `widgets.renderInteractiveCampaign(campaign)` — no CustomEvent bus,
23
23
  * no listeners, no registration-order bugs.
24
24
  */
25
- import { AegisInAppManager, type AegisInAppConfig, type InAppCampaign } from '../inapp';
25
+ import { AegisInAppManager, type AegisInAppConfig, type InAppCampaign, type CartState } from '../inapp';
26
26
  import { AegisWidgetManager, type AegisWidgetConfig } from '../widgets';
27
27
  import { IntentRuleEvaluator } from '../triggers/IntentRuleEvaluator';
28
28
  import { ContactScoresFetcher } from '../triggers/ContactScoresFetcher';
29
+ import { AegisLoyaltyManager } from '../loyalty';
30
+ import { AegisChat, type AegisChatConfig } from '../chat';
29
31
  export interface AegisMessageRuntimeConfig extends AegisInAppConfig {
30
32
  /** If set, a `TriggerEngine` from the SDK is wired to the WidgetManager
31
33
  * for its exit-intent / scroll-velocity / inactivity handlers. Leave
@@ -38,6 +40,27 @@ export interface AegisMessageRuntimeConfig extends AegisInAppConfig {
38
40
  /** Source platform for gamification attribution (shopify / woocommerce
39
41
  * / magento / mobile_sdk / web). Passed through to WidgetManager. */
40
42
  sourcePlatform?: AegisWidgetConfig['sourcePlatform'];
43
+ /** Storefront slug for the loyalty subsystem — needed when the host
44
+ * app calls `runtime.loyalty.storeCredit.*` / `runtime.loyalty.giftCards.*`.
45
+ * When omitted, the loyalty manager is constructed but every call
46
+ * will throw until configure() is invoked. */
47
+ storefrontSlug?: string;
48
+ /** Mount the customer chat launcher ("Shopping Assistant") — a floating
49
+ * bubble that talks to the channel-neutral chat agent (Track A). Off by
50
+ * default; opt in per host page. */
51
+ enableChat?: boolean;
52
+ /** Presentation overrides for the chat launcher (title / greeting / accent),
53
+ * plus proactive auto-open. writeKey / apiHost / org / contact are inherited
54
+ * from the runtime. */
55
+ chat?: Pick<AegisChatConfig, 'title' | 'greeting' | 'accentColor' | 'channel' | 'icon' | 'logoUrl' | 'position'> & {
56
+ /** Client-side TriggerEngine events that auto-open the launcher (e.g.
57
+ * ['exit_intent', 'inactivity_90']). The trigger ONLY opens the panel —
58
+ * it never creates an offer. The proactive *opener* is owned server-side
59
+ * by the journey→warm-offer-queue bridge (Track B B4); the bot surfaces it
60
+ * on the customer's first turn. This keeps a single offer source — no
61
+ * parallel client-side offer infra. */
62
+ proactiveTriggers?: string[];
63
+ };
41
64
  }
42
65
  /**
43
66
  * One runtime, all in-app types. Consumers should prefer this over
@@ -56,8 +79,21 @@ export declare class AegisMessageRuntime {
56
79
  /** Bridges GET /v1/sdk/contact-scores into the evaluator snapshot.
57
80
  * Triggered automatically on initialize() and updateContactId(). */
58
81
  readonly contactScores: ContactScoresFetcher;
82
+ /** Closed-loop store credit + directed gift cards (Phase 7).
83
+ * Usage:
84
+ * await runtime.loyalty.storeCredit.listActive(phone)
85
+ * await runtime.loyalty.storeCredit.startReload({ phone, workspaceId, amountPaise })
86
+ * await runtime.loyalty.giftCards.startPurchase({ ... })
87
+ * await runtime.loyalty.giftCards.claim({ giftCardId, phone })
88
+ * Tracker: docs/architecture/ACTIVE_LOYALTY_STORE_CREDIT_GIFT_CARDS_TRACKER.md */
89
+ readonly loyalty: AegisLoyaltyManager;
90
+ /** Customer chat launcher ("Shopping Assistant"). Constructed only when
91
+ * `enableChat` is set; undefined otherwise. (Track A) */
92
+ readonly chat?: AegisChat;
59
93
  private contactId?;
60
94
  private initialized;
95
+ /** apiHost captured for the device-pairing entry (`?aegis_pair=CODE`). */
96
+ private readonly pairingApiHost;
61
97
  constructor(config: AegisMessageRuntimeConfig);
62
98
  /**
63
99
  * Boots both managers in parallel. Safe to call multiple times — the
@@ -81,6 +117,47 @@ export declare class AegisMessageRuntime {
81
117
  * channel already.
82
118
  */
83
119
  onClientEvent(eventName: string, eventData?: Record<string, unknown>): void;
120
+ /**
121
+ * Unified reactive context — the GENERAL imperative state API (Plotline-style
122
+ * "global context state engine"). The host app feeds named namespaces:
123
+ * runtime.updateContext('cart', { total: 540, items: 3, currency: 'INR' })
124
+ * runtime.updateContext('user', { tier: 'gold', lifecycle: 'active' })
125
+ * runtime.updateContext('session', { idle_s: 60 })
126
+ *
127
+ * Held in PRIVATE runtime state (no `window` pollution). Merged shallowly per
128
+ * namespace and evaluated entirely client-side — no round-trip. This supersedes
129
+ * the single-purpose `setCartState` (kept as a thin wrapper for back-compat).
130
+ *
131
+ * Design note (vs the proposed rewrite): we DELIBERATELY do NOT introduce a
132
+ * parallel rule schema. Our canonical `IntentRule` (shared with the cell-plane
133
+ * Pydantic schema + the editor + seeded campaigns) stays the source of truth.
134
+ * `updateContext` BRIDGES known paths into the existing enum-keyed
135
+ * `IntentSnapshot` (e.g. cart.total → the `cart_value` signal) so today's rules
136
+ * keep firing — fed by the generic API. Arbitrary dot-path leaves
137
+ * (`cart.foo`) are a forward-compatible follow-up that adds an optional `path`
138
+ * to the canonical `IntentLeaf` (schema-mirrored + drift-guarded), NOT a second
139
+ * evaluator. Tri-state (null = unprovable) evaluation is preserved — we do not
140
+ * regress missing paths to `false`.
141
+ */
142
+ updateContext(namespace: string, data: Record<string, unknown>): void;
143
+ /** Read the current client-side context snapshot (for the editor scrubber /
144
+ * preview bridge to mirror live values). Read-only view of the ONE store. */
145
+ getContext(): Readonly<Record<string, Readonly<Record<string, unknown>>>>;
146
+ /**
147
+ * Bridge well-known context paths into the canonical enum-keyed IntentSnapshot,
148
+ * so existing signal-based rules (cart_value) keep firing off the generic
149
+ * `updateContext`. Reads from the ONE store (the evaluator). Extend here as new
150
+ * signals map to context paths — no parallel engine.
151
+ */
152
+ private bridgeContextToSignals;
153
+ /** Derive the typed cart snapshot for the progress-bar renderer reader from
154
+ * the ONE context store (the `cart` namespace). No separate cart field. */
155
+ private deriveCartState;
156
+ /**
157
+ * Back-compat wrapper over `updateContext('cart', …)`. The single-purpose cart
158
+ * feed for headless storefronts; prefer `updateContext` for new code.
159
+ */
160
+ setCartState(state: CartState): void;
84
161
  /**
85
162
  * Notify the in-app subsystem that a tracked event occurred. Schedules
86
163
  * a debounced refresh of armed campaigns so newly eligible in-app
@@ -1 +1 @@
1
- {"version":3,"file":"AegisMessageRuntime.d.ts","sourceRoot":"","sources":["../../src/runtime/AegisMessageRuntime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AAExE,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE;;kEAE8D;IAC9D,aAAa,CAAC,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAC;IACnD;;sDAEkD;IAClD,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;0EACsE;IACtE,cAAc,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;CACtD;AAED;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAC;IACrC;;;;+CAI2C;IAC3C,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD;yEACqE;IACrE,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;IAC7C,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,yBAAyB;IAoD7C;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBjC;;;;;;;OAOG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD;;;;;;OAMG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI;IAkB/E;;;;;;;OAOG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAK9B;;;;;OAKG;IACH,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAIzB;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK1B;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIxC;;;;;OAKG;IACH,OAAO,IAAI,IAAI;IAMf;;;;;OAKG;IACH,YAAY,IAAI,aAAa,EAAE;CAMhC"}
1
+ {"version":3,"file":"AegisMessageRuntime.d.ts","sourceRoot":"","sources":["../../src/runtime/AegisMessageRuntime.ts"],"names":[],"mappings":"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"}
@@ -48,6 +48,12 @@ export interface ContactScoresResponse {
48
48
  price_sensitivity_score?: number;
49
49
  scoring_tier?: string;
50
50
  scores_updated_at?: string | null;
51
+ /** Governed contact context — namespaced { user: { custom: {…} }, loyalty:
52
+ * { points, tier } } from contacts.attributes + the loyalty record (the SAME
53
+ * source segments + {{custom.x}} merge fields use). Pushed into the rule
54
+ * engine's namespaces so `user.custom.<attr>` / `loyalty.points` path-leaves
55
+ * resolve client-side off one catalog. */
56
+ context?: Record<string, Record<string, unknown>>;
51
57
  }
52
58
  export interface ContactScoresFetcherConfig {
53
59
  apiHost: string;
@@ -1 +1 @@
1
- {"version":3,"file":"ContactScoresFetcher.d.ts","sourceRoot":"","sources":["../../src/triggers/ContactScoresFetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEjF,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC5C,kBAAkB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;wEACoE;IACpE,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC;8BAC0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA4C;IAErE,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAsC;gBAEhD,GAAG,EAAE,0BAA0B;IAU3C;kDAC8C;IAC9C,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAI9D;;;;gCAI4B;IAC5B,WAAW,IAAI,QAAQ,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAIrD;;;;sEAIkE;IAC5D,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAC7B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;CAuDzC;AAED;;;;;;kEAMkE;AAClE,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,qBAAqB,GAAG,cAAc,CAYzE"}
1
+ {"version":3,"file":"ContactScoresFetcher.d.ts","sourceRoot":"","sources":["../../src/triggers/ContactScoresFetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEjF,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC5C,kBAAkB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;;+CAI2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;wEACoE;IACpE,SAAS,CAAC,EAAE,mBAAmB,CAAC;IAChC;8BAC0B;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA4C;IAErE,OAAO,CAAC,oBAAoB,CAAC,CAAS;IACtC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,cAAc,CAAsC;gBAEhD,GAAG,EAAE,0BAA0B;IAU3C;kDAC8C;IAC9C,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAI9D;;;;gCAI4B;IAC5B,WAAW,IAAI,QAAQ,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAIrD;;;;sEAIkE;IAC5D,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO,GAC7B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC;CAkEzC;AAED;;;;;;kEAMkE;AAClE,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,qBAAqB,GAAG,cAAc,CAYzE"}
@@ -30,10 +30,21 @@ export type IntentSignal = 'scroll_depth' | 'time_on_page' | 'exit_intent' | 'ba
30
30
  export type Comparator = 'gte' | 'lte' | 'gt' | 'lt' | 'eq' | 'neq' | 'in' | 'nin';
31
31
  export type IntentLeafValue = number | string | boolean | ReadonlyArray<number | string | boolean>;
32
32
  export interface IntentLeaf {
33
- signal: IntentSignal;
33
+ /** Canonical enum signal (Layer-1/2). Mutually exclusive with `path`. */
34
+ signal?: IntentSignal;
35
+ /** Dot-path into the updateContext namespaces (cart.total / user.tier /
36
+ * session.idle_s). Mutually exclusive with `signal`. */
37
+ path?: string;
34
38
  op: Comparator;
35
39
  value: IntentLeafValue;
36
40
  }
41
+ /** A value/node in the generic reactive context (fed via updateContext). */
42
+ export type ContextValue = string | number | boolean | null | ContextNode | ReadonlyArray<ContextValue>;
43
+ export interface ContextNode {
44
+ [key: string]: ContextValue;
45
+ }
46
+ /** Named namespaces: { cart: {...}, user: {...}, session: {...}, custom: {...} }. */
47
+ export type ContextNamespaces = Record<string, ContextNode>;
37
48
  export interface IntentNode {
38
49
  op: 'AND' | 'OR' | 'NOT';
39
50
  operands: ReadonlyArray<IntentExpr>;
@@ -53,6 +64,10 @@ export type SignalValue = number | string | boolean | null;
53
64
  * same as `null`. */
54
65
  export type IntentSnapshot = Partial<Record<IntentSignal, SignalValue>>;
55
66
  export declare function isIntentLeaf(expr: IntentExpr): expr is IntentLeaf;
67
+ /** Resolve a dot-path (`cart.total`) against the context namespaces.
68
+ * Returns undefined on any miss → tri-state null in evaluateLeaf (same as a
69
+ * missing signal). Never throws. */
70
+ export declare function getValueByPath(namespaces: ContextNamespaces | undefined, path: string): ContextValue | undefined;
56
71
  export declare function isIntentNode(expr: IntentExpr): expr is IntentNode;
57
72
  /**
58
73
  * Evaluate a single leaf comparison against a snapshot.
@@ -71,7 +86,7 @@ export declare function isIntentNode(expr: IntentExpr): expr is IntentNode;
71
86
  * contact's RFM segment is "not in" the forbidden list if we don't
72
87
  * know it yet — conservative).
73
88
  */
74
- export declare function evaluateLeaf(leaf: IntentLeaf, snapshot: IntentSnapshot): boolean | null;
89
+ export declare function evaluateLeaf(leaf: IntentLeaf, snapshot: IntentSnapshot, namespaces?: ContextNamespaces): boolean | null;
75
90
  /**
76
91
  * Evaluate the full predicate tree.
77
92
  *
@@ -93,11 +108,11 @@ export declare function evaluateLeaf(leaf: IntentLeaf, snapshot: IntentSnapshot)
93
108
  * Top-level: a null result from the root expression means the rule
94
109
  * cannot be evaluated yet — do NOT fire.
95
110
  */
96
- export declare function evaluateExpr(expr: IntentExpr, snapshot: IntentSnapshot): boolean | null;
111
+ export declare function evaluateExpr(expr: IntentExpr, snapshot: IntentSnapshot, namespaces?: ContextNamespaces): boolean | null;
97
112
  /** Top-level "does the rule fire right now?" check. Treats a null
98
113
  * evaluation result as "do not fire" (we can't prove the rule's
99
114
  * conditions are met). */
100
- export declare function evaluateRule(rule: IntentRule, snapshot: IntentSnapshot): boolean;
115
+ export declare function evaluateRule(rule: IntentRule, snapshot: IntentSnapshot, namespaces?: ContextNamespaces): boolean;
101
116
  /** A campaign carrying a compound rule, in the SDK's runtime shape.
102
117
  * Mirrors the cell-plane payload at /v1/in-app/active when
103
118
  * client_trigger.type === 'micro_intent'. */
@@ -126,6 +141,9 @@ export declare class IntentRuleEvaluator {
126
141
  * updateSnapshot whenever TriggerEngine emits a state change or
127
142
  * contact-scores fetch completes. */
128
143
  private snapshot;
144
+ /** Generic reactive context namespaces fed via updateContext() — the SINGLE
145
+ * store path-leaves resolve against (no parallel state elsewhere). */
146
+ private namespaces;
129
147
  /** Campaigns that have already fired this session — never re-fire. */
130
148
  private firedThisSession;
131
149
  /** Campaigns silenced by a prior `suppress_competing` win. The set
@@ -136,6 +154,13 @@ export declare class IntentRuleEvaluator {
136
154
  updateSignal(signal: IntentSignal, value: SignalValue): void;
137
155
  updateSnapshot(partial: IntentSnapshot): void;
138
156
  getSnapshot(): Readonly<IntentSnapshot>;
157
+ /** Merge a namespace's data (shallow). Path-leaves (`cart.total`) resolve
158
+ * against this. The runtime's updateContext() delegates here so there is ONE
159
+ * context store, then bridges well-known paths to enum signals. */
160
+ updateContext(namespace: string, data: ContextNode): void;
161
+ getNamespaces(): Readonly<ContextNamespaces>;
162
+ /** Resolve a dot-path against the live context (for previews / debugging). */
163
+ getValueByPath(path: string): ContextValue | undefined;
139
164
  setArmed(campaigns: ArmedCampaign[]): void;
140
165
  getArmed(): ReadonlyArray<ArmedCampaign>;
141
166
  markFired(campaignId: string): void;