@apirtc/expo-apirtc-options-plugin 0.0.1

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 ADDED
@@ -0,0 +1,42 @@
1
+ # expo-apirtc-options-plugin
2
+
3
+ Plugin to add apiRTC features in React Native application using Expo.
4
+ This plugin simplify the integration of following features :
5
+ - screenSharing for Android and iOS
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @apizee/expo-apirtc-options-plugin-test
11
+
12
+ ```
13
+
14
+ ## Usage in app.json
15
+
16
+ You need to declare the plugin in app.json file :
17
+
18
+ ```js
19
+ module.exports = {
20
+ ...
21
+ plugins: [
22
+ ['@apizee/expo-apirtc-options-plugin-test', { }]
23
+ ],
24
+ };
25
+ ```
26
+
27
+ ### Possible options :
28
+
29
+ | Parameter | Description | Default value |
30
+ | --- | --- | --- |
31
+ | enableMediaProjectionService | Can be used to deactivate screenSharing activation on Android | true |
32
+
33
+
34
+ ```js
35
+ module.exports = {
36
+ ...
37
+ plugins: [
38
+ ['@apizee/expo-apirtc-options-plugin-test', { enableMediaProjectionService: false }]
39
+ ],
40
+ };
41
+ ```
42
+
@@ -0,0 +1,143 @@
1
+ package com.reactnativeapirtc;
2
+ import com.facebook.react.bridge.NativeModule;
3
+ import com.facebook.react.bridge.ReactApplicationContext;
4
+ import com.facebook.react.bridge.ReactContext;
5
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
6
+ import com.facebook.react.bridge.ReactMethod;
7
+ import com.facebook.react.bridge.LifecycleEventListener;
8
+
9
+ import com.oney.WebRTCModule.WebRTCModule;
10
+
11
+ import com.facebook.react.modules.core.DeviceEventManagerModule;
12
+ import com.facebook.react.bridge.WritableMap;
13
+ import com.facebook.react.bridge.ReadableMap;
14
+ import com.facebook.react.bridge.Arguments;
15
+
16
+ import android.util.Log;
17
+
18
+ import java.util.Map;
19
+ import java.util.HashMap;
20
+
21
+ public class AppLifecycleModule extends ReactContextBaseJavaModule implements LifecycleEventListener {
22
+
23
+ private final ReactApplicationContext reactContext;
24
+ private String localStreamReactTag = null; // Variable to store the reactTag value
25
+ private String localStreamTrackId = null; // Variable to store the trackId value
26
+ // Variables to store the screen sharing reactTag and trackId
27
+ private String localScreenReactTag = null; // Variable to store the reactTag value
28
+ private String localScreenTrackId = null; // Variable to store the trackId value
29
+
30
+ AppLifecycleModule(ReactApplicationContext context) {
31
+ super(context);
32
+ this.reactContext = context;
33
+
34
+ // Add the listener for `lifecycleEvents`
35
+ reactContext.addLifecycleEventListener(this);
36
+ }
37
+
38
+ @Override
39
+ public String getName() {
40
+ return "AppLifecycleModule";
41
+ }
42
+
43
+ @Override
44
+ public void onHostResume() {
45
+ Log.d("AppLifecycleModule", "Activity onResume");
46
+ }
47
+
48
+ @Override
49
+ public void onHostPause() {
50
+ Log.d("AppLifecycleModule", "Activity onPause");
51
+ }
52
+
53
+ @Override
54
+ public void onHostDestroy() {
55
+ Log.d("AppLifecycleModule", "Activity onDestroy");
56
+ WritableMap params = Arguments.createMap();
57
+ params.putString("eventType", "onDestroy");
58
+
59
+ // Send the event to the application level
60
+ sendEventToApplicationLevel(reactContext, "liveCycleEvent", params);
61
+
62
+ //get acces to WebRTCModule
63
+ WebRTCModule mWebRTCModule = reactContext.getNativeModule(WebRTCModule.class);
64
+
65
+ if (mWebRTCModule != null && this.localStreamReactTag != null) {
66
+ Log.d("AppLifecycleModule", "mWebRTCModule.mediaStreamRelease called with localStreamReactTag: " + this.localStreamReactTag);
67
+ mWebRTCModule.mediaStreamRelease(this.localStreamReactTag);
68
+ } else {
69
+ Log.d("AppLifecycleModule", "WebRTCModule or this.localStreamReactTag is null");
70
+ }
71
+
72
+ if (mWebRTCModule != null && this.localScreenTrackId != null) {
73
+ Log.d("AppLifecycleModule", "mWebRTCModule.mediaStreamTrackRelease called with localScreenTrackId: " + this.localScreenTrackId);
74
+ mWebRTCModule.mediaStreamTrackRelease(this.localScreenTrackId);
75
+ //Using mediaStreamTrackRelease enbale to stop the screen sharing extension
76
+ } else {
77
+ Log.d("AppLifecycleModule", "WebRTCModule or this.localScreenTrackId is null");
78
+ }
79
+ }
80
+
81
+ @ReactMethod
82
+ public void sendInfoToAppLifecycleModule(ReadableMap params) {
83
+ Log.d("AppLifecycleModule", "sendInfoToAppLifecycleModule called with params: " + params);
84
+
85
+ // Handle the incoming params
86
+ String localStreamReactTagParam = params.getString("localStreamReactTag");
87
+ String localStreamTrackIdParam = params.getString("localStreamTrackId");
88
+ String localScreenReactTagParam = params.getString("localScreenReactTag");
89
+ String localScreenTrackIdParam = params.getString("localScreenTrackId");
90
+
91
+ if (localStreamReactTagParam != null) {
92
+ Log.d("AppLifecycleModule", "localStreamReactTag is not null: " + localStreamReactTagParam);
93
+ if (localStreamReactTagParam.equals("STOPPED")) {
94
+ this.localStreamReactTag = null; // Reset if "STOPPED"
95
+ } else {
96
+ this.localStreamReactTag = localStreamReactTagParam;
97
+ }
98
+ Log.d("AppLifecycleModule", "localStreamReactTag set to: " + this.localStreamReactTag);
99
+ } else {
100
+ Log.d("AppLifecycleModule", "localStreamReactTag is not modified");
101
+ }
102
+
103
+ if (localStreamTrackIdParam != null) {
104
+ Log.d("AppLifecycleModule", "localStreamTrackId is not null: " + localStreamTrackIdParam);
105
+ if (localStreamTrackIdParam.equals("STOPPED")) {
106
+ this.localStreamTrackId = null; // Reset if "STOPPED"
107
+ } else {
108
+ this.localStreamTrackId = localStreamTrackIdParam;
109
+ }
110
+ Log.d("AppLifecycleModule", "localStreamTrackId set to: " + this.localStreamTrackId);
111
+ } else {
112
+ Log.d("AppLifecycleModule", "localStreamTrackId is not modified");
113
+ }
114
+
115
+ if (localScreenReactTagParam != null) {
116
+ Log.d("AppLifecycleModule", "localScreenReactTag is not null: " + localScreenReactTagParam);
117
+ if (localScreenReactTagParam.equals("STOPPED")) {
118
+ this.localScreenReactTag = null; // Reset if "STOPPED"
119
+ } else {
120
+ this.localScreenReactTag = localScreenReactTagParam;
121
+ }
122
+ Log.d("AppLifecycleModule", "localScreenReactTag set to: " + this.localScreenReactTag);
123
+ } else {
124
+ Log.d("AppLifecycleModule", "localScreenReactTag is not modified");
125
+ }
126
+
127
+ if (localScreenTrackIdParam != null) {
128
+ Log.d("AppLifecycleModule", "localScreenTrackId is not null: " + localScreenTrackIdParam);
129
+ if (localScreenTrackIdParam.equals("STOPPED")) {
130
+ this.localScreenTrackId = null; // Reset if "STOPPED"
131
+ } else {
132
+ this.localScreenTrackId = localScreenTrackIdParam;
133
+ }
134
+ Log.d("AppLifecycleModule", "localScreenTrackId set to: " + this.localScreenTrackId);
135
+ } else {
136
+ Log.d("AppLifecycleModule", "localScreenTrackId is not modified");
137
+ }
138
+ }
139
+
140
+ private void sendEventToApplicationLevel(ReactContext reactContext, String eventName, WritableMap params) {
141
+ reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
142
+ }
143
+ }
@@ -0,0 +1,26 @@
1
+ package com.reactnativeapirtc;
2
+ import com.facebook.react.ReactPackage;
3
+ import com.facebook.react.bridge.NativeModule;
4
+ import com.facebook.react.bridge.ReactApplicationContext;
5
+ import com.facebook.react.uimanager.ViewManager;
6
+
7
+ import java.util.ArrayList;
8
+ import java.util.Collections;
9
+ import java.util.List;
10
+
11
+ public class AppLifecyclePackage implements ReactPackage {
12
+
13
+ @Override
14
+ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
15
+ return Collections.emptyList();
16
+ }
17
+
18
+ @Override
19
+ public List<NativeModule> createNativeModules(
20
+ ReactApplicationContext reactContext) {
21
+ List<NativeModule> modules = new ArrayList<>();
22
+
23
+ modules.add(new AppLifecycleModule(reactContext));
24
+ return modules;
25
+ }
26
+ }
@@ -0,0 +1,6 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ type PluginProps = {
3
+ enableMediaProjectionService?: boolean;
4
+ };
5
+ declare const withAndroidPlugin: ConfigPlugin<PluginProps>;
6
+ export default withAndroidPlugin;
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const config_plugins_1 = require("@expo/config-plugins");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const REQUIRED_PERMISSIONS = [
40
+ "android.permission.ACCESS_NETWORK_STATE",
41
+ "android.permission.CAMERA",
42
+ "android.permission.INTERNET",
43
+ "android.permission.MODIFY_AUDIO_SETTINGS",
44
+ "android.permission.RECORD_AUDIO",
45
+ "android.permission.SYSTEM_ALERT_WINDOW",
46
+ "android.permission.WAKE_LOCK",
47
+ "android.permission.BLUETOOTH",
48
+ "android.permission.FOREGROUND_SERVICE",
49
+ "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION",
50
+ "android.permission.FOREGROUND_SERVICE_MICROPHONE",
51
+ "android.permission.FOREGROUND_SERVICE_CAMERA"
52
+ ];
53
+ function getAndroidPackagePath(projectRoot) {
54
+ var _a, _b;
55
+ const manifestPath = path.join(projectRoot, 'android/app/src/main/AndroidManifest.xml');
56
+ let packageName;
57
+ if (fs.existsSync(manifestPath)) {
58
+ const manifestContent = fs.readFileSync(manifestPath, 'utf8');
59
+ const match = manifestContent.match(/package="([\w.]+)"/);
60
+ if (match) {
61
+ packageName = match[1];
62
+ }
63
+ }
64
+ // Fallback: lire dans app.json
65
+ if (!packageName) {
66
+ const appJsonPath = path.join(projectRoot, 'app.json');
67
+ if (fs.existsSync(appJsonPath)) {
68
+ const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf8'));
69
+ packageName = (_b = (_a = appJson === null || appJson === void 0 ? void 0 : appJson.expo) === null || _a === void 0 ? void 0 : _a.android) === null || _b === void 0 ? void 0 : _b.package;
70
+ }
71
+ }
72
+ if (!packageName) {
73
+ throw new Error('Package name not found in AndroidManifest.xml or app.json');
74
+ }
75
+ return path.join(projectRoot, 'android/app/src/main/java', ...packageName.split('.'));
76
+ }
77
+ function copyAndPatchJavaFiles(projectRoot) {
78
+ const srcDir = path.join(__dirname, 'static/java');
79
+ const destDir = getAndroidPackagePath(projectRoot);
80
+ // Récupère le packageName sous forme de string (ex: com.reactnativeapirtc)
81
+ const packageName = destDir
82
+ .replace(path.join(projectRoot, 'android/app/src/main/java') + path.sep, '')
83
+ .split(path.sep)
84
+ .join('.');
85
+ if (!fs.existsSync(destDir)) {
86
+ fs.mkdirSync(destDir, { recursive: true });
87
+ }
88
+ ['AppLifecycleModule.java', 'AppLifecyclePackage.java'].forEach(file => {
89
+ const src = path.join(srcDir, file);
90
+ const dest = path.join(destDir, file);
91
+ if (fs.existsSync(src)) {
92
+ let content = fs.readFileSync(src, 'utf8');
93
+ // Remplace la ligne de package par la bonne valeur
94
+ content = content.replace(/^package\s+[\w.]+;/m, `package ${packageName};`);
95
+ fs.writeFileSync(dest, content, 'utf8');
96
+ console.log(`Copied and patched ${file} to android sources with package: ${packageName}`);
97
+ }
98
+ });
99
+ }
100
+ const withAndroidPermissions = (config) => {
101
+ return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
102
+ const manifest = config.modResults.manifest;
103
+ if (!manifest['uses-permission']) {
104
+ manifest['uses-permission'] = [];
105
+ }
106
+ REQUIRED_PERMISSIONS.forEach((permission) => {
107
+ console.error('check manifest permission:', permission);
108
+ if (Array.isArray(manifest['uses-permission']) &&
109
+ !manifest['uses-permission'].some((item) => item.$ && item.$['android:name'] === permission)) {
110
+ manifest['uses-permission'].push({
111
+ $: { 'android:name': permission }
112
+ });
113
+ console.error(`Added permission: ${permission}`);
114
+ }
115
+ });
116
+ return config;
117
+ });
118
+ };
119
+ const withAndroidPlugin = (config, props) => {
120
+ console.error('withAndroidPlugin called with props:', props);
121
+ let updatedConfig = (0, config_plugins_1.withMainApplication)(config, (config) => {
122
+ const mainApplication = config.modResults;
123
+ if (props.enableMediaProjectionService === false) {
124
+ // If the feature is disabled, we can return early
125
+ console.warn('Media Projection Service is disabled, skipping modifications.');
126
+ return config;
127
+ }
128
+ // ✅ Ajouter import après la ligne "package com.xxx"
129
+ if (!mainApplication.contents.includes('import android.util.Log')) {
130
+ mainApplication.contents = mainApplication.contents.replace(/(package\s+[\w.]+(?:\r?\n))/, `$1\nimport com.oney.WebRTCModule.WebRTCModuleOptions\n`);
131
+ }
132
+ // ✅ Ajouter le code au début de onCreate()
133
+ const onCreateRegex = /override fun onCreate\(\) \{\n/;
134
+ const customCode = [
135
+ ' val options: WebRTCModuleOptions = WebRTCModuleOptions.getInstance()',
136
+ ' options.enableMediaProjectionService = true\n'
137
+ ].join('\n');
138
+ if (!mainApplication.contents.includes('WebRTCModuleOptions.getInstance()')) {
139
+ mainApplication.contents = mainApplication.contents.replace(onCreateRegex, match => `${match}${customCode}`);
140
+ }
141
+ // Ajout du package AppLifecyclePackage dans la liste (compatible Kotlin)
142
+ // Récupère le packageName automatiquement
143
+ const packageName = (() => {
144
+ try {
145
+ return getAndroidPackagePath(config.modRequest.projectRoot)
146
+ .replace(path.join(config.modRequest.projectRoot, 'android/app/src/main/java') + path.sep, '')
147
+ .split(path.sep)
148
+ .join('.');
149
+ }
150
+ catch (_a) {
151
+ return null;
152
+ }
153
+ })();
154
+ if (packageName) {
155
+ const importLine = `import ${packageName}.AppLifecyclePackage\n`;
156
+ if (!mainApplication.contents.includes(importLine.trim())) {
157
+ mainApplication.contents = mainApplication.contents.replace(/(package\s+[\w.]+(?:\r?\n))/, `$1${importLine}`);
158
+ }
159
+ }
160
+ if (!mainApplication.contents.includes('packages.add(AppLifecyclePackage())')) {
161
+ mainApplication.contents = mainApplication.contents.replace(/(val packages = PackageList\(this\)\.packages[\s\S]*?)(\n\s*return packages)/, (match, p1, p2) => {
162
+ return `${p1}\n packages.add(AppLifecyclePackage())${p2}`;
163
+ });
164
+ }
165
+ // Copie les fichiers Java natifs nécessaires pour AppLifecyclePackage
166
+ copyAndPatchJavaFiles(config.modRequest.projectRoot);
167
+ return config;
168
+ });
169
+ updatedConfig = withAndroidPermissions(updatedConfig);
170
+ return updatedConfig;
171
+ };
172
+ exports.default = withAndroidPlugin;
@@ -0,0 +1,6 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ type PluginProps = {
3
+ enableMediaProjectionService?: boolean;
4
+ };
5
+ export declare const withPlugin: ConfigPlugin<PluginProps>;
6
+ export default withPlugin;
@@ -0,0 +1,12 @@
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
+ exports.withPlugin = void 0;
7
+ const withAndroidPlugin_1 = __importDefault(require("./withAndroidPlugin"));
8
+ const withPlugin = (config, props = { enableMediaProjectionService: true }) => {
9
+ return (0, withAndroidPlugin_1.default)(config, props);
10
+ };
11
+ exports.withPlugin = withPlugin;
12
+ exports.default = exports.withPlugin;
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ const { withPlugin } = require('./build/withPlugin');
2
+
3
+ module.exports = function (config, props) {
4
+ return withPlugin(config, props);
5
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@apirtc/expo-apirtc-options-plugin",
3
+ "version": "0.0.1",
4
+ "description": "Plugin Expo pour ajouter les features apiRTC dans une application React Native",
5
+ "main": "index.js",
6
+ "keywords": [
7
+ "expo",
8
+ "config-plugin",
9
+ "react-native",
10
+ "webrtc",
11
+ "apiRTC"
12
+ ],
13
+ "author": "F.Luart",
14
+ "license": "MIT",
15
+ "dependencies": {
16
+ "@expo/config-plugins": "^10.1.2",
17
+ "@types/node": "^24.0.12",
18
+ "typescript": "^5.8.3"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc && npm run copy-static",
22
+ "copy-static": "mkdir -p ./build/static && cp -R ./plugin/static/* ./build/static"
23
+ },
24
+ "files": [
25
+ "index.js",
26
+ "build"
27
+ ],
28
+ "types": "./build/plugin/withWebRTCOptions.d.ts"
29
+ }