@digia-engage/core 2.3.2 → 2.4.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 (62) hide show
  1. package/DigiaEngageReactNative.podspec +1 -1
  2. package/android/.project +28 -0
  3. package/android/build.gradle +1 -1
  4. package/android/local.properties +1 -0
  5. package/android/src/main/java/com/digia/engage/rn/DigiaModule.kt +64 -6
  6. package/ios/DigiaModule.swift +70 -8
  7. package/ios/RNEventBridgePlugin.swift +2 -0
  8. package/lib/commonjs/Digia.js +139 -100
  9. package/lib/commonjs/Digia.js.map +1 -1
  10. package/lib/commonjs/DigiaAnchorView.js +11 -1
  11. package/lib/commonjs/DigiaAnchorView.js.map +1 -1
  12. package/lib/commonjs/DigiaProvider.js +63 -2
  13. package/lib/commonjs/DigiaProvider.js.map +1 -1
  14. package/lib/commonjs/NativeDigiaEngage.js +3 -0
  15. package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
  16. package/lib/commonjs/digiaAnchorRegistry.js +3 -1
  17. package/lib/commonjs/digiaAnchorRegistry.js.map +1 -1
  18. package/lib/commonjs/frequencyStore.js +3 -3
  19. package/lib/commonjs/frequencyStore.js.map +1 -1
  20. package/lib/commonjs/index.js +0 -7
  21. package/lib/commonjs/index.js.map +1 -1
  22. package/lib/module/Digia.js +139 -100
  23. package/lib/module/Digia.js.map +1 -1
  24. package/lib/module/DigiaAnchorView.js +11 -1
  25. package/lib/module/DigiaAnchorView.js.map +1 -1
  26. package/lib/module/DigiaProvider.js +65 -3
  27. package/lib/module/DigiaProvider.js.map +1 -1
  28. package/lib/module/NativeDigiaEngage.js +3 -0
  29. package/lib/module/NativeDigiaEngage.js.map +1 -1
  30. package/lib/module/digiaAnchorRegistry.js +3 -1
  31. package/lib/module/digiaAnchorRegistry.js.map +1 -1
  32. package/lib/module/frequencyStore.js +3 -3
  33. package/lib/module/frequencyStore.js.map +1 -1
  34. package/lib/module/index.js +4 -4
  35. package/lib/module/index.js.map +1 -1
  36. package/lib/typescript/Digia.d.ts +17 -8
  37. package/lib/typescript/Digia.d.ts.map +1 -1
  38. package/lib/typescript/DigiaAnchorView.d.ts.map +1 -1
  39. package/lib/typescript/DigiaProvider.d.ts +3 -1
  40. package/lib/typescript/DigiaProvider.d.ts.map +1 -1
  41. package/lib/typescript/NativeDigiaEngage.d.ts +9 -0
  42. package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
  43. package/lib/typescript/digiaAnchorRegistry.d.ts +1 -0
  44. package/lib/typescript/digiaAnchorRegistry.d.ts.map +1 -1
  45. package/lib/typescript/frequencyStore.d.ts +1 -1
  46. package/lib/typescript/frequencyStore.d.ts.map +1 -1
  47. package/lib/typescript/index.d.ts +5 -5
  48. package/lib/typescript/index.d.ts.map +1 -1
  49. package/lib/typescript/templateTypes.d.ts +24 -1
  50. package/lib/typescript/templateTypes.d.ts.map +1 -1
  51. package/lib/typescript/types.d.ts +17 -13
  52. package/lib/typescript/types.d.ts.map +1 -1
  53. package/package.json +8 -9
  54. package/src/Digia.ts +142 -114
  55. package/src/DigiaAnchorView.tsx +6 -1
  56. package/src/DigiaProvider.tsx +76 -2
  57. package/src/NativeDigiaEngage.ts +20 -0
  58. package/src/digiaAnchorRegistry.ts +3 -1
  59. package/src/frequencyStore.ts +4 -4
  60. package/src/index.ts +5 -5
  61. package/src/templateTypes.ts +31 -1
  62. package/src/types.ts +17 -13
@@ -28,7 +28,7 @@ Pod::Spec.new do |s|
28
28
  s.dependency 'React-Core'
29
29
 
30
30
  # ── Digia Engage iOS SDK ──────────────────────────────────────────────────
31
- s.dependency 'DigiaEngage','~> 2.3.1'
31
+ s.dependency 'DigiaEngage','~> 2.4.0'
32
32
 
33
33
  # ── New Architecture (Fabric / TurboModules) support ─────────────────────
34
34
  install_modules_dependencies(s)
@@ -0,0 +1,28 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>digia-engage_core</name>
4
+ <comment>Project digia-engage_core created by Buildship.</comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ <buildCommand>
9
+ <name>org.eclipse.buildship.core.gradleprojectbuilder</name>
10
+ <arguments>
11
+ </arguments>
12
+ </buildCommand>
13
+ </buildSpec>
14
+ <natures>
15
+ <nature>org.eclipse.buildship.core.gradleprojectnature</nature>
16
+ </natures>
17
+ <filteredResources>
18
+ <filter>
19
+ <id>1780322402778</id>
20
+ <name></name>
21
+ <type>30</type>
22
+ <matcher>
23
+ <id>org.eclipse.core.resources.regexFilterMatcher</id>
24
+ <arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
25
+ </matcher>
26
+ </filter>
27
+ </filteredResources>
28
+ </projectDescription>
@@ -71,7 +71,7 @@ android {
71
71
 
72
72
  dependencies {
73
73
  // Digia Engage Android library
74
- implementation 'tech.digia:engage:2.1.1'
74
+ implementation 'tech.digia:engage:2.2.0'
75
75
 
76
76
  // ── React Native ─────────────────────────────────────────────────────────
77
77
  // React Native is provided by the host app; mark as compileOnly so it is
@@ -0,0 +1 @@
1
+ sdk.dir=/Users/ram/Library/Android/sdk
@@ -29,6 +29,7 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
29
29
  import androidx.lifecycle.setViewTreeViewModelStoreOwner
30
30
  import androidx.savedstate.SavedStateRegistryOwner
31
31
  import androidx.savedstate.setViewTreeSavedStateRegistryOwner
32
+ import com.digia.engage.CEPTriggerPayload
32
33
  import com.digia.engage.DiagnosticReport
33
34
  import com.digia.engage.Digia
34
35
  import com.digia.engage.DigiaCEPDelegate
@@ -126,6 +127,54 @@ internal class DigiaModule(
126
127
  Digia.setCurrentScreen(name)
127
128
  }
128
129
 
130
+ // ─── Analytics identity ───────────────────────────────────────────────────
131
+
132
+ @ReactMethod
133
+ fun setUserId(userId: String) {
134
+ Digia.setUserId(userId)
135
+ }
136
+
137
+ @ReactMethod
138
+ fun clearUserId() {
139
+ Digia.clearUserId()
140
+ }
141
+
142
+ // ─── Analytics event forwarding (guide / JS-rendered campaigns) ───────────
143
+
144
+ /**
145
+ * Records an analytics event originating from JS-rendered campaigns (guides).
146
+ * Native campaigns (nudge, inline, survey) are tracked internally by the SDK.
147
+ */
148
+ @ReactMethod
149
+ fun trackEvent(
150
+ eventType: String,
151
+ campaignId: String,
152
+ campaignKey: String,
153
+ campaignType: String,
154
+ elementId: String?,
155
+ ) {
156
+ android.util.Log.d("DigiaAnalytics", "[trackEvent] RN bridge received: type=$eventType campaignId=$campaignId campaignKey=$campaignKey campaignType=$campaignType elementId=$elementId")
157
+ val event = when (eventType) {
158
+ "impressed" -> DigiaExperienceEvent.Impressed
159
+ "clicked" -> DigiaExperienceEvent.Clicked(elementId)
160
+ "dismissed" -> DigiaExperienceEvent.Dismissed
161
+ else -> {
162
+ android.util.Log.w("DigiaAnalytics", "[trackEvent] unknown eventType='$eventType' — dropped")
163
+ return
164
+ }
165
+ }
166
+ val payload = InAppPayload(
167
+ id = campaignKey,
168
+ content = mapOf(
169
+ "campaign_id" to campaignId,
170
+ "campaign_key" to campaignKey,
171
+ "campaign_type" to campaignType,
172
+ ),
173
+ )
174
+ android.util.Log.d("DigiaAnalytics", "[trackEvent] forwarding to Digia.captureAnalyticsEvent: event=${event::class.simpleName}")
175
+ Digia.captureAnalyticsEvent(event, payload)
176
+ }
177
+
129
178
  // ─── triggerCampaign ──────────────────────────────────────────────────────
130
179
 
131
180
  /**
@@ -138,11 +187,20 @@ internal class DigiaModule(
138
187
  @ReactMethod
139
188
  fun triggerCampaign(id: String, content: ReadableMap, cepContext: ReadableMap) {
140
189
  val delegate = rnPlugin.delegate ?: return
190
+ val contentMap = content.toHashMap().toMap()
191
+ val campaignKey = (contentMap["digia_campaign_key"] as? String)
192
+ ?: (contentMap["campaign_key"] as? String)
193
+ ?: id
194
+ @Suppress("UNCHECKED_CAST")
195
+ val variables = (contentMap["variables"] as? Map<*, *>)
196
+ ?.entries
197
+ ?.associate { it.key.toString() to it.value?.toString().orEmpty() }
141
198
  delegate.onCampaignTriggered(
142
- InAppPayload(
143
- id = id,
144
- content = content.toHashMap().toMap(),
145
- cepContext = cepContext.toHashMap().toMap(),
199
+ CEPTriggerPayload(
200
+ cepCampaignId = id,
201
+ campaignKey = campaignKey,
202
+ cepMetadata = cepContext.toHashMap().toMap(),
203
+ variables = variables,
146
204
  )
147
205
  )
148
206
  }
@@ -277,10 +335,10 @@ internal class RNEventBridgePlugin(
277
335
  /* forwarded by Digia.setCurrentScreen() on the native side */
278
336
  }
279
337
 
280
- override fun notifyEvent(event: DigiaExperienceEvent, payload: InAppPayload) {
338
+ override fun notifyEvent(event: DigiaExperienceEvent, payload: CEPTriggerPayload) {
281
339
  val params =
282
340
  Arguments.createMap().apply {
283
- putString("campaignId", payload.id)
341
+ putString("campaignId", payload.cepCampaignId)
284
342
  when (event) {
285
343
  is DigiaExperienceEvent.Impressed -> putString("type", "impressed")
286
344
  is DigiaExperienceEvent.Clicked -> {
@@ -78,13 +78,18 @@ final class DigiaModule: RCTEventEmitter {
78
78
  default: logLevelValue = .error
79
79
  }
80
80
 
81
+ let cleanBaseUrl = baseUrl.flatMap { url -> String? in
82
+ var s = url.trimmingCharacters(in: .whitespacesAndNewlines)
83
+ .trimmingCharacters(in: CharacterSet(charactersIn: "/"))
84
+ if s.hasSuffix("/api/v1") { s = String(s.dropLast(7)) }
85
+ return s.isEmpty ? nil : s
86
+ }
87
+
81
88
  let config = DigiaConfig(
82
89
  apiKey: projectId,
83
90
  logLevel: logLevelValue,
84
91
  environment: envValue,
85
- developerConfig: baseUrl.flatMap {
86
- $0.isEmpty ? nil : DigiaDeveloperConfig(baseURL: $0)
87
- },
92
+ developerConfig: cleanBaseUrl.map { DigiaDeveloperConfig(baseURL: $0) },
88
93
  fontFamily: fontFamily.flatMap { $0.isEmpty ? nil : $0 }
89
94
  )
90
95
 
@@ -109,10 +114,27 @@ final class DigiaModule: RCTEventEmitter {
109
114
  @objc
110
115
  func registerBridge() {
111
116
  Task { @MainActor in
117
+ // Dismiss any stale overlay left over from the previous JS session.
118
+ Digia.dismissActiveNudge()
119
+
120
+ // After a JS reload, re-bring the overlay host to the front.
121
+ // Expo/RN may have added views during the reload cycle that sit on
122
+ // top of the host, causing the SwiftUI layer to be unreachable by
123
+ // touch even when hasActiveOverlay = true.
124
+ if let rootVC = UIApplication.shared.connectedScenes
125
+ .compactMap({ ($0 as? UIWindowScene)?.keyWindow?.rootViewController })
126
+ .first,
127
+ let hostView = rootVC.view.viewWithTag(Self.overlayMountTag) {
128
+ rootVC.view.bringSubviewToFront(hostView)
129
+ }
130
+
112
131
  Digia.register(self.rnPlugin)
132
+ print("[DigiaRN] registerBridge: rnPlugin registered, delegate=\(self.rnPlugin.delegate != nil ? "set" : "nil")")
113
133
  }
114
134
  }
115
135
 
136
+ private static let overlayMountTag = 0xD19140
137
+
116
138
  // ────────────────────────────────────────────────────────────────────────
117
139
  // MARK: - setCurrentScreen
118
140
 
@@ -140,10 +162,17 @@ final class DigiaModule: RCTEventEmitter {
140
162
  let content = buildInAppPayloadContent(from: contentMap)
141
163
  let cepContext = (cepContextMap as? [String: String]) ?? [:]
142
164
  let payload = InAppPayload(id: id, content: content, cepContext: cepContext)
165
+ print("[DigiaRN] triggerCampaign id=\(id) campaignKey=\(content.campaignKey ?? "nil")")
143
166
 
144
167
  Task { @MainActor in
145
- guard let delegate = self.rnPlugin.delegate else { return }
168
+ guard let delegate = self.rnPlugin.delegate else {
169
+ print("[DigiaRN] triggerCampaign: delegate is nil — registerBridge() may not have run yet")
170
+ return
171
+ }
146
172
  delegate.onCampaignTriggered(payload)
173
+ Task { @MainActor in
174
+ print("[DigiaRN] triggerCampaign post-call: hasActiveOverlay=\(Digia.hasActiveOverlay)")
175
+ }
147
176
  }
148
177
  }
149
178
 
@@ -213,12 +242,11 @@ final class DigiaModule: RCTEventEmitter {
213
242
  else { return }
214
243
 
215
244
  // Guard against double-mounting (e.g. fast-refresh).
216
- let mountTag = 0xD19140
217
- if rootVC.view.viewWithTag(mountTag) != nil { return }
245
+ if rootVC.view.viewWithTag(Self.overlayMountTag) != nil { return }
218
246
 
219
247
  let hc = UIHostingController(rootView: DigiaHostWrapperView())
220
248
  let hostView = DigiaRootOverlayView(hostingController: hc)
221
- hostView.tag = mountTag
249
+ hostView.tag = Self.overlayMountTag
222
250
  hostView.translatesAutoresizingMaskIntoConstraints = false
223
251
  hostView.backgroundColor = .clear
224
252
 
@@ -254,6 +282,7 @@ final class DigiaModule: RCTEventEmitter {
254
282
  let screenId = map["screenId"] as? String
255
283
  let campaignKey =
256
284
  (map["campaignKey"] as? String) ?? (map["campaign_key"] as? String)
285
+ ?? (map["digia_campaign_key"] as? String)
257
286
  ?? (map["digiaKey"] as? String)
258
287
  var type = (map["type"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
259
288
  if type.isEmpty {
@@ -266,6 +295,13 @@ final class DigiaModule: RCTEventEmitter {
266
295
  return raw.compactMapValues { JSONValue(rawValue: $0) }
267
296
  }()
268
297
 
298
+ // CEP trigger variables for `{{ }}` interpolation. CleverTap's nudge
299
+ // mapper puts them at `content.variables` (top-level); the command/inline
300
+ // mappers may nest them under `args.variables`. Mirror the JS bridge's
301
+ // `_extractVariables` (content.variables first, then args.variables).
302
+ let variables = Self.variableMap(map["variables"])
303
+ ?? Self.variableMap((map["args"] as? [String: Any])?["variables"])
304
+
269
305
  return InAppPayloadContent(
270
306
  type: type,
271
307
  placementKey: pk,
@@ -275,9 +311,35 @@ final class DigiaModule: RCTEventEmitter {
275
311
  command: command,
276
312
  args: args,
277
313
  screenId: screenId,
278
- campaignKey: campaignKey
314
+ campaignKey: campaignKey,
315
+ variables: variables
279
316
  )
280
317
  }
318
+
319
+ /// Coerces a raw `variables` value into a `[String: String]` map, stringifying
320
+ /// scalar values (string / number / bool) and dropping anything else. Returns
321
+ /// nil for a missing or empty map. Mirrors the JS `parseVariableMap`.
322
+ private static func variableMap(_ raw: Any?) -> [String: String]? {
323
+ guard let dict = raw as? [String: Any] else { return nil }
324
+ var result: [String: String] = [:]
325
+ for (key, value) in dict {
326
+ switch value {
327
+ case let string as String:
328
+ result[key] = string
329
+ case let number as NSNumber:
330
+ // Distinguish a boxed bool from a numeric NSNumber so `true`
331
+ // stays "true" rather than "1".
332
+ if CFGetTypeID(number) == CFBooleanGetTypeID() {
333
+ result[key] = number.boolValue ? "true" : "false"
334
+ } else {
335
+ result[key] = number.stringValue
336
+ }
337
+ default:
338
+ continue
339
+ }
340
+ }
341
+ return result.isEmpty ? nil : result
342
+ }
281
343
  }
282
344
 
283
345
  // MARK: - DigiaRootOverlayView
@@ -54,6 +54,8 @@ internal final class RNEventBridgePlugin: NSObject, DigiaCEPPlugin {
54
54
  case .dismissed:
55
55
  body["type"] = "dismissed"
56
56
  }
57
+ let hasEmitter = eventEmitter != nil
58
+ print("[DigiaRN] RNEventBridgePlugin.notifyEvent type=\(body["type"] ?? "?") campaignId=\(payload.id) hasEmitter=\(hasEmitter)")
57
59
  eventEmitter?.sendEvent(withName: "digiaEngageEvent", body: body)
58
60
  }
59
61