react-native-test-adx 0.1.3 → 0.1.4
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/README.md +367 -243
- package/lib/module/AdaxonSdk.js +51 -44
- package/lib/module/components/AdaxonProvider.js +13 -3
- package/lib/module/components/InterstitialAd.js +279 -0
- package/lib/module/config/ApiConstants.js +3 -1
- package/lib/module/core/AdaxonClient.js +1 -5
- package/lib/module/hooks/useAdaxon.js +1 -3
- package/lib/module/platform/app.js +44 -3
- package/lib/module/platform/reactNative.js +11 -9
- package/lib/module/services/event/EventBuilder.js +0 -6
- package/lib/module/services/event/EventService.js +7 -5
- package/lib/module/types/AdaxonConfig.js +1 -3
- package/lib/typescript/src/AdaxonSdk.d.ts +7 -7
- package/lib/typescript/src/components/InterstitialAd.d.ts +10 -0
- package/lib/typescript/src/config/ApiConstants.d.ts +2 -1
- package/lib/typescript/src/core/AdaxonClient.d.ts +0 -1
- package/lib/typescript/src/hooks/useAdaxon.d.ts +1 -3
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/platform/app.d.ts +2 -10
- package/lib/typescript/src/platform/reactNative.d.ts +1 -0
- package/lib/typescript/src/services/event/EventBuilder.d.ts +0 -3
- package/lib/typescript/src/types/AdaxonConfig.d.ts +0 -10
- package/lib/typescript/src/types/EventTypes.d.ts +0 -4
- package/package.json +1 -1
package/lib/module/AdaxonSdk.js
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { AdaxonClient } from "./core/AdaxonClient.js";
|
|
4
|
-
import { MEDIATION_PROVIDER, OFFER_OPENED_EVENT } from "./config/ApiConstants.js";
|
|
4
|
+
import { MEDIATION_PROVIDER, OFFER_OPENED_EVENT, RECOMMENDATION_VIEW_EVENT } from "./config/ApiConstants.js";
|
|
5
|
+
import { dismissInterstitial, presentInterstitial } from "./components/InterstitialAd.js";
|
|
5
6
|
import { AdaxonSdkError } from "./errors/AdaxonSdkError.js";
|
|
6
7
|
import { detectReactNativePlatform, reactNativeAppInfoReader, reactNativeDeviceReader, reactNativeNetworkInfoReader, reactNativeNetworkStatusReader, reactNativeUrlOpener } from "./platform/reactNative.js";
|
|
7
8
|
import { buildAddToCartProperties, buildPurchaseProperties, normalizeEventValues } from "./services/event/normalizeProperties.js";
|
|
8
9
|
import { AdxEvent } from "./services/event/predefined.js";
|
|
9
10
|
export class AdaxonSdk {
|
|
10
|
-
static
|
|
11
|
+
static shared = new AdaxonSdk();
|
|
11
12
|
client = null;
|
|
12
13
|
testMode = false;
|
|
13
|
-
static get shared() {
|
|
14
|
-
return AdaxonSdk.sharedInstance;
|
|
15
|
-
}
|
|
16
14
|
isInitialized() {
|
|
17
15
|
return this.client !== null;
|
|
18
16
|
}
|
|
@@ -70,58 +68,67 @@ export class AdaxonSdk {
|
|
|
70
68
|
async trackCompleteRegistration(params = {}, options = {}) {
|
|
71
69
|
return this.logEvent(AdxEvent.COMPLETE_REGISTRATION, params, options);
|
|
72
70
|
}
|
|
73
|
-
async
|
|
71
|
+
async showAd() {
|
|
74
72
|
try {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
listener?.onRecommendationsLoaded(response.offers);
|
|
84
|
-
return response.offers;
|
|
85
|
-
} catch (error) {
|
|
86
|
-
const message = AdaxonSdkError.messageOf(error, 'Failed to load recommendations.');
|
|
87
|
-
const code = error instanceof AdaxonSdkError ? error.code : undefined;
|
|
88
|
-
listener?.onRecommendationsFailedToLoad(message, code);
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
async showOffer(offer, listener) {
|
|
93
|
-
try {
|
|
94
|
-
const client = this.requireClient();
|
|
95
|
-
const targetUrl = offer.offer_url;
|
|
96
|
-
const canOpen = await reactNativeUrlOpener.canOpenURL(targetUrl);
|
|
97
|
-
if (!canOpen) {
|
|
98
|
-
throw new AdaxonSdkError('VALIDATION_ERROR', `Cannot open offer URL: ${targetUrl}`);
|
|
73
|
+
const offers = await this.loadRecommendations(RECOMMENDATION_VIEW_EVENT);
|
|
74
|
+
const offer = offers[0];
|
|
75
|
+
if (!offer) {
|
|
76
|
+
console.warn('[Adaxon] No offer available.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!this.displayInterstitial(offer)) {
|
|
80
|
+
console.warn('[Adaxon] Interstitial host is not mounted. Wrap the app in AdaxonProvider.');
|
|
99
81
|
}
|
|
100
|
-
await reactNativeUrlOpener.openURL(targetUrl);
|
|
101
|
-
await client.track(OFFER_OPENED_EVENT, {
|
|
102
|
-
offer_id: offer.offer_id,
|
|
103
|
-
offer_name: offer.offer_name,
|
|
104
|
-
ad_type: offer.ad_type,
|
|
105
|
-
mediation_provider: MEDIATION_PROVIDER
|
|
106
|
-
});
|
|
107
|
-
listener?.onOfferOpened(offer);
|
|
108
82
|
} catch (error) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
83
|
+
console.warn('[Adaxon] Failed to show ad.', AdaxonSdkError.messageOf(error, 'Failed to show ad.'));
|
|
84
|
+
if (this.testMode) {
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
112
87
|
}
|
|
113
88
|
}
|
|
114
|
-
setExternalUserId(externalUserId) {
|
|
115
|
-
this.requireClient().setExternalUserId(externalUserId);
|
|
116
|
-
}
|
|
117
89
|
isNetworkAvailable() {
|
|
118
90
|
return this.client?.isNetworkAvailable() ?? false;
|
|
119
91
|
}
|
|
120
92
|
destroy() {
|
|
93
|
+
dismissInterstitial();
|
|
121
94
|
this.client?.destroy();
|
|
122
95
|
this.client = null;
|
|
123
96
|
this.testMode = false;
|
|
124
97
|
}
|
|
98
|
+
async loadRecommendations(eventName, properties = {}) {
|
|
99
|
+
const response = await this.requireClient().getRecommendations({
|
|
100
|
+
eventName,
|
|
101
|
+
properties: {
|
|
102
|
+
...properties,
|
|
103
|
+
mediation_provider: MEDIATION_PROVIDER,
|
|
104
|
+
test_mode: this.testMode
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return response.offers;
|
|
108
|
+
}
|
|
109
|
+
displayInterstitial(offer) {
|
|
110
|
+
return presentInterstitial({
|
|
111
|
+
offer,
|
|
112
|
+
interact: () => {
|
|
113
|
+
this.openOfferUrl(offer).then(dismissInterstitial).catch(error => {
|
|
114
|
+
console.warn('[Adaxon] Failed to open offer URL.', AdaxonSdkError.messageOf(error, 'Failed to open offer.'));
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async openOfferUrl(offer) {
|
|
120
|
+
const client = this.requireClient();
|
|
121
|
+
if (!(await reactNativeUrlOpener.canOpenURL(offer.offer_url))) {
|
|
122
|
+
throw new AdaxonSdkError('VALIDATION_ERROR', `Cannot open offer URL: ${offer.offer_url}`);
|
|
123
|
+
}
|
|
124
|
+
await reactNativeUrlOpener.openURL(offer.offer_url);
|
|
125
|
+
await client.track(OFFER_OPENED_EVENT, {
|
|
126
|
+
offer_id: offer.offer_id,
|
|
127
|
+
offer_name: offer.offer_name,
|
|
128
|
+
ad_type: offer.ad_type,
|
|
129
|
+
mediation_provider: MEDIATION_PROVIDER
|
|
130
|
+
});
|
|
131
|
+
}
|
|
125
132
|
requireClient() {
|
|
126
133
|
if (!this.client) {
|
|
127
134
|
throw new AdaxonSdkError('INVALID_CONFIG', 'Adaxon SDK is not initialized. Call Adaxon.initialize(...) first.');
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { StyleSheet, View } from "../platform/reactNative.js";
|
|
4
5
|
import { AdaxonSdk } from "../AdaxonSdk.js";
|
|
5
6
|
import { AdaxonSdkError } from "../errors/AdaxonSdkError.js";
|
|
6
7
|
import { AdaxonContext } from "../state/AdaxonContext.js";
|
|
7
|
-
import {
|
|
8
|
+
import { InterstitialAd } from "./InterstitialAd.js";
|
|
9
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
10
|
export function AdaxonProvider({
|
|
9
11
|
configuration,
|
|
10
12
|
listener,
|
|
@@ -44,6 +46,14 @@ export function AdaxonProvider({
|
|
|
44
46
|
}), [initializationError, isInitialized, sdk]);
|
|
45
47
|
return /*#__PURE__*/_jsx(AdaxonContext.Provider, {
|
|
46
48
|
value: value,
|
|
47
|
-
children:
|
|
49
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
50
|
+
style: styles.root,
|
|
51
|
+
children: [children, /*#__PURE__*/_jsx(InterstitialAd, {})]
|
|
52
|
+
})
|
|
48
53
|
});
|
|
49
|
-
}
|
|
54
|
+
}
|
|
55
|
+
const styles = StyleSheet.create({
|
|
56
|
+
root: {
|
|
57
|
+
flex: 1
|
|
58
|
+
}
|
|
59
|
+
});
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { BackHandler, Image, Platform, Pressable, SafeAreaView, StatusBar, StyleSheet, Text, View } from "../platform/reactNative.js";
|
|
5
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
6
|
+
const CLOSE_EDGE_INSET = 12;
|
|
7
|
+
const CLOSE_HIT_SIZE = 44;
|
|
8
|
+
let listener = null;
|
|
9
|
+
export function presentInterstitial(session) {
|
|
10
|
+
if (!listener) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
listener(session);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
export function dismissInterstitial() {
|
|
17
|
+
listener?.(null);
|
|
18
|
+
}
|
|
19
|
+
const emptySafeAreaInsets = {
|
|
20
|
+
top: 0,
|
|
21
|
+
right: 0,
|
|
22
|
+
bottom: 0,
|
|
23
|
+
left: 0
|
|
24
|
+
};
|
|
25
|
+
function useEmptySafeAreaInsets() {
|
|
26
|
+
return emptySafeAreaInsets;
|
|
27
|
+
}
|
|
28
|
+
const useSafeAreaInsets = loadUseSafeAreaInsets() ?? useEmptySafeAreaInsets;
|
|
29
|
+
function loadUseSafeAreaInsets() {
|
|
30
|
+
try {
|
|
31
|
+
const module = require('react-native-safe-area-context');
|
|
32
|
+
return typeof module.useSafeAreaInsets === 'function' ? module.useSafeAreaInsets : undefined;
|
|
33
|
+
} catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function useTopInset() {
|
|
38
|
+
const safeArea = useSafeAreaInsets();
|
|
39
|
+
if (safeArea.top > 0) {
|
|
40
|
+
return safeArea.top;
|
|
41
|
+
}
|
|
42
|
+
if (Platform.OS === 'android') {
|
|
43
|
+
return StatusBar.currentHeight ?? 0;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
export function InterstitialAd() {
|
|
48
|
+
const [session, setSession] = useState(null);
|
|
49
|
+
const [closeTone, setCloseTone] = useState('onLightSurface');
|
|
50
|
+
const safeArea = useSafeAreaInsets();
|
|
51
|
+
const topInset = useTopInset();
|
|
52
|
+
const offerId = session?.offer.offer_id;
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
listener = setSession;
|
|
55
|
+
return () => {
|
|
56
|
+
if (listener === setSession) {
|
|
57
|
+
listener = null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
setCloseTone('onLightSurface');
|
|
63
|
+
}, [offerId]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!session) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const onBackPress = () => {
|
|
69
|
+
dismissInterstitial();
|
|
70
|
+
return true;
|
|
71
|
+
};
|
|
72
|
+
const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
|
73
|
+
return () => subscription.remove();
|
|
74
|
+
}, [session]);
|
|
75
|
+
if (!session) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const {
|
|
79
|
+
offer,
|
|
80
|
+
interact
|
|
81
|
+
} = session;
|
|
82
|
+
const closeAppearance = closeButtonAppearance(closeTone);
|
|
83
|
+
const closeRightInset = Platform.OS === 'android' ? CLOSE_EDGE_INSET + safeArea.right : CLOSE_EDGE_INSET;
|
|
84
|
+
const adFrame = /*#__PURE__*/_jsx(_Fragment, {
|
|
85
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
86
|
+
style: styles.adContainer,
|
|
87
|
+
children: [/*#__PURE__*/_jsx(Pressable, {
|
|
88
|
+
accessibilityRole: "button",
|
|
89
|
+
accessibilityLabel: "Close ad",
|
|
90
|
+
onPress: dismissInterstitial,
|
|
91
|
+
style: [styles.closeButton, closeAppearance.button, {
|
|
92
|
+
top: CLOSE_EDGE_INSET,
|
|
93
|
+
right: closeRightInset
|
|
94
|
+
}],
|
|
95
|
+
testID: "adaxon-interstitial-close",
|
|
96
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
97
|
+
style: [styles.closeLabel, closeAppearance.label],
|
|
98
|
+
children: "\xD7"
|
|
99
|
+
})
|
|
100
|
+
}), /*#__PURE__*/_jsx(Pressable, {
|
|
101
|
+
accessibilityRole: "button",
|
|
102
|
+
accessibilityLabel: offer.offer_name,
|
|
103
|
+
onPress: interact,
|
|
104
|
+
style: styles.offer,
|
|
105
|
+
testID: "adaxon-interstitial-offer",
|
|
106
|
+
children: /*#__PURE__*/_jsx(OfferCreative, {
|
|
107
|
+
offer: offer,
|
|
108
|
+
onCloseToneChange: setCloseTone
|
|
109
|
+
})
|
|
110
|
+
})]
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
return /*#__PURE__*/_jsx(View, {
|
|
114
|
+
style: styles.overlay,
|
|
115
|
+
testID: "adaxon-interstitial",
|
|
116
|
+
children: Platform.OS === 'ios' ? /*#__PURE__*/_jsx(SafeAreaView, {
|
|
117
|
+
style: styles.safeFrame,
|
|
118
|
+
children: adFrame
|
|
119
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
120
|
+
style: [styles.safeFrame, {
|
|
121
|
+
paddingTop: topInset
|
|
122
|
+
}],
|
|
123
|
+
children: adFrame
|
|
124
|
+
})
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function OfferCreative({
|
|
128
|
+
offer,
|
|
129
|
+
onCloseToneChange
|
|
130
|
+
}) {
|
|
131
|
+
const imageUrl = remoteImageUrl(offer.creative_url) ?? remoteImageUrl(offer.preview_url);
|
|
132
|
+
const [failed, setFailed] = useState(false);
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
setFailed(false);
|
|
135
|
+
}, [imageUrl]);
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!imageUrl || failed) {
|
|
138
|
+
onCloseToneChange('onDarkSurface');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
onCloseToneChange('onLightSurface');
|
|
142
|
+
}, [failed, imageUrl, onCloseToneChange]);
|
|
143
|
+
if (!imageUrl || failed) {
|
|
144
|
+
return /*#__PURE__*/_jsx(View, {
|
|
145
|
+
style: styles.fallback,
|
|
146
|
+
testID: "adaxon-interstitial-fallback",
|
|
147
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
148
|
+
style: styles.offerName,
|
|
149
|
+
children: offer.offer_name
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return /*#__PURE__*/_jsx(Image, {
|
|
154
|
+
source: {
|
|
155
|
+
uri: imageUrl
|
|
156
|
+
},
|
|
157
|
+
style: styles.creativeImage,
|
|
158
|
+
resizeMode: "contain",
|
|
159
|
+
onError: () => setFailed(true),
|
|
160
|
+
testID: "adaxon-interstitial-image"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function closeButtonAppearance(tone) {
|
|
164
|
+
if (tone === 'onDarkSurface') {
|
|
165
|
+
return {
|
|
166
|
+
button: {
|
|
167
|
+
backgroundColor: 'rgba(255, 255, 255, 0.94)',
|
|
168
|
+
borderColor: 'rgba(0, 0, 0, 0.35)'
|
|
169
|
+
},
|
|
170
|
+
label: {
|
|
171
|
+
color: '#111111',
|
|
172
|
+
textShadowColor: 'rgba(255, 255, 255, 0.85)',
|
|
173
|
+
textShadowRadius: 2
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
button: {
|
|
179
|
+
backgroundColor: 'rgba(0, 0, 0, 0.62)',
|
|
180
|
+
borderColor: 'rgba(255, 255, 255, 0.55)'
|
|
181
|
+
},
|
|
182
|
+
label: {
|
|
183
|
+
color: '#ffffff',
|
|
184
|
+
textShadowColor: 'rgba(0, 0, 0, 0.75)',
|
|
185
|
+
textShadowRadius: 3
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function remoteImageUrl(value) {
|
|
190
|
+
const trimmed = value?.trim();
|
|
191
|
+
if (!trimmed) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const url = new URL(trimmed);
|
|
196
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
return trimmed;
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const styles = StyleSheet.create({
|
|
205
|
+
overlay: {
|
|
206
|
+
position: 'absolute',
|
|
207
|
+
top: 0,
|
|
208
|
+
right: 0,
|
|
209
|
+
bottom: 0,
|
|
210
|
+
left: 0,
|
|
211
|
+
zIndex: 9999,
|
|
212
|
+
elevation: 9999,
|
|
213
|
+
backgroundColor: '#000000'
|
|
214
|
+
},
|
|
215
|
+
safeFrame: {
|
|
216
|
+
flex: 1,
|
|
217
|
+
width: '100%'
|
|
218
|
+
},
|
|
219
|
+
adContainer: {
|
|
220
|
+
flex: 1,
|
|
221
|
+
width: '100%',
|
|
222
|
+
position: 'relative'
|
|
223
|
+
},
|
|
224
|
+
closeButton: {
|
|
225
|
+
position: 'absolute',
|
|
226
|
+
zIndex: 2,
|
|
227
|
+
minWidth: CLOSE_HIT_SIZE,
|
|
228
|
+
minHeight: CLOSE_HIT_SIZE,
|
|
229
|
+
width: CLOSE_HIT_SIZE,
|
|
230
|
+
height: CLOSE_HIT_SIZE,
|
|
231
|
+
alignItems: 'center',
|
|
232
|
+
justifyContent: 'center',
|
|
233
|
+
borderRadius: CLOSE_HIT_SIZE / 2,
|
|
234
|
+
borderWidth: 1.5,
|
|
235
|
+
shadowColor: '#000000',
|
|
236
|
+
shadowOffset: {
|
|
237
|
+
width: 0,
|
|
238
|
+
height: 2
|
|
239
|
+
},
|
|
240
|
+
shadowOpacity: 0.45,
|
|
241
|
+
shadowRadius: 4,
|
|
242
|
+
elevation: 8
|
|
243
|
+
},
|
|
244
|
+
closeLabel: {
|
|
245
|
+
fontSize: 32,
|
|
246
|
+
lineHeight: 36,
|
|
247
|
+
textShadowOffset: {
|
|
248
|
+
width: 0,
|
|
249
|
+
height: 0
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
offer: {
|
|
253
|
+
flex: 1,
|
|
254
|
+
width: '100%',
|
|
255
|
+
alignSelf: 'stretch',
|
|
256
|
+
overflow: 'hidden'
|
|
257
|
+
},
|
|
258
|
+
creativeImage: {
|
|
259
|
+
position: 'absolute',
|
|
260
|
+
top: 0,
|
|
261
|
+
right: 0,
|
|
262
|
+
bottom: 0,
|
|
263
|
+
left: 0,
|
|
264
|
+
width: '100%',
|
|
265
|
+
height: '100%'
|
|
266
|
+
},
|
|
267
|
+
fallback: {
|
|
268
|
+
flex: 1,
|
|
269
|
+
width: '100%',
|
|
270
|
+
alignItems: 'center',
|
|
271
|
+
justifyContent: 'center',
|
|
272
|
+
paddingHorizontal: 24
|
|
273
|
+
},
|
|
274
|
+
offerName: {
|
|
275
|
+
color: '#ffffff',
|
|
276
|
+
fontSize: 22,
|
|
277
|
+
textAlign: 'center'
|
|
278
|
+
}
|
|
279
|
+
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import packageJson from '../../package.json';
|
|
4
|
+
export const SDK_VERSION = packageJson.version;
|
|
4
5
|
export const MEDIATION_PROVIDER = 'mts';
|
|
5
6
|
export const BASE_URL = 'http://localhost:3002';
|
|
6
7
|
export const EVENT_TRACKING_PATH = '/event/tracking';
|
|
7
8
|
export const RECOMMENDATIONS_PATH = '/api/mts/recommendations';
|
|
8
9
|
export const CLICK_TRACKING_PATH = '/click/tracking';
|
|
9
10
|
export const OFFER_OPENED_EVENT = 'offer_opened';
|
|
11
|
+
export const RECOMMENDATION_VIEW_EVENT = 'recommendation_view';
|
|
10
12
|
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
11
13
|
export const PUBLIC_IP_LOOKUP_URL = 'https://api.ipify.org?format=json';
|
|
12
14
|
export const PUBLIC_IP_LOOKUP_TIMEOUT_MS = 2_000;
|
|
@@ -29,8 +29,7 @@ export class AdaxonClient {
|
|
|
29
29
|
deviceInfo: new DeviceInfoService(config.platform ?? 'web', config.deviceInfoReader, config.storage, auth.appId),
|
|
30
30
|
appInfo: new AppInfoService(Boolean(config.testMode), config.appInfoReader),
|
|
31
31
|
networkInfo,
|
|
32
|
-
validator: this.validator
|
|
33
|
-
externalUserId: config.externalUserId
|
|
32
|
+
validator: this.validator
|
|
34
33
|
});
|
|
35
34
|
this.events = new EventService({
|
|
36
35
|
api,
|
|
@@ -115,9 +114,6 @@ export class AdaxonClient {
|
|
|
115
114
|
});
|
|
116
115
|
return `${BASE_URL}${CLICK_TRACKING_PATH}?${query}`;
|
|
117
116
|
}
|
|
118
|
-
setExternalUserId(externalUserId) {
|
|
119
|
-
this.builder.setExternalUserId(externalUserId);
|
|
120
|
-
}
|
|
121
117
|
toQueryString(params) {
|
|
122
118
|
return Object.entries(params).filter(([, value]) => value !== undefined).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join('&');
|
|
123
119
|
}
|
|
@@ -23,9 +23,7 @@ export function useAdaxon() {
|
|
|
23
23
|
trackAddToCart: sdk.trackAddToCart.bind(sdk),
|
|
24
24
|
trackLogin: sdk.trackLogin.bind(sdk),
|
|
25
25
|
trackCompleteRegistration: sdk.trackCompleteRegistration.bind(sdk),
|
|
26
|
-
|
|
27
|
-
showOffer: sdk.showOffer.bind(sdk),
|
|
28
|
-
setExternalUserId: sdk.setExternalUserId.bind(sdk)
|
|
26
|
+
showAd: sdk.showAd.bind(sdk)
|
|
29
27
|
}), [sdk]);
|
|
30
28
|
return {
|
|
31
29
|
isInitialized,
|
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { nonEmptyString, setIfPresent } from "../utils/strings.js";
|
|
4
|
-
export function appFieldsFromNative(
|
|
4
|
+
export function appFieldsFromNative(...sources) {
|
|
5
|
+
const value = key => lastPresent(sources, key);
|
|
6
|
+
const expo = expoConfig(value('expoConfig'), value('manifest'), value('extra'));
|
|
5
7
|
const fields = {};
|
|
6
|
-
setIfPresent(fields, 'package_name', firstNonEmpty(
|
|
7
|
-
setIfPresent(fields, 'app_version', firstNonEmpty(
|
|
8
|
+
setIfPresent(fields, 'package_name', firstNonEmpty(value('applicationId'), value('bundleId'), value('packageName'), value('androidPackage'), value('iosBundleIdentifier'), asRecord(value('android'))?.package, asRecord(value('ios'))?.bundleIdentifier, asRecord(expo?.android)?.package, asRecord(expo?.ios)?.bundleIdentifier));
|
|
9
|
+
setIfPresent(fields, 'app_version', firstNonEmpty(value('nativeApplicationVersion'), value('appVersion'), value('nativeAppVersion'), value('version'), expo?.version));
|
|
8
10
|
return fields;
|
|
9
11
|
}
|
|
12
|
+
function expoConfig(expoConfigValue, manifestValue, extraValue) {
|
|
13
|
+
const manifest = asRecord(manifestValue);
|
|
14
|
+
return firstRecord(expoConfigValue, asRecord(manifest?.extra)?.expoClient, manifest?.expoClient, manifest, asRecord(extraValue)?.expoClient);
|
|
15
|
+
}
|
|
16
|
+
function lastPresent(sources, key) {
|
|
17
|
+
let found;
|
|
18
|
+
for (const source of sources) {
|
|
19
|
+
if (!source) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const next = source[key];
|
|
23
|
+
if (next != null && next !== '') {
|
|
24
|
+
found = next;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return found;
|
|
28
|
+
}
|
|
29
|
+
function firstRecord(...values) {
|
|
30
|
+
for (const value of values) {
|
|
31
|
+
const record = asRecord(value);
|
|
32
|
+
if (record) {
|
|
33
|
+
return record;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
function asRecord(value) {
|
|
39
|
+
if (typeof value === 'string') {
|
|
40
|
+
try {
|
|
41
|
+
return asRecord(JSON.parse(value));
|
|
42
|
+
} catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
10
51
|
function firstNonEmpty(...values) {
|
|
11
52
|
for (const value of values) {
|
|
12
53
|
const next = nonEmptyString(value);
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { Linking, NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
4
|
+
export { BackHandler, Dimensions, Image, Modal, Platform, Pressable, SafeAreaView, StatusBar, StyleSheet, Text, View } from 'react-native';
|
|
4
5
|
import { nonEmptyString, setIfPresent } from "../utils/strings.js";
|
|
5
6
|
import { readLocaleFields } from "../utils/locale.js";
|
|
6
7
|
import { appFieldsFromNative } from "./app.js";
|
|
7
8
|
import { deviceFieldsFromAndroid } from "./android.js";
|
|
8
9
|
import { deviceFieldsFromIos } from "./ios.js";
|
|
9
10
|
import { connectionTypeFromNativeState, ipAddressFromNativeState, isOnlineFromNativeState, usableIpAddress } from "./network.js";
|
|
11
|
+
function expoNativeModule(name) {
|
|
12
|
+
try {
|
|
13
|
+
const expoGlobal = globalThis;
|
|
14
|
+
return expoGlobal.expo?.modules?.[name];
|
|
15
|
+
} catch {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
10
19
|
function nativeModule(name) {
|
|
11
|
-
return (NativeModules ?? {})[name];
|
|
20
|
+
return (NativeModules ?? {})[name] ?? expoNativeModule(name);
|
|
12
21
|
}
|
|
13
22
|
async function callNative(method, arg) {
|
|
14
23
|
if (typeof method !== 'function') {
|
|
@@ -53,13 +62,6 @@ async function readUniqueIdFromNative() {
|
|
|
53
62
|
const deviceInfo = nativeModule('RNDeviceInfo');
|
|
54
63
|
return nonEmptyString(await callNative(deviceInfo?.getUniqueId)) ?? '';
|
|
55
64
|
}
|
|
56
|
-
function readNativeAppConstants() {
|
|
57
|
-
return {
|
|
58
|
-
...nativeModule('RNDeviceInfo'),
|
|
59
|
-
...nativeModule('ExponentConstants'),
|
|
60
|
-
...nativeModule('ExpoApplication')
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
65
|
function nativeNetworkModules() {
|
|
64
66
|
return {
|
|
65
67
|
netInfo: nativeModule('RNCNetInfo'),
|
|
@@ -121,7 +123,7 @@ export const reactNativeDeviceReader = {
|
|
|
121
123
|
getUniqueId: readUniqueIdFromNative
|
|
122
124
|
};
|
|
123
125
|
export const reactNativeAppInfoReader = {
|
|
124
|
-
readAppInfo: () => appFieldsFromNative(
|
|
126
|
+
readAppInfo: () => appFieldsFromNative(nativeModule('RNDeviceInfo'), nativeModule('ExponentConstants'), nativeModule('ExpoApplication'))
|
|
125
127
|
};
|
|
126
128
|
export const reactNativeNetworkInfoReader = {
|
|
127
129
|
readNetworkInfo: collectFromNativeNetwork
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { createUuid } from "../../utils/uuid.js";
|
|
4
|
-
import { nonEmptyString, setIfPresent } from "../../utils/strings.js";
|
|
5
4
|
export class EventBuilder {
|
|
6
5
|
sessionId = createUuid();
|
|
7
6
|
constructor(config) {
|
|
8
7
|
this.config = config;
|
|
9
|
-
this.externalUserId = config.externalUserId;
|
|
10
|
-
}
|
|
11
|
-
setExternalUserId(externalUserId) {
|
|
12
|
-
this.externalUserId = externalUserId;
|
|
13
8
|
}
|
|
14
9
|
async build(input) {
|
|
15
10
|
const {
|
|
@@ -23,7 +18,6 @@ export class EventBuilder {
|
|
|
23
18
|
const user = {
|
|
24
19
|
anonymous_id: await deviceInfo.getUniqueId()
|
|
25
20
|
};
|
|
26
|
-
setIfPresent(user, 'external_user_id', nonEmptyString(this.externalUserId));
|
|
27
21
|
return {
|
|
28
22
|
event_id: input.eventId ?? createUuid(),
|
|
29
23
|
event_name: input.eventName,
|
|
@@ -19,11 +19,13 @@ export class EventService {
|
|
|
19
19
|
}
|
|
20
20
|
async getRecommendations(input) {
|
|
21
21
|
const eventPayload = await this.builder.build(input);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
await this.postEvents([eventPayload]);
|
|
23
|
+
const response = await this.api.post(RECOMMENDATIONS_PATH, {
|
|
24
|
+
app_id: eventPayload.app_id,
|
|
25
|
+
device: {
|
|
26
|
+
platform: eventPayload.device.platform
|
|
27
|
+
}
|
|
28
|
+
});
|
|
27
29
|
if (!Array.isArray(response.offers)) {
|
|
28
30
|
throw new AdaxonSdkError('HTTP_ERROR', 'Adaxon recommendation response is missing the offers array.');
|
|
29
31
|
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { type TrackAddToCartParams, type TrackCompleteRegistrationParams, type TrackLoginParams, type TrackPurchaseParams } from './services/event/normalizeProperties.js';
|
|
2
|
-
import type { AdaxonInitializationConfiguration, AdaxonInitializationListener
|
|
3
|
-
import type { EventIngestionResponse
|
|
2
|
+
import type { AdaxonInitializationConfiguration, AdaxonInitializationListener } from './types/AdaxonConfig.js';
|
|
3
|
+
import type { EventIngestionResponse } from './types/ApiTypes.js';
|
|
4
4
|
import type { EventProperties, TrackEventInput, TrackEventOptions } from './types/EventTypes.js';
|
|
5
5
|
import type { BatchProcessResult } from './types/QueuedEvent.js';
|
|
6
6
|
export declare class AdaxonSdk {
|
|
7
|
-
|
|
7
|
+
static readonly shared: AdaxonSdk;
|
|
8
8
|
private client;
|
|
9
9
|
private testMode;
|
|
10
|
-
static get shared(): AdaxonSdk;
|
|
11
10
|
isInitialized(): boolean;
|
|
12
11
|
initialize(configuration: AdaxonInitializationConfiguration, listener?: AdaxonInitializationListener): Promise<void>;
|
|
13
12
|
track(eventName: string, properties?: EventProperties, options?: TrackEventOptions): Promise<EventIngestionResponse>;
|
|
@@ -19,11 +18,12 @@ export declare class AdaxonSdk {
|
|
|
19
18
|
trackAddToCart(params: TrackAddToCartParams, options?: TrackEventOptions): Promise<EventIngestionResponse>;
|
|
20
19
|
trackLogin(params?: TrackLoginParams, options?: TrackEventOptions): Promise<EventIngestionResponse>;
|
|
21
20
|
trackCompleteRegistration(params?: TrackCompleteRegistrationParams, options?: TrackEventOptions): Promise<EventIngestionResponse>;
|
|
22
|
-
|
|
23
|
-
showOffer(offer: RecommendationOffer, listener?: AdaxonOfferShowListener): Promise<void>;
|
|
24
|
-
setExternalUserId(externalUserId?: string): void;
|
|
21
|
+
showAd(): Promise<void>;
|
|
25
22
|
isNetworkAvailable(): boolean;
|
|
26
23
|
destroy(): void;
|
|
24
|
+
private loadRecommendations;
|
|
25
|
+
private displayInterstitial;
|
|
26
|
+
private openOfferUrl;
|
|
27
27
|
private requireClient;
|
|
28
28
|
}
|
|
29
29
|
//# sourceMappingURL=AdaxonSdk.d.ts.map
|