@advenue/react-native 0.3.1 → 0.5.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.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]
@@ -174,10 +174,10 @@ function parseDirectLink(url) {
174
174
  const path = parsed.pathname.replace(/^\/+/, "");
175
175
  const isHttp = parsed.protocol === "http:" || parsed.protocol === "https:";
176
176
  deepLinkValue = isHttp ? path : [parsed.host, path].filter(Boolean).join("/");
177
- for (const [k, v] of parsed.searchParams.entries()) {
177
+ parsed.searchParams.forEach((v, k) => {
178
178
  params ??= {};
179
179
  params[k] = v;
180
- }
180
+ });
181
181
  } catch {
182
182
  deepLinkValue = url;
183
183
  }
@@ -217,14 +217,23 @@ function createMMKVStorage(mmkv) {
217
217
  // src/native.ts
218
218
  function getIosNative() {
219
219
  try {
220
- return __require("@advenue/sdk-ios").AdvenueIos;
220
+ const core = __require("expo-modules-core");
221
+ return core.requireNativeModule("AdvenueIos");
221
222
  } catch {
222
223
  return null;
223
224
  }
224
225
  }
225
226
  function getAndroidNative() {
226
227
  try {
227
- return __require("@advenue/sdk-android").AdvenueAndroid;
228
+ const core = __require("expo-modules-core");
229
+ return core.requireNativeModule("AdvenueAndroid");
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+ function getPlatformOS() {
235
+ try {
236
+ return __require("react-native").Platform.OS ?? null;
228
237
  } catch {
229
238
  return null;
230
239
  }
@@ -244,11 +253,38 @@ function getLinking() {
244
253
  }
245
254
  }
246
255
 
256
+ // src/native-storage.ts
257
+ function createNativeStorage(mod) {
258
+ const m = mod;
259
+ if (!m || typeof m.storageGetItem !== "function" || typeof m.storageSetItem !== "function" || typeof m.storageRemoveItem !== "function") {
260
+ return null;
261
+ }
262
+ return {
263
+ getItem: (key) => m.storageGetItem?.(key) ?? null,
264
+ setItem: (key, value) => m.storageSetItem?.(key, value),
265
+ removeItem: (key) => m.storageRemoveItem?.(key)
266
+ };
267
+ }
268
+ function createNativeSecureStore(mod) {
269
+ const m = mod;
270
+ if (!m || typeof m.secureGetItem !== "function" || typeof m.secureSetItem !== "function" || typeof m.secureRemoveItem !== "function") {
271
+ return null;
272
+ }
273
+ return {
274
+ getItemAsync: async (key) => m.secureGetItem?.(key) ?? null,
275
+ setItemAsync: async (key, value) => m.secureSetItem?.(key, value),
276
+ deleteItemAsync: async (key) => m.secureRemoveItem?.(key)
277
+ };
278
+ }
279
+
247
280
  // src/secure-store.ts
248
281
  function createSecureStore(secureStore2) {
249
282
  return {
250
283
  getItemAsync: async (key) => await secureStore2.getItemAsync(key) ?? null,
251
- setItemAsync: (key, value) => secureStore2.setItemAsync(key, value)
284
+ setItemAsync: (key, value) => secureStore2.setItemAsync(key, value),
285
+ ...secureStore2.deleteItemAsync ? {
286
+ deleteItemAsync: (key) => secureStore2.deleteItemAsync?.(key) ?? Promise.resolve()
287
+ } : {}
252
288
  };
253
289
  }
254
290
 
@@ -276,7 +312,14 @@ async function resolveAdvertisingId() {
276
312
  const r = await android.getAdvertisingId();
277
313
  const vendorId = typeof android.getAppSetId === "function" ? await android.getAppSetId() : null;
278
314
  const adFields = r ? r.limitAdTracking ? { limitAdTracking: true } : { gaid: r.id } : {};
279
- client.setAdvertisingId({ ...adFields, ...vendorId ? { vendorId } : {} });
315
+ const hasUsableGaid = !!r && !r.limitAdTracking;
316
+ const rawAndroidId = !hasUsableGaid && typeof android.getAndroidId === "function" ? android.getAndroidId() : null;
317
+ const androidId = rawAndroidId && /^[0-9a-fA-F]{8,32}$/.test(rawAndroidId) ? rawAndroidId : null;
318
+ client.setAdvertisingId({
319
+ ...adFields,
320
+ ...vendorId ? { vendorId } : {},
321
+ ...androidId ? { androidId } : {}
322
+ });
280
323
  return;
281
324
  }
282
325
  client.setAdvertisingId({});
@@ -300,7 +343,14 @@ async function resolveClipboardMatch() {
300
343
  if (!ios || typeof ios.getPasteboardMatchToken !== "function") {
301
344
  return null;
302
345
  }
303
- const matchId = parseMatchToken(ios.getPasteboardMatchToken());
346
+ let token;
347
+ try {
348
+ token = ios.getPasteboardMatchToken();
349
+ } catch (err) {
350
+ reportError("enrich.clipboardMatch", err);
351
+ return null;
352
+ }
353
+ const matchId = parseMatchToken(token);
304
354
  if (!matchId) return null;
305
355
  return client.exchangeClipboardMatch(matchId);
306
356
  }
@@ -318,6 +368,8 @@ var secureStore = null;
318
368
  var installFlagStore = null;
319
369
  var INSTALL_SENT_KEY = "advenue.install_sent";
320
370
  var installInFlight = false;
371
+ var autoInstallPending = false;
372
+ var autoInstallEnabled = true;
321
373
  var configuredAppId = null;
322
374
  var configuredApiKey = null;
323
375
  var configuredEndpoint = DEFAULT_ENDPOINT;
@@ -330,9 +382,37 @@ function active() {
330
382
  function reportError(context, cause) {
331
383
  instance?.report(context, cause);
332
384
  }
385
+ function fireAutoInstall() {
386
+ void trackInstall().catch((err) => reportError("install.auto", err));
387
+ }
388
+ function resolvePlatform(explicit) {
389
+ if (explicit) {
390
+ return explicit;
391
+ }
392
+ const os = getPlatformOS();
393
+ if (os === "ios" || os === "android") {
394
+ return os;
395
+ }
396
+ if (os) {
397
+ return "web";
398
+ }
399
+ throw new Error(
400
+ "Advenue.initialize(): platform could not be detected (react-native unavailable) \u2014 pass platform: 'ios' | 'android' | 'web'."
401
+ );
402
+ }
403
+ function fireAutoInstallIfPending() {
404
+ if (!autoInstallPending) {
405
+ return;
406
+ }
407
+ autoInstallPending = false;
408
+ fireAutoInstall();
409
+ }
333
410
  var Advenue = {
334
411
  async initialize(config) {
335
- const { mmkv, secureStore: secureMod, onDeepLink, appId, ...rest } = config;
412
+ const { mmkv, secureStore: secureMod, onDeepLink, appId, autoTrackInstall, ...rest } = config;
413
+ autoInstallPending = false;
414
+ autoInstallEnabled = autoTrackInstall !== false;
415
+ const platform = resolvePlatform(rest.platform);
336
416
  configuredAppId = appId ?? null;
337
417
  configuredApiKey = config.apiKey;
338
418
  configuredEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
@@ -341,14 +421,16 @@ var Advenue = {
341
421
  deepLinkSub?.remove();
342
422
  deepLinkSub = null;
343
423
  instance?.shutdown();
344
- const storage = mmkv ? createMMKVStorage(mmkv) : new MemoryStorage();
424
+ const iosMod = getIosNative();
425
+ const androidMod = getAndroidNative();
426
+ const storage = mmkv ? createMMKVStorage(mmkv) : createNativeStorage(iosMod ?? androidMod) ?? new MemoryStorage();
345
427
  installFlagStore = storage;
346
- secureStore = secureMod ? createSecureStore(secureMod) : null;
428
+ secureStore = secureMod ? createSecureStore(secureMod) : createNativeSecureStore(iosMod);
347
429
  const deviceId = rest.deviceId ?? await resolveDurableDeviceId(storage, secureStore ?? void 0);
348
- instance = new AdvenueClient({ ...rest, deviceId, storage });
430
+ instance = new AdvenueClient({ ...rest, platform, deviceId, storage });
349
431
  void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
350
432
  const deviceInfo = collectDeviceInfo({
351
- platform: rest.platform,
433
+ platform,
352
434
  ios: getIosNative(),
353
435
  android: getAndroidNative()
354
436
  });
@@ -388,6 +470,14 @@ var Advenue = {
388
470
  resolveDeferred: async () => await resolveClipboardMatch() ?? client.resolveDeferredDeepLink(),
389
471
  onDeepLink
390
472
  });
473
+ deepLinkSub.ready.catch((err) => reportError("deepLink.resolve", err));
474
+ }
475
+ }
476
+ if (autoTrackInstall !== false) {
477
+ if (instance.getRequireConsent() && !instance.getTrackingConsent()) {
478
+ autoInstallPending = true;
479
+ } else {
480
+ fireAutoInstall();
391
481
  }
392
482
  }
393
483
  return instance;
@@ -402,142 +492,33 @@ var Advenue = {
402
492
  const status = ios ? await ios.requestTrackingAuthorization() : "authorized";
403
493
  active().setTrackingConsent(status === "authorized");
404
494
  void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
495
+ if (status === "authorized") {
496
+ fireAutoInstallIfPending();
497
+ }
405
498
  return status;
406
499
  },
407
- /**
408
- * Tracks the install event enriched with native attribution data: Play +
409
- * Meta install referrers on Android, SKAdNetwork registration on iOS.
410
- * Safe to call on every launch — when a `secureStore` is wired it fires at
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
- }
500
+ setTrackingConsent(granted) {
501
+ active().setTrackingConsent(granted);
502
+ if (granted) {
503
+ fireAutoInstallIfPending();
463
504
  }
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
- }
479
- }
480
- await resolveAdvertisingId();
481
- const trackOpts = { type: "install", ...opts };
482
- if (configuredAppId && android && typeof android.getIntegrityToken === "function") {
483
- try {
484
- const deviceId = client.getDeviceId();
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);
505
+ },
506
+ getTrackingConsent: () => active().getTrackingConsent(),
507
+ /** GDPR/CCPA erasure: stop tracking and wipe local identifiers/queue. */
508
+ forgetMe() {
509
+ active().forgetMe();
524
510
  try {
525
- installFlagStore?.setItem(INSTALL_SENT_KEY, "1");
511
+ installFlagStore?.removeItem(INSTALL_SENT_KEY);
526
512
  } catch (err) {
527
- reportError("install.flagPersist", err);
513
+ reportError("forget.wipe", err);
528
514
  }
529
- if (secureStore) {
530
- try {
531
- await secureStore.setItemAsync(INSTALL_SENT_KEY, "1");
532
- } catch (err) {
533
- reportError("install.flagPersistSecure", err);
515
+ const secure = secureStore;
516
+ if (secure?.deleteItemAsync) {
517
+ for (const key of [DEVICE_ID_KEY, INSTALL_SENT_KEY]) {
518
+ void secure.deleteItemAsync(key).catch((err) => reportError("forget.secureWipe", err));
534
519
  }
535
520
  }
536
521
  },
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
522
  setConsentData(consent) {
542
523
  active().setConsentData(consent);
543
524
  void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
@@ -554,10 +535,146 @@ var Advenue = {
554
535
  instance = null;
555
536
  secureStore = null;
556
537
  installInFlight = false;
538
+ autoInstallPending = false;
539
+ autoInstallEnabled = true;
557
540
  configuredAppId = null;
558
541
  configuredApiKey = null;
559
542
  configuredEndpoint = DEFAULT_ENDPOINT;
560
543
  }
561
544
  };
545
+ async function trackInstall() {
546
+ const client = active();
547
+ if (client.getRequireConsent() && !client.getTrackingConsent()) {
548
+ return;
549
+ }
550
+ let sent = installFlagStore?.getItem(INSTALL_SENT_KEY) === "1";
551
+ if (!sent && secureStore) {
552
+ try {
553
+ sent = await secureStore.getItemAsync(INSTALL_SENT_KEY) === "1";
554
+ } catch (err) {
555
+ reportError("install.guardRead", err);
556
+ }
557
+ }
558
+ if (sent) {
559
+ return;
560
+ }
561
+ if (installInFlight) {
562
+ return;
563
+ }
564
+ installInFlight = true;
565
+ const opts = {};
566
+ const props = {};
567
+ const android = getAndroidNative();
568
+ if (android) {
569
+ try {
570
+ const result = await android.getInstallReferrer();
571
+ if (result) {
572
+ const parsed = parseInstallReferrer(result.referrer);
573
+ if (parsed.network) opts.network = parsed.network;
574
+ if (parsed.campaign) opts.campaign = parsed.campaign;
575
+ if (parsed.gclid) props.gclid = parsed.gclid;
576
+ if (parsed.fbclid) props.fbclid = parsed.fbclid;
577
+ Object.assign(props, extractReferrerProperties(result));
578
+ }
579
+ } catch (err) {
580
+ reportError("enrich.installReferrer", err);
581
+ }
582
+ try {
583
+ const meta = await android.getMetaInstallReferrer();
584
+ const parsedMeta = meta ? parseMetaInstallReferrer(meta) : null;
585
+ if (parsedMeta) {
586
+ props.metaInstallReferrer = parsedMeta.raw;
587
+ if (parsedMeta.encryptedData) props.metaReferrerData = parsedMeta.encryptedData;
588
+ if (parsedMeta.nonce) props.metaReferrerNonce = parsedMeta.nonce;
589
+ if (!opts.network && parsedMeta.isClickThrough) {
590
+ opts.network = "meta";
591
+ if (parsedMeta.campaign) opts.campaign = parsedMeta.campaign;
592
+ }
593
+ }
594
+ } catch (err) {
595
+ reportError("enrich.metaReferrer", err);
596
+ }
597
+ }
598
+ const ios = getIosNative();
599
+ if (ios) {
600
+ try {
601
+ ios.registerAppForAttribution();
602
+ } catch (err) {
603
+ reportError("enrich.skanRegister", err);
604
+ }
605
+ try {
606
+ const asaToken = await ios.getAttributionToken();
607
+ if (asaToken) {
608
+ opts.adservicesToken = asaToken;
609
+ }
610
+ } catch (err) {
611
+ reportError("enrich.adservicesToken", err);
612
+ }
613
+ }
614
+ await resolveAdvertisingId();
615
+ const trackOpts = { type: "install", ...opts };
616
+ if (configuredAppId && android && typeof android.getIntegrityToken === "function") {
617
+ try {
618
+ const deviceId = client.getDeviceId();
619
+ const requestHash = sha256Hex(`${configuredAppId}:${deviceId}`);
620
+ const token = await android.getIntegrityToken(requestHash);
621
+ if (token) {
622
+ trackOpts.attestationToken = token;
623
+ trackOpts.attestationType = "play-integrity";
624
+ }
625
+ } catch (err) {
626
+ reportError("attest.playIntegrity", err);
627
+ }
628
+ }
629
+ if (configuredAppId && configuredApiKey && ios && typeof ios.attestKey === "function") {
630
+ try {
631
+ const deviceId = client.getDeviceId();
632
+ const challengeUrl = `${configuredEndpoint}/v1/attest/challenge?deviceId=${encodeURIComponent(deviceId)}`;
633
+ const challengeRes = await fetch(challengeUrl, {
634
+ headers: { "x-api-key": configuredApiKey }
635
+ });
636
+ if (!challengeRes.ok) throw new Error(`challenge HTTP ${challengeRes.status}`);
637
+ const { challenge } = await challengeRes.json();
638
+ const { keyId, attestationObject } = await ios.attestKey(challenge);
639
+ trackOpts.attestationToken = attestationObject;
640
+ trackOpts.attestationType = "app-attest";
641
+ trackOpts.attestationKeyId = keyId;
642
+ trackOpts.attestationChallenge = challenge;
643
+ } catch (err) {
644
+ reportError("attest.appAttest", err);
645
+ }
646
+ }
647
+ if (ios && typeof ios.getDeviceCheckToken === "function") {
648
+ try {
649
+ const token = await ios.getDeviceCheckToken();
650
+ if (token) {
651
+ trackOpts.deviceCheckToken = token;
652
+ }
653
+ } catch (err) {
654
+ reportError("attest.deviceCheck", err);
655
+ }
656
+ }
657
+ const pendingBefore = client.pendingCount;
658
+ client.track("install", props, trackOpts);
659
+ if (client.pendingCount === pendingBefore) {
660
+ installInFlight = false;
661
+ if (autoInstallEnabled && client.getRequireConsent() && !client.getTrackingConsent()) {
662
+ autoInstallPending = true;
663
+ }
664
+ return;
665
+ }
666
+ try {
667
+ installFlagStore?.setItem(INSTALL_SENT_KEY, "1");
668
+ } catch (err) {
669
+ reportError("install.flagPersist", err);
670
+ }
671
+ if (secureStore) {
672
+ try {
673
+ await secureStore.setItemAsync(INSTALL_SENT_KEY, "1");
674
+ } catch (err) {
675
+ reportError("install.flagPersistSecure", err);
676
+ }
677
+ }
678
+ }
562
679
 
563
- export { Advenue, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch };
680
+ export { Advenue, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch, trackInstall };
@@ -0,0 +1,9 @@
1
+ {
2
+ "platforms": ["apple", "android"],
3
+ "apple": {
4
+ "modules": ["AdvenueIosModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.advenue.AdvenueAndroidModule"]
8
+ }
9
+ }
@@ -0,0 +1,20 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'AdvenueIos'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.author = 'Advenue'
11
+ s.homepage = 'https://github.com/broverse/advenue'
12
+ s.platforms = { :ios => '15.1' }
13
+ s.swift_version = '5.4'
14
+ s.source = { git: 'https://github.com/broverse/advenue.git' }
15
+ s.static_framework = true
16
+
17
+ s.dependency 'ExpoModulesCore'
18
+
19
+ s.source_files = '**/*.{h,m,swift}'
20
+ end