@digia-engage/core 2.3.1 → 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.
- package/DigiaEngageReactNative.podspec +1 -1
- package/README.md +70 -0
- package/android/.project +28 -0
- package/android/build.gradle +1 -1
- package/android/local.properties +1 -0
- package/android/src/main/java/com/digia/engage/rn/DigiaModule.kt +64 -6
- package/ios/DigiaModule.swift +70 -8
- package/ios/RNEventBridgePlugin.swift +2 -0
- package/lib/commonjs/Digia.js +139 -100
- package/lib/commonjs/Digia.js.map +1 -1
- package/lib/commonjs/DigiaAnchorView.js +11 -1
- package/lib/commonjs/DigiaAnchorView.js.map +1 -1
- package/lib/commonjs/DigiaProvider.js +63 -2
- package/lib/commonjs/DigiaProvider.js.map +1 -1
- package/lib/commonjs/NativeDigiaEngage.js +3 -0
- package/lib/commonjs/NativeDigiaEngage.js.map +1 -1
- package/lib/commonjs/digiaAnchorRegistry.js +3 -1
- package/lib/commonjs/digiaAnchorRegistry.js.map +1 -1
- package/lib/commonjs/frequencyStore.js +3 -3
- package/lib/commonjs/frequencyStore.js.map +1 -1
- package/lib/commonjs/index.js +0 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/Digia.js +139 -100
- package/lib/module/Digia.js.map +1 -1
- package/lib/module/DigiaAnchorView.js +11 -1
- package/lib/module/DigiaAnchorView.js.map +1 -1
- package/lib/module/DigiaProvider.js +65 -3
- package/lib/module/DigiaProvider.js.map +1 -1
- package/lib/module/NativeDigiaEngage.js +3 -0
- package/lib/module/NativeDigiaEngage.js.map +1 -1
- package/lib/module/digiaAnchorRegistry.js +3 -1
- package/lib/module/digiaAnchorRegistry.js.map +1 -1
- package/lib/module/frequencyStore.js +3 -3
- package/lib/module/frequencyStore.js.map +1 -1
- package/lib/module/index.js +4 -4
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/Digia.d.ts +17 -8
- package/lib/typescript/Digia.d.ts.map +1 -1
- package/lib/typescript/DigiaAnchorView.d.ts.map +1 -1
- package/lib/typescript/DigiaProvider.d.ts +3 -1
- package/lib/typescript/DigiaProvider.d.ts.map +1 -1
- package/lib/typescript/NativeDigiaEngage.d.ts +9 -0
- package/lib/typescript/NativeDigiaEngage.d.ts.map +1 -1
- package/lib/typescript/digiaAnchorRegistry.d.ts +1 -0
- package/lib/typescript/digiaAnchorRegistry.d.ts.map +1 -1
- package/lib/typescript/frequencyStore.d.ts +1 -1
- package/lib/typescript/frequencyStore.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +5 -5
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/templateTypes.d.ts +24 -1
- package/lib/typescript/templateTypes.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +17 -13
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +13 -14
- package/src/Digia.ts +142 -114
- package/src/DigiaAnchorView.tsx +6 -1
- package/src/DigiaProvider.tsx +76 -2
- package/src/NativeDigiaEngage.ts +20 -0
- package/src/digiaAnchorRegistry.ts +3 -1
- package/src/frequencyStore.ts +4 -4
- package/src/index.ts +5 -5
- package/src/templateTypes.ts +31 -1
- 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.
|
|
31
|
+
s.dependency 'DigiaEngage','~> 2.4.0'
|
|
32
32
|
|
|
33
33
|
# ── New Architecture (Fabric / TurboModules) support ─────────────────────
|
|
34
34
|
install_modules_dependencies(s)
|
package/README.md
CHANGED
|
@@ -266,6 +266,76 @@ react-native/ios/ iOS bridge
|
|
|
266
266
|
|
|
267
267
|
---
|
|
268
268
|
|
|
269
|
+
## Build & publishing (hybrid model)
|
|
270
|
+
|
|
271
|
+
This package is published using the **hybrid** React Native library layout (the
|
|
272
|
+
[`react-native-builder-bob`](https://github.com/callstack/react-native-builder-bob)
|
|
273
|
+
convention): the npm tarball ships **both** the compiled output (`lib/`) **and**
|
|
274
|
+
the original TypeScript source (`src/`).
|
|
275
|
+
|
|
276
|
+
### What the entry fields mean
|
|
277
|
+
|
|
278
|
+
| `package.json` field | Points to | Used by |
|
|
279
|
+
|---|---|---|
|
|
280
|
+
| `main` | `lib/commonjs/index` | Node / CommonJS consumers, Jest |
|
|
281
|
+
| `module` | `lib/module/index` | Bundlers that understand ESM |
|
|
282
|
+
| `types` | `lib/typescript/index.d.ts` | TypeScript |
|
|
283
|
+
| `react-native` / `source` | `src/index` | **Metro** — RN apps bundle straight from source |
|
|
284
|
+
|
|
285
|
+
Because Metro resolves the `react-native`/`source` field, a React Native app that
|
|
286
|
+
consumes this package bundles the **actual `.ts` source**. That gives our users
|
|
287
|
+
the best developer experience:
|
|
288
|
+
|
|
289
|
+
- **Stack traces** point at real `src/*.ts` lines, not transpiled output.
|
|
290
|
+
- **Step-debugging** walks through the real source.
|
|
291
|
+
- **Go-to-definition** lands on the real source — we ship `.d.ts` **and**
|
|
292
|
+
`.d.ts.map` (declaration maps) alongside `src/`, so an IDE jumps from the type
|
|
293
|
+
definition through to the `.ts` it came from.
|
|
294
|
+
|
|
295
|
+
The compiled `lib/` is a robust fallback for any tool that does *not* honour the
|
|
296
|
+
`react-native` field (Node, Jest, web bundlers, type resolvers), so the package
|
|
297
|
+
never breaks outside Metro.
|
|
298
|
+
|
|
299
|
+
### Why not ship raw `src/` only?
|
|
300
|
+
|
|
301
|
+
Shipping only `src/*.ts` works in RN (Metro strips the types) but breaks
|
|
302
|
+
everywhere else — bundlers skip `node_modules` transpilation by default, and a
|
|
303
|
+
consumer's stricter `tsconfig` would re-type-check our source and surface errors
|
|
304
|
+
they can't fix. The hybrid layout keeps the great RN DX *and* stays safe for
|
|
305
|
+
every other consumer.
|
|
306
|
+
|
|
307
|
+
### Build config that makes this work
|
|
308
|
+
|
|
309
|
+
`tsconfig.build.json` (used only to generate type definitions):
|
|
310
|
+
|
|
311
|
+
- `declaration: true` — emit `.d.ts`
|
|
312
|
+
- `declarationMap: true` — emit `.d.ts.map` so go-to-definition reaches `src/`
|
|
313
|
+
- `sourceMap: true` + `inlineSources: true` — map compiled JS back to source
|
|
314
|
+
- `rootDir: "src"` — keeps `index.d.ts` at the top of `lib/typescript/`
|
|
315
|
+
- **No** `declarationDir` / `noEmit` here — `bob` sets those via the CLI; leaving
|
|
316
|
+
them in the config produces conflict warnings
|
|
317
|
+
|
|
318
|
+
`files` includes both `src` and `lib` so the maps resolve on the consumer's disk.
|
|
319
|
+
|
|
320
|
+
### Publishing
|
|
321
|
+
|
|
322
|
+
The `prepare` script runs `bob build` automatically on install and publish, so
|
|
323
|
+
the build toolchain (`typescript`, `react-native-builder-bob`) must be installed
|
|
324
|
+
first:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npm install # installs devDeps AND runs prepare → bob build
|
|
328
|
+
npm pack --dry-run # verify lib/ + src/ + maps are in the tarball
|
|
329
|
+
npm publish
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
> **Heads-up:** `package-lock.json` is **gitignored** for this library (a lib's
|
|
333
|
+
> lockfile is ignored by consumers and only hides dependency-range drift). On a
|
|
334
|
+
> fresh CI clone there is no lockfile, so use `npm install` — **not** `npm ci`,
|
|
335
|
+
> which requires one.
|
|
336
|
+
|
|
337
|
+
**Never hand-edit `lib/`** — it is generated. Edit `src/` and rebuild.
|
|
338
|
+
|
|
269
339
|
## License
|
|
270
340
|
|
|
271
341
|
Business Source License 1.1 (BUSL-1.1) © Digia Technology Private Limited — see [LICENSE](LICENSE)
|
package/android/.project
ADDED
|
@@ -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>
|
package/android/build.gradle
CHANGED
|
@@ -71,7 +71,7 @@ android {
|
|
|
71
71
|
|
|
72
72
|
dependencies {
|
|
73
73
|
// Digia Engage Android library
|
|
74
|
-
implementation 'tech.digia:engage:2.
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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:
|
|
338
|
+
override fun notifyEvent(event: DigiaExperienceEvent, payload: CEPTriggerPayload) {
|
|
281
339
|
val params =
|
|
282
340
|
Arguments.createMap().apply {
|
|
283
|
-
putString("campaignId", payload.
|
|
341
|
+
putString("campaignId", payload.cepCampaignId)
|
|
284
342
|
when (event) {
|
|
285
343
|
is DigiaExperienceEvent.Impressed -> putString("type", "impressed")
|
|
286
344
|
is DigiaExperienceEvent.Clicked -> {
|
package/ios/DigiaModule.swift
CHANGED
|
@@ -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:
|
|
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 {
|
|
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
|
-
|
|
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 =
|
|
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
|
|