react-native-test-adx 0.1.3 → 0.1.5
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 +77 -42
- package/lib/module/components/AdaxonProvider.js +13 -3
- package/lib/module/components/InterstitialAd.js +284 -0
- package/lib/module/config/ApiConstants.js +6 -2
- 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 +20 -7
- package/lib/typescript/src/components/InterstitialAd.d.ts +11 -0
- package/lib/typescript/src/config/ApiConstants.d.ts +3 -2
- 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 +4 -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,95 @@ 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(options = {}) {
|
|
74
72
|
try {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
const offers = await this.loadRecommendations(RECOMMENDATION_VIEW_EVENT);
|
|
74
|
+
if (offers.length === 0) {
|
|
75
|
+
console.warn('[Adaxon] No offer available.');
|
|
76
|
+
return {
|
|
77
|
+
shown: false,
|
|
78
|
+
reason: 'no_offer'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const excluded = new Set(options.excludeOfferIds ?? []);
|
|
82
|
+
const offer = offers.find(item => !excluded.has(item.offer_id));
|
|
83
|
+
if (!offer) {
|
|
84
|
+
console.warn('[Adaxon] No eligible offer available.');
|
|
85
|
+
return {
|
|
86
|
+
shown: false,
|
|
87
|
+
reason: 'no_eligible_offer'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return await new Promise(resolve => {
|
|
91
|
+
const displayed = presentInterstitial({
|
|
92
|
+
offer,
|
|
93
|
+
interact: () => {
|
|
94
|
+
options.onOfferClicked?.(offer.offer_id);
|
|
95
|
+
this.openOfferUrl(offer).then(() => dismissInterstitial('click')).catch(error => {
|
|
96
|
+
console.warn('[Adaxon] Failed to open offer URL.', AdaxonSdkError.messageOf(error, 'Failed to open offer.'));
|
|
97
|
+
dismissInterstitial('close');
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
onDismiss: dismissReason => {
|
|
101
|
+
resolve({
|
|
102
|
+
shown: true,
|
|
103
|
+
offerId: offer.offer_id,
|
|
104
|
+
dismissReason
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
if (!displayed) {
|
|
109
|
+
console.warn('[Adaxon] Interstitial host is not mounted. Wrap the app in AdaxonProvider.');
|
|
110
|
+
resolve({
|
|
111
|
+
shown: false,
|
|
112
|
+
reason: 'not_mounted'
|
|
113
|
+
});
|
|
81
114
|
}
|
|
82
115
|
});
|
|
83
|
-
listener?.onRecommendationsLoaded(response.offers);
|
|
84
|
-
return response.offers;
|
|
85
116
|
} catch (error) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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}`);
|
|
117
|
+
console.warn('[Adaxon] Failed to show ad.', AdaxonSdkError.messageOf(error, 'Failed to show ad.'));
|
|
118
|
+
if (this.testMode) {
|
|
119
|
+
throw error;
|
|
99
120
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
ad_type: offer.ad_type,
|
|
105
|
-
mediation_provider: MEDIATION_PROVIDER
|
|
106
|
-
});
|
|
107
|
-
listener?.onOfferOpened(offer);
|
|
108
|
-
} catch (error) {
|
|
109
|
-
const message = AdaxonSdkError.messageOf(error, 'Failed to open offer.');
|
|
110
|
-
listener?.onOfferFailedToOpen(offer, message);
|
|
111
|
-
throw error;
|
|
121
|
+
return {
|
|
122
|
+
shown: false,
|
|
123
|
+
reason: 'no_offer'
|
|
124
|
+
};
|
|
112
125
|
}
|
|
113
126
|
}
|
|
114
|
-
setExternalUserId(externalUserId) {
|
|
115
|
-
this.requireClient().setExternalUserId(externalUserId);
|
|
116
|
-
}
|
|
117
127
|
isNetworkAvailable() {
|
|
118
128
|
return this.client?.isNetworkAvailable() ?? false;
|
|
119
129
|
}
|
|
120
130
|
destroy() {
|
|
131
|
+
dismissInterstitial();
|
|
121
132
|
this.client?.destroy();
|
|
122
133
|
this.client = null;
|
|
123
134
|
this.testMode = false;
|
|
124
135
|
}
|
|
136
|
+
async loadRecommendations(eventName, properties = {}) {
|
|
137
|
+
const response = await this.requireClient().getRecommendations({
|
|
138
|
+
eventName,
|
|
139
|
+
properties: {
|
|
140
|
+
...properties,
|
|
141
|
+
mediation_provider: MEDIATION_PROVIDER,
|
|
142
|
+
test_mode: this.testMode
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return response.offers;
|
|
146
|
+
}
|
|
147
|
+
async openOfferUrl(offer) {
|
|
148
|
+
const client = this.requireClient();
|
|
149
|
+
if (!(await reactNativeUrlOpener.canOpenURL(offer.offer_url))) {
|
|
150
|
+
throw new AdaxonSdkError('VALIDATION_ERROR', `Cannot open offer URL: ${offer.offer_url}`);
|
|
151
|
+
}
|
|
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
|
+
}
|
|
125
160
|
requireClient() {
|
|
126
161
|
if (!this.client) {
|
|
127
162
|
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,284 @@
|
|
|
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
|
+
let activeSession = null;
|
|
10
|
+
export function presentInterstitial(session) {
|
|
11
|
+
if (!listener) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
activeSession = session;
|
|
15
|
+
listener(session);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
export function dismissInterstitial(reason = 'close') {
|
|
19
|
+
const ended = activeSession;
|
|
20
|
+
activeSession = null;
|
|
21
|
+
listener?.(null);
|
|
22
|
+
ended?.onDismiss?.(reason);
|
|
23
|
+
}
|
|
24
|
+
const emptySafeAreaInsets = {
|
|
25
|
+
top: 0,
|
|
26
|
+
right: 0,
|
|
27
|
+
bottom: 0,
|
|
28
|
+
left: 0
|
|
29
|
+
};
|
|
30
|
+
function useEmptySafeAreaInsets() {
|
|
31
|
+
return emptySafeAreaInsets;
|
|
32
|
+
}
|
|
33
|
+
const useSafeAreaInsets = loadUseSafeAreaInsets() ?? useEmptySafeAreaInsets;
|
|
34
|
+
function loadUseSafeAreaInsets() {
|
|
35
|
+
try {
|
|
36
|
+
const module = require('react-native-safe-area-context');
|
|
37
|
+
return typeof module.useSafeAreaInsets === 'function' ? module.useSafeAreaInsets : undefined;
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function useTopInset() {
|
|
43
|
+
const safeArea = useSafeAreaInsets();
|
|
44
|
+
if (safeArea.top > 0) {
|
|
45
|
+
return safeArea.top;
|
|
46
|
+
}
|
|
47
|
+
if (Platform.OS === 'android') {
|
|
48
|
+
return StatusBar.currentHeight ?? 0;
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
export function InterstitialAd() {
|
|
53
|
+
const [session, setSession] = useState(null);
|
|
54
|
+
const [closeTone, setCloseTone] = useState('onLightSurface');
|
|
55
|
+
const safeArea = useSafeAreaInsets();
|
|
56
|
+
const topInset = useTopInset();
|
|
57
|
+
const offerId = session?.offer.offer_id;
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
listener = setSession;
|
|
60
|
+
return () => {
|
|
61
|
+
if (listener === setSession) {
|
|
62
|
+
listener = null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setCloseTone('onLightSurface');
|
|
68
|
+
}, [offerId]);
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!session) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const onBackPress = () => {
|
|
74
|
+
dismissInterstitial('back');
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
const subscription = BackHandler.addEventListener('hardwareBackPress', onBackPress);
|
|
78
|
+
return () => subscription.remove();
|
|
79
|
+
}, [session]);
|
|
80
|
+
if (!session) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const {
|
|
84
|
+
offer,
|
|
85
|
+
interact
|
|
86
|
+
} = session;
|
|
87
|
+
const closeAppearance = closeButtonAppearance(closeTone);
|
|
88
|
+
const closeRightInset = Platform.OS === 'android' ? CLOSE_EDGE_INSET + safeArea.right : CLOSE_EDGE_INSET;
|
|
89
|
+
const adFrame = /*#__PURE__*/_jsx(_Fragment, {
|
|
90
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
91
|
+
style: styles.adContainer,
|
|
92
|
+
children: [/*#__PURE__*/_jsx(Pressable, {
|
|
93
|
+
accessibilityRole: "button",
|
|
94
|
+
accessibilityLabel: "Close ad",
|
|
95
|
+
onPress: () => dismissInterstitial('close'),
|
|
96
|
+
style: [styles.closeButton, closeAppearance.button, {
|
|
97
|
+
top: CLOSE_EDGE_INSET,
|
|
98
|
+
right: closeRightInset
|
|
99
|
+
}],
|
|
100
|
+
testID: "adaxon-interstitial-close",
|
|
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
|
+
})]
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
return /*#__PURE__*/_jsx(View, {
|
|
119
|
+
style: styles.overlay,
|
|
120
|
+
testID: "adaxon-interstitial",
|
|
121
|
+
children: Platform.OS === 'ios' ? /*#__PURE__*/_jsx(SafeAreaView, {
|
|
122
|
+
style: styles.safeFrame,
|
|
123
|
+
children: adFrame
|
|
124
|
+
}) : /*#__PURE__*/_jsx(View, {
|
|
125
|
+
style: [styles.safeFrame, {
|
|
126
|
+
paddingTop: topInset
|
|
127
|
+
}],
|
|
128
|
+
children: adFrame
|
|
129
|
+
})
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function OfferCreative({
|
|
133
|
+
offer,
|
|
134
|
+
onCloseToneChange
|
|
135
|
+
}) {
|
|
136
|
+
const imageUrl = remoteImageUrl(offer.creative_url) ?? remoteImageUrl(offer.preview_url);
|
|
137
|
+
const [failed, setFailed] = useState(false);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
setFailed(false);
|
|
140
|
+
}, [imageUrl]);
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!imageUrl || failed) {
|
|
143
|
+
onCloseToneChange('onDarkSurface');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
onCloseToneChange('onLightSurface');
|
|
147
|
+
}, [failed, imageUrl, onCloseToneChange]);
|
|
148
|
+
if (!imageUrl || failed) {
|
|
149
|
+
return /*#__PURE__*/_jsx(View, {
|
|
150
|
+
style: styles.fallback,
|
|
151
|
+
testID: "adaxon-interstitial-fallback",
|
|
152
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
153
|
+
style: styles.offerName,
|
|
154
|
+
children: offer.offer_name
|
|
155
|
+
})
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return /*#__PURE__*/_jsx(Image, {
|
|
159
|
+
source: {
|
|
160
|
+
uri: imageUrl
|
|
161
|
+
},
|
|
162
|
+
style: styles.creativeImage,
|
|
163
|
+
resizeMode: "contain",
|
|
164
|
+
onError: () => setFailed(true),
|
|
165
|
+
testID: "adaxon-interstitial-image"
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function closeButtonAppearance(tone) {
|
|
169
|
+
if (tone === 'onDarkSurface') {
|
|
170
|
+
return {
|
|
171
|
+
button: {
|
|
172
|
+
backgroundColor: 'rgba(255, 255, 255, 0.94)',
|
|
173
|
+
borderColor: 'rgba(0, 0, 0, 0.35)'
|
|
174
|
+
},
|
|
175
|
+
label: {
|
|
176
|
+
color: '#111111',
|
|
177
|
+
textShadowColor: 'rgba(255, 255, 255, 0.85)',
|
|
178
|
+
textShadowRadius: 2
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
button: {
|
|
184
|
+
backgroundColor: 'rgba(0, 0, 0, 0.62)',
|
|
185
|
+
borderColor: 'rgba(255, 255, 255, 0.55)'
|
|
186
|
+
},
|
|
187
|
+
label: {
|
|
188
|
+
color: '#ffffff',
|
|
189
|
+
textShadowColor: 'rgba(0, 0, 0, 0.75)',
|
|
190
|
+
textShadowRadius: 3
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function remoteImageUrl(value) {
|
|
195
|
+
const trimmed = value?.trim();
|
|
196
|
+
if (!trimmed) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const url = new URL(trimmed);
|
|
201
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
return trimmed;
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const styles = StyleSheet.create({
|
|
210
|
+
overlay: {
|
|
211
|
+
position: 'absolute',
|
|
212
|
+
top: 0,
|
|
213
|
+
right: 0,
|
|
214
|
+
bottom: 0,
|
|
215
|
+
left: 0,
|
|
216
|
+
zIndex: 9999,
|
|
217
|
+
elevation: 9999,
|
|
218
|
+
backgroundColor: '#000000'
|
|
219
|
+
},
|
|
220
|
+
safeFrame: {
|
|
221
|
+
flex: 1,
|
|
222
|
+
width: '100%'
|
|
223
|
+
},
|
|
224
|
+
adContainer: {
|
|
225
|
+
flex: 1,
|
|
226
|
+
width: '100%',
|
|
227
|
+
position: 'relative'
|
|
228
|
+
},
|
|
229
|
+
closeButton: {
|
|
230
|
+
position: 'absolute',
|
|
231
|
+
zIndex: 2,
|
|
232
|
+
minWidth: CLOSE_HIT_SIZE,
|
|
233
|
+
minHeight: CLOSE_HIT_SIZE,
|
|
234
|
+
width: CLOSE_HIT_SIZE,
|
|
235
|
+
height: CLOSE_HIT_SIZE,
|
|
236
|
+
alignItems: 'center',
|
|
237
|
+
justifyContent: 'center',
|
|
238
|
+
borderRadius: CLOSE_HIT_SIZE / 2,
|
|
239
|
+
borderWidth: 1.5,
|
|
240
|
+
shadowColor: '#000000',
|
|
241
|
+
shadowOffset: {
|
|
242
|
+
width: 0,
|
|
243
|
+
height: 2
|
|
244
|
+
},
|
|
245
|
+
shadowOpacity: 0.45,
|
|
246
|
+
shadowRadius: 4,
|
|
247
|
+
elevation: 8
|
|
248
|
+
},
|
|
249
|
+
closeLabel: {
|
|
250
|
+
fontSize: 32,
|
|
251
|
+
lineHeight: 36,
|
|
252
|
+
textShadowOffset: {
|
|
253
|
+
width: 0,
|
|
254
|
+
height: 0
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
offer: {
|
|
258
|
+
flex: 1,
|
|
259
|
+
width: '100%',
|
|
260
|
+
alignSelf: 'stretch',
|
|
261
|
+
overflow: 'hidden'
|
|
262
|
+
},
|
|
263
|
+
creativeImage: {
|
|
264
|
+
position: 'absolute',
|
|
265
|
+
top: 0,
|
|
266
|
+
right: 0,
|
|
267
|
+
bottom: 0,
|
|
268
|
+
left: 0,
|
|
269
|
+
width: '100%',
|
|
270
|
+
height: '100%'
|
|
271
|
+
},
|
|
272
|
+
fallback: {
|
|
273
|
+
flex: 1,
|
|
274
|
+
width: '100%',
|
|
275
|
+
alignItems: 'center',
|
|
276
|
+
justifyContent: 'center',
|
|
277
|
+
paddingHorizontal: 24
|
|
278
|
+
},
|
|
279
|
+
offerName: {
|
|
280
|
+
color: '#ffffff',
|
|
281
|
+
fontSize: 22,
|
|
282
|
+
textAlign: 'center'
|
|
283
|
+
}
|
|
284
|
+
});
|
|
@@ -1,12 +1,16 @@
|
|
|
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
|
+
|
|
7
|
+
//export const BASE_URL = 'http://localhost:3002';
|
|
8
|
+
export const BASE_URL = 'https://mts-tracking.mobupps.com';
|
|
6
9
|
export const EVENT_TRACKING_PATH = '/event/tracking';
|
|
7
10
|
export const RECOMMENDATIONS_PATH = '/api/mts/recommendations';
|
|
8
11
|
export const CLICK_TRACKING_PATH = '/click/tracking';
|
|
9
12
|
export const OFFER_OPENED_EVENT = 'offer_opened';
|
|
13
|
+
export const RECOMMENDATION_VIEW_EVENT = 'recommendation_view';
|
|
10
14
|
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
11
15
|
export const PUBLIC_IP_LOOKUP_URL = 'https://api.ipify.org?format=json';
|
|
12
16
|
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
|
}
|