@digia-engage/core 1.1.1 → 2.0.0-rc.2

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 (109) hide show
  1. package/README.md +134 -51
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/java/com/digia/engage/rn/DigiaModule.kt +52 -8
  4. package/android/src/main/java/com/digia/engage/rn/DigiaSlotViewManager.kt +6 -2
  5. package/android/src/main/java/com/digia/engage/rn/DigiaViewManager.kt +1 -0
  6. package/ios/DigiaEngageModule.m +7 -1
  7. package/ios/DigiaHostViewManager.swift +20 -20
  8. package/ios/DigiaModule.swift +8 -4
  9. package/lib/commonjs/Digia.js +390 -4
  10. package/lib/commonjs/Digia.js.map +1 -1
  11. package/lib/commonjs/DigiaAnchorView.js +35 -3
  12. package/lib/commonjs/DigiaAnchorView.js.map +1 -1
  13. package/lib/commonjs/DigiaGuideController.js +59 -0
  14. package/lib/commonjs/DigiaGuideController.js.map +1 -0
  15. package/lib/commonjs/DigiaHealthReporter.js +45 -0
  16. package/lib/commonjs/DigiaHealthReporter.js.map +1 -0
  17. package/lib/commonjs/DigiaProvider.js +1081 -0
  18. package/lib/commonjs/DigiaProvider.js.map +1 -0
  19. package/lib/commonjs/DigiaSlotView.js +18 -3
  20. package/lib/commonjs/DigiaSlotView.js.map +1 -1
  21. package/lib/commonjs/NativeDigiaEngage.js +14 -8
  22. package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
  23. package/lib/commonjs/actionHandler.js +316 -0
  24. package/lib/commonjs/actionHandler.js.map +1 -0
  25. package/lib/commonjs/defaultInAppBrowser.js +31 -0
  26. package/lib/commonjs/defaultInAppBrowser.js.map +1 -0
  27. package/lib/commonjs/digiaAnchorRegistry.js +32 -0
  28. package/lib/commonjs/digiaAnchorRegistry.js.map +1 -0
  29. package/lib/commonjs/frequencyEvaluator.js +70 -0
  30. package/lib/commonjs/frequencyEvaluator.js.map +1 -0
  31. package/lib/commonjs/frequencyStore.js +70 -0
  32. package/lib/commonjs/frequencyStore.js.map +1 -0
  33. package/lib/commonjs/index.js +7 -0
  34. package/lib/commonjs/index.js.map +1 -1
  35. package/lib/commonjs/templateTypes.js +2 -0
  36. package/lib/commonjs/templateTypes.js.map +1 -0
  37. package/lib/module/Digia.js +389 -4
  38. package/lib/module/Digia.js.map +1 -1
  39. package/lib/module/DigiaAnchorView.js +33 -1
  40. package/lib/module/DigiaAnchorView.js.map +1 -1
  41. package/lib/module/DigiaGuideController.js +53 -0
  42. package/lib/module/DigiaGuideController.js.map +1 -0
  43. package/lib/module/DigiaHealthReporter.js +38 -0
  44. package/lib/module/DigiaHealthReporter.js.map +1 -0
  45. package/lib/module/DigiaProvider.js +1074 -0
  46. package/lib/module/DigiaProvider.js.map +1 -0
  47. package/lib/module/DigiaSlotView.js +20 -5
  48. package/lib/module/DigiaSlotView.js.map +1 -1
  49. package/lib/module/NativeDigiaEngage.js +14 -8
  50. package/lib/module/NativeDigiaEngage.js.map +1 -1
  51. package/lib/module/actionHandler.js +311 -0
  52. package/lib/module/actionHandler.js.map +1 -0
  53. package/lib/module/defaultInAppBrowser.js +25 -0
  54. package/lib/module/defaultInAppBrowser.js.map +1 -0
  55. package/lib/module/digiaAnchorRegistry.js +26 -0
  56. package/lib/module/digiaAnchorRegistry.js.map +1 -0
  57. package/lib/module/frequencyEvaluator.js +61 -0
  58. package/lib/module/frequencyEvaluator.js.map +1 -0
  59. package/lib/module/frequencyStore.js +64 -0
  60. package/lib/module/frequencyStore.js.map +1 -0
  61. package/lib/module/index.js +1 -0
  62. package/lib/module/index.js.map +1 -1
  63. package/lib/module/templateTypes.js +2 -0
  64. package/lib/module/templateTypes.js.map +1 -0
  65. package/lib/typescript/Digia.d.ts +35 -3
  66. package/lib/typescript/Digia.d.ts.map +1 -1
  67. package/lib/typescript/DigiaAnchorView.d.ts +5 -1
  68. package/lib/typescript/DigiaAnchorView.d.ts.map +1 -1
  69. package/lib/typescript/DigiaGuideController.d.ts +30 -0
  70. package/lib/typescript/DigiaGuideController.d.ts.map +1 -0
  71. package/lib/typescript/DigiaHealthReporter.d.ts +24 -0
  72. package/lib/typescript/DigiaHealthReporter.d.ts.map +1 -0
  73. package/lib/typescript/DigiaProvider.d.ts +3 -0
  74. package/lib/typescript/DigiaProvider.d.ts.map +1 -0
  75. package/lib/typescript/DigiaSlotView.d.ts.map +1 -1
  76. package/lib/typescript/NativeDigiaEngage.d.ts +10 -6
  77. package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
  78. package/lib/typescript/actionHandler.d.ts +20 -0
  79. package/lib/typescript/actionHandler.d.ts.map +1 -0
  80. package/lib/typescript/defaultInAppBrowser.d.ts +3 -0
  81. package/lib/typescript/defaultInAppBrowser.d.ts.map +1 -0
  82. package/lib/typescript/digiaAnchorRegistry.d.ts +15 -0
  83. package/lib/typescript/digiaAnchorRegistry.d.ts.map +1 -0
  84. package/lib/typescript/frequencyEvaluator.d.ts +14 -0
  85. package/lib/typescript/frequencyEvaluator.d.ts.map +1 -0
  86. package/lib/typescript/frequencyStore.d.ts +7 -0
  87. package/lib/typescript/frequencyStore.d.ts.map +1 -0
  88. package/lib/typescript/index.d.ts +1 -0
  89. package/lib/typescript/index.d.ts.map +1 -1
  90. package/lib/typescript/templateTypes.d.ts +140 -0
  91. package/lib/typescript/templateTypes.d.ts.map +1 -0
  92. package/lib/typescript/types.d.ts +163 -4
  93. package/lib/typescript/types.d.ts.map +1 -1
  94. package/package.json +15 -3
  95. package/src/Digia.ts +439 -4
  96. package/src/DigiaAnchorView.tsx +30 -2
  97. package/src/DigiaGuideController.ts +61 -0
  98. package/src/DigiaHealthReporter.ts +43 -0
  99. package/src/DigiaProvider.tsx +778 -0
  100. package/src/DigiaSlotView.tsx +26 -6
  101. package/src/NativeDigiaEngage.ts +28 -13
  102. package/src/actionHandler.ts +311 -0
  103. package/src/defaultInAppBrowser.ts +31 -0
  104. package/src/digiaAnchorRegistry.ts +27 -0
  105. package/src/frequencyEvaluator.ts +57 -0
  106. package/src/frequencyStore.ts +79 -0
  107. package/src/index.ts +1 -0
  108. package/src/templateTypes.ts +121 -0
  109. package/src/types.ts +132 -6
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.isSessionPolicy = exports.hasPolicy = exports.evaluate = void 0;
7
+ const WINDOW_MS = {
8
+ day: 86_400_000,
9
+ week: 7 * 86_400_000,
10
+ month: 30 * 86_400_000
11
+ };
12
+ const isSessionPolicy = policy => policy.max_per_window?.window === 'session';
13
+
14
+ /**
15
+ * Pure eligibility function. No side effects.
16
+ *
17
+ * Semantics for max_per_window { count, window }:
18
+ * - "count" shows are allowed, measured from first_shown_at.
19
+ * - Once the window duration has elapsed since first_shown_at, permanently blocked (reason: 'window').
20
+ * - Once shown_count >= count, permanently blocked (reason: 'max_total').
21
+ * - 'session' window is checked by the caller via in-memory state — same logic applies.
22
+ */
23
+ exports.isSessionPolicy = isSessionPolicy;
24
+ const evaluate = (policy, state, now) => {
25
+ if (!state) return {
26
+ allow: true,
27
+ reason: null
28
+ };
29
+ if (state.stopped_at !== null) {
30
+ return {
31
+ allow: false,
32
+ reason: 'stopped'
33
+ };
34
+ }
35
+ if (policy.max_total !== null && state.shown_count >= policy.max_total) {
36
+ return {
37
+ allow: false,
38
+ reason: 'max_total'
39
+ };
40
+ }
41
+ if (policy.max_per_window !== null) {
42
+ const {
43
+ count,
44
+ window: win
45
+ } = policy.max_per_window;
46
+ const windowMs = WINDOW_MS[win];
47
+ if (windowMs !== undefined && state.first_shown_at !== null) {
48
+ if (now - state.first_shown_at > windowMs) {
49
+ return {
50
+ allow: false,
51
+ reason: 'window'
52
+ };
53
+ }
54
+ }
55
+ if (state.shown_count >= count) {
56
+ return {
57
+ allow: false,
58
+ reason: 'max_total'
59
+ };
60
+ }
61
+ }
62
+ return {
63
+ allow: true,
64
+ reason: null
65
+ };
66
+ };
67
+ exports.evaluate = evaluate;
68
+ const hasPolicy = policy => policy !== null && policy !== undefined && (policy.max_total !== null || policy.max_per_window !== null || policy.stop_on !== null);
69
+ exports.hasPolicy = hasPolicy;
70
+ //# sourceMappingURL=frequencyEvaluator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["WINDOW_MS","day","week","month","isSessionPolicy","policy","max_per_window","window","exports","evaluate","state","now","allow","reason","stopped_at","max_total","shown_count","count","win","windowMs","undefined","first_shown_at","hasPolicy","stop_on"],"sourceRoot":"../../src","sources":["frequencyEvaluator.ts"],"mappings":";;;;;;AAEA,MAAMA,SAAiC,GAAG;EACtCC,GAAG,EAAI,UAAU;EACjBC,IAAI,EAAG,CAAC,GAAG,UAAU;EACrBC,KAAK,EAAE,EAAE,GAAG;AAChB,CAAC;AAEM,MAAMC,eAAe,GAAIC,MAAuB,IACnDA,MAAM,CAACC,cAAc,EAAEC,MAAM,KAAK,SAAS;;AAE/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AARAC,OAAA,CAAAJ,eAAA,GAAAA,eAAA;AASO,MAAMK,QAAQ,GAAGA,CACpBJ,MAAuB,EACvBK,KAA4B,EAC5BC,GAAW,KACW;EACtB,IAAI,CAACD,KAAK,EAAE,OAAO;IAAEE,KAAK,EAAE,IAAI;IAAEC,MAAM,EAAE;EAAK,CAAC;EAEhD,IAAIH,KAAK,CAACI,UAAU,KAAK,IAAI,EAAE;IAC3B,OAAO;MAAEF,KAAK,EAAE,KAAK;MAAEC,MAAM,EAAE;IAAU,CAAC;EAC9C;EAEA,IAAIR,MAAM,CAACU,SAAS,KAAK,IAAI,IAAIL,KAAK,CAACM,WAAW,IAAIX,MAAM,CAACU,SAAS,EAAE;IACpE,OAAO;MAAEH,KAAK,EAAE,KAAK;MAAEC,MAAM,EAAE;IAAY,CAAC;EAChD;EAEA,IAAIR,MAAM,CAACC,cAAc,KAAK,IAAI,EAAE;IAChC,MAAM;MAAEW,KAAK;MAAEV,MAAM,EAAEW;IAAI,CAAC,GAAGb,MAAM,CAACC,cAAc;IACpD,MAAMa,QAAQ,GAAGnB,SAAS,CAACkB,GAAG,CAAC;IAE/B,IAAIC,QAAQ,KAAKC,SAAS,IAAIV,KAAK,CAACW,cAAc,KAAK,IAAI,EAAE;MACzD,IAAIV,GAAG,GAAGD,KAAK,CAACW,cAAc,GAAGF,QAAQ,EAAE;QACvC,OAAO;UAAEP,KAAK,EAAE,KAAK;UAAEC,MAAM,EAAE;QAAS,CAAC;MAC7C;IACJ;IAEA,IAAIH,KAAK,CAACM,WAAW,IAAIC,KAAK,EAAE;MAC5B,OAAO;QAAEL,KAAK,EAAE,KAAK;QAAEC,MAAM,EAAE;MAAY,CAAC;IAChD;EACJ;EAEA,OAAO;IAAED,KAAK,EAAE,IAAI;IAAEC,MAAM,EAAE;EAAK,CAAC;AACxC,CAAC;AAACL,OAAA,CAAAC,QAAA,GAAAA,QAAA;AAEK,MAAMa,SAAS,GAAIjB,MAA0C,IAChEA,MAAM,KAAK,IAAI,IACfA,MAAM,KAAKe,SAAS,KACnBf,MAAM,CAACU,SAAS,KAAK,IAAI,IAAIV,MAAM,CAACC,cAAc,KAAK,IAAI,IAAID,MAAM,CAACkB,OAAO,KAAK,IAAI,CAAC;AAACf,OAAA,CAAAc,SAAA,GAAAA,SAAA","ignoreList":[]}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.frequencyStore = void 0;
7
+ // eslint-disable-next-line @typescript-eslint/naming-convention
8
+ const STORE_META_KEY = 'digia:freq:__meta__';
9
+ let _storage = null;
10
+ const _loadStorage = () => {
11
+ try {
12
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
13
+ const mod = require('@react-native-async-storage/async-storage');
14
+ return mod.default ?? mod;
15
+ } catch {
16
+ console.warn('[Digia] AsyncStorage unavailable — frequency state is in-memory only (resets on app restart)');
17
+ return null;
18
+ }
19
+ };
20
+ const getStorage = () => {
21
+ if (_storage === undefined) {
22
+ _storage = _loadStorage();
23
+ }
24
+ return _storage;
25
+ };
26
+ const _sessionStore = new Map();
27
+ const storeKey = campaignKey => `digia:freq:${campaignKey}`;
28
+ const frequencyStore = exports.frequencyStore = {
29
+ async checkProjectId(projectId) {
30
+ const storage = getStorage();
31
+ if (!storage) return;
32
+ try {
33
+ const stored = await storage.getItem(STORE_META_KEY);
34
+ const meta = stored ? JSON.parse(stored) : null;
35
+ if (meta && meta.projectId !== projectId) {
36
+ const keys = await storage.getAllKeys();
37
+ const digiaKeys = keys.filter(k => k.startsWith('digia:freq:'));
38
+ if (digiaKeys.length > 0) await storage.multiRemove([...digiaKeys]);
39
+ }
40
+ await storage.setItem(STORE_META_KEY, JSON.stringify({
41
+ projectId
42
+ }));
43
+ } catch {
44
+ // non-fatal
45
+ }
46
+ },
47
+ async get(campaignKey, isSession) {
48
+ if (isSession) return _sessionStore.get(campaignKey) ?? null;
49
+ const storage = getStorage();
50
+ if (!storage) return _sessionStore.get(campaignKey) ?? null;
51
+ try {
52
+ const raw = await storage.getItem(storeKey(campaignKey));
53
+ return raw ? JSON.parse(raw) : null;
54
+ } catch {
55
+ return null;
56
+ }
57
+ },
58
+ async set(campaignKey, state, isSession) {
59
+ _sessionStore.set(campaignKey, state);
60
+ if (isSession) return;
61
+ const storage = getStorage();
62
+ if (!storage) return;
63
+ try {
64
+ await storage.setItem(storeKey(campaignKey), JSON.stringify(state));
65
+ } catch {
66
+ // non-fatal: state already updated in-memory above
67
+ }
68
+ }
69
+ };
70
+ //# sourceMappingURL=frequencyStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["STORE_META_KEY","_storage","_loadStorage","mod","require","default","console","warn","getStorage","undefined","_sessionStore","Map","storeKey","campaignKey","frequencyStore","exports","checkProjectId","projectId","storage","stored","getItem","meta","JSON","parse","keys","getAllKeys","digiaKeys","filter","k","startsWith","length","multiRemove","setItem","stringify","get","isSession","raw","set","state"],"sourceRoot":"../../src","sources":["frequencyStore.ts"],"mappings":"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","ignoreList":[]}
@@ -15,6 +15,12 @@ Object.defineProperty(exports, "DigiaAnchorView", {
15
15
  return _DigiaAnchorView.DigiaAnchorView;
16
16
  }
17
17
  });
18
+ Object.defineProperty(exports, "DigiaHost", {
19
+ enumerable: true,
20
+ get: function () {
21
+ return _DigiaProvider.DigiaHost;
22
+ }
23
+ });
18
24
  Object.defineProperty(exports, "DigiaHostView", {
19
25
  enumerable: true,
20
26
  get: function () {
@@ -29,6 +35,7 @@ Object.defineProperty(exports, "DigiaSlotView", {
29
35
  });
30
36
  var _Digia = require("./Digia");
31
37
  var _DigiaHostView = require("./DigiaHostView");
38
+ var _DigiaProvider = require("./DigiaProvider");
32
39
  var _DigiaSlotView = require("./DigiaSlotView");
33
40
  var _DigiaAnchorView = require("./DigiaAnchorView");
34
41
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["_Digia","require","_DigiaHostView","_DigiaSlotView","_DigiaAnchorView"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAD,OAAA;AACA,IAAAE,cAAA,GAAAF,OAAA;AACA,IAAAG,gBAAA,GAAAH,OAAA","ignoreList":[]}
1
+ {"version":3,"names":["_Digia","require","_DigiaHostView","_DigiaProvider","_DigiaSlotView","_DigiaAnchorView"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,cAAA,GAAAD,OAAA;AACA,IAAAE,cAAA,GAAAF,OAAA;AACA,IAAAG,cAAA,GAAAH,OAAA;AACA,IAAAI,gBAAA,GAAAJ,OAAA","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=templateTypes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":[],"sourceRoot":"../../src","sources":["templateTypes.ts"],"mappings":"","ignoreList":[]}
@@ -7,7 +7,7 @@
7
7
  * import { Digia } from '@digia/engage-react-native';
8
8
  *
9
9
  * // In your App entry point (e.g. App.tsx):
10
- * await Digia.initialize({ apiKey: 'YOUR_API_KEY' });
10
+ * await Digia.initialize({ projectId: 'YOUR_PROJECT_ID' });
11
11
  *
12
12
  * // Whenever your navigation screen changes:
13
13
  * Digia.setCurrentScreen('Home');
@@ -17,6 +17,15 @@
17
17
 
18
18
  import { DeviceEventEmitter } from 'react-native';
19
19
  import { nativeDigiaModule } from './NativeDigiaEngage';
20
+ import { digiaHealthReporter, HealthEventType } from './DigiaHealthReporter';
21
+ import { digiaGuideController } from './DigiaGuideController';
22
+ import { digiaActionHandler } from './actionHandler';
23
+ import uuid from 'react-native-uuid';
24
+ import { frequencyStore } from './frequencyStore';
25
+ import { evaluate, hasPolicy, isSessionPolicy } from './frequencyEvaluator';
26
+ const PRODUCTION_API_ROOT = 'https://app.digia.tech';
27
+ const SANDBOX_API_ROOT = 'https://dev.digia.tech';
28
+ const DIGIA_SDK_VERSION = '1.0.0';
20
29
  class DigiaClass {
21
30
  _plugins = new Map();
22
31
  // Tracks whether the native bridge plugin (RNEventBridgePlugin) has been
@@ -26,6 +35,13 @@ class DigiaClass {
26
35
  // the full InAppPayload when overlay lifecycle events arrive from native.
27
36
  _activePayloads = new Map();
28
37
  _engageSubscription = null;
38
+ _projectId = '';
39
+ _deviceId = '';
40
+ _apiBaseUrl = '';
41
+ _logLevel = 'error';
42
+ _currentScreen = null;
43
+ _campaignsByKey = new Map();
44
+ _registeredAnchorKeys = new Set();
29
45
 
30
46
  /**
31
47
  * Initialise the Digia Engage SDK.
@@ -36,7 +52,26 @@ class DigiaClass {
36
52
  async initialize(config) {
37
53
  const environment = config.environment ?? 'production';
38
54
  const logLevel = config.logLevel ?? 'error';
39
- await nativeDigiaModule.initialize(config.apiKey, environment, logLevel);
55
+ this._projectId = config.projectId;
56
+ this._apiBaseUrl = this._resolveApiBaseUrl(config);
57
+ this._logLevel = logLevel;
58
+ this._fontFamily = config.fontFamily?.trim() || undefined;
59
+ digiaHealthReporter.init(config.projectId, this._apiBaseUrl);
60
+ digiaActionHandler.configure({
61
+ onAction: config.onAction,
62
+ routeViaSystemLinking: config.linking?.routeViaSystemLinking ?? true,
63
+ inAppBrowser: config.linking?.inAppBrowser
64
+ });
65
+ try {
66
+ await nativeDigiaModule.initialize(config.projectId, environment, logLevel, config.baseUrl, config.fontFamily);
67
+ } catch (e) {
68
+ // TODO: TO BE PICKED LATER @aditya-digia — health event reporting being removed
69
+ // digiaHealthReporter.report(HealthEventType.fetch_failed, { error_code: 0, platform: 'react_native' });
70
+ throw e;
71
+ }
72
+ this._deviceId = await this._loadOrCreateDeviceId();
73
+ await frequencyStore.checkProjectId(config.projectId);
74
+ await this._refreshCampaignStore();
40
75
  }
41
76
 
42
77
  /**
@@ -50,7 +85,7 @@ class DigiaClass {
50
85
  * ```ts
51
86
  * import { DigiaMoEngagePlugin } from '@digia/moengage-plugin';
52
87
  *
53
- * await Digia.initialize({ apiKey: 'YOUR_KEY' });
88
+ * await Digia.initialize({ projectId: 'YOUR_PROJECT_ID' });
54
89
  * Digia.register(new DigiaMoEngagePlugin({ moEngage: MoEngage }));
55
90
  * ```
56
91
  */
@@ -66,6 +101,7 @@ class DigiaClass {
66
101
  this._nativeBridgeWired = true;
67
102
  }
68
103
  plugin.setup(this);
104
+ plugin.reportHealth?.(digiaHealthReporter);
69
105
  this._plugins.set(plugin.identifier, plugin);
70
106
  }
71
107
 
@@ -87,20 +123,104 @@ class DigiaClass {
87
123
  * All registered plugins will have forwardScreen() called automatically.
88
124
  */
89
125
  setCurrentScreen(name) {
126
+ this._currentScreen = name;
90
127
  nativeDigiaModule.setCurrentScreen(name);
91
128
  this._plugins.forEach(plugin => plugin.forwardScreen(name));
92
129
  }
130
+ registerAnchor(anchorKey, _screenName) {
131
+ const cleanAnchorKey = anchorKey.trim();
132
+ if (!cleanAnchorKey) return;
133
+ this._registeredAnchorKeys.add(cleanAnchorKey);
134
+ }
135
+ unregisterAnchor(anchorKey) {
136
+ this._registeredAnchorKeys.delete(anchorKey);
137
+ }
138
+
139
+ /**
140
+ * Global font family configured via {@link initialize}, or `undefined` when
141
+ * none was set. Used by the JS-rendered guide overlays (tooltip/spotlight)
142
+ * so their text matches native-rendered campaigns.
143
+ */
144
+ get fontFamily() {
145
+ return this._fontFamily;
146
+ }
93
147
 
94
148
  // ── DigiaDelegate ────────────────────────────────────────────────────────
95
149
  // Mirrors DigiaCEPDelegate on Android.
96
150
  // Forwards to the native DigiaCEPDelegate via the bridge.
97
151
 
98
- onCampaignTriggered(payload) {
152
+ async onCampaignTriggered(payload) {
153
+ if (!this._nativeBridgeWired) {
154
+ digiaHealthReporter.report(HealthEventType.plugin_not_registered, {
155
+ campaign_key: payload.id
156
+ });
157
+ }
158
+ const campaignKey = this._extractCampaignKey(payload);
159
+ this._log(`onCampaignTriggered payloadId=${payload.id} extractedKey=${campaignKey} knownKeys=[${[...this._campaignsByKey.keys()].join(', ')}]`);
160
+ if (campaignKey) {
161
+ const campaign = this._campaignsByKey.get(campaignKey);
162
+ if (campaign && hasPolicy(campaign.frequency)) {
163
+ const policy = campaign.frequency;
164
+ const isSession = isSessionPolicy(policy);
165
+ const state = await this._getFrequencyState(campaignKey, isSession);
166
+ const result = evaluate(policy, state, Date.now());
167
+ if (!result.allow) {
168
+ this._log(`frequency_capped campaign_key=${campaignKey} reason=${result.reason}`);
169
+ return;
170
+ }
171
+ }
172
+ if (campaign?.campaign_type === 'inline' || campaign?.campaign_type === 'survey') {
173
+ this._log(`${campaign.campaign_type} campaign triggered campaign_key=${campaignKey}, forwarding to native`);
174
+ this._activePayloads.set(payload.id, payload);
175
+ if (campaign.campaign_type === 'inline') {
176
+ this._emitSlotWidth(campaign);
177
+ }
178
+ nativeDigiaModule.triggerCampaign(payload.id, payload.content, payload.cepContext);
179
+ return;
180
+ }
181
+ if (campaign?.campaign_type === 'guide') {
182
+ const config = this._parseTemplateConfig(campaign);
183
+ if (!config || config.templateType !== 'tooltip' && config.templateType !== 'spotlight' || config.steps.length === 0) {
184
+ digiaHealthReporter.report(HealthEventType.anchor_not_on_screen, {
185
+ campaign_key: campaignKey,
186
+ reason: 'guide_campaign_has_no_steps'
187
+ });
188
+ return;
189
+ }
190
+ this._activePayloads.set(payload.id, payload);
191
+ const digiaId = campaign._id ?? campaign.id ?? campaignKey;
192
+ const mounted = digiaGuideController.start({
193
+ payloadId: payload.id,
194
+ campaignKey,
195
+ campaignId: digiaId,
196
+ config,
197
+ onExperienceEvent: event => this._onGuideLifecycleEvent(event, payload.id, campaignKey, digiaId)
198
+ });
199
+ this._log(`guide trigger campaign_key=${campaignKey} mounted=${mounted}`);
200
+ if (!mounted) {
201
+ digiaHealthReporter.report(HealthEventType.host_not_mounted, {
202
+ campaign_key: campaignKey,
203
+ payload_id: payload.id
204
+ });
205
+ }
206
+ return;
207
+ }
208
+ if (!campaign) {
209
+ this._log(`campaign_key_mismatch: no campaign found for key="${campaignKey}"`);
210
+ digiaHealthReporter.report(HealthEventType.campaign_key_mismatch, {
211
+ campaign_key: campaignKey,
212
+ payload_id: payload.id,
213
+ available_campaign_keys: [...this._campaignsByKey.keys()]
214
+ });
215
+ return;
216
+ }
217
+ }
99
218
  this._activePayloads.set(payload.id, payload);
100
219
  nativeDigiaModule.triggerCampaign(payload.id, payload.content, payload.cepContext);
101
220
  }
102
221
  onCampaignInvalidated(campaignId) {
103
222
  this._activePayloads.delete(campaignId);
223
+ digiaGuideController.cancel(campaignId);
104
224
  nativeDigiaModule.invalidateCampaign(campaignId);
105
225
  }
106
226
 
@@ -121,23 +241,27 @@ class DigiaClass {
121
241
  _forwardExperienceEvent(data) {
122
242
  const payload = this._activePayloads.get(data.campaignId);
123
243
  if (!payload) return;
244
+ const campaignKey = this._extractCampaignKey(payload);
124
245
  let event;
125
246
  switch (data.type) {
126
247
  case 'impressed':
127
248
  event = {
128
249
  type: 'impressed'
129
250
  };
251
+ if (campaignKey) void this._bumpFrequencyImpression(campaignKey);
130
252
  break;
131
253
  case 'clicked':
132
254
  event = {
133
255
  type: 'clicked',
134
256
  elementId: data.elementId
135
257
  };
258
+ if (campaignKey) void this._applyStopOn(campaignKey, 'click');
136
259
  break;
137
260
  case 'dismissed':
138
261
  event = {
139
262
  type: 'dismissed'
140
263
  };
264
+ if (campaignKey) void this._applyStopOn(campaignKey, 'dismiss');
141
265
  this._activePayloads.delete(data.campaignId);
142
266
  break;
143
267
  default:
@@ -145,6 +269,267 @@ class DigiaClass {
145
269
  }
146
270
  this._plugins.forEach(plugin => plugin.notifyEvent(event, payload));
147
271
  }
272
+ _onGuideLifecycleEvent(event, payloadId, campaignKey, campaignId) {
273
+ const eventName = this._guideEventName(event.type);
274
+ const properties = this._buildGuideProperties(event, campaignId, campaignKey);
275
+ this._plugins.forEach(p => p.track?.(eventName, properties));
276
+ if (event.type === 'viewed') {
277
+ void this._bumpFrequencyImpression(campaignKey);
278
+ }
279
+ if (event.type === 'clicked' || event.type === 'completed') {
280
+ void this._applyStopOn(campaignKey, 'click');
281
+ }
282
+ if (event.type === 'dismissed') {
283
+ void this._applyStopOn(campaignKey, 'dismiss');
284
+ }
285
+
286
+ // Notify plugins of CEP lifecycle termination (template cleanup) on exit events.
287
+ if (event.type === 'dismissed' || event.type === 'completed') {
288
+ const storedPayload = this._activePayloads.get(payloadId);
289
+ if (storedPayload) {
290
+ this._plugins.forEach(p => p.notifyEvent({
291
+ type: 'dismissed'
292
+ }, storedPayload));
293
+ this._activePayloads.delete(payloadId);
294
+ }
295
+ }
296
+ }
297
+ _guideEventName(type) {
298
+ switch (type) {
299
+ case 'viewed':
300
+ return 'Digia Experience Viewed';
301
+ case 'step_viewed':
302
+ return 'Digia Step Viewed';
303
+ case 'clicked':
304
+ return 'Digia Experience Clicked';
305
+ case 'step_clicked':
306
+ return 'Digia Step Clicked';
307
+ case 'dismissed':
308
+ return 'Digia Experience Dismissed';
309
+ case 'step_dismissed':
310
+ return 'Digia Step Dismissed';
311
+ case 'completed':
312
+ return 'Digia Experience Completed';
313
+ }
314
+ }
315
+ _buildGuideProperties(event, campaignId, campaignKey) {
316
+ const base = {
317
+ campaign_id: campaignId,
318
+ campaign_key: campaignKey,
319
+ campaign_type: 'guide',
320
+ display_style: event.displayStyle,
321
+ step_index: event.stepIndex + 1,
322
+ step_total: event.stepTotal,
323
+ anchor_key: event.anchorKey,
324
+ slot_key: null,
325
+ element_id: null,
326
+ cta_label: null,
327
+ action_type: null,
328
+ action_url: null,
329
+ dismiss_reason: null,
330
+ abandoned_at_step: null,
331
+ digia_sdk_version: DIGIA_SDK_VERSION,
332
+ digia_platform: 'react_native'
333
+ };
334
+ if (event.type === 'clicked' || event.type === 'step_clicked') {
335
+ base.element_id = event.elementId ?? null;
336
+ base.cta_label = event.ctaLabel;
337
+ base.action_type = event.actionType;
338
+ base.action_url = event.actionUrl ?? null;
339
+ } else if (event.type === 'dismissed' || event.type === 'step_dismissed') {
340
+ base.dismiss_reason = event.dismissReason;
341
+ base.abandoned_at_step = event.stepIndex + 1;
342
+ }
343
+ return base;
344
+ }
345
+ _resolveApiBaseUrl(config) {
346
+ const root = (config.baseUrl ?? (config.environment === 'sandbox' ? SANDBOX_API_ROOT : PRODUCTION_API_ROOT)).trim();
347
+ const cleanRoot = root.replace(/\/+$/, '');
348
+ return cleanRoot.endsWith('/api/v1') ? cleanRoot : `${cleanRoot}/api/v1`;
349
+ }
350
+ async _refreshCampaignStore() {
351
+ try {
352
+ this._log(`fetching campaigns from ${this._apiBaseUrl}/engage/sdk/getCampaigns`);
353
+ const campaigns = await this._sdkPost('getCampaigns');
354
+ this._campaignsByKey.clear();
355
+ if (campaigns.length > 0) {
356
+ this._log(`campaign[0] raw keys: ${JSON.stringify(Object.keys(campaigns[0]))}`);
357
+ }
358
+ campaigns.forEach(campaign => {
359
+ const raw = campaign;
360
+ const key = typeof raw.campaign_key === 'string' && raw.campaign_key || typeof raw.campaignKey === 'string' && raw.campaignKey || null;
361
+ const type = typeof raw.campaign_type === 'string' && raw.campaign_type || typeof raw.campaignType === 'string' && raw.campaignType || '';
362
+ if (key) {
363
+ this._campaignsByKey.set(key, {
364
+ ...campaign,
365
+ campaign_key: key,
366
+ campaign_type: type
367
+ });
368
+ }
369
+ });
370
+ this._log(`loaded ${campaigns.length} campaign(s): [${[...this._campaignsByKey.keys()].join(', ')}]`);
371
+ } catch (e) {
372
+ const reason = e instanceof Error ? e.message : String(e);
373
+ this._log(`getCampaigns FAILED: ${reason}`);
374
+ digiaHealthReporter.report(HealthEventType.fetch_failed, {
375
+ error_code: 0,
376
+ platform: 'react_native',
377
+ reason
378
+ });
379
+ }
380
+ }
381
+ async _sdkPost(path, body = {}) {
382
+ const res = await fetch(`${this._apiBaseUrl}/engage/sdk/${path}`, {
383
+ method: 'POST',
384
+ headers: {
385
+ 'Content-Type': 'application/json',
386
+ 'x-digia-project-id': this._projectId,
387
+ 'x-digia-device-id': this._deviceId
388
+ },
389
+ body: JSON.stringify(body)
390
+ });
391
+ if (!res.ok) {
392
+ throw new Error(`${path} failed: HTTP ${res.status}`);
393
+ }
394
+ const json = await res.json();
395
+ return this._extractApiResponse(json);
396
+ }
397
+ _extractApiResponse(json) {
398
+ if (Array.isArray(json)) return json;
399
+ if (json && typeof json === 'object') {
400
+ const obj = json;
401
+ const data = obj.data;
402
+ if (data && typeof data === 'object' && 'response' in data) {
403
+ const value = data.response;
404
+ if (value == null) throw new Error('SDK response.data.response is null');
405
+ return value;
406
+ }
407
+ if ('response' in obj) {
408
+ const value = obj.response;
409
+ if (value == null) throw new Error('SDK response.response is null');
410
+ return value;
411
+ }
412
+ }
413
+ throw new Error('SDK response missing data.response');
414
+ }
415
+ _emitSlotWidth(campaign) {
416
+ const raw = campaign;
417
+ const config = raw.templateConfig;
418
+ if (!config) {
419
+ this._log(`_emitSlotWidth: no templateConfig for campaign ${campaign.campaign_key}`);
420
+ return;
421
+ }
422
+ const slotKey = typeof config.slotKey === 'string' ? config.slotKey : null;
423
+ const width = typeof config.width === 'number' && config.width > 0 ? config.width : null;
424
+ this._log(`_emitSlotWidth slotKey=${slotKey} width=${width} campaign=${campaign.campaign_key}`);
425
+ if (slotKey) {
426
+ DeviceEventEmitter.emit('digiaSlotWidth', {
427
+ slotKey,
428
+ width
429
+ });
430
+ } else {
431
+ this._log(`_emitSlotWidth: no slotKey in templateConfig ${JSON.stringify(config)}`);
432
+ }
433
+ }
434
+ _extractCampaignKey(payload) {
435
+ const fromContent = this._extractString(payload.content, 'digiaKey', 'campaign_key', 'campaignKey');
436
+ if (fromContent) return fromContent;
437
+ const args = payload.content.args;
438
+ if (args && typeof args === 'object' && !Array.isArray(args)) {
439
+ const fromArgs = this._extractString(args, 'digiaKey', 'campaign_key', 'campaignKey');
440
+ if (fromArgs) return fromArgs;
441
+ }
442
+ if (this._campaignsByKey.has(payload.id)) return payload.id;
443
+ return null;
444
+ }
445
+ _extractString(data, ...keys) {
446
+ for (const key of keys) {
447
+ const value = data[key];
448
+ if (typeof value === 'string' && value.trim()) return value.trim();
449
+ }
450
+ return null;
451
+ }
452
+ _parseTemplateConfig(campaign) {
453
+ const c = campaign;
454
+ const raw = c.templateConfig;
455
+ if (!raw || typeof raw !== 'object') return null;
456
+ const type = raw.templateType;
457
+ if (type !== 'tooltip' && type !== 'spotlight' && type !== 'carousel') {
458
+ this._log(`unknown templateType="${type}" for campaign_key=${campaign.campaign_key}`);
459
+ return null;
460
+ }
461
+ if (type === 'carousel') {
462
+ return raw;
463
+ }
464
+ const steps = Array.isArray(raw.steps) ? raw.steps : [];
465
+ return {
466
+ ...raw,
467
+ templateType: type,
468
+ steps
469
+ };
470
+ }
471
+
472
+ // ── Device ID ────────────────────────────────────────────────────────────
473
+
474
+ async _loadOrCreateDeviceId() {
475
+ const DEVICE_ID_KEY = 'digia:device_id';
476
+ try {
477
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
478
+ const AsyncStorage = require('@react-native-async-storage/async-storage').default;
479
+ const stored = await AsyncStorage.getItem(DEVICE_ID_KEY);
480
+ if (stored) return stored;
481
+ const id = uuid.v4();
482
+ await AsyncStorage.setItem(DEVICE_ID_KEY, id);
483
+ return id;
484
+ } catch {
485
+ return uuid.v4();
486
+ }
487
+ }
488
+
489
+ // ── Frequency capping ────────────────────────────────────────────────────
490
+
491
+ async _getFrequencyState(campaignKey, isSession) {
492
+ return frequencyStore.get(campaignKey, isSession);
493
+ }
494
+ async _bumpFrequencyImpression(campaignKey) {
495
+ const campaign = this._campaignsByKey.get(campaignKey);
496
+ if (!campaign || !hasPolicy(campaign.frequency)) return;
497
+ const isSession = isSessionPolicy(campaign.frequency);
498
+ const now = Date.now();
499
+ const prev = await frequencyStore.get(campaignKey, isSession);
500
+ const next = {
501
+ shown_count: (prev?.shown_count ?? 0) + 1,
502
+ first_shown_at: prev?.first_shown_at ?? now,
503
+ last_shown_at: now,
504
+ stopped_at: prev?.stopped_at ?? null,
505
+ stopped_reason: prev?.stopped_reason ?? null
506
+ };
507
+ await frequencyStore.set(campaignKey, next, isSession);
508
+ }
509
+ async _applyStopOn(campaignKey, interactionType) {
510
+ const campaign = this._campaignsByKey.get(campaignKey);
511
+ const stopOn = campaign?.frequency?.stop_on;
512
+ if (!stopOn) return;
513
+ const matches = stopOn === 'any_action' || stopOn === interactionType;
514
+ if (!matches) return;
515
+ const isSession = isSessionPolicy(campaign.frequency);
516
+ const prev = await frequencyStore.get(campaignKey, isSession);
517
+ if (prev?.stopped_at) return;
518
+ const now = Date.now();
519
+ const next = {
520
+ shown_count: prev?.shown_count ?? 0,
521
+ first_shown_at: prev?.first_shown_at ?? null,
522
+ last_shown_at: prev?.last_shown_at ?? null,
523
+ stopped_at: now,
524
+ stopped_reason: interactionType
525
+ };
526
+ await frequencyStore.set(campaignKey, next, isSession);
527
+ }
528
+ _log(message) {
529
+ if (this._logLevel !== 'verbose') return;
530
+ // eslint-disable-next-line no-console
531
+ console.log(`[Digia] ${message}`);
532
+ }
148
533
  }
149
534
  export const Digia = new DigiaClass();
150
535
  //# sourceMappingURL=Digia.js.map