@digia-engage/core 2.4.0 → 2.5.1
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/android/build.gradle +1 -1
- package/ios/DigiaEngageModule.m +8 -0
- package/ios/DigiaModule.swift +47 -0
- package/lib/commonjs/Digia.js +25 -14
- package/lib/commonjs/Digia.js.map +1 -1
- package/lib/commonjs/DigiaGuideController.js +2 -0
- package/lib/commonjs/DigiaGuideController.js.map +1 -1
- package/lib/commonjs/DigiaProvider.js +9 -21
- package/lib/commonjs/DigiaProvider.js.map +1 -1
- package/lib/module/Digia.js +25 -14
- package/lib/module/Digia.js.map +1 -1
- package/lib/module/DigiaGuideController.js +2 -0
- package/lib/module/DigiaGuideController.js.map +1 -1
- package/lib/module/DigiaProvider.js +10 -23
- package/lib/module/DigiaProvider.js.map +1 -1
- package/lib/typescript/Digia.d.ts +3 -1
- package/lib/typescript/Digia.d.ts.map +1 -1
- package/lib/typescript/DigiaGuideController.d.ts +1 -1
- package/lib/typescript/DigiaGuideController.d.ts.map +1 -1
- package/lib/typescript/DigiaProvider.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +7 -2
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Digia.ts +23 -12
- package/src/DigiaGuideController.ts +3 -1
- package/src/DigiaProvider.tsx +9 -28
- package/src/types.ts +7 -2
package/package.json
CHANGED
package/src/Digia.ts
CHANGED
|
@@ -205,7 +205,7 @@ class DigiaClass implements DigiaDelegate {
|
|
|
205
205
|
// Mirrors DigiaCEPDelegate on Android.
|
|
206
206
|
// Forwards to the native DigiaCEPDelegate via the bridge.
|
|
207
207
|
|
|
208
|
-
async onCampaignTriggered(payload: CEPTriggerPayload): Promise<
|
|
208
|
+
async onCampaignTriggered(payload: CEPTriggerPayload): Promise<boolean> {
|
|
209
209
|
if (!this._nativeBridgeWired) {
|
|
210
210
|
digiaHealthReporter.report(HealthEventType.plugin_not_registered, { campaign_key: payload.campaignKey });
|
|
211
211
|
}
|
|
@@ -222,7 +222,7 @@ class DigiaClass implements DigiaDelegate {
|
|
|
222
222
|
const result = evaluate(policy, state, Date.now());
|
|
223
223
|
if (!result.allow) {
|
|
224
224
|
this._log(`frequency_capped campaign_key=${campaignKey} reason=${result.reason}`);
|
|
225
|
-
return;
|
|
225
|
+
return false;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
@@ -237,7 +237,7 @@ class DigiaClass implements DigiaDelegate {
|
|
|
237
237
|
this._emitSlotWidth(campaign);
|
|
238
238
|
}
|
|
239
239
|
nativeDigiaModule.triggerCampaign(cepCampaignId, bridgeContent, cepMetadata);
|
|
240
|
-
return;
|
|
240
|
+
return true;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
if (campaign?.campaign_type === 'guide') {
|
|
@@ -251,7 +251,7 @@ class DigiaClass implements DigiaDelegate {
|
|
|
251
251
|
campaign_key: campaignKey,
|
|
252
252
|
reason: 'guide_campaign_has_no_steps',
|
|
253
253
|
});
|
|
254
|
-
return;
|
|
254
|
+
return false;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
257
|
const firstAnchorKey = config.steps[0].anchorKey;
|
|
@@ -263,7 +263,7 @@ class DigiaClass implements DigiaDelegate {
|
|
|
263
263
|
reason: 'anchor_key_not_registered',
|
|
264
264
|
anchor_key: firstAnchorKey,
|
|
265
265
|
});
|
|
266
|
-
return;
|
|
266
|
+
return false;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
this._activePayloads.set(cepCampaignId, payload);
|
|
@@ -275,6 +275,10 @@ class DigiaClass implements DigiaDelegate {
|
|
|
275
275
|
variables,
|
|
276
276
|
config,
|
|
277
277
|
onExperienceEvent: (event) => this._onGuideLifecycleEvent(event, cepCampaignId, campaignKey, digiaId),
|
|
278
|
+
// Safety net: release the CEP slot on every guide exit, including
|
|
279
|
+
// CTA-close / advance-past-end / anchor-drop paths that emit no
|
|
280
|
+
// terminal lifecycle event. Idempotent with the explicit path below.
|
|
281
|
+
onEnd: () => this._releaseGuideSlot(cepCampaignId),
|
|
278
282
|
});
|
|
279
283
|
|
|
280
284
|
this._log(`guide trigger campaign_key=${campaignKey} mounted=${mounted}`);
|
|
@@ -284,8 +288,10 @@ class DigiaClass implements DigiaDelegate {
|
|
|
284
288
|
campaign_key: campaignKey,
|
|
285
289
|
payload_id: cepCampaignId,
|
|
286
290
|
});
|
|
291
|
+
this._activePayloads.delete(cepCampaignId);
|
|
292
|
+
return false;
|
|
287
293
|
}
|
|
288
|
-
return;
|
|
294
|
+
return true;
|
|
289
295
|
}
|
|
290
296
|
|
|
291
297
|
if (!campaign) {
|
|
@@ -295,11 +301,12 @@ class DigiaClass implements DigiaDelegate {
|
|
|
295
301
|
payload_id: cepCampaignId,
|
|
296
302
|
available_campaign_keys: [...this._campaignsByKey.keys()],
|
|
297
303
|
});
|
|
298
|
-
return;
|
|
304
|
+
return false;
|
|
299
305
|
}
|
|
300
306
|
|
|
301
307
|
this._activePayloads.set(cepCampaignId, payload);
|
|
302
308
|
nativeDigiaModule.triggerCampaign(cepCampaignId, bridgeContent, cepMetadata);
|
|
309
|
+
return true;
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
onCampaignInvalidated(campaignId: string): void {
|
|
@@ -404,14 +411,18 @@ class DigiaClass implements DigiaDelegate {
|
|
|
404
411
|
|
|
405
412
|
// Notify plugins of CEP lifecycle termination (template cleanup) on exit events.
|
|
406
413
|
if (event.type === 'dismissed' || event.type === 'completed') {
|
|
407
|
-
|
|
408
|
-
if (storedPayload) {
|
|
409
|
-
this._plugins.forEach((p) => p.notifyEvent({ type: 'dismissed' }, storedPayload));
|
|
410
|
-
this._activePayloads.delete(payloadId);
|
|
411
|
-
}
|
|
414
|
+
this._releaseGuideSlot(payloadId);
|
|
412
415
|
}
|
|
413
416
|
}
|
|
414
417
|
|
|
418
|
+
/** Releases the CEP in-app slot for a guide payload. Idempotent. */
|
|
419
|
+
private _releaseGuideSlot(payloadId: string): void {
|
|
420
|
+
const storedPayload = this._activePayloads.get(payloadId);
|
|
421
|
+
if (!storedPayload) return;
|
|
422
|
+
this._plugins.forEach((p) => p.notifyEvent({ type: 'dismissed' }, storedPayload));
|
|
423
|
+
this._activePayloads.delete(payloadId);
|
|
424
|
+
}
|
|
425
|
+
|
|
415
426
|
private _guideEventName(type: GuideLifecycleEvent['type']): string {
|
|
416
427
|
switch (type) {
|
|
417
428
|
case 'viewed': return 'Digia Experience Viewed';
|
|
@@ -5,11 +5,11 @@ import type { VariableMap } from './interpolate';
|
|
|
5
5
|
export interface DigiaGuideRequest {
|
|
6
6
|
payloadId: string;
|
|
7
7
|
campaignKey: string;
|
|
8
|
-
/** Digia backend UUID for this campaign (from the campaign store _id field). */
|
|
9
8
|
campaignId: string;
|
|
10
9
|
variables?: VariableMap;
|
|
11
10
|
config: TemplateConfig;
|
|
12
11
|
onExperienceEvent: (event: GuideLifecycleEvent) => void;
|
|
12
|
+
onEnd?: () => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
type DigiaGuideControllerEvent =
|
|
@@ -46,8 +46,10 @@ class DigiaGuideController {
|
|
|
46
46
|
cancel(payloadId: string): void {
|
|
47
47
|
this._queue = this._queue.filter(r => r.payloadId !== payloadId);
|
|
48
48
|
if (this._activeRequest?.payloadId === payloadId) {
|
|
49
|
+
const ended = this._activeRequest;
|
|
49
50
|
this._listener?.({ type: 'cancel', payloadId });
|
|
50
51
|
this._activeRequest = null;
|
|
52
|
+
ended.onEnd?.();
|
|
51
53
|
this._dispatchNext();
|
|
52
54
|
}
|
|
53
55
|
}
|
package/src/DigiaProvider.tsx
CHANGED
|
@@ -8,16 +8,9 @@ import {
|
|
|
8
8
|
StyleSheet,
|
|
9
9
|
Text,
|
|
10
10
|
View,
|
|
11
|
-
requireNativeComponent,
|
|
12
11
|
useWindowDimensions,
|
|
13
12
|
} from 'react-native';
|
|
14
13
|
|
|
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;
|
|
21
14
|
import { computePosition, flip, offset, shift } from '@floating-ui/core';
|
|
22
15
|
import Svg, { Path } from 'react-native-svg';
|
|
23
16
|
import { Digia } from './Digia';
|
|
@@ -857,37 +850,25 @@ function DigiaGuideRuntime() {
|
|
|
857
850
|
// Place once at the app root. Accepts optional children (wrap mode) or can be
|
|
858
851
|
// used standalone (<DigiaHost />) as a sibling alongside other root elements.
|
|
859
852
|
//
|
|
860
|
-
// Renders
|
|
861
|
-
//
|
|
862
|
-
//
|
|
853
|
+
// Renders ONLY the JS guide / tooltip / spotlight runtime (DigiaGuideRuntime).
|
|
854
|
+
// Native overlays (nudges, dialogs, bottom sheets, surveys) render through the
|
|
855
|
+
// single native overlay host that DigiaModule mounts imperatively after
|
|
856
|
+
// Digia.initialize() — that host owns its own touch handling and claims touches
|
|
857
|
+
// only while an overlay is active. Mounting a native host here as well would
|
|
858
|
+
// render every overlay twice (one stacked behind the other), and a
|
|
859
|
+
// React-tree host is not reliably touch-correct under the New Architecture.
|
|
863
860
|
|
|
864
861
|
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
862
|
if (children != null) {
|
|
882
863
|
return (
|
|
883
864
|
<>
|
|
884
865
|
{children}
|
|
885
|
-
|
|
866
|
+
<DigiaGuideRuntime />
|
|
886
867
|
</>
|
|
887
868
|
);
|
|
888
869
|
}
|
|
889
870
|
|
|
890
|
-
return
|
|
871
|
+
return <DigiaGuideRuntime />;
|
|
891
872
|
}
|
|
892
873
|
|
|
893
874
|
// ─── Styles ───────────────────────────────────────────────────────────────────
|
package/src/types.ts
CHANGED
|
@@ -73,8 +73,13 @@ export type GuideLifecycleEvent =
|
|
|
73
73
|
* Call these instead of touching Digia directly from inside a plugin.
|
|
74
74
|
*/
|
|
75
75
|
export interface DigiaDelegate {
|
|
76
|
-
/**
|
|
77
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Deliver a campaign payload into the Digia rendering engine.
|
|
78
|
+
* @returns `true` if accepted for display, `false` if dropped (frequency cap,
|
|
79
|
+
* missing anchor, unknown key, …). Plugins holding a display lock must
|
|
80
|
+
* release it on `false`.
|
|
81
|
+
*/
|
|
82
|
+
onCampaignTriggered(payload: CEPTriggerPayload): boolean | Promise<boolean>;
|
|
78
83
|
/** Invalidate / dismiss a campaign by its ID. */
|
|
79
84
|
onCampaignInvalidated(campaignId: string): void;
|
|
80
85
|
}
|