@advenue/react-native 0.9.0 → 1.0.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/README.md +8 -7
- package/android/src/main/java/expo/modules/advenue/AdvenueAndroidModule.kt +118 -368
- package/android/src/main/kotlin/io/advenue/Advenue.kt +549 -0
- package/android/src/main/kotlin/io/advenue/AdvenueConfig.kt +114 -0
- package/android/src/main/kotlin/io/advenue/core/Backoff.kt +28 -0
- package/android/src/main/kotlin/io/advenue/core/ClientEvent.kt +136 -0
- package/android/src/main/kotlin/io/advenue/core/CommandPipe.kt +186 -0
- package/android/src/main/kotlin/io/advenue/core/Consent.kt +52 -0
- package/android/src/main/kotlin/io/advenue/core/Contracts.kt +80 -0
- package/android/src/main/kotlin/io/advenue/core/Conversion.kt +69 -0
- package/android/src/main/kotlin/io/advenue/core/Engine.kt +414 -0
- package/android/src/main/kotlin/io/advenue/core/EventQueue.kt +89 -0
- package/android/src/main/kotlin/io/advenue/core/HmacSigner.kt +31 -0
- package/android/src/main/kotlin/io/advenue/core/InstallReferrer.kt +146 -0
- package/android/src/main/kotlin/io/advenue/core/Json.kt +272 -0
- package/android/src/main/kotlin/io/advenue/core/Limits.kt +47 -0
- package/android/src/main/kotlin/io/advenue/core/MetaReferrer.kt +80 -0
- package/android/src/main/kotlin/io/advenue/core/SessionTracker.kt +189 -0
- package/android/src/main/kotlin/io/advenue/core/SystemServices.kt +58 -0
- package/android/src/main/kotlin/io/advenue/core/Tcf.kt +48 -0
- package/android/src/main/kotlin/io/advenue/core/Time.kt +56 -0
- package/android/src/main/kotlin/io/advenue/platform/Collectors.kt +149 -0
- package/android/src/main/kotlin/io/advenue/platform/CompositeStore.kt +33 -0
- package/android/src/main/kotlin/io/advenue/platform/ConversionFetcher.kt +68 -0
- package/android/src/main/kotlin/io/advenue/platform/ForegroundTracker.kt +90 -0
- package/android/src/main/kotlin/io/advenue/platform/HttpUrlTransport.kt +101 -0
- package/android/src/main/kotlin/io/advenue/platform/Identity.kt +79 -0
- package/android/src/main/kotlin/io/advenue/platform/InstallEnrichment.kt +197 -0
- package/android/src/main/kotlin/io/advenue/platform/InstallScopedStore.kt +91 -0
- package/android/src/main/kotlin/io/advenue/platform/LifecycleBridge.kt +72 -0
- package/android/src/main/kotlin/io/advenue/platform/PreferencesStore.kt +32 -0
- package/android/src/main/kotlin/io/advenue/plugin/Contracts.kt +67 -0
- package/android/src/main/kotlin/io/advenue/plugin/FirebaseAppInstanceIdSource.kt +73 -0
- package/android/src/main/kotlin/io/advenue/plugin/PlayAdvertisingIdSource.kt +47 -0
- package/android/src/main/kotlin/io/advenue/plugin/PlayInstallReferrerSource.kt +85 -0
- package/android/src/main/kotlin/io/advenue/plugin/PlayIntegritySource.kt +86 -0
- package/android/src/main/kotlin/io/advenue/plugin/PluginRegistry.kt +61 -0
- package/dist/index.cjs +183 -675
- package/dist/index.d.cts +199 -336
- package/dist/index.d.ts +199 -336
- package/dist/index.js +182 -681
- package/ios/AdvenueIosModule.swift +144 -446
- package/ios/vendor/Advenue/Advenue.swift +596 -0
- package/ios/vendor/Advenue/AdvenueConfig.swift +77 -0
- package/ios/vendor/AdvenueCore/AdvenueValue.swift +103 -0
- package/ios/vendor/AdvenueCore/Attestation.swift +52 -0
- package/ios/vendor/AdvenueCore/Backoff.swift +28 -0
- package/ios/vendor/AdvenueCore/ClientEvent.swift +154 -0
- package/ios/vendor/AdvenueCore/Consent.swift +40 -0
- package/ios/vendor/AdvenueCore/Contracts.swift +59 -0
- package/ios/vendor/AdvenueCore/Conversion.swift +74 -0
- package/ios/vendor/AdvenueCore/ConversionValue.swift +217 -0
- package/ios/vendor/AdvenueCore/Engine.swift +587 -0
- package/ios/vendor/AdvenueCore/EventQueue.swift +89 -0
- package/ios/vendor/AdvenueCore/Limits.swift +41 -0
- package/ios/vendor/AdvenueCore/PIIScrub.swift +102 -0
- package/ios/vendor/AdvenueCore/SessionTracker.swift +139 -0
- package/ios/vendor/AdvenueCore/SkanConfig.swift +76 -0
- package/ios/vendor/AdvenueCore/SkanReporter.swift +25 -0
- package/ios/vendor/AdvenueCore/SkanState.swift +258 -0
- package/ios/vendor/AdvenueCore/Tcf.swift +48 -0
- package/ios/vendor/AdvenueCore/Transport.swift +14 -0
- package/ios/vendor/AdvenueFirebase/FirebaseAppInstanceId.swift +33 -0
- package/ios/vendor/AdvenuePlatform/AdvertisingIdentity.swift +87 -0
- package/ios/vendor/AdvenuePlatform/ChallengeFetcher.swift +42 -0
- package/ios/vendor/AdvenuePlatform/ConversionFetcher.swift +63 -0
- package/ios/vendor/AdvenuePlatform/CryptoKitSigner.swift +19 -0
- package/ios/vendor/AdvenuePlatform/DeviceCheckAttestation.swift +91 -0
- package/ios/vendor/AdvenuePlatform/DeviceInfo.swift +65 -0
- package/ios/vendor/AdvenuePlatform/ForegroundTracker.swift +55 -0
- package/ios/vendor/AdvenuePlatform/HttpTransport.swift +96 -0
- package/ios/vendor/AdvenuePlatform/Identity.swift +52 -0
- package/ios/vendor/AdvenuePlatform/InstallEnrichment.swift +143 -0
- package/ios/vendor/AdvenuePlatform/KeychainStore.swift +95 -0
- package/ios/vendor/AdvenuePlatform/SearchAdsToken.swift +76 -0
- package/ios/vendor/AdvenuePlatform/SkanConfigFetcher.swift +70 -0
- package/ios/vendor/AdvenuePlatform/StoreKitSkanReporter.swift +140 -0
- package/ios/vendor/AdvenuePlatform/SystemServices.swift +55 -0
- package/ios/vendor/AdvenuePlatform/TcfReader.swift +18 -0
- package/ios/vendor/AdvenuePlatform/UserDefaultsStore.swift +26 -0
- package/package.json +9 -11
- package/scripts/check-dist.mjs +17 -0
- package/scripts/check-vendored-swift.mjs +148 -0
- package/scripts/vendor-natives.mjs +149 -0
- package/scripts/vendor-natives.test.mjs +112 -0
- package/src/deep-links.ts +19 -1
- package/src/index.ts +266 -830
- package/src/native-types.ts +74 -181
- package/src/native.ts +0 -23
- package/src/types.ts +81 -0
- package/src/aem.ts +0 -33
- package/src/mmkv-storage.ts +0 -21
- package/src/native-storage.ts +0 -63
- package/src/secure-store.ts +0 -26
package/scripts/check-dist.mjs
CHANGED
|
@@ -24,6 +24,23 @@ for (const mod of ['expo-modules-core', 'react-native']) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// The published package must be self-contained. It imports no workspace code
|
|
28
|
+
// at all now, so the check is the general one rather than a list of the private
|
|
29
|
+
// packages that used to need inlining: ANY surviving @advenue/* import breaks
|
|
30
|
+
// `npm install @advenue/react-native` outside the monorepo.
|
|
31
|
+
for (const file of ['../dist/index.js', '../dist/index.cjs']) {
|
|
32
|
+
const bundle = readFileSync(fileURLToPath(new URL(file, import.meta.url)), 'utf8');
|
|
33
|
+
// Covers every specifier position esbuild can emit: `from "x"`, a bare
|
|
34
|
+
// side-effect `import "x"`, and `require("x")`. A pattern that only knew
|
|
35
|
+
// `from` would pass a bundle that still pulls in a workspace package.
|
|
36
|
+
const found = bundle.match(/(?:from\s*["']|\bimport\s*["']|require\(\s*["'])(@advenue\/[\w-]+)/);
|
|
37
|
+
if (found) {
|
|
38
|
+
problems.push(
|
|
39
|
+
`${file} imports ${found[1]} — the published package must import no workspace code`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
if (problems.length > 0) {
|
|
28
45
|
console.error(`check-dist failed:\n- ${problems.join('\n- ')}`);
|
|
29
46
|
process.exit(1);
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Compiles the vendored Swift tree the way CocoaPods will: as ONE module.
|
|
2
|
+
//
|
|
3
|
+
// This is the check whose absence let a non-building package onto main. The
|
|
4
|
+
// Swift SDK is three SwiftPM modules that import each other; the podspec globs
|
|
5
|
+
// all of their sources into a single pod target, where `import AdvenueCore`
|
|
6
|
+
// names a module that does not exist. Nothing caught it, because the Expo
|
|
7
|
+
// module compiles only inside a host app and no host app is built here.
|
|
8
|
+
//
|
|
9
|
+
// A whole host app is not needed to catch it. `swiftc -emit-module` over the
|
|
10
|
+
// vendored sources reproduces exactly the condition that broke — one module,
|
|
11
|
+
// same deployment target — and needs neither Xcode projects nor
|
|
12
|
+
// ExpoModulesCore. What it does NOT cover is `AdvenueIosModule.swift` itself,
|
|
13
|
+
// which does need ExpoModulesCore; that gap is real and stays.
|
|
14
|
+
import { execFileSync } from 'node:child_process';
|
|
15
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
16
|
+
import { tmpdir } from 'node:os';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
|
+
|
|
22
|
+
/** iOS deployment target — must match `s.platforms` in the podspec. */
|
|
23
|
+
const DEPLOYMENT_TARGET = '15.1';
|
|
24
|
+
|
|
25
|
+
function swiftFiles(dir) {
|
|
26
|
+
if (!existsSync(dir)) return [];
|
|
27
|
+
let out = [];
|
|
28
|
+
for (const entry of readdirSync(dir)) {
|
|
29
|
+
const path = join(dir, entry);
|
|
30
|
+
if (statSync(path).isDirectory()) out = out.concat(swiftFiles(path));
|
|
31
|
+
else if (entry.endsWith('.swift')) out.push(path);
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The SDK's own SwiftPM module names, which do not survive flattening. */
|
|
37
|
+
const SDK_MODULES = ['Advenue', 'AdvenueCore', 'AdvenuePlatform', 'AdvenueFirebase'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compiled by name, not by directory, unlike `AdvenueIosModule.swift`: that
|
|
41
|
+
* file is excluded from this compile step simply by living outside
|
|
42
|
+
* `vendorDir`. The Flutter plugin has no such sibling location — Flutter's
|
|
43
|
+
* fixed Kotlin/Swift layout puts it INSIDE the vendored tree, because both
|
|
44
|
+
* packaging systems compile that directory as one module — so it has to be
|
|
45
|
+
* excluded here explicitly. It `import Flutter`, a framework only a host-app
|
|
46
|
+
* build provides, and failing to compile it here is not a defect: the same
|
|
47
|
+
* gap that already excuses `AdvenueIosModule.swift` (which needs
|
|
48
|
+
* ExpoModulesCore) applies to it too. The text import check below still
|
|
49
|
+
* covers it — that is what would catch a genuine `import Advenue`.
|
|
50
|
+
*/
|
|
51
|
+
const EXCLUDED_FROM_COMPILE = ['AdvenueFlutterPlugin.swift'];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* No file in `ios/` may import one of the SDK's own modules.
|
|
55
|
+
*
|
|
56
|
+
* The compile check below covers `ios/vendor`, but not `AdvenueIosModule.swift`
|
|
57
|
+
* — that one needs ExpoModulesCore and cannot be compiled here. It lands in the
|
|
58
|
+
* same pod target all the same, so the same import kills the build there. A
|
|
59
|
+
* text check reaches where the compiler cannot.
|
|
60
|
+
*/
|
|
61
|
+
export function checkNoSdkImports({ root = packageRoot, vendorDir = 'ios/vendor' } = {}) {
|
|
62
|
+
const pattern = new RegExp(`^\\s*import\\s+(${SDK_MODULES.join('|')})\\s*$`, 'm');
|
|
63
|
+
const offenders = [];
|
|
64
|
+
for (const file of swiftFiles(join(root, vendorDir, '..'))) {
|
|
65
|
+
const match = pattern.exec(readFileSync(file, 'utf8'));
|
|
66
|
+
if (match) offenders.push(`${file.replace(`${root}/`, '')} imports ${match[1]}`);
|
|
67
|
+
}
|
|
68
|
+
return offenders;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function checkVendoredSwift({ root = packageRoot, vendorDir = 'ios/vendor' } = {}) {
|
|
72
|
+
const offenders = checkNoSdkImports({ root, vendorDir });
|
|
73
|
+
if (offenders.length > 0) {
|
|
74
|
+
return {
|
|
75
|
+
ok: false,
|
|
76
|
+
error: `these files import a module the pod does not have:\n${offenders.join('\n')}`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (process.platform !== 'darwin') {
|
|
81
|
+
return { ok: true, skipped: 'not macOS — the Swift toolchain is unavailable here' };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const sources = swiftFiles(join(root, vendorDir)).filter(
|
|
85
|
+
(file) => !EXCLUDED_FROM_COMPILE.some((name) => file.endsWith(`/${name}`)),
|
|
86
|
+
);
|
|
87
|
+
if (sources.length === 0) {
|
|
88
|
+
return { ok: false, error: 'no vendored Swift sources — run the vendor step first' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let sdk;
|
|
92
|
+
try {
|
|
93
|
+
sdk = execFileSync('xcrun', ['--sdk', 'iphonesimulator', '--show-sdk-path'], {
|
|
94
|
+
encoding: 'utf8',
|
|
95
|
+
}).trim();
|
|
96
|
+
} catch {
|
|
97
|
+
return { ok: true, skipped: 'no iOS SDK installed — Xcode command line tools are absent' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// A real directory, not /dev/null: -emit-module writes .swiftdoc,
|
|
101
|
+
// .swiftsourceinfo and .abi.json alongside the module, and those siblings
|
|
102
|
+
// cannot be opened next to a device file.
|
|
103
|
+
const out = mkdtempSync(join(tmpdir(), 'advenue-swiftcheck-'));
|
|
104
|
+
try {
|
|
105
|
+
execFileSync(
|
|
106
|
+
'xcrun',
|
|
107
|
+
[
|
|
108
|
+
'swiftc',
|
|
109
|
+
'-sdk',
|
|
110
|
+
sdk,
|
|
111
|
+
'-target',
|
|
112
|
+
`arm64-apple-ios${DEPLOYMENT_TARGET}-simulator`,
|
|
113
|
+
'-module-name',
|
|
114
|
+
'AdvenueIos',
|
|
115
|
+
'-emit-module',
|
|
116
|
+
'-o',
|
|
117
|
+
join(out, 'AdvenueIos.swiftmodule'),
|
|
118
|
+
...sources,
|
|
119
|
+
],
|
|
120
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
121
|
+
);
|
|
122
|
+
} catch (failure) {
|
|
123
|
+
const output = `${failure.stderr ?? ''}${failure.stdout ?? ''}`;
|
|
124
|
+
const errors = output
|
|
125
|
+
.split('\n')
|
|
126
|
+
.filter((line) => line.includes('error:'))
|
|
127
|
+
.slice(0, 10)
|
|
128
|
+
.join('\n');
|
|
129
|
+
return { ok: false, count: sources.length, error: errors || output.slice(0, 2000) };
|
|
130
|
+
} finally {
|
|
131
|
+
rmSync(out, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { ok: true, count: sources.length };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (process.argv[1]?.endsWith('check-vendored-swift.mjs')) {
|
|
138
|
+
const result = checkVendoredSwift();
|
|
139
|
+
if (!result.ok) {
|
|
140
|
+
console.error(`vendored Swift does not compile as one module:\n${result.error}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
console.log(
|
|
144
|
+
result.skipped
|
|
145
|
+
? `check-vendored-swift skipped: ${result.skipped}`
|
|
146
|
+
: `check-vendored-swift ok: ${result.count} sources compile as one module`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Copies the native SDK sources into this package so the npm tarball carries
|
|
2
|
+
// them. The monorepo copy is the source of truth; the copies here are build
|
|
3
|
+
// output and are gitignored, because a checked-in duplicate drifts and nobody
|
|
4
|
+
// notices until a fix lands in one of them.
|
|
5
|
+
//
|
|
6
|
+
// The counting is not decoration. A copy step that silently copies nothing
|
|
7
|
+
// ships an SDK with no native code: it installs, it links, it does nothing, and
|
|
8
|
+
// nothing errors. That is the worst failure available, so this refuses to
|
|
9
|
+
// succeed quietly.
|
|
10
|
+
import {
|
|
11
|
+
cpSync,
|
|
12
|
+
existsSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
readdirSync,
|
|
16
|
+
rmSync,
|
|
17
|
+
statSync,
|
|
18
|
+
writeFileSync,
|
|
19
|
+
} from 'node:fs';
|
|
20
|
+
import { dirname, join, resolve } from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
|
|
23
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const packageRoot = resolve(here, '..');
|
|
25
|
+
const monorepo = resolve(packageRoot, '../..');
|
|
26
|
+
|
|
27
|
+
/** Every file under `dir`, recursively, matching one of `extensions`. */
|
|
28
|
+
function countSources(dir, extensions) {
|
|
29
|
+
if (!existsSync(dir)) return 0;
|
|
30
|
+
let total = 0;
|
|
31
|
+
for (const entry of readdirSync(dir)) {
|
|
32
|
+
const path = join(dir, entry);
|
|
33
|
+
if (statSync(path).isDirectory()) total += countSources(path, extensions);
|
|
34
|
+
else if (extensions.some((ext) => entry.endsWith(ext))) total += 1;
|
|
35
|
+
}
|
|
36
|
+
return total;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The Android plugin modules whose sources must travel with the base SDK.
|
|
41
|
+
*
|
|
42
|
+
* The SDK resolves these by fixed class name with `Class.forName`, so a plugin
|
|
43
|
+
* that is not in the package resolves to null — silently, because an absent
|
|
44
|
+
* plugin is the ordinary case for a native app that did not ask for it. Leaving
|
|
45
|
+
* them behind therefore produced no error and no log: just an Android SDK with
|
|
46
|
+
* no Play Install Referrer, reporting paid installs as organic.
|
|
47
|
+
*
|
|
48
|
+
* `plugin-firebase` is deliberately absent and `plugin-firebase-reflect` takes
|
|
49
|
+
* its place: the typed one imports firebase-analytics, and adding that
|
|
50
|
+
* dependency would force Firebase on every React Native app. The reflect
|
|
51
|
+
* variant declares the same class name and reaches Firebase only if the host
|
|
52
|
+
* app already ships it. The two are never on one classpath, because a wrapper
|
|
53
|
+
* app has no way to add the Maven artifact.
|
|
54
|
+
*/
|
|
55
|
+
const ANDROID_PLUGINS = [
|
|
56
|
+
'plugin-play-referrer',
|
|
57
|
+
'plugin-play-identity',
|
|
58
|
+
'plugin-play-integrity',
|
|
59
|
+
'plugin-firebase-reflect',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The SwiftPM modules the SDK is split into. They import each other, and the
|
|
64
|
+
* podspec globs all of their sources into ONE pod target — where those imports
|
|
65
|
+
* name modules that do not exist.
|
|
66
|
+
*
|
|
67
|
+
* So the imports are stripped on the way in. They are meaningful only in the
|
|
68
|
+
* SwiftPM distribution; in the flattened one every symbol is already in scope,
|
|
69
|
+
* and `internal` is module-scoped, so flattening makes things MORE visible
|
|
70
|
+
* rather than less. The only thing it could break is a name collision between
|
|
71
|
+
* two modules, and the compiler says so — which is why the vendored tree is
|
|
72
|
+
* compiled as one module by `check-vendored-swift.mjs` rather than trusted.
|
|
73
|
+
*/
|
|
74
|
+
const SWIFT_MODULES = ['Advenue', 'AdvenueCore', 'AdvenuePlatform', 'AdvenueFirebase'];
|
|
75
|
+
|
|
76
|
+
/** Removes intra-SDK imports from every vendored Swift file. */
|
|
77
|
+
function flattenSwiftModules(dir) {
|
|
78
|
+
const pattern = new RegExp(`^\\s*import\\s+(?:${SWIFT_MODULES.join('|')})\\s*$\\n?`, 'gm');
|
|
79
|
+
let stripped = 0;
|
|
80
|
+
for (const entry of readdirSync(dir)) {
|
|
81
|
+
const path = join(dir, entry);
|
|
82
|
+
if (statSync(path).isDirectory()) {
|
|
83
|
+
stripped += flattenSwiftModules(path);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!entry.endsWith('.swift')) continue;
|
|
87
|
+
const source = readFileSync(path, 'utf8');
|
|
88
|
+
const flattened = source.replace(pattern, '');
|
|
89
|
+
if (flattened !== source) {
|
|
90
|
+
writeFileSync(path, flattened);
|
|
91
|
+
stripped += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return stripped;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function vendor({ swiftFrom, kotlinFrom, into, layout } = {}) {
|
|
98
|
+
const swiftSource = swiftFrom ?? resolve(monorepo, 'packages/sdk-swift/Sources');
|
|
99
|
+
const kotlinSource = kotlinFrom ?? resolve(monorepo, 'packages/sdk-android/sdk/src/main/kotlin');
|
|
100
|
+
const target = into ?? packageRoot;
|
|
101
|
+
|
|
102
|
+
// Where each wrapper wants the sources. React Native's layout is the default
|
|
103
|
+
// so this change moves nothing; Flutter passes its own, because its iOS path
|
|
104
|
+
// is fixed by Flutter's Swift Package Manager structure rather than by us.
|
|
105
|
+
const swiftTarget = join(target, layout?.swift ?? 'ios/vendor');
|
|
106
|
+
const kotlinTarget = join(target, layout?.kotlin ?? 'android/src/main/kotlin');
|
|
107
|
+
|
|
108
|
+
for (const dir of [swiftTarget, kotlinTarget]) {
|
|
109
|
+
rmSync(dir, { recursive: true, force: true });
|
|
110
|
+
mkdirSync(dir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (existsSync(swiftSource)) cpSync(swiftSource, swiftTarget, { recursive: true });
|
|
114
|
+
if (existsSync(kotlinSource)) cpSync(kotlinSource, kotlinTarget, { recursive: true });
|
|
115
|
+
|
|
116
|
+
// The plugin modules merge into the same package tree the base SDK occupies,
|
|
117
|
+
// which is exactly what the fixed-name lookup expects to find.
|
|
118
|
+
if (kotlinFrom === undefined) {
|
|
119
|
+
for (const plugin of ANDROID_PLUGINS) {
|
|
120
|
+
const source = resolve(monorepo, `packages/sdk-android/${plugin}/src/main/kotlin`);
|
|
121
|
+
if (existsSync(source)) cpSync(source, kotlinTarget, { recursive: true });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (existsSync(swiftTarget)) flattenSwiftModules(swiftTarget);
|
|
126
|
+
|
|
127
|
+
const swift = countSources(swiftTarget, ['.swift']);
|
|
128
|
+
const kotlin = countSources(kotlinTarget, ['.kt']);
|
|
129
|
+
|
|
130
|
+
if (swift === 0 || kotlin === 0) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
swift,
|
|
134
|
+
kotlin,
|
|
135
|
+
error: `vendoring copied no files (swift=${swift}, kotlin=${kotlin}). Shipping this package would install an SDK with no native code.`,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { ok: true, swift, kotlin };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Run directly: report and fail loudly.
|
|
142
|
+
if (process.argv[1]?.endsWith('vendor-natives.mjs')) {
|
|
143
|
+
const result = vendor();
|
|
144
|
+
if (!result.ok) {
|
|
145
|
+
console.error(result.error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
console.log(`vendored ${result.swift} Swift and ${result.kotlin} Kotlin sources`);
|
|
149
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {
|
|
3
|
+
mkdirSync,
|
|
4
|
+
mkdtempSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
statSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
|
|
13
|
+
/** Every .kt file under `dir`, recursively; an absent dir yields none. */
|
|
14
|
+
function kotlinFiles(dir) {
|
|
15
|
+
let out = [];
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = readdirSync(dir);
|
|
19
|
+
} catch {
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const path = join(dir, entry);
|
|
24
|
+
if (statSync(path).isDirectory()) out = out.concat(kotlinFiles(path));
|
|
25
|
+
else if (entry.endsWith('.kt')) out.push(path);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
import { test } from 'node:test';
|
|
30
|
+
import { vendor } from './vendor-natives.mjs';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A copy step that silently copies nothing ships an SDK with no native code:
|
|
34
|
+
* it installs, it links, it does nothing, and nothing errors. That is the
|
|
35
|
+
* failure this refuses to allow, so it is the first thing asserted.
|
|
36
|
+
*/
|
|
37
|
+
test('refuses an empty result rather than succeeding quietly', () => {
|
|
38
|
+
const empty = mkdtempSync(join(tmpdir(), 'advenue-empty-'));
|
|
39
|
+
const target = mkdtempSync(join(tmpdir(), 'advenue-target-'));
|
|
40
|
+
|
|
41
|
+
const result = vendor({ swiftFrom: empty, kotlinFrom: empty, into: target });
|
|
42
|
+
|
|
43
|
+
assert.equal(result.ok, false);
|
|
44
|
+
assert.match(result.error, /copied no files/);
|
|
45
|
+
assert.match(result.error, /no native code/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('copies the real SDK sources and counts them', () => {
|
|
49
|
+
const target = mkdtempSync(join(tmpdir(), 'advenue-target-'));
|
|
50
|
+
const result = vendor({ into: target });
|
|
51
|
+
|
|
52
|
+
assert.equal(result.ok, true, result.error);
|
|
53
|
+
assert.ok(result.swift > 20, `expected the Swift SDK's sources, got ${result.swift}`);
|
|
54
|
+
assert.ok(result.kotlin > 15, `expected the Kotlin SDK's sources, got ${result.kotlin}`);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('a partial source tree is still a failure', () => {
|
|
58
|
+
const swiftOnly = mkdtempSync(join(tmpdir(), 'advenue-swift-'));
|
|
59
|
+
mkdirSync(join(swiftOnly, 'Core'), { recursive: true });
|
|
60
|
+
writeFileSync(join(swiftOnly, 'Core/A.swift'), '// source');
|
|
61
|
+
const empty = mkdtempSync(join(tmpdir(), 'advenue-empty-'));
|
|
62
|
+
const target = mkdtempSync(join(tmpdir(), 'advenue-target-'));
|
|
63
|
+
|
|
64
|
+
const result = vendor({ swiftFrom: swiftOnly, kotlinFrom: empty, into: target });
|
|
65
|
+
|
|
66
|
+
assert.equal(result.ok, false, 'one platform missing is still a broken package');
|
|
67
|
+
assert.equal(result.swift, 1);
|
|
68
|
+
assert.equal(result.kotlin, 0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Every plugin class the SDK looks up by name must exist in this package.
|
|
73
|
+
*
|
|
74
|
+
* This is the check that was missing, and its absence shipped an outage. The
|
|
75
|
+
* Android SDK resolves its enrichment plugins with `Class.forName` against
|
|
76
|
+
* fixed names in `PluginRegistry`; the implementations live in separate Gradle
|
|
77
|
+
* modules that the vendoring did not copy. So `Class.forName` returned null for
|
|
78
|
+
* all four, silently, and an app built on the React Native SDK had no Play
|
|
79
|
+
* Install Referrer — Android's primary deterministic attribution signal, whose
|
|
80
|
+
* absence reports paid installs as organic.
|
|
81
|
+
*
|
|
82
|
+
* Counting files cannot catch that: the count was right and the package was
|
|
83
|
+
* still broken. What catches it is asking whether what was copied is
|
|
84
|
+
* SUFFICIENT, which is what this asserts.
|
|
85
|
+
*/
|
|
86
|
+
test('every plugin class the registry looks up is present in the package', () => {
|
|
87
|
+
const target = mkdtempSync(join(tmpdir(), 'advenue-target-'));
|
|
88
|
+
const result = vendor({ into: target });
|
|
89
|
+
assert.equal(result.ok, true, result.error);
|
|
90
|
+
|
|
91
|
+
const registry = readFileSync(
|
|
92
|
+
join(target, 'android/src/main/kotlin/io/advenue/plugin/PluginRegistry.kt'),
|
|
93
|
+
'utf8',
|
|
94
|
+
);
|
|
95
|
+
const expected = [...registry.matchAll(/"(io\.advenue\.plugin\.\w+)"/g)].map((m) => m[1]);
|
|
96
|
+
assert.ok(expected.length >= 4, `expected the registry's plugin names, got ${expected.length}`);
|
|
97
|
+
|
|
98
|
+
const declared = new Set();
|
|
99
|
+
for (const root of [join(target, 'android/src/main/kotlin'), 'android/src/main/java']) {
|
|
100
|
+
for (const file of kotlinFiles(root)) {
|
|
101
|
+
const source = readFileSync(file, 'utf8');
|
|
102
|
+
const pkg = /^package\s+([\w.]+)/m.exec(source)?.[1];
|
|
103
|
+
if (!pkg) continue;
|
|
104
|
+
for (const [, name] of source.matchAll(/^(?:public |internal )?(?:class|object)\s+(\w+)/gm)) {
|
|
105
|
+
declared.add(`${pkg}.${name}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const missing = expected.filter((name) => !declared.has(name));
|
|
111
|
+
assert.deepEqual(missing, [], 'a plugin the SDK looks up by name is not in the package');
|
|
112
|
+
});
|
package/src/deep-links.ts
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
|
-
import type { DeepLink } from '
|
|
1
|
+
import type { DeepLink } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* M3: `params`'a kopyalanmasına izin verilen query anahtarları.
|
|
5
|
+
*
|
|
6
|
+
* `parseDirectLink` eskiden tüm query dizgesini kopyalıyordu (`?email=…&phone=…`
|
|
7
|
+
* gibi PII taşıyan anahtarlar dahil). Ham `url` aynen korunur (uygulama kendi
|
|
8
|
+
* yönlendirmesi için kendisi ayrıştırabilir), ancak `params` yalnızca
|
|
9
|
+
* attribution/yönlendirme için güvenli olduğu bilinen anahtarları taşır.
|
|
10
|
+
*/
|
|
11
|
+
const ALLOWED_QUERY_KEYS = new Set([
|
|
12
|
+
'ref',
|
|
13
|
+
'campaign',
|
|
14
|
+
'network',
|
|
15
|
+
'influencer_id',
|
|
16
|
+
'influencerId',
|
|
17
|
+
'al_applink_data',
|
|
18
|
+
]);
|
|
2
19
|
|
|
3
20
|
/** Handle for tearing down the OS deep-link listener. */
|
|
4
21
|
export interface DeepLinkSubscription {
|
|
@@ -32,6 +49,7 @@ export function parseDirectLink(url: string): DeepLink {
|
|
|
32
49
|
// lib.dom.iterable / @types/node, neither of which is guaranteed in the
|
|
33
50
|
// dts build's lib set — forEach is in the base lib everywhere.
|
|
34
51
|
parsed.searchParams.forEach((v, k) => {
|
|
52
|
+
if (!ALLOWED_QUERY_KEYS.has(k)) return;
|
|
35
53
|
params ??= {};
|
|
36
54
|
params[k] = v;
|
|
37
55
|
});
|