@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
package/src/DigiaProvider.tsx
CHANGED
|
@@ -3,17 +3,27 @@ import {
|
|
|
3
3
|
Animated,
|
|
4
4
|
Dimensions,
|
|
5
5
|
Modal,
|
|
6
|
+
Platform,
|
|
6
7
|
Pressable,
|
|
7
8
|
StyleSheet,
|
|
8
9
|
Text,
|
|
9
10
|
View,
|
|
11
|
+
requireNativeComponent,
|
|
10
12
|
useWindowDimensions,
|
|
11
13
|
} from 'react-native';
|
|
14
|
+
|
|
15
|
+
// Native view that hosts Digia's Compose overlay (dialogs / bottom sheets).
|
|
16
|
+
// pointerEvents="none" ensures it never intercepts touches.
|
|
17
|
+
const NativeDigiaHostView =
|
|
18
|
+
Platform.OS === 'android' || Platform.OS === 'ios'
|
|
19
|
+
? requireNativeComponent<{ style?: object; pointerEvents?: string }>('DigiaHostView')
|
|
20
|
+
: null;
|
|
12
21
|
import { computePosition, flip, offset, shift } from '@floating-ui/core';
|
|
13
22
|
import Svg, { Path } from 'react-native-svg';
|
|
14
23
|
import { Digia } from './Digia';
|
|
15
24
|
import { digiaGuideController, type DigiaGuideRequest } from './DigiaGuideController';
|
|
16
25
|
import { digiaAnchorRegistry, type AnchorLayout } from './digiaAnchorRegistry';
|
|
26
|
+
import { digiaHealthReporter, HealthEventType } from './DigiaHealthReporter';
|
|
17
27
|
import { digiaActionHandler, type ActionCallbacks } from './actionHandler';
|
|
18
28
|
import type { DismissReason } from './types';
|
|
19
29
|
import type { Action, SpotlightConfig, SpotlightStep, TooltipConfig, TooltipStep } from './templateTypes';
|
|
@@ -247,9 +257,25 @@ function TooltipOverlay({
|
|
|
247
257
|
setLayout(null);
|
|
248
258
|
setFloatPos(null);
|
|
249
259
|
if (!ready) return;
|
|
260
|
+
if (!digiaAnchorRegistry.isRegistered(step.anchorKey)) {
|
|
261
|
+
// eslint-disable-next-line no-console
|
|
262
|
+
console.warn(`[Digia] campaign dropped — anchor_key "${step.anchorKey}" is not registered on this screen (campaign_key=${request.campaignKey}, step=${stepIndex})`);
|
|
263
|
+
digiaHealthReporter.report(HealthEventType.anchor_not_on_screen, {
|
|
264
|
+
campaign_key: request.campaignKey,
|
|
265
|
+
reason: 'anchor_key_not_registered',
|
|
266
|
+
anchor_key: step.anchorKey,
|
|
267
|
+
step_index: stepIndex,
|
|
268
|
+
});
|
|
269
|
+
digiaGuideController.cancel(request.payloadId);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
250
272
|
let skipCached = false;
|
|
251
273
|
const unsub = digiaAnchorRegistry.subscribe(step.anchorKey, (l) => {
|
|
252
274
|
if (!skipCached) return;
|
|
275
|
+
if (l.width === 0 || l.height === 0) {
|
|
276
|
+
digiaGuideController.cancel(request.payloadId);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
253
279
|
const { width: screenW, height: screenH } = Dimensions.get('window');
|
|
254
280
|
if (l.pageY + l.height <= 0 || l.pageY >= screenH || l.pageX + l.width <= 0 || l.pageX >= screenW) {
|
|
255
281
|
digiaGuideController.cancel(request.payloadId);
|
|
@@ -632,9 +658,25 @@ function SpotlightOverlay({
|
|
|
632
658
|
useEffect(() => {
|
|
633
659
|
setLayout(null);
|
|
634
660
|
if (!ready) return; // hold off measuring/showing until the delay has elapsed
|
|
661
|
+
if (!digiaAnchorRegistry.isRegistered(step.anchorKey)) {
|
|
662
|
+
// eslint-disable-next-line no-console
|
|
663
|
+
console.warn(`[Digia] campaign dropped — anchor_key "${step.anchorKey}" is not registered on this screen (campaign_key=${request.campaignKey}, step=${stepIndex})`);
|
|
664
|
+
digiaHealthReporter.report(HealthEventType.anchor_not_on_screen, {
|
|
665
|
+
campaign_key: request.campaignKey,
|
|
666
|
+
reason: 'anchor_key_not_registered',
|
|
667
|
+
anchor_key: step.anchorKey,
|
|
668
|
+
step_index: stepIndex,
|
|
669
|
+
});
|
|
670
|
+
digiaGuideController.cancel(request.payloadId);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
635
673
|
let skipCached = false;
|
|
636
674
|
const unsub = digiaAnchorRegistry.subscribe(step.anchorKey, (l) => {
|
|
637
675
|
if (!skipCached) return;
|
|
676
|
+
if (l.width === 0 || l.height === 0) {
|
|
677
|
+
digiaGuideController.cancel(request.payloadId);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
638
680
|
const { width: screenW, height: screenH } = Dimensions.get('window');
|
|
639
681
|
if (l.pageY + l.height <= 0 || l.pageY >= screenH || l.pageX + l.width <= 0 || l.pageX >= screenW) {
|
|
640
682
|
digiaGuideController.cancel(request.payloadId);
|
|
@@ -811,9 +853,41 @@ function DigiaGuideRuntime() {
|
|
|
811
853
|
}
|
|
812
854
|
|
|
813
855
|
// ─── DigiaHost ────────────────────────────────────────────────────────────────
|
|
856
|
+
//
|
|
857
|
+
// Place once at the app root. Accepts optional children (wrap mode) or can be
|
|
858
|
+
// used standalone (<DigiaHost />) as a sibling alongside other root elements.
|
|
859
|
+
//
|
|
860
|
+
// Renders two overlays, both non-interactive (pointerEvents="none"):
|
|
861
|
+
// 1. JS guide / tooltip / spotlight renderer (DigiaGuideRuntime)
|
|
862
|
+
// 2. Native Compose overlay for dialogs / bottom sheets (DigiaHostView)
|
|
863
|
+
|
|
864
|
+
export function DigiaHost({ children }: { children?: React.ReactNode }) {
|
|
865
|
+
const overlay = (
|
|
866
|
+
<>
|
|
867
|
+
<DigiaGuideRuntime />
|
|
868
|
+
{NativeDigiaHostView && (
|
|
869
|
+
// The outer View with pointerEvents="none" is critical: RN's touch
|
|
870
|
+
// dispatch respects pointerEvents on plain Views even in bridgeless
|
|
871
|
+
// mode. Applying it directly on requireNativeComponent views is
|
|
872
|
+
// unreliable in New Architecture. The Compose dialogs/sheets inside
|
|
873
|
+
// render in their own Android PhoneWindow so they stay interactive.
|
|
874
|
+
<View style={StyleSheet.absoluteFillObject} pointerEvents="none">
|
|
875
|
+
<NativeDigiaHostView style={StyleSheet.absoluteFillObject} />
|
|
876
|
+
</View>
|
|
877
|
+
)}
|
|
878
|
+
</>
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
if (children != null) {
|
|
882
|
+
return (
|
|
883
|
+
<>
|
|
884
|
+
{children}
|
|
885
|
+
{overlay}
|
|
886
|
+
</>
|
|
887
|
+
);
|
|
888
|
+
}
|
|
814
889
|
|
|
815
|
-
|
|
816
|
-
return <DigiaGuideRuntime />;
|
|
890
|
+
return overlay;
|
|
817
891
|
}
|
|
818
892
|
|
|
819
893
|
// ─── Styles ───────────────────────────────────────────────────────────────────
|
package/src/NativeDigiaEngage.ts
CHANGED
|
@@ -55,6 +55,22 @@ export interface Spec extends TurboModule {
|
|
|
55
55
|
|
|
56
56
|
/** Return all registered components (anchors/slots) for health reporting. */
|
|
57
57
|
getRegisteredComponents(): Promise<Array<{ component_key: string; component_type: 'anchor' | 'slot'; screen_name: string | null }>>;
|
|
58
|
+
|
|
59
|
+
/** Set the authenticated user ID for analytics identity stitching. */
|
|
60
|
+
setUserId(userId: string): void;
|
|
61
|
+
/** Clear the user ID (e.g. on logout); rotates the analytics session. */
|
|
62
|
+
clearUserId(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Record an analytics event from a JS-rendered campaign (guide/tooltip/spotlight).
|
|
65
|
+
* Native campaigns (nudge, inline, survey) are tracked automatically by the SDK.
|
|
66
|
+
*/
|
|
67
|
+
trackEvent(
|
|
68
|
+
eventType: string,
|
|
69
|
+
campaignId: string,
|
|
70
|
+
campaignKey: string,
|
|
71
|
+
campaignType: string,
|
|
72
|
+
elementId?: string | null,
|
|
73
|
+
): void;
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
let _resolved: Spec | null = null;
|
|
@@ -89,5 +105,9 @@ export const nativeDigiaModule: Spec = {
|
|
|
89
105
|
registerAnchor: (key, x, y, width, height) => getModule()?.registerAnchor(key, x, y, width, height),
|
|
90
106
|
unregisterAnchor: (key) => getModule()?.unregisterAnchor(key),
|
|
91
107
|
getRegisteredComponents: () => getModule()?.getRegisteredComponents() ?? Promise.resolve([]),
|
|
108
|
+
setUserId: (userId) => getModule()?.setUserId(userId),
|
|
109
|
+
clearUserId: () => getModule()?.clearUserId(),
|
|
110
|
+
trackEvent: (eventType, campaignId, campaignKey, campaignType, elementId) =>
|
|
111
|
+
getModule()?.trackEvent(eventType, campaignId, campaignKey, campaignType, elementId),
|
|
92
112
|
getConstants: () => getModule()?.getConstants?.() ?? {},
|
|
93
113
|
};
|
|
@@ -38,5 +38,7 @@ const remeasure = (key: string) => {
|
|
|
38
38
|
_measureCallbacks.get(key)?.()
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const isRegistered = (key: string): boolean => _measureCallbacks.has(key)
|
|
42
|
+
|
|
41
43
|
export type { AnchorLayout }
|
|
42
|
-
export const digiaAnchorRegistry = { setLayout, getLayout, subscribe, remove, registerMeasure, unregisterMeasure, remeasure }
|
|
44
|
+
export const digiaAnchorRegistry = { setLayout, getLayout, subscribe, remove, registerMeasure, unregisterMeasure, remeasure, isRegistered }
|
package/src/frequencyStore.ts
CHANGED
|
@@ -36,18 +36,18 @@ const _sessionStore = new Map<string, FrequencyState>();
|
|
|
36
36
|
const storeKey = (campaignKey: string) => `digia:freq:${campaignKey}`;
|
|
37
37
|
|
|
38
38
|
export const frequencyStore = {
|
|
39
|
-
async
|
|
39
|
+
async checkApiKey(apiKey: string): Promise<void> {
|
|
40
40
|
const storage = getStorage();
|
|
41
41
|
if (!storage) return;
|
|
42
42
|
try {
|
|
43
43
|
const stored = await storage.getItem(STORE_META_KEY);
|
|
44
|
-
const meta = stored ? (JSON.parse(stored) as {
|
|
45
|
-
if (meta && meta.
|
|
44
|
+
const meta = stored ? (JSON.parse(stored) as { apiKey: string }) : null;
|
|
45
|
+
if (meta && meta.apiKey !== apiKey) {
|
|
46
46
|
const keys = await storage.getAllKeys();
|
|
47
47
|
const digiaKeys = keys.filter((k) => k.startsWith('digia:freq:'));
|
|
48
48
|
if (digiaKeys.length > 0) await storage.multiRemove([...digiaKeys]);
|
|
49
49
|
}
|
|
50
|
-
await storage.setItem(STORE_META_KEY, JSON.stringify({
|
|
50
|
+
await storage.setItem(STORE_META_KEY, JSON.stringify({ apiKey }));
|
|
51
51
|
} catch {
|
|
52
52
|
// non-fatal
|
|
53
53
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
* React Native bridge for the Digia Engage SDK.
|
|
5
5
|
*
|
|
6
6
|
* The SDK surfaces Digia Compose UI inside React Native via:
|
|
7
|
-
* • `Digia`
|
|
8
|
-
* • `
|
|
9
|
-
*
|
|
7
|
+
* • `Digia` – SDK lifecycle (initialize, setCurrentScreen)
|
|
8
|
+
* • `DigiaHost` – Place once at the app root. Hosts JS guide/tooltip overlays
|
|
9
|
+
* and the native Compose overlay for dialogs/bottom-sheets.
|
|
10
|
+
* Use as <DigiaHost /> standalone or <DigiaHost>{children}</DigiaHost>.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
export { Digia } from './Digia';
|
|
13
|
-
export { DigiaHostView } from './DigiaHostView';
|
|
14
14
|
export { DigiaHost } from './DigiaProvider';
|
|
15
15
|
export { DigiaSlotView } from './DigiaSlotView';
|
|
16
16
|
export { DigiaAnchorView } from './DigiaAnchorView';
|
|
17
17
|
export type { DigiaAnchorViewRef } from './DigiaAnchorView';
|
|
18
|
-
export type { ActionContext, ActionResult, CampaignType, DigiaAction, DigiaConfig, DigiaDelegate, DigiaExperienceEvent, DigiaPlugin, InAppBrowserAdapter,
|
|
18
|
+
export type { ActionContext, ActionResult, CEPTriggerPayload, CampaignType, DigiaAction, DigiaConfig, DigiaDelegate, DigiaExperienceEvent, DigiaPlugin, InAppBrowserAdapter, OnAction } from './types';
|
|
19
19
|
export { defaultInAppBrowser } from './defaultInAppBrowser';
|
|
20
20
|
export { DigiaHealthReporter, HealthEventType, digiaHealthReporter } from './DigiaHealthReporter';
|
|
21
21
|
export type {
|
package/src/templateTypes.ts
CHANGED
|
@@ -120,4 +120,34 @@ export type SurveyTemplateConfig = {
|
|
|
120
120
|
rootNodeId: string
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
export type
|
|
123
|
+
export type NudgeContainerConfig = {
|
|
124
|
+
bgColor?: string
|
|
125
|
+
cornerRadius?: number
|
|
126
|
+
padding?: number
|
|
127
|
+
dismissOnOutsideTap?: boolean
|
|
128
|
+
scrimColor?: string
|
|
129
|
+
/** Bottom-sheet only: max height as a fraction of screen height. */
|
|
130
|
+
maxHeightRatio?: number
|
|
131
|
+
/** Bottom-sheet only: show the drag handle + enable drag-to-dismiss. */
|
|
132
|
+
dragHandle?: boolean
|
|
133
|
+
/** Dialog only: width in dp. */
|
|
134
|
+
width?: number
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* BottomSheet / dialog nudge. `layout` is the native DUI VWData tree (root
|
|
139
|
+
* `digia/column`), parsed and rendered entirely by the native SDK — JS does not
|
|
140
|
+
* render nudges; they are forwarded to the native bridge via triggerCampaign.
|
|
141
|
+
*/
|
|
142
|
+
export type NudgeConfig = {
|
|
143
|
+
templateType: 'bottomSheet' | 'dialog'
|
|
144
|
+
container: NudgeContainerConfig
|
|
145
|
+
layout: Record<string, unknown>
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type TemplateConfig =
|
|
149
|
+
| TooltipConfig
|
|
150
|
+
| SpotlightConfig
|
|
151
|
+
| CarouselConfig
|
|
152
|
+
| SurveyTemplateConfig
|
|
153
|
+
| NudgeConfig
|
package/src/types.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* The translation contract between a CEP plugin and Digia's rendering engine.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Plugin authors map their CEP's native callback into this struct.
|
|
5
|
+
* Mirrors CEPTriggerPayload on Android / Flutter — Digia core never imports
|
|
6
|
+
* CleverTap, MoEngage, or WebEngage types directly.
|
|
5
7
|
*/
|
|
6
|
-
export interface
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
/** CEP
|
|
12
|
-
|
|
8
|
+
export interface CEPTriggerPayload {
|
|
9
|
+
/** The CEP's own identifier for this campaign instance. Opaque to Digia — passed through for analytics correlation. */
|
|
10
|
+
cepCampaignId: string;
|
|
11
|
+
/** Additional metadata the CEP passes through (UTM params, user segment, CEP-specific tracking fields). Forwarded as-is in ExperienceEvents. */
|
|
12
|
+
cepMetadata: Record<string, unknown>;
|
|
13
|
+
/** The coupling key linking this CEP campaign to a Digia campaign. Used to look up the matching campaign in the store. */
|
|
14
|
+
campaignKey: string;
|
|
15
|
+
/** Optional runtime variables to interpolate into the campaign config. Keys must match variable placeholders in the Digia dashboard. */
|
|
16
|
+
variables?: Record<string, string>;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export type CampaignType = 'nudge' | 'guide' | 'inline' | 'survey';
|
|
@@ -70,7 +74,7 @@ export type GuideLifecycleEvent =
|
|
|
70
74
|
*/
|
|
71
75
|
export interface DigiaDelegate {
|
|
72
76
|
/** Deliver a campaign payload into the Digia rendering engine. */
|
|
73
|
-
onCampaignTriggered(payload:
|
|
77
|
+
onCampaignTriggered(payload: CEPTriggerPayload): void | Promise<void>;
|
|
74
78
|
/** Invalidate / dismiss a campaign by its ID. */
|
|
75
79
|
onCampaignInvalidated(campaignId: string): void;
|
|
76
80
|
}
|
|
@@ -90,7 +94,7 @@ export interface DigiaPlugin {
|
|
|
90
94
|
* (impressed / clicked / dismissed). Plugins use this to report
|
|
91
95
|
* analytics back to their CEP platform.
|
|
92
96
|
*/
|
|
93
|
-
notifyEvent(event: DigiaExperienceEvent, payload:
|
|
97
|
+
notifyEvent(event: DigiaExperienceEvent, payload: CEPTriggerPayload): void;
|
|
94
98
|
/**
|
|
95
99
|
* Called by the Digia SDK to record a named analytics event with properties.
|
|
96
100
|
* Implement this to forward Digia lifecycle events (e.g. "Digia Experience Viewed")
|
|
@@ -170,8 +174,8 @@ export interface FrequencyEvalResult {
|
|
|
170
174
|
* Configuration for initialising the Digia Engage SDK.
|
|
171
175
|
*/
|
|
172
176
|
export interface DigiaConfig {
|
|
173
|
-
/** The Engage
|
|
174
|
-
|
|
177
|
+
/** The Engage API key — sent as x-digia-project-id on all SDK requests. */
|
|
178
|
+
apiKey: string;
|
|
175
179
|
/**
|
|
176
180
|
* Base URL for the Digia API.
|
|
177
181
|
* Defaults to the production API root, or the Engage sandbox root when
|