@advenue/react-native 0.8.0 → 0.9.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/android/src/main/java/expo/modules/advenue/AdvenueAndroidModule.kt +25 -4
- package/dist/index.cjs +38 -18
- package/dist/index.d.cts +24 -19
- package/dist/index.d.ts +24 -19
- package/dist/index.js +38 -18
- package/ios/AdvenueIosModule.swift +14 -0
- package/package.json +3 -3
- package/src/index.ts +71 -43
- package/src/native-types.ts +13 -3
|
@@ -215,10 +215,12 @@ class AdvenueAndroidModule : Module() {
|
|
|
215
215
|
|
|
216
216
|
// Play Integrity Standard API — device attestation (G3.1n).
|
|
217
217
|
//
|
|
218
|
-
// `requestHash` MUST be sha256_hex("<
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
218
|
+
// `requestHash` MUST be sha256_hex("<deviceId>") — lowercase hex of SHA-256
|
|
219
|
+
// over the device id alone. The server's mapIntegrityVerdict re-derives the
|
|
220
|
+
// same string and compares it to the token's requestHash field; the strings
|
|
221
|
+
// must match byte-for-byte. No app id is hashed in: the token is decoded
|
|
222
|
+
// against this app's Play package name and must be PLAY_RECOGNIZED, which
|
|
223
|
+
// binds the app already.
|
|
222
224
|
//
|
|
223
225
|
// The provider is warmed in OnCreate (above). If it is null — because the
|
|
224
226
|
// manifest meta-data key is absent, Play Integrity is unsupported, or warming
|
|
@@ -284,6 +286,25 @@ class AdvenueAndroidModule : Module() {
|
|
|
284
286
|
|
|
285
287
|
val ctx = appContext.reactContext?.applicationContext
|
|
286
288
|
if (ctx != null) {
|
|
289
|
+
// Meta CAPI extinfo slots 1-3. versionName is the user-facing version
|
|
290
|
+
// (slot 2, surfaced as the event's appVersion); the version code is the
|
|
291
|
+
// build identifier (slot 3). Best-effort: a missing PackageInfo must
|
|
292
|
+
// not cost the caller the rest of the device metadata.
|
|
293
|
+
map["packageName"] = ctx.packageName
|
|
294
|
+
try {
|
|
295
|
+
val pi = ctx.packageManager.getPackageInfo(ctx.packageName, 0)
|
|
296
|
+
pi.versionName?.let { map["shortVersion"] = it }
|
|
297
|
+
@Suppress("DEPRECATION")
|
|
298
|
+
val code =
|
|
299
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
|
300
|
+
pi.longVersionCode
|
|
301
|
+
} else {
|
|
302
|
+
pi.versionCode.toLong()
|
|
303
|
+
}
|
|
304
|
+
map["longVersion"] = code.toString()
|
|
305
|
+
} catch (_: Exception) {
|
|
306
|
+
// package info best-effort
|
|
307
|
+
}
|
|
287
308
|
try {
|
|
288
309
|
val dm = ctx.resources.displayMetrics
|
|
289
310
|
map["screenWidth"] = dm.widthPixels
|
package/dist/index.cjs
CHANGED
|
@@ -222,7 +222,7 @@ function sha256Hex(message) {
|
|
|
222
222
|
// package.json
|
|
223
223
|
var package_default = {
|
|
224
224
|
name: "@advenue/react-native",
|
|
225
|
-
version: "0.
|
|
225
|
+
version: "0.9.0",
|
|
226
226
|
description: "Advenue MMP React Native SDK \u2014 attribution, install tracking, and deep links (bundled iOS/Android Expo native modules)",
|
|
227
227
|
type: "module",
|
|
228
228
|
license: "MIT",
|
|
@@ -497,7 +497,7 @@ function resolveDmaConsent() {
|
|
|
497
497
|
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
498
498
|
return consent;
|
|
499
499
|
}
|
|
500
|
-
function
|
|
500
|
+
function readNativeDeviceInfo(natives) {
|
|
501
501
|
try {
|
|
502
502
|
if (natives.platform === "android" && natives.android?.getDeviceInfo) {
|
|
503
503
|
return natives.android.getDeviceInfo();
|
|
@@ -509,6 +509,10 @@ function collectDeviceInfo(natives) {
|
|
|
509
509
|
}
|
|
510
510
|
return {};
|
|
511
511
|
}
|
|
512
|
+
function collectDeviceInfo(natives) {
|
|
513
|
+
const { shortVersion: _shortVersion, ...info } = readNativeDeviceInfo(natives);
|
|
514
|
+
return info;
|
|
515
|
+
}
|
|
512
516
|
function applyAppState(client, status) {
|
|
513
517
|
if (status === "active") {
|
|
514
518
|
client.notifyAppActive();
|
|
@@ -527,7 +531,7 @@ var autoInstallPending = false;
|
|
|
527
531
|
var autoInstallEnabled = true;
|
|
528
532
|
var manualConsentSet = false;
|
|
529
533
|
var tcfCollectionEnabled = false;
|
|
530
|
-
var
|
|
534
|
+
var ATTEST_CHALLENGE_TIMEOUT_MS = 5e3;
|
|
531
535
|
var configuredApiKey = null;
|
|
532
536
|
var configuredEndpoint = import_sdk_core.DEFAULT_ENDPOINT;
|
|
533
537
|
var aemSeenUrlHashes = /* @__PURE__ */ new Set();
|
|
@@ -571,7 +575,6 @@ var Advenue = {
|
|
|
571
575
|
mmkv,
|
|
572
576
|
secureStore: secureMod,
|
|
573
577
|
onDeepLink,
|
|
574
|
-
appId,
|
|
575
578
|
autoTrackInstall,
|
|
576
579
|
tcfDataCollection,
|
|
577
580
|
...rest
|
|
@@ -581,7 +584,6 @@ var Advenue = {
|
|
|
581
584
|
tcfCollectionEnabled = tcfDataCollection === true;
|
|
582
585
|
autoInstallEnabled = autoTrackInstall !== false;
|
|
583
586
|
const platform = resolvePlatform(rest.platform);
|
|
584
|
-
configuredAppId = appId ?? null;
|
|
585
587
|
configuredApiKey = config.apiKey;
|
|
586
588
|
configuredEndpoint = config.endpoint ?? import_sdk_core.DEFAULT_ENDPOINT;
|
|
587
589
|
appStateSub?.remove();
|
|
@@ -596,21 +598,26 @@ var Advenue = {
|
|
|
596
598
|
secureStore = secureMod ? createSecureStore(secureMod) : createNativeSecureStore(iosMod);
|
|
597
599
|
const deviceId = rest.deviceId ?? await (0, import_sdk_core.resolveDurableDeviceId)(storage, secureStore ?? void 0);
|
|
598
600
|
const osVersion = rest.osVersion ?? getOsVersion() ?? void 0;
|
|
601
|
+
const nativeDeviceInfo = readNativeDeviceInfo({
|
|
602
|
+
platform,
|
|
603
|
+
ios: getIosNative(),
|
|
604
|
+
android: getAndroidNative()
|
|
605
|
+
});
|
|
606
|
+
const { shortVersion, ...deviceInfo } = nativeDeviceInfo;
|
|
607
|
+
const appVersion = rest.appVersion ?? shortVersion ?? void 0;
|
|
599
608
|
instance = new import_sdk_core.AdvenueClient({
|
|
600
609
|
...rest,
|
|
601
610
|
platform,
|
|
602
611
|
deviceId,
|
|
603
612
|
storage,
|
|
604
|
-
...osVersion ? { osVersion } : {}
|
|
613
|
+
...osVersion ? { osVersion } : {},
|
|
614
|
+
...appVersion ? { appVersion } : {},
|
|
615
|
+
// Stamped by the SDK, never by the host app — see AdvenueConfig.sdkVersion.
|
|
616
|
+
sdkVersion: package_default.version
|
|
605
617
|
});
|
|
606
618
|
instance.debugLog(`SDK v${package_default.version} \u2014 events -> ${configuredEndpoint}`);
|
|
607
619
|
if (tcfCollectionEnabled) resolveDmaConsent();
|
|
608
620
|
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
609
|
-
const deviceInfo = collectDeviceInfo({
|
|
610
|
-
platform,
|
|
611
|
-
ios: getIosNative(),
|
|
612
|
-
android: getAndroidNative()
|
|
613
|
-
});
|
|
614
621
|
if (Object.keys(deviceInfo).length > 0) instance.setDeviceInfo(deviceInfo);
|
|
615
622
|
if (!config.disableAutoSessions) {
|
|
616
623
|
const appState = getAppState();
|
|
@@ -721,6 +728,9 @@ var Advenue = {
|
|
|
721
728
|
getDeviceId: () => active().getDeviceId(),
|
|
722
729
|
/** The Advenue ID — alias of getDeviceId (see AdvenueClient.getAdvenueId). */
|
|
723
730
|
getAdvenueId: () => active().getAdvenueId(),
|
|
731
|
+
/** Which app the server resolved this API key to; null until a batch lands
|
|
732
|
+
* (see AdvenueClient.getResolvedAppId). */
|
|
733
|
+
getResolvedAppId: () => active().getResolvedAppId(),
|
|
724
734
|
shutdown() {
|
|
725
735
|
appStateSub?.remove();
|
|
726
736
|
appStateSub = null;
|
|
@@ -734,7 +744,6 @@ var Advenue = {
|
|
|
734
744
|
autoInstallEnabled = true;
|
|
735
745
|
manualConsentSet = false;
|
|
736
746
|
tcfCollectionEnabled = false;
|
|
737
|
-
configuredAppId = null;
|
|
738
747
|
configuredApiKey = null;
|
|
739
748
|
configuredEndpoint = import_sdk_core.DEFAULT_ENDPOINT;
|
|
740
749
|
aemSeenUrlHashes.clear();
|
|
@@ -812,10 +821,10 @@ async function trackInstall() {
|
|
|
812
821
|
}
|
|
813
822
|
await resolveAdvertisingId();
|
|
814
823
|
const trackOpts = { type: "install", ...opts };
|
|
815
|
-
if (
|
|
824
|
+
if (android && typeof android.getIntegrityToken === "function") {
|
|
816
825
|
try {
|
|
817
826
|
const deviceId = client.getDeviceId();
|
|
818
|
-
const requestHash = sha256Hex(
|
|
827
|
+
const requestHash = sha256Hex(deviceId);
|
|
819
828
|
const token = await android.getIntegrityToken(requestHash);
|
|
820
829
|
if (token) {
|
|
821
830
|
trackOpts.attestationToken = token;
|
|
@@ -825,13 +834,24 @@ async function trackInstall() {
|
|
|
825
834
|
reportError("attest.playIntegrity", err);
|
|
826
835
|
}
|
|
827
836
|
}
|
|
828
|
-
if (
|
|
837
|
+
if (configuredApiKey && ios && typeof ios.attestKey === "function") {
|
|
829
838
|
try {
|
|
830
839
|
const deviceId = client.getDeviceId();
|
|
831
840
|
const challengeUrl = `${configuredEndpoint}/v1/attest/challenge?deviceId=${encodeURIComponent(deviceId)}`;
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
841
|
+
const ac = new AbortController();
|
|
842
|
+
const timer = setTimeout(
|
|
843
|
+
() => ac.abort(new Error("attest challenge timed out")),
|
|
844
|
+
ATTEST_CHALLENGE_TIMEOUT_MS
|
|
845
|
+
);
|
|
846
|
+
let challengeRes;
|
|
847
|
+
try {
|
|
848
|
+
challengeRes = await fetch(challengeUrl, {
|
|
849
|
+
headers: { "x-api-key": configuredApiKey },
|
|
850
|
+
signal: ac.signal
|
|
851
|
+
});
|
|
852
|
+
} finally {
|
|
853
|
+
clearTimeout(timer);
|
|
854
|
+
}
|
|
835
855
|
if (!challengeRes.ok) throw new Error(`challenge HTTP ${challengeRes.status}`);
|
|
836
856
|
const { challenge } = await challengeRes.json();
|
|
837
857
|
const { keyId, attestationObject } = await ios.attestKey(challenge);
|
package/dist/index.d.cts
CHANGED
|
@@ -44,6 +44,14 @@ interface MetaInstallReferrerResult {
|
|
|
44
44
|
* real `DeviceInfo` shape.
|
|
45
45
|
*/
|
|
46
46
|
interface AdvenueDeviceInfo {
|
|
47
|
+
/** Bundle id / package name — Meta CAPI extinfo slot 1. */
|
|
48
|
+
packageName?: string;
|
|
49
|
+
/** User-facing app version (CFBundleShortVersionString / versionName) —
|
|
50
|
+
* extinfo slot 2. Surfaced as the event's top-level `appVersion`, not as
|
|
51
|
+
* part of the deviceInfo blob. */
|
|
52
|
+
shortVersion?: string;
|
|
53
|
+
/** Build identifier (CFBundleVersion / versionCode) — extinfo slot 3. */
|
|
54
|
+
longVersion?: string;
|
|
47
55
|
model?: string;
|
|
48
56
|
locale?: string;
|
|
49
57
|
carrier?: string;
|
|
@@ -83,9 +91,11 @@ interface AdvenueAndroidNativeModule {
|
|
|
83
91
|
/**
|
|
84
92
|
* Play Integrity Standard API token (G3.1n).
|
|
85
93
|
*
|
|
86
|
-
* `requestHash` MUST be sha256_hex("<
|
|
87
|
-
*
|
|
88
|
-
*
|
|
94
|
+
* `requestHash` MUST be sha256_hex("<deviceId>") — lowercase hex SHA-256 of the
|
|
95
|
+
* device id alone. The server's mapIntegrityVerdict re-derives the same string
|
|
96
|
+
* and compares it byte-for-byte. No app id is hashed in: the token is decoded
|
|
97
|
+
* against the app's Play package name and must be PLAY_RECOGNIZED, which binds
|
|
98
|
+
* the app already.
|
|
89
99
|
*
|
|
90
100
|
* Rejects when:
|
|
91
101
|
* - ECONFIG: `io.advenue.INTEGRITY_CLOUD_PROJECT_NUMBER` meta-data is absent.
|
|
@@ -264,15 +274,19 @@ declare function resolveDmaConsent(): Consent | null;
|
|
|
264
274
|
* native-side error degrades to an empty object so callers can unconditionally
|
|
265
275
|
* pass the result through.
|
|
266
276
|
*/
|
|
267
|
-
|
|
277
|
+
type NativeDeviceInfo = Partial<DeviceInfo> & {
|
|
278
|
+
shortVersion?: string;
|
|
279
|
+
};
|
|
280
|
+
type DeviceInfoNatives = {
|
|
268
281
|
platform: 'ios' | 'android' | string;
|
|
269
282
|
ios?: {
|
|
270
|
-
getDeviceInfo?: () =>
|
|
283
|
+
getDeviceInfo?: () => NativeDeviceInfo;
|
|
271
284
|
} | null;
|
|
272
285
|
android?: {
|
|
273
|
-
getDeviceInfo?: () =>
|
|
286
|
+
getDeviceInfo?: () => NativeDeviceInfo;
|
|
274
287
|
} | null;
|
|
275
|
-
}
|
|
288
|
+
};
|
|
289
|
+
declare function collectDeviceInfo(natives: DeviceInfoNatives): DeviceInfo;
|
|
276
290
|
interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
|
|
277
291
|
/**
|
|
278
292
|
* Auto-detected from React Native's `Platform.OS` — omit it. Pass explicitly
|
|
@@ -287,18 +301,6 @@ interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
|
|
|
287
301
|
* supply storage. When set, it replaces the built-in native storage.
|
|
288
302
|
*/
|
|
289
303
|
mmkv?: MMKVLike;
|
|
290
|
-
/**
|
|
291
|
-
* G3.1n — Play Integrity (Android) / App Attest (iOS) attestation.
|
|
292
|
-
*
|
|
293
|
-
* Your app's Advenue appId (the UUID in Settings → API Keys). When set on
|
|
294
|
-
* Android, the automatic install obtains a Play Integrity Standard token and
|
|
295
|
-
* attaches it to the install event. The requestHash is computed as
|
|
296
|
-
* `sha256Hex("<appId>:<deviceId>")`, matching the server's verification
|
|
297
|
-
* formula exactly (see `makeDecodeAndroid` in the worker).
|
|
298
|
-
*
|
|
299
|
-
* Omit to run without attestation (the server field stays `unavailable`).
|
|
300
|
-
*/
|
|
301
|
-
appId?: string;
|
|
302
304
|
/**
|
|
303
305
|
* @internal Secure-store override (tests / custom embeddings). The SDK uses
|
|
304
306
|
* its own Keychain-backed native storage on iOS for the reinstall-resilient
|
|
@@ -363,6 +365,9 @@ declare const Advenue: {
|
|
|
363
365
|
getDeviceId: () => string;
|
|
364
366
|
/** The Advenue ID — alias of getDeviceId (see AdvenueClient.getAdvenueId). */
|
|
365
367
|
getAdvenueId: () => string;
|
|
368
|
+
/** Which app the server resolved this API key to; null until a batch lands
|
|
369
|
+
* (see AdvenueClient.getResolvedAppId). */
|
|
370
|
+
getResolvedAppId: () => string | null;
|
|
366
371
|
shutdown(): void;
|
|
367
372
|
};
|
|
368
373
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,14 @@ interface MetaInstallReferrerResult {
|
|
|
44
44
|
* real `DeviceInfo` shape.
|
|
45
45
|
*/
|
|
46
46
|
interface AdvenueDeviceInfo {
|
|
47
|
+
/** Bundle id / package name — Meta CAPI extinfo slot 1. */
|
|
48
|
+
packageName?: string;
|
|
49
|
+
/** User-facing app version (CFBundleShortVersionString / versionName) —
|
|
50
|
+
* extinfo slot 2. Surfaced as the event's top-level `appVersion`, not as
|
|
51
|
+
* part of the deviceInfo blob. */
|
|
52
|
+
shortVersion?: string;
|
|
53
|
+
/** Build identifier (CFBundleVersion / versionCode) — extinfo slot 3. */
|
|
54
|
+
longVersion?: string;
|
|
47
55
|
model?: string;
|
|
48
56
|
locale?: string;
|
|
49
57
|
carrier?: string;
|
|
@@ -83,9 +91,11 @@ interface AdvenueAndroidNativeModule {
|
|
|
83
91
|
/**
|
|
84
92
|
* Play Integrity Standard API token (G3.1n).
|
|
85
93
|
*
|
|
86
|
-
* `requestHash` MUST be sha256_hex("<
|
|
87
|
-
*
|
|
88
|
-
*
|
|
94
|
+
* `requestHash` MUST be sha256_hex("<deviceId>") — lowercase hex SHA-256 of the
|
|
95
|
+
* device id alone. The server's mapIntegrityVerdict re-derives the same string
|
|
96
|
+
* and compares it byte-for-byte. No app id is hashed in: the token is decoded
|
|
97
|
+
* against the app's Play package name and must be PLAY_RECOGNIZED, which binds
|
|
98
|
+
* the app already.
|
|
89
99
|
*
|
|
90
100
|
* Rejects when:
|
|
91
101
|
* - ECONFIG: `io.advenue.INTEGRITY_CLOUD_PROJECT_NUMBER` meta-data is absent.
|
|
@@ -264,15 +274,19 @@ declare function resolveDmaConsent(): Consent | null;
|
|
|
264
274
|
* native-side error degrades to an empty object so callers can unconditionally
|
|
265
275
|
* pass the result through.
|
|
266
276
|
*/
|
|
267
|
-
|
|
277
|
+
type NativeDeviceInfo = Partial<DeviceInfo> & {
|
|
278
|
+
shortVersion?: string;
|
|
279
|
+
};
|
|
280
|
+
type DeviceInfoNatives = {
|
|
268
281
|
platform: 'ios' | 'android' | string;
|
|
269
282
|
ios?: {
|
|
270
|
-
getDeviceInfo?: () =>
|
|
283
|
+
getDeviceInfo?: () => NativeDeviceInfo;
|
|
271
284
|
} | null;
|
|
272
285
|
android?: {
|
|
273
|
-
getDeviceInfo?: () =>
|
|
286
|
+
getDeviceInfo?: () => NativeDeviceInfo;
|
|
274
287
|
} | null;
|
|
275
|
-
}
|
|
288
|
+
};
|
|
289
|
+
declare function collectDeviceInfo(natives: DeviceInfoNatives): DeviceInfo;
|
|
276
290
|
interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
|
|
277
291
|
/**
|
|
278
292
|
* Auto-detected from React Native's `Platform.OS` — omit it. Pass explicitly
|
|
@@ -287,18 +301,6 @@ interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
|
|
|
287
301
|
* supply storage. When set, it replaces the built-in native storage.
|
|
288
302
|
*/
|
|
289
303
|
mmkv?: MMKVLike;
|
|
290
|
-
/**
|
|
291
|
-
* G3.1n — Play Integrity (Android) / App Attest (iOS) attestation.
|
|
292
|
-
*
|
|
293
|
-
* Your app's Advenue appId (the UUID in Settings → API Keys). When set on
|
|
294
|
-
* Android, the automatic install obtains a Play Integrity Standard token and
|
|
295
|
-
* attaches it to the install event. The requestHash is computed as
|
|
296
|
-
* `sha256Hex("<appId>:<deviceId>")`, matching the server's verification
|
|
297
|
-
* formula exactly (see `makeDecodeAndroid` in the worker).
|
|
298
|
-
*
|
|
299
|
-
* Omit to run without attestation (the server field stays `unavailable`).
|
|
300
|
-
*/
|
|
301
|
-
appId?: string;
|
|
302
304
|
/**
|
|
303
305
|
* @internal Secure-store override (tests / custom embeddings). The SDK uses
|
|
304
306
|
* its own Keychain-backed native storage on iOS for the reinstall-resilient
|
|
@@ -363,6 +365,9 @@ declare const Advenue: {
|
|
|
363
365
|
getDeviceId: () => string;
|
|
364
366
|
/** The Advenue ID — alias of getDeviceId (see AdvenueClient.getAdvenueId). */
|
|
365
367
|
getAdvenueId: () => string;
|
|
368
|
+
/** Which app the server resolved this API key to; null until a batch lands
|
|
369
|
+
* (see AdvenueClient.getResolvedAppId). */
|
|
370
|
+
getResolvedAppId: () => string | null;
|
|
366
371
|
shutdown(): void;
|
|
367
372
|
};
|
|
368
373
|
/**
|
package/dist/index.js
CHANGED
|
@@ -218,7 +218,7 @@ function sha256Hex(message) {
|
|
|
218
218
|
// package.json
|
|
219
219
|
var package_default = {
|
|
220
220
|
name: "@advenue/react-native",
|
|
221
|
-
version: "0.
|
|
221
|
+
version: "0.9.0",
|
|
222
222
|
description: "Advenue MMP React Native SDK \u2014 attribution, install tracking, and deep links (bundled iOS/Android Expo native modules)",
|
|
223
223
|
type: "module",
|
|
224
224
|
license: "MIT",
|
|
@@ -493,7 +493,7 @@ function resolveDmaConsent() {
|
|
|
493
493
|
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
494
494
|
return consent;
|
|
495
495
|
}
|
|
496
|
-
function
|
|
496
|
+
function readNativeDeviceInfo(natives) {
|
|
497
497
|
try {
|
|
498
498
|
if (natives.platform === "android" && natives.android?.getDeviceInfo) {
|
|
499
499
|
return natives.android.getDeviceInfo();
|
|
@@ -505,6 +505,10 @@ function collectDeviceInfo(natives) {
|
|
|
505
505
|
}
|
|
506
506
|
return {};
|
|
507
507
|
}
|
|
508
|
+
function collectDeviceInfo(natives) {
|
|
509
|
+
const { shortVersion: _shortVersion, ...info } = readNativeDeviceInfo(natives);
|
|
510
|
+
return info;
|
|
511
|
+
}
|
|
508
512
|
function applyAppState(client, status) {
|
|
509
513
|
if (status === "active") {
|
|
510
514
|
client.notifyAppActive();
|
|
@@ -523,7 +527,7 @@ var autoInstallPending = false;
|
|
|
523
527
|
var autoInstallEnabled = true;
|
|
524
528
|
var manualConsentSet = false;
|
|
525
529
|
var tcfCollectionEnabled = false;
|
|
526
|
-
var
|
|
530
|
+
var ATTEST_CHALLENGE_TIMEOUT_MS = 5e3;
|
|
527
531
|
var configuredApiKey = null;
|
|
528
532
|
var configuredEndpoint = DEFAULT_ENDPOINT;
|
|
529
533
|
var aemSeenUrlHashes = /* @__PURE__ */ new Set();
|
|
@@ -567,7 +571,6 @@ var Advenue = {
|
|
|
567
571
|
mmkv,
|
|
568
572
|
secureStore: secureMod,
|
|
569
573
|
onDeepLink,
|
|
570
|
-
appId,
|
|
571
574
|
autoTrackInstall,
|
|
572
575
|
tcfDataCollection,
|
|
573
576
|
...rest
|
|
@@ -577,7 +580,6 @@ var Advenue = {
|
|
|
577
580
|
tcfCollectionEnabled = tcfDataCollection === true;
|
|
578
581
|
autoInstallEnabled = autoTrackInstall !== false;
|
|
579
582
|
const platform = resolvePlatform(rest.platform);
|
|
580
|
-
configuredAppId = appId ?? null;
|
|
581
583
|
configuredApiKey = config.apiKey;
|
|
582
584
|
configuredEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
583
585
|
appStateSub?.remove();
|
|
@@ -592,21 +594,26 @@ var Advenue = {
|
|
|
592
594
|
secureStore = secureMod ? createSecureStore(secureMod) : createNativeSecureStore(iosMod);
|
|
593
595
|
const deviceId = rest.deviceId ?? await resolveDurableDeviceId(storage, secureStore ?? void 0);
|
|
594
596
|
const osVersion = rest.osVersion ?? getOsVersion() ?? void 0;
|
|
597
|
+
const nativeDeviceInfo = readNativeDeviceInfo({
|
|
598
|
+
platform,
|
|
599
|
+
ios: getIosNative(),
|
|
600
|
+
android: getAndroidNative()
|
|
601
|
+
});
|
|
602
|
+
const { shortVersion, ...deviceInfo } = nativeDeviceInfo;
|
|
603
|
+
const appVersion = rest.appVersion ?? shortVersion ?? void 0;
|
|
595
604
|
instance = new AdvenueClient({
|
|
596
605
|
...rest,
|
|
597
606
|
platform,
|
|
598
607
|
deviceId,
|
|
599
608
|
storage,
|
|
600
|
-
...osVersion ? { osVersion } : {}
|
|
609
|
+
...osVersion ? { osVersion } : {},
|
|
610
|
+
...appVersion ? { appVersion } : {},
|
|
611
|
+
// Stamped by the SDK, never by the host app — see AdvenueConfig.sdkVersion.
|
|
612
|
+
sdkVersion: package_default.version
|
|
601
613
|
});
|
|
602
614
|
instance.debugLog(`SDK v${package_default.version} \u2014 events -> ${configuredEndpoint}`);
|
|
603
615
|
if (tcfCollectionEnabled) resolveDmaConsent();
|
|
604
616
|
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
605
|
-
const deviceInfo = collectDeviceInfo({
|
|
606
|
-
platform,
|
|
607
|
-
ios: getIosNative(),
|
|
608
|
-
android: getAndroidNative()
|
|
609
|
-
});
|
|
610
617
|
if (Object.keys(deviceInfo).length > 0) instance.setDeviceInfo(deviceInfo);
|
|
611
618
|
if (!config.disableAutoSessions) {
|
|
612
619
|
const appState = getAppState();
|
|
@@ -717,6 +724,9 @@ var Advenue = {
|
|
|
717
724
|
getDeviceId: () => active().getDeviceId(),
|
|
718
725
|
/** The Advenue ID — alias of getDeviceId (see AdvenueClient.getAdvenueId). */
|
|
719
726
|
getAdvenueId: () => active().getAdvenueId(),
|
|
727
|
+
/** Which app the server resolved this API key to; null until a batch lands
|
|
728
|
+
* (see AdvenueClient.getResolvedAppId). */
|
|
729
|
+
getResolvedAppId: () => active().getResolvedAppId(),
|
|
720
730
|
shutdown() {
|
|
721
731
|
appStateSub?.remove();
|
|
722
732
|
appStateSub = null;
|
|
@@ -730,7 +740,6 @@ var Advenue = {
|
|
|
730
740
|
autoInstallEnabled = true;
|
|
731
741
|
manualConsentSet = false;
|
|
732
742
|
tcfCollectionEnabled = false;
|
|
733
|
-
configuredAppId = null;
|
|
734
743
|
configuredApiKey = null;
|
|
735
744
|
configuredEndpoint = DEFAULT_ENDPOINT;
|
|
736
745
|
aemSeenUrlHashes.clear();
|
|
@@ -808,10 +817,10 @@ async function trackInstall() {
|
|
|
808
817
|
}
|
|
809
818
|
await resolveAdvertisingId();
|
|
810
819
|
const trackOpts = { type: "install", ...opts };
|
|
811
|
-
if (
|
|
820
|
+
if (android && typeof android.getIntegrityToken === "function") {
|
|
812
821
|
try {
|
|
813
822
|
const deviceId = client.getDeviceId();
|
|
814
|
-
const requestHash = sha256Hex(
|
|
823
|
+
const requestHash = sha256Hex(deviceId);
|
|
815
824
|
const token = await android.getIntegrityToken(requestHash);
|
|
816
825
|
if (token) {
|
|
817
826
|
trackOpts.attestationToken = token;
|
|
@@ -821,13 +830,24 @@ async function trackInstall() {
|
|
|
821
830
|
reportError("attest.playIntegrity", err);
|
|
822
831
|
}
|
|
823
832
|
}
|
|
824
|
-
if (
|
|
833
|
+
if (configuredApiKey && ios && typeof ios.attestKey === "function") {
|
|
825
834
|
try {
|
|
826
835
|
const deviceId = client.getDeviceId();
|
|
827
836
|
const challengeUrl = `${configuredEndpoint}/v1/attest/challenge?deviceId=${encodeURIComponent(deviceId)}`;
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
837
|
+
const ac = new AbortController();
|
|
838
|
+
const timer = setTimeout(
|
|
839
|
+
() => ac.abort(new Error("attest challenge timed out")),
|
|
840
|
+
ATTEST_CHALLENGE_TIMEOUT_MS
|
|
841
|
+
);
|
|
842
|
+
let challengeRes;
|
|
843
|
+
try {
|
|
844
|
+
challengeRes = await fetch(challengeUrl, {
|
|
845
|
+
headers: { "x-api-key": configuredApiKey },
|
|
846
|
+
signal: ac.signal
|
|
847
|
+
});
|
|
848
|
+
} finally {
|
|
849
|
+
clearTimeout(timer);
|
|
850
|
+
}
|
|
831
851
|
if (!challengeRes.ok) throw new Error(`challenge HTTP ${challengeRes.status}`);
|
|
832
852
|
const { challenge } = await challengeRes.json();
|
|
833
853
|
const { keyId, attestationObject } = await ios.attestKey(challenge);
|
|
@@ -249,6 +249,20 @@ public class AdvenueIosModule: Module {
|
|
|
249
249
|
info["timezone"] = TimeZone.current.identifier
|
|
250
250
|
info["cpuCores"] = ProcessInfo.processInfo.activeProcessorCount
|
|
251
251
|
|
|
252
|
+
// Meta CAPI extinfo slots 1-3. CFBundleShortVersionString is the
|
|
253
|
+
// user-facing version (slot 2, surfaced as the event's appVersion);
|
|
254
|
+
// CFBundleVersion is the build identifier (slot 3).
|
|
255
|
+
let bundle = Bundle.main
|
|
256
|
+
if let bundleId = bundle.bundleIdentifier {
|
|
257
|
+
info["packageName"] = bundleId
|
|
258
|
+
}
|
|
259
|
+
if let short = bundle.infoDictionary?["CFBundleShortVersionString"] as? String {
|
|
260
|
+
info["shortVersion"] = short
|
|
261
|
+
}
|
|
262
|
+
if let build = bundle.infoDictionary?["CFBundleVersion"] as? String {
|
|
263
|
+
info["longVersion"] = build
|
|
264
|
+
}
|
|
265
|
+
|
|
252
266
|
let (screenWidth, screenHeight, screenDensity) = Self.onMainSync { () -> (Int, Int, Double) in
|
|
253
267
|
let screen = UIScreen.main
|
|
254
268
|
return (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@advenue/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@advenue/install-referrer": "0.3.0",
|
|
33
|
-
"@advenue/sdk-core": "0.
|
|
33
|
+
"@advenue/sdk-core": "0.6.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@expo/config-plugins": ">=8",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"tsup": "^8.5.1",
|
|
51
51
|
"typescript": "^5.7.2",
|
|
52
52
|
"vitest": "^2.1.8",
|
|
53
|
-
"@advenue/shared": "0.
|
|
53
|
+
"@advenue/shared": "0.3.0",
|
|
54
54
|
"@advenue/signing": "0.0.0"
|
|
55
55
|
},
|
|
56
56
|
"react-native": "./dist/index.cjs",
|
package/src/index.ts
CHANGED
|
@@ -178,11 +178,18 @@ export function resolveDmaConsent(): Consent | null {
|
|
|
178
178
|
* native-side error degrades to an empty object so callers can unconditionally
|
|
179
179
|
* pass the result through.
|
|
180
180
|
*/
|
|
181
|
-
|
|
181
|
+
type NativeDeviceInfo = Partial<DeviceInfo> & { shortVersion?: string };
|
|
182
|
+
|
|
183
|
+
type DeviceInfoNatives = {
|
|
182
184
|
platform: 'ios' | 'android' | string;
|
|
183
|
-
ios?: { getDeviceInfo?: () =>
|
|
184
|
-
android?: { getDeviceInfo?: () =>
|
|
185
|
-
}
|
|
185
|
+
ios?: { getDeviceInfo?: () => NativeDeviceInfo } | null;
|
|
186
|
+
android?: { getDeviceInfo?: () => NativeDeviceInfo } | null;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** The raw native map, `shortVersion` included. Internal: `initialize` needs
|
|
190
|
+
* that field for the event's top-level `appVersion`, everyone else wants the
|
|
191
|
+
* deviceInfo blob that `collectDeviceInfo` returns. */
|
|
192
|
+
function readNativeDeviceInfo(natives: DeviceInfoNatives): NativeDeviceInfo {
|
|
186
193
|
try {
|
|
187
194
|
if (natives.platform === 'android' && natives.android?.getDeviceInfo) {
|
|
188
195
|
return natives.android.getDeviceInfo();
|
|
@@ -196,6 +203,13 @@ export function collectDeviceInfo(natives: {
|
|
|
196
203
|
return {};
|
|
197
204
|
}
|
|
198
205
|
|
|
206
|
+
export function collectDeviceInfo(natives: DeviceInfoNatives): DeviceInfo {
|
|
207
|
+
// shortVersion is the app's own version, carried by the event's `appVersion`
|
|
208
|
+
// field (extinfo slot 2) — it is not part of the persisted deviceInfo blob.
|
|
209
|
+
const { shortVersion: _shortVersion, ...info } = readNativeDeviceInfo(natives);
|
|
210
|
+
return info;
|
|
211
|
+
}
|
|
212
|
+
|
|
199
213
|
export interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platform'> {
|
|
200
214
|
/**
|
|
201
215
|
* Auto-detected from React Native's `Platform.OS` — omit it. Pass explicitly
|
|
@@ -210,18 +224,6 @@ export interface RNAdvenueConfig extends Omit<AdvenueConfig, 'storage' | 'platfo
|
|
|
210
224
|
* supply storage. When set, it replaces the built-in native storage.
|
|
211
225
|
*/
|
|
212
226
|
mmkv?: MMKVLike;
|
|
213
|
-
/**
|
|
214
|
-
* G3.1n — Play Integrity (Android) / App Attest (iOS) attestation.
|
|
215
|
-
*
|
|
216
|
-
* Your app's Advenue appId (the UUID in Settings → API Keys). When set on
|
|
217
|
-
* Android, the automatic install obtains a Play Integrity Standard token and
|
|
218
|
-
* attaches it to the install event. The requestHash is computed as
|
|
219
|
-
* `sha256Hex("<appId>:<deviceId>")`, matching the server's verification
|
|
220
|
-
* formula exactly (see `makeDecodeAndroid` in the worker).
|
|
221
|
-
*
|
|
222
|
-
* Omit to run without attestation (the server field stays `unavailable`).
|
|
223
|
-
*/
|
|
224
|
-
appId?: string;
|
|
225
227
|
/**
|
|
226
228
|
* @internal Secure-store override (tests / custom embeddings). The SDK uses
|
|
227
229
|
* its own Keychain-backed native storage on iOS for the reinstall-resilient
|
|
@@ -299,9 +301,10 @@ let manualConsentSet = false;
|
|
|
299
301
|
// Whether the current initialize() opted into TCF auto-collection
|
|
300
302
|
// (RNAdvenueConfig.tcfDataCollection). Reset by initialize()/shutdown().
|
|
301
303
|
let tcfCollectionEnabled = false;
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
304
|
+
/** Deadline for the App Attest challenge fetch. Enrichment on the install path:
|
|
305
|
+
* bounded well under the transport's 15s so a slow host costs the install a
|
|
306
|
+
* moment, never the platform socket timeout. */
|
|
307
|
+
const ATTEST_CHALLENGE_TIMEOUT_MS = 5_000;
|
|
305
308
|
// G3.2n: API key + endpoint needed to fetch the App Attest challenge from the
|
|
306
309
|
// server. Stored at module level so trackInstall() can issue the challenge
|
|
307
310
|
// request without reaching into the private AdvenueClient fields.
|
|
@@ -386,7 +389,6 @@ export const Advenue = {
|
|
|
386
389
|
mmkv,
|
|
387
390
|
secureStore: secureMod,
|
|
388
391
|
onDeepLink,
|
|
389
|
-
appId,
|
|
390
392
|
autoTrackInstall,
|
|
391
393
|
tcfDataCollection,
|
|
392
394
|
...rest
|
|
@@ -398,7 +400,6 @@ export const Advenue = {
|
|
|
398
400
|
tcfCollectionEnabled = tcfDataCollection === true;
|
|
399
401
|
autoInstallEnabled = autoTrackInstall !== false;
|
|
400
402
|
const platform = resolvePlatform(rest.platform);
|
|
401
|
-
configuredAppId = appId ?? null;
|
|
402
403
|
configuredApiKey = config.apiKey;
|
|
403
404
|
configuredEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
404
405
|
appStateSub?.remove();
|
|
@@ -421,12 +422,29 @@ export const Advenue = {
|
|
|
421
422
|
// Auto-collect the OS release version — it feeds the server's normalized
|
|
422
423
|
// probabilistic fingerprint (spec D1); an explicit config value wins.
|
|
423
424
|
const osVersion = rest.osVersion ?? getOsVersion() ?? undefined;
|
|
425
|
+
// Native device metadata (Meta CAPI extinfo) — read BEFORE the client is
|
|
426
|
+
// constructed because the app's short version becomes the event's
|
|
427
|
+
// `appVersion`, which the client takes at construction time. Best-effort
|
|
428
|
+
// and synchronous, so no fire-and-forget needed.
|
|
429
|
+
const nativeDeviceInfo = readNativeDeviceInfo({
|
|
430
|
+
platform,
|
|
431
|
+
ios: getIosNative(),
|
|
432
|
+
android: getAndroidNative(),
|
|
433
|
+
});
|
|
434
|
+
const { shortVersion, ...deviceInfo } = nativeDeviceInfo;
|
|
435
|
+
// Same contract as osVersion: the config wins, the platform fills the gap.
|
|
436
|
+
// Without this fallback nothing ever set it — every event, and every
|
|
437
|
+
// device_profiles row, recorded an empty app version.
|
|
438
|
+
const appVersion = rest.appVersion ?? shortVersion ?? undefined;
|
|
424
439
|
instance = new AdvenueClient({
|
|
425
440
|
...rest,
|
|
426
441
|
platform,
|
|
427
442
|
deviceId,
|
|
428
443
|
storage,
|
|
429
444
|
...(osVersion ? { osVersion } : {}),
|
|
445
|
+
...(appVersion ? { appVersion } : {}),
|
|
446
|
+
// Stamped by the SDK, never by the host app — see AdvenueConfig.sdkVersion.
|
|
447
|
+
sdkVersion: pkg.version,
|
|
430
448
|
});
|
|
431
449
|
// One-line ground truth for "which SDK build talks to which host" — the
|
|
432
450
|
// exact questions a silent integration always raises first.
|
|
@@ -436,13 +454,6 @@ export const Advenue = {
|
|
|
436
454
|
if (tcfCollectionEnabled) resolveDmaConsent();
|
|
437
455
|
// (a) resolve ad-id at init time
|
|
438
456
|
void resolveAdvertisingId().catch((err) => reportError('enrich.advertisingId', err));
|
|
439
|
-
// Collect native device info (Meta CAPI extinfo) once at init time; a
|
|
440
|
-
// best-effort, synchronous read, so no fire-and-forget needed.
|
|
441
|
-
const deviceInfo = collectDeviceInfo({
|
|
442
|
-
platform,
|
|
443
|
-
ios: getIosNative(),
|
|
444
|
-
android: getAndroidNative(),
|
|
445
|
-
});
|
|
446
457
|
if (Object.keys(deviceInfo).length > 0) instance.setDeviceInfo(deviceInfo);
|
|
447
458
|
if (!config.disableAutoSessions) {
|
|
448
459
|
const appState = getAppState();
|
|
@@ -593,6 +604,9 @@ export const Advenue = {
|
|
|
593
604
|
getDeviceId: () => active().getDeviceId(),
|
|
594
605
|
/** The Advenue ID — alias of getDeviceId (see AdvenueClient.getAdvenueId). */
|
|
595
606
|
getAdvenueId: () => active().getAdvenueId(),
|
|
607
|
+
/** Which app the server resolved this API key to; null until a batch lands
|
|
608
|
+
* (see AdvenueClient.getResolvedAppId). */
|
|
609
|
+
getResolvedAppId: () => active().getResolvedAppId(),
|
|
596
610
|
shutdown() {
|
|
597
611
|
appStateSub?.remove();
|
|
598
612
|
appStateSub = null;
|
|
@@ -606,7 +620,6 @@ export const Advenue = {
|
|
|
606
620
|
autoInstallEnabled = true;
|
|
607
621
|
manualConsentSet = false;
|
|
608
622
|
tcfCollectionEnabled = false;
|
|
609
|
-
configuredAppId = null;
|
|
610
623
|
configuredApiKey = null;
|
|
611
624
|
configuredEndpoint = DEFAULT_ENDPOINT;
|
|
612
625
|
aemSeenUrlHashes.clear();
|
|
@@ -735,18 +748,20 @@ export async function trackInstall(): Promise<void> {
|
|
|
735
748
|
// leaves attestation absent; the install is never blocked.
|
|
736
749
|
const trackOpts: TrackOptions = { type: 'install', ...opts };
|
|
737
750
|
if (
|
|
738
|
-
configuredAppId &&
|
|
739
751
|
android &&
|
|
740
752
|
typeof (android as { getIntegrityToken?: unknown }).getIntegrityToken === 'function'
|
|
741
753
|
) {
|
|
742
754
|
try {
|
|
743
|
-
// requestHash = sha256_hex("<
|
|
755
|
+
// requestHash = sha256_hex("<deviceId>") — must match the server's
|
|
744
756
|
// mapIntegrityVerdict computation byte-for-byte (see makeDecodeAndroid in
|
|
745
757
|
// apps/worker/src/attestation-decoders.ts):
|
|
746
|
-
// expectedRequestHash = sha256(
|
|
747
|
-
//
|
|
758
|
+
// expectedRequestHash = sha256(event.deviceId)
|
|
759
|
+
// No app id is hashed in, deliberately: the token is already decoded
|
|
760
|
+
// against the app's Play package name and must be PLAY_RECOGNIZED, which
|
|
761
|
+
// binds the app more tightly than an internal UUID could — and it keeps
|
|
762
|
+
// the API key as the SDK's only app identity.
|
|
748
763
|
const deviceId = client.getDeviceId();
|
|
749
|
-
const requestHash = sha256Hex(
|
|
764
|
+
const requestHash = sha256Hex(deviceId);
|
|
750
765
|
const token = await (
|
|
751
766
|
android as { getIntegrityToken(h: string): Promise<string> }
|
|
752
767
|
).getIntegrityToken(requestHash);
|
|
@@ -763,19 +778,32 @@ export async function trackInstall(): Promise<void> {
|
|
|
763
778
|
// G3.2n — App Attest attestation (iOS only). Best-effort: any error (no
|
|
764
779
|
// support on simulator, challenge fetch failure, attest failure) leaves
|
|
765
780
|
// attestation absent; the install is NEVER blocked.
|
|
766
|
-
if (
|
|
767
|
-
configuredAppId &&
|
|
768
|
-
configuredApiKey &&
|
|
769
|
-
ios &&
|
|
770
|
-
typeof (ios as { attestKey?: unknown }).attestKey === 'function'
|
|
771
|
-
) {
|
|
781
|
+
if (configuredApiKey && ios && typeof (ios as { attestKey?: unknown }).attestKey === 'function') {
|
|
772
782
|
try {
|
|
773
783
|
// Step 1: fetch a one-time server challenge bound to this device.
|
|
784
|
+
//
|
|
785
|
+
// Deadline-bounded, like every other SDK fetch (see sdk-core transport):
|
|
786
|
+
// the install event is only enqueued after this await, and attestation is
|
|
787
|
+
// attempted on every install — so an unreachable or hung host would
|
|
788
|
+
// otherwise delay the install by the platform socket timeout, shifting the
|
|
789
|
+
// recorded install time and the deferred deep link the user is waiting on.
|
|
790
|
+
// Shorter than the transport's 15s: this is enrichment, not the payload.
|
|
774
791
|
const deviceId = client.getDeviceId();
|
|
775
792
|
const challengeUrl = `${configuredEndpoint}/v1/attest/challenge?deviceId=${encodeURIComponent(deviceId)}`;
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
793
|
+
const ac = new AbortController();
|
|
794
|
+
const timer = setTimeout(
|
|
795
|
+
() => ac.abort(new Error('attest challenge timed out')),
|
|
796
|
+
ATTEST_CHALLENGE_TIMEOUT_MS,
|
|
797
|
+
);
|
|
798
|
+
let challengeRes: Response;
|
|
799
|
+
try {
|
|
800
|
+
challengeRes = await fetch(challengeUrl, {
|
|
801
|
+
headers: { 'x-api-key': configuredApiKey },
|
|
802
|
+
signal: ac.signal,
|
|
803
|
+
});
|
|
804
|
+
} finally {
|
|
805
|
+
clearTimeout(timer);
|
|
806
|
+
}
|
|
779
807
|
if (!challengeRes.ok) throw new Error(`challenge HTTP ${challengeRes.status}`);
|
|
780
808
|
const { challenge } = (await challengeRes.json()) as { challenge: string };
|
|
781
809
|
|
package/src/native-types.ts
CHANGED
|
@@ -35,6 +35,14 @@ export interface MetaInstallReferrerResult {
|
|
|
35
35
|
* real `DeviceInfo` shape.
|
|
36
36
|
*/
|
|
37
37
|
export interface AdvenueDeviceInfo {
|
|
38
|
+
/** Bundle id / package name — Meta CAPI extinfo slot 1. */
|
|
39
|
+
packageName?: string;
|
|
40
|
+
/** User-facing app version (CFBundleShortVersionString / versionName) —
|
|
41
|
+
* extinfo slot 2. Surfaced as the event's top-level `appVersion`, not as
|
|
42
|
+
* part of the deviceInfo blob. */
|
|
43
|
+
shortVersion?: string;
|
|
44
|
+
/** Build identifier (CFBundleVersion / versionCode) — extinfo slot 3. */
|
|
45
|
+
longVersion?: string;
|
|
38
46
|
model?: string;
|
|
39
47
|
locale?: string;
|
|
40
48
|
carrier?: string;
|
|
@@ -73,9 +81,11 @@ export interface AdvenueAndroidNativeModule {
|
|
|
73
81
|
/**
|
|
74
82
|
* Play Integrity Standard API token (G3.1n).
|
|
75
83
|
*
|
|
76
|
-
* `requestHash` MUST be sha256_hex("<
|
|
77
|
-
*
|
|
78
|
-
*
|
|
84
|
+
* `requestHash` MUST be sha256_hex("<deviceId>") — lowercase hex SHA-256 of the
|
|
85
|
+
* device id alone. The server's mapIntegrityVerdict re-derives the same string
|
|
86
|
+
* and compares it byte-for-byte. No app id is hashed in: the token is decoded
|
|
87
|
+
* against the app's Play package name and must be PLAY_RECOGNIZED, which binds
|
|
88
|
+
* the app already.
|
|
79
89
|
*
|
|
80
90
|
* Rejects when:
|
|
81
91
|
* - ECONFIG: `io.advenue.INTEGRITY_CLOUD_PROJECT_NUMBER` meta-data is absent.
|