react-native-test-adx 0.1.13 → 0.1.15
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 +6 -0
- package/lib/module/AdaxonSdk.js +131 -2
- package/lib/module/core/AdaxonClient.js +8 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/services/authentication/AuthenticationService.js +8 -0
- package/lib/module/services/event/BatchProcessor.js +37 -2
- package/lib/module/services/event/EventService.js +47 -2
- package/lib/module/services/logging/AdaxonLogger.js +241 -0
- package/lib/module/services/logging/sanitizeLogMetadata.js +45 -0
- package/lib/module/services/network/ApiClient.js +53 -4
- package/lib/typescript/src/sdkVersion.d.ts +1 -1
- package/lib/typescript/src/services/logging/AdaxonLogger.d.ts +30 -0
- package/lib/typescript/src/services/logging/sanitizeLogMetadata.d.ts +3 -0
- package/lib/typescript/src/services/network/ApiClient.d.ts +1 -0
- package/lib/typescript/src/types/AdaxonConfig.d.ts +4 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -75,9 +75,15 @@ Pass `configuration` to `AdaxonProvider`, or to `Adaxon.initialize()` when not u
|
|
|
75
75
|
| `storage` | No | In-memory | Persists offline event queue and anonymous id |
|
|
76
76
|
| `timeoutMs` | No | `10000` | HTTP timeout in ms |
|
|
77
77
|
| `testMode` | No | `false` | Sets `sdk_version` to `{version}-test` when true |
|
|
78
|
+
| `activityLogging` | No | `false` | When true, records SDK diagnostics to Sentry (testing/debugging) |
|
|
79
|
+
| `sentryDsn` | No | — | Sentry DSN when `activityLogging` is true and the host app has not initialized Sentry |
|
|
78
80
|
|
|
79
81
|
Only `appId` and `sdkKey` are required. Do not pass a tracker URL — the SDK owns the host and API paths.
|
|
80
82
|
|
|
83
|
+
For Sentry activity logging, install the optional peer dependency `@sentry/react-native` in the host app.
|
|
84
|
+
|
|
85
|
+
Activities are emitted primarily as **Sentry structured logs** (`enableLogs: true` when the SDK initializes Sentry). Breadcrumbs are added only for `warn`/`error` to enrich captured issues. If structured logging is unavailable (for example an older host Sentry client), a small set of milestone activities may use `captureMessage` instead so they still appear independently without flooding issue volume.
|
|
86
|
+
|
|
81
87
|
The SDK attaches `device`, `app`, `user.anonymous_id`, and network fields automatically. Do not pass them in event calls.
|
|
82
88
|
|
|
83
89
|
### Persistent storage (optional)
|
package/lib/module/AdaxonSdk.js
CHANGED
|
@@ -7,6 +7,7 @@ import { AdaxonSdkError } from "./errors/AdaxonSdkError.js";
|
|
|
7
7
|
import { detectReactNativePlatform, reactNativeAppInfoReader, reactNativeDeviceReader, reactNativeNetworkInfoReader, reactNativeNetworkStatusReader, reactNativeUrlOpener } from "./platform/reactNative.js";
|
|
8
8
|
import { buildAddToCartProperties, buildPurchaseProperties, normalizeEventValues } from "./services/event/normalizeProperties.js";
|
|
9
9
|
import { AdxEvent } from "./services/event/predefined.js";
|
|
10
|
+
import { AdaxonLogger, logSdkActivity } from "./services/logging/AdaxonLogger.js";
|
|
10
11
|
function openableOfferUrl(url) {
|
|
11
12
|
return url.replace(/[{}]/g, brace => brace === '{' ? '%7B' : '%7D');
|
|
12
13
|
}
|
|
@@ -18,11 +19,26 @@ export class AdaxonSdk {
|
|
|
18
19
|
return this.client !== null;
|
|
19
20
|
}
|
|
20
21
|
async initialize(configuration, listener) {
|
|
22
|
+
const platform = detectReactNativePlatform();
|
|
23
|
+
AdaxonLogger.configure({
|
|
24
|
+
enabled: Boolean(configuration.activityLogging),
|
|
25
|
+
dsn: configuration.sentryDsn,
|
|
26
|
+
platform,
|
|
27
|
+
testMode: Boolean(configuration.testMode)
|
|
28
|
+
});
|
|
29
|
+
logSdkActivity({
|
|
30
|
+
eventName: 'sdk.initialize.started',
|
|
31
|
+
level: 'info',
|
|
32
|
+
metadata: {
|
|
33
|
+
app_id: configuration.appId,
|
|
34
|
+
test_mode: Boolean(configuration.testMode)
|
|
35
|
+
}
|
|
36
|
+
});
|
|
21
37
|
try {
|
|
22
38
|
const previous = this.client;
|
|
23
39
|
const client = new AdaxonClient({
|
|
24
40
|
...configuration,
|
|
25
|
-
platform
|
|
41
|
+
platform,
|
|
26
42
|
deviceInfoReader: reactNativeDeviceReader,
|
|
27
43
|
appInfoReader: reactNativeAppInfoReader,
|
|
28
44
|
networkInfoReader: reactNativeNetworkInfoReader,
|
|
@@ -32,15 +48,31 @@ export class AdaxonSdk {
|
|
|
32
48
|
previous?.destroy();
|
|
33
49
|
this.client = client;
|
|
34
50
|
this.testMode = Boolean(configuration.testMode);
|
|
51
|
+
logSdkActivity({
|
|
52
|
+
eventName: 'sdk.initialize.completed',
|
|
53
|
+
level: 'info',
|
|
54
|
+
metadata: {
|
|
55
|
+
network_available: client.isNetworkAvailable()
|
|
56
|
+
}
|
|
57
|
+
});
|
|
35
58
|
listener?.onInitializationComplete();
|
|
36
59
|
if (client.isNetworkAvailable()) {
|
|
37
60
|
client.processPendingEvents().catch(() => undefined);
|
|
38
61
|
}
|
|
39
62
|
} catch (error) {
|
|
40
|
-
this.destroy();
|
|
41
63
|
const message = AdaxonSdkError.messageOf(error, 'Initialization failed.');
|
|
42
64
|
const code = error instanceof AdaxonSdkError && error.code === 'INVALID_CONFIG' ? 'INVALID_CONFIG' : 'UNKNOWN';
|
|
65
|
+
logSdkActivity({
|
|
66
|
+
eventName: 'sdk.initialize.failed',
|
|
67
|
+
level: code === 'INVALID_CONFIG' ? 'warn' : 'error',
|
|
68
|
+
metadata: {
|
|
69
|
+
error_code: code
|
|
70
|
+
},
|
|
71
|
+
error,
|
|
72
|
+
captureAsEvent: code !== 'INVALID_CONFIG'
|
|
73
|
+
});
|
|
43
74
|
listener?.onInitializationFailed(code, message);
|
|
75
|
+
this.destroy();
|
|
44
76
|
throw error;
|
|
45
77
|
}
|
|
46
78
|
}
|
|
@@ -75,6 +107,10 @@ export class AdaxonSdk {
|
|
|
75
107
|
return this.logEvent(AdxEvent.COMPLETE_REGISTRATION, params, options);
|
|
76
108
|
}
|
|
77
109
|
async showAd(options = {}) {
|
|
110
|
+
logSdkActivity({
|
|
111
|
+
eventName: 'ad.show.started',
|
|
112
|
+
level: 'info'
|
|
113
|
+
});
|
|
78
114
|
try {
|
|
79
115
|
const {
|
|
80
116
|
offers,
|
|
@@ -82,6 +118,13 @@ export class AdaxonSdk {
|
|
|
82
118
|
} = await this.loadRecommendations();
|
|
83
119
|
if (offers.length === 0) {
|
|
84
120
|
console.warn('[Adaxon] No offer available.');
|
|
121
|
+
logSdkActivity({
|
|
122
|
+
eventName: 'ad.show.failed',
|
|
123
|
+
level: 'info',
|
|
124
|
+
metadata: {
|
|
125
|
+
reason: 'no_offer'
|
|
126
|
+
}
|
|
127
|
+
});
|
|
85
128
|
return {
|
|
86
129
|
shown: false,
|
|
87
130
|
reason: 'no_offer'
|
|
@@ -91,6 +134,13 @@ export class AdaxonSdk {
|
|
|
91
134
|
const offer = offers.find(item => !excluded.has(item.offer_id));
|
|
92
135
|
if (!offer) {
|
|
93
136
|
console.warn('[Adaxon] No eligible offer available.');
|
|
137
|
+
logSdkActivity({
|
|
138
|
+
eventName: 'ad.show.failed',
|
|
139
|
+
level: 'info',
|
|
140
|
+
metadata: {
|
|
141
|
+
reason: 'no_eligible_offer'
|
|
142
|
+
}
|
|
143
|
+
});
|
|
94
144
|
return {
|
|
95
145
|
shown: false,
|
|
96
146
|
reason: 'no_eligible_offer'
|
|
@@ -100,11 +150,34 @@ export class AdaxonSdk {
|
|
|
100
150
|
const displayed = presentInterstitial({
|
|
101
151
|
offer,
|
|
102
152
|
interact: () => {
|
|
153
|
+
logSdkActivity({
|
|
154
|
+
eventName: 'ad.click',
|
|
155
|
+
level: 'info',
|
|
156
|
+
metadata: {
|
|
157
|
+
offer_id: offer.offer_id
|
|
158
|
+
}
|
|
159
|
+
});
|
|
103
160
|
this.openOfferUrl(offer, requestEventId).then(() => options.onOfferClicked?.(offer.offer_id)).catch(error => {
|
|
104
161
|
console.warn('[Adaxon] Failed to open offer URL.', AdaxonSdkError.messageOf(error, 'Failed to open offer.'));
|
|
162
|
+
logSdkActivity({
|
|
163
|
+
eventName: 'ad.click.failed',
|
|
164
|
+
level: 'warn',
|
|
165
|
+
metadata: {
|
|
166
|
+
offer_id: offer.offer_id
|
|
167
|
+
},
|
|
168
|
+
error
|
|
169
|
+
});
|
|
105
170
|
});
|
|
106
171
|
},
|
|
107
172
|
onDismiss: dismissReason => {
|
|
173
|
+
logSdkActivity({
|
|
174
|
+
eventName: 'ad.dismissed',
|
|
175
|
+
level: 'info',
|
|
176
|
+
metadata: {
|
|
177
|
+
offer_id: offer.offer_id,
|
|
178
|
+
dismiss_reason: dismissReason
|
|
179
|
+
}
|
|
180
|
+
});
|
|
108
181
|
resolve({
|
|
109
182
|
shown: true,
|
|
110
183
|
offerId: offer.offer_id,
|
|
@@ -114,16 +187,38 @@ export class AdaxonSdk {
|
|
|
114
187
|
});
|
|
115
188
|
if (!displayed) {
|
|
116
189
|
console.warn('[Adaxon] Interstitial host is not mounted. Wrap the app in AdaxonProvider.');
|
|
190
|
+
logSdkActivity({
|
|
191
|
+
eventName: 'ad.show.failed',
|
|
192
|
+
level: 'warn',
|
|
193
|
+
metadata: {
|
|
194
|
+
reason: 'not_mounted'
|
|
195
|
+
}
|
|
196
|
+
});
|
|
117
197
|
resolve({
|
|
118
198
|
shown: false,
|
|
119
199
|
reason: 'not_mounted'
|
|
120
200
|
});
|
|
121
201
|
return;
|
|
122
202
|
}
|
|
203
|
+
logSdkActivity({
|
|
204
|
+
eventName: 'ad.displayed',
|
|
205
|
+
level: 'info',
|
|
206
|
+
metadata: {
|
|
207
|
+
offer_id: offer.offer_id
|
|
208
|
+
}
|
|
209
|
+
});
|
|
123
210
|
this.trackRecommendationEvent(CREATIVE_VIEW_EVENT, requestEventId).catch(() => undefined);
|
|
124
211
|
});
|
|
125
212
|
} catch (error) {
|
|
126
213
|
console.warn('[Adaxon] Failed to show ad.', AdaxonSdkError.messageOf(error, 'Failed to show ad.'));
|
|
214
|
+
logSdkActivity({
|
|
215
|
+
eventName: 'ad.show.failed',
|
|
216
|
+
level: 'warn',
|
|
217
|
+
metadata: {
|
|
218
|
+
error_code: error instanceof AdaxonSdkError ? error.code : 'UNKNOWN'
|
|
219
|
+
},
|
|
220
|
+
error
|
|
221
|
+
});
|
|
127
222
|
if (this.testMode) {
|
|
128
223
|
throw error;
|
|
129
224
|
}
|
|
@@ -137,11 +232,16 @@ export class AdaxonSdk {
|
|
|
137
232
|
return this.client?.isNetworkAvailable() ?? false;
|
|
138
233
|
}
|
|
139
234
|
destroy() {
|
|
235
|
+
logSdkActivity({
|
|
236
|
+
eventName: 'sdk.destroyed',
|
|
237
|
+
level: 'debug'
|
|
238
|
+
});
|
|
140
239
|
dismissInterstitial();
|
|
141
240
|
dismissOfferBrowser();
|
|
142
241
|
this.client?.destroy();
|
|
143
242
|
this.client = null;
|
|
144
243
|
this.testMode = false;
|
|
244
|
+
AdaxonLogger.reset();
|
|
145
245
|
}
|
|
146
246
|
loadRecommendations() {
|
|
147
247
|
return this.requireClient().getRecommendations({
|
|
@@ -153,8 +253,22 @@ export class AdaxonSdk {
|
|
|
153
253
|
await this.trackRecommendationEvent(CREATIVE_CLICK_EVENT, parentEventId);
|
|
154
254
|
const offerUrl = openableOfferUrl(offer.offer_url);
|
|
155
255
|
if (presentOfferBrowser(offerUrl)) {
|
|
256
|
+
logSdkActivity({
|
|
257
|
+
eventName: 'ad.offer_browser.opened',
|
|
258
|
+
level: 'debug',
|
|
259
|
+
metadata: {
|
|
260
|
+
offer_id: offer.offer_id
|
|
261
|
+
}
|
|
262
|
+
});
|
|
156
263
|
return;
|
|
157
264
|
}
|
|
265
|
+
logSdkActivity({
|
|
266
|
+
eventName: 'ad.offer_url.opened',
|
|
267
|
+
level: 'debug',
|
|
268
|
+
metadata: {
|
|
269
|
+
offer_id: offer.offer_id
|
|
270
|
+
}
|
|
271
|
+
});
|
|
158
272
|
await reactNativeUrlOpener.openURL(offerUrl);
|
|
159
273
|
dismissInterstitial('click');
|
|
160
274
|
}
|
|
@@ -165,8 +279,23 @@ export class AdaxonSdk {
|
|
|
165
279
|
}, {
|
|
166
280
|
eventType: RECOMMENDATION_EVENT_TYPE
|
|
167
281
|
});
|
|
282
|
+
logSdkActivity({
|
|
283
|
+
eventName: eventName === CREATIVE_VIEW_EVENT ? 'ad.impression.tracked' : 'ad.click.tracked',
|
|
284
|
+
level: 'debug',
|
|
285
|
+
metadata: {
|
|
286
|
+
parent_event_id: parentEventId
|
|
287
|
+
}
|
|
288
|
+
});
|
|
168
289
|
} catch (error) {
|
|
169
290
|
console.warn(`[Adaxon] Failed to log ${eventName}.`, AdaxonSdkError.messageOf(error, `Failed to log ${eventName}.`));
|
|
291
|
+
logSdkActivity({
|
|
292
|
+
eventName: 'tracking.recommendation_event.failed',
|
|
293
|
+
level: 'warn',
|
|
294
|
+
metadata: {
|
|
295
|
+
mts_event_name: eventName
|
|
296
|
+
},
|
|
297
|
+
error
|
|
298
|
+
});
|
|
170
299
|
}
|
|
171
300
|
}
|
|
172
301
|
requireClient() {
|
|
@@ -12,6 +12,7 @@ import { ApiClient } from "../services/network/ApiClient.js";
|
|
|
12
12
|
import { NetworkInfoService } from "../services/network/NetworkInfoService.js";
|
|
13
13
|
import { NetworkService } from "../services/network/NetworkService.js";
|
|
14
14
|
import { EventQueueStorage } from "../storage/EventQueueStorage.js";
|
|
15
|
+
import { logSdkActivity } from "../services/logging/AdaxonLogger.js";
|
|
15
16
|
import { EventValidator } from "../validation/EventValidator.js";
|
|
16
17
|
export class AdaxonClient {
|
|
17
18
|
started = false;
|
|
@@ -73,6 +74,13 @@ export class AdaxonClient {
|
|
|
73
74
|
}
|
|
74
75
|
this.validator.validateBatch(events);
|
|
75
76
|
const payloads = await Promise.all(events.map(event => this.builder.build(event)));
|
|
77
|
+
logSdkActivity({
|
|
78
|
+
eventName: 'tracking.events.queued_offline',
|
|
79
|
+
level: 'info',
|
|
80
|
+
metadata: {
|
|
81
|
+
event_count: payloads.length
|
|
82
|
+
}
|
|
83
|
+
});
|
|
76
84
|
for (const payload of payloads) {
|
|
77
85
|
await this.eventQueue.add(payload);
|
|
78
86
|
}
|
package/lib/module/sdkVersion.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { DEFAULT_TIMEOUT_MS } from "../../config/ApiConstants.js";
|
|
4
4
|
import { AdaxonSdkError } from "../../errors/AdaxonSdkError.js";
|
|
5
|
+
import { logSdkActivity } from "../logging/AdaxonLogger.js";
|
|
5
6
|
export class AuthenticationService {
|
|
6
7
|
constructor(config) {
|
|
7
8
|
this.validateConfig(config);
|
|
@@ -21,6 +22,13 @@ export class AuthenticationService {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
fail(message) {
|
|
25
|
+
logSdkActivity({
|
|
26
|
+
eventName: 'sdk.config.invalid',
|
|
27
|
+
level: 'warn',
|
|
28
|
+
metadata: {
|
|
29
|
+
reason: message
|
|
30
|
+
}
|
|
31
|
+
});
|
|
24
32
|
throw new AdaxonSdkError('INVALID_CONFIG', message);
|
|
25
33
|
}
|
|
26
34
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { MAX_BATCH_SIZE } from "../../config/ApiConstants.js";
|
|
4
4
|
import { AdaxonSdkError } from "../../errors/AdaxonSdkError.js";
|
|
5
|
+
import { logSdkActivity } from "../logging/AdaxonLogger.js";
|
|
5
6
|
const TEMPORARY_FAILURE_CODES = new Set(['NETWORK_ERROR', 'TIMEOUT', 'HTTP_ERROR', 'AUTHENTICATION_ERROR']);
|
|
6
7
|
export class BatchProcessor {
|
|
7
8
|
constructor(config) {
|
|
@@ -10,14 +11,30 @@ export class BatchProcessor {
|
|
|
10
11
|
}
|
|
11
12
|
async process() {
|
|
12
13
|
if (!this.queue.tryAcquireProcessorLock()) {
|
|
14
|
+
logSdkActivity({
|
|
15
|
+
eventName: 'tracking.queue.processor.busy',
|
|
16
|
+
level: 'debug'
|
|
17
|
+
});
|
|
13
18
|
return this.result('already_processing');
|
|
14
19
|
}
|
|
15
20
|
let batchesSent = 0;
|
|
16
21
|
let eventsProcessed = 0;
|
|
22
|
+
logSdkActivity({
|
|
23
|
+
eventName: 'tracking.queue.processor.started',
|
|
24
|
+
level: 'debug'
|
|
25
|
+
});
|
|
17
26
|
try {
|
|
18
27
|
while (true) {
|
|
19
28
|
const events = await this.queue.getNextBatch(MAX_BATCH_SIZE);
|
|
20
29
|
if (events.length === 0) {
|
|
30
|
+
logSdkActivity({
|
|
31
|
+
eventName: 'tracking.queue.processor.completed',
|
|
32
|
+
level: 'debug',
|
|
33
|
+
metadata: {
|
|
34
|
+
batches_sent: batchesSent,
|
|
35
|
+
events_processed: eventsProcessed
|
|
36
|
+
}
|
|
37
|
+
});
|
|
21
38
|
return this.result('completed', {
|
|
22
39
|
batchesSent,
|
|
23
40
|
eventsProcessed
|
|
@@ -42,8 +59,26 @@ export class BatchProcessor {
|
|
|
42
59
|
}
|
|
43
60
|
}
|
|
44
61
|
stopped(batchesSent, eventsProcessed, events, error) {
|
|
45
|
-
if (error !== undefined
|
|
46
|
-
|
|
62
|
+
if (error !== undefined) {
|
|
63
|
+
const temporaryFailure = error instanceof AdaxonSdkError && TEMPORARY_FAILURE_CODES.has(error.code);
|
|
64
|
+
if (!temporaryFailure) {
|
|
65
|
+
console.error('[Adaxon] Batch processor failed unexpectedly', error);
|
|
66
|
+
}
|
|
67
|
+
logSdkActivity({
|
|
68
|
+
eventName: temporaryFailure ? 'tracking.queue.processor.stopped' : 'tracking.queue.processor.failed',
|
|
69
|
+
level: temporaryFailure ? 'warn' : 'error',
|
|
70
|
+
metadata: {
|
|
71
|
+
batches_sent: batchesSent,
|
|
72
|
+
events_processed: eventsProcessed,
|
|
73
|
+
...(temporaryFailure ? {
|
|
74
|
+
error_code: error instanceof AdaxonSdkError ? error.code : 'UNKNOWN'
|
|
75
|
+
} : {})
|
|
76
|
+
},
|
|
77
|
+
error,
|
|
78
|
+
...(!temporaryFailure ? {
|
|
79
|
+
captureAsEvent: true
|
|
80
|
+
} : {})
|
|
81
|
+
});
|
|
47
82
|
}
|
|
48
83
|
return this.result('stopped', {
|
|
49
84
|
batchesSent,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { EVENT_TRACKING_PATH, RECOMMENDATION_EVENT_TYPE, RECOMMENDATION_REQUEST_EVENT, RECOMMENDATION_RESPONSE_EVENT, RECOMMENDATIONS_PATH } from "../../config/ApiConstants.js";
|
|
4
4
|
import { AdaxonSdkError } from "../../errors/AdaxonSdkError.js";
|
|
5
|
+
import { logSdkActivity } from "../logging/AdaxonLogger.js";
|
|
5
6
|
export class EventService {
|
|
6
7
|
constructor(config) {
|
|
7
8
|
this.api = config.api;
|
|
@@ -10,14 +11,35 @@ export class EventService {
|
|
|
10
11
|
}
|
|
11
12
|
async trackBatch(events) {
|
|
12
13
|
this.validator.validateBatch(events);
|
|
14
|
+
logSdkActivity({
|
|
15
|
+
eventName: 'tracking.batch.processing',
|
|
16
|
+
level: 'debug',
|
|
17
|
+
metadata: {
|
|
18
|
+
event_count: events.length
|
|
19
|
+
}
|
|
20
|
+
});
|
|
13
21
|
const payloads = await Promise.all(events.map(event => this.builder.build(event)));
|
|
14
|
-
|
|
22
|
+
const response = await this.postEvents(payloads);
|
|
23
|
+
logSdkActivity({
|
|
24
|
+
eventName: 'tracking.batch.completed',
|
|
25
|
+
level: 'debug',
|
|
26
|
+
metadata: {
|
|
27
|
+
event_count: events.length,
|
|
28
|
+
accepted: response.accepted,
|
|
29
|
+
rejected: response.rejected
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return response;
|
|
15
33
|
}
|
|
16
34
|
sendPayloads(payloads) {
|
|
17
35
|
this.validator.validateBatch(payloads);
|
|
18
36
|
return this.postEvents(payloads);
|
|
19
37
|
}
|
|
20
38
|
async getRecommendations(properties = {}) {
|
|
39
|
+
logSdkActivity({
|
|
40
|
+
eventName: 'recommendation.request.started',
|
|
41
|
+
level: 'info'
|
|
42
|
+
});
|
|
21
43
|
const requestEvent = await this.builder.build({
|
|
22
44
|
eventName: RECOMMENDATION_REQUEST_EVENT,
|
|
23
45
|
eventType: RECOMMENDATION_EVENT_TYPE,
|
|
@@ -31,8 +53,24 @@ export class EventService {
|
|
|
31
53
|
}
|
|
32
54
|
});
|
|
33
55
|
if (!Array.isArray(response.offers)) {
|
|
34
|
-
|
|
56
|
+
const error = new AdaxonSdkError('HTTP_ERROR', 'Adaxon recommendation response is missing the offers array.');
|
|
57
|
+
logSdkActivity({
|
|
58
|
+
eventName: 'recommendation.response.invalid',
|
|
59
|
+
level: 'warn',
|
|
60
|
+
metadata: {
|
|
61
|
+
error_code: error.code
|
|
62
|
+
},
|
|
63
|
+
error
|
|
64
|
+
});
|
|
65
|
+
throw error;
|
|
35
66
|
}
|
|
67
|
+
logSdkActivity({
|
|
68
|
+
eventName: 'recommendation.response.received',
|
|
69
|
+
level: 'info',
|
|
70
|
+
metadata: {
|
|
71
|
+
offer_count: response.offers.length
|
|
72
|
+
}
|
|
73
|
+
});
|
|
36
74
|
await this.postEvents([await this.builder.build({
|
|
37
75
|
eventName: RECOMMENDATION_RESPONSE_EVENT,
|
|
38
76
|
eventType: RECOMMENDATION_EVENT_TYPE,
|
|
@@ -40,6 +78,13 @@ export class EventService {
|
|
|
40
78
|
parent_event_id: requestEvent.event_id
|
|
41
79
|
}
|
|
42
80
|
})]);
|
|
81
|
+
logSdkActivity({
|
|
82
|
+
eventName: 'recommendation.processing.completed',
|
|
83
|
+
level: 'debug',
|
|
84
|
+
metadata: {
|
|
85
|
+
offer_count: response.offers.length
|
|
86
|
+
}
|
|
87
|
+
});
|
|
43
88
|
return {
|
|
44
89
|
offers: response.offers,
|
|
45
90
|
requestEventId: requestEvent.event_id
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { SDK_VERSION } from "../../sdkVersion.js";
|
|
4
|
+
import { AdaxonSdkError } from "../../errors/AdaxonSdkError.js";
|
|
5
|
+
import { sanitizeLogMetadata } from "./sanitizeLogMetadata.js";
|
|
6
|
+
const DEFAULT_LEVEL = 'info';
|
|
7
|
+
|
|
8
|
+
/** Milestone events that fall back to captureMessage when structured logs are unavailable. */
|
|
9
|
+
const MESSAGE_FALLBACK_EVENTS = new Set(['sdk.initialize.completed', 'sdk.initialize.failed', 'recommendation.response.received', 'ad.displayed', 'ad.show.failed']);
|
|
10
|
+
/** undefined = not loaded yet; null = unavailable in this app bundle */
|
|
11
|
+
let cachedSentryModule;
|
|
12
|
+
let missingSentryPeerWarningShown = false;
|
|
13
|
+
const SENTRY_UNAVAILABLE_HINT = 'Bundled @sentry/react-native could not be loaded. Reinstall react-native-adaxon dependencies and rebuild the native app.';
|
|
14
|
+
function loadSentryModule() {
|
|
15
|
+
if (cachedSentryModule !== undefined) {
|
|
16
|
+
return cachedSentryModule;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const loaded = require('@sentry/react-native');
|
|
20
|
+
cachedSentryModule = loaded && typeof loaded.init === 'function' ? loaded : loaded.default ?? null;
|
|
21
|
+
} catch {
|
|
22
|
+
cachedSentryModule = null;
|
|
23
|
+
}
|
|
24
|
+
return cachedSentryModule;
|
|
25
|
+
}
|
|
26
|
+
function warnSentryUnavailableOnce(hasDsn) {
|
|
27
|
+
if (!hasDsn || missingSentryPeerWarningShown || typeof __DEV__ === 'undefined' || !__DEV__) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (loadSentryModule()) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
missingSentryPeerWarningShown = true;
|
|
34
|
+
console.warn(`[Adaxon] Activity logging is on and Sentry DSN is set, but ${SENTRY_UNAVAILABLE_HINT} ` + 'Until then, events appear locally as [Adaxon activity] in Metro/logcat only.');
|
|
35
|
+
}
|
|
36
|
+
function sentryLevel(level, target) {
|
|
37
|
+
if (level === 'warn') {
|
|
38
|
+
return 'warning';
|
|
39
|
+
}
|
|
40
|
+
if (level === 'error') {
|
|
41
|
+
return 'error';
|
|
42
|
+
}
|
|
43
|
+
if (level === 'debug') {
|
|
44
|
+
return target === 'breadcrumb' ? 'debug' : 'info';
|
|
45
|
+
}
|
|
46
|
+
return 'info';
|
|
47
|
+
}
|
|
48
|
+
function shouldCaptureMessage(input, level, structuredLogsUsed) {
|
|
49
|
+
if (structuredLogsUsed || level === 'debug') {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (input.captureAsEvent && input.error === undefined) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return MESSAGE_FALLBACK_EVENTS.has(input.eventName);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Sentry Logs + logger.* break Metro in dev; release native builds can use them. */
|
|
59
|
+
function usesStructuredSentryLogs(platform) {
|
|
60
|
+
if (platform === 'web') {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return typeof __DEV__ !== 'undefined' && !__DEV__;
|
|
64
|
+
}
|
|
65
|
+
function usesMinimalNativeSentryInit(platform) {
|
|
66
|
+
return platform !== 'web' && (typeof __DEV__ === 'undefined' || __DEV__);
|
|
67
|
+
}
|
|
68
|
+
function emitStructuredLog(sentry, level, eventName, metadata) {
|
|
69
|
+
const loggerFn = sentry.logger?.[level];
|
|
70
|
+
if (typeof loggerFn !== 'function') {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
loggerFn.call(sentry.logger, eventName, {
|
|
74
|
+
...metadata,
|
|
75
|
+
event_name: eventName
|
|
76
|
+
});
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
function emitNativeActivityBreadcrumb(sentry, level, eventName, metadata) {
|
|
80
|
+
sentry.addBreadcrumb({
|
|
81
|
+
category: 'adaxon.activity',
|
|
82
|
+
message: eventName,
|
|
83
|
+
level: sentryLevel(level, 'breadcrumb'),
|
|
84
|
+
data: metadata,
|
|
85
|
+
timestamp: Date.now() / 1000
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function emitDevActivityFallback(level, eventName, metadata, deliveredToSentry) {
|
|
89
|
+
if (deliveredToSentry || typeof __DEV__ === 'undefined' || !__DEV__) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const consoleFn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.info;
|
|
93
|
+
const write = () => {
|
|
94
|
+
consoleFn(`[Adaxon activity] ${eventName}`, metadata);
|
|
95
|
+
};
|
|
96
|
+
const sentry = loadSentryModule();
|
|
97
|
+
if (typeof sentry?.consoleSandbox === 'function') {
|
|
98
|
+
sentry.consoleSandbox(write);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
write();
|
|
102
|
+
}
|
|
103
|
+
function activityTags(eventName, platform) {
|
|
104
|
+
return {
|
|
105
|
+
adaxon_event: eventName,
|
|
106
|
+
adaxon_platform: platform,
|
|
107
|
+
adaxon_sdk_version: SDK_VERSION
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
class AdaxonLoggerService {
|
|
111
|
+
enabled = false;
|
|
112
|
+
platform = 'web';
|
|
113
|
+
sentryReady = false;
|
|
114
|
+
configure(input) {
|
|
115
|
+
this.enabled = Boolean(input.enabled);
|
|
116
|
+
this.platform = input.platform;
|
|
117
|
+
this.sentryReady = false;
|
|
118
|
+
if (!this.enabled) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
warnSentryUnavailableOnce(Boolean(input.dsn?.trim()));
|
|
122
|
+
this.ensureSentry(input.dsn, Boolean(input.testMode));
|
|
123
|
+
}
|
|
124
|
+
reset() {
|
|
125
|
+
this.enabled = false;
|
|
126
|
+
this.sentryReady = false;
|
|
127
|
+
this.platform = 'web';
|
|
128
|
+
cachedSentryModule = undefined;
|
|
129
|
+
missingSentryPeerWarningShown = false;
|
|
130
|
+
}
|
|
131
|
+
logActivity(input) {
|
|
132
|
+
if (!this.enabled) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const level = input.level ?? DEFAULT_LEVEL;
|
|
136
|
+
const metadata = sanitizeLogMetadata({
|
|
137
|
+
...input.metadata,
|
|
138
|
+
sdk_version: SDK_VERSION,
|
|
139
|
+
platform: this.platform,
|
|
140
|
+
timestamp: new Date().toISOString()
|
|
141
|
+
});
|
|
142
|
+
const tags = activityTags(input.eventName, this.platform);
|
|
143
|
+
let deliveredToSentry = false;
|
|
144
|
+
this.safe(() => {
|
|
145
|
+
const sentry = loadSentryModule();
|
|
146
|
+
if (!sentry || !this.sentryReady) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const structuredLogsUsed = usesStructuredSentryLogs(this.platform);
|
|
150
|
+
let structuredLogSent = false;
|
|
151
|
+
if (structuredLogsUsed) {
|
|
152
|
+
structuredLogSent = emitStructuredLog(sentry, level, input.eventName, metadata);
|
|
153
|
+
deliveredToSentry = structuredLogSent;
|
|
154
|
+
if (level === 'warn' || level === 'error') {
|
|
155
|
+
sentry.addBreadcrumb({
|
|
156
|
+
category: 'adaxon.sdk',
|
|
157
|
+
message: input.eventName,
|
|
158
|
+
level: sentryLevel(level, 'breadcrumb'),
|
|
159
|
+
data: metadata,
|
|
160
|
+
timestamp: Date.now() / 1000
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
emitNativeActivityBreadcrumb(sentry, level, input.eventName, metadata);
|
|
165
|
+
}
|
|
166
|
+
if (!usesMinimalNativeSentryInit(this.platform) && shouldCaptureMessage(input, level, structuredLogSent) && typeof sentry.captureMessage === 'function') {
|
|
167
|
+
sentry.captureMessage(input.eventName, {
|
|
168
|
+
level: sentryLevel(level, 'message'),
|
|
169
|
+
tags,
|
|
170
|
+
extra: metadata
|
|
171
|
+
});
|
|
172
|
+
deliveredToSentry = true;
|
|
173
|
+
}
|
|
174
|
+
if (input.captureAsEvent && input.error !== undefined) {
|
|
175
|
+
const captureError = input.error instanceof Error ? input.error : new Error(AdaxonSdkError.messageOf(input.error, `Adaxon SDK activity: ${input.eventName}`));
|
|
176
|
+
sentry.captureException(captureError, {
|
|
177
|
+
tags,
|
|
178
|
+
extra: metadata
|
|
179
|
+
});
|
|
180
|
+
deliveredToSentry = true;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
emitDevActivityFallback(level, input.eventName, metadata, deliveredToSentry);
|
|
184
|
+
}
|
|
185
|
+
ensureSentry(dsn, testMode) {
|
|
186
|
+
this.safe(() => {
|
|
187
|
+
const sentry = loadSentryModule();
|
|
188
|
+
if (!sentry) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!sentry.getClient()) {
|
|
192
|
+
if (!dsn?.trim()) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const environment = testMode ? 'adaxon-sdk-test' : 'adaxon-sdk';
|
|
196
|
+
const trimmedDsn = dsn.trim();
|
|
197
|
+
if (usesMinimalNativeSentryInit(this.platform)) {
|
|
198
|
+
// Dev / Metro: avoid Sentry Logs + default integrations (Metro "unknown module undefined").
|
|
199
|
+
sentry.init({
|
|
200
|
+
dsn: trimmedDsn,
|
|
201
|
+
environment,
|
|
202
|
+
enableLogs: false,
|
|
203
|
+
enableAutoConsoleLogs: false,
|
|
204
|
+
enableNative: false,
|
|
205
|
+
autoInitializeNativeSdk: false,
|
|
206
|
+
defaultIntegrations: false,
|
|
207
|
+
integrations: [],
|
|
208
|
+
tracesSampleRate: 0,
|
|
209
|
+
enableAutoSessionTracking: false,
|
|
210
|
+
sendDefaultPii: false
|
|
211
|
+
});
|
|
212
|
+
} else {
|
|
213
|
+
// Web + release native: structured logs reach Sentry (Logs product / logger API).
|
|
214
|
+
sentry.init({
|
|
215
|
+
dsn: trimmedDsn,
|
|
216
|
+
environment,
|
|
217
|
+
enableLogs: true,
|
|
218
|
+
enableAutoConsoleLogs: false,
|
|
219
|
+
tracesSampleRate: 0,
|
|
220
|
+
enableAutoSessionTracking: false,
|
|
221
|
+
sendDefaultPii: false
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
sentry.setTag('adaxon_sdk_version', SDK_VERSION);
|
|
226
|
+
sentry.setTag('adaxon_platform', this.platform);
|
|
227
|
+
this.sentryReady = true;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
safe(run) {
|
|
231
|
+
try {
|
|
232
|
+
run();
|
|
233
|
+
} catch {
|
|
234
|
+
// Logging must never interrupt SDK behavior.
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export const AdaxonLogger = new AdaxonLoggerService();
|
|
239
|
+
export function logSdkActivity(input) {
|
|
240
|
+
AdaxonLogger.logActivity(input);
|
|
241
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const SENSITIVE_KEY = /^(sdk[_-]?key|api[_-]?key|authorization|x-sdk-key|password|secret|token|cookie|credentials?|dsn|sentry[_-]?dsn|headers)$/i;
|
|
4
|
+
const MAX_STRING_LENGTH = 500;
|
|
5
|
+
const MAX_NESTED_KEYS = 40;
|
|
6
|
+
function truncate(value) {
|
|
7
|
+
return value.length <= MAX_STRING_LENGTH ? value : `${value.slice(0, MAX_STRING_LENGTH)}…`;
|
|
8
|
+
}
|
|
9
|
+
function sanitizeField(key, value) {
|
|
10
|
+
if (SENSITIVE_KEY.test(key)) {
|
|
11
|
+
return '[redacted]';
|
|
12
|
+
}
|
|
13
|
+
if (value === null || value === undefined) {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
return truncate(value);
|
|
18
|
+
}
|
|
19
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return value.map(item => typeof item === 'string' ? truncate(item) : item);
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === 'object') {
|
|
26
|
+
const nested = {};
|
|
27
|
+
for (const [nestedKey, nestedValue] of Object.entries(value).slice(0, MAX_NESTED_KEYS)) {
|
|
28
|
+
nested[nestedKey] = SENSITIVE_KEY.test(nestedKey) ? '[redacted]' : typeof nestedValue === 'string' ? truncate(nestedValue) : nestedValue;
|
|
29
|
+
}
|
|
30
|
+
return nested;
|
|
31
|
+
}
|
|
32
|
+
return String(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Redact secrets and cap string size before activity logs leave the SDK. */
|
|
36
|
+
export function sanitizeLogMetadata(metadata) {
|
|
37
|
+
if (!metadata) {
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
const output = {};
|
|
41
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
42
|
+
output[key] = sanitizeField(key, value);
|
|
43
|
+
}
|
|
44
|
+
return output;
|
|
45
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { BASE_URL } from "../../config/ApiConstants.js";
|
|
4
4
|
import { AdaxonSdkError } from "../../errors/AdaxonSdkError.js";
|
|
5
|
+
import { logSdkActivity } from "../logging/AdaxonLogger.js";
|
|
5
6
|
export class ApiClient {
|
|
6
7
|
constructor(config) {
|
|
7
8
|
this.config = config;
|
|
@@ -14,6 +15,15 @@ export class ApiClient {
|
|
|
14
15
|
async post(path, body) {
|
|
15
16
|
const controller = new AbortController();
|
|
16
17
|
const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
|
|
18
|
+
const batchSize = Array.isArray(body) ? body.length : undefined;
|
|
19
|
+
logSdkActivity({
|
|
20
|
+
eventName: 'api.request.started',
|
|
21
|
+
level: 'debug',
|
|
22
|
+
metadata: {
|
|
23
|
+
path,
|
|
24
|
+
batch_size: batchSize
|
|
25
|
+
}
|
|
26
|
+
});
|
|
17
27
|
try {
|
|
18
28
|
const response = await this.fetchImplementation(`${BASE_URL}${path}`, {
|
|
19
29
|
method: 'POST',
|
|
@@ -27,31 +37,70 @@ export class ApiClient {
|
|
|
27
37
|
});
|
|
28
38
|
const parsedBody = this.parseJson(await response.text());
|
|
29
39
|
if (!response.ok) {
|
|
30
|
-
|
|
40
|
+
const httpError = AdaxonSdkError.fromHttp(response.status, parsedBody);
|
|
41
|
+
this.logRequestFailed(path, httpError, response.status);
|
|
42
|
+
throw httpError;
|
|
31
43
|
}
|
|
32
44
|
if (parsedBody === null) {
|
|
33
|
-
|
|
45
|
+
const invalidJsonError = new AdaxonSdkError('HTTP_ERROR', 'Adaxon returned an empty or invalid JSON response.', {
|
|
34
46
|
status: response.status
|
|
35
47
|
});
|
|
48
|
+
this.logRequestFailed(path, invalidJsonError, response.status);
|
|
49
|
+
throw invalidJsonError;
|
|
36
50
|
}
|
|
51
|
+
logSdkActivity({
|
|
52
|
+
eventName: 'api.request.succeeded',
|
|
53
|
+
level: 'debug',
|
|
54
|
+
metadata: {
|
|
55
|
+
path,
|
|
56
|
+
status: response.status,
|
|
57
|
+
batch_size: batchSize
|
|
58
|
+
}
|
|
59
|
+
});
|
|
37
60
|
return parsedBody;
|
|
38
61
|
} catch (error) {
|
|
39
62
|
if (error instanceof AdaxonSdkError) {
|
|
40
63
|
throw error;
|
|
41
64
|
}
|
|
42
65
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
43
|
-
|
|
66
|
+
const timeoutError = new AdaxonSdkError('TIMEOUT', 'Adaxon request timed out.', {
|
|
44
67
|
cause: error
|
|
45
68
|
});
|
|
69
|
+
logSdkActivity({
|
|
70
|
+
eventName: 'api.request.timeout',
|
|
71
|
+
level: 'warn',
|
|
72
|
+
metadata: {
|
|
73
|
+
path,
|
|
74
|
+
error_code: timeoutError.code
|
|
75
|
+
},
|
|
76
|
+
error: timeoutError
|
|
77
|
+
});
|
|
78
|
+
throw timeoutError;
|
|
46
79
|
}
|
|
47
80
|
const detail = AdaxonSdkError.messageOf(error, '');
|
|
48
|
-
|
|
81
|
+
const networkError = new AdaxonSdkError('NETWORK_ERROR', `Unable to reach Adaxon at ${BASE_URL}.${detail ? ` ${detail}` : ''}`, {
|
|
49
82
|
cause: error
|
|
50
83
|
});
|
|
84
|
+
this.logRequestFailed(path, networkError);
|
|
85
|
+
throw networkError;
|
|
51
86
|
} finally {
|
|
52
87
|
clearTimeout(timeout);
|
|
53
88
|
}
|
|
54
89
|
}
|
|
90
|
+
logRequestFailed(path, error, status) {
|
|
91
|
+
logSdkActivity({
|
|
92
|
+
eventName: 'api.request.failed',
|
|
93
|
+
level: 'warn',
|
|
94
|
+
metadata: {
|
|
95
|
+
path,
|
|
96
|
+
error_code: error.code,
|
|
97
|
+
...(status !== undefined ? {
|
|
98
|
+
status
|
|
99
|
+
} : {})
|
|
100
|
+
},
|
|
101
|
+
error
|
|
102
|
+
});
|
|
103
|
+
}
|
|
55
104
|
parseJson(rawBody) {
|
|
56
105
|
try {
|
|
57
106
|
return rawBody ? JSON.parse(rawBody) : null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AdaxonPlatform } from '../../types/EventTypes.js';
|
|
2
|
+
export type AdaxonLogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
3
|
+
export type AdaxonActivityLogInput = {
|
|
4
|
+
eventName: string;
|
|
5
|
+
level?: AdaxonLogLevel;
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
|
+
error?: unknown;
|
|
8
|
+
/** Send a standalone Sentry issue (message or exception). Use sparingly. */
|
|
9
|
+
captureAsEvent?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type AdaxonLoggerConfigureInput = {
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
dsn?: string;
|
|
14
|
+
platform: AdaxonPlatform;
|
|
15
|
+
testMode?: boolean;
|
|
16
|
+
};
|
|
17
|
+
declare class AdaxonLoggerService {
|
|
18
|
+
private enabled;
|
|
19
|
+
private platform;
|
|
20
|
+
private sentryReady;
|
|
21
|
+
configure(input: AdaxonLoggerConfigureInput): void;
|
|
22
|
+
reset(): void;
|
|
23
|
+
logActivity(input: AdaxonActivityLogInput): void;
|
|
24
|
+
private ensureSentry;
|
|
25
|
+
private safe;
|
|
26
|
+
}
|
|
27
|
+
export declare const AdaxonLogger: AdaxonLoggerService;
|
|
28
|
+
export declare function logSdkActivity(input: AdaxonActivityLogInput): void;
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=AdaxonLogger.d.ts.map
|
|
@@ -10,6 +10,10 @@ export interface AdaxonSdkConfig {
|
|
|
10
10
|
timeoutMs?: number;
|
|
11
11
|
fetch?: typeof globalThis.fetch;
|
|
12
12
|
testMode?: boolean;
|
|
13
|
+
/** When true, records SDK activity to Sentry for testing/debugging. Default false. */
|
|
14
|
+
activityLogging?: boolean;
|
|
15
|
+
/** Sentry DSN used when activity logging is enabled and the host app has not initialized Sentry. */
|
|
16
|
+
sentryDsn?: string;
|
|
13
17
|
}
|
|
14
18
|
export type AdaxonInitializationConfiguration = AdaxonSdkConfig;
|
|
15
19
|
export type AdaxonInitializationErrorCode = 'INVALID_CONFIG' | 'UNKNOWN';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-test-adx",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Adaxon React Native / Expo SDK for event tracking and offer recommendations.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -56,6 +56,9 @@
|
|
|
56
56
|
"access": "public",
|
|
57
57
|
"registry": "https://registry.npmjs.org/"
|
|
58
58
|
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@sentry/react-native": "^8.31.0"
|
|
61
|
+
},
|
|
59
62
|
"devDependencies": {
|
|
60
63
|
"@eslint/compat": "^2.1.0",
|
|
61
64
|
"@eslint/eslintrc": "^3.3.5",
|