@voltkit-rtc/react-native 0.0.0-stage → 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/CHANGELOG.md +96 -0
- package/LICENSE +21 -0
- package/README.md +1606 -2
- package/android/build.gradle +70 -0
- package/android/consumer-rules.pro +21 -0
- package/android/src/main/AndroidManifest.xml +25 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/AppVisibility.kt +67 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/AudioDevices.kt +67 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/AudioRouter.kt +487 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/CallServiceTypes.kt +120 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/NativeTimers.kt +106 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/NetworkWatch.kt +120 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/OutputChoice.kt +65 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/RtcConfig.kt +83 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/ScoLink.kt +61 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitCallService.kt +287 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitRtcInitProvider.kt +215 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitRtcModule.kt +313 -0
- package/android/src/main/java/com/voltkit/rtc/reactnative/VoltkitRtcPackage.kt +30 -0
- package/android/src/main/res/drawable/voltkit_rtc_default_call_icon.xml +15 -0
- package/android/src/main/res/raw/voltkit_rtc_keep.xml +7 -0
- package/app.plugin.js +17 -0
- package/lib/commonjs/audio.js +215 -0
- package/lib/commonjs/audio.js.map +1 -0
- package/lib/commonjs/components.js +207 -0
- package/lib/commonjs/components.js.map +1 -0
- package/lib/commonjs/create-client.js +47 -0
- package/lib/commonjs/create-client.js.map +1 -0
- package/lib/commonjs/errors.js +99 -0
- package/lib/commonjs/errors.js.map +1 -0
- package/lib/commonjs/globals.js +61 -0
- package/lib/commonjs/globals.js.map +1 -0
- package/lib/commonjs/index.js +234 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/local-media.js +392 -0
- package/lib/commonjs/local-media.js.map +1 -0
- package/lib/commonjs/native-session.js +314 -0
- package/lib/commonjs/native-session.js.map +1 -0
- package/lib/commonjs/native.js +99 -0
- package/lib/commonjs/native.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/permissions.js +166 -0
- package/lib/commonjs/permissions.js.map +1 -0
- package/lib/commonjs/platform/android.js +126 -0
- package/lib/commonjs/platform/android.js.map +1 -0
- package/lib/commonjs/platform/common.js +138 -0
- package/lib/commonjs/platform/common.js.map +1 -0
- package/lib/commonjs/platform/devices.js +151 -0
- package/lib/commonjs/platform/devices.js.map +1 -0
- package/lib/commonjs/platform/index.js +35 -0
- package/lib/commonjs/platform/index.js.map +1 -0
- package/lib/commonjs/platform/ios.js +54 -0
- package/lib/commonjs/platform/ios.js.map +1 -0
- package/lib/commonjs/platform/streams.js +273 -0
- package/lib/commonjs/platform/streams.js.map +1 -0
- package/lib/commonjs/platform/timers.js +71 -0
- package/lib/commonjs/platform/timers.js.map +1 -0
- package/lib/commonjs/platform/visibility.js +60 -0
- package/lib/commonjs/platform/visibility.js.map +1 -0
- package/lib/commonjs/preflight.js +77 -0
- package/lib/commonjs/preflight.js.map +1 -0
- package/lib/commonjs/provider.js +73 -0
- package/lib/commonjs/provider.js.map +1 -0
- package/lib/commonjs/specs/NativeVoltkitRtc.js +29 -0
- package/lib/commonjs/specs/NativeVoltkitRtc.js.map +1 -0
- package/lib/module/audio.js +207 -0
- package/lib/module/audio.js.map +1 -0
- package/lib/module/components.js +200 -0
- package/lib/module/components.js.map +1 -0
- package/lib/module/create-client.js +42 -0
- package/lib/module/create-client.js.map +1 -0
- package/lib/module/errors.js +87 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/globals.js +53 -0
- package/lib/module/globals.js.map +1 -0
- package/lib/module/index.js +33 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/local-media.js +387 -0
- package/lib/module/local-media.js.map +1 -0
- package/lib/module/native-session.js +309 -0
- package/lib/module/native-session.js.map +1 -0
- package/lib/module/native.js +87 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/permissions.js +160 -0
- package/lib/module/permissions.js.map +1 -0
- package/lib/module/platform/android.js +122 -0
- package/lib/module/platform/android.js.map +1 -0
- package/lib/module/platform/common.js +128 -0
- package/lib/module/platform/common.js.map +1 -0
- package/lib/module/platform/devices.js +142 -0
- package/lib/module/platform/devices.js.map +1 -0
- package/lib/module/platform/index.js +24 -0
- package/lib/module/platform/index.js.map +1 -0
- package/lib/module/platform/ios.js +49 -0
- package/lib/module/platform/ios.js.map +1 -0
- package/lib/module/platform/streams.js +268 -0
- package/lib/module/platform/streams.js.map +1 -0
- package/lib/module/platform/timers.js +67 -0
- package/lib/module/platform/timers.js.map +1 -0
- package/lib/module/platform/visibility.js +54 -0
- package/lib/module/platform/visibility.js.map +1 -0
- package/lib/module/preflight.js +72 -0
- package/lib/module/preflight.js.map +1 -0
- package/lib/module/provider.js +68 -0
- package/lib/module/provider.js.map +1 -0
- package/lib/module/specs/NativeVoltkitRtc.js +37 -0
- package/lib/module/specs/NativeVoltkitRtc.js.map +1 -0
- package/lib/typescript/commonjs/audio.d.ts +58 -0
- package/lib/typescript/commonjs/audio.d.ts.map +1 -0
- package/lib/typescript/commonjs/components.d.ts +58 -0
- package/lib/typescript/commonjs/components.d.ts.map +1 -0
- package/lib/typescript/commonjs/create-client.d.ts +35 -0
- package/lib/typescript/commonjs/create-client.d.ts.map +1 -0
- package/lib/typescript/commonjs/errors.d.ts +34 -0
- package/lib/typescript/commonjs/errors.d.ts.map +1 -0
- package/lib/typescript/commonjs/globals.d.ts +16 -0
- package/lib/typescript/commonjs/globals.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +24 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/local-media.d.ts +86 -0
- package/lib/typescript/commonjs/local-media.d.ts.map +1 -0
- package/lib/typescript/commonjs/native-session.d.ts +38 -0
- package/lib/typescript/commonjs/native-session.d.ts.map +1 -0
- package/lib/typescript/commonjs/native.d.ts +18 -0
- package/lib/typescript/commonjs/native.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/permissions.d.ts +30 -0
- package/lib/typescript/commonjs/permissions.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/android.d.ts +4 -0
- package/lib/typescript/commonjs/platform/android.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/common.d.ts +101 -0
- package/lib/typescript/commonjs/platform/common.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/devices.d.ts +42 -0
- package/lib/typescript/commonjs/platform/devices.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/index.d.ts +6 -0
- package/lib/typescript/commonjs/platform/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/ios.d.ts +4 -0
- package/lib/typescript/commonjs/platform/ios.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/streams.d.ts +82 -0
- package/lib/typescript/commonjs/platform/streams.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/timers.d.ts +19 -0
- package/lib/typescript/commonjs/platform/timers.d.ts.map +1 -0
- package/lib/typescript/commonjs/platform/visibility.d.ts +11 -0
- package/lib/typescript/commonjs/platform/visibility.d.ts.map +1 -0
- package/lib/typescript/commonjs/preflight.d.ts +19 -0
- package/lib/typescript/commonjs/preflight.d.ts.map +1 -0
- package/lib/typescript/commonjs/provider.d.ts +35 -0
- package/lib/typescript/commonjs/provider.d.ts.map +1 -0
- package/lib/typescript/commonjs/specs/NativeVoltkitRtc.d.ts +157 -0
- package/lib/typescript/commonjs/specs/NativeVoltkitRtc.d.ts.map +1 -0
- package/lib/typescript/module/audio.d.ts +58 -0
- package/lib/typescript/module/audio.d.ts.map +1 -0
- package/lib/typescript/module/components.d.ts +58 -0
- package/lib/typescript/module/components.d.ts.map +1 -0
- package/lib/typescript/module/create-client.d.ts +35 -0
- package/lib/typescript/module/create-client.d.ts.map +1 -0
- package/lib/typescript/module/errors.d.ts +34 -0
- package/lib/typescript/module/errors.d.ts.map +1 -0
- package/lib/typescript/module/globals.d.ts +16 -0
- package/lib/typescript/module/globals.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +24 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/local-media.d.ts +86 -0
- package/lib/typescript/module/local-media.d.ts.map +1 -0
- package/lib/typescript/module/native-session.d.ts +38 -0
- package/lib/typescript/module/native-session.d.ts.map +1 -0
- package/lib/typescript/module/native.d.ts +18 -0
- package/lib/typescript/module/native.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/permissions.d.ts +30 -0
- package/lib/typescript/module/permissions.d.ts.map +1 -0
- package/lib/typescript/module/platform/android.d.ts +4 -0
- package/lib/typescript/module/platform/android.d.ts.map +1 -0
- package/lib/typescript/module/platform/common.d.ts +101 -0
- package/lib/typescript/module/platform/common.d.ts.map +1 -0
- package/lib/typescript/module/platform/devices.d.ts +42 -0
- package/lib/typescript/module/platform/devices.d.ts.map +1 -0
- package/lib/typescript/module/platform/index.d.ts +6 -0
- package/lib/typescript/module/platform/index.d.ts.map +1 -0
- package/lib/typescript/module/platform/ios.d.ts +4 -0
- package/lib/typescript/module/platform/ios.d.ts.map +1 -0
- package/lib/typescript/module/platform/streams.d.ts +82 -0
- package/lib/typescript/module/platform/streams.d.ts.map +1 -0
- package/lib/typescript/module/platform/timers.d.ts +19 -0
- package/lib/typescript/module/platform/timers.d.ts.map +1 -0
- package/lib/typescript/module/platform/visibility.d.ts +11 -0
- package/lib/typescript/module/platform/visibility.d.ts.map +1 -0
- package/lib/typescript/module/preflight.d.ts +19 -0
- package/lib/typescript/module/preflight.d.ts.map +1 -0
- package/lib/typescript/module/provider.d.ts +35 -0
- package/lib/typescript/module/provider.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeVoltkitRtc.d.ts +157 -0
- package/lib/typescript/module/specs/NativeVoltkitRtc.d.ts.map +1 -0
- package/package.json +140 -4
- package/plugin/build/android.d.ts +36 -0
- package/plugin/build/android.js +265 -0
- package/plugin/build/index.d.ts +9 -0
- package/plugin/build/index.js +54 -0
- package/plugin/build/ios.d.ts +4 -0
- package/plugin/build/ios.js +43 -0
- package/plugin/build/options.d.ts +93 -0
- package/plugin/build/options.js +140 -0
- package/react-native.config.js +11 -0
- package/src/audio.ts +221 -0
- package/src/components.tsx +233 -0
- package/src/create-client.ts +53 -0
- package/src/errors.ts +81 -0
- package/src/globals.ts +54 -0
- package/src/index.ts +95 -0
- package/src/local-media.ts +420 -0
- package/src/native-session.tsx +338 -0
- package/src/native.ts +84 -0
- package/src/permissions.ts +173 -0
- package/src/platform/android.ts +128 -0
- package/src/platform/common.ts +157 -0
- package/src/platform/devices.ts +148 -0
- package/src/platform/index.ts +22 -0
- package/src/platform/ios.ts +48 -0
- package/src/platform/streams.ts +285 -0
- package/src/platform/timers.ts +72 -0
- package/src/platform/visibility.ts +59 -0
- package/src/preflight.ts +60 -0
- package/src/provider.tsx +86 -0
- package/src/specs/NativeVoltkitRtc.ts +180 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withVoltkitAndroid = exports.META_SCREEN_SHARE = exports.META_HW_AEC = exports.META_AUDIO_MODE = exports.CALL_SERVICE_CLASS = void 0;
|
|
4
|
+
exports.blockedPermissions = blockedPermissions;
|
|
5
|
+
exports.planAndroid = planAndroid;
|
|
6
|
+
exports.setVoltkitManifest = setVoltkitManifest;
|
|
7
|
+
/**
|
|
8
|
+
* What the plugin writes into the app's AndroidManifest.xml (plan §5.5). The names are the ones the SDK's native side
|
|
9
|
+
* reads (android/src/main/java/com/voltkit/rtc/reactnative/):
|
|
10
|
+
*
|
|
11
|
+
* <uses-permission android:name="android.permission.CAMERA" /> unless android.camera is false
|
|
12
|
+
* <uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
13
|
+
* <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
14
|
+
* <uses-permission android:name="android.permission.INTERNET" />
|
|
15
|
+
* <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
16
|
+
* <!-- with the call service (android.callService, default on) -->
|
|
17
|
+
* <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
18
|
+
* <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
|
19
|
+
* <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
|
20
|
+
* <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
21
|
+
* <!-- for the permissions above, so Google Play does not read them as hardware the app cannot do without -->
|
|
22
|
+
* <uses-feature android:name="android.hardware.camera" android:required="false" /> with CAMERA
|
|
23
|
+
* <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
|
24
|
+
* <uses-feature android:name="android.hardware.camera.front" android:required="false" />
|
|
25
|
+
* <uses-feature android:name="android.hardware.microphone" android:required="false" /> with RECORD_AUDIO
|
|
26
|
+
* <application>
|
|
27
|
+
* <meta-data android:name="in.voltkit.rtc.AUDIO_MODE" android:value="communication" /> RtcConfig.kt
|
|
28
|
+
* <meta-data android:name="in.voltkit.rtc.HW_AEC" android:value="true" />
|
|
29
|
+
* <meta-data android:name="in.voltkit.rtc.SCREEN_SHARE" android:value="false" />
|
|
30
|
+
* <service android:name="com.voltkit.rtc.reactnative.VoltkitCallService" android:exported="false"
|
|
31
|
+
* android:foregroundServiceType="microphone|mediaPlayback" /> VoltkitCallService.kt
|
|
32
|
+
* </application>
|
|
33
|
+
*
|
|
34
|
+
* android.callServiceCamera adds the camera type and FOREGROUND_SERVICE_CAMERA; android.screenShare adds
|
|
35
|
+
* FOREGROUND_SERVICE_MEDIA_PROJECTION, SCREEN_SHARE=true and the mediaProjection type, and, without the call service,
|
|
36
|
+
* still FOREGROUND_SERVICE and POST_NOTIFICATIONS: react-native-webrtc's screen-capture service is a foreground service
|
|
37
|
+
* with a notification of its own. The call service's types are what it MAY take: CallServiceTypes.kt picks from them
|
|
38
|
+
* at run time (microphone when RECORD_AUDIO is granted, otherwise mediaPlayback; camera only with pauseCamera false;
|
|
39
|
+
* mediaProjection only while a screen share is live). POST_NOTIFICATIONS goes with any foreground service: from
|
|
40
|
+
* Android 13 its notification shows only once the user allows notifications (the service runs either way).
|
|
41
|
+
*
|
|
42
|
+
* The uses-feature entries: Google Play reads CAMERA as "needs a camera with autofocus" and RECORD_AUDIO as "needs a
|
|
43
|
+
* microphone" unless the manifest says otherwise, and hides the app from devices without them (tablets, TVs,
|
|
44
|
+
* Chromebooks, some car and kiosk devices): a voice-only app, or a listener, would vanish from them for a camera it
|
|
45
|
+
* never opens. The plugin marks each feature of the permissions it adds as not required (the front camera too, which
|
|
46
|
+
* calls use); a missing camera or microphone is reported at run time (media-unavailable). The app's own <uses-feature>
|
|
47
|
+
* entries, by android:name, are never changed, so an app that does require a camera keeps its android:required="true".
|
|
48
|
+
*
|
|
49
|
+
* The SDK's own library manifest has only INTERNET, ACCESS_NETWORK_STATE, MODIFY_AUDIO_SETTINGS and its init provider,
|
|
50
|
+
* so an app that turns the call service off takes on no foreground service at all.
|
|
51
|
+
*
|
|
52
|
+
* Never written, whatever the options: a Bluetooth permission (call audio routing needs none, as in the Android SDK),
|
|
53
|
+
* SYSTEM_ALERT_WINDOW (which the community react-native-webrtc plugin adds), VIBRATE or storage. Expo's template adds
|
|
54
|
+
* SYSTEM_ALERT_WINDOW, VIBRATE and the storage permissions to every app; they are not the plugin's, so it leaves them as
|
|
55
|
+
* they are, and the app drops them with "android.blockedPermissions" (PLAN_UPDATES 10; the docs show the list).
|
|
56
|
+
*
|
|
57
|
+
* The app's blocks win over the plugin: a permission in android.blockedPermissions (or already in the manifest with
|
|
58
|
+
* tools:node="remove") is never asked for again, and a call-service type that a block leaves unusable is not declared,
|
|
59
|
+
* with its FOREGROUND_SERVICE_* permission, e.g. a listen-only app that blocks RECORD_AUDIO gets a mediaPlayback-only
|
|
60
|
+
* service. Screen sharing goes the same way (SCREEN_SHARE=false, no mediaProjection type, a warning) when a block
|
|
61
|
+
* leaves react-native-webrtc's screen-capture service unable to run, i.e. FOREGROUND_SERVICE or, on Android 14 and
|
|
62
|
+
* later, FOREGROUND_SERVICE_MEDIA_PROJECTION: with SCREEN_SHARE=true the first share would crash the app (a
|
|
63
|
+
* SecurityException in that service's startForeground), where the SDK now raises screen-share-service-required.
|
|
64
|
+
* Expo's own blockedPermissions plugin runs before app plugins' manifest mods, so both orders are handled.
|
|
65
|
+
*
|
|
66
|
+
* GOOGLE PLAY. An app that targets Android 14 (API 34) or later and whose manifest declares foreground service types
|
|
67
|
+
* must declare each type in Play Console before it can publish (Monitor and improve > App content > Foreground service
|
|
68
|
+
* permissions; https://support.google.com/googleplay/android-developer/answer/13392821): for each type, the feature it
|
|
69
|
+
* serves, what happens to the user if the task is deferred or interrupted, and a link to a short video of a user
|
|
70
|
+
* starting it. With the defaults that is two types:
|
|
71
|
+
* - microphone: "Keeps the user's call audio going when they switch apps or turn the screen off during a call they
|
|
72
|
+
* started"; the video starts a call, goes to the home screen and keeps talking.
|
|
73
|
+
* - mediaPlayback: "Keeps a call audible for a listener who leaves the app"; the video joins as a listener, goes to
|
|
74
|
+
* the home screen and keeps listening.
|
|
75
|
+
* camera (android.callServiceCamera) and mediaProjection (android.screenShare; react-native-webrtc's
|
|
76
|
+
* MediaProjectionService declares it too) are declared the same way. android.callService: false declares no
|
|
77
|
+
* foreground service and needs no declaration, at the price of calls that drop about 6 s after the app leaves the screen
|
|
78
|
+
* (Android cuts a background app's network without one; Android 17 also blocks its background audio).
|
|
79
|
+
*/
|
|
80
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
81
|
+
const options_1 = require("./options");
|
|
82
|
+
/** The SDK's call service (VoltkitCallService.kt); not exported: only the app itself starts it. */
|
|
83
|
+
exports.CALL_SERVICE_CLASS = 'com.voltkit.rtc.reactnative.VoltkitCallService';
|
|
84
|
+
/** The meta-data the SDK's init provider reads once when the app starts (RtcConfig.kt). */
|
|
85
|
+
exports.META_AUDIO_MODE = 'in.voltkit.rtc.AUDIO_MODE';
|
|
86
|
+
exports.META_HW_AEC = 'in.voltkit.rtc.HW_AEC';
|
|
87
|
+
exports.META_SCREEN_SHARE = 'in.voltkit.rtc.SCREEN_SHARE';
|
|
88
|
+
const permission = (name) => `android.permission.${name}`;
|
|
89
|
+
const CAMERA = permission('CAMERA');
|
|
90
|
+
const RECORD_AUDIO = permission('RECORD_AUDIO');
|
|
91
|
+
const FOREGROUND_SERVICE = permission('FOREGROUND_SERVICE');
|
|
92
|
+
const FOREGROUND_SERVICE_MEDIA_PROJECTION = permission('FOREGROUND_SERVICE_MEDIA_PROJECTION');
|
|
93
|
+
const POST_NOTIFICATIONS = permission('POST_NOTIFICATIONS');
|
|
94
|
+
const CALL_PERMISSIONS = ['RECORD_AUDIO', 'MODIFY_AUDIO_SETTINGS', 'INTERNET', 'ACCESS_NETWORK_STATE'].map(permission);
|
|
95
|
+
/**
|
|
96
|
+
* The device features Google Play reads from a permission as required unless the manifest says otherwise
|
|
97
|
+
* (developer.android.com/guide/topics/manifest/uses-feature-element, "Permissions that imply feature requirements"),
|
|
98
|
+
* plus the front camera, which calls use.
|
|
99
|
+
*/
|
|
100
|
+
const FEATURES = [
|
|
101
|
+
{
|
|
102
|
+
permission: CAMERA,
|
|
103
|
+
features: ['android.hardware.camera', 'android.hardware.camera.autofocus', 'android.hardware.camera.front'],
|
|
104
|
+
},
|
|
105
|
+
{ permission: RECORD_AUDIO, features: ['android.hardware.microphone'] },
|
|
106
|
+
];
|
|
107
|
+
const SERVICE_TYPES = {
|
|
108
|
+
microphone: { permission: permission('FOREGROUND_SERVICE_MICROPHONE'), needs: [RECORD_AUDIO] },
|
|
109
|
+
mediaPlayback: { permission: permission('FOREGROUND_SERVICE_MEDIA_PLAYBACK'), needs: [] },
|
|
110
|
+
camera: { permission: permission('FOREGROUND_SERVICE_CAMERA'), needs: [CAMERA] },
|
|
111
|
+
mediaProjection: { permission: FOREGROUND_SERVICE_MEDIA_PROJECTION, needs: [] },
|
|
112
|
+
};
|
|
113
|
+
/** The permissions the app blocks: android.blockedPermissions, and entries with tools:node="remove" already there. */
|
|
114
|
+
function blockedPermissions(manifest, configBlocked = []) {
|
|
115
|
+
const blocked = new Set(configBlocked.map((name) => config_plugins_1.AndroidConfig.Permissions.ensurePermissionNameFormat(name)));
|
|
116
|
+
for (const entry of manifest.manifest['uses-permission'] ?? []) {
|
|
117
|
+
if (entry.$?.['tools:node'] === 'remove')
|
|
118
|
+
blocked.add(entry.$['android:name']);
|
|
119
|
+
}
|
|
120
|
+
return blocked;
|
|
121
|
+
}
|
|
122
|
+
function planAndroid(options, blocked) {
|
|
123
|
+
const usable = (...names) => names.every((name) => !blocked.has(name));
|
|
124
|
+
// react-native-webrtc's screen-capture service is a foreground service of type mediaProjection: without these it
|
|
125
|
+
// would crash the app at the first share (a SecurityException in its startForeground), so sharing stays off.
|
|
126
|
+
let screenShare = options.screenShare;
|
|
127
|
+
let screenShareDropped = null;
|
|
128
|
+
if (options.screenShare) {
|
|
129
|
+
const blocks = [FOREGROUND_SERVICE, FOREGROUND_SERVICE_MEDIA_PROJECTION].filter((name) => blocked.has(name));
|
|
130
|
+
if (blocks.length > 0) {
|
|
131
|
+
screenShare = false;
|
|
132
|
+
screenShareDropped = `android.blockedPermissions blocks ${blocks.join(' and ')}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let serviceTypes = [];
|
|
136
|
+
let serviceDropped = null;
|
|
137
|
+
if (options.callService) {
|
|
138
|
+
const wanted = ['microphone', 'mediaPlayback'];
|
|
139
|
+
if (options.callServiceCamera)
|
|
140
|
+
wanted.push('camera');
|
|
141
|
+
if (screenShare)
|
|
142
|
+
wanted.push('mediaProjection');
|
|
143
|
+
serviceTypes = wanted.filter((type) => usable(SERVICE_TYPES[type].permission, ...SERVICE_TYPES[type].needs));
|
|
144
|
+
if (!usable(FOREGROUND_SERVICE)) {
|
|
145
|
+
serviceDropped = `android.blockedPermissions blocks ${FOREGROUND_SERVICE}`;
|
|
146
|
+
}
|
|
147
|
+
else if (!serviceTypes.includes('microphone') && !serviceTypes.includes('mediaPlayback')) {
|
|
148
|
+
// CallServiceTypes.kt runs the service as microphone or mediaPlayback; camera or mediaProjection alone is no call.
|
|
149
|
+
const blocks = [RECORD_AUDIO, SERVICE_TYPES.microphone.permission, SERVICE_TYPES.mediaPlayback.permission].filter((name) => blocked.has(name));
|
|
150
|
+
serviceDropped = `android.blockedPermissions blocks ${blocks.join(', ')}, which leaves it neither the microphone nor the mediaPlayback type`;
|
|
151
|
+
}
|
|
152
|
+
if (serviceDropped)
|
|
153
|
+
serviceTypes = [];
|
|
154
|
+
}
|
|
155
|
+
// A foreground service of the app's: the call service, or react-native-webrtc's screen-capture service.
|
|
156
|
+
const foregroundService = serviceTypes.length > 0 || screenShare;
|
|
157
|
+
const wanted = [];
|
|
158
|
+
if (options.camera)
|
|
159
|
+
wanted.push(CAMERA);
|
|
160
|
+
wanted.push(...CALL_PERMISSIONS);
|
|
161
|
+
if (foregroundService)
|
|
162
|
+
wanted.push(FOREGROUND_SERVICE);
|
|
163
|
+
for (const type of serviceTypes)
|
|
164
|
+
wanted.push(SERVICE_TYPES[type].permission);
|
|
165
|
+
// react-native-webrtc's own screen-capture service needs it too, with or without the call service.
|
|
166
|
+
if (screenShare)
|
|
167
|
+
wanted.push(FOREGROUND_SERVICE_MEDIA_PROJECTION);
|
|
168
|
+
// Every foreground service has a notification, which Android 13 and later show only once the user allows them.
|
|
169
|
+
if (foregroundService)
|
|
170
|
+
wanted.push(POST_NOTIFICATIONS);
|
|
171
|
+
const permissions = [...new Set(wanted)].filter((name) => usable(name));
|
|
172
|
+
const features = FEATURES.flatMap((entry) => (permissions.includes(entry.permission) ? entry.features : []));
|
|
173
|
+
return { permissions, features, serviceTypes, serviceDropped, screenShare, screenShareDropped };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Marks these device features as not required, leaving the app's own <uses-feature> entries (by android:name) as they
|
|
177
|
+
* are. A manifest without any gets the list right before <application>, which the manifest must keep as its last
|
|
178
|
+
* child: xml2js writes the children in key order, and Expo's ensurePermissions appends a manifest's first
|
|
179
|
+
* <uses-permission> after <application>, so the rebuilt order ends with <application> whatever came before.
|
|
180
|
+
*/
|
|
181
|
+
function setUsesFeatures(manifest, names) {
|
|
182
|
+
const root = manifest.manifest;
|
|
183
|
+
const existing = root['uses-feature'];
|
|
184
|
+
const declared = new Set((existing ?? []).map((entry) => entry.$?.['android:name']));
|
|
185
|
+
const added = names
|
|
186
|
+
.filter((name) => !declared.has(name))
|
|
187
|
+
.map((name) => ({ $: { 'android:name': name, 'android:required': 'false' } }));
|
|
188
|
+
if (added.length === 0)
|
|
189
|
+
return;
|
|
190
|
+
if (existing) {
|
|
191
|
+
existing.push(...added);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const ordered = {};
|
|
195
|
+
for (const [key, value] of Object.entries(root)) {
|
|
196
|
+
if (key !== 'application')
|
|
197
|
+
ordered[key] = value;
|
|
198
|
+
}
|
|
199
|
+
ordered['uses-feature'] = added;
|
|
200
|
+
if ('application' in root)
|
|
201
|
+
ordered.application = root.application;
|
|
202
|
+
manifest.manifest = ordered;
|
|
203
|
+
}
|
|
204
|
+
/** Declares the call service with these types, in place (an earlier prebuild's entry is updated), or takes it out. */
|
|
205
|
+
function setCallService(application, types) {
|
|
206
|
+
const services = application.service ?? [];
|
|
207
|
+
const isCallService = (service) => service.$?.['android:name'] === exports.CALL_SERVICE_CLASS;
|
|
208
|
+
if (types.length === 0) {
|
|
209
|
+
const others = services.filter((service) => !isCallService(service));
|
|
210
|
+
if (others.length > 0)
|
|
211
|
+
application.service = others;
|
|
212
|
+
else
|
|
213
|
+
delete application.service;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const attributes = {
|
|
217
|
+
'android:name': exports.CALL_SERVICE_CLASS,
|
|
218
|
+
'android:exported': 'false',
|
|
219
|
+
'android:foregroundServiceType': types.join('|'),
|
|
220
|
+
};
|
|
221
|
+
let written = false;
|
|
222
|
+
const next = [];
|
|
223
|
+
for (const service of services) {
|
|
224
|
+
if (!isCallService(service))
|
|
225
|
+
next.push(service);
|
|
226
|
+
else if (!written) {
|
|
227
|
+
next.push({ ...service, $: { ...service.$, ...attributes } });
|
|
228
|
+
written = true;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (!written)
|
|
232
|
+
next.push({ $: attributes });
|
|
233
|
+
application.service = next;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Writes the permissions, the device features, the meta-data and the call service for these options into the manifest.
|
|
237
|
+
* Running it again on its own output changes nothing; after an option changes, the meta-data and the service are
|
|
238
|
+
* updated in place.
|
|
239
|
+
*/
|
|
240
|
+
function setVoltkitManifest(manifest, options, configBlocked = []) {
|
|
241
|
+
const plan = planAndroid(options, blockedPermissions(manifest, configBlocked));
|
|
242
|
+
// ensurePermissions skips a name already listed, including a tools:node="remove" block.
|
|
243
|
+
config_plugins_1.AndroidConfig.Permissions.ensurePermissions(manifest, plan.permissions);
|
|
244
|
+
setUsesFeatures(manifest, plan.features);
|
|
245
|
+
const application = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);
|
|
246
|
+
config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(application, exports.META_AUDIO_MODE, options.audioMode);
|
|
247
|
+
config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(application, exports.META_HW_AEC, String(options.hardwareEchoCancellation));
|
|
248
|
+
config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(application, exports.META_SCREEN_SHARE, String(plan.screenShare));
|
|
249
|
+
setCallService(application, plan.serviceTypes);
|
|
250
|
+
if (plan.serviceDropped) {
|
|
251
|
+
config_plugins_1.WarningAggregator.addWarningAndroid(`${options_1.PLUGIN_NAME} android.callService`, `VoltkitCallService is not declared: ${plan.serviceDropped}. Calls will drop a few seconds after the app leaves ` +
|
|
252
|
+
'the screen; unblock those permissions, or set android.callService to false to silence this.');
|
|
253
|
+
}
|
|
254
|
+
if (plan.screenShareDropped) {
|
|
255
|
+
config_plugins_1.WarningAggregator.addWarningAndroid(`${options_1.PLUGIN_NAME} android.screenShare`, `screen sharing is off (${exports.META_SCREEN_SHARE}=false): ${plan.screenShareDropped}, which react-native-webrtc's ` +
|
|
256
|
+
'screen-capture service needs; with it on, the first share would crash the app. Unblock those permissions, or ' +
|
|
257
|
+
'set android.screenShare to false to silence this.');
|
|
258
|
+
}
|
|
259
|
+
return manifest;
|
|
260
|
+
}
|
|
261
|
+
const withVoltkitAndroid = (config, options) => (0, config_plugins_1.withAndroidManifest)(config, (modConfig) => {
|
|
262
|
+
modConfig.modResults = setVoltkitManifest(modConfig.modResults, options, modConfig.android?.blockedPermissions ?? []);
|
|
263
|
+
return modConfig;
|
|
264
|
+
});
|
|
265
|
+
exports.withVoltkitAndroid = withVoltkitAndroid;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
+
import type { VoltkitRtcPluginProps } from './options';
|
|
3
|
+
declare const _default: ConfigPlugin<void | VoltkitRtcPluginProps | null>;
|
|
4
|
+
export default _default;
|
|
5
|
+
export { CALL_SERVICE_CLASS, META_AUDIO_MODE, META_HW_AEC, META_SCREEN_SHARE, blockedPermissions, planAndroid, setVoltkitManifest, withVoltkitAndroid, } from './android';
|
|
6
|
+
export type { AndroidPlan } from './android';
|
|
7
|
+
export { setVoltkitInfoPlist, withVoltkitIos } from './ios';
|
|
8
|
+
export { DEFAULT_CAMERA_PERMISSION, DEFAULT_MICROPHONE_PERMISSION, PLUGIN_NAME, resolveOptions } from './options';
|
|
9
|
+
export type { AndroidOptions, AudioMode, IosOptions, PluginOptions, VoltkitRtcAndroidProps, VoltkitRtcIosProps, VoltkitRtcPluginProps, } from './options';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveOptions = exports.PLUGIN_NAME = exports.DEFAULT_MICROPHONE_PERMISSION = exports.DEFAULT_CAMERA_PERMISSION = exports.withVoltkitIos = exports.setVoltkitInfoPlist = exports.withVoltkitAndroid = exports.setVoltkitManifest = exports.planAndroid = exports.blockedPermissions = exports.META_SCREEN_SHARE = exports.META_HW_AEC = exports.META_AUDIO_MODE = exports.CALL_SERVICE_CLASS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The Expo config plugin of @voltkit-rtc/react-native (plan §5.5). An app lists it in app.json:
|
|
6
|
+
*
|
|
7
|
+
* "plugins": [["@voltkit-rtc/react-native", { "android": { "callService": true } }]]
|
|
8
|
+
*
|
|
9
|
+
* and `npx expo prebuild` (or `npx expo run:android`) writes the permissions, the call service and the meta-data the SDK
|
|
10
|
+
* reads into AndroidManifest.xml (./android.ts), and the usage strings and background modes into Info.plist (./ios.ts):
|
|
11
|
+
* the app edits no native file. Self-sufficient: no @config-plugins/react-native-webrtc, whose table stops at Expo 56
|
|
12
|
+
* and which adds SYSTEM_ALERT_WINDOW. Options are checked when the plugin is applied (./options.ts).
|
|
13
|
+
*
|
|
14
|
+
* Expo finds it through app.plugin.js at the package root, which loads plugin/build (this file, built by
|
|
15
|
+
* `npm run build:plugin`). It runs once per app, under the package's name, even if app.json lists it twice.
|
|
16
|
+
*/
|
|
17
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
18
|
+
const android_1 = require("./android");
|
|
19
|
+
const ios_1 = require("./ios");
|
|
20
|
+
const options_1 = require("./options");
|
|
21
|
+
/** The package's version, for Expo's plugin history (`expo config` shows it); from src/ and build/ alike. */
|
|
22
|
+
function packageVersion() {
|
|
23
|
+
try {
|
|
24
|
+
const version = require('../../package.json').version;
|
|
25
|
+
return typeof version === 'string' ? version : undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const withVoltkitRtc = (config, props) => {
|
|
32
|
+
const options = (0, options_1.resolveOptions)(props);
|
|
33
|
+
config = (0, android_1.withVoltkitAndroid)(config, options.android);
|
|
34
|
+
config = (0, ios_1.withVoltkitIos)(config, options.ios);
|
|
35
|
+
return config;
|
|
36
|
+
};
|
|
37
|
+
exports.default = (0, config_plugins_1.createRunOncePlugin)(withVoltkitRtc, options_1.PLUGIN_NAME, packageVersion());
|
|
38
|
+
var android_2 = require("./android");
|
|
39
|
+
Object.defineProperty(exports, "CALL_SERVICE_CLASS", { enumerable: true, get: function () { return android_2.CALL_SERVICE_CLASS; } });
|
|
40
|
+
Object.defineProperty(exports, "META_AUDIO_MODE", { enumerable: true, get: function () { return android_2.META_AUDIO_MODE; } });
|
|
41
|
+
Object.defineProperty(exports, "META_HW_AEC", { enumerable: true, get: function () { return android_2.META_HW_AEC; } });
|
|
42
|
+
Object.defineProperty(exports, "META_SCREEN_SHARE", { enumerable: true, get: function () { return android_2.META_SCREEN_SHARE; } });
|
|
43
|
+
Object.defineProperty(exports, "blockedPermissions", { enumerable: true, get: function () { return android_2.blockedPermissions; } });
|
|
44
|
+
Object.defineProperty(exports, "planAndroid", { enumerable: true, get: function () { return android_2.planAndroid; } });
|
|
45
|
+
Object.defineProperty(exports, "setVoltkitManifest", { enumerable: true, get: function () { return android_2.setVoltkitManifest; } });
|
|
46
|
+
Object.defineProperty(exports, "withVoltkitAndroid", { enumerable: true, get: function () { return android_2.withVoltkitAndroid; } });
|
|
47
|
+
var ios_2 = require("./ios");
|
|
48
|
+
Object.defineProperty(exports, "setVoltkitInfoPlist", { enumerable: true, get: function () { return ios_2.setVoltkitInfoPlist; } });
|
|
49
|
+
Object.defineProperty(exports, "withVoltkitIos", { enumerable: true, get: function () { return ios_2.withVoltkitIos; } });
|
|
50
|
+
var options_2 = require("./options");
|
|
51
|
+
Object.defineProperty(exports, "DEFAULT_CAMERA_PERMISSION", { enumerable: true, get: function () { return options_2.DEFAULT_CAMERA_PERMISSION; } });
|
|
52
|
+
Object.defineProperty(exports, "DEFAULT_MICROPHONE_PERMISSION", { enumerable: true, get: function () { return options_2.DEFAULT_MICROPHONE_PERMISSION; } });
|
|
53
|
+
Object.defineProperty(exports, "PLUGIN_NAME", { enumerable: true, get: function () { return options_2.PLUGIN_NAME; } });
|
|
54
|
+
Object.defineProperty(exports, "resolveOptions", { enumerable: true, get: function () { return options_2.resolveOptions; } });
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ConfigPlugin, InfoPlist } from 'expo/config-plugins';
|
|
2
|
+
import type { IosOptions } from './options';
|
|
3
|
+
export declare function setVoltkitInfoPlist(plist: InfoPlist, options: IosOptions): InfoPlist;
|
|
4
|
+
export declare const withVoltkitIos: ConfigPlugin<IosOptions>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withVoltkitIos = void 0;
|
|
4
|
+
exports.setVoltkitInfoPlist = setVoltkitInfoPlist;
|
|
5
|
+
/**
|
|
6
|
+
* What the plugin writes into the app's Info.plist (plan §5.5; the SDK's iOS side is a preview, JavaScript only):
|
|
7
|
+
*
|
|
8
|
+
* NSCameraUsageDescription cameraPermission, else the app's own text, else a short default
|
|
9
|
+
* NSMicrophoneUsageDescription microphonePermission, else the app's own text, else a short default
|
|
10
|
+
* UIBackgroundModes + "audio" (ios.backgroundAudio, default on), + "voip" (ios.voip, default off)
|
|
11
|
+
*
|
|
12
|
+
* It only adds: the app's other background modes stay, none is listed twice, and running it again changes nothing.
|
|
13
|
+
*/
|
|
14
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
15
|
+
const options_1 = require("./options");
|
|
16
|
+
function usageText(given, current, fallback) {
|
|
17
|
+
if (given !== undefined)
|
|
18
|
+
return given;
|
|
19
|
+
if (typeof current === 'string' && current.trim() !== '')
|
|
20
|
+
return current;
|
|
21
|
+
return fallback;
|
|
22
|
+
}
|
|
23
|
+
function setVoltkitInfoPlist(plist, options) {
|
|
24
|
+
plist.NSCameraUsageDescription = usageText(options.cameraPermission, plist.NSCameraUsageDescription, options_1.DEFAULT_CAMERA_PERMISSION);
|
|
25
|
+
plist.NSMicrophoneUsageDescription = usageText(options.microphonePermission, plist.NSMicrophoneUsageDescription, options_1.DEFAULT_MICROPHONE_PERMISSION);
|
|
26
|
+
const modes = Array.isArray(plist.UIBackgroundModes) ? [...plist.UIBackgroundModes] : [];
|
|
27
|
+
const add = (mode) => {
|
|
28
|
+
if (!modes.includes(mode))
|
|
29
|
+
modes.push(mode);
|
|
30
|
+
};
|
|
31
|
+
if (options.backgroundAudio)
|
|
32
|
+
add('audio');
|
|
33
|
+
if (options.voip)
|
|
34
|
+
add('voip');
|
|
35
|
+
if (modes.length > 0)
|
|
36
|
+
plist.UIBackgroundModes = modes;
|
|
37
|
+
return plist;
|
|
38
|
+
}
|
|
39
|
+
const withVoltkitIos = (config, options) => (0, config_plugins_1.withInfoPlist)(config, (modConfig) => {
|
|
40
|
+
modConfig.modResults = setVoltkitInfoPlist(modConfig.modResults, options);
|
|
41
|
+
return modConfig;
|
|
42
|
+
});
|
|
43
|
+
exports.withVoltkitIos = withVoltkitIos;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Expo config plugin's options (plan §5.5), as an app writes them in app.json or app.config.ts:
|
|
3
|
+
*
|
|
4
|
+
* "plugins": [
|
|
5
|
+
* ["@voltkit-rtc/react-native", {
|
|
6
|
+
* "cameraPermission": "Your camera is shown to the people in your call.",
|
|
7
|
+
* "microphonePermission": "Your microphone is heard by the people in your call.",
|
|
8
|
+
* "android": { "callService": true, "screenShare": false },
|
|
9
|
+
* "ios": { "backgroundAudio": true }
|
|
10
|
+
* }]
|
|
11
|
+
* ]
|
|
12
|
+
*
|
|
13
|
+
* Every option is optional. resolveOptions checks them when the plugin is applied, before any native file is written,
|
|
14
|
+
* and fills in the defaults; an unknown name or a wrong type stops prebuild with a message that names the option, so a
|
|
15
|
+
* typo never falls back to a default without a word.
|
|
16
|
+
*/
|
|
17
|
+
export declare const PLUGIN_NAME = "@voltkit-rtc/react-native";
|
|
18
|
+
/** How the app plays call audio (meta-data in.voltkit.rtc.AUDIO_MODE; RtcConfig.kt). */
|
|
19
|
+
export type AudioMode = 'communication' | 'media';
|
|
20
|
+
export interface VoltkitRtcAndroidProps {
|
|
21
|
+
/**
|
|
22
|
+
* "communication" (default): call audio, echo cancellation, the earpiece, the loudspeaker and headsets. "media", for
|
|
23
|
+
* an app whose users only ever listen: the media volume and Bluetooth's high-quality profile, no earpiece. Fixed for
|
|
24
|
+
* the life of the app's process; never "media" where anyone may speak.
|
|
25
|
+
*/
|
|
26
|
+
audioMode?: AudioMode;
|
|
27
|
+
/**
|
|
28
|
+
* Declares CAMERA (default true), with the camera, its autofocus and the front camera marked as hardware the app
|
|
29
|
+
* can do without (uses-feature android:required="false", so Google Play does not hide the app from devices without
|
|
30
|
+
* them). false for an app that never publishes video.
|
|
31
|
+
*/
|
|
32
|
+
camera?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Declares the call service, VoltkitCallService, with the types microphone and mediaPlayback, and its permissions:
|
|
35
|
+
* FOREGROUND_SERVICE, FOREGROUND_SERVICE_MICROPHONE, FOREGROUND_SERVICE_MEDIA_PLAYBACK and POST_NOTIFICATIONS
|
|
36
|
+
* (default true, plan open decision 1). It keeps a call's network, microphone and sound going in the background and
|
|
37
|
+
* with the screen off. false declares no foreground service: Google Play then needs no foreground-service
|
|
38
|
+
* declaration, but a call drops a few seconds after the app leaves the screen.
|
|
39
|
+
*/
|
|
40
|
+
callService?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Adds the camera type to the call service and FOREGROUND_SERVICE_CAMERA (default false), for an app that keeps the
|
|
43
|
+
* camera on in the background (`background={{ pauseCamera: false }}` on the provider). Needs camera and callService.
|
|
44
|
+
*/
|
|
45
|
+
callServiceCamera?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Turns on react-native-webrtc's screen-capture service (meta-data in.voltkit.rtc.SCREEN_SHARE) and declares
|
|
48
|
+
* FOREGROUND_SERVICE_MEDIA_PROJECTION, plus the mediaProjection type on the call service, which takes it only while
|
|
49
|
+
* a share is live (default false). That service is a foreground service with a notification, so without the call
|
|
50
|
+
* service it still brings FOREGROUND_SERVICE and POST_NOTIFICATIONS. Stays off, with a warning, when
|
|
51
|
+
* android.blockedPermissions blocks what it needs.
|
|
52
|
+
*/
|
|
53
|
+
screenShare?: boolean;
|
|
54
|
+
/** The phone's own echo canceller and noise suppressor, where it has them (meta-data in.voltkit.rtc.HW_AEC; default true). */
|
|
55
|
+
hardwareEchoCancellation?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface VoltkitRtcIosProps {
|
|
58
|
+
/** Adds `audio` to UIBackgroundModes, so a call keeps its sound in the background (default true). */
|
|
59
|
+
backgroundAudio?: boolean;
|
|
60
|
+
/** Adds `voip` to UIBackgroundModes, for an app that integrates CallKit itself (default false). */
|
|
61
|
+
voip?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface VoltkitRtcPluginProps {
|
|
64
|
+
/** The iOS camera prompt (NSCameraUsageDescription). Default: the app's own text, or a short one. */
|
|
65
|
+
cameraPermission?: string;
|
|
66
|
+
/** The iOS microphone prompt (NSMicrophoneUsageDescription). Default: the app's own text, or a short one. */
|
|
67
|
+
microphonePermission?: string;
|
|
68
|
+
android?: VoltkitRtcAndroidProps;
|
|
69
|
+
ios?: VoltkitRtcIosProps;
|
|
70
|
+
}
|
|
71
|
+
export interface AndroidOptions {
|
|
72
|
+
audioMode: AudioMode;
|
|
73
|
+
camera: boolean;
|
|
74
|
+
callService: boolean;
|
|
75
|
+
callServiceCamera: boolean;
|
|
76
|
+
screenShare: boolean;
|
|
77
|
+
hardwareEchoCancellation: boolean;
|
|
78
|
+
}
|
|
79
|
+
export interface IosOptions {
|
|
80
|
+
/** Absent: keep the Info.plist's own text, or write the default. */
|
|
81
|
+
cameraPermission?: string;
|
|
82
|
+
microphonePermission?: string;
|
|
83
|
+
backgroundAudio: boolean;
|
|
84
|
+
voip: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface PluginOptions {
|
|
87
|
+
android: AndroidOptions;
|
|
88
|
+
ios: IosOptions;
|
|
89
|
+
}
|
|
90
|
+
export declare const DEFAULT_CAMERA_PERMISSION = "Allow $(PRODUCT_NAME) to use your camera in calls.";
|
|
91
|
+
export declare const DEFAULT_MICROPHONE_PERMISSION = "Allow $(PRODUCT_NAME) to use your microphone in calls.";
|
|
92
|
+
/** The app's options, checked, with every default filled in. */
|
|
93
|
+
export declare function resolveOptions(props: unknown): PluginOptions;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The Expo config plugin's options (plan §5.5), as an app writes them in app.json or app.config.ts:
|
|
4
|
+
*
|
|
5
|
+
* "plugins": [
|
|
6
|
+
* ["@voltkit-rtc/react-native", {
|
|
7
|
+
* "cameraPermission": "Your camera is shown to the people in your call.",
|
|
8
|
+
* "microphonePermission": "Your microphone is heard by the people in your call.",
|
|
9
|
+
* "android": { "callService": true, "screenShare": false },
|
|
10
|
+
* "ios": { "backgroundAudio": true }
|
|
11
|
+
* }]
|
|
12
|
+
* ]
|
|
13
|
+
*
|
|
14
|
+
* Every option is optional. resolveOptions checks them when the plugin is applied, before any native file is written,
|
|
15
|
+
* and fills in the defaults; an unknown name or a wrong type stops prebuild with a message that names the option, so a
|
|
16
|
+
* typo never falls back to a default without a word.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.DEFAULT_MICROPHONE_PERMISSION = exports.DEFAULT_CAMERA_PERMISSION = exports.PLUGIN_NAME = void 0;
|
|
20
|
+
exports.resolveOptions = resolveOptions;
|
|
21
|
+
exports.PLUGIN_NAME = '@voltkit-rtc/react-native';
|
|
22
|
+
exports.DEFAULT_CAMERA_PERMISSION = 'Allow $(PRODUCT_NAME) to use your camera in calls.';
|
|
23
|
+
exports.DEFAULT_MICROPHONE_PERMISSION = 'Allow $(PRODUCT_NAME) to use your microphone in calls.';
|
|
24
|
+
const AUDIO_MODES = ['communication', 'media'];
|
|
25
|
+
const ANDROID_DEFAULTS = {
|
|
26
|
+
audioMode: 'communication',
|
|
27
|
+
camera: true,
|
|
28
|
+
callService: true,
|
|
29
|
+
callServiceCamera: false,
|
|
30
|
+
screenShare: false,
|
|
31
|
+
hardwareEchoCancellation: true,
|
|
32
|
+
};
|
|
33
|
+
const IOS_DEFAULTS = { backgroundAudio: true, voip: false };
|
|
34
|
+
const TOP_KEYS = ['cameraPermission', 'microphonePermission', 'android', 'ios'];
|
|
35
|
+
const ANDROID_KEYS = Object.keys(ANDROID_DEFAULTS);
|
|
36
|
+
const IOS_KEYS = Object.keys(IOS_DEFAULTS);
|
|
37
|
+
/** Every option by its full name, for "did you mean" when one is in the wrong place or misspelt. */
|
|
38
|
+
const FULL_NAMES = [
|
|
39
|
+
'cameraPermission',
|
|
40
|
+
'microphonePermission',
|
|
41
|
+
...ANDROID_KEYS.map((key) => `android.${key}`),
|
|
42
|
+
...IOS_KEYS.map((key) => `ios.${key}`),
|
|
43
|
+
];
|
|
44
|
+
function fail(problem) {
|
|
45
|
+
throw new Error(`${exports.PLUGIN_NAME} config plugin: ${problem}${/[.?]$/.test(problem) ? '' : '.'}`);
|
|
46
|
+
}
|
|
47
|
+
function isObject(value) {
|
|
48
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
function show(value) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
return 'undefined';
|
|
53
|
+
if (Array.isArray(value))
|
|
54
|
+
return 'an array';
|
|
55
|
+
if (typeof value === 'object' && value !== null)
|
|
56
|
+
return 'an object';
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
/** Refuses a name this scope does not have, pointing at the right one when it is elsewhere or differs only in case. */
|
|
60
|
+
function checkKeys(props, known, scope) {
|
|
61
|
+
for (const key of Object.keys(props)) {
|
|
62
|
+
if (known.includes(key))
|
|
63
|
+
continue;
|
|
64
|
+
const name = scope ? `${scope}.${key}` : key;
|
|
65
|
+
const near = FULL_NAMES.find((full) => full.split('.').pop()?.toLowerCase() === key.toLowerCase());
|
|
66
|
+
if (near)
|
|
67
|
+
fail(`unknown option "${name}"; did you mean "${near}"?`);
|
|
68
|
+
fail(`unknown option "${name}"; the ${scope ? `${scope} options are` : 'options are'} ${known.join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function bool(props, key, scope, fallback) {
|
|
72
|
+
if (!(key in props) || props[key] === undefined)
|
|
73
|
+
return fallback;
|
|
74
|
+
const value = props[key];
|
|
75
|
+
if (typeof value !== 'boolean')
|
|
76
|
+
fail(`${scope}.${key} must be true or false, not ${show(value)}`);
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
function text(props, key, what) {
|
|
80
|
+
if (!(key in props) || props[key] === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
const value = props[key];
|
|
83
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
84
|
+
fail(`${key} must be a non-empty string (${what}), not ${show(value)}`);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function section(props, key) {
|
|
88
|
+
const value = props[key];
|
|
89
|
+
if (value === undefined)
|
|
90
|
+
return {};
|
|
91
|
+
if (!isObject(value))
|
|
92
|
+
fail(`${key} must be an object, like "${key}": { ... }, not ${show(value)}`);
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
/** The app's options, checked, with every default filled in. */
|
|
96
|
+
function resolveOptions(props) {
|
|
97
|
+
if (props === undefined || props === null)
|
|
98
|
+
props = {};
|
|
99
|
+
if (!isObject(props)) {
|
|
100
|
+
fail(`its options must be an object, like ["${exports.PLUGIN_NAME}", { "android": { "callService": true } }], not ${show(props)}`);
|
|
101
|
+
}
|
|
102
|
+
checkKeys(props, TOP_KEYS, '');
|
|
103
|
+
const androidProps = section(props, 'android');
|
|
104
|
+
checkKeys(androidProps, ANDROID_KEYS, 'android');
|
|
105
|
+
const iosProps = section(props, 'ios');
|
|
106
|
+
checkKeys(iosProps, IOS_KEYS, 'ios');
|
|
107
|
+
let audioMode = ANDROID_DEFAULTS.audioMode;
|
|
108
|
+
if (androidProps.audioMode !== undefined) {
|
|
109
|
+
const value = androidProps.audioMode;
|
|
110
|
+
if (!AUDIO_MODES.includes(value)) {
|
|
111
|
+
fail(`android.audioMode must be ${AUDIO_MODES.map((mode) => `"${mode}"`).join(' or ')}, not ${show(value)}`);
|
|
112
|
+
}
|
|
113
|
+
audioMode = value;
|
|
114
|
+
}
|
|
115
|
+
const android = {
|
|
116
|
+
audioMode,
|
|
117
|
+
camera: bool(androidProps, 'camera', 'android', ANDROID_DEFAULTS.camera),
|
|
118
|
+
callService: bool(androidProps, 'callService', 'android', ANDROID_DEFAULTS.callService),
|
|
119
|
+
callServiceCamera: bool(androidProps, 'callServiceCamera', 'android', ANDROID_DEFAULTS.callServiceCamera),
|
|
120
|
+
screenShare: bool(androidProps, 'screenShare', 'android', ANDROID_DEFAULTS.screenShare),
|
|
121
|
+
hardwareEchoCancellation: bool(androidProps, 'hardwareEchoCancellation', 'android', ANDROID_DEFAULTS.hardwareEchoCancellation),
|
|
122
|
+
};
|
|
123
|
+
if (android.callServiceCamera && !android.callService) {
|
|
124
|
+
fail('android.callServiceCamera needs android.callService: the camera type belongs to the call service');
|
|
125
|
+
}
|
|
126
|
+
if (android.callServiceCamera && !android.camera) {
|
|
127
|
+
fail('android.callServiceCamera needs android.camera: a camera foreground service needs the CAMERA permission');
|
|
128
|
+
}
|
|
129
|
+
const ios = {
|
|
130
|
+
backgroundAudio: bool(iosProps, 'backgroundAudio', 'ios', IOS_DEFAULTS.backgroundAudio),
|
|
131
|
+
voip: bool(iosProps, 'voip', 'ios', IOS_DEFAULTS.voip),
|
|
132
|
+
};
|
|
133
|
+
const cameraPermission = text(props, 'cameraPermission', "the iOS camera prompt's text");
|
|
134
|
+
const microphonePermission = text(props, 'microphonePermission', "the iOS microphone prompt's text");
|
|
135
|
+
if (cameraPermission !== undefined)
|
|
136
|
+
ios.cameraPermission = cameraPermission;
|
|
137
|
+
if (microphonePermission !== undefined)
|
|
138
|
+
ios.microphonePermission = microphonePermission;
|
|
139
|
+
return { android, ios };
|
|
140
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// How the React Native CLI and Expo autolink this package.
|
|
2
|
+
// Android: the TurboModule (VoltkitRtcPackage) and the init provider in android/. The codegen spec is
|
|
3
|
+
// src/specs/NativeVoltkitRtc.ts (package.json codegenConfig); the app runs codegen for it at build time.
|
|
4
|
+
// iOS: JavaScript only in 0.1.0, with no podspec, so there is nothing to link.
|
|
5
|
+
module.exports = {
|
|
6
|
+
dependency: {
|
|
7
|
+
platforms: {
|
|
8
|
+
ios: null,
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
};
|