@amplytools/react-native-amply-sdk 0.1.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 +178 -0
- package/README.md +714 -0
- package/android/build.gradle +90 -0
- package/android/consumer-rules.pro +1 -0
- package/android/gradle.properties +3 -0
- package/android/settings.gradle +9 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyModule.kt +384 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/AmplyPackage.kt +39 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/core/AmplyClient.kt +30 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/core/DefaultAmplyClient.kt +296 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/AmplyInitializationOptions.kt +10 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/DataSetType.kt +42 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/DataSetTypeMapper.kt +38 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/DeepLinkPayload.kt +8 -0
- package/android/src/main/java/tools/amply/sdk/reactnative/model/EventEnvelope.kt +9 -0
- package/android/src/main/jni/AmplyTurboModule.cpp +29 -0
- package/android/src/main/jni/CMakeLists.txt +76 -0
- package/android/src/newarch/java/tools/amply/sdk/reactnative/NativeAmplyModuleSpec.java +75 -0
- package/android/src/newarch/jni/AmplyReactNative-generated.cpp +77 -0
- package/android/src/newarch/jni/AmplyReactNative.h +31 -0
- package/android/src/newarch/jni/CMakeLists.txt +40 -0
- package/app.plugin.js +1 -0
- package/dist/index.js +272 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +234 -0
- package/dist/index.mjs.map +1 -0
- package/dist/plugin/index.d.ts +6 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +186 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/index.mjs +169 -0
- package/dist/plugin/index.mjs.map +1 -0
- package/dist/plugin/src/index.d.ts +6 -0
- package/dist/plugin/src/index.d.ts.map +1 -0
- package/dist/plugin/src/index.js +3 -0
- package/dist/plugin/src/withAmply.d.ts +30 -0
- package/dist/plugin/src/withAmply.d.ts.map +1 -0
- package/dist/plugin/src/withAmply.js +51 -0
- package/dist/plugin/withAmply.d.ts +12 -0
- package/dist/plugin/withAmply.d.ts.map +1 -0
- package/dist/src/__tests__/index.test.d.ts +2 -0
- package/dist/src/__tests__/index.test.d.ts.map +1 -0
- package/dist/src/__tests__/index.test.js +70 -0
- package/dist/src/hooks/useAmplySystemEvents.d.ts +12 -0
- package/dist/src/hooks/useAmplySystemEvents.d.ts.map +1 -0
- package/dist/src/hooks/useAmplySystemEvents.js +56 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +80 -0
- package/dist/src/nativeModule.d.ts +5 -0
- package/dist/src/nativeModule.d.ts.map +1 -0
- package/dist/src/nativeModule.js +48 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts +75 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.d.ts.map +1 -0
- package/dist/src/nativeSpecs/NativeAmplyModule.js +2 -0
- package/dist/src/systemEventUtils.d.ts +3 -0
- package/dist/src/systemEventUtils.d.ts.map +1 -0
- package/dist/src/systemEventUtils.js +30 -0
- package/dist/src/systemEvents.d.ts +6 -0
- package/dist/src/systemEvents.d.ts.map +1 -0
- package/dist/src/systemEvents.js +8 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/ARCHITECTURE.md +1115 -0
- package/expo-module.config.json +11 -0
- package/ios/AmplyReactNative.podspec +32 -0
- package/ios/README.md +11 -0
- package/ios/Sources/AmplyReactNative/AmplyModule.mm +332 -0
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative-generated.mm +111 -0
- package/ios/Sources/AmplyReactNative/AmplyReactNative/AmplyReactNative.h +152 -0
- package/package.json +71 -0
- package/plugin/build/index.d.ts +5 -0
- package/plugin/build/index.js +8 -0
- package/plugin/build/withAmply.d.ts +29 -0
- package/plugin/build/withAmply.js +53 -0
- package/plugin/src/index.ts +7 -0
- package/plugin/src/withAmply.ts +68 -0
- package/plugin/tsconfig.json +8 -0
- package/plugin/tsconfig.tsbuildinfo +1 -0
- package/react-native.config.js +34 -0
- package/scripts/codegen.js +212 -0
- package/src/__tests__/index.test.ts +92 -0
- package/src/hooks/useAmplySystemEvents.ts +75 -0
- package/src/index.ts +115 -0
- package/src/nativeModule.ts +65 -0
- package/src/nativeSpecs/NativeAmplyModule.ts +80 -0
- package/src/systemEventUtils.ts +35 -0
- package/src/systemEvents.ts +13 -0
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amplytools/react-native-amply-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React Native SDK for Amply: Mobile SDK to orchestrate in-app experiences and campaigns remotely, without app releases",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/amply/amply-react-native.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/amply/amply-react-native",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/amply/amply-react-native/issues"
|
|
13
|
+
},
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"module": "dist/index.mjs",
|
|
16
|
+
"types": "dist/src/index.d.ts",
|
|
17
|
+
"react-native": "src/index.ts",
|
|
18
|
+
"source": "src/index.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"android/",
|
|
21
|
+
"!android/.cxx/",
|
|
22
|
+
"!android/build/",
|
|
23
|
+
"!android/.gradle/",
|
|
24
|
+
"ios/",
|
|
25
|
+
"dist/",
|
|
26
|
+
"src/",
|
|
27
|
+
"plugin/",
|
|
28
|
+
"scripts/",
|
|
29
|
+
"react-native.config.js",
|
|
30
|
+
"app.plugin.js",
|
|
31
|
+
"expo-module.config.json",
|
|
32
|
+
"README.md",
|
|
33
|
+
"docs/"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "expo-module build",
|
|
37
|
+
"build:plugin": "expo-module build plugin",
|
|
38
|
+
"clean": "expo-module clean",
|
|
39
|
+
"test": "expo-module test",
|
|
40
|
+
"prepare": "expo-module prepare",
|
|
41
|
+
"prepublishOnly": "expo-module prepublishOnly"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"expo": ">=54.0.0",
|
|
45
|
+
"react": ">=18.2.0",
|
|
46
|
+
"react-native": ">=0.79.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@react-native/codegen": "0.81.4",
|
|
50
|
+
"@react-native/eslint-config": "^0.74.81",
|
|
51
|
+
"@react-native/typescript-config": "0.81.0",
|
|
52
|
+
"@types/jest": "^29.5.11",
|
|
53
|
+
"@types/node": "^25.0.3",
|
|
54
|
+
"@types/react": "^19.1.1",
|
|
55
|
+
"@types/react-native": "^0.73.0",
|
|
56
|
+
"eslint": "^8.56.0",
|
|
57
|
+
"expo": "^54.0.0",
|
|
58
|
+
"expo-module-scripts": "^3.5.0",
|
|
59
|
+
"jest": "^29.7.0",
|
|
60
|
+
"typescript": "^5.4.0"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=18"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"expo": {
|
|
69
|
+
"plugin": "./app.plugin.js"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const withAmply_1 = __importDefault(require("./withAmply"));
|
|
7
|
+
const plugin = (config, props) => (0, withAmply_1.default)(config, props);
|
|
8
|
+
exports.default = plugin;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo Config Plugin for Amply React Native SDK
|
|
3
|
+
*
|
|
4
|
+
* The Amply React Native SDK is a TurboModule that uses react-native.config.js
|
|
5
|
+
* for autolinking in Bare React Native. For Expo apps, this plugin ensures
|
|
6
|
+
* AmplyPackage is registered in MainApplication (Android).
|
|
7
|
+
*
|
|
8
|
+
* iOS: AmplySDK is fetched automatically from CocoaPods Trunk via the podspec
|
|
9
|
+
* dependency declaration - no Podfile modifications needed.
|
|
10
|
+
*
|
|
11
|
+
* Architecture:
|
|
12
|
+
* - Bare RN: Uses react-native.config.js autolinking (fully automatic)
|
|
13
|
+
* - Expo: Uses react-native.config.js + Expo config plugin for Android registration
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* 1. Add to app.json: "@amply/amply-react-native" in plugins array
|
|
17
|
+
* 2. Run: expo prebuild --clean
|
|
18
|
+
* 3. Run: expo start and expo run:android/ios
|
|
19
|
+
*/
|
|
20
|
+
import { ConfigPlugin } from '@expo/config-plugins';
|
|
21
|
+
type WithAmplyPluginProps = {};
|
|
22
|
+
/**
|
|
23
|
+
* Expo Config Plugin for Amply React Native SDK
|
|
24
|
+
*
|
|
25
|
+
* Registers AmplyPackage in MainApplication for Expo apps (Android).
|
|
26
|
+
* iOS uses CocoaPods Trunk for AmplySDK - no modifications needed.
|
|
27
|
+
*/
|
|
28
|
+
declare const withAmply: ConfigPlugin<WithAmplyPluginProps>;
|
|
29
|
+
export default withAmply;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Expo Config Plugin for Amply React Native SDK
|
|
4
|
+
*
|
|
5
|
+
* The Amply React Native SDK is a TurboModule that uses react-native.config.js
|
|
6
|
+
* for autolinking in Bare React Native. For Expo apps, this plugin ensures
|
|
7
|
+
* AmplyPackage is registered in MainApplication (Android).
|
|
8
|
+
*
|
|
9
|
+
* iOS: AmplySDK is fetched automatically from CocoaPods Trunk via the podspec
|
|
10
|
+
* dependency declaration - no Podfile modifications needed.
|
|
11
|
+
*
|
|
12
|
+
* Architecture:
|
|
13
|
+
* - Bare RN: Uses react-native.config.js autolinking (fully automatic)
|
|
14
|
+
* - Expo: Uses react-native.config.js + Expo config plugin for Android registration
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* 1. Add to app.json: "@amply/amply-react-native" in plugins array
|
|
18
|
+
* 2. Run: expo prebuild --clean
|
|
19
|
+
* 3. Run: expo start and expo run:android/ios
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
const config_plugins_1 = require("@expo/config-plugins");
|
|
23
|
+
/**
|
|
24
|
+
* Modifies Android MainApplication to register AmplyPackage
|
|
25
|
+
*/
|
|
26
|
+
const withAmplyAndroid = (config) => {
|
|
27
|
+
return (0, config_plugins_1.withMainApplication)(config, async (cfg) => {
|
|
28
|
+
let contents = cfg.modResults.contents;
|
|
29
|
+
// Check if AmplyPackage is already imported
|
|
30
|
+
if (!contents.includes('import tools.amply.sdk.reactnative.AmplyPackage')) {
|
|
31
|
+
// Add import after the expo imports
|
|
32
|
+
contents = contents.replace(/(import expo\.modules\.ReactNativeHostWrapper)/, `import tools.amply.sdk.reactnative.AmplyPackage\n$1`);
|
|
33
|
+
}
|
|
34
|
+
// For Kotlin syntax: packages.apply { ... }
|
|
35
|
+
// We need to add the package instance inside the apply block
|
|
36
|
+
if (!contents.includes('add(AmplyPackage())')) {
|
|
37
|
+
contents = contents.replace(/(PackageList\(this\)\.packages\.apply \{[\s\S]*?)(\/\/ add.*?)\n(\s+\})/, `$1$2\n add(AmplyPackage())\n$3`);
|
|
38
|
+
}
|
|
39
|
+
cfg.modResults.contents = contents;
|
|
40
|
+
return cfg;
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Expo Config Plugin for Amply React Native SDK
|
|
45
|
+
*
|
|
46
|
+
* Registers AmplyPackage in MainApplication for Expo apps (Android).
|
|
47
|
+
* iOS uses CocoaPods Trunk for AmplySDK - no modifications needed.
|
|
48
|
+
*/
|
|
49
|
+
const withAmply = (config) => {
|
|
50
|
+
// Apply Android modifications
|
|
51
|
+
return withAmplyAndroid(config);
|
|
52
|
+
};
|
|
53
|
+
exports.default = withAmply;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo Config Plugin for Amply React Native SDK
|
|
3
|
+
*
|
|
4
|
+
* The Amply React Native SDK is a TurboModule that uses react-native.config.js
|
|
5
|
+
* for autolinking in Bare React Native. For Expo apps, this plugin ensures
|
|
6
|
+
* AmplyPackage is registered in MainApplication (Android).
|
|
7
|
+
*
|
|
8
|
+
* iOS: AmplySDK is fetched automatically from CocoaPods Trunk via the podspec
|
|
9
|
+
* dependency declaration - no Podfile modifications needed.
|
|
10
|
+
*
|
|
11
|
+
* Architecture:
|
|
12
|
+
* - Bare RN: Uses react-native.config.js autolinking (fully automatic)
|
|
13
|
+
* - Expo: Uses react-native.config.js + Expo config plugin for Android registration
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* 1. Add to app.json: "@amply/amply-react-native" in plugins array
|
|
17
|
+
* 2. Run: expo prebuild --clean
|
|
18
|
+
* 3. Run: expo start and expo run:android/ios
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { ConfigPlugin, withMainApplication } from '@expo/config-plugins';
|
|
22
|
+
|
|
23
|
+
type WithAmplyPluginProps = {
|
|
24
|
+
// Props for future use
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Modifies Android MainApplication to register AmplyPackage
|
|
29
|
+
*/
|
|
30
|
+
const withAmplyAndroid: ConfigPlugin = (config) => {
|
|
31
|
+
return withMainApplication(config, async (cfg) => {
|
|
32
|
+
let contents = cfg.modResults.contents;
|
|
33
|
+
|
|
34
|
+
// Check if AmplyPackage is already imported
|
|
35
|
+
if (!contents.includes('import tools.amply.sdk.reactnative.AmplyPackage')) {
|
|
36
|
+
// Add import after the expo imports
|
|
37
|
+
contents = contents.replace(
|
|
38
|
+
/(import expo\.modules\.ReactNativeHostWrapper)/,
|
|
39
|
+
`import tools.amply.sdk.reactnative.AmplyPackage\n$1`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// For Kotlin syntax: packages.apply { ... }
|
|
44
|
+
// We need to add the package instance inside the apply block
|
|
45
|
+
if (!contents.includes('add(AmplyPackage())')) {
|
|
46
|
+
contents = contents.replace(
|
|
47
|
+
/(PackageList\(this\)\.packages\.apply \{[\s\S]*?)(\/\/ add.*?)\n(\s+\})/,
|
|
48
|
+
`$1$2\n add(AmplyPackage())\n$3`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cfg.modResults.contents = contents;
|
|
53
|
+
return cfg;
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Expo Config Plugin for Amply React Native SDK
|
|
59
|
+
*
|
|
60
|
+
* Registers AmplyPackage in MainApplication for Expo apps (Android).
|
|
61
|
+
* iOS uses CocoaPods Trunk for AmplySDK - no modifications needed.
|
|
62
|
+
*/
|
|
63
|
+
const withAmply: ConfigPlugin<WithAmplyPluginProps> = (config) => {
|
|
64
|
+
// Apply Android modifications
|
|
65
|
+
return withAmplyAndroid(config);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default withAmply;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts","./src/withamply.ts"],"version":"5.9.3"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const codegenSourceDirAndroid = path.join(__dirname, 'android', 'src', 'newarch');
|
|
4
|
+
const codegenSourceDirIos = path.join(__dirname, 'ios', 'Sources', 'AmplyReactNative');
|
|
5
|
+
const androidProjectDir = path.join(__dirname, 'android');
|
|
6
|
+
const androidJniDir = path.join(androidProjectDir, 'src', 'main', 'jni');
|
|
7
|
+
|
|
8
|
+
module.exports = {
|
|
9
|
+
dependency: {
|
|
10
|
+
platforms: {
|
|
11
|
+
android: {
|
|
12
|
+
sourceDir: androidProjectDir,
|
|
13
|
+
packageImportPath: 'import tools.amply.sdk.reactnative.AmplyPackage;',
|
|
14
|
+
packageInstance: 'new AmplyPackage()',
|
|
15
|
+
cmakeListsPath: path.join(androidJniDir, 'CMakeLists.txt'),
|
|
16
|
+
},
|
|
17
|
+
ios: null,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
commands: [],
|
|
21
|
+
assets: [],
|
|
22
|
+
codegenConfig: {
|
|
23
|
+
name: 'AmplyReactNative',
|
|
24
|
+
type: 'modules',
|
|
25
|
+
jsSrcsDir: path.join(__dirname, 'src'),
|
|
26
|
+
android: {
|
|
27
|
+
sourceDir: codegenSourceDirAndroid,
|
|
28
|
+
packageName: 'tools.amply.sdk.reactnative',
|
|
29
|
+
},
|
|
30
|
+
ios: {
|
|
31
|
+
sourceDir: codegenSourceDirIos,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const {
|
|
5
|
+
combineSchemasInFileList,
|
|
6
|
+
} = require('@react-native/codegen/lib/cli/combine/combine-js-to-schema');
|
|
7
|
+
const RNCodegen = require('@react-native/codegen/lib/generators/RNCodegen');
|
|
8
|
+
|
|
9
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
10
|
+
const configPath = path.join(ROOT, 'codegen.config.json');
|
|
11
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Clean up stale generated files from previous codegen runs with different libraryName.
|
|
15
|
+
* RN codegen doesn't automatically clean old files when libraryName changes.
|
|
16
|
+
*/
|
|
17
|
+
function cleanupStaleGeneratedFiles(outputDir, currentLibraryName) {
|
|
18
|
+
if (!fs.existsSync(outputDir)) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const files = fs.readdirSync(outputDir);
|
|
23
|
+
const stalePatterns = [
|
|
24
|
+
/-generated\.(cpp|h)$/, // Files ending in -generated.cpp or -generated.h
|
|
25
|
+
/^[^/]+(\.h)$/, // Header files
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Only delete files that don't match current library name
|
|
29
|
+
files.forEach(file => {
|
|
30
|
+
if (file.includes(currentLibraryName)) {
|
|
31
|
+
return; // Keep files with current library name
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (stalePatterns.some(pattern => pattern.test(file))) {
|
|
35
|
+
const filePath = path.join(outputDir, file);
|
|
36
|
+
try {
|
|
37
|
+
fs.unlinkSync(filePath);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// Silently ignore - might be a directory
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Патчим android/src/newarch/jni/CMakeLists.txt после генерации.
|
|
47
|
+
* RN 0.79 в Gradle передаёт в CMake параметр:
|
|
48
|
+
* -DREACT_NATIVE_DIR=/.../node_modules/react-native
|
|
49
|
+
*
|
|
50
|
+
* Мы из него делаем REACT_COMMON_DIR и подрубаем
|
|
51
|
+
* ReactCommon/cmake-utils/react-native-flags.cmake
|
|
52
|
+
* Там как раз и объявлен target_compile_reactnative_options.
|
|
53
|
+
*/
|
|
54
|
+
function patchAndroidCMake(cmakePath) {
|
|
55
|
+
if (!fs.existsSync(cmakePath)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const original = fs.readFileSync(cmakePath, 'utf-8');
|
|
60
|
+
|
|
61
|
+
// Уже патчили — выходим.
|
|
62
|
+
if (original.includes('react-native-flags.cmake')) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const insertSnippet = `
|
|
67
|
+
# --- React Native 0.79: wire up compile flags ---
|
|
68
|
+
if(NOT DEFINED REACT_NATIVE_DIR)
|
|
69
|
+
message(FATAL_ERROR "REACT_NATIVE_DIR is not defined; it must point to node_modules/react-native")
|
|
70
|
+
endif()
|
|
71
|
+
|
|
72
|
+
# Gradle (externalNativeBuild) passes REACT_NATIVE_DIR.
|
|
73
|
+
# From it we derive ReactCommon path used by RN CMake utils.
|
|
74
|
+
set(REACT_COMMON_DIR "\${REACT_NATIVE_DIR}/ReactCommon")
|
|
75
|
+
|
|
76
|
+
# This provides target_compile_reactnative_options and imported targets
|
|
77
|
+
# like 'reactnative', 'jsi', 'fbjni', etc.
|
|
78
|
+
include(\${REACT_COMMON_DIR}/cmake-utils/react-native-flags.cmake)
|
|
79
|
+
# --- end RN wiring ---
|
|
80
|
+
`.trim();
|
|
81
|
+
|
|
82
|
+
const linesIn = original.split('\n');
|
|
83
|
+
const linesOut = [];
|
|
84
|
+
let inserted = false;
|
|
85
|
+
|
|
86
|
+
for (let i = 0; i < linesIn.length; i++) {
|
|
87
|
+
const line = linesIn[i];
|
|
88
|
+
linesOut.push(line);
|
|
89
|
+
|
|
90
|
+
// Вставляем наш блок сразу после set(CMAKE_VERBOSE_MAKEFILE on)
|
|
91
|
+
if (!inserted && line.match(/set\s*\(\s*CMAKE_VERBOSE_MAKEFILE\s+on\s*\)/)) {
|
|
92
|
+
linesOut.push('');
|
|
93
|
+
linesOut.push(insertSnippet);
|
|
94
|
+
linesOut.push('');
|
|
95
|
+
inserted = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const updated = linesOut.join('\n');
|
|
100
|
+
fs.writeFileSync(cmakePath, updated);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Patch the newarch CMakeLists.txt to add missing JSI include directories
|
|
105
|
+
* and ensure proper inclusion of React headers.
|
|
106
|
+
*/
|
|
107
|
+
function enhanceNewarchCMake(cmakePath) {
|
|
108
|
+
if (!fs.existsSync(cmakePath)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let content = fs.readFileSync(cmakePath, 'utf-8');
|
|
113
|
+
|
|
114
|
+
// Check if we've already added the JSI includes
|
|
115
|
+
if (content.includes('REACT_JSI_DIR')) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Additional imports needed for generated TurboModule code
|
|
120
|
+
const jsiEnhancement = `\n# Ensure fbjni is available as imported target
|
|
121
|
+
find_package(fbjni REQUIRED CONFIG)
|
|
122
|
+
|
|
123
|
+
# For OBJECT libraries, we need to explicitly add include directories since
|
|
124
|
+
# target_link_libraries doesn't propagate INTERFACE_INCLUDE_DIRECTORIES for OBJECT libs
|
|
125
|
+
set(REACT_JSI_DIR "\${REACT_COMMON_DIR}/jsi")
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
// Insert after the react-native-flags.cmake include, before file(GLOB...)
|
|
129
|
+
content = content.replace(
|
|
130
|
+
/# --- end RN wiring ---\n\n\nfile\(GLOB/,
|
|
131
|
+
'# --- end RN wiring ---' + jsiEnhancement + '\nfile(GLOB'
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// For OBJECT libraries, we need to add JSI include directories explicitly
|
|
135
|
+
// Note: folly and reactnative includes will be added by the parent CMakeLists.txt
|
|
136
|
+
// after targets are created. We only need JSI here since it's a source directory.
|
|
137
|
+
const includePattern = /target_include_directories\(react_codegen_\w+ PUBLIC \. react\/renderer\/components\/\w+\)/g;
|
|
138
|
+
content = content.replace(includePattern, (match) => {
|
|
139
|
+
if (match.includes('${REACT_JSI_DIR}')) {
|
|
140
|
+
return match; // Already has includes
|
|
141
|
+
}
|
|
142
|
+
// Add JSI include path
|
|
143
|
+
return match.replace(')', ` \${REACT_JSI_DIR})`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Remove target_link_libraries from OBJECT library
|
|
147
|
+
// OBJECT libraries don't need to be linked in the generated CMakeLists
|
|
148
|
+
// The parent CMakeLists will link the main library against the necessary targets
|
|
149
|
+
// and add includes for the OBJECT library separately.
|
|
150
|
+
// This avoids duplicate linking and unresolved symbols in the final link step.
|
|
151
|
+
content = content.replace(
|
|
152
|
+
/target_link_libraries\([^)]+fbjni[^)]+jsi[^)]+reactnative[^)]*\)\n/s,
|
|
153
|
+
''
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
fs.writeFileSync(cmakePath, content);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Генерация для одного модуля из codegen.config.json
|
|
161
|
+
*/
|
|
162
|
+
function generateModule(name, moduleConfig) {
|
|
163
|
+
const jsSpecFile = path.resolve(ROOT, moduleConfig.jsSpecFile);
|
|
164
|
+
const schema = combineSchemasInFileList([jsSpecFile]);
|
|
165
|
+
const libraryName = moduleConfig.libraryName ?? name;
|
|
166
|
+
|
|
167
|
+
const androidConfig = moduleConfig.android;
|
|
168
|
+
if (androidConfig && androidConfig.sourceDir) {
|
|
169
|
+
const outputDirectory = path.resolve(ROOT, androidConfig.sourceDir);
|
|
170
|
+
const jniDir = path.join(outputDirectory, 'jni');
|
|
171
|
+
|
|
172
|
+
// Clean up stale files before generating
|
|
173
|
+
cleanupStaleGeneratedFiles(jniDir, libraryName);
|
|
174
|
+
|
|
175
|
+
RNCodegen.generate(
|
|
176
|
+
{
|
|
177
|
+
libraryName,
|
|
178
|
+
schema,
|
|
179
|
+
outputDirectory,
|
|
180
|
+
packageName: androidConfig.packageName ?? 'com.facebook.fbreact.specs',
|
|
181
|
+
assumeNonnull: true,
|
|
182
|
+
},
|
|
183
|
+
{generators: ['modulesAndroid']},
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const cmakePath = path.join(outputDirectory, 'jni', 'CMakeLists.txt');
|
|
187
|
+
patchAndroidCMake(cmakePath);
|
|
188
|
+
enhanceNewarchCMake(cmakePath); // Add JSI and React header configurations
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const iosConfig = moduleConfig.ios;
|
|
192
|
+
if (iosConfig && iosConfig.sourceDir) {
|
|
193
|
+
RNCodegen.generate(
|
|
194
|
+
{
|
|
195
|
+
libraryName,
|
|
196
|
+
schema,
|
|
197
|
+
outputDirectory: path.resolve(ROOT, iosConfig.sourceDir),
|
|
198
|
+
moduleName: iosConfig.moduleName ?? libraryName,
|
|
199
|
+
assumeNonnull: true,
|
|
200
|
+
},
|
|
201
|
+
{generators: ['modulesIOS']},
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Обрабатываем все модули из codegen.config.json
|
|
207
|
+
Object.entries(config.modules).forEach(([name, moduleConfig]) => {
|
|
208
|
+
if (moduleConfig.type !== 'NativeModule') {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
generateModule(name, moduleConfig);
|
|
212
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as Amply from '../index';
|
|
2
|
+
import {__setNativeModule} from '../nativeModule';
|
|
3
|
+
|
|
4
|
+
const mockNativeModule = {
|
|
5
|
+
initialize: jest.fn(),
|
|
6
|
+
isInitialized: jest.fn(() => true),
|
|
7
|
+
track: jest.fn(),
|
|
8
|
+
getRecentEvents: jest.fn(),
|
|
9
|
+
getDataSetSnapshot: jest.fn(),
|
|
10
|
+
registerDeepLinkListener: jest.fn(),
|
|
11
|
+
addListener: jest.fn(),
|
|
12
|
+
removeListeners: jest.fn(),
|
|
13
|
+
onSystemEvent: jest.fn(),
|
|
14
|
+
onDeepLink: jest.fn(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('Amply JS API', () => {
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
__setNativeModule(null);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
__setNativeModule(mockNativeModule as never);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('initializes the native module', async () => {
|
|
28
|
+
await Amply.initialize({ appId: 'id', apiKeyPublic: 'public' });
|
|
29
|
+
|
|
30
|
+
expect(mockNativeModule.initialize).toHaveBeenCalledWith(
|
|
31
|
+
expect.objectContaining({ appId: 'id', apiKeyPublic: 'public' })
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('tracks events through the native layer', async () => {
|
|
36
|
+
await Amply.track({ name: 'Test Event', properties: { foo: 'bar' } });
|
|
37
|
+
|
|
38
|
+
expect(mockNativeModule.track).toHaveBeenCalledWith({
|
|
39
|
+
name: 'Test Event',
|
|
40
|
+
properties: { foo: 'bar' },
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('reads recent events', async () => {
|
|
45
|
+
const events = [{ name: 'e1' }];
|
|
46
|
+
mockNativeModule.getRecentEvents.mockResolvedValueOnce(events);
|
|
47
|
+
|
|
48
|
+
const result = await Amply.getRecentEvents(3);
|
|
49
|
+
|
|
50
|
+
expect(mockNativeModule.getRecentEvents).toHaveBeenCalledWith(3);
|
|
51
|
+
expect(result).toBe(events);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('requests dataset snapshots', async () => {
|
|
55
|
+
const snapshot = { foo: 'bar' };
|
|
56
|
+
mockNativeModule.getDataSetSnapshot.mockResolvedValueOnce(snapshot);
|
|
57
|
+
|
|
58
|
+
const type = { kind: '@device' as const };
|
|
59
|
+
const result = await Amply.getDataSetSnapshot(type);
|
|
60
|
+
|
|
61
|
+
expect(mockNativeModule.getDataSetSnapshot).toHaveBeenCalledWith(type);
|
|
62
|
+
expect(result).toBe(snapshot);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('subscribes to system events through the TurboModule emitter', async () => {
|
|
66
|
+
const remove = jest.fn();
|
|
67
|
+
mockNativeModule.onSystemEvent.mockReturnValueOnce({ remove });
|
|
68
|
+
|
|
69
|
+
const listener = jest.fn();
|
|
70
|
+
const unsubscribe = await Amply.addSystemEventListener(listener);
|
|
71
|
+
|
|
72
|
+
expect(mockNativeModule.onSystemEvent).toHaveBeenCalledWith(listener);
|
|
73
|
+
unsubscribe();
|
|
74
|
+
expect(remove).toHaveBeenCalledTimes(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('registers deep links once and subscribes via the TurboModule emitter', async () => {
|
|
78
|
+
const remove = jest.fn();
|
|
79
|
+
mockNativeModule.onDeepLink.mockReturnValue({ remove });
|
|
80
|
+
|
|
81
|
+
const listener = jest.fn();
|
|
82
|
+
const unsubscribe = await Amply.addDeepLinkListener(listener);
|
|
83
|
+
expect(mockNativeModule.registerDeepLinkListener).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(mockNativeModule.onDeepLink).toHaveBeenCalledWith(listener);
|
|
85
|
+
|
|
86
|
+
await Amply.addDeepLinkListener(() => {});
|
|
87
|
+
expect(mockNativeModule.registerDeepLinkListener).toHaveBeenCalledTimes(1);
|
|
88
|
+
|
|
89
|
+
unsubscribe();
|
|
90
|
+
expect(remove).toHaveBeenCalledTimes(1);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {useCallback, useEffect, useRef, useState} from 'react';
|
|
2
|
+
import type {EventRecord} from '../nativeSpecs/NativeAmplyModule';
|
|
3
|
+
import {addSystemEventListener} from '../systemEvents';
|
|
4
|
+
|
|
5
|
+
export type UseAmplySystemEventsOptions = {
|
|
6
|
+
maxEntries?: number;
|
|
7
|
+
dedupe?: boolean;
|
|
8
|
+
onEvent?: (event: EventRecord) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type UseAmplySystemEventsResult = {
|
|
12
|
+
events: EventRecord[];
|
|
13
|
+
reset: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function useAmplySystemEvents(
|
|
17
|
+
options: UseAmplySystemEventsOptions = {},
|
|
18
|
+
): UseAmplySystemEventsResult {
|
|
19
|
+
const {maxEntries = 50, dedupe = true, onEvent} = options;
|
|
20
|
+
const [events, setEvents] = useState<EventRecord[]>([]);
|
|
21
|
+
const seenKeysRef = useRef<Set<string>>(new Set());
|
|
22
|
+
const handlerRef = useRef<typeof onEvent>(onEvent);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
handlerRef.current = onEvent;
|
|
26
|
+
}, [onEvent]);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let unsubscribe: (() => void) | undefined;
|
|
30
|
+
let isMounted = true;
|
|
31
|
+
|
|
32
|
+
addSystemEventListener(event => {
|
|
33
|
+
if (!isMounted) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (dedupe) {
|
|
37
|
+
const key = `${event.name}-${event.timestamp}`;
|
|
38
|
+
if (seenKeysRef.current.has(key)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
seenKeysRef.current.add(key);
|
|
42
|
+
}
|
|
43
|
+
handlerRef.current?.(event);
|
|
44
|
+
setEvents(prev => {
|
|
45
|
+
const next = [...prev, event];
|
|
46
|
+
if (next.length > maxEntries) {
|
|
47
|
+
next.splice(0, next.length - maxEntries);
|
|
48
|
+
}
|
|
49
|
+
return next;
|
|
50
|
+
});
|
|
51
|
+
})
|
|
52
|
+
.then(unsub => {
|
|
53
|
+
if (!isMounted) {
|
|
54
|
+
unsub();
|
|
55
|
+
} else {
|
|
56
|
+
unsubscribe = unsub;
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.catch(error => {
|
|
60
|
+
console.warn('[Amply] system event listener error', error);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return () => {
|
|
64
|
+
isMounted = false;
|
|
65
|
+
unsubscribe?.();
|
|
66
|
+
};
|
|
67
|
+
}, [dedupe, maxEntries]);
|
|
68
|
+
|
|
69
|
+
const reset = useCallback(() => {
|
|
70
|
+
seenKeysRef.current.clear();
|
|
71
|
+
setEvents([]);
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
return {events, reset};
|
|
75
|
+
}
|