@advenue/react-native 0.3.1 → 0.4.0
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/dist/index.d.ts +42 -22
- package/dist/index.js +252 -137
- package/package.json +6 -14
- package/plugin/index.js +4 -8
- package/plugin/links.js +23 -1
- package/src/index.ts +403 -230
- package/src/native-storage.ts +63 -0
- package/src/native.ts +13 -0
- package/src/secure-store.ts +7 -0
package/dist/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ declare function createMMKVStorage(mmkv: MMKVLike): StorageAdapter;
|
|
|
25
25
|
interface SecureStoreLike {
|
|
26
26
|
getItemAsync(key: string): Promise<string | null>;
|
|
27
27
|
setItemAsync(key: string, value: string): Promise<void>;
|
|
28
|
+
/** Optional — forwarded so GDPR erasure can wipe the durable entries. */
|
|
29
|
+
deleteItemAsync?(key: string): Promise<void>;
|
|
28
30
|
}
|
|
29
31
|
/** Wraps expo-secure-store as an Advenue SecureStorageAdapter (async, durable). */
|
|
30
32
|
declare function createSecureStore(secureStore: SecureStoreLike): SecureStorageAdapter;
|
|
@@ -80,20 +82,26 @@ declare function collectDeviceInfo(natives: {
|
|
|
80
82
|
* paste banner — the user-visible, privacy-forward deferred path.)
|
|
81
83
|
*/
|
|
82
84
|
declare function resolveClipboardMatch(): Promise<DeepLink | null>;
|
|
83
|
-
interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage'> {
|
|
85
|
+
interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
|
|
84
86
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
87
|
+
* Auto-detected from React Native's `Platform.OS` — omit it. Pass explicitly
|
|
88
|
+
* only outside a React Native runtime (tests, custom embeddings); `ios` and
|
|
89
|
+
* `android` map verbatim, any other RN target (web, desktop) reports as
|
|
90
|
+
* `web`.
|
|
91
|
+
*/
|
|
92
|
+
platform?: AdvenueConfig['platform'];
|
|
93
|
+
/**
|
|
94
|
+
* @internal Storage override (tests / custom embeddings). The SDK persists
|
|
95
|
+
* via its own native modules (UserDefaults/SharedPreferences) — apps never
|
|
96
|
+
* supply storage. When set, it replaces the built-in native storage.
|
|
89
97
|
*/
|
|
90
98
|
mmkv?: MMKVLike;
|
|
91
99
|
/**
|
|
92
100
|
* G3.1n — Play Integrity (Android) / App Attest (iOS) attestation.
|
|
93
101
|
*
|
|
94
102
|
* Your app's Advenue appId (the UUID shown in the dashboard). When set on
|
|
95
|
-
* Android,
|
|
96
|
-
*
|
|
103
|
+
* Android, the automatic install obtains a Play Integrity Standard token and
|
|
104
|
+
* attaches it to the install event. The requestHash is computed as
|
|
97
105
|
* `sha256Hex("<appId>:<deviceId>")`, matching the server's verification
|
|
98
106
|
* formula exactly (see `makeDecodeAndroid` in the worker).
|
|
99
107
|
*
|
|
@@ -101,10 +109,10 @@ interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage'> {
|
|
|
101
109
|
*/
|
|
102
110
|
appId?: string;
|
|
103
111
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
112
|
+
* @internal Secure-store override (tests / custom embeddings). The SDK uses
|
|
113
|
+
* its own Keychain-backed native storage on iOS for the reinstall-resilient
|
|
114
|
+
* device identity — apps never supply a secure store. When set, it replaces
|
|
115
|
+
* the built-in Keychain adapter.
|
|
108
116
|
*/
|
|
109
117
|
secureStore?: SecureStoreLike;
|
|
110
118
|
/**
|
|
@@ -115,6 +123,13 @@ interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage'> {
|
|
|
115
123
|
* to distinguish the two. Omit to disable deep-link handling entirely.
|
|
116
124
|
*/
|
|
117
125
|
onDeepLink?: (link: DeepLink) => void;
|
|
126
|
+
/**
|
|
127
|
+
* @internal Test hook — disables the automatic install fired by
|
|
128
|
+
* `initialize()`. Install tracking is always automatic in production (there
|
|
129
|
+
* is no public `trackInstall`); tests turn it off to exercise the internal
|
|
130
|
+
* install path deterministically.
|
|
131
|
+
*/
|
|
132
|
+
autoTrackInstall?: boolean;
|
|
118
133
|
}
|
|
119
134
|
/**
|
|
120
135
|
* Maps a React Native AppState status to the client's session lifecycle hooks.
|
|
@@ -134,18 +149,10 @@ declare const Advenue: {
|
|
|
134
149
|
* where ATT doesn't exist) and forwards the result to the consent gate.
|
|
135
150
|
*/
|
|
136
151
|
requestTrackingAuthorization(): Promise<TrackingAuthorizationStatus>;
|
|
137
|
-
|
|
138
|
-
* Tracks the install event enriched with native attribution data: Play +
|
|
139
|
-
* Meta install referrers on Android, SKAdNetwork registration on iOS.
|
|
140
|
-
* Safe to call on every launch — when a `secureStore` is wired it fires at
|
|
141
|
-
* most once per device (reinstall-resilient on iOS via Keychain; Android
|
|
142
|
-
* re-fires after a reinstall, which is unavoidable post-GAID).
|
|
143
|
-
*/
|
|
144
|
-
trackInstall(properties?: Record<string, unknown>): Promise<void>;
|
|
145
|
-
setTrackingConsent: (granted: boolean) => void;
|
|
152
|
+
setTrackingConsent(granted: boolean): void;
|
|
146
153
|
getTrackingConsent: () => boolean;
|
|
147
154
|
/** GDPR/CCPA erasure: stop tracking and wipe local identifiers/queue. */
|
|
148
|
-
forgetMe
|
|
155
|
+
forgetMe(): void;
|
|
149
156
|
setConsentData(consent: Consent): void;
|
|
150
157
|
getConsentData: () => {
|
|
151
158
|
isUserSubjectToGDPR: boolean;
|
|
@@ -157,5 +164,18 @@ declare const Advenue: {
|
|
|
157
164
|
getDeviceId: () => string;
|
|
158
165
|
shutdown(): void;
|
|
159
166
|
};
|
|
167
|
+
/**
|
|
168
|
+
* Tracks the install event enriched with native attribution data: Play + Meta
|
|
169
|
+
* install referrers on Android, SKAdNetwork registration on iOS. Fired
|
|
170
|
+
* automatically by `initialize()` (or its consent deferral); when a
|
|
171
|
+
* `secureStore` is wired it fires at most once per device (reinstall-resilient
|
|
172
|
+
* on iOS via Keychain; Android re-fires after a reinstall, which is
|
|
173
|
+
* unavoidable post-GAID).
|
|
174
|
+
*
|
|
175
|
+
* @internal Exported for tests only — install tracking is automatic and this
|
|
176
|
+
* function is deliberately NOT on the `Advenue` facade. Do not call it from
|
|
177
|
+
* app code.
|
|
178
|
+
*/
|
|
179
|
+
declare function trackInstall(): Promise<void>;
|
|
160
180
|
|
|
161
|
-
export { Advenue, type MMKVLike, type RNAdvenueConfig, type SecureStoreLike, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch };
|
|
181
|
+
export { Advenue, type MMKVLike, type RNAdvenueConfig, type SecureStoreLike, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch, trackInstall };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseInstallReferrer, extractReferrerProperties, parseMetaInstallReferrer } from '@advenue/install-referrer';
|
|
2
|
-
import { DEFAULT_ENDPOINT, MemoryStorage, resolveDurableDeviceId, AdvenueClient } from '@advenue/sdk-core';
|
|
2
|
+
import { DEVICE_ID_KEY, DEFAULT_ENDPOINT, MemoryStorage, resolveDurableDeviceId, AdvenueClient } from '@advenue/sdk-core';
|
|
3
3
|
|
|
4
4
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
5
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
@@ -229,6 +229,13 @@ function getAndroidNative() {
|
|
|
229
229
|
return null;
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
+
function getPlatformOS() {
|
|
233
|
+
try {
|
|
234
|
+
return __require("react-native").Platform.OS ?? null;
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
232
239
|
function getAppState() {
|
|
233
240
|
try {
|
|
234
241
|
return __require("react-native").AppState;
|
|
@@ -244,11 +251,38 @@ function getLinking() {
|
|
|
244
251
|
}
|
|
245
252
|
}
|
|
246
253
|
|
|
254
|
+
// src/native-storage.ts
|
|
255
|
+
function createNativeStorage(mod) {
|
|
256
|
+
const m = mod;
|
|
257
|
+
if (!m || typeof m.storageGetItem !== "function" || typeof m.storageSetItem !== "function" || typeof m.storageRemoveItem !== "function") {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
getItem: (key) => m.storageGetItem?.(key) ?? null,
|
|
262
|
+
setItem: (key, value) => m.storageSetItem?.(key, value),
|
|
263
|
+
removeItem: (key) => m.storageRemoveItem?.(key)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function createNativeSecureStore(mod) {
|
|
267
|
+
const m = mod;
|
|
268
|
+
if (!m || typeof m.secureGetItem !== "function" || typeof m.secureSetItem !== "function" || typeof m.secureRemoveItem !== "function") {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
getItemAsync: async (key) => m.secureGetItem?.(key) ?? null,
|
|
273
|
+
setItemAsync: async (key, value) => m.secureSetItem?.(key, value),
|
|
274
|
+
deleteItemAsync: async (key) => m.secureRemoveItem?.(key)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
247
278
|
// src/secure-store.ts
|
|
248
279
|
function createSecureStore(secureStore2) {
|
|
249
280
|
return {
|
|
250
281
|
getItemAsync: async (key) => await secureStore2.getItemAsync(key) ?? null,
|
|
251
|
-
setItemAsync: (key, value) => secureStore2.setItemAsync(key, value)
|
|
282
|
+
setItemAsync: (key, value) => secureStore2.setItemAsync(key, value),
|
|
283
|
+
...secureStore2.deleteItemAsync ? {
|
|
284
|
+
deleteItemAsync: (key) => secureStore2.deleteItemAsync?.(key) ?? Promise.resolve()
|
|
285
|
+
} : {}
|
|
252
286
|
};
|
|
253
287
|
}
|
|
254
288
|
|
|
@@ -276,7 +310,14 @@ async function resolveAdvertisingId() {
|
|
|
276
310
|
const r = await android.getAdvertisingId();
|
|
277
311
|
const vendorId = typeof android.getAppSetId === "function" ? await android.getAppSetId() : null;
|
|
278
312
|
const adFields = r ? r.limitAdTracking ? { limitAdTracking: true } : { gaid: r.id } : {};
|
|
279
|
-
|
|
313
|
+
const hasUsableGaid = !!r && !r.limitAdTracking;
|
|
314
|
+
const rawAndroidId = !hasUsableGaid && typeof android.getAndroidId === "function" ? android.getAndroidId() : null;
|
|
315
|
+
const androidId = rawAndroidId && /^[0-9a-fA-F]{8,32}$/.test(rawAndroidId) ? rawAndroidId : null;
|
|
316
|
+
client.setAdvertisingId({
|
|
317
|
+
...adFields,
|
|
318
|
+
...vendorId ? { vendorId } : {},
|
|
319
|
+
...androidId ? { androidId } : {}
|
|
320
|
+
});
|
|
280
321
|
return;
|
|
281
322
|
}
|
|
282
323
|
client.setAdvertisingId({});
|
|
@@ -300,7 +341,14 @@ async function resolveClipboardMatch() {
|
|
|
300
341
|
if (!ios || typeof ios.getPasteboardMatchToken !== "function") {
|
|
301
342
|
return null;
|
|
302
343
|
}
|
|
303
|
-
|
|
344
|
+
let token;
|
|
345
|
+
try {
|
|
346
|
+
token = ios.getPasteboardMatchToken();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
reportError("enrich.clipboardMatch", err);
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const matchId = parseMatchToken(token);
|
|
304
352
|
if (!matchId) return null;
|
|
305
353
|
return client.exchangeClipboardMatch(matchId);
|
|
306
354
|
}
|
|
@@ -318,6 +366,8 @@ var secureStore = null;
|
|
|
318
366
|
var installFlagStore = null;
|
|
319
367
|
var INSTALL_SENT_KEY = "advenue.install_sent";
|
|
320
368
|
var installInFlight = false;
|
|
369
|
+
var autoInstallPending = false;
|
|
370
|
+
var autoInstallEnabled = true;
|
|
321
371
|
var configuredAppId = null;
|
|
322
372
|
var configuredApiKey = null;
|
|
323
373
|
var configuredEndpoint = DEFAULT_ENDPOINT;
|
|
@@ -330,9 +380,37 @@ function active() {
|
|
|
330
380
|
function reportError(context, cause) {
|
|
331
381
|
instance?.report(context, cause);
|
|
332
382
|
}
|
|
383
|
+
function fireAutoInstall() {
|
|
384
|
+
void trackInstall().catch((err) => reportError("install.auto", err));
|
|
385
|
+
}
|
|
386
|
+
function resolvePlatform(explicit) {
|
|
387
|
+
if (explicit) {
|
|
388
|
+
return explicit;
|
|
389
|
+
}
|
|
390
|
+
const os = getPlatformOS();
|
|
391
|
+
if (os === "ios" || os === "android") {
|
|
392
|
+
return os;
|
|
393
|
+
}
|
|
394
|
+
if (os) {
|
|
395
|
+
return "web";
|
|
396
|
+
}
|
|
397
|
+
throw new Error(
|
|
398
|
+
"Advenue.initialize(): platform could not be detected (react-native unavailable) \u2014 pass platform: 'ios' | 'android' | 'web'."
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
function fireAutoInstallIfPending() {
|
|
402
|
+
if (!autoInstallPending) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
autoInstallPending = false;
|
|
406
|
+
fireAutoInstall();
|
|
407
|
+
}
|
|
333
408
|
var Advenue = {
|
|
334
409
|
async initialize(config) {
|
|
335
|
-
const { mmkv, secureStore: secureMod, onDeepLink, appId, ...rest } = config;
|
|
410
|
+
const { mmkv, secureStore: secureMod, onDeepLink, appId, autoTrackInstall, ...rest } = config;
|
|
411
|
+
autoInstallPending = false;
|
|
412
|
+
autoInstallEnabled = autoTrackInstall !== false;
|
|
413
|
+
const platform = resolvePlatform(rest.platform);
|
|
336
414
|
configuredAppId = appId ?? null;
|
|
337
415
|
configuredApiKey = config.apiKey;
|
|
338
416
|
configuredEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
@@ -341,14 +419,16 @@ var Advenue = {
|
|
|
341
419
|
deepLinkSub?.remove();
|
|
342
420
|
deepLinkSub = null;
|
|
343
421
|
instance?.shutdown();
|
|
344
|
-
const
|
|
422
|
+
const iosMod = getIosNative();
|
|
423
|
+
const androidMod = getAndroidNative();
|
|
424
|
+
const storage = mmkv ? createMMKVStorage(mmkv) : createNativeStorage(iosMod ?? androidMod) ?? new MemoryStorage();
|
|
345
425
|
installFlagStore = storage;
|
|
346
|
-
secureStore = secureMod ? createSecureStore(secureMod) :
|
|
426
|
+
secureStore = secureMod ? createSecureStore(secureMod) : createNativeSecureStore(iosMod);
|
|
347
427
|
const deviceId = rest.deviceId ?? await resolveDurableDeviceId(storage, secureStore ?? void 0);
|
|
348
|
-
instance = new AdvenueClient({ ...rest, deviceId, storage });
|
|
428
|
+
instance = new AdvenueClient({ ...rest, platform, deviceId, storage });
|
|
349
429
|
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
350
430
|
const deviceInfo = collectDeviceInfo({
|
|
351
|
-
platform
|
|
431
|
+
platform,
|
|
352
432
|
ios: getIosNative(),
|
|
353
433
|
android: getAndroidNative()
|
|
354
434
|
});
|
|
@@ -388,6 +468,14 @@ var Advenue = {
|
|
|
388
468
|
resolveDeferred: async () => await resolveClipboardMatch() ?? client.resolveDeferredDeepLink(),
|
|
389
469
|
onDeepLink
|
|
390
470
|
});
|
|
471
|
+
deepLinkSub.ready.catch((err) => reportError("deepLink.resolve", err));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (autoTrackInstall !== false) {
|
|
475
|
+
if (instance.getRequireConsent() && !instance.getTrackingConsent()) {
|
|
476
|
+
autoInstallPending = true;
|
|
477
|
+
} else {
|
|
478
|
+
fireAutoInstall();
|
|
391
479
|
}
|
|
392
480
|
}
|
|
393
481
|
return instance;
|
|
@@ -402,142 +490,33 @@ var Advenue = {
|
|
|
402
490
|
const status = ios ? await ios.requestTrackingAuthorization() : "authorized";
|
|
403
491
|
active().setTrackingConsent(status === "authorized");
|
|
404
492
|
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
493
|
+
if (status === "authorized") {
|
|
494
|
+
fireAutoInstallIfPending();
|
|
495
|
+
}
|
|
405
496
|
return status;
|
|
406
497
|
},
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
* most once per device (reinstall-resilient on iOS via Keychain; Android
|
|
412
|
-
* re-fires after a reinstall, which is unavoidable post-GAID).
|
|
413
|
-
*/
|
|
414
|
-
async trackInstall(properties) {
|
|
415
|
-
const client = active();
|
|
416
|
-
let sent = installFlagStore?.getItem(INSTALL_SENT_KEY) === "1";
|
|
417
|
-
if (!sent && secureStore) {
|
|
418
|
-
try {
|
|
419
|
-
sent = await secureStore.getItemAsync(INSTALL_SENT_KEY) === "1";
|
|
420
|
-
} catch (err) {
|
|
421
|
-
reportError("install.guardRead", err);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
if (sent) {
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (installInFlight) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
installInFlight = true;
|
|
431
|
-
const opts = {};
|
|
432
|
-
const props = { ...properties };
|
|
433
|
-
const android = getAndroidNative();
|
|
434
|
-
if (android) {
|
|
435
|
-
try {
|
|
436
|
-
const result = await android.getInstallReferrer();
|
|
437
|
-
if (result) {
|
|
438
|
-
const parsed = parseInstallReferrer(result.referrer);
|
|
439
|
-
if (parsed.network) opts.network = parsed.network;
|
|
440
|
-
if (parsed.campaign) opts.campaign = parsed.campaign;
|
|
441
|
-
if (parsed.gclid) props.gclid = parsed.gclid;
|
|
442
|
-
if (parsed.fbclid) props.fbclid = parsed.fbclid;
|
|
443
|
-
Object.assign(props, extractReferrerProperties(result));
|
|
444
|
-
}
|
|
445
|
-
} catch (err) {
|
|
446
|
-
reportError("enrich.installReferrer", err);
|
|
447
|
-
}
|
|
448
|
-
try {
|
|
449
|
-
const meta = await android.getMetaInstallReferrer();
|
|
450
|
-
const parsedMeta = meta ? parseMetaInstallReferrer(meta) : null;
|
|
451
|
-
if (parsedMeta) {
|
|
452
|
-
props.metaInstallReferrer = parsedMeta.raw;
|
|
453
|
-
if (parsedMeta.encryptedData) props.metaReferrerData = parsedMeta.encryptedData;
|
|
454
|
-
if (parsedMeta.nonce) props.metaReferrerNonce = parsedMeta.nonce;
|
|
455
|
-
if (!opts.network && parsedMeta.isClickThrough) {
|
|
456
|
-
opts.network = "meta";
|
|
457
|
-
if (parsedMeta.campaign) opts.campaign = parsedMeta.campaign;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
} catch (err) {
|
|
461
|
-
reportError("enrich.metaReferrer", err);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const ios = getIosNative();
|
|
465
|
-
if (ios) {
|
|
466
|
-
try {
|
|
467
|
-
ios.registerAppForAttribution();
|
|
468
|
-
} catch (err) {
|
|
469
|
-
reportError("enrich.skanRegister", err);
|
|
470
|
-
}
|
|
471
|
-
try {
|
|
472
|
-
const asaToken = await ios.getAttributionToken();
|
|
473
|
-
if (asaToken) {
|
|
474
|
-
opts.adservicesToken = asaToken;
|
|
475
|
-
}
|
|
476
|
-
} catch (err) {
|
|
477
|
-
reportError("enrich.adservicesToken", err);
|
|
478
|
-
}
|
|
498
|
+
setTrackingConsent(granted) {
|
|
499
|
+
active().setTrackingConsent(granted);
|
|
500
|
+
if (granted) {
|
|
501
|
+
fireAutoInstallIfPending();
|
|
479
502
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
const requestHash = sha256Hex(`${configuredAppId}:${deviceId}`);
|
|
486
|
-
const token = await android.getIntegrityToken(requestHash);
|
|
487
|
-
if (token) {
|
|
488
|
-
trackOpts.attestationToken = token;
|
|
489
|
-
trackOpts.attestationType = "play-integrity";
|
|
490
|
-
}
|
|
491
|
-
} catch (err) {
|
|
492
|
-
reportError("attest.playIntegrity", err);
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (configuredAppId && configuredApiKey && ios && typeof ios.attestKey === "function") {
|
|
496
|
-
try {
|
|
497
|
-
const deviceId = client.getDeviceId();
|
|
498
|
-
const challengeUrl = `${configuredEndpoint}/v1/attest/challenge?deviceId=${encodeURIComponent(deviceId)}`;
|
|
499
|
-
const challengeRes = await fetch(challengeUrl, {
|
|
500
|
-
headers: { "x-api-key": configuredApiKey }
|
|
501
|
-
});
|
|
502
|
-
if (!challengeRes.ok) throw new Error(`challenge HTTP ${challengeRes.status}`);
|
|
503
|
-
const { challenge } = await challengeRes.json();
|
|
504
|
-
const { keyId, attestationObject } = await ios.attestKey(challenge);
|
|
505
|
-
trackOpts.attestationToken = attestationObject;
|
|
506
|
-
trackOpts.attestationType = "app-attest";
|
|
507
|
-
trackOpts.attestationKeyId = keyId;
|
|
508
|
-
trackOpts.attestationChallenge = challenge;
|
|
509
|
-
} catch (err) {
|
|
510
|
-
reportError("attest.appAttest", err);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
if (ios && typeof ios.getDeviceCheckToken === "function") {
|
|
514
|
-
try {
|
|
515
|
-
const token = await ios.getDeviceCheckToken();
|
|
516
|
-
if (token) {
|
|
517
|
-
trackOpts.deviceCheckToken = token;
|
|
518
|
-
}
|
|
519
|
-
} catch (err) {
|
|
520
|
-
reportError("attest.deviceCheck", err);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
client.track("install", props, trackOpts);
|
|
503
|
+
},
|
|
504
|
+
getTrackingConsent: () => active().getTrackingConsent(),
|
|
505
|
+
/** GDPR/CCPA erasure: stop tracking and wipe local identifiers/queue. */
|
|
506
|
+
forgetMe() {
|
|
507
|
+
active().forgetMe();
|
|
524
508
|
try {
|
|
525
|
-
installFlagStore?.
|
|
509
|
+
installFlagStore?.removeItem(INSTALL_SENT_KEY);
|
|
526
510
|
} catch (err) {
|
|
527
|
-
reportError("
|
|
511
|
+
reportError("forget.wipe", err);
|
|
528
512
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
reportError("install.flagPersistSecure", err);
|
|
513
|
+
const secure = secureStore;
|
|
514
|
+
if (secure?.deleteItemAsync) {
|
|
515
|
+
for (const key of [DEVICE_ID_KEY, INSTALL_SENT_KEY]) {
|
|
516
|
+
void secure.deleteItemAsync(key).catch((err) => reportError("forget.secureWipe", err));
|
|
534
517
|
}
|
|
535
518
|
}
|
|
536
519
|
},
|
|
537
|
-
setTrackingConsent: (granted) => active().setTrackingConsent(granted),
|
|
538
|
-
getTrackingConsent: () => active().getTrackingConsent(),
|
|
539
|
-
/** GDPR/CCPA erasure: stop tracking and wipe local identifiers/queue. */
|
|
540
|
-
forgetMe: () => active().forgetMe(),
|
|
541
520
|
setConsentData(consent) {
|
|
542
521
|
active().setConsentData(consent);
|
|
543
522
|
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
@@ -554,10 +533,146 @@ var Advenue = {
|
|
|
554
533
|
instance = null;
|
|
555
534
|
secureStore = null;
|
|
556
535
|
installInFlight = false;
|
|
536
|
+
autoInstallPending = false;
|
|
537
|
+
autoInstallEnabled = true;
|
|
557
538
|
configuredAppId = null;
|
|
558
539
|
configuredApiKey = null;
|
|
559
540
|
configuredEndpoint = DEFAULT_ENDPOINT;
|
|
560
541
|
}
|
|
561
542
|
};
|
|
543
|
+
async function trackInstall() {
|
|
544
|
+
const client = active();
|
|
545
|
+
if (client.getRequireConsent() && !client.getTrackingConsent()) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
let sent = installFlagStore?.getItem(INSTALL_SENT_KEY) === "1";
|
|
549
|
+
if (!sent && secureStore) {
|
|
550
|
+
try {
|
|
551
|
+
sent = await secureStore.getItemAsync(INSTALL_SENT_KEY) === "1";
|
|
552
|
+
} catch (err) {
|
|
553
|
+
reportError("install.guardRead", err);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (sent) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (installInFlight) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
installInFlight = true;
|
|
563
|
+
const opts = {};
|
|
564
|
+
const props = {};
|
|
565
|
+
const android = getAndroidNative();
|
|
566
|
+
if (android) {
|
|
567
|
+
try {
|
|
568
|
+
const result = await android.getInstallReferrer();
|
|
569
|
+
if (result) {
|
|
570
|
+
const parsed = parseInstallReferrer(result.referrer);
|
|
571
|
+
if (parsed.network) opts.network = parsed.network;
|
|
572
|
+
if (parsed.campaign) opts.campaign = parsed.campaign;
|
|
573
|
+
if (parsed.gclid) props.gclid = parsed.gclid;
|
|
574
|
+
if (parsed.fbclid) props.fbclid = parsed.fbclid;
|
|
575
|
+
Object.assign(props, extractReferrerProperties(result));
|
|
576
|
+
}
|
|
577
|
+
} catch (err) {
|
|
578
|
+
reportError("enrich.installReferrer", err);
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
const meta = await android.getMetaInstallReferrer();
|
|
582
|
+
const parsedMeta = meta ? parseMetaInstallReferrer(meta) : null;
|
|
583
|
+
if (parsedMeta) {
|
|
584
|
+
props.metaInstallReferrer = parsedMeta.raw;
|
|
585
|
+
if (parsedMeta.encryptedData) props.metaReferrerData = parsedMeta.encryptedData;
|
|
586
|
+
if (parsedMeta.nonce) props.metaReferrerNonce = parsedMeta.nonce;
|
|
587
|
+
if (!opts.network && parsedMeta.isClickThrough) {
|
|
588
|
+
opts.network = "meta";
|
|
589
|
+
if (parsedMeta.campaign) opts.campaign = parsedMeta.campaign;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
} catch (err) {
|
|
593
|
+
reportError("enrich.metaReferrer", err);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
const ios = getIosNative();
|
|
597
|
+
if (ios) {
|
|
598
|
+
try {
|
|
599
|
+
ios.registerAppForAttribution();
|
|
600
|
+
} catch (err) {
|
|
601
|
+
reportError("enrich.skanRegister", err);
|
|
602
|
+
}
|
|
603
|
+
try {
|
|
604
|
+
const asaToken = await ios.getAttributionToken();
|
|
605
|
+
if (asaToken) {
|
|
606
|
+
opts.adservicesToken = asaToken;
|
|
607
|
+
}
|
|
608
|
+
} catch (err) {
|
|
609
|
+
reportError("enrich.adservicesToken", err);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
await resolveAdvertisingId();
|
|
613
|
+
const trackOpts = { type: "install", ...opts };
|
|
614
|
+
if (configuredAppId && android && typeof android.getIntegrityToken === "function") {
|
|
615
|
+
try {
|
|
616
|
+
const deviceId = client.getDeviceId();
|
|
617
|
+
const requestHash = sha256Hex(`${configuredAppId}:${deviceId}`);
|
|
618
|
+
const token = await android.getIntegrityToken(requestHash);
|
|
619
|
+
if (token) {
|
|
620
|
+
trackOpts.attestationToken = token;
|
|
621
|
+
trackOpts.attestationType = "play-integrity";
|
|
622
|
+
}
|
|
623
|
+
} catch (err) {
|
|
624
|
+
reportError("attest.playIntegrity", err);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if (configuredAppId && configuredApiKey && ios && typeof ios.attestKey === "function") {
|
|
628
|
+
try {
|
|
629
|
+
const deviceId = client.getDeviceId();
|
|
630
|
+
const challengeUrl = `${configuredEndpoint}/v1/attest/challenge?deviceId=${encodeURIComponent(deviceId)}`;
|
|
631
|
+
const challengeRes = await fetch(challengeUrl, {
|
|
632
|
+
headers: { "x-api-key": configuredApiKey }
|
|
633
|
+
});
|
|
634
|
+
if (!challengeRes.ok) throw new Error(`challenge HTTP ${challengeRes.status}`);
|
|
635
|
+
const { challenge } = await challengeRes.json();
|
|
636
|
+
const { keyId, attestationObject } = await ios.attestKey(challenge);
|
|
637
|
+
trackOpts.attestationToken = attestationObject;
|
|
638
|
+
trackOpts.attestationType = "app-attest";
|
|
639
|
+
trackOpts.attestationKeyId = keyId;
|
|
640
|
+
trackOpts.attestationChallenge = challenge;
|
|
641
|
+
} catch (err) {
|
|
642
|
+
reportError("attest.appAttest", err);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (ios && typeof ios.getDeviceCheckToken === "function") {
|
|
646
|
+
try {
|
|
647
|
+
const token = await ios.getDeviceCheckToken();
|
|
648
|
+
if (token) {
|
|
649
|
+
trackOpts.deviceCheckToken = token;
|
|
650
|
+
}
|
|
651
|
+
} catch (err) {
|
|
652
|
+
reportError("attest.deviceCheck", err);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const pendingBefore = client.pendingCount;
|
|
656
|
+
client.track("install", props, trackOpts);
|
|
657
|
+
if (client.pendingCount === pendingBefore) {
|
|
658
|
+
installInFlight = false;
|
|
659
|
+
if (autoInstallEnabled && client.getRequireConsent() && !client.getTrackingConsent()) {
|
|
660
|
+
autoInstallPending = true;
|
|
661
|
+
}
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
installFlagStore?.setItem(INSTALL_SENT_KEY, "1");
|
|
666
|
+
} catch (err) {
|
|
667
|
+
reportError("install.flagPersist", err);
|
|
668
|
+
}
|
|
669
|
+
if (secureStore) {
|
|
670
|
+
try {
|
|
671
|
+
await secureStore.setItemAsync(INSTALL_SENT_KEY, "1");
|
|
672
|
+
} catch (err) {
|
|
673
|
+
reportError("install.flagPersistSecure", err);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
562
677
|
|
|
563
|
-
export { Advenue, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch };
|
|
678
|
+
export { Advenue, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch, trackInstall };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@advenue/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -23,16 +23,14 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@advenue/install-referrer": "0.1.1",
|
|
26
|
-
"@advenue/sdk-core": "0.
|
|
26
|
+
"@advenue/sdk-core": "0.3.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@advenue/sdk-android": "*",
|
|
30
30
|
"@advenue/sdk-ios": "*",
|
|
31
31
|
"@expo/config-plugins": ">=8",
|
|
32
|
-
"expo-secure-store": ">=12",
|
|
33
32
|
"react": ">=18.2",
|
|
34
|
-
"react-native": ">=0.74"
|
|
35
|
-
"react-native-mmkv": ">=3"
|
|
33
|
+
"react-native": ">=0.74"
|
|
36
34
|
},
|
|
37
35
|
"peerDependenciesMeta": {
|
|
38
36
|
"@expo/config-plugins": {
|
|
@@ -43,21 +41,15 @@
|
|
|
43
41
|
},
|
|
44
42
|
"@advenue/sdk-ios": {
|
|
45
43
|
"optional": true
|
|
46
|
-
},
|
|
47
|
-
"expo-secure-store": {
|
|
48
|
-
"optional": true
|
|
49
|
-
},
|
|
50
|
-
"react-native-mmkv": {
|
|
51
|
-
"optional": true
|
|
52
44
|
}
|
|
53
45
|
},
|
|
54
46
|
"devDependencies": {
|
|
55
47
|
"tsup": "^8.5.1",
|
|
56
48
|
"typescript": "^5.7.2",
|
|
57
49
|
"vitest": "^2.1.8",
|
|
58
|
-
"@advenue/sdk-
|
|
59
|
-
"@advenue/sdk-
|
|
60
|
-
"@advenue/shared": "0.
|
|
50
|
+
"@advenue/sdk-android": "0.3.0",
|
|
51
|
+
"@advenue/sdk-ios": "0.3.0",
|
|
52
|
+
"@advenue/shared": "0.1.0",
|
|
61
53
|
"@advenue/signing": "0.0.0"
|
|
62
54
|
},
|
|
63
55
|
"scripts": {
|
package/plugin/index.js
CHANGED
|
@@ -5,23 +5,19 @@
|
|
|
5
5
|
// backend already serves the matching apple-app-site-association / assetlinks.json.
|
|
6
6
|
//
|
|
7
7
|
// Usage (app.json / app.config.js):
|
|
8
|
-
//
|
|
8
|
+
// "@advenue/react-native/plugin" — Advenue cloud (go.advenue.io)
|
|
9
|
+
// ["@advenue/react-native/plugin", { "domains": ["go.acme.com"] }] — self-host / custom link domain
|
|
9
10
|
//
|
|
10
11
|
// @expo/config-plugins is resolved from the consuming app's Expo install.
|
|
11
12
|
const { withEntitlementsPlist, withAndroidManifest } = require('@expo/config-plugins');
|
|
12
|
-
const { addAssociatedDomains, addAppLinksIntentFilter } = require('./links');
|
|
13
|
+
const { addAssociatedDomains, addAppLinksIntentFilter, resolveDomains } = require('./links');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @param {any} config
|
|
16
17
|
* @param {{ domains?: string[] } | undefined} props
|
|
17
18
|
*/
|
|
18
19
|
function withAdvenueLinks(config, props) {
|
|
19
|
-
const domains = props
|
|
20
|
-
if (domains.length === 0) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'@advenue/react-native/plugin: provide at least one link domain, e.g. { "domains": ["go.advenue.io"] }',
|
|
23
|
-
);
|
|
24
|
-
}
|
|
20
|
+
const domains = resolveDomains(props);
|
|
25
21
|
|
|
26
22
|
let result = withEntitlementsPlist(config, (cfg) => {
|
|
27
23
|
cfg.modResults = addAssociatedDomains(cfg.modResults, domains);
|