@amplytools/react-native-amply-sdk 0.4.1 → 0.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/AmplyReactNative.podspec +1 -1
- package/android/build.gradle +1 -1
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +38 -27
- package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +13 -12
- package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +195 -65
- package/android/src/main/java/tools/amply/sdk/reactnative/core/GateDecision.kt +23 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/CampaignPresentPayload.kt +4 -2
- package/android/src/newarch/java/tools/amply/sdk/reactnative/NativeAmplyModuleSpec.java +2 -2
- package/android/src/newarch/jni/AmplyReactNative-generated.cpp +6 -6
- package/dist/src/__tests__/campaignResolved.test.js +115 -56
- package/dist/src/index.d.ts +61 -37
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +79 -43
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +50 -24
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ios/Sources/AmplyReactNative/AmplyModule.mm +277 -83
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +6 -6
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +7 -5
- package/package.json +1 -1
- package/src/__tests__/campaignResolved.test.ts +169 -73
- package/src/index.ts +133 -92
- package/src/nativeSpecs/NativeAmplyModule.ts +48 -24
|
@@ -11,12 +11,12 @@ const mockNativeModule = {
|
|
|
11
11
|
initialize: jest.fn(),
|
|
12
12
|
isInitialized: jest.fn(() => true),
|
|
13
13
|
track: jest.fn(),
|
|
14
|
-
|
|
14
|
+
trackGated: jest.fn(),
|
|
15
15
|
resolveCampaign: jest.fn(),
|
|
16
16
|
getRecentEvents: jest.fn(),
|
|
17
17
|
getDataSetSnapshot: jest.fn(),
|
|
18
18
|
registerDeepLinkListener: jest.fn(),
|
|
19
|
-
|
|
19
|
+
registerGate: jest.fn(),
|
|
20
20
|
addListener: jest.fn(),
|
|
21
21
|
removeListeners: jest.fn(),
|
|
22
22
|
onSystemEvent: jest.fn(),
|
|
@@ -27,97 +27,129 @@ const mockNativeModule = {
|
|
|
27
27
|
removeCustomProperty: jest.fn(),
|
|
28
28
|
clearCustomProperties: jest.fn(),
|
|
29
29
|
};
|
|
30
|
-
describe('Amply
|
|
30
|
+
describe('Amply gate API (trackGated + registerGate)', () => {
|
|
31
31
|
afterEach(() => {
|
|
32
32
|
jest.clearAllMocks();
|
|
33
|
+
Amply.removeAllListeners();
|
|
33
34
|
__setNativeModule(null);
|
|
34
35
|
});
|
|
35
36
|
beforeEach(() => {
|
|
36
37
|
__setNativeModule(mockNativeModule);
|
|
37
38
|
});
|
|
38
|
-
describe('trackEvent
|
|
39
|
-
it('
|
|
39
|
+
describe('trackEvent (fire-and-forget)', () => {
|
|
40
|
+
it('forwards to native track and never gates', async () => {
|
|
40
41
|
await Amply.trackEvent('SaveTapped', { foo: 'bar' });
|
|
41
42
|
expect(mockNativeModule.track).toHaveBeenCalledWith({
|
|
42
43
|
name: 'SaveTapped',
|
|
43
44
|
properties: { foo: 'bar' },
|
|
44
45
|
});
|
|
45
|
-
expect(mockNativeModule.
|
|
46
|
+
expect(mockNativeModule.trackGated).not.toHaveBeenCalled();
|
|
46
47
|
});
|
|
47
|
-
it('
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
it('defaults missing properties to an empty object', async () => {
|
|
49
|
+
await Amply.trackEvent('Ping');
|
|
50
|
+
expect(mockNativeModule.track).toHaveBeenCalledWith({
|
|
51
|
+
name: 'Ping',
|
|
52
|
+
properties: {},
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('trackGated', () => {
|
|
57
|
+
it('resolves proceed/completed when the gate is satisfied', async () => {
|
|
58
|
+
mockNativeModule.trackGated.mockResolvedValueOnce({
|
|
59
|
+
outcome: 'proceed',
|
|
60
|
+
reason: 'completed',
|
|
61
|
+
});
|
|
62
|
+
const decision = await Amply.trackGated('SaveTapped', { foo: 'bar' });
|
|
63
|
+
expect(mockNativeModule.trackGated).toHaveBeenCalledWith('SaveTapped', {
|
|
53
64
|
foo: 'bar',
|
|
54
65
|
});
|
|
55
|
-
expect(
|
|
56
|
-
|
|
66
|
+
expect(decision).toEqual({ outcome: 'proceed', reason: 'completed' });
|
|
67
|
+
});
|
|
68
|
+
it('resolves proceed/failOpen when the native layer reports fail-open', async () => {
|
|
69
|
+
mockNativeModule.trackGated.mockResolvedValueOnce({
|
|
70
|
+
outcome: 'proceed',
|
|
71
|
+
reason: 'failOpen',
|
|
72
|
+
});
|
|
73
|
+
const decision = await Amply.trackGated('SaveTapped', {});
|
|
74
|
+
expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
|
|
57
75
|
});
|
|
58
|
-
it('
|
|
59
|
-
mockNativeModule.
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
expect(mockNativeModule.trackEventGated).toHaveBeenCalledWith('SaveTapped', {});
|
|
64
|
-
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
65
|
-
expect(onProceed).not.toHaveBeenCalled();
|
|
76
|
+
it('resolves cancelled on a user dismiss under onAbort=cancel', async () => {
|
|
77
|
+
mockNativeModule.trackGated.mockResolvedValueOnce({ outcome: 'cancelled' });
|
|
78
|
+
const decision = await Amply.trackGated('SaveTapped', undefined);
|
|
79
|
+
expect(mockNativeModule.trackGated).toHaveBeenCalledWith('SaveTapped', {});
|
|
80
|
+
expect(decision).toEqual({ outcome: 'cancelled' });
|
|
66
81
|
});
|
|
67
|
-
it('fails open to
|
|
68
|
-
mockNativeModule.
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
await Amply.trackEvent('SaveTapped', {}, { onProceed, onCancel });
|
|
72
|
-
expect(onProceed).toHaveBeenCalledTimes(1);
|
|
73
|
-
expect(onCancel).not.toHaveBeenCalled();
|
|
82
|
+
it('never rejects — fails open to proceed/failOpen when native rejects', async () => {
|
|
83
|
+
mockNativeModule.trackGated.mockRejectedValueOnce(new Error('boom'));
|
|
84
|
+
const decision = await Amply.trackGated('SaveTapped', {});
|
|
85
|
+
expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
|
|
74
86
|
});
|
|
75
|
-
it('
|
|
76
|
-
mockNativeModule.
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
it('fails open on an unexpected/malformed native decision', async () => {
|
|
88
|
+
mockNativeModule.trackGated.mockResolvedValueOnce({ outcome: 'wat' });
|
|
89
|
+
const decision = await Amply.trackGated('SaveTapped', {});
|
|
90
|
+
expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
|
|
91
|
+
});
|
|
92
|
+
it('normalizes an unknown proceed reason to failOpen', async () => {
|
|
93
|
+
mockNativeModule.trackGated.mockResolvedValueOnce({
|
|
94
|
+
outcome: 'proceed',
|
|
95
|
+
reason: 'something-else',
|
|
96
|
+
});
|
|
97
|
+
const decision = await Amply.trackGated('SaveTapped', {});
|
|
98
|
+
expect(decision).toEqual({ outcome: 'proceed', reason: 'failOpen' });
|
|
82
99
|
});
|
|
83
100
|
});
|
|
84
|
-
describe('
|
|
85
|
-
it('registers once on the native module and subscribes to onCampaignPresent', async () => {
|
|
101
|
+
describe('registerGate', () => {
|
|
102
|
+
it('registers once on the native module per baseUrl and subscribes to onCampaignPresent', async () => {
|
|
86
103
|
const remove = jest.fn();
|
|
87
104
|
mockNativeModule.onCampaignPresent.mockReturnValue({ remove });
|
|
88
|
-
const presenter = jest.fn(
|
|
89
|
-
const unsubscribe = await Amply.
|
|
90
|
-
expect(mockNativeModule.
|
|
105
|
+
const presenter = jest.fn();
|
|
106
|
+
const unsubscribe = await Amply.registerGate('stillframe://ad', presenter);
|
|
107
|
+
expect(mockNativeModule.registerGate).toHaveBeenCalledTimes(1);
|
|
108
|
+
expect(mockNativeModule.registerGate).toHaveBeenCalledWith('stillframe://ad', 'cancel', 0);
|
|
91
109
|
expect(mockNativeModule.onCampaignPresent).toHaveBeenCalledTimes(1);
|
|
110
|
+
// Second registration of the same baseUrl re-subscribes but does NOT re-register.
|
|
111
|
+
await Amply.registerGate('stillframe://ad', jest.fn());
|
|
112
|
+
expect(mockNativeModule.registerGate).toHaveBeenCalledTimes(1);
|
|
92
113
|
unsubscribe();
|
|
93
114
|
expect(remove).toHaveBeenCalledTimes(1);
|
|
94
115
|
});
|
|
95
|
-
it('
|
|
116
|
+
it('passes through onAbort and timeoutMs options', async () => {
|
|
117
|
+
mockNativeModule.onCampaignPresent.mockReturnValue({ remove: jest.fn() });
|
|
118
|
+
await Amply.registerGate('stillframe://ad', jest.fn(), {
|
|
119
|
+
onAbort: 'proceed',
|
|
120
|
+
timeoutMs: 8000,
|
|
121
|
+
});
|
|
122
|
+
expect(mockNativeModule.registerGate).toHaveBeenCalledWith('stillframe://ad', 'proceed', 8000);
|
|
123
|
+
});
|
|
124
|
+
it('delivers params + info to the presenter and forwards completed', async () => {
|
|
96
125
|
let emit;
|
|
97
126
|
mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
|
|
98
127
|
emit = listener;
|
|
99
128
|
return { remove: jest.fn() };
|
|
100
129
|
});
|
|
101
|
-
const presenter = jest.fn(() =>
|
|
102
|
-
await Amply.
|
|
130
|
+
const presenter = jest.fn((_params, _info, resolution) => resolution.completed());
|
|
131
|
+
await Amply.registerGate('stillframe://ad', presenter);
|
|
103
132
|
expect(emit).toBeDefined();
|
|
104
|
-
emit({
|
|
105
|
-
|
|
133
|
+
emit({
|
|
134
|
+
url: 'stillframe://ad?adUnit=rewarded_main',
|
|
135
|
+
params: { adUnit: 'rewarded_main' },
|
|
136
|
+
info: { campaignId: 'c1' },
|
|
137
|
+
mediationId: 'm1',
|
|
138
|
+
});
|
|
106
139
|
await flushMicrotasks();
|
|
107
|
-
expect(presenter).toHaveBeenCalledWith('
|
|
108
|
-
expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m1', '
|
|
140
|
+
expect(presenter).toHaveBeenCalledWith({ adUnit: 'rewarded_main' }, { campaignId: 'c1' }, expect.any(Object));
|
|
141
|
+
expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m1', 'Completed');
|
|
109
142
|
});
|
|
110
|
-
it('forwards a
|
|
143
|
+
it('forwards a dismissed resolution', async () => {
|
|
111
144
|
let emit;
|
|
112
145
|
mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
|
|
113
146
|
emit = listener;
|
|
114
147
|
return { remove: jest.fn() };
|
|
115
148
|
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
emit({ url: 'stillframe://ad', info: {}, mediationId: 'm2' });
|
|
149
|
+
await Amply.registerGate('stillframe://ad', (_p, _i, resolution) => resolution.dismissed());
|
|
150
|
+
emit({ url: 'stillframe://ad', params: {}, info: {}, mediationId: 'm2' });
|
|
119
151
|
await flushMicrotasks();
|
|
120
|
-
expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m2', '
|
|
152
|
+
expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m2', 'Dismissed');
|
|
121
153
|
});
|
|
122
154
|
it('reports Unavailable (fail-open) when the presenter throws', async () => {
|
|
123
155
|
let emit;
|
|
@@ -125,13 +157,40 @@ describe('Amply CampaignResolved present/resolve/result API', () => {
|
|
|
125
157
|
emit = listener;
|
|
126
158
|
return { remove: jest.fn() };
|
|
127
159
|
});
|
|
128
|
-
|
|
160
|
+
await Amply.registerGate('stillframe://ad', () => {
|
|
129
161
|
throw new Error('presenter blew up');
|
|
130
162
|
});
|
|
131
|
-
|
|
132
|
-
emit({ url: 'stillframe://ad', info: {}, mediationId: 'm3' });
|
|
163
|
+
emit({ url: 'stillframe://ad', params: {}, info: {}, mediationId: 'm3' });
|
|
133
164
|
await flushMicrotasks();
|
|
134
165
|
expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m3', 'Unavailable');
|
|
135
166
|
});
|
|
167
|
+
it('resolves at most once even if the presenter reports twice', async () => {
|
|
168
|
+
let emit;
|
|
169
|
+
mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
|
|
170
|
+
emit = listener;
|
|
171
|
+
return { remove: jest.fn() };
|
|
172
|
+
});
|
|
173
|
+
await Amply.registerGate('stillframe://ad', (_p, _i, resolution) => {
|
|
174
|
+
resolution.completed();
|
|
175
|
+
resolution.dismissed();
|
|
176
|
+
});
|
|
177
|
+
emit({ url: 'stillframe://ad', params: {}, info: {}, mediationId: 'm4' });
|
|
178
|
+
await flushMicrotasks();
|
|
179
|
+
expect(mockNativeModule.resolveCampaign).toHaveBeenCalledTimes(1);
|
|
180
|
+
expect(mockNativeModule.resolveCampaign).toHaveBeenCalledWith('m4', 'Completed');
|
|
181
|
+
});
|
|
182
|
+
it('ignores dispatches whose url does not match the registered baseUrl', async () => {
|
|
183
|
+
let emit;
|
|
184
|
+
mockNativeModule.onCampaignPresent.mockImplementation((listener) => {
|
|
185
|
+
emit = listener;
|
|
186
|
+
return { remove: jest.fn() };
|
|
187
|
+
});
|
|
188
|
+
const presenter = jest.fn();
|
|
189
|
+
await Amply.registerGate('stillframe://ad', presenter);
|
|
190
|
+
emit({ url: 'other://thing', params: {}, info: {}, mediationId: 'm5' });
|
|
191
|
+
await flushMicrotasks();
|
|
192
|
+
expect(presenter).not.toHaveBeenCalled();
|
|
193
|
+
expect(mockNativeModule.resolveCampaign).not.toHaveBeenCalled();
|
|
194
|
+
});
|
|
136
195
|
});
|
|
137
196
|
});
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { useAmplySystemEvents } from './hooks/useAmplySystemEvents';
|
|
2
2
|
export { formatSystemEventLabel } from './systemEventUtils';
|
|
3
3
|
export type { FormatOptions } from './systemEventUtils';
|
|
4
|
-
import type { CampaignResult, AmplyInitializationConfig, DataSetSnapshot, DataSetType, DeepLinkEvent, EventRecord, LogLevel, CampaignPresentEvent, TrackEventPayload } from './nativeSpecs/NativeAmplyModule';
|
|
4
|
+
import type { CampaignResult, GateDecision, AmplyInitializationConfig, DataSetSnapshot, DataSetType, DeepLinkEvent, EventRecord, LogLevel, CampaignPresentEvent, TrackEventPayload } from './nativeSpecs/NativeAmplyModule';
|
|
5
5
|
export declare function initialize(config: AmplyInitializationConfig): Promise<void>;
|
|
6
6
|
/**
|
|
7
7
|
* Set the user ID for analytics. Pass null to clear.
|
|
@@ -21,58 +21,81 @@ export declare function getLogLevel(): LogLevel;
|
|
|
21
21
|
export declare function isInitialized(): boolean;
|
|
22
22
|
export declare function track(payload: TrackEventPayload): Promise<void>;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* The raw campaign result is deliberately not exposed — app code must never branch
|
|
32
|
-
* on which action ran or how it resolved.
|
|
24
|
+
* Possible results a gate presenter can report. Re-exported from the
|
|
25
|
+
* native spec for app presenters.
|
|
26
|
+
*/
|
|
27
|
+
export type { CampaignResult, GateDecision };
|
|
28
|
+
/**
|
|
29
|
+
* Outcome of a gate presenter's presentation, reported back via {@link GateResolution}.
|
|
33
30
|
*/
|
|
34
|
-
export type
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
export type GatePresenterResult = CampaignResult;
|
|
32
|
+
/**
|
|
33
|
+
* Resolution token handed to a {@link GatePresenter}. The presenter MUST report
|
|
34
|
+
* exactly one terminal result. Convenience helpers map to the underlying
|
|
35
|
+
* {@link CampaignResult}.
|
|
36
|
+
*/
|
|
37
|
+
export type GateResolution = {
|
|
38
|
+
/** The gate condition was satisfied (e.g. rewarded ad watched to completion). */
|
|
39
|
+
completed: () => void;
|
|
40
|
+
/** The user deliberately dismissed without satisfying the condition. */
|
|
41
|
+
dismissed: () => void;
|
|
42
|
+
/** Any non-user failure (no-fill, not-ready, failed-to-show). Fails open. */
|
|
43
|
+
unavailable: () => void;
|
|
37
44
|
};
|
|
38
45
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
46
|
+
* A capability presenter for a gate (blocking) action, registered via
|
|
47
|
+
* {@link registerGate} and keyed by the gate's `baseUrl`. Receives the parsed query
|
|
48
|
+
* `params`, the enriched `info`, and a {@link GateResolution} token it must resolve
|
|
49
|
+
* exactly once. Throwing synchronously is treated as `unavailable` (fail-open).
|
|
41
50
|
*/
|
|
42
|
-
export type
|
|
51
|
+
export type GatePresenter = (params: Record<string, unknown>, info: Record<string, unknown>, resolution: GateResolution) => void;
|
|
43
52
|
/**
|
|
44
|
-
*
|
|
45
|
-
* URL semantics, NOT by event — the app is a registry of capabilities. Returns (or
|
|
46
|
-
* resolves) the terminal {@link CampaignResult}. Throwing / rejecting is treated as
|
|
47
|
-
* `'Unavailable'` (fail-open).
|
|
53
|
+
* Options for {@link registerGate}.
|
|
48
54
|
*/
|
|
49
|
-
export type
|
|
55
|
+
export type RegisterGateOptions = {
|
|
56
|
+
/**
|
|
57
|
+
* Abort policy when the user dismisses the gate without satisfying it.
|
|
58
|
+
* - `'cancel'` → {@link trackGated} resolves `{ outcome: 'cancelled' }`.
|
|
59
|
+
* - `'proceed'` → {@link trackGated} resolves `{ outcome: 'proceed', reason: 'failOpen' }`.
|
|
60
|
+
* Defaults to `'cancel'`.
|
|
61
|
+
*/
|
|
62
|
+
onAbort?: 'cancel' | 'proceed';
|
|
63
|
+
/**
|
|
64
|
+
* Max time (ms) the gate waits for a presenter result before failing open.
|
|
65
|
+
* Defaults to the SDK's gate default.
|
|
66
|
+
*/
|
|
67
|
+
timeoutMs?: number;
|
|
68
|
+
};
|
|
50
69
|
/**
|
|
51
70
|
* Fire-and-forget analytics track. Records the event and lets any matching NORMAL
|
|
52
|
-
* campaign action dispatch as usual. A
|
|
53
|
-
* there is no
|
|
71
|
+
* campaign action dispatch as usual. A gate action matched here will NOT block —
|
|
72
|
+
* there is no caller to gate. Use {@link trackGated} to await a gate.
|
|
54
73
|
*/
|
|
55
74
|
export declare function trackEvent(event: string, properties?: Record<string, unknown> | null): Promise<void>;
|
|
56
75
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* before resolving
|
|
60
|
-
* - proceed
|
|
61
|
-
* -
|
|
76
|
+
* Gated track. Records the event; if a gate action matches AND a presenter is
|
|
77
|
+
* registered for its `baseUrl`, the SDK dispatches the presentation and awaits its
|
|
78
|
+
* outcome before resolving:
|
|
79
|
+
* - `{ outcome: 'proceed', reason: 'completed' }` — the presenter satisfied the gate.
|
|
80
|
+
* - `{ outcome: 'proceed', reason: 'failOpen' }` — no gate matched, not initialized,
|
|
81
|
+
* timeout, unavailable, infra failure, or dismiss under `onAbort=proceed`.
|
|
82
|
+
* - `{ outcome: 'cancelled' }` — user dismiss under an `onAbort=cancel` gate.
|
|
62
83
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
84
|
+
* This NEVER rejects: any infra failure resolves to `{ outcome: 'proceed', reason:
|
|
85
|
+
* 'failOpen' }` so the caller is never stranded.
|
|
65
86
|
*/
|
|
66
|
-
export declare function
|
|
87
|
+
export declare function trackGated(event: string, properties?: Record<string, unknown> | null): Promise<GateDecision>;
|
|
67
88
|
/**
|
|
68
|
-
* Register a
|
|
69
|
-
* is invoked when a
|
|
70
|
-
*
|
|
71
|
-
*
|
|
89
|
+
* Register a gate presenter for a gate (blocking) action, keyed by its `baseUrl`.
|
|
90
|
+
* The presenter is invoked when a matching gate is dispatched; it receives the parsed
|
|
91
|
+
* query `params`, the enriched `info`, and a {@link GateResolution} it must resolve
|
|
92
|
+
* exactly once (`completed`/`dismissed`/`unavailable`). The SDK collapses multi-step
|
|
93
|
+
* ad lifecycles into the single result you report. A synchronous throw → `unavailable`
|
|
94
|
+
* (fail-open).
|
|
72
95
|
*
|
|
73
96
|
* @returns an unsubscribe function.
|
|
74
97
|
*/
|
|
75
|
-
export declare function
|
|
98
|
+
export declare function registerGate(baseUrl: string, presenter: GatePresenter, opts?: RegisterGateOptions): Promise<() => void>;
|
|
76
99
|
export declare function getRecentEvents(limit: number): Promise<EventRecord[]>;
|
|
77
100
|
export declare function getDataSetSnapshot(type: DataSetType): Promise<DataSetSnapshot>;
|
|
78
101
|
export declare function addDeepLinkListener(listener: (event: DeepLinkEvent) => void): Promise<() => void>;
|
|
@@ -118,7 +141,8 @@ declare const _default: {
|
|
|
118
141
|
isInitialized: typeof isInitialized;
|
|
119
142
|
track: typeof track;
|
|
120
143
|
trackEvent: typeof trackEvent;
|
|
121
|
-
|
|
144
|
+
trackGated: typeof trackGated;
|
|
145
|
+
registerGate: typeof registerGate;
|
|
122
146
|
getRecentEvents: typeof getRecentEvents;
|
|
123
147
|
getDataSetSnapshot: typeof getDataSetSnapshot;
|
|
124
148
|
addDeepLinkListener: typeof addDeepLinkListener;
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,oBAAoB,EAAC,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAC,sBAAsB,EAAC,MAAM,oBAAoB,CAAC;AAC1D,YAAY,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EACV,cAAc,EACd,yBAAyB,EACzB,eAAe,EACf,WAAW,EACX,aAAa,EACb,WAAW,EACX,QAAQ,EACR,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,iCAAiC,CAAC;AAgFzC,wBAAsB,UAAU,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMjF;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAErD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAKjD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAErE;AAED
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { useAmplySystemEvents } from './hooks/useAmplySystemEvents';
|
|
|
4
4
|
export { formatSystemEventLabel } from './systemEventUtils';
|
|
5
5
|
let deepLinkRegistered = false;
|
|
6
6
|
let debugLogListenerRegistered = false;
|
|
7
|
-
|
|
7
|
+
const registeredGates = new Set();
|
|
8
8
|
const deepLinkSubscriptions = new Set();
|
|
9
9
|
const campaignPresenterSubscriptions = new Set();
|
|
10
10
|
/**
|
|
@@ -109,36 +109,45 @@ export function isInitialized() {
|
|
|
109
109
|
export async function track(payload) {
|
|
110
110
|
await getNativeModule().track(payload);
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Fire-and-forget analytics track. Records the event and lets any matching NORMAL
|
|
114
|
+
* campaign action dispatch as usual. A gate action matched here will NOT block —
|
|
115
|
+
* there is no caller to gate. Use {@link trackGated} to await a gate.
|
|
116
|
+
*/
|
|
117
|
+
export async function trackEvent(event, properties) {
|
|
118
|
+
await getNativeModule().track({ name: event, properties: properties !== null && properties !== void 0 ? properties : {} });
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Gated track. Records the event; if a gate action matches AND a presenter is
|
|
122
|
+
* registered for its `baseUrl`, the SDK dispatches the presentation and awaits its
|
|
123
|
+
* outcome before resolving:
|
|
124
|
+
* - `{ outcome: 'proceed', reason: 'completed' }` — the presenter satisfied the gate.
|
|
125
|
+
* - `{ outcome: 'proceed', reason: 'failOpen' }` — no gate matched, not initialized,
|
|
126
|
+
* timeout, unavailable, infra failure, or dismiss under `onAbort=proceed`.
|
|
127
|
+
* - `{ outcome: 'cancelled' }` — user dismiss under an `onAbort=cancel` gate.
|
|
128
|
+
*
|
|
129
|
+
* This NEVER rejects: any infra failure resolves to `{ outcome: 'proceed', reason:
|
|
130
|
+
* 'failOpen' }` so the caller is never stranded.
|
|
131
|
+
*/
|
|
132
|
+
export async function trackGated(event, properties) {
|
|
114
133
|
const props = properties !== null && properties !== void 0 ? properties : {};
|
|
115
|
-
if (!continuation) {
|
|
116
|
-
await getNativeModule().track({ name: event, properties: props });
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
let decision;
|
|
120
134
|
try {
|
|
121
|
-
decision = await getNativeModule().
|
|
135
|
+
const decision = (await getNativeModule().trackGated(event, props));
|
|
136
|
+
if (decision && decision.outcome === 'cancelled') {
|
|
137
|
+
return { outcome: 'cancelled' };
|
|
138
|
+
}
|
|
139
|
+
if (decision && decision.outcome === 'proceed') {
|
|
140
|
+
const reason = decision.reason === 'completed' ? 'completed' : 'failOpen';
|
|
141
|
+
return { outcome: 'proceed', reason };
|
|
142
|
+
}
|
|
143
|
+
// Unexpected/malformed payload: fail open.
|
|
144
|
+
console.warn('[Amply] trackGated returned an unexpected decision; failing open', decision);
|
|
145
|
+
return { outcome: 'proceed', reason: 'failOpen' };
|
|
122
146
|
}
|
|
123
147
|
catch (error) {
|
|
124
|
-
// Fail open: any bridge/SDK failure must proceed, never strand the
|
|
125
|
-
console.warn('[Amply]
|
|
126
|
-
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (decision === 'cancel') {
|
|
130
|
-
// A deliberate user-dismiss under an onAbort=cancel action. Only the local-UI
|
|
131
|
-
// cleanup hook runs; the continuation is suppressed.
|
|
132
|
-
(_a = continuation.onCancel) === null || _a === void 0 ? void 0 : _a.call(continuation);
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
// 'complete' and any unexpected value fail open to onProceed.
|
|
136
|
-
continuation.onProceed();
|
|
137
|
-
}
|
|
138
|
-
async function ensureCampaignPresenterRegistration() {
|
|
139
|
-
if (!campaignPresenterRegistered) {
|
|
140
|
-
getNativeModule().registerCampaignPresenter();
|
|
141
|
-
campaignPresenterRegistered = true;
|
|
148
|
+
// Fail open: any bridge/SDK failure must proceed, never strand the caller.
|
|
149
|
+
console.warn('[Amply] trackGated failed; proceeding (fail-open)', error);
|
|
150
|
+
return { outcome: 'proceed', reason: 'failOpen' };
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
function trackCampaignPresenterSubscription(subscription) {
|
|
@@ -156,27 +165,52 @@ function trackCampaignPresenterSubscription(subscription) {
|
|
|
156
165
|
return unsubscribe;
|
|
157
166
|
}
|
|
158
167
|
/**
|
|
159
|
-
* Register a
|
|
160
|
-
* is invoked when a
|
|
161
|
-
*
|
|
162
|
-
*
|
|
168
|
+
* Register a gate presenter for a gate (blocking) action, keyed by its `baseUrl`.
|
|
169
|
+
* The presenter is invoked when a matching gate is dispatched; it receives the parsed
|
|
170
|
+
* query `params`, the enriched `info`, and a {@link GateResolution} it must resolve
|
|
171
|
+
* exactly once (`completed`/`dismissed`/`unavailable`). The SDK collapses multi-step
|
|
172
|
+
* ad lifecycles into the single result you report. A synchronous throw → `unavailable`
|
|
173
|
+
* (fail-open).
|
|
163
174
|
*
|
|
164
175
|
* @returns an unsubscribe function.
|
|
165
176
|
*/
|
|
166
|
-
export async function
|
|
167
|
-
|
|
177
|
+
export async function registerGate(baseUrl, presenter, opts) {
|
|
178
|
+
var _a, _b;
|
|
179
|
+
const onAbort = (_a = opts === null || opts === void 0 ? void 0 : opts.onAbort) !== null && _a !== void 0 ? _a : 'cancel';
|
|
180
|
+
const timeoutMs = (_b = opts === null || opts === void 0 ? void 0 : opts.timeoutMs) !== null && _b !== void 0 ? _b : 0;
|
|
181
|
+
if (!registeredGates.has(baseUrl)) {
|
|
182
|
+
getNativeModule().registerGate(baseUrl, onAbort, timeoutMs);
|
|
183
|
+
registeredGates.add(baseUrl);
|
|
184
|
+
}
|
|
168
185
|
const nativeModule = getNativeModule();
|
|
169
186
|
const subscription = nativeModule.onCampaignPresent((event) => {
|
|
170
|
-
const { url, info, mediationId } = event;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
187
|
+
const { url, params, info, mediationId } = event;
|
|
188
|
+
// The native side routes by baseUrl, but guard here too in case multiple gates
|
|
189
|
+
// share the single onCampaignPresent channel.
|
|
190
|
+
if (baseUrl && url && !url.startsWith(baseUrl)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
let settled = false;
|
|
194
|
+
const report = (result) => {
|
|
195
|
+
if (settled) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
settled = true;
|
|
174
199
|
nativeModule.resolveCampaign(mediationId, result);
|
|
175
|
-
}
|
|
200
|
+
};
|
|
201
|
+
const resolution = {
|
|
202
|
+
completed: () => report('Completed'),
|
|
203
|
+
dismissed: () => report('Dismissed'),
|
|
204
|
+
unavailable: () => report('Unavailable'),
|
|
205
|
+
};
|
|
206
|
+
try {
|
|
207
|
+
presenter((params !== null && params !== void 0 ? params : {}), info, resolution);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
176
210
|
// Presenter failure is an infra failure, never a user dismiss → Unavailable.
|
|
177
|
-
console.warn('[Amply]
|
|
178
|
-
|
|
179
|
-
}
|
|
211
|
+
console.warn('[Amply] gate presenter threw; reporting Unavailable (fail-open)', error);
|
|
212
|
+
report('Unavailable');
|
|
213
|
+
}
|
|
180
214
|
});
|
|
181
215
|
return trackCampaignPresenterSubscription(subscription);
|
|
182
216
|
}
|
|
@@ -248,10 +282,11 @@ export function removeAllListeners() {
|
|
|
248
282
|
unsubscribe();
|
|
249
283
|
}
|
|
250
284
|
catch (error) {
|
|
251
|
-
console.warn('[Amply] Failed to remove
|
|
285
|
+
console.warn('[Amply] Failed to remove gate presenter', error);
|
|
252
286
|
}
|
|
253
287
|
});
|
|
254
288
|
campaignPresenterSubscriptions.clear();
|
|
289
|
+
registeredGates.clear();
|
|
255
290
|
}
|
|
256
291
|
export function addSystemEventsListener(listener) {
|
|
257
292
|
return addSystemEventListener(listener);
|
|
@@ -264,7 +299,8 @@ export default {
|
|
|
264
299
|
isInitialized,
|
|
265
300
|
track,
|
|
266
301
|
trackEvent,
|
|
267
|
-
|
|
302
|
+
trackGated,
|
|
303
|
+
registerGate,
|
|
268
304
|
getRecentEvents,
|
|
269
305
|
getDataSetSnapshot,
|
|
270
306
|
addDeepLinkListener,
|