react-native-test-adx 0.1.7 → 0.1.9
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/lib/module/AdaxonSdk.js +35 -26
- package/lib/module/components/InterstitialAd.js +168 -46
- package/lib/module/config/ApiConstants.js +5 -2
- package/lib/module/core/AdaxonClient.js +2 -2
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/services/event/EventBuilder.js +5 -1
- package/lib/module/services/event/EventService.js +21 -7
- package/lib/module/validation/EventValidator.js +5 -2
- package/lib/typescript/src/AdaxonSdk.d.ts +1 -0
- package/lib/typescript/src/components/InterstitialAd.d.ts +2 -0
- package/lib/typescript/src/config/ApiConstants.d.ts +5 -2
- package/lib/typescript/src/core/AdaxonClient.d.ts +2 -2
- package/lib/typescript/src/sdkVersion.d.ts +1 -1
- package/lib/typescript/src/services/event/EventService.d.ts +3 -3
- package/lib/typescript/src/types/ApiTypes.d.ts +4 -0
- package/lib/typescript/src/types/EventTypes.d.ts +2 -0
- package/lib/typescript/src/validation/EventValidator.d.ts +1 -1
- package/package.json +1 -1
package/lib/module/AdaxonSdk.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { AdaxonClient } from "./core/AdaxonClient.js";
|
|
4
|
-
import {
|
|
5
|
-
import { dismissInterstitial, presentInterstitial } from "./components/InterstitialAd.js";
|
|
4
|
+
import { CREATIVE_CLICK_EVENT, CREATIVE_VIEW_EVENT, MEDIATION_PROVIDER, RECOMMENDATION_EVENT_TYPE } from "./config/ApiConstants.js";
|
|
5
|
+
import { dismissInterstitial, dismissOfferBrowser, presentInterstitial, presentOfferBrowser } from "./components/InterstitialAd.js";
|
|
6
6
|
import { AdaxonSdkError } from "./errors/AdaxonSdkError.js";
|
|
7
7
|
import { detectReactNativePlatform, reactNativeAppInfoReader, reactNativeDeviceReader, reactNativeNetworkInfoReader, reactNativeNetworkStatusReader, reactNativeUrlOpener } from "./platform/reactNative.js";
|
|
8
8
|
import { buildAddToCartProperties, buildPurchaseProperties, normalizeEventValues } from "./services/event/normalizeProperties.js";
|
|
9
9
|
import { AdxEvent } from "./services/event/predefined.js";
|
|
10
|
+
function openableOfferUrl(url) {
|
|
11
|
+
return url.replace(/[{}]/g, brace => brace === '{' ? '%7B' : '%7D');
|
|
12
|
+
}
|
|
10
13
|
export class AdaxonSdk {
|
|
11
14
|
static shared = new AdaxonSdk();
|
|
12
15
|
client = null;
|
|
@@ -70,7 +73,10 @@ export class AdaxonSdk {
|
|
|
70
73
|
}
|
|
71
74
|
async showAd(options = {}) {
|
|
72
75
|
try {
|
|
73
|
-
const
|
|
76
|
+
const {
|
|
77
|
+
offers,
|
|
78
|
+
requestEventId
|
|
79
|
+
} = await this.loadRecommendations();
|
|
74
80
|
if (offers.length === 0) {
|
|
75
81
|
console.warn('[Adaxon] No offer available.');
|
|
76
82
|
return {
|
|
@@ -91,10 +97,8 @@ export class AdaxonSdk {
|
|
|
91
97
|
const displayed = presentInterstitial({
|
|
92
98
|
offer,
|
|
93
99
|
interact: () => {
|
|
94
|
-
options.onOfferClicked?.(offer.offer_id)
|
|
95
|
-
this.openOfferUrl(offer).then(() => dismissInterstitial('click')).catch(error => {
|
|
100
|
+
this.openOfferUrl(offer, requestEventId).then(() => options.onOfferClicked?.(offer.offer_id)).catch(error => {
|
|
96
101
|
console.warn('[Adaxon] Failed to open offer URL.', AdaxonSdkError.messageOf(error, 'Failed to open offer.'));
|
|
97
|
-
dismissInterstitial('close');
|
|
98
102
|
});
|
|
99
103
|
},
|
|
100
104
|
onDismiss: dismissReason => {
|
|
@@ -111,7 +115,9 @@ export class AdaxonSdk {
|
|
|
111
115
|
shown: false,
|
|
112
116
|
reason: 'not_mounted'
|
|
113
117
|
});
|
|
118
|
+
return;
|
|
114
119
|
}
|
|
120
|
+
this.trackRecommendationEvent(CREATIVE_VIEW_EVENT, requestEventId).catch(() => undefined);
|
|
115
121
|
});
|
|
116
122
|
} catch (error) {
|
|
117
123
|
console.warn('[Adaxon] Failed to show ad.', AdaxonSdkError.messageOf(error, 'Failed to show ad.'));
|
|
@@ -129,33 +135,36 @@ export class AdaxonSdk {
|
|
|
129
135
|
}
|
|
130
136
|
destroy() {
|
|
131
137
|
dismissInterstitial();
|
|
138
|
+
dismissOfferBrowser();
|
|
132
139
|
this.client?.destroy();
|
|
133
140
|
this.client = null;
|
|
134
141
|
this.testMode = false;
|
|
135
142
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
...properties,
|
|
141
|
-
mediation_provider: MEDIATION_PROVIDER,
|
|
142
|
-
test_mode: this.testMode
|
|
143
|
-
}
|
|
143
|
+
loadRecommendations() {
|
|
144
|
+
return this.requireClient().getRecommendations({
|
|
145
|
+
mediation_provider: MEDIATION_PROVIDER,
|
|
146
|
+
test_mode: this.testMode
|
|
144
147
|
});
|
|
145
|
-
return response.offers;
|
|
146
148
|
}
|
|
147
|
-
async openOfferUrl(offer) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
async openOfferUrl(offer, parentEventId) {
|
|
150
|
+
await this.trackRecommendationEvent(CREATIVE_CLICK_EVENT, parentEventId);
|
|
151
|
+
const offerUrl = openableOfferUrl(offer.offer_url);
|
|
152
|
+
if (presentOfferBrowser(offerUrl)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
await reactNativeUrlOpener.openURL(offerUrl);
|
|
156
|
+
dismissInterstitial('click');
|
|
157
|
+
}
|
|
158
|
+
async trackRecommendationEvent(eventName, parentEventId) {
|
|
159
|
+
try {
|
|
160
|
+
await this.requireClient().track(eventName, {
|
|
161
|
+
parent_event_id: parentEventId
|
|
162
|
+
}, {
|
|
163
|
+
eventType: RECOMMENDATION_EVENT_TYPE
|
|
164
|
+
});
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.warn(`[Adaxon] Failed to log ${eventName}.`, AdaxonSdkError.messageOf(error, `Failed to log ${eventName}.`));
|
|
151
167
|
}
|
|
152
|
-
await reactNativeUrlOpener.openURL(offer.offer_url);
|
|
153
|
-
await client.track(OFFER_OPENED_EVENT, {
|
|
154
|
-
offer_id: offer.offer_id,
|
|
155
|
-
offer_name: offer.offer_name,
|
|
156
|
-
ad_type: offer.ad_type,
|
|
157
|
-
mediation_provider: MEDIATION_PROVIDER
|
|
158
|
-
});
|
|
159
168
|
}
|
|
160
169
|
requireClient() {
|
|
161
170
|
if (!this.client) {
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
-
import { BackHandler, Image, Platform, Pressable, SafeAreaView, StatusBar, StyleSheet, Text, View } from "../platform/reactNative.js";
|
|
4
|
+
import { BackHandler, Image, Platform, Pressable, SafeAreaView, StatusBar, StyleSheet, Text, View, reactNativeUrlOpener } from "../platform/reactNative.js";
|
|
5
5
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
6
6
|
const CLOSE_EDGE_INSET = 12;
|
|
7
7
|
const CLOSE_HIT_SIZE = 44;
|
|
8
|
+
function loadInAppBrowser() {
|
|
9
|
+
try {
|
|
10
|
+
const module = require('react-native-webview');
|
|
11
|
+
return module.WebView ?? null;
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const InAppBrowserView = loadInAppBrowser();
|
|
8
17
|
let listener = null;
|
|
9
18
|
let activeSession = null;
|
|
19
|
+
let browserListener = null;
|
|
20
|
+
let activeBrowserUrl = null;
|
|
21
|
+
export function presentOfferBrowser(url) {
|
|
22
|
+
if (!browserListener || !InAppBrowserView) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
activeBrowserUrl = url;
|
|
26
|
+
browserListener(url);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
export function dismissOfferBrowser() {
|
|
30
|
+
activeBrowserUrl = null;
|
|
31
|
+
browserListener?.(null);
|
|
32
|
+
}
|
|
10
33
|
export function presentInterstitial(session) {
|
|
11
34
|
if (!listener) {
|
|
12
35
|
return false;
|
|
@@ -51,83 +74,137 @@ function useTopInset() {
|
|
|
51
74
|
}
|
|
52
75
|
export function InterstitialAd() {
|
|
53
76
|
const [session, setSession] = useState(null);
|
|
77
|
+
const [browserUrl, setBrowserUrl] = useState(null);
|
|
78
|
+
const [offerPageVisible, setOfferPageVisible] = useState(false);
|
|
54
79
|
const [closeTone, setCloseTone] = useState('onLightSurface');
|
|
55
80
|
const safeArea = useSafeAreaInsets();
|
|
56
81
|
const topInset = useTopInset();
|
|
57
82
|
const offerId = session?.offer.offer_id;
|
|
58
83
|
useEffect(() => {
|
|
59
84
|
listener = setSession;
|
|
85
|
+
browserListener = setBrowserUrl;
|
|
86
|
+
setBrowserUrl(activeBrowserUrl);
|
|
60
87
|
return () => {
|
|
61
88
|
if (listener === setSession) {
|
|
62
89
|
listener = null;
|
|
63
90
|
}
|
|
91
|
+
if (browserListener === setBrowserUrl) {
|
|
92
|
+
browserListener = null;
|
|
93
|
+
}
|
|
64
94
|
};
|
|
65
95
|
}, []);
|
|
66
96
|
useEffect(() => {
|
|
67
97
|
setCloseTone('onLightSurface');
|
|
68
98
|
}, [offerId]);
|
|
69
99
|
useEffect(() => {
|
|
70
|
-
|
|
100
|
+
setOfferPageVisible(false);
|
|
101
|
+
}, [browserUrl]);
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (!session && !browserUrl) {
|
|
71
104
|
return;
|
|
72
105
|
}
|
|
73
106
|
const onBackPress = () => {
|
|
74
|
-
|
|
107
|
+
dismissOfferBrowser();
|
|
108
|
+
if (session && !offerPageVisible) {
|
|
109
|
+
dismissInterstitial('back');
|
|
110
|
+
}
|
|
75
111
|
return true;
|
|
76
112
|
};
|
|
77
113
|
const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
|
78
114
|
return () => subscription.remove();
|
|
79
|
-
}, [session]);
|
|
80
|
-
if (!session) {
|
|
115
|
+
}, [browserUrl, offerPageVisible, session]);
|
|
116
|
+
if (!session && !browserUrl) {
|
|
81
117
|
return null;
|
|
82
118
|
}
|
|
83
|
-
const {
|
|
84
|
-
offer,
|
|
85
|
-
interact
|
|
86
|
-
} = session;
|
|
87
119
|
const closeAppearance = closeButtonAppearance(closeTone);
|
|
88
120
|
const closeRightInset = Platform.OS === 'android' ? CLOSE_EDGE_INSET + safeArea.right : CLOSE_EDGE_INSET;
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
102
|
-
style: [styles.closeLabel, closeAppearance.label],
|
|
103
|
-
children: "\xD7"
|
|
104
|
-
})
|
|
105
|
-
}), /*#__PURE__*/_jsx(Pressable, {
|
|
106
|
-
accessibilityRole: "button",
|
|
107
|
-
accessibilityLabel: offer.offer_name,
|
|
108
|
-
onPress: interact,
|
|
109
|
-
style: styles.offer,
|
|
110
|
-
testID: "adaxon-interstitial-offer",
|
|
111
|
-
children: /*#__PURE__*/_jsx(OfferCreative, {
|
|
112
|
-
offer: offer,
|
|
113
|
-
onCloseToneChange: setCloseTone
|
|
114
|
-
})
|
|
115
|
-
})]
|
|
121
|
+
const closeButton = (label, onPress, testID) => /*#__PURE__*/_jsx(Pressable, {
|
|
122
|
+
accessibilityRole: "button",
|
|
123
|
+
accessibilityLabel: label,
|
|
124
|
+
onPress: onPress,
|
|
125
|
+
style: [styles.closeButton, closeAppearance.button, {
|
|
126
|
+
top: CLOSE_EDGE_INSET,
|
|
127
|
+
right: closeRightInset
|
|
128
|
+
}],
|
|
129
|
+
testID: testID,
|
|
130
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
131
|
+
style: [styles.closeLabel, closeAppearance.label],
|
|
132
|
+
children: "\xD7"
|
|
116
133
|
})
|
|
117
134
|
});
|
|
118
|
-
|
|
119
|
-
style: styles.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
135
|
+
const adFrame = session ? /*#__PURE__*/_jsxs(View, {
|
|
136
|
+
style: styles.adContainer,
|
|
137
|
+
children: [/*#__PURE__*/_jsx(Pressable, {
|
|
138
|
+
accessibilityRole: "button",
|
|
139
|
+
accessibilityLabel: session.offer.offer_name,
|
|
140
|
+
onPress: session.interact,
|
|
141
|
+
style: styles.offer,
|
|
142
|
+
testID: "adaxon-interstitial-offer",
|
|
143
|
+
children: /*#__PURE__*/_jsx(OfferCreative, {
|
|
144
|
+
offer: session.offer,
|
|
145
|
+
onCloseToneChange: setCloseTone
|
|
146
|
+
})
|
|
147
|
+
}), closeButton('Close ad', () => {
|
|
148
|
+
dismissOfferBrowser();
|
|
149
|
+
dismissInterstitial('close');
|
|
150
|
+
}, 'adaxon-interstitial-close')]
|
|
151
|
+
}) : null;
|
|
152
|
+
const onOfferNavigation = request => {
|
|
153
|
+
const externalUrl = externalAppUrl(request.url);
|
|
154
|
+
if (!externalUrl) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
reactNativeUrlOpener.openURL(externalUrl).catch(() => undefined);
|
|
158
|
+
dismissOfferBrowser();
|
|
159
|
+
dismissInterstitial('click');
|
|
160
|
+
return false;
|
|
161
|
+
};
|
|
162
|
+
const onOfferPage = state => {
|
|
163
|
+
if (state.loading || !state.url || !browserUrl) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
setOfferPageVisible(true);
|
|
167
|
+
dismissInterstitial('click');
|
|
168
|
+
};
|
|
169
|
+
const offerBrowser = InAppBrowserView && browserUrl ? /*#__PURE__*/_jsx(InAppBrowserView, {
|
|
170
|
+
source: {
|
|
171
|
+
uri: browserUrl
|
|
172
|
+
},
|
|
173
|
+
pointerEvents: offerPageVisible ? 'auto' : 'none',
|
|
174
|
+
style: offerPageVisible ? styles.browser : styles.hiddenBrowser,
|
|
175
|
+
originWhitelist: ['*'],
|
|
176
|
+
onShouldStartLoadWithRequest: onOfferNavigation,
|
|
177
|
+
onNavigationStateChange: onOfferPage,
|
|
178
|
+
testID: "adaxon-interstitial-browser"
|
|
179
|
+
}) : null;
|
|
180
|
+
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
181
|
+
children: [offerBrowser && !offerPageVisible ? offerBrowser : null, session ? /*#__PURE__*/_jsx(View, {
|
|
182
|
+
style: styles.overlay,
|
|
183
|
+
testID: "adaxon-interstitial",
|
|
184
|
+
children: framed(adFrame)
|
|
185
|
+
}) : null, offerBrowser && offerPageVisible ? /*#__PURE__*/_jsx(View, {
|
|
186
|
+
style: styles.overlay,
|
|
187
|
+
testID: "adaxon-offer-browser",
|
|
188
|
+
children: framed(/*#__PURE__*/_jsxs(View, {
|
|
189
|
+
style: styles.adContainer,
|
|
190
|
+
children: [offerBrowser, closeButton('Close offer', dismissOfferBrowser, 'adaxon-offer-close')]
|
|
191
|
+
}))
|
|
192
|
+
}) : null]
|
|
193
|
+
});
|
|
194
|
+
function framed(content) {
|
|
195
|
+
if (Platform.OS === 'ios') {
|
|
196
|
+
return /*#__PURE__*/_jsx(SafeAreaView, {
|
|
197
|
+
style: styles.safeFrame,
|
|
198
|
+
children: content
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return /*#__PURE__*/_jsx(View, {
|
|
125
202
|
style: [styles.safeFrame, {
|
|
126
203
|
paddingTop: topInset
|
|
127
204
|
}],
|
|
128
|
-
children:
|
|
129
|
-
})
|
|
130
|
-
}
|
|
205
|
+
children: content
|
|
206
|
+
});
|
|
207
|
+
}
|
|
131
208
|
}
|
|
132
209
|
function OfferCreative({
|
|
133
210
|
offer,
|
|
@@ -191,6 +268,37 @@ function closeButtonAppearance(tone) {
|
|
|
191
268
|
}
|
|
192
269
|
};
|
|
193
270
|
}
|
|
271
|
+
function externalAppUrl(url) {
|
|
272
|
+
const trimmed = url.trim();
|
|
273
|
+
if (!trimmed || trimmed === 'about:blank') {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
let protocol = '';
|
|
277
|
+
try {
|
|
278
|
+
protocol = new URL(trimmed).protocol;
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
if (protocol === 'http:' || protocol === 'https:' || protocol === 'about:' || protocol === 'blob:' || protocol === 'data:') {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
if (protocol === 'intent:') {
|
|
286
|
+
return intentTarget(trimmed);
|
|
287
|
+
}
|
|
288
|
+
return trimmed;
|
|
289
|
+
}
|
|
290
|
+
function intentTarget(url) {
|
|
291
|
+
const fallback = /S\.browser_fallback_url=([^;]+)/.exec(url)?.[1];
|
|
292
|
+
if (fallback) {
|
|
293
|
+
return decodeURIComponent(fallback);
|
|
294
|
+
}
|
|
295
|
+
const scheme = /scheme=([^;]+)/.exec(url)?.[1];
|
|
296
|
+
const path = url.slice('intent:'.length).split('#')[0];
|
|
297
|
+
if (!scheme || !path) {
|
|
298
|
+
return url;
|
|
299
|
+
}
|
|
300
|
+
return `${scheme}:${path}`;
|
|
301
|
+
}
|
|
194
302
|
function remoteImageUrl(value) {
|
|
195
303
|
const trimmed = value?.trim();
|
|
196
304
|
if (!trimmed) {
|
|
@@ -260,6 +368,20 @@ const styles = StyleSheet.create({
|
|
|
260
368
|
alignSelf: 'stretch',
|
|
261
369
|
overflow: 'hidden'
|
|
262
370
|
},
|
|
371
|
+
browser: {
|
|
372
|
+
flex: 1,
|
|
373
|
+
width: '100%',
|
|
374
|
+
backgroundColor: '#ffffff'
|
|
375
|
+
},
|
|
376
|
+
hiddenBrowser: {
|
|
377
|
+
position: 'absolute',
|
|
378
|
+
top: 0,
|
|
379
|
+
right: 0,
|
|
380
|
+
bottom: 0,
|
|
381
|
+
left: 0,
|
|
382
|
+
opacity: 0,
|
|
383
|
+
zIndex: 0
|
|
384
|
+
},
|
|
263
385
|
creativeImage: {
|
|
264
386
|
position: 'absolute',
|
|
265
387
|
top: 0,
|
|
@@ -8,8 +8,11 @@ export const BASE_URL = 'https://mts-tracking.mobupps.com';
|
|
|
8
8
|
export const EVENT_TRACKING_PATH = '/event/tracking';
|
|
9
9
|
export const RECOMMENDATIONS_PATH = '/api/mts/recommendations';
|
|
10
10
|
export const CLICK_TRACKING_PATH = '/click/tracking';
|
|
11
|
-
export const
|
|
12
|
-
export const
|
|
11
|
+
export const RECOMMENDATION_EVENT_TYPE = 'recommendation';
|
|
12
|
+
export const RECOMMENDATION_REQUEST_EVENT = 'recommendation_request';
|
|
13
|
+
export const RECOMMENDATION_RESPONSE_EVENT = 'recommendation_response';
|
|
14
|
+
export const CREATIVE_VIEW_EVENT = 'creative_view';
|
|
15
|
+
export const CREATIVE_CLICK_EVENT = 'creative_click';
|
|
13
16
|
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
14
17
|
export const PUBLIC_IP_LOOKUP_URL = 'https://api.ipify.org?format=json';
|
|
15
18
|
export const PUBLIC_IP_LOOKUP_TIMEOUT_MS = 2_000;
|
|
@@ -95,8 +95,8 @@ export class AdaxonClient {
|
|
|
95
95
|
processPendingEvents() {
|
|
96
96
|
return this.batchProcessor.process();
|
|
97
97
|
}
|
|
98
|
-
getRecommendations(
|
|
99
|
-
return this.events.getRecommendations(
|
|
98
|
+
getRecommendations(properties = {}) {
|
|
99
|
+
return this.events.getRecommendations(properties);
|
|
100
100
|
}
|
|
101
101
|
createClickUrl(attribution) {
|
|
102
102
|
this.validator.validateClickAttribution(attribution);
|
package/lib/module/sdkVersion.js
CHANGED
|
@@ -18,7 +18,7 @@ export class EventBuilder {
|
|
|
18
18
|
const user = {
|
|
19
19
|
anonymous_id: await deviceInfo.getUniqueId()
|
|
20
20
|
};
|
|
21
|
-
|
|
21
|
+
const payload = {
|
|
22
22
|
event_id: input.eventId ?? createUuid(),
|
|
23
23
|
event_name: input.eventName,
|
|
24
24
|
event_time: input.eventTime ?? new Date().toISOString(),
|
|
@@ -32,5 +32,9 @@ export class EventBuilder {
|
|
|
32
32
|
},
|
|
33
33
|
network: await networkInfo.getNetworkInfo()
|
|
34
34
|
};
|
|
35
|
+
if (input.eventType) {
|
|
36
|
+
payload.event_type = input.eventType;
|
|
37
|
+
}
|
|
38
|
+
return payload;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { EVENT_TRACKING_PATH, RECOMMENDATIONS_PATH } from "../../config/ApiConstants.js";
|
|
3
|
+
import { EVENT_TRACKING_PATH, RECOMMENDATION_EVENT_TYPE, RECOMMENDATION_REQUEST_EVENT, RECOMMENDATION_RESPONSE_EVENT, RECOMMENDATIONS_PATH } from "../../config/ApiConstants.js";
|
|
4
4
|
import { AdaxonSdkError } from "../../errors/AdaxonSdkError.js";
|
|
5
5
|
export class EventService {
|
|
6
6
|
constructor(config) {
|
|
@@ -17,19 +17,33 @@ export class EventService {
|
|
|
17
17
|
this.validator.validateBatch(payloads);
|
|
18
18
|
return this.postEvents(payloads);
|
|
19
19
|
}
|
|
20
|
-
async getRecommendations(
|
|
21
|
-
const
|
|
22
|
-
|
|
20
|
+
async getRecommendations(properties = {}) {
|
|
21
|
+
const requestEvent = await this.builder.build({
|
|
22
|
+
eventName: RECOMMENDATION_REQUEST_EVENT,
|
|
23
|
+
eventType: RECOMMENDATION_EVENT_TYPE,
|
|
24
|
+
properties
|
|
25
|
+
});
|
|
26
|
+
await this.postEvents([requestEvent]);
|
|
23
27
|
const response = await this.api.post(RECOMMENDATIONS_PATH, {
|
|
24
|
-
app_id:
|
|
28
|
+
app_id: requestEvent.app_id,
|
|
25
29
|
device: {
|
|
26
|
-
platform:
|
|
30
|
+
platform: requestEvent.device.platform
|
|
27
31
|
}
|
|
28
32
|
});
|
|
29
33
|
if (!Array.isArray(response.offers)) {
|
|
30
34
|
throw new AdaxonSdkError('HTTP_ERROR', 'Adaxon recommendation response is missing the offers array.');
|
|
31
35
|
}
|
|
32
|
-
|
|
36
|
+
await this.postEvents([await this.builder.build({
|
|
37
|
+
eventName: RECOMMENDATION_RESPONSE_EVENT,
|
|
38
|
+
eventType: RECOMMENDATION_EVENT_TYPE,
|
|
39
|
+
properties: {
|
|
40
|
+
parent_event_id: requestEvent.event_id
|
|
41
|
+
}
|
|
42
|
+
})]);
|
|
43
|
+
return {
|
|
44
|
+
offers: response.offers,
|
|
45
|
+
requestEventId: requestEvent.event_id
|
|
46
|
+
};
|
|
33
47
|
}
|
|
34
48
|
postEvents(payloads) {
|
|
35
49
|
return this.api.post(EVENT_TRACKING_PATH, payloads);
|
|
@@ -12,6 +12,9 @@ export class EventValidator {
|
|
|
12
12
|
}
|
|
13
13
|
validateTrackInput(input) {
|
|
14
14
|
this.validateEventName(input.eventName);
|
|
15
|
+
if (input.eventType) {
|
|
16
|
+
this.validateEventName(input.eventType, 'eventType');
|
|
17
|
+
}
|
|
15
18
|
this.validateProperties(input.properties ?? {});
|
|
16
19
|
if (input.eventId && !UUID_PATTERN.test(input.eventId)) {
|
|
17
20
|
this.fail('eventId must be a valid UUID.');
|
|
@@ -20,9 +23,9 @@ export class EventValidator {
|
|
|
20
23
|
this.fail('eventTime must be a valid ISO 8601 datetime string.');
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
|
-
validateEventName(eventName) {
|
|
26
|
+
validateEventName(eventName, label = 'eventName') {
|
|
24
27
|
if (!eventName || eventName.length > MAX_EVENT_NAME_LENGTH || !EVENT_NAME_PATTERN.test(eventName)) {
|
|
25
|
-
this.fail(
|
|
28
|
+
this.fail(`${label} must start with a letter, contain only letters, numbers, and underscores, and be at most ${MAX_EVENT_NAME_LENGTH} characters.`);
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
validateProperties(properties) {
|
|
@@ -5,6 +5,8 @@ export interface InterstitialAdSession {
|
|
|
5
5
|
interact: () => void;
|
|
6
6
|
onDismiss?: (reason: InterstitialDismissReason) => void;
|
|
7
7
|
}
|
|
8
|
+
export declare function presentOfferBrowser(url: string): boolean;
|
|
9
|
+
export declare function dismissOfferBrowser(): void;
|
|
8
10
|
export declare function presentInterstitial(session: InterstitialAdSession): boolean;
|
|
9
11
|
export declare function dismissInterstitial(reason?: InterstitialDismissReason): void;
|
|
10
12
|
export declare function InterstitialAd(): import("react").JSX.Element | null;
|
|
@@ -4,8 +4,11 @@ export declare const BASE_URL = "https://mts-tracking.mobupps.com";
|
|
|
4
4
|
export declare const EVENT_TRACKING_PATH = "/event/tracking";
|
|
5
5
|
export declare const RECOMMENDATIONS_PATH = "/api/mts/recommendations";
|
|
6
6
|
export declare const CLICK_TRACKING_PATH = "/click/tracking";
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
7
|
+
export declare const RECOMMENDATION_EVENT_TYPE = "recommendation";
|
|
8
|
+
export declare const RECOMMENDATION_REQUEST_EVENT = "recommendation_request";
|
|
9
|
+
export declare const RECOMMENDATION_RESPONSE_EVENT = "recommendation_response";
|
|
10
|
+
export declare const CREATIVE_VIEW_EVENT = "creative_view";
|
|
11
|
+
export declare const CREATIVE_CLICK_EVENT = "creative_click";
|
|
9
12
|
export declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
10
13
|
export declare const PUBLIC_IP_LOOKUP_URL = "https://api.ipify.org?format=json";
|
|
11
14
|
export declare const PUBLIC_IP_LOOKUP_TIMEOUT_MS = 2000;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AppInfoReader, DeviceInfoReader, NetworkInfoReader, NetworkStatusReader } from '../platform/types.js';
|
|
2
2
|
import type { AdaxonSdkConfig } from '../types/AdaxonConfig.js';
|
|
3
|
-
import type { EventIngestionResponse,
|
|
3
|
+
import type { EventIngestionResponse, RecommendationFlowResult } from '../types/ApiTypes.js';
|
|
4
4
|
import type { AdaxonPlatform, ClickAttribution, EventProperties, TrackEventInput, TrackEventOptions } from '../types/EventTypes.js';
|
|
5
5
|
import type { BatchProcessResult } from '../types/QueuedEvent.js';
|
|
6
6
|
interface AdaxonClientConfig extends AdaxonSdkConfig {
|
|
@@ -26,7 +26,7 @@ export declare class AdaxonClient {
|
|
|
26
26
|
trackBatch(events: readonly TrackEventInput[]): Promise<EventIngestionResponse>;
|
|
27
27
|
enqueueEvent(eventName: string, properties?: EventProperties, options?: TrackEventOptions): Promise<void>;
|
|
28
28
|
processPendingEvents(): Promise<BatchProcessResult>;
|
|
29
|
-
getRecommendations(
|
|
29
|
+
getRecommendations(properties?: EventProperties): Promise<RecommendationFlowResult>;
|
|
30
30
|
createClickUrl(attribution: ClickAttribution): string;
|
|
31
31
|
private toQueryString;
|
|
32
32
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from '../network/ApiClient.js';
|
|
2
|
-
import type { EventIngestionResponse,
|
|
3
|
-
import type { EventPayload, TrackEventInput } from '../../types/EventTypes.js';
|
|
2
|
+
import type { EventIngestionResponse, RecommendationFlowResult } from '../../types/ApiTypes.js';
|
|
3
|
+
import type { EventPayload, EventProperties, TrackEventInput } from '../../types/EventTypes.js';
|
|
4
4
|
import type { EventBuilder } from './EventBuilder.js';
|
|
5
5
|
import type { EventValidator } from '../../validation/EventValidator.js';
|
|
6
6
|
interface EventServiceConfig {
|
|
@@ -15,7 +15,7 @@ export declare class EventService {
|
|
|
15
15
|
constructor(config: EventServiceConfig);
|
|
16
16
|
trackBatch(events: readonly TrackEventInput[]): Promise<EventIngestionResponse>;
|
|
17
17
|
sendPayloads(payloads: readonly EventPayload[]): Promise<EventIngestionResponse>;
|
|
18
|
-
getRecommendations(
|
|
18
|
+
getRecommendations(properties?: EventProperties): Promise<RecommendationFlowResult>;
|
|
19
19
|
private postEvents;
|
|
20
20
|
}
|
|
21
21
|
export {};
|
|
@@ -23,6 +23,10 @@ export interface RecommendationOffer {
|
|
|
23
23
|
export interface RecommendationResponse {
|
|
24
24
|
offers: RecommendationOffer[];
|
|
25
25
|
}
|
|
26
|
+
export interface RecommendationFlowResult {
|
|
27
|
+
offers: RecommendationOffer[];
|
|
28
|
+
requestEventId: string;
|
|
29
|
+
}
|
|
26
30
|
export interface AdaxonApiErrorBody {
|
|
27
31
|
success?: boolean;
|
|
28
32
|
message?: string;
|
|
@@ -32,11 +32,13 @@ export interface TrackEventInput {
|
|
|
32
32
|
properties?: EventProperties;
|
|
33
33
|
eventId?: string;
|
|
34
34
|
eventTime?: string;
|
|
35
|
+
eventType?: string;
|
|
35
36
|
}
|
|
36
37
|
export type TrackEventOptions = Omit<TrackEventInput, 'eventName' | 'properties'>;
|
|
37
38
|
export interface EventPayload {
|
|
38
39
|
event_id: string;
|
|
39
40
|
event_name: string;
|
|
41
|
+
event_type?: string;
|
|
40
42
|
event_time: string;
|
|
41
43
|
app_id: string;
|
|
42
44
|
properties: EventProperties;
|
|
@@ -4,7 +4,7 @@ export declare class EventValidator {
|
|
|
4
4
|
readonly length: number;
|
|
5
5
|
}): void;
|
|
6
6
|
validateTrackInput(input: TrackEventInput): void;
|
|
7
|
-
validateEventName(eventName: string): void;
|
|
7
|
+
validateEventName(eventName: string, label?: string): void;
|
|
8
8
|
validateProperties(properties: EventProperties): void;
|
|
9
9
|
validateClickAttribution(attribution: ClickAttribution): void;
|
|
10
10
|
private validatePositiveInteger;
|
package/package.json
CHANGED