@advenue/react-native 0.6.5 → 0.7.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.cjs CHANGED
@@ -63,21 +63,12 @@ __export(index_exports, {
63
63
  getIosNative: () => getIosNative,
64
64
  parseDirectLink: () => parseDirectLink,
65
65
  resolveAdvertisingId: () => resolveAdvertisingId,
66
- resolveClipboardMatch: () => resolveClipboardMatch,
67
66
  trackInstall: () => trackInstall
68
67
  });
69
68
  module.exports = __toCommonJS(index_exports);
70
69
  var import_install_referrer = require("@advenue/install-referrer");
71
70
  var import_sdk_core = require("@advenue/sdk-core");
72
71
 
73
- // ../shared/src/match-token.ts
74
- var MATCH_TOKEN_PREFIX = "advmatch:";
75
- function parseMatchToken(raw) {
76
- if (!raw || !raw.startsWith(MATCH_TOKEN_PREFIX)) return null;
77
- const id = raw.slice(MATCH_TOKEN_PREFIX.length).trim();
78
- return /^[A-Za-z0-9_-]{1,128}$/.test(id) ? id : null;
79
- }
80
-
81
72
  // ../signing/src/index.ts
82
73
  var K = new Uint32Array([
83
74
  1116352408,
@@ -230,7 +221,7 @@ function sha256Hex(message) {
230
221
  // package.json
231
222
  var package_default = {
232
223
  name: "@advenue/react-native",
233
- version: "0.6.5",
224
+ version: "0.7.0",
234
225
  description: "Advenue MMP React Native SDK \u2014 attribution, install tracking, and deep links (bundled iOS/Android Expo native modules)",
235
226
  type: "module",
236
227
  license: "MIT",
@@ -366,6 +357,19 @@ function getPlatformOS() {
366
357
  return null;
367
358
  }
368
359
  }
360
+ function getOsVersion() {
361
+ try {
362
+ const rn = (0, import_native_require.requireReactNative)();
363
+ if (!rn) return null;
364
+ if (rn.Platform.OS === "android") {
365
+ const release = rn.Platform.constants?.Release;
366
+ return release != null ? String(release) : null;
367
+ }
368
+ return rn.Platform.Version != null ? String(rn.Platform.Version) : null;
369
+ } catch {
370
+ return null;
371
+ }
372
+ }
369
373
  function getAppState() {
370
374
  try {
371
375
  const rn = (0, import_native_require.requireReactNative)();
@@ -466,24 +470,6 @@ function collectDeviceInfo(natives) {
466
470
  }
467
471
  return {};
468
472
  }
469
- async function resolveClipboardMatch() {
470
- const client = active();
471
- if (client.getRequireConsent() && !client.getTrackingConsent()) return null;
472
- const ios = getIosNative();
473
- if (!ios || typeof ios.getPasteboardMatchToken !== "function") {
474
- return null;
475
- }
476
- let token;
477
- try {
478
- token = ios.getPasteboardMatchToken();
479
- } catch (err) {
480
- reportError("enrich.clipboardMatch", err);
481
- return null;
482
- }
483
- const matchId = parseMatchToken(token);
484
- if (!matchId) return null;
485
- return client.exchangeClipboardMatch(matchId);
486
- }
487
473
  function applyAppState(client, status) {
488
474
  if (status === "active") {
489
475
  client.notifyAppActive();
@@ -557,7 +543,14 @@ var Advenue = {
557
543
  installFlagStore = storage;
558
544
  secureStore = secureMod ? createSecureStore(secureMod) : createNativeSecureStore(iosMod);
559
545
  const deviceId = rest.deviceId ?? await (0, import_sdk_core.resolveDurableDeviceId)(storage, secureStore ?? void 0);
560
- instance = new import_sdk_core.AdvenueClient({ ...rest, platform, deviceId, storage });
546
+ const osVersion = rest.osVersion ?? getOsVersion() ?? void 0;
547
+ instance = new import_sdk_core.AdvenueClient({
548
+ ...rest,
549
+ platform,
550
+ deviceId,
551
+ storage,
552
+ ...osVersion ? { osVersion } : {}
553
+ });
561
554
  instance.debugLog(`SDK v${package_default.version} \u2014 events -> ${configuredEndpoint}`);
562
555
  void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
563
556
  const deviceInfo = collectDeviceInfo({
@@ -596,9 +589,9 @@ var Advenue = {
596
589
  deepLinkSub = setupDeepLinks({
597
590
  getInitialURL: () => linking.getInitialURL(),
598
591
  addUrlListener: (handler) => linking.addEventListener("url", (e) => handler(e.url)),
599
- // Deferred waterfall: clipboard (deterministic) first, then the
600
- // server-side conversion poll (IDFA / probabilistic).
601
- resolveDeferred: async () => await resolveClipboardMatch() ?? client.resolveDeferredDeepLink(),
592
+ // Deferred deep link: server-side conversion poll (deterministic ids
593
+ // where available, probabilistic otherwise).
594
+ resolveDeferred: () => client.resolveDeferredDeepLink(),
602
595
  onDeepLink
603
596
  });
604
597
  deepLinkSub.ready.catch((err) => reportError("deepLink.resolve", err));
@@ -822,6 +815,5 @@ async function trackInstall() {
822
815
  getIosNative,
823
816
  parseDirectLink,
824
817
  resolveAdvertisingId,
825
- resolveClipboardMatch,
826
818
  trackInstall
827
819
  });
package/dist/index.d.cts CHANGED
@@ -132,8 +132,6 @@ interface AdvenueIosNativeModule {
132
132
  getAdvertisingId(): Promise<string | null>;
133
133
  /** Vendor-scoped device id (IDFV) — available without ATT; a fallback anchor. */
134
134
  getIdentifierForVendor(): string | null;
135
- /** Clipboard match token (advmatch:<id>) for deterministic deferred deep linking; null if absent. */
136
- getPasteboardMatchToken(): string | null;
137
135
  /**
138
136
  * G3.2n — App Attest. Generates (or reuses a keychain-cached) DCAppAttest
139
137
  * key, computes clientDataHash = SHA-256(challenge), and calls
@@ -239,14 +237,6 @@ declare function collectDeviceInfo(natives: {
239
237
  getDeviceInfo?: () => Partial<DeviceInfo>;
240
238
  } | null;
241
239
  }): DeviceInfo;
242
- /**
243
- * iOS clipboard-match deferred deep linking: reads the `advmatch:<id>` token the
244
- * link redirector wrote to the pasteboard and exchanges it for the deep link — a
245
- * DETERMINISTIC match (no IDFA, no fingerprinting). Returns null off iOS, when
246
- * there's no token, or without consent. (Reading the pasteboard shows the iOS
247
- * paste banner — the user-visible, privacy-forward deferred path.)
248
- */
249
- declare function resolveClipboardMatch(): Promise<DeepLink | null>;
250
240
  interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
251
241
  /**
252
242
  * Auto-detected from React Native's `Platform.OS` — omit it. Pass explicitly
@@ -343,4 +333,4 @@ declare const Advenue: {
343
333
  */
344
334
  declare function trackInstall(): Promise<void>;
345
335
 
346
- export { Advenue, type AdvenueAndroidNativeModule, type AdvenueDeviceInfo, type AdvenueIosNativeModule, type InstallReferrerResult, type MMKVLike, type MetaInstallReferrerResult, type RNAdvenueConfig, type SecureStoreLike, type TrackingAuthorizationStatus, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch, trackInstall };
336
+ export { Advenue, type AdvenueAndroidNativeModule, type AdvenueDeviceInfo, type AdvenueIosNativeModule, type InstallReferrerResult, type MMKVLike, type MetaInstallReferrerResult, type RNAdvenueConfig, type SecureStoreLike, type TrackingAuthorizationStatus, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, trackInstall };
package/dist/index.d.ts CHANGED
@@ -132,8 +132,6 @@ interface AdvenueIosNativeModule {
132
132
  getAdvertisingId(): Promise<string | null>;
133
133
  /** Vendor-scoped device id (IDFV) — available without ATT; a fallback anchor. */
134
134
  getIdentifierForVendor(): string | null;
135
- /** Clipboard match token (advmatch:<id>) for deterministic deferred deep linking; null if absent. */
136
- getPasteboardMatchToken(): string | null;
137
135
  /**
138
136
  * G3.2n — App Attest. Generates (or reuses a keychain-cached) DCAppAttest
139
137
  * key, computes clientDataHash = SHA-256(challenge), and calls
@@ -239,14 +237,6 @@ declare function collectDeviceInfo(natives: {
239
237
  getDeviceInfo?: () => Partial<DeviceInfo>;
240
238
  } | null;
241
239
  }): DeviceInfo;
242
- /**
243
- * iOS clipboard-match deferred deep linking: reads the `advmatch:<id>` token the
244
- * link redirector wrote to the pasteboard and exchanges it for the deep link — a
245
- * DETERMINISTIC match (no IDFA, no fingerprinting). Returns null off iOS, when
246
- * there's no token, or without consent. (Reading the pasteboard shows the iOS
247
- * paste banner — the user-visible, privacy-forward deferred path.)
248
- */
249
- declare function resolveClipboardMatch(): Promise<DeepLink | null>;
250
240
  interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
251
241
  /**
252
242
  * Auto-detected from React Native's `Platform.OS` — omit it. Pass explicitly
@@ -343,4 +333,4 @@ declare const Advenue: {
343
333
  */
344
334
  declare function trackInstall(): Promise<void>;
345
335
 
346
- export { Advenue, type AdvenueAndroidNativeModule, type AdvenueDeviceInfo, type AdvenueIosNativeModule, type InstallReferrerResult, type MMKVLike, type MetaInstallReferrerResult, type RNAdvenueConfig, type SecureStoreLike, type TrackingAuthorizationStatus, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch, trackInstall };
336
+ export { Advenue, type AdvenueAndroidNativeModule, type AdvenueDeviceInfo, type AdvenueIosNativeModule, type InstallReferrerResult, type MMKVLike, type MetaInstallReferrerResult, type RNAdvenueConfig, type SecureStoreLike, type TrackingAuthorizationStatus, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, trackInstall };
package/dist/index.js CHANGED
@@ -65,14 +65,6 @@ import {
65
65
  resolveDurableDeviceId
66
66
  } from "@advenue/sdk-core";
67
67
 
68
- // ../shared/src/match-token.ts
69
- var MATCH_TOKEN_PREFIX = "advmatch:";
70
- function parseMatchToken(raw) {
71
- if (!raw || !raw.startsWith(MATCH_TOKEN_PREFIX)) return null;
72
- const id = raw.slice(MATCH_TOKEN_PREFIX.length).trim();
73
- return /^[A-Za-z0-9_-]{1,128}$/.test(id) ? id : null;
74
- }
75
-
76
68
  // ../signing/src/index.ts
77
69
  var K = new Uint32Array([
78
70
  1116352408,
@@ -225,7 +217,7 @@ function sha256Hex(message) {
225
217
  // package.json
226
218
  var package_default = {
227
219
  name: "@advenue/react-native",
228
- version: "0.6.5",
220
+ version: "0.7.0",
229
221
  description: "Advenue MMP React Native SDK \u2014 attribution, install tracking, and deep links (bundled iOS/Android Expo native modules)",
230
222
  type: "module",
231
223
  license: "MIT",
@@ -361,6 +353,19 @@ function getPlatformOS() {
361
353
  return null;
362
354
  }
363
355
  }
356
+ function getOsVersion() {
357
+ try {
358
+ const rn = (0, import_native_require.requireReactNative)();
359
+ if (!rn) return null;
360
+ if (rn.Platform.OS === "android") {
361
+ const release = rn.Platform.constants?.Release;
362
+ return release != null ? String(release) : null;
363
+ }
364
+ return rn.Platform.Version != null ? String(rn.Platform.Version) : null;
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
364
369
  function getAppState() {
365
370
  try {
366
371
  const rn = (0, import_native_require.requireReactNative)();
@@ -461,24 +466,6 @@ function collectDeviceInfo(natives) {
461
466
  }
462
467
  return {};
463
468
  }
464
- async function resolveClipboardMatch() {
465
- const client = active();
466
- if (client.getRequireConsent() && !client.getTrackingConsent()) return null;
467
- const ios = getIosNative();
468
- if (!ios || typeof ios.getPasteboardMatchToken !== "function") {
469
- return null;
470
- }
471
- let token;
472
- try {
473
- token = ios.getPasteboardMatchToken();
474
- } catch (err) {
475
- reportError("enrich.clipboardMatch", err);
476
- return null;
477
- }
478
- const matchId = parseMatchToken(token);
479
- if (!matchId) return null;
480
- return client.exchangeClipboardMatch(matchId);
481
- }
482
469
  function applyAppState(client, status) {
483
470
  if (status === "active") {
484
471
  client.notifyAppActive();
@@ -552,7 +539,14 @@ var Advenue = {
552
539
  installFlagStore = storage;
553
540
  secureStore = secureMod ? createSecureStore(secureMod) : createNativeSecureStore(iosMod);
554
541
  const deviceId = rest.deviceId ?? await resolveDurableDeviceId(storage, secureStore ?? void 0);
555
- instance = new AdvenueClient({ ...rest, platform, deviceId, storage });
542
+ const osVersion = rest.osVersion ?? getOsVersion() ?? void 0;
543
+ instance = new AdvenueClient({
544
+ ...rest,
545
+ platform,
546
+ deviceId,
547
+ storage,
548
+ ...osVersion ? { osVersion } : {}
549
+ });
556
550
  instance.debugLog(`SDK v${package_default.version} \u2014 events -> ${configuredEndpoint}`);
557
551
  void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
558
552
  const deviceInfo = collectDeviceInfo({
@@ -591,9 +585,9 @@ var Advenue = {
591
585
  deepLinkSub = setupDeepLinks({
592
586
  getInitialURL: () => linking.getInitialURL(),
593
587
  addUrlListener: (handler) => linking.addEventListener("url", (e) => handler(e.url)),
594
- // Deferred waterfall: clipboard (deterministic) first, then the
595
- // server-side conversion poll (IDFA / probabilistic).
596
- resolveDeferred: async () => await resolveClipboardMatch() ?? client.resolveDeferredDeepLink(),
588
+ // Deferred deep link: server-side conversion poll (deterministic ids
589
+ // where available, probabilistic otherwise).
590
+ resolveDeferred: () => client.resolveDeferredDeepLink(),
597
591
  onDeepLink
598
592
  });
599
593
  deepLinkSub.ready.catch((err) => reportError("deepLink.resolve", err));
@@ -816,6 +810,5 @@ export {
816
810
  getIosNative,
817
811
  parseDirectLink,
818
812
  resolveAdvertisingId,
819
- resolveClipboardMatch,
820
813
  trackInstall
821
814
  };
@@ -17,4 +17,8 @@ Pod::Spec.new do |s|
17
17
  s.dependency 'ExpoModulesCore'
18
18
 
19
19
  s.source_files = '**/*.{h,m,swift}'
20
+ # Apple privacy manifest (required since May 2024 for SDKs that persist
21
+ # state / collect device info) — shipped as a resource bundle so Xcode
22
+ # aggregates it into the app's privacy report.
23
+ s.resource_bundles = { 'AdvenueIos_Privacy' => ['PrivacyInfo.xcprivacy'] }
20
24
  end
@@ -137,17 +137,6 @@ public class AdvenueIosModule: Module {
137
137
  return Self.onMainSync { UIDevice.current.identifierForVendor?.uuidString }
138
138
  }
139
139
 
140
- // Reads our clipboard match token (advmatch:<id>) written by the link
141
- // redirector, for deterministic deferred deep linking. Returns ONLY our
142
- // token, never arbitrary clipboard contents. (Reading the pasteboard shows
143
- // the iOS paste notification — expected, user-visible, deterministic path.)
144
- Function("getPasteboardMatchToken") { () -> String? in
145
- return Self.onMainSync { () -> String? in
146
- guard let s = UIPasteboard.general.string, s.hasPrefix("advmatch:") else { return nil }
147
- return s
148
- }
149
- }
150
-
151
140
  // G3.3 — DeviceCheck token. Calls DCDevice.current.generateToken to obtain
152
141
  // the opaque base64 token that identifies this physical device to the Apple
153
142
  // DeviceCheck API. The server uses it (with the per-app DeviceCheck key) to
@@ -0,0 +1,56 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <!-- The SDK itself does not track (no fingerprinting; IDFA collection is
6
+ app-mediated via the ATT prompt) and calls no tracking domains. -->
7
+ <key>NSPrivacyTracking</key>
8
+ <false/>
9
+ <key>NSPrivacyTrackingDomains</key>
10
+ <array/>
11
+ <key>NSPrivacyCollectedDataTypes</key>
12
+ <array>
13
+ <!-- Durable SDK device id (Keychain-backed) — attribution/analytics. -->
14
+ <dict>
15
+ <key>NSPrivacyCollectedDataType</key>
16
+ <string>NSPrivacyCollectedDataTypeDeviceID</string>
17
+ <key>NSPrivacyCollectedDataTypeLinked</key>
18
+ <true/>
19
+ <key>NSPrivacyCollectedDataTypeTracking</key>
20
+ <false/>
21
+ <key>NSPrivacyCollectedDataTypePurposes</key>
22
+ <array>
23
+ <string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>
24
+ <string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
25
+ </array>
26
+ </dict>
27
+ <!-- Coarse device characteristics (model, OS version, locale, screen) —
28
+ event enrichment / attribution corroboration. -->
29
+ <dict>
30
+ <key>NSPrivacyCollectedDataType</key>
31
+ <string>NSPrivacyCollectedDataTypeOtherDataTypes</string>
32
+ <key>NSPrivacyCollectedDataTypeLinked</key>
33
+ <true/>
34
+ <key>NSPrivacyCollectedDataTypeTracking</key>
35
+ <false/>
36
+ <key>NSPrivacyCollectedDataTypePurposes</key>
37
+ <array>
38
+ <string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
39
+ </array>
40
+ </dict>
41
+ </array>
42
+ <key>NSPrivacyAccessedAPITypes</key>
43
+ <array>
44
+ <!-- CA92.1: UserDefaults is read/written only for this app's own SDK
45
+ state (device id mirror, install flag, consent). -->
46
+ <dict>
47
+ <key>NSPrivacyAccessedAPIType</key>
48
+ <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
49
+ <key>NSPrivacyAccessedAPITypeReasons</key>
50
+ <array>
51
+ <string>CA92.1</string>
52
+ </array>
53
+ </dict>
54
+ </array>
55
+ </dict>
56
+ </plist>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@advenue/react-native",
3
- "version": "0.6.5",
3
+ "version": "0.7.0",
4
4
  "description": "Advenue MMP React Native SDK — attribution, install tracking, and deep links (bundled iOS/Android Expo native modules)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,7 +29,7 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@advenue/install-referrer": "0.2.0",
32
+ "@advenue/install-referrer": "0.3.0",
33
33
  "@advenue/sdk-core": "0.4.0"
34
34
  },
35
35
  "peerDependencies": {
package/src/index.ts CHANGED
@@ -17,7 +17,6 @@ import {
17
17
  type TrackOptions,
18
18
  resolveDurableDeviceId,
19
19
  } from '@advenue/sdk-core';
20
- import { parseMatchToken } from '@advenue/shared/match-token';
21
20
  import { sha256Hex } from '@advenue/signing';
22
21
  import pkg from '../package.json' with { type: 'json' };
23
22
  import { type DeepLinkHandle, setupDeepLinks } from './deep-links';
@@ -28,6 +27,7 @@ import {
28
27
  getAppState,
29
28
  getIosNative,
30
29
  getLinking,
30
+ getOsVersion,
31
31
  getPlatformOS,
32
32
  } from './native';
33
33
  import { createNativeSecureStore, createNativeStorage } from './native-storage';
@@ -149,37 +149,6 @@ export function collectDeviceInfo(natives: {
149
149
  return {};
150
150
  }
151
151
 
152
- /**
153
- * iOS clipboard-match deferred deep linking: reads the `advmatch:<id>` token the
154
- * link redirector wrote to the pasteboard and exchanges it for the deep link — a
155
- * DETERMINISTIC match (no IDFA, no fingerprinting). Returns null off iOS, when
156
- * there's no token, or without consent. (Reading the pasteboard shows the iOS
157
- * paste banner — the user-visible, privacy-forward deferred path.)
158
- */
159
- export async function resolveClipboardMatch(): Promise<DeepLink | null> {
160
- const client = active();
161
- if (client.getRequireConsent() && !client.getTrackingConsent()) return null;
162
- const ios = getIosNative();
163
- if (
164
- !ios ||
165
- typeof (ios as { getPasteboardMatchToken?: unknown }).getPasteboardMatchToken !== 'function'
166
- ) {
167
- return null;
168
- }
169
- let token: string | null;
170
- try {
171
- token = ios.getPasteboardMatchToken();
172
- } catch (err) {
173
- // Native pasteboard read failed (bridge error, MDM restriction) — deferred
174
- // resolution falls through to the server-side conversion poll.
175
- reportError('enrich.clipboardMatch', err);
176
- return null;
177
- }
178
- const matchId = parseMatchToken(token);
179
- if (!matchId) return null;
180
- return client.exchangeClipboardMatch(matchId);
181
- }
182
-
183
152
  export interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
184
153
  /**
185
154
  * Auto-detected from React Native's `Platform.OS` — omit it. Pass explicitly
@@ -371,7 +340,16 @@ export const Advenue = {
371
340
  secureStore = secureMod ? createSecureStore(secureMod) : createNativeSecureStore(iosMod);
372
341
  const deviceId =
373
342
  rest.deviceId ?? (await resolveDurableDeviceId(storage, secureStore ?? undefined));
374
- instance = new AdvenueClient({ ...rest, platform, deviceId, storage });
343
+ // Auto-collect the OS release version — it feeds the server's normalized
344
+ // probabilistic fingerprint (spec D1); an explicit config value wins.
345
+ const osVersion = rest.osVersion ?? getOsVersion() ?? undefined;
346
+ instance = new AdvenueClient({
347
+ ...rest,
348
+ platform,
349
+ deviceId,
350
+ storage,
351
+ ...(osVersion ? { osVersion } : {}),
352
+ });
375
353
  // One-line ground truth for "which SDK build talks to which host" — the
376
354
  // exact questions a silent integration always raises first.
377
355
  instance.debugLog(`SDK v${pkg.version} — events -> ${configuredEndpoint}`);
@@ -421,10 +399,9 @@ export const Advenue = {
421
399
  deepLinkSub = setupDeepLinks({
422
400
  getInitialURL: () => linking.getInitialURL(),
423
401
  addUrlListener: (handler) => linking.addEventListener('url', (e) => handler(e.url)),
424
- // Deferred waterfall: clipboard (deterministic) first, then the
425
- // server-side conversion poll (IDFA / probabilistic).
426
- resolveDeferred: async () =>
427
- (await resolveClipboardMatch()) ?? client.resolveDeferredDeepLink(),
402
+ // Deferred deep link: server-side conversion poll (deterministic ids
403
+ // where available, probabilistic otherwise).
404
+ resolveDeferred: () => client.resolveDeferredDeepLink(),
428
405
  onDeepLink,
429
406
  });
430
407
  // Cold-start resolution is fire-and-forget: a failure (Linking or the
@@ -123,8 +123,6 @@ export interface AdvenueIosNativeModule {
123
123
  getAdvertisingId(): Promise<string | null>;
124
124
  /** Vendor-scoped device id (IDFV) — available without ATT; a fallback anchor. */
125
125
  getIdentifierForVendor(): string | null;
126
- /** Clipboard match token (advmatch:<id>) for deterministic deferred deep linking; null if absent. */
127
- getPasteboardMatchToken(): string | null;
128
126
  /**
129
127
  * G3.2n — App Attest. Generates (or reuses a keychain-cached) DCAppAttest
130
128
  * key, computes clientDataHash = SHA-256(challenge), and calls
package/src/native.ts CHANGED
@@ -44,6 +44,29 @@ export function getPlatformOS(): string | null {
44
44
  }
45
45
  }
46
46
 
47
+ /**
48
+ * OS *release* version for attribution matching: iOS `Platform.Version` is the
49
+ * system version string ('18.2'); Android `Platform.Version` is the API LEVEL
50
+ * (34) — sending it would break the server's normalized fingerprint (the
51
+ * click-side browser UA says "Android 14"), so Android reads
52
+ * `Platform.constants.Release` and degrades to null when it's absent.
53
+ */
54
+ export function getOsVersion(): string | null {
55
+ try {
56
+ const rn = requireReactNative() as {
57
+ Platform: { OS: string; Version?: unknown; constants?: { Release?: unknown } };
58
+ } | null;
59
+ if (!rn) return null;
60
+ if (rn.Platform.OS === 'android') {
61
+ const release = rn.Platform.constants?.Release;
62
+ return release != null ? String(release) : null;
63
+ }
64
+ return rn.Platform.Version != null ? String(rn.Platform.Version) : null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
47
70
  /** Subscription handle returned by AppState.addEventListener. */
48
71
  export interface AppStateSubscription {
49
72
  remove(): void;