@advenue/react-native 0.3.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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/index.d.ts +161 -0
- package/dist/index.js +563 -0
- package/package.json +68 -0
- package/plugin/index.js +40 -0
- package/plugin/links.js +79 -0
- package/plugin/package.json +3 -0
- package/src/deep-links.ts +85 -0
- package/src/index.ts +574 -0
- package/src/mmkv-storage.ts +21 -0
- package/src/native.ts +76 -0
- package/src/secure-store.ts +19 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
import { parseInstallReferrer, extractReferrerProperties, parseMetaInstallReferrer } from '@advenue/install-referrer';
|
|
2
|
+
import { DEFAULT_ENDPOINT, MemoryStorage, resolveDurableDeviceId, AdvenueClient } from '@advenue/sdk-core';
|
|
3
|
+
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// ../shared/src/match-token.ts
|
|
12
|
+
var MATCH_TOKEN_PREFIX = "advmatch:";
|
|
13
|
+
function parseMatchToken(raw) {
|
|
14
|
+
if (!raw || !raw.startsWith(MATCH_TOKEN_PREFIX)) return null;
|
|
15
|
+
const id = raw.slice(MATCH_TOKEN_PREFIX.length).trim();
|
|
16
|
+
return /^[A-Za-z0-9_-]{1,128}$/.test(id) ? id : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ../signing/src/index.ts
|
|
20
|
+
var K = new Uint32Array([
|
|
21
|
+
1116352408,
|
|
22
|
+
1899447441,
|
|
23
|
+
3049323471,
|
|
24
|
+
3921009573,
|
|
25
|
+
961987163,
|
|
26
|
+
1508970993,
|
|
27
|
+
2453635748,
|
|
28
|
+
2870763221,
|
|
29
|
+
3624381080,
|
|
30
|
+
310598401,
|
|
31
|
+
607225278,
|
|
32
|
+
1426881987,
|
|
33
|
+
1925078388,
|
|
34
|
+
2162078206,
|
|
35
|
+
2614888103,
|
|
36
|
+
3248222580,
|
|
37
|
+
3835390401,
|
|
38
|
+
4022224774,
|
|
39
|
+
264347078,
|
|
40
|
+
604807628,
|
|
41
|
+
770255983,
|
|
42
|
+
1249150122,
|
|
43
|
+
1555081692,
|
|
44
|
+
1996064986,
|
|
45
|
+
2554220882,
|
|
46
|
+
2821834349,
|
|
47
|
+
2952996808,
|
|
48
|
+
3210313671,
|
|
49
|
+
3336571891,
|
|
50
|
+
3584528711,
|
|
51
|
+
113926993,
|
|
52
|
+
338241895,
|
|
53
|
+
666307205,
|
|
54
|
+
773529912,
|
|
55
|
+
1294757372,
|
|
56
|
+
1396182291,
|
|
57
|
+
1695183700,
|
|
58
|
+
1986661051,
|
|
59
|
+
2177026350,
|
|
60
|
+
2456956037,
|
|
61
|
+
2730485921,
|
|
62
|
+
2820302411,
|
|
63
|
+
3259730800,
|
|
64
|
+
3345764771,
|
|
65
|
+
3516065817,
|
|
66
|
+
3600352804,
|
|
67
|
+
4094571909,
|
|
68
|
+
275423344,
|
|
69
|
+
430227734,
|
|
70
|
+
506948616,
|
|
71
|
+
659060556,
|
|
72
|
+
883997877,
|
|
73
|
+
958139571,
|
|
74
|
+
1322822218,
|
|
75
|
+
1537002063,
|
|
76
|
+
1747873779,
|
|
77
|
+
1955562222,
|
|
78
|
+
2024104815,
|
|
79
|
+
2227730452,
|
|
80
|
+
2361852424,
|
|
81
|
+
2428436474,
|
|
82
|
+
2756734187,
|
|
83
|
+
3204031479,
|
|
84
|
+
3329325298
|
|
85
|
+
]);
|
|
86
|
+
function rotr(x, n) {
|
|
87
|
+
return x >>> n | x << 32 - n;
|
|
88
|
+
}
|
|
89
|
+
function sha256Bytes(msg) {
|
|
90
|
+
const h = new Uint32Array([
|
|
91
|
+
1779033703,
|
|
92
|
+
3144134277,
|
|
93
|
+
1013904242,
|
|
94
|
+
2773480762,
|
|
95
|
+
1359893119,
|
|
96
|
+
2600822924,
|
|
97
|
+
528734635,
|
|
98
|
+
1541459225
|
|
99
|
+
]);
|
|
100
|
+
const l = msg.length;
|
|
101
|
+
const bitLen = l * 8;
|
|
102
|
+
const withOne = l + 1;
|
|
103
|
+
const padTo = (56 - withOne % 64 + 64) % 64;
|
|
104
|
+
const padded = new Uint8Array(withOne + padTo + 8);
|
|
105
|
+
padded.set(msg);
|
|
106
|
+
padded[l] = 128;
|
|
107
|
+
const dv = new DataView(padded.buffer);
|
|
108
|
+
dv.setUint32(padded.length - 8, Math.floor(bitLen / 4294967296));
|
|
109
|
+
dv.setUint32(padded.length - 4, bitLen >>> 0);
|
|
110
|
+
const w = new Uint32Array(64);
|
|
111
|
+
for (let i = 0; i < padded.length; i += 64) {
|
|
112
|
+
for (let t = 0; t < 16; t++) w[t] = dv.getUint32(i + t * 4);
|
|
113
|
+
for (let t = 16; t < 64; t++) {
|
|
114
|
+
const w15 = w[t - 15];
|
|
115
|
+
const w2 = w[t - 2];
|
|
116
|
+
const s0 = rotr(w15, 7) ^ rotr(w15, 18) ^ w15 >>> 3;
|
|
117
|
+
const s1 = rotr(w2, 17) ^ rotr(w2, 19) ^ w2 >>> 10;
|
|
118
|
+
w[t] = w[t - 16] + s0 + w[t - 7] + s1 >>> 0;
|
|
119
|
+
}
|
|
120
|
+
let a = h[0];
|
|
121
|
+
let b = h[1];
|
|
122
|
+
let c = h[2];
|
|
123
|
+
let d = h[3];
|
|
124
|
+
let e = h[4];
|
|
125
|
+
let f = h[5];
|
|
126
|
+
let g = h[6];
|
|
127
|
+
let hh = h[7];
|
|
128
|
+
for (let t = 0; t < 64; t++) {
|
|
129
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
130
|
+
const ch = e & f ^ ~e & g;
|
|
131
|
+
const temp1 = hh + S1 + ch + K[t] + w[t] >>> 0;
|
|
132
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
133
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
134
|
+
const temp2 = S0 + maj >>> 0;
|
|
135
|
+
hh = g;
|
|
136
|
+
g = f;
|
|
137
|
+
f = e;
|
|
138
|
+
e = d + temp1 >>> 0;
|
|
139
|
+
d = c;
|
|
140
|
+
c = b;
|
|
141
|
+
b = a;
|
|
142
|
+
a = temp1 + temp2 >>> 0;
|
|
143
|
+
}
|
|
144
|
+
h[0] = h[0] + a >>> 0;
|
|
145
|
+
h[1] = h[1] + b >>> 0;
|
|
146
|
+
h[2] = h[2] + c >>> 0;
|
|
147
|
+
h[3] = h[3] + d >>> 0;
|
|
148
|
+
h[4] = h[4] + e >>> 0;
|
|
149
|
+
h[5] = h[5] + f >>> 0;
|
|
150
|
+
h[6] = h[6] + g >>> 0;
|
|
151
|
+
h[7] = h[7] + hh >>> 0;
|
|
152
|
+
}
|
|
153
|
+
const out = new Uint8Array(32);
|
|
154
|
+
const odv = new DataView(out.buffer);
|
|
155
|
+
for (let i = 0; i < 8; i++) odv.setUint32(i * 4, h[i]);
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
function toHex(bytes) {
|
|
159
|
+
let s = "";
|
|
160
|
+
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, "0");
|
|
161
|
+
return s;
|
|
162
|
+
}
|
|
163
|
+
var enc = new TextEncoder();
|
|
164
|
+
function sha256Hex(message) {
|
|
165
|
+
return toHex(sha256Bytes(enc.encode(message)));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/deep-links.ts
|
|
169
|
+
function parseDirectLink(url) {
|
|
170
|
+
let deepLinkValue = "";
|
|
171
|
+
let params;
|
|
172
|
+
try {
|
|
173
|
+
const parsed = new URL(url);
|
|
174
|
+
const path = parsed.pathname.replace(/^\/+/, "");
|
|
175
|
+
const isHttp = parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
176
|
+
deepLinkValue = isHttp ? path : [parsed.host, path].filter(Boolean).join("/");
|
|
177
|
+
for (const [k, v] of parsed.searchParams.entries()) {
|
|
178
|
+
params ??= {};
|
|
179
|
+
params[k] = v;
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
deepLinkValue = url;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
deepLinkValue,
|
|
186
|
+
...params ? { params } : {},
|
|
187
|
+
url,
|
|
188
|
+
isDeferred: false,
|
|
189
|
+
isFirstLaunch: false
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function setupDeepLinks(setup) {
|
|
193
|
+
const listener = setup.addUrlListener((url) => setup.onDeepLink(parseDirectLink(url)));
|
|
194
|
+
const ready = (async () => {
|
|
195
|
+
const initial = await setup.getInitialURL();
|
|
196
|
+
if (initial) {
|
|
197
|
+
setup.onDeepLink(parseDirectLink(initial));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const deferred = await setup.resolveDeferred();
|
|
201
|
+
if (deferred?.deepLinkValue) {
|
|
202
|
+
setup.onDeepLink(deferred);
|
|
203
|
+
}
|
|
204
|
+
})();
|
|
205
|
+
return { remove: () => listener.remove(), ready };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/mmkv-storage.ts
|
|
209
|
+
function createMMKVStorage(mmkv) {
|
|
210
|
+
return {
|
|
211
|
+
getItem: (key) => mmkv.getString(key) ?? null,
|
|
212
|
+
setItem: (key, value) => mmkv.set(key, value),
|
|
213
|
+
removeItem: (key) => mmkv.delete(key)
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/native.ts
|
|
218
|
+
function getIosNative() {
|
|
219
|
+
try {
|
|
220
|
+
return __require("@advenue/sdk-ios").AdvenueIos;
|
|
221
|
+
} catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function getAndroidNative() {
|
|
226
|
+
try {
|
|
227
|
+
return __require("@advenue/sdk-android").AdvenueAndroid;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function getAppState() {
|
|
233
|
+
try {
|
|
234
|
+
return __require("react-native").AppState;
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function getLinking() {
|
|
240
|
+
try {
|
|
241
|
+
return __require("react-native").Linking;
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/secure-store.ts
|
|
248
|
+
function createSecureStore(secureStore2) {
|
|
249
|
+
return {
|
|
250
|
+
getItemAsync: async (key) => await secureStore2.getItemAsync(key) ?? null,
|
|
251
|
+
setItemAsync: (key, value) => secureStore2.setItemAsync(key, value)
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/index.ts
|
|
256
|
+
async function resolveAdvertisingId() {
|
|
257
|
+
const client = active();
|
|
258
|
+
const consent = client.getConsentData();
|
|
259
|
+
if (client.getRequireConsent() && consent == null) {
|
|
260
|
+
client.setAdvertisingId({});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (consent?.isUserSubjectToGDPR === true && consent.hasConsentForDataUsage !== true) {
|
|
264
|
+
client.setAdvertisingId({});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const ios = getIosNative();
|
|
268
|
+
if (ios) {
|
|
269
|
+
const idfa = ios.getTrackingAuthorizationStatus() === "authorized" ? await ios.getAdvertisingId() : null;
|
|
270
|
+
const vendorId = typeof ios.getIdentifierForVendor === "function" ? ios.getIdentifierForVendor() : null;
|
|
271
|
+
client.setAdvertisingId({ ...idfa ? { idfa } : {}, ...vendorId ? { vendorId } : {} });
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const android = getAndroidNative();
|
|
275
|
+
if (android && typeof android.getAdvertisingId === "function") {
|
|
276
|
+
const r = await android.getAdvertisingId();
|
|
277
|
+
const vendorId = typeof android.getAppSetId === "function" ? await android.getAppSetId() : null;
|
|
278
|
+
const adFields = r ? r.limitAdTracking ? { limitAdTracking: true } : { gaid: r.id } : {};
|
|
279
|
+
client.setAdvertisingId({ ...adFields, ...vendorId ? { vendorId } : {} });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
client.setAdvertisingId({});
|
|
283
|
+
}
|
|
284
|
+
function collectDeviceInfo(natives) {
|
|
285
|
+
try {
|
|
286
|
+
if (natives.platform === "android" && natives.android?.getDeviceInfo) {
|
|
287
|
+
return natives.android.getDeviceInfo();
|
|
288
|
+
}
|
|
289
|
+
if (natives.platform === "ios" && natives.ios?.getDeviceInfo) {
|
|
290
|
+
return natives.ios.getDeviceInfo();
|
|
291
|
+
}
|
|
292
|
+
} catch {
|
|
293
|
+
}
|
|
294
|
+
return {};
|
|
295
|
+
}
|
|
296
|
+
async function resolveClipboardMatch() {
|
|
297
|
+
const client = active();
|
|
298
|
+
if (client.getRequireConsent() && !client.getTrackingConsent()) return null;
|
|
299
|
+
const ios = getIosNative();
|
|
300
|
+
if (!ios || typeof ios.getPasteboardMatchToken !== "function") {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
const matchId = parseMatchToken(ios.getPasteboardMatchToken());
|
|
304
|
+
if (!matchId) return null;
|
|
305
|
+
return client.exchangeClipboardMatch(matchId);
|
|
306
|
+
}
|
|
307
|
+
function applyAppState(client, status) {
|
|
308
|
+
if (status === "active") {
|
|
309
|
+
client.notifyAppActive();
|
|
310
|
+
} else if (status === "background") {
|
|
311
|
+
client.notifyAppBackground();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
var instance = null;
|
|
315
|
+
var appStateSub = null;
|
|
316
|
+
var deepLinkSub = null;
|
|
317
|
+
var secureStore = null;
|
|
318
|
+
var installFlagStore = null;
|
|
319
|
+
var INSTALL_SENT_KEY = "advenue.install_sent";
|
|
320
|
+
var installInFlight = false;
|
|
321
|
+
var configuredAppId = null;
|
|
322
|
+
var configuredApiKey = null;
|
|
323
|
+
var configuredEndpoint = DEFAULT_ENDPOINT;
|
|
324
|
+
function active() {
|
|
325
|
+
if (!instance) {
|
|
326
|
+
throw new Error("Advenue.initialize() must be called before tracking events.");
|
|
327
|
+
}
|
|
328
|
+
return instance;
|
|
329
|
+
}
|
|
330
|
+
function reportError(context, cause) {
|
|
331
|
+
instance?.report(context, cause);
|
|
332
|
+
}
|
|
333
|
+
var Advenue = {
|
|
334
|
+
async initialize(config) {
|
|
335
|
+
const { mmkv, secureStore: secureMod, onDeepLink, appId, ...rest } = config;
|
|
336
|
+
configuredAppId = appId ?? null;
|
|
337
|
+
configuredApiKey = config.apiKey;
|
|
338
|
+
configuredEndpoint = config.endpoint ?? DEFAULT_ENDPOINT;
|
|
339
|
+
appStateSub?.remove();
|
|
340
|
+
appStateSub = null;
|
|
341
|
+
deepLinkSub?.remove();
|
|
342
|
+
deepLinkSub = null;
|
|
343
|
+
instance?.shutdown();
|
|
344
|
+
const storage = mmkv ? createMMKVStorage(mmkv) : new MemoryStorage();
|
|
345
|
+
installFlagStore = storage;
|
|
346
|
+
secureStore = secureMod ? createSecureStore(secureMod) : null;
|
|
347
|
+
const deviceId = rest.deviceId ?? await resolveDurableDeviceId(storage, secureStore ?? void 0);
|
|
348
|
+
instance = new AdvenueClient({ ...rest, deviceId, storage });
|
|
349
|
+
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
350
|
+
const deviceInfo = collectDeviceInfo({
|
|
351
|
+
platform: rest.platform,
|
|
352
|
+
ios: getIosNative(),
|
|
353
|
+
android: getAndroidNative()
|
|
354
|
+
});
|
|
355
|
+
if (Object.keys(deviceInfo).length > 0) instance.setDeviceInfo(deviceInfo);
|
|
356
|
+
if (!config.disableAutoSessions) {
|
|
357
|
+
const appState = getAppState();
|
|
358
|
+
if (appState) {
|
|
359
|
+
appStateSub?.remove();
|
|
360
|
+
let seenBackground = false;
|
|
361
|
+
const handler = (status) => {
|
|
362
|
+
if (!instance) return;
|
|
363
|
+
if (status === "background") {
|
|
364
|
+
seenBackground = true;
|
|
365
|
+
applyAppState(instance, status);
|
|
366
|
+
} else if (status === "active") {
|
|
367
|
+
if (!seenBackground) return;
|
|
368
|
+
seenBackground = false;
|
|
369
|
+
applyAppState(instance, status);
|
|
370
|
+
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
appStateSub = appState.addEventListener("change", handler);
|
|
374
|
+
instance.notifyAppActive();
|
|
375
|
+
} else {
|
|
376
|
+
instance.notifyAppActive();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (onDeepLink) {
|
|
380
|
+
const linking = getLinking();
|
|
381
|
+
if (linking) {
|
|
382
|
+
const client = instance;
|
|
383
|
+
deepLinkSub = setupDeepLinks({
|
|
384
|
+
getInitialURL: () => linking.getInitialURL(),
|
|
385
|
+
addUrlListener: (handler) => linking.addEventListener("url", (e) => handler(e.url)),
|
|
386
|
+
// Deferred waterfall: clipboard (deterministic) first, then the
|
|
387
|
+
// server-side conversion poll (IDFA / probabilistic).
|
|
388
|
+
resolveDeferred: async () => await resolveClipboardMatch() ?? client.resolveDeferredDeepLink(),
|
|
389
|
+
onDeepLink
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return instance;
|
|
394
|
+
},
|
|
395
|
+
track: (name, properties, opts) => active().track(name, properties, opts),
|
|
396
|
+
/**
|
|
397
|
+
* Shows the iOS ATT prompt (resolves 'authorized' immediately on Android/web,
|
|
398
|
+
* where ATT doesn't exist) and forwards the result to the consent gate.
|
|
399
|
+
*/
|
|
400
|
+
async requestTrackingAuthorization() {
|
|
401
|
+
const ios = getIosNative();
|
|
402
|
+
const status = ios ? await ios.requestTrackingAuthorization() : "authorized";
|
|
403
|
+
active().setTrackingConsent(status === "authorized");
|
|
404
|
+
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
405
|
+
return status;
|
|
406
|
+
},
|
|
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
|
+
}
|
|
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
|
+
}
|
|
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);
|
|
524
|
+
try {
|
|
525
|
+
installFlagStore?.setItem(INSTALL_SENT_KEY, "1");
|
|
526
|
+
} catch (err) {
|
|
527
|
+
reportError("install.flagPersist", err);
|
|
528
|
+
}
|
|
529
|
+
if (secureStore) {
|
|
530
|
+
try {
|
|
531
|
+
await secureStore.setItemAsync(INSTALL_SENT_KEY, "1");
|
|
532
|
+
} catch (err) {
|
|
533
|
+
reportError("install.flagPersistSecure", err);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
},
|
|
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
|
+
setConsentData(consent) {
|
|
542
|
+
active().setConsentData(consent);
|
|
543
|
+
void resolveAdvertisingId().catch((err) => reportError("enrich.advertisingId", err));
|
|
544
|
+
},
|
|
545
|
+
getConsentData: () => active().getConsentData(),
|
|
546
|
+
flush: () => active().flush(),
|
|
547
|
+
getDeviceId: () => active().getDeviceId(),
|
|
548
|
+
shutdown() {
|
|
549
|
+
appStateSub?.remove();
|
|
550
|
+
appStateSub = null;
|
|
551
|
+
deepLinkSub?.remove();
|
|
552
|
+
deepLinkSub = null;
|
|
553
|
+
instance?.shutdown();
|
|
554
|
+
instance = null;
|
|
555
|
+
secureStore = null;
|
|
556
|
+
installInFlight = false;
|
|
557
|
+
configuredAppId = null;
|
|
558
|
+
configuredApiKey = null;
|
|
559
|
+
configuredEndpoint = DEFAULT_ENDPOINT;
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
export { Advenue, applyAppState, collectDeviceInfo, createMMKVStorage, createSecureStore, getAndroidNative, getIosNative, parseDirectLink, resolveAdvertisingId, resolveClipboardMatch };
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@advenue/react-native",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./plugin": "./plugin/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"plugin",
|
|
19
|
+
"!**/*.test.ts"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@advenue/install-referrer": "0.1.0",
|
|
26
|
+
"@advenue/sdk-core": "0.2.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@advenue/sdk-android": "*",
|
|
30
|
+
"@advenue/sdk-ios": "*",
|
|
31
|
+
"@expo/config-plugins": ">=8",
|
|
32
|
+
"expo-secure-store": ">=12",
|
|
33
|
+
"react": ">=18.2",
|
|
34
|
+
"react-native": ">=0.74",
|
|
35
|
+
"react-native-mmkv": ">=3"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@expo/config-plugins": {
|
|
39
|
+
"optional": true
|
|
40
|
+
},
|
|
41
|
+
"@advenue/sdk-android": {
|
|
42
|
+
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"@advenue/sdk-ios": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"expo-secure-store": {
|
|
48
|
+
"optional": true
|
|
49
|
+
},
|
|
50
|
+
"react-native-mmkv": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"tsup": "^8.5.1",
|
|
56
|
+
"typescript": "^5.7.2",
|
|
57
|
+
"vitest": "^2.1.8",
|
|
58
|
+
"@advenue/sdk-android": "0.2.0",
|
|
59
|
+
"@advenue/sdk-ios": "0.2.0",
|
|
60
|
+
"@advenue/shared": "0.0.0",
|
|
61
|
+
"@advenue/signing": "0.0.0"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsup",
|
|
65
|
+
"typecheck": "tsc --noEmit",
|
|
66
|
+
"test": "vitest run"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/plugin/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
// Expo config plugin: wires the native association config that makes Advenue
|
|
3
|
+
// deep links resolve on device. iOS gets the Associated Domains entitlement
|
|
4
|
+
// (Universal Links); Android gets an autoVerify intent filter (App Links). The
|
|
5
|
+
// backend already serves the matching apple-app-site-association / assetlinks.json.
|
|
6
|
+
//
|
|
7
|
+
// Usage (app.json / app.config.js):
|
|
8
|
+
// ["@advenue/react-native/plugin", { "domains": ["go.advenue.io"] }]
|
|
9
|
+
//
|
|
10
|
+
// @expo/config-plugins is resolved from the consuming app's Expo install.
|
|
11
|
+
const { withEntitlementsPlist, withAndroidManifest } = require('@expo/config-plugins');
|
|
12
|
+
const { addAssociatedDomains, addAppLinksIntentFilter } = require('./links');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {any} config
|
|
16
|
+
* @param {{ domains?: string[] } | undefined} props
|
|
17
|
+
*/
|
|
18
|
+
function withAdvenueLinks(config, props) {
|
|
19
|
+
const domains = props?.domains ?? [];
|
|
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
|
+
}
|
|
25
|
+
|
|
26
|
+
let result = withEntitlementsPlist(config, (cfg) => {
|
|
27
|
+
cfg.modResults = addAssociatedDomains(cfg.modResults, domains);
|
|
28
|
+
return cfg;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
result = withAndroidManifest(result, (cfg) => {
|
|
32
|
+
cfg.modResults = addAppLinksIntentFilter(cfg.modResults, domains);
|
|
33
|
+
return cfg;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = withAdvenueLinks;
|
|
40
|
+
module.exports.default = withAdvenueLinks;
|