react-native-test-adx 0.1.1 → 0.1.2
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 +52 -16
- package/lib/module/AdaxonSdk.js +20 -9
- package/lib/module/components/AdaxonProvider.js +6 -3
- package/lib/module/config/ApiConstants.js +1 -1
- package/lib/module/core/AdaxonClient.js +41 -3
- package/lib/module/platform/network.js +20 -0
- package/lib/module/platform/reactNative.js +67 -5
- package/lib/module/services/event/EventService.js +0 -7
- package/lib/module/services/network/NetworkService.js +82 -0
- package/lib/typescript/src/AdaxonSdk.d.ts +2 -1
- package/lib/typescript/src/config/ApiConstants.d.ts +1 -1
- package/lib/typescript/src/core/AdaxonClient.d.ts +7 -1
- package/lib/typescript/src/platform/network.d.ts +1 -0
- package/lib/typescript/src/platform/reactNative.d.ts +2 -1
- package/lib/typescript/src/platform/types.d.ts +4 -0
- package/lib/typescript/src/services/event/EventService.d.ts +1 -2
- package/lib/typescript/src/services/network/NetworkService.d.ts +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,11 +36,7 @@ import { AdaxonProvider, useAdaxon } from 'react-native-test-adx';
|
|
|
36
36
|
|
|
37
37
|
Both packages expose the same API. Only the npm name differs.
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```sh
|
|
42
|
-
npx expo install expo-secure-store
|
|
43
|
-
```
|
|
39
|
+
`expo-secure-store` is **not** required. The SDK does not depend on it.
|
|
44
40
|
|
|
45
41
|
## Quick start (React Native)
|
|
46
42
|
|
|
@@ -61,7 +57,7 @@ The SDK owns the tracker host and API paths (`/event/tracking`, `/api/mts/recomm
|
|
|
61
57
|
| `testMode` | No | `false` (`sdk_version` gets a `-test` suffix when true) |
|
|
62
58
|
| `externalUserId` | No | Unset |
|
|
63
59
|
|
|
64
|
-
|
|
60
|
+
The SDK detects the device platform and collects device/app fields automatically. Do **not** pass a tracker URL — requests go to the SDK `BASE_URL`.
|
|
65
61
|
|
|
66
62
|
```tsx
|
|
67
63
|
import {
|
|
@@ -70,7 +66,6 @@ import {
|
|
|
70
66
|
useAdaxon,
|
|
71
67
|
type RecommendationOffer,
|
|
72
68
|
} from 'react-native-adaxon';
|
|
73
|
-
import * as SecureStore from 'expo-secure-store';
|
|
74
69
|
import { Button, Text, View } from 'react-native';
|
|
75
70
|
|
|
76
71
|
function App() {
|
|
@@ -80,11 +75,6 @@ function App() {
|
|
|
80
75
|
appId: 'adx_your_app_id',
|
|
81
76
|
sdkKey: 'your_sdk_key',
|
|
82
77
|
testMode: __DEV__,
|
|
83
|
-
storage: {
|
|
84
|
-
getItem: (key) => SecureStore.getItemAsync(key),
|
|
85
|
-
setItem: (key, value) => SecureStore.setItemAsync(key, value),
|
|
86
|
-
removeItem: (key) => SecureStore.deleteItemAsync(key),
|
|
87
|
-
},
|
|
88
78
|
}}
|
|
89
79
|
>
|
|
90
80
|
<HomeScreen />
|
|
@@ -156,6 +146,41 @@ function HomeScreen() {
|
|
|
156
146
|
|
|
157
147
|
Do **not** pass device or app fields. The SDK attaches `device` and `app` automatically on every event.
|
|
158
148
|
|
|
149
|
+
### Storage
|
|
150
|
+
|
|
151
|
+
`storage` is optional. You do not have to install `expo-secure-store` (or any other store) for the SDK to run. Only `appId` and `sdkKey` are required.
|
|
152
|
+
|
|
153
|
+
| What you pass | What happens |
|
|
154
|
+
|---|---|
|
|
155
|
+
| No `storage` | SDK uses built-in in-memory storage. Tracking works. Anonymous id and queued events are lost when the app closes. |
|
|
156
|
+
| A storage adapter | Anonymous id and queued events survive restart. |
|
|
157
|
+
|
|
158
|
+
The adapter is only `getItem` / `setItem` / `removeItem`. You do not implement the queue. Any store works: Expo SecureStore, AsyncStorage, MMKV, or your own.
|
|
159
|
+
|
|
160
|
+
Optional SecureStore helper (not required):
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
npx expo install expo-secure-store
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import * as SecureStore from 'expo-secure-store';
|
|
168
|
+
|
|
169
|
+
<AdaxonProvider
|
|
170
|
+
configuration={{
|
|
171
|
+
appId: 'adx_your_app_id',
|
|
172
|
+
sdkKey: 'your_sdk_key',
|
|
173
|
+
storage: {
|
|
174
|
+
getItem: (key) => SecureStore.getItemAsync(key),
|
|
175
|
+
setItem: (key, value) => SecureStore.setItemAsync(key, value),
|
|
176
|
+
removeItem: (key) => SecureStore.deleteItemAsync(key),
|
|
177
|
+
},
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
SecureStore has a small per-key size limit. For a large event queue, prefer AsyncStorage (or similar) and keep SecureStore for the anonymous id if you want.
|
|
183
|
+
|
|
159
184
|
### Non-component boot
|
|
160
185
|
|
|
161
186
|
`Adaxon.initialize()` is still available if you are not inside a React tree. Prefer the provider in React Native apps.
|
|
@@ -218,16 +243,27 @@ POST /event/tracking
|
|
|
218
243
|
|
|
219
244
|
Rules:
|
|
220
245
|
|
|
221
|
-
- Pending events are stored through the `storage` adapter, so they survive app restart.
|
|
246
|
+
- Pending events are stored through the `storage` adapter when you pass one, so they survive app restart. If you omit `storage`, they stay in memory only.
|
|
222
247
|
- Events are processed in creation order (FIFO).
|
|
223
248
|
- Each request contains at most **100** events. A queue of 250 is sent as 100 + 100 + 50.
|
|
224
249
|
- Events are removed only after the API confirms success (`success: true`). On network, HTTP, or unknown errors they stay in the queue.
|
|
225
250
|
- Only one processor runs at a time for the same app. A second `processPendingEvents()` call returns `already_processing`.
|
|
226
251
|
- Large queues are read 100 events at a time. The SDK does not load the whole queue into memory.
|
|
227
|
-
- On `Adaxon.initialize()` / `AdaxonProvider` mount, pending events start flushing in the background without blocking startup.
|
|
252
|
+
- On `Adaxon.initialize()` / `AdaxonProvider` mount, pending events start flushing in the background without blocking startup when the network is available.
|
|
228
253
|
- Pass `removeItem` on the storage adapter so successful events can be deleted. If it is omitted, the SDK writes an empty value instead.
|
|
229
254
|
|
|
230
|
-
|
|
255
|
+
## Network loss
|
|
256
|
+
|
|
257
|
+
The SDK watches connectivity through the host app's NetInfo / Expo Network listener. It does **not** poll.
|
|
258
|
+
|
|
259
|
+
- Online: `track` / `trackBatch` send immediately, same as before.
|
|
260
|
+
- Offline: events are stored in the event queue and no API request is made.
|
|
261
|
+
- Offline → online: the batch processor is notified once and pending events are flushed. Duplicate `ONLINE` events do not start extra processors.
|
|
262
|
+
- `isNetworkAvailable()` reports the current state. `destroy()` (and Provider unmount) removes the network listener.
|
|
263
|
+
|
|
264
|
+
A missing or failing network module does not crash the app. Tracking continues as online until the device is known to be offline.
|
|
265
|
+
|
|
266
|
+
Offline events use the same `storage` adapter as above. Without it, they are held in memory and are lost if the app is closed before the network returns.
|
|
231
267
|
|
|
232
268
|
## Automatic device information
|
|
233
269
|
|
|
@@ -253,7 +289,7 @@ Values come from React Native `Platform` (`src/platform/reactNative.ts`). They a
|
|
|
253
289
|
"app": {
|
|
254
290
|
"package_name": "com.example.app",
|
|
255
291
|
"app_version": "2.4.1",
|
|
256
|
-
"sdk_version": "0.1.
|
|
292
|
+
"sdk_version": "0.1.2"
|
|
257
293
|
}
|
|
258
294
|
}
|
|
259
295
|
```
|
package/lib/module/AdaxonSdk.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { AdaxonClient } from "./core/AdaxonClient.js";
|
|
4
4
|
import { MEDIATION_PROVIDER, OFFER_OPENED_EVENT } from "./config/ApiConstants.js";
|
|
5
5
|
import { AdaxonSdkError } from "./errors/AdaxonSdkError.js";
|
|
6
|
-
import { detectReactNativePlatform, reactNativeAppInfoReader, reactNativeDeviceReader, reactNativeNetworkInfoReader, reactNativeUrlOpener } from "./platform/reactNative.js";
|
|
6
|
+
import { detectReactNativePlatform, reactNativeAppInfoReader, reactNativeDeviceReader, reactNativeNetworkInfoReader, reactNativeNetworkStatusReader, reactNativeUrlOpener } from "./platform/reactNative.js";
|
|
7
7
|
import { buildAddToCartProperties, buildPurchaseProperties, normalizeEventValues } from "./services/event/normalizeProperties.js";
|
|
8
8
|
import { AdxEvent } from "./services/event/predefined.js";
|
|
9
9
|
export class AdaxonSdk {
|
|
@@ -18,18 +18,25 @@ export class AdaxonSdk {
|
|
|
18
18
|
}
|
|
19
19
|
async initialize(configuration, listener) {
|
|
20
20
|
try {
|
|
21
|
-
this.client
|
|
21
|
+
const previous = this.client;
|
|
22
|
+
const client = new AdaxonClient({
|
|
22
23
|
...configuration,
|
|
23
24
|
platform: detectReactNativePlatform(),
|
|
24
25
|
deviceInfoReader: reactNativeDeviceReader,
|
|
25
26
|
appInfoReader: reactNativeAppInfoReader,
|
|
26
|
-
networkInfoReader: reactNativeNetworkInfoReader
|
|
27
|
+
networkInfoReader: reactNativeNetworkInfoReader,
|
|
28
|
+
networkStatusReader: reactNativeNetworkStatusReader
|
|
27
29
|
});
|
|
30
|
+
await client.start();
|
|
31
|
+
previous?.destroy();
|
|
32
|
+
this.client = client;
|
|
28
33
|
this.testMode = Boolean(configuration.testMode);
|
|
29
34
|
listener?.onInitializationComplete();
|
|
30
|
-
|
|
35
|
+
if (client.isNetworkAvailable()) {
|
|
36
|
+
client.processPendingEvents().catch(() => undefined);
|
|
37
|
+
}
|
|
31
38
|
} catch (error) {
|
|
32
|
-
this.
|
|
39
|
+
this.destroy();
|
|
33
40
|
const message = AdaxonSdkError.messageOf(error, 'Initialization failed.');
|
|
34
41
|
const code = error instanceof AdaxonSdkError && error.code === 'INVALID_CONFIG' ? 'INVALID_CONFIG' : 'UNKNOWN';
|
|
35
42
|
listener?.onInitializationFailed(code, message);
|
|
@@ -107,14 +114,18 @@ export class AdaxonSdk {
|
|
|
107
114
|
setExternalUserId(externalUserId) {
|
|
108
115
|
this.requireClient().setExternalUserId(externalUserId);
|
|
109
116
|
}
|
|
117
|
+
isNetworkAvailable() {
|
|
118
|
+
return this.client?.isNetworkAvailable() ?? false;
|
|
119
|
+
}
|
|
120
|
+
destroy() {
|
|
121
|
+
this.client?.destroy();
|
|
122
|
+
this.client = null;
|
|
123
|
+
this.testMode = false;
|
|
124
|
+
}
|
|
110
125
|
requireClient() {
|
|
111
126
|
if (!this.client) {
|
|
112
127
|
throw new AdaxonSdkError('INVALID_CONFIG', 'Adaxon SDK is not initialized. Call Adaxon.initialize(...) first.');
|
|
113
128
|
}
|
|
114
129
|
return this.client;
|
|
115
130
|
}
|
|
116
|
-
reset() {
|
|
117
|
-
this.client = null;
|
|
118
|
-
this.testMode = false;
|
|
119
|
-
}
|
|
120
131
|
}
|
|
@@ -20,10 +20,12 @@ export function AdaxonProvider({
|
|
|
20
20
|
useEffect(() => {
|
|
21
21
|
let cancelled = false;
|
|
22
22
|
sdk.initialize(configurationRef.current, listenerRef.current).then(() => {
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (cancelled) {
|
|
24
|
+
sdk.destroy();
|
|
25
|
+
return;
|
|
26
26
|
}
|
|
27
|
+
setIsInitialized(true);
|
|
28
|
+
setInitializationError(null);
|
|
27
29
|
}).catch(error => {
|
|
28
30
|
if (!cancelled) {
|
|
29
31
|
setIsInitialized(false);
|
|
@@ -32,6 +34,7 @@ export function AdaxonProvider({
|
|
|
32
34
|
});
|
|
33
35
|
return () => {
|
|
34
36
|
cancelled = true;
|
|
37
|
+
sdk.destroy();
|
|
35
38
|
};
|
|
36
39
|
}, [sdk]);
|
|
37
40
|
const value = useMemo(() => ({
|
|
@@ -10,10 +10,12 @@ import { EventQueueService } from "../services/event/EventQueueService.js";
|
|
|
10
10
|
import { EventService } from "../services/event/EventService.js";
|
|
11
11
|
import { ApiClient } from "../services/network/ApiClient.js";
|
|
12
12
|
import { NetworkInfoService } from "../services/network/NetworkInfoService.js";
|
|
13
|
+
import { NetworkService } from "../services/network/NetworkService.js";
|
|
13
14
|
import { AnonymousIdService } from "../storage/AnonymousIdService.js";
|
|
14
15
|
import { EventQueueStorage } from "../storage/EventQueueStorage.js";
|
|
15
16
|
import { EventValidator } from "../validation/EventValidator.js";
|
|
16
17
|
export class AdaxonClient {
|
|
18
|
+
started = false;
|
|
17
19
|
constructor(config) {
|
|
18
20
|
const auth = new AuthenticationService(config);
|
|
19
21
|
this.validator = new EventValidator();
|
|
@@ -42,12 +44,48 @@ export class AdaxonClient {
|
|
|
42
44
|
queue: this.eventQueue,
|
|
43
45
|
sendBatch: payloads => this.events.sendPayloads(payloads)
|
|
44
46
|
});
|
|
47
|
+
this.network = new NetworkService(config.networkStatusReader);
|
|
48
|
+
}
|
|
49
|
+
async start() {
|
|
50
|
+
if (this.started) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.started = true;
|
|
54
|
+
await this.network.initialize();
|
|
55
|
+
this.network.subscribe(() => {
|
|
56
|
+
this.batchProcessor.process().catch(() => undefined);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
destroy() {
|
|
60
|
+
this.network.destroy();
|
|
61
|
+
this.started = false;
|
|
62
|
+
}
|
|
63
|
+
isNetworkAvailable() {
|
|
64
|
+
return this.network.isAvailable();
|
|
45
65
|
}
|
|
46
66
|
track(eventName, properties = {}, options = {}) {
|
|
47
|
-
return this.
|
|
67
|
+
return this.trackBatch([{
|
|
68
|
+
eventName,
|
|
69
|
+
properties,
|
|
70
|
+
...options
|
|
71
|
+
}]);
|
|
48
72
|
}
|
|
49
|
-
trackBatch(events) {
|
|
50
|
-
|
|
73
|
+
async trackBatch(events) {
|
|
74
|
+
if (this.network.isAvailable()) {
|
|
75
|
+
return this.events.trackBatch(events);
|
|
76
|
+
}
|
|
77
|
+
this.validator.validateBatch(events);
|
|
78
|
+
const payloads = await Promise.all(events.map(event => this.builder.build(event)));
|
|
79
|
+
for (const payload of payloads) {
|
|
80
|
+
await this.eventQueue.add(payload);
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
message: 'queued',
|
|
85
|
+
accepted: payloads.length,
|
|
86
|
+
received: payloads.length,
|
|
87
|
+
rejected: 0
|
|
88
|
+
};
|
|
51
89
|
}
|
|
52
90
|
async enqueueEvent(eventName, properties = {}, options = {}) {
|
|
53
91
|
const payload = await this.builder.build({
|
|
@@ -13,6 +13,26 @@ function publicIpAddress(value) {
|
|
|
13
13
|
const ip = usableIpAddress(value);
|
|
14
14
|
return ip && !isPrivateIpAddress(ip) ? ip : undefined;
|
|
15
15
|
}
|
|
16
|
+
export function isOnlineFromNativeState(state) {
|
|
17
|
+
const value = asNativeNetworkState(state);
|
|
18
|
+
if (!value) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (value.isConnected === false || value.isInternetReachable === false) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const type = nonEmptyString(value.type)?.toLowerCase();
|
|
25
|
+
if (type === 'none') {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (value.isConnected === true) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (type && type !== 'unknown') {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
16
36
|
export function connectionTypeFromNativeState(state) {
|
|
17
37
|
const type = nonEmptyString(asNativeNetworkState(state)?.type)?.toLowerCase();
|
|
18
38
|
return type && CONNECTION_TYPES.has(type) ? type : undefined;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { Linking, NativeModules, Platform } from 'react-native';
|
|
3
|
+
import { Linking, NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
4
4
|
import { nonEmptyString, setIfPresent } from "../utils/strings.js";
|
|
5
5
|
import { readLocaleFields } from "../utils/locale.js";
|
|
6
6
|
import { appFieldsFromNative } from "./app.js";
|
|
7
7
|
import { deviceFieldsFromAndroid } from "./android.js";
|
|
8
8
|
import { deviceFieldsFromIos } from "./ios.js";
|
|
9
|
-
import { connectionTypeFromNativeState, ipAddressFromNativeState, usableIpAddress } from "./network.js";
|
|
9
|
+
import { connectionTypeFromNativeState, ipAddressFromNativeState, isOnlineFromNativeState, usableIpAddress } from "./network.js";
|
|
10
10
|
export function detectReactNativePlatform() {
|
|
11
11
|
return Platform.OS === 'ios' || Platform.OS === 'android' ? Platform.OS : 'web';
|
|
12
12
|
}
|
|
@@ -36,6 +36,10 @@ export const reactNativeAppInfoReader = {
|
|
|
36
36
|
export const reactNativeNetworkInfoReader = {
|
|
37
37
|
readNetworkInfo: collectFromNativeNetwork
|
|
38
38
|
};
|
|
39
|
+
export const reactNativeNetworkStatusReader = {
|
|
40
|
+
getIsOnline: readIsOnlineFromNative,
|
|
41
|
+
subscribe: subscribeNativeNetworkStatus
|
|
42
|
+
};
|
|
39
43
|
export const reactNativeUrlOpener = Linking;
|
|
40
44
|
function readNativeAppConstants() {
|
|
41
45
|
const modules = NativeModules ?? {};
|
|
@@ -48,6 +52,63 @@ function readNativeAppConstants() {
|
|
|
48
52
|
...expoApp
|
|
49
53
|
};
|
|
50
54
|
}
|
|
55
|
+
function nativeNetworkModules() {
|
|
56
|
+
const modules = NativeModules ?? {};
|
|
57
|
+
return {
|
|
58
|
+
netInfo: modules.RNCNetInfo,
|
|
59
|
+
expoNetwork: modules.ExpoNetwork
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function safeUnsubscribe(remove) {
|
|
63
|
+
return () => {
|
|
64
|
+
try {
|
|
65
|
+
remove?.();
|
|
66
|
+
} catch {
|
|
67
|
+
// Native unsubscribe can throw after the module is torn down.
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async function readIsOnlineFromNative() {
|
|
72
|
+
const {
|
|
73
|
+
netInfo,
|
|
74
|
+
expoNetwork
|
|
75
|
+
} = nativeNetworkModules();
|
|
76
|
+
const getCurrentState = netInfo?.getCurrentState;
|
|
77
|
+
if (typeof getCurrentState === 'function') {
|
|
78
|
+
const online = isOnlineFromNativeState(await readOptionalNative(() => getCurrentState(null)));
|
|
79
|
+
if (online !== null) {
|
|
80
|
+
return online;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const getNetworkState = expoNetwork?.getNetworkStateAsync;
|
|
84
|
+
if (typeof getNetworkState === 'function') {
|
|
85
|
+
return isOnlineFromNativeState(await readOptionalNative(getNetworkState));
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function subscribeNativeNetworkStatus(listener) {
|
|
90
|
+
const {
|
|
91
|
+
netInfo,
|
|
92
|
+
expoNetwork
|
|
93
|
+
} = nativeNetworkModules();
|
|
94
|
+
const onState = state => {
|
|
95
|
+
listener(isOnlineFromNativeState(state));
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
if (netInfo) {
|
|
99
|
+
const subscription = new NativeEventEmitter(NativeModules.RNCNetInfo).addListener('netInfo.networkStatusDidChange', onState);
|
|
100
|
+
return safeUnsubscribe(() => subscription.remove());
|
|
101
|
+
}
|
|
102
|
+
const addListener = expoNetwork?.addListener;
|
|
103
|
+
if (typeof addListener === 'function') {
|
|
104
|
+
const subscription = addListener.call(expoNetwork, 'onNetworkStateChanged', onState);
|
|
105
|
+
return safeUnsubscribe(() => subscription?.remove?.());
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('[Adaxon] Failed to register the network listener', error);
|
|
109
|
+
}
|
|
110
|
+
return () => undefined;
|
|
111
|
+
}
|
|
51
112
|
async function readOptionalNative(read) {
|
|
52
113
|
try {
|
|
53
114
|
return await read();
|
|
@@ -57,9 +118,10 @@ async function readOptionalNative(read) {
|
|
|
57
118
|
}
|
|
58
119
|
async function collectFromNativeNetwork() {
|
|
59
120
|
const network = {};
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
121
|
+
const {
|
|
122
|
+
netInfo,
|
|
123
|
+
expoNetwork
|
|
124
|
+
} = nativeNetworkModules();
|
|
63
125
|
const getCurrentState = netInfo?.getCurrentState;
|
|
64
126
|
if (typeof getCurrentState === 'function') {
|
|
65
127
|
const state = await readOptionalNative(() => getCurrentState(null));
|
|
@@ -8,13 +8,6 @@ export class EventService {
|
|
|
8
8
|
this.builder = config.builder;
|
|
9
9
|
this.validator = config.validator;
|
|
10
10
|
}
|
|
11
|
-
track(eventName, properties = {}, options = {}) {
|
|
12
|
-
return this.trackBatch([{
|
|
13
|
-
eventName,
|
|
14
|
-
properties,
|
|
15
|
-
...options
|
|
16
|
-
}]);
|
|
17
|
-
}
|
|
18
11
|
async trackBatch(events) {
|
|
19
12
|
this.validator.validateBatch(events);
|
|
20
13
|
const payloads = await Promise.all(events.map(event => this.builder.build(event)));
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function toState(isOnline) {
|
|
4
|
+
if (isOnline === true) {
|
|
5
|
+
return 'ONLINE';
|
|
6
|
+
}
|
|
7
|
+
if (isOnline === false) {
|
|
8
|
+
return 'OFFLINE';
|
|
9
|
+
}
|
|
10
|
+
return 'UNKNOWN';
|
|
11
|
+
}
|
|
12
|
+
export class NetworkService {
|
|
13
|
+
state = 'UNKNOWN';
|
|
14
|
+
started = false;
|
|
15
|
+
listeners = new Set();
|
|
16
|
+
constructor(reader) {
|
|
17
|
+
this.reader = reader;
|
|
18
|
+
}
|
|
19
|
+
getState() {
|
|
20
|
+
return this.state;
|
|
21
|
+
}
|
|
22
|
+
isAvailable() {
|
|
23
|
+
return this.state !== 'OFFLINE';
|
|
24
|
+
}
|
|
25
|
+
async initialize() {
|
|
26
|
+
if (this.started) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.started = true;
|
|
30
|
+
if (!this.reader) {
|
|
31
|
+
this.applyState('ONLINE');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
this.applyState(toState(await this.reader.getIsOnline()));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('[Adaxon] Network monitoring failed', error);
|
|
38
|
+
this.applyState('UNKNOWN');
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
this.unsubscribeReader = this.reader.subscribe(isOnline => {
|
|
42
|
+
this.applyState(toState(isOnline));
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error('[Adaxon] Network monitoring failed', error);
|
|
46
|
+
this.applyState('UNKNOWN');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
subscribe(listener) {
|
|
50
|
+
this.listeners.add(listener);
|
|
51
|
+
return () => {
|
|
52
|
+
this.listeners.delete(listener);
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
destroy() {
|
|
56
|
+
try {
|
|
57
|
+
this.unsubscribeReader?.();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('[Adaxon] Failed to remove the network listener', error);
|
|
60
|
+
}
|
|
61
|
+
this.unsubscribeReader = undefined;
|
|
62
|
+
this.started = false;
|
|
63
|
+
this.listeners.clear();
|
|
64
|
+
}
|
|
65
|
+
applyState(next) {
|
|
66
|
+
const previous = this.state;
|
|
67
|
+
if (previous === next) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.state = next;
|
|
71
|
+
if (previous !== 'OFFLINE' || next !== 'ONLINE') {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
for (const listener of [...this.listeners]) {
|
|
75
|
+
try {
|
|
76
|
+
listener();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('[Adaxon] Network listener failed', error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -22,7 +22,8 @@ export declare class AdaxonSdk {
|
|
|
22
22
|
loadRecommendations(eventName: string, properties?: EventProperties, listener?: AdaxonRecommendationLoadListener): Promise<RecommendationOffer[]>;
|
|
23
23
|
showOffer(offer: RecommendationOffer, listener?: AdaxonOfferShowListener): Promise<void>;
|
|
24
24
|
setExternalUserId(externalUserId?: string): void;
|
|
25
|
+
isNetworkAvailable(): boolean;
|
|
26
|
+
destroy(): void;
|
|
25
27
|
private requireClient;
|
|
26
|
-
private reset;
|
|
27
28
|
}
|
|
28
29
|
//# sourceMappingURL=AdaxonSdk.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AppInfoReader, DeviceInfoReader, NetworkInfoReader } from '../platform/types.js';
|
|
1
|
+
import type { AppInfoReader, DeviceInfoReader, NetworkInfoReader, NetworkStatusReader } from '../platform/types.js';
|
|
2
2
|
import type { AdaxonSdkConfig } from '../types/AdaxonConfig.js';
|
|
3
3
|
import type { EventIngestionResponse, RecommendationResponse } from '../types/ApiTypes.js';
|
|
4
4
|
import type { AdaxonPlatform, ClickAttribution, EventProperties, TrackEventInput, TrackEventOptions } from '../types/EventTypes.js';
|
|
@@ -8,6 +8,7 @@ interface AdaxonClientConfig extends AdaxonSdkConfig {
|
|
|
8
8
|
deviceInfoReader?: DeviceInfoReader;
|
|
9
9
|
appInfoReader?: AppInfoReader;
|
|
10
10
|
networkInfoReader?: NetworkInfoReader;
|
|
11
|
+
networkStatusReader?: NetworkStatusReader;
|
|
11
12
|
}
|
|
12
13
|
export declare class AdaxonClient {
|
|
13
14
|
private readonly events;
|
|
@@ -15,7 +16,12 @@ export declare class AdaxonClient {
|
|
|
15
16
|
private readonly validator;
|
|
16
17
|
private readonly eventQueue;
|
|
17
18
|
private readonly batchProcessor;
|
|
19
|
+
private readonly network;
|
|
20
|
+
private started;
|
|
18
21
|
constructor(config: AdaxonClientConfig);
|
|
22
|
+
start(): Promise<void>;
|
|
23
|
+
destroy(): void;
|
|
24
|
+
isNetworkAvailable(): boolean;
|
|
19
25
|
track(eventName: string, properties?: EventProperties, options?: TrackEventOptions): Promise<EventIngestionResponse>;
|
|
20
26
|
trackBatch(events: readonly TrackEventInput[]): Promise<EventIngestionResponse>;
|
|
21
27
|
enqueueEvent(eventName: string, properties?: EventProperties, options?: TrackEventOptions): Promise<void>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function isOnlineFromNativeState(state: unknown): boolean | null;
|
|
1
2
|
export declare function connectionTypeFromNativeState(state: unknown): string | undefined;
|
|
2
3
|
export declare function ipAddressFromNativeState(state: unknown): string | undefined;
|
|
3
4
|
export declare function usableIpAddress(value: string | undefined): string | undefined;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AdaxonPlatform } from '../types/EventTypes.js';
|
|
2
|
-
import type { AppInfoReader, DeviceInfoReader, NetworkInfoReader, UrlOpener } from './types.js';
|
|
2
|
+
import type { AppInfoReader, DeviceInfoReader, NetworkInfoReader, NetworkStatusReader, UrlOpener } from './types.js';
|
|
3
3
|
export declare function detectReactNativePlatform(): AdaxonPlatform;
|
|
4
4
|
export declare const reactNativeDeviceReader: DeviceInfoReader;
|
|
5
5
|
export declare const reactNativeAppInfoReader: AppInfoReader;
|
|
6
6
|
export declare const reactNativeNetworkInfoReader: NetworkInfoReader;
|
|
7
|
+
export declare const reactNativeNetworkStatusReader: NetworkStatusReader;
|
|
7
8
|
export declare const reactNativeUrlOpener: UrlOpener;
|
|
8
9
|
//# sourceMappingURL=reactNative.d.ts.map
|
|
@@ -12,4 +12,8 @@ export interface UrlOpener {
|
|
|
12
12
|
canOpenURL(url: string): Promise<boolean>;
|
|
13
13
|
openURL(url: string): Promise<void>;
|
|
14
14
|
}
|
|
15
|
+
export interface NetworkStatusReader {
|
|
16
|
+
getIsOnline(): Promise<boolean | null>;
|
|
17
|
+
subscribe(listener: (isOnline: boolean | null) => void): () => void;
|
|
18
|
+
}
|
|
15
19
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApiClient } from '../network/ApiClient.js';
|
|
2
2
|
import type { EventIngestionResponse, RecommendationResponse } from '../../types/ApiTypes.js';
|
|
3
|
-
import type { EventPayload,
|
|
3
|
+
import type { EventPayload, 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 {
|
|
@@ -13,7 +13,6 @@ export declare class EventService {
|
|
|
13
13
|
private readonly builder;
|
|
14
14
|
private readonly validator;
|
|
15
15
|
constructor(config: EventServiceConfig);
|
|
16
|
-
track(eventName: string, properties?: EventProperties, options?: TrackEventOptions): Promise<EventIngestionResponse>;
|
|
17
16
|
trackBatch(events: readonly TrackEventInput[]): Promise<EventIngestionResponse>;
|
|
18
17
|
sendPayloads(payloads: readonly EventPayload[]): Promise<EventIngestionResponse>;
|
|
19
18
|
getRecommendations(input: TrackEventInput): Promise<RecommendationResponse>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { NetworkStatusReader } from '../../platform/types.js';
|
|
2
|
+
export type NetworkState = 'ONLINE' | 'OFFLINE' | 'UNKNOWN';
|
|
3
|
+
export declare class NetworkService {
|
|
4
|
+
private readonly reader?;
|
|
5
|
+
private state;
|
|
6
|
+
private started;
|
|
7
|
+
private unsubscribeReader?;
|
|
8
|
+
private readonly listeners;
|
|
9
|
+
constructor(reader?: NetworkStatusReader | undefined);
|
|
10
|
+
getState(): NetworkState;
|
|
11
|
+
isAvailable(): boolean;
|
|
12
|
+
initialize(): Promise<void>;
|
|
13
|
+
subscribe(listener: () => void): () => void;
|
|
14
|
+
destroy(): void;
|
|
15
|
+
private applyState;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=NetworkService.d.ts.map
|
package/package.json
CHANGED