react-native-test-adx 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +333 -0
  3. package/lib/module/AdaxonSdk.js +120 -0
  4. package/lib/module/components/AdaxonProvider.js +46 -0
  5. package/lib/module/config/ApiConstants.js +18 -0
  6. package/lib/module/config/StorageKeys.js +12 -0
  7. package/lib/module/core/AdaxonClient.js +88 -0
  8. package/lib/module/errors/AdaxonSdkError.js +23 -0
  9. package/lib/module/hooks/useAdaxon.js +35 -0
  10. package/lib/module/index.js +10 -0
  11. package/lib/module/package.json +1 -0
  12. package/lib/module/platform/android.js +10 -0
  13. package/lib/module/platform/app.js +18 -0
  14. package/lib/module/platform/ios.js +11 -0
  15. package/lib/module/platform/network.js +61 -0
  16. package/lib/module/platform/reactNative.js +78 -0
  17. package/lib/module/platform/types.js +3 -0
  18. package/lib/module/services/app/AppInfoService.js +25 -0
  19. package/lib/module/services/authentication/AuthenticationService.js +26 -0
  20. package/lib/module/services/device/DeviceInfoService.js +22 -0
  21. package/lib/module/services/event/BatchProcessor.js +64 -0
  22. package/lib/module/services/event/EventBuilder.js +43 -0
  23. package/lib/module/services/event/EventQueueService.js +35 -0
  24. package/lib/module/services/event/EventService.js +42 -0
  25. package/lib/module/services/event/normalizeProperties.js +42 -0
  26. package/lib/module/services/event/predefined.js +13 -0
  27. package/lib/module/services/network/ApiClient.js +62 -0
  28. package/lib/module/services/network/NetworkInfoService.js +69 -0
  29. package/lib/module/state/AdaxonContext.js +7 -0
  30. package/lib/module/storage/AnonymousIdService.js +31 -0
  31. package/lib/module/storage/EventQueueStorage.js +143 -0
  32. package/lib/module/storage/memoryStorage.js +16 -0
  33. package/lib/module/types/AdaxonConfig.js +3 -0
  34. package/lib/module/types/ApiTypes.js +1 -0
  35. package/lib/module/types/EventTypes.js +1 -0
  36. package/lib/module/types/QueuedEvent.js +3 -0
  37. package/lib/module/utils/locale.js +15 -0
  38. package/lib/module/utils/strings.js +17 -0
  39. package/lib/module/utils/uuid.js +13 -0
  40. package/lib/module/validation/EventValidator.js +63 -0
  41. package/lib/typescript/package.json +1 -0
  42. package/lib/typescript/src/AdaxonSdk.d.ts +28 -0
  43. package/lib/typescript/src/components/AdaxonProvider.d.ts +11 -0
  44. package/lib/typescript/src/config/ApiConstants.d.ts +17 -0
  45. package/lib/typescript/src/config/StorageKeys.d.ts +5 -0
  46. package/lib/typescript/src/core/AdaxonClient.d.ts +29 -0
  47. package/lib/typescript/src/errors/AdaxonSdkError.d.ts +15 -0
  48. package/lib/typescript/src/hooks/useAdaxon.d.ts +19 -0
  49. package/lib/typescript/src/index.d.ts +19 -0
  50. package/lib/typescript/src/platform/android.d.ts +10 -0
  51. package/lib/typescript/src/platform/app.d.ts +14 -0
  52. package/lib/typescript/src/platform/ios.d.ts +8 -0
  53. package/lib/typescript/src/platform/network.d.ts +6 -0
  54. package/lib/typescript/src/platform/reactNative.d.ts +8 -0
  55. package/lib/typescript/src/platform/types.d.ts +15 -0
  56. package/lib/typescript/src/services/app/AppInfoService.d.ts +11 -0
  57. package/lib/typescript/src/services/authentication/AuthenticationService.d.ts +10 -0
  58. package/lib/typescript/src/services/device/DeviceInfoService.d.ts +10 -0
  59. package/lib/typescript/src/services/event/BatchProcessor.d.ts +19 -0
  60. package/lib/typescript/src/services/event/EventBuilder.d.ts +25 -0
  61. package/lib/typescript/src/services/event/EventQueueService.d.ts +13 -0
  62. package/lib/typescript/src/services/event/EventService.d.ts +23 -0
  63. package/lib/typescript/src/services/event/normalizeProperties.d.ts +26 -0
  64. package/lib/typescript/src/services/event/predefined.d.ts +14 -0
  65. package/lib/typescript/src/services/network/ApiClient.d.ts +14 -0
  66. package/lib/typescript/src/services/network/NetworkInfoService.d.ts +14 -0
  67. package/lib/typescript/src/state/AdaxonContext.d.ts +9 -0
  68. package/lib/typescript/src/storage/AnonymousIdService.d.ts +11 -0
  69. package/lib/typescript/src/storage/EventQueueStorage.d.ts +21 -0
  70. package/lib/typescript/src/storage/memoryStorage.d.ts +3 -0
  71. package/lib/typescript/src/types/AdaxonConfig.d.ts +30 -0
  72. package/lib/typescript/src/types/ApiTypes.d.ts +31 -0
  73. package/lib/typescript/src/types/EventTypes.d.ts +65 -0
  74. package/lib/typescript/src/types/QueuedEvent.d.ts +16 -0
  75. package/lib/typescript/src/utils/locale.d.ts +3 -0
  76. package/lib/typescript/src/utils/strings.d.ts +3 -0
  77. package/lib/typescript/src/utils/uuid.d.ts +3 -0
  78. package/lib/typescript/src/validation/EventValidator.d.ts +13 -0
  79. package/package.json +107 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ritesh Rana
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,333 @@
1
+ # react-native-adaxon
2
+
3
+ Adaxon React Native / Expo SDK for event tracking and offer recommendations.
4
+
5
+ The SDK talks to the MTS tracker over HTTPS. The app does not call Kafka or MySQL.
6
+
7
+ ```text
8
+ Mobile app → Adaxon SDK → HTTPS → MTS tracker → Kafka → MTS API → MySQL
9
+ ```
10
+
11
+ ## Install
12
+
13
+ ### Production apps
14
+
15
+ ```sh
16
+ npm install react-native-adaxon
17
+ # or
18
+ yarn add react-native-adaxon
19
+ # or
20
+ npx expo install react-native-adaxon
21
+ ```
22
+
23
+ ```ts
24
+ import { AdaxonProvider, useAdaxon } from 'react-native-adaxon';
25
+ ```
26
+
27
+ ### Testing / staging apps
28
+
29
+ ```sh
30
+ npm install react-native-test-adx
31
+ ```
32
+
33
+ ```ts
34
+ import { AdaxonProvider, useAdaxon } from 'react-native-test-adx';
35
+ ```
36
+
37
+ Both packages expose the same API. Only the npm name differs.
38
+
39
+ Recommended storage helper:
40
+
41
+ ```sh
42
+ npx expo install expo-secure-store
43
+ ```
44
+
45
+ ## Quick start (React Native)
46
+
47
+ Wrap the app in `AdaxonProvider`. Screens use `useAdaxon()`.
48
+
49
+ Tracking does **not** write React state, so `logEvent` / `track` will not re-render the tree.
50
+
51
+ ### Configuration
52
+
53
+ The SDK owns the tracker host and API paths (`/event/tracking`, `/api/mts/recommendations`, `/click/tracking`). The app only provides credentials.
54
+
55
+ | Field | Required | Default |
56
+ |---|---|---|
57
+ | `appId` | **Yes** | None. MTS app id (`adx_…`). |
58
+ | `sdkKey` | **Yes** | None. Sent as the `x-sdk-key` header. |
59
+ | `storage` | No | In-memory. Queue and anonymous id are lost on app restart. |
60
+ | `timeoutMs` | No | `10000` |
61
+ | `testMode` | No | `false` (`sdk_version` gets a `-test` suffix when true) |
62
+ | `externalUserId` | No | Unset |
63
+
64
+ Pass `storage` in production so the anonymous id and queued events survive restart. 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
+
66
+ ```tsx
67
+ import {
68
+ AdaxonProvider,
69
+ AdxEvent,
70
+ useAdaxon,
71
+ type RecommendationOffer,
72
+ } from 'react-native-adaxon';
73
+ import * as SecureStore from 'expo-secure-store';
74
+ import { Button, Text, View } from 'react-native';
75
+
76
+ function App() {
77
+ return (
78
+ <AdaxonProvider
79
+ configuration={{
80
+ appId: 'adx_your_app_id',
81
+ sdkKey: 'your_sdk_key',
82
+ 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
+ }}
89
+ >
90
+ <HomeScreen />
91
+ </AdaxonProvider>
92
+ );
93
+ }
94
+
95
+ function HomeScreen() {
96
+ const {
97
+ isInitialized,
98
+ logEvent,
99
+ trackPurchase,
100
+ trackAddToCart,
101
+ loadRecommendations,
102
+ showOffer,
103
+ } = useAdaxon();
104
+
105
+ if (!isInitialized) {
106
+ return <Text>Initializing…</Text>;
107
+ }
108
+
109
+ return (
110
+ <View>
111
+ <Button
112
+ title="Custom event"
113
+ onPress={() =>
114
+ logEvent('level_completed', {
115
+ level_number: 10,
116
+ coins_used: 50,
117
+ })
118
+ }
119
+ />
120
+ <Button
121
+ title="Purchase"
122
+ onPress={() =>
123
+ trackPurchase({
124
+ order_id: 'ORD123',
125
+ amount: 999,
126
+ currency: 'INR',
127
+ })
128
+ }
129
+ />
130
+ <Button
131
+ title="Add to cart"
132
+ onPress={() =>
133
+ trackAddToCart({
134
+ product_id: 'P123',
135
+ price: 499,
136
+ currency: 'INR',
137
+ })
138
+ }
139
+ />
140
+ <Button
141
+ title="Load offers"
142
+ onPress={async () => {
143
+ const offers: RecommendationOffer[] = await loadRecommendations(
144
+ 'recommendation_view',
145
+ { placement: 'home' }
146
+ );
147
+ if (offers[0]) {
148
+ await showOffer(offers[0]);
149
+ }
150
+ }}
151
+ />
152
+ </View>
153
+ );
154
+ }
155
+ ```
156
+
157
+ Do **not** pass device or app fields. The SDK attaches `device` and `app` automatically on every event.
158
+
159
+ ### Non-component boot
160
+
161
+ `Adaxon.initialize()` is still available if you are not inside a React tree. Prefer the provider in React Native apps.
162
+
163
+ ```ts
164
+ import { Adaxon } from 'react-native-adaxon';
165
+
166
+ await Adaxon.initialize({
167
+ appId: 'adx_your_app_id',
168
+ sdkKey: 'your_sdk_key',
169
+ });
170
+
171
+ await Adaxon.logEvent('level_completed', { level_number: 10 });
172
+ ```
173
+
174
+ ## API
175
+
176
+ | Method | What it does |
177
+ |---|---|
178
+ | `AdaxonProvider` | Starts the SDK once when the app mounts |
179
+ | `useAdaxon()` | Hook for screens: track, recommendations, offers |
180
+ | `logEvent(name, values)` | Custom or predefined event |
181
+ | `trackPurchase(params)` | Sends `adx_purchase` |
182
+ | `trackAddToCart(params)` | Sends `adx_add_to_cart` |
183
+ | `trackLogin(params)` | Sends `adx_login` |
184
+ | `trackCompleteRegistration(params)` | Sends `adx_complete_registration` |
185
+ | `track` / `trackBatch` | Send immediately. `trackBatch` accepts 1–100 events |
186
+ | `enqueueEvent(name, properties)` | Store an event locally; does not send yet |
187
+ | `processPendingEvents()` | Send queued events in batches of up to 100 |
188
+ | `loadRecommendations(eventName, properties)` | POST `/api/mts/recommendations` |
189
+ | `showOffer(offer)` | Opens the offer URL and tracks `offer_opened` |
190
+ | `setExternalUserId(id)` | Attaches `external_user_id` on later events |
191
+
192
+ Predefined names: `AdxEvent.PURCHASE`, `AdxEvent.ADD_TO_CART`, `AdxEvent.LOGIN`, `AdxEvent.COMPLETE_REGISTRATION`.
193
+
194
+ ## Event queue and batch processing
195
+
196
+ `track` and `trackBatch` send immediately. Use `enqueueEvent` / `processPendingEvents` when events should wait in the local queue.
197
+
198
+ ```ts
199
+ const { enqueueEvent, processPendingEvents } = useAdaxon();
200
+
201
+ await enqueueEvent('level_completed', { level_number: 10 });
202
+ const result = await processPendingEvents();
203
+ // result.status: 'completed' | 'stopped' | 'already_processing'
204
+ ```
205
+
206
+ How it works:
207
+
208
+ ```text
209
+ Persistent event queue
210
+ ↓
211
+ Get next ≤ 100 events (FIFO)
212
+ ↓
213
+ POST /event/tracking
214
+ ↓
215
+ Success → remove those events → next batch
216
+ Failure → keep events → stop (retry later)
217
+ ```
218
+
219
+ Rules:
220
+
221
+ - Pending events are stored through the `storage` adapter, so they survive app restart.
222
+ - Events are processed in creation order (FIFO).
223
+ - Each request contains at most **100** events. A queue of 250 is sent as 100 + 100 + 50.
224
+ - Events are removed only after the API confirms success (`success: true`). On network, HTTP, or unknown errors they stay in the queue.
225
+ - Only one processor runs at a time for the same app. A second `processPendingEvents()` call returns `already_processing`.
226
+ - 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.
228
+ - Pass `removeItem` on the storage adapter so successful events can be deleted. If it is omitted, the SDK writes an empty value instead.
229
+
230
+ Expo SecureStore has a small per-key size limit. For a large event queue, use a storage backend that can hold JSON payloads (for example AsyncStorage) and keep SecureStore for the anonymous id if you want.
231
+
232
+ ## Automatic device information
233
+
234
+ ```json
235
+ {
236
+ "device": {
237
+ "platform": "android",
238
+ "os_version": "14",
239
+ "brand": "Samsung",
240
+ "model": "S23",
241
+ "language": "en",
242
+ "timezone": "Asia/Kolkata"
243
+ }
244
+ }
245
+ ```
246
+
247
+ Values come from React Native `Platform` (`src/platform/reactNative.ts`). They are read **once** and cached. Missing optional fields are omitted and never block the request.
248
+
249
+ ## Automatic app information
250
+
251
+ ```json
252
+ {
253
+ "app": {
254
+ "package_name": "com.example.app",
255
+ "app_version": "2.4.1",
256
+ "sdk_version": "0.1.1"
257
+ }
258
+ }
259
+ ```
260
+
261
+ `package_name` and `app_version` are read from native application APIs (Android package / iOS bundle). `sdk_version` is always set by the SDK. Do **not** pass these fields in `logEvent`, `track`, or `trackBatch` — every event, including each item in a batch, receives the same `app` object automatically. Missing optional fields are omitted and never block the request.
262
+
263
+ ## How the folders work
264
+
265
+ ```text
266
+ App screen
267
+ │ useAdaxon().logEvent(...) or enqueueEvent(...) → processPendingEvents()
268
+ ▼
269
+ components/ + hooks/ + state/ React layer (init only)
270
+ ▼
271
+ AdaxonSdk.ts Public methods
272
+ ▼
273
+ core/AdaxonClient.ts Wires services
274
+ ▼
275
+ services/ Validate, build JSON, queue, batch, HTTPS
276
+ ▼
277
+ MTS tracker
278
+ ```
279
+
280
+ | Folder | Role |
281
+ |---|---|
282
+ | `src/index.ts` | Public exports (`Adaxon`, `AdaxonProvider`, `useAdaxon`) |
283
+ | `src/AdaxonSdk.ts` | SDK facade (`initialize`, `logEvent`, queue, offers) |
284
+ | `src/components/` | `AdaxonProvider` |
285
+ | `src/hooks/` | `useAdaxon()` |
286
+ | `src/state/` | Init status only — no event list in React state |
287
+ | `src/core/` | `AdaxonClient` — connects auth, events, queue, HTTP |
288
+ | `src/services/` | Auth, device, app, event build/send, batch processor, network |
289
+ | `src/platform/` | Android/iOS fields. **Only `react-native` import** |
290
+ | `src/config/` | Tracker host, API paths, timeouts, storage key rules |
291
+ | `src/validation/` | Event name, property, and batch-size rules |
292
+ | `src/storage/` | Anonymous user id and persistent event queue |
293
+ | `example/src/` | Demo app: `App.tsx`, `screens/`, `components/` |
294
+
295
+ Screens belong in the **host app**, not in the SDK.
296
+
297
+ Longer walkthrough for managers and teammates: [ARCHITECTURE.md](./ARCHITECTURE.md).
298
+ Framework cost vs vanilla JS is checked by `src/__tests__/performance.framework.test.tsx`.
299
+
300
+ ## Android notes
301
+
302
+ Credential `platform` in Adaxon must allow Android (`android` or `react_native`).
303
+
304
+ ## Local development (this repo)
305
+
306
+ ```sh
307
+ cd adaxon-sdk
308
+ corepack enable
309
+ yarn
310
+ yarn typecheck
311
+ yarn test --watchman=false
312
+ yarn prepare
313
+ yarn example android:emulator
314
+ ```
315
+
316
+ ## Publish to npm
317
+
318
+ ```sh
319
+ npm login
320
+ yarn publish:test:dry # dry-run testing package
321
+ yarn publish:test # publishes react-native-test-adx
322
+ yarn publish:prod:dry # dry-run production package
323
+ yarn publish:prod # publishes react-native-adaxon
324
+ ```
325
+
326
+ | Environment | Install |
327
+ |---|---|
328
+ | Testing | `npm install react-native-test-adx` |
329
+ | Production | `npm install react-native-adaxon` |
330
+
331
+ ## License
332
+
333
+ MIT
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+
3
+ import { AdaxonClient } from "./core/AdaxonClient.js";
4
+ import { MEDIATION_PROVIDER, OFFER_OPENED_EVENT } from "./config/ApiConstants.js";
5
+ import { AdaxonSdkError } from "./errors/AdaxonSdkError.js";
6
+ import { detectReactNativePlatform, reactNativeAppInfoReader, reactNativeDeviceReader, reactNativeNetworkInfoReader, reactNativeUrlOpener } from "./platform/reactNative.js";
7
+ import { buildAddToCartProperties, buildPurchaseProperties, normalizeEventValues } from "./services/event/normalizeProperties.js";
8
+ import { AdxEvent } from "./services/event/predefined.js";
9
+ export class AdaxonSdk {
10
+ static sharedInstance = new AdaxonSdk();
11
+ client = null;
12
+ testMode = false;
13
+ static get shared() {
14
+ return AdaxonSdk.sharedInstance;
15
+ }
16
+ isInitialized() {
17
+ return this.client !== null;
18
+ }
19
+ async initialize(configuration, listener) {
20
+ try {
21
+ this.client = new AdaxonClient({
22
+ ...configuration,
23
+ platform: detectReactNativePlatform(),
24
+ deviceInfoReader: reactNativeDeviceReader,
25
+ appInfoReader: reactNativeAppInfoReader,
26
+ networkInfoReader: reactNativeNetworkInfoReader
27
+ });
28
+ this.testMode = Boolean(configuration.testMode);
29
+ listener?.onInitializationComplete();
30
+ this.client.processPendingEvents().catch(() => undefined);
31
+ } catch (error) {
32
+ this.reset();
33
+ const message = AdaxonSdkError.messageOf(error, 'Initialization failed.');
34
+ const code = error instanceof AdaxonSdkError && error.code === 'INVALID_CONFIG' ? 'INVALID_CONFIG' : 'UNKNOWN';
35
+ listener?.onInitializationFailed(code, message);
36
+ throw error;
37
+ }
38
+ }
39
+ async track(eventName, properties = {}, options = {}) {
40
+ return this.requireClient().track(eventName, properties, options);
41
+ }
42
+ async trackBatch(events) {
43
+ return this.requireClient().trackBatch(events);
44
+ }
45
+ async enqueueEvent(eventName, properties = {}, options = {}) {
46
+ await this.requireClient().enqueueEvent(eventName, properties, options);
47
+ }
48
+ async processPendingEvents() {
49
+ return this.requireClient().processPendingEvents();
50
+ }
51
+ async logEvent(eventName, values = {}, options = {}) {
52
+ return this.track(eventName, normalizeEventValues(values), options);
53
+ }
54
+ async trackPurchase(params, options = {}) {
55
+ return this.logEvent(AdxEvent.PURCHASE, buildPurchaseProperties(params), options);
56
+ }
57
+ async trackAddToCart(params, options = {}) {
58
+ return this.logEvent(AdxEvent.ADD_TO_CART, buildAddToCartProperties(params), options);
59
+ }
60
+ async trackLogin(params = {}, options = {}) {
61
+ return this.logEvent(AdxEvent.LOGIN, params, options);
62
+ }
63
+ async trackCompleteRegistration(params = {}, options = {}) {
64
+ return this.logEvent(AdxEvent.COMPLETE_REGISTRATION, params, options);
65
+ }
66
+ async loadRecommendations(eventName, properties = {}, listener) {
67
+ try {
68
+ const response = await this.requireClient().getRecommendations({
69
+ eventName,
70
+ properties: {
71
+ ...properties,
72
+ mediation_provider: MEDIATION_PROVIDER,
73
+ test_mode: this.testMode
74
+ }
75
+ });
76
+ listener?.onRecommendationsLoaded(response.offers);
77
+ return response.offers;
78
+ } catch (error) {
79
+ const message = AdaxonSdkError.messageOf(error, 'Failed to load recommendations.');
80
+ const code = error instanceof AdaxonSdkError ? error.code : undefined;
81
+ listener?.onRecommendationsFailedToLoad(message, code);
82
+ throw error;
83
+ }
84
+ }
85
+ async showOffer(offer, listener) {
86
+ try {
87
+ const client = this.requireClient();
88
+ const targetUrl = offer.offer_url;
89
+ const canOpen = await reactNativeUrlOpener.canOpenURL(targetUrl);
90
+ if (!canOpen) {
91
+ throw new AdaxonSdkError('VALIDATION_ERROR', `Cannot open offer URL: ${targetUrl}`);
92
+ }
93
+ await reactNativeUrlOpener.openURL(targetUrl);
94
+ await client.track(OFFER_OPENED_EVENT, {
95
+ offer_id: offer.offer_id,
96
+ offer_name: offer.offer_name,
97
+ ad_type: offer.ad_type,
98
+ mediation_provider: MEDIATION_PROVIDER
99
+ });
100
+ listener?.onOfferOpened(offer);
101
+ } catch (error) {
102
+ const message = AdaxonSdkError.messageOf(error, 'Failed to open offer.');
103
+ listener?.onOfferFailedToOpen(offer, message);
104
+ throw error;
105
+ }
106
+ }
107
+ setExternalUserId(externalUserId) {
108
+ this.requireClient().setExternalUserId(externalUserId);
109
+ }
110
+ requireClient() {
111
+ if (!this.client) {
112
+ throw new AdaxonSdkError('INVALID_CONFIG', 'Adaxon SDK is not initialized. Call Adaxon.initialize(...) first.');
113
+ }
114
+ return this.client;
115
+ }
116
+ reset() {
117
+ this.client = null;
118
+ this.testMode = false;
119
+ }
120
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useMemo, useRef, useState } from 'react';
4
+ import { AdaxonSdk } from "../AdaxonSdk.js";
5
+ import { AdaxonSdkError } from "../errors/AdaxonSdkError.js";
6
+ import { AdaxonContext } from "../state/AdaxonContext.js";
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
+ export function AdaxonProvider({
9
+ configuration,
10
+ listener,
11
+ sdk = AdaxonSdk.shared,
12
+ children
13
+ }) {
14
+ const [isInitialized, setIsInitialized] = useState(sdk.isInitialized());
15
+ const [initializationError, setInitializationError] = useState(null);
16
+ const configurationRef = useRef(configuration);
17
+ const listenerRef = useRef(listener);
18
+ configurationRef.current = configuration;
19
+ listenerRef.current = listener;
20
+ useEffect(() => {
21
+ let cancelled = false;
22
+ sdk.initialize(configurationRef.current, listenerRef.current).then(() => {
23
+ if (!cancelled) {
24
+ setIsInitialized(true);
25
+ setInitializationError(null);
26
+ }
27
+ }).catch(error => {
28
+ if (!cancelled) {
29
+ setIsInitialized(false);
30
+ setInitializationError(AdaxonSdkError.messageOf(error, 'Initialization failed.'));
31
+ }
32
+ });
33
+ return () => {
34
+ cancelled = true;
35
+ };
36
+ }, [sdk]);
37
+ const value = useMemo(() => ({
38
+ isInitialized,
39
+ initializationError,
40
+ sdk
41
+ }), [initializationError, isInitialized, sdk]);
42
+ return /*#__PURE__*/_jsx(AdaxonContext.Provider, {
43
+ value: value,
44
+ children: children
45
+ });
46
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+
3
+ export const SDK_VERSION = '0.1.1';
4
+ export const MEDIATION_PROVIDER = 'mts';
5
+ export const BASE_URL = 'http://localhost:3002';
6
+ export const EVENT_TRACKING_PATH = '/event/tracking';
7
+ export const RECOMMENDATIONS_PATH = '/api/mts/recommendations';
8
+ export const CLICK_TRACKING_PATH = '/click/tracking';
9
+ export const OFFER_OPENED_EVENT = 'offer_opened';
10
+ export const DEFAULT_TIMEOUT_MS = 10_000;
11
+ export const PUBLIC_IP_LOOKUP_URL = 'https://api.ipify.org?format=json';
12
+ export const PUBLIC_IP_LOOKUP_TIMEOUT_MS = 2_000;
13
+ export const MAX_BATCH_SIZE = 100;
14
+ export const MAX_EVENT_NAME_LENGTH = 100;
15
+ export const MAX_PROPERTIES = 50;
16
+ export const MAX_PROPERTY_KEY_LENGTH = 100;
17
+ export const MAX_PROPERTY_VALUE_LENGTH = 1000;
18
+ export const EVENT_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ export const ANONYMOUS_ID_KEY_PREFIX = 'adaxon_sdk.anonymous_id.';
4
+ export const EVENT_QUEUE_META_KEY_PREFIX = 'adaxon_sdk.event_queue.meta.';
5
+ export const EVENT_QUEUE_ITEM_KEY_PREFIX = 'adaxon_sdk.event_queue.item.';
6
+
7
+ // Expo SecureStore and Keychain-backed adapters only accept alphanumerics
8
+ // plus `.`, `-` and `_`, so app ids are sanitized before use as a key.
9
+ const UNSAFE_STORAGE_KEY_CHARS = /[^A-Za-z0-9._-]/g;
10
+ export function sanitizeStorageId(appId) {
11
+ return appId.replace(UNSAFE_STORAGE_KEY_CHARS, '_');
12
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+
3
+ import { BASE_URL, CLICK_TRACKING_PATH } from "../config/ApiConstants.js";
4
+ import { AuthenticationService } from "../services/authentication/AuthenticationService.js";
5
+ import { AppInfoService } from "../services/app/AppInfoService.js";
6
+ import { DeviceInfoService } from "../services/device/DeviceInfoService.js";
7
+ import { BatchProcessor } from "../services/event/BatchProcessor.js";
8
+ import { EventBuilder } from "../services/event/EventBuilder.js";
9
+ import { EventQueueService } from "../services/event/EventQueueService.js";
10
+ import { EventService } from "../services/event/EventService.js";
11
+ import { ApiClient } from "../services/network/ApiClient.js";
12
+ import { NetworkInfoService } from "../services/network/NetworkInfoService.js";
13
+ import { AnonymousIdService } from "../storage/AnonymousIdService.js";
14
+ import { EventQueueStorage } from "../storage/EventQueueStorage.js";
15
+ import { EventValidator } from "../validation/EventValidator.js";
16
+ export class AdaxonClient {
17
+ constructor(config) {
18
+ const auth = new AuthenticationService(config);
19
+ this.validator = new EventValidator();
20
+ const api = new ApiClient({
21
+ sdkKey: auth.sdkKey,
22
+ timeoutMs: auth.timeoutMs,
23
+ fetch: config.fetch
24
+ });
25
+ const networkInfo = new NetworkInfoService(config.networkInfoReader);
26
+ this.builder = new EventBuilder({
27
+ appId: auth.appId,
28
+ deviceInfo: new DeviceInfoService(config.platform ?? 'web', config.deviceInfoReader),
29
+ appInfo: new AppInfoService(Boolean(config.testMode), config.appInfoReader),
30
+ networkInfo,
31
+ anonymousId: new AnonymousIdService(auth.appId, config.storage),
32
+ validator: this.validator,
33
+ externalUserId: config.externalUserId
34
+ });
35
+ this.events = new EventService({
36
+ api,
37
+ builder: this.builder,
38
+ validator: this.validator
39
+ });
40
+ this.eventQueue = new EventQueueService(new EventQueueStorage(auth.appId, config.storage));
41
+ this.batchProcessor = new BatchProcessor({
42
+ queue: this.eventQueue,
43
+ sendBatch: payloads => this.events.sendPayloads(payloads)
44
+ });
45
+ }
46
+ track(eventName, properties = {}, options = {}) {
47
+ return this.events.track(eventName, properties, options);
48
+ }
49
+ trackBatch(events) {
50
+ return this.events.trackBatch(events);
51
+ }
52
+ async enqueueEvent(eventName, properties = {}, options = {}) {
53
+ const payload = await this.builder.build({
54
+ eventName,
55
+ properties,
56
+ ...options
57
+ });
58
+ await this.eventQueue.add(payload);
59
+ }
60
+ processPendingEvents() {
61
+ return this.batchProcessor.process();
62
+ }
63
+ getRecommendations(input) {
64
+ return this.events.getRecommendations(input);
65
+ }
66
+ createClickUrl(attribution) {
67
+ this.validator.validateClickAttribution(attribution);
68
+ const query = this.toQueryString({
69
+ offer_id: attribution.offerId,
70
+ affiliate_id: attribution.affiliateId,
71
+ advertiser_id: attribution.advertiserId,
72
+ aff_click_id: attribution.affClickId,
73
+ source: attribution.source,
74
+ aff_sub: attribution.affSub,
75
+ aff_sub2: attribution.affSub2,
76
+ aff_sub3: attribution.affSub3,
77
+ aff_sub4: attribution.affSub4,
78
+ aff_sub5: attribution.affSub5
79
+ });
80
+ return `${BASE_URL}${CLICK_TRACKING_PATH}?${query}`;
81
+ }
82
+ setExternalUserId(externalUserId) {
83
+ this.builder.setExternalUserId(externalUserId);
84
+ }
85
+ toQueryString(params) {
86
+ return Object.entries(params).filter(([, value]) => value !== undefined).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join('&');
87
+ }
88
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ export class AdaxonSdkError extends Error {
4
+ constructor(code, message, options = {}) {
5
+ super(message, {
6
+ cause: options.cause
7
+ });
8
+ this.name = 'AdaxonSdkError';
9
+ this.code = code;
10
+ this.status = options.status;
11
+ this.details = options.details;
12
+ }
13
+ static messageOf(error, fallback) {
14
+ return error instanceof Error ? error.message : fallback;
15
+ }
16
+ static fromHttp(status, body) {
17
+ const code = status === 401 ? 'AUTHENTICATION_ERROR' : 'HTTP_ERROR';
18
+ return new AdaxonSdkError(code, body?.message || `Adaxon request failed with HTTP ${status}.`, {
19
+ status,
20
+ details: body?.errors ?? body
21
+ });
22
+ }
23
+ }