rns-nativecall 1.2.0 → 1.2.2
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 +14 -18
- package/android/src/main/java/com/rnsnativecall/CallModule.kt +1 -1
- package/index.d.ts +5 -5
- package/index.js +40 -34
- package/ios/CallModule.m +66 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,32 +132,28 @@ export default function App() {
|
|
|
132
132
|
|
|
133
133
|
| Method | Platform | Description |
|
|
134
134
|
| :--- | :--- | :--- |
|
|
135
|
-
| **registerHeadlessTask(callback)** |
|
|
136
|
-
| **
|
|
137
|
-
| **
|
|
138
|
-
| **
|
|
139
|
-
| **
|
|
140
|
-
| **
|
|
141
|
-
| **
|
|
142
|
-
| **
|
|
143
|
-
| **
|
|
144
|
-
| **
|
|
145
|
-
| **
|
|
146
|
-
| **subscribe(onAccept, onReject, onFailed)** | Android | Listens for native Answer/Decline button presses and system errors. |
|
|
135
|
+
| **registerHeadlessTask(callback)** | All | Registers the background task. `eventType` is 'INCOMING_CALL', 'BUSY', or 'ABORTED_CALL'. |
|
|
136
|
+
| **displayCall(uuid, name, type)** | All | Android: Launches full-screen Activity. iOS: Reports incoming call to CallKit. |
|
|
137
|
+
| **checkCallValidity(uuid)** | All | Android only: Returns {isValid, isCanceled} to prevent ghost/canceled calls. |
|
|
138
|
+
| **checkCallStatus(uuid)** | All | Android only: Returns {isCanceled, isActive, shouldDisplay} for UI syncing. |
|
|
139
|
+
| **checkOverlayPermission()** | Android | Android only: Returns true if app can draw over other apps while unlocked. |
|
|
140
|
+
| **checkFullScreenPermission()** | Android | Android 14+: Checks if app can trigger full-screen intents on lockscreen. |
|
|
141
|
+
| **requestOverlayPermission()** | Android | Android only: Navigates user to "Draw over other apps" system settings. |
|
|
142
|
+
| **requestFullScreenSettings()** | Android | Android 14+: Navigates user to "Full Screen Intent" system settings. |
|
|
143
|
+
| **destroyNativeCallUI(uuid)** | All | Android: Stops ringtone/Activity. iOS: Ends CallKit (Handoff vs Hangup logic). |
|
|
144
|
+
| **getInitialCallData()** | All | Retrieves the call payload if the app was cold-started via a notification Answer. |
|
|
145
|
+
| **subscribe(onAccept, onReject, onFailed)** | All | Listens for Answer/Decline button presses and system-level bridge errors. |
|
|
147
146
|
---
|
|
148
147
|
|
|
149
148
|
# Implementation Notes
|
|
150
149
|
|
|
151
|
-
1. Android
|
|
152
|
-
Because this library uses a Foreground Service on Android, the notification will persist and show a "Call Pill" in the status bar. To remove this after the call ends or connects, you MUST call 'CallHandler.stopForegroundService()'.
|
|
153
|
-
|
|
154
|
-
2. Android Overlay:
|
|
150
|
+
1. Android Overlay:
|
|
155
151
|
For your React Native call screen to show up when the phone is locked, the user must grant the "Overlay Permission". Use 'checkOverlayPermission()' and 'requestOverlayPermission()' during your app's onboarding or call initiation.
|
|
156
152
|
|
|
157
|
-
|
|
153
|
+
2. iOS CallKit:
|
|
158
154
|
On iOS, 'displayCall' uses the native system CallKit UI. This works automatically in the background and on the lockscreen without extra overlay permissions.
|
|
159
155
|
|
|
160
|
-
|
|
156
|
+
3. Single Call Gate:
|
|
161
157
|
The library automatically prevents multiple overlapping native UIs. If a call is already active, subsequent calls will trigger the 'BUSY' event in your Headless Task.
|
|
162
158
|
---
|
|
163
159
|
|
|
@@ -96,7 +96,7 @@ class CallModule(
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
@ReactMethod
|
|
99
|
-
fun
|
|
99
|
+
fun checkFullScreenPermission(promise: Promise) {
|
|
100
100
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
101
101
|
val notificationManager = reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
102
102
|
promise.resolve(notificationManager.canUseFullScreenIntent())
|
package/index.d.ts
CHANGED
|
@@ -75,6 +75,11 @@ export interface CallHandlerType {
|
|
|
75
75
|
*/
|
|
76
76
|
checkOverlayPermission(): Promise<boolean>;
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Checks if the app has permission to use full screen intent.
|
|
80
|
+
*/
|
|
81
|
+
checkFullScreenPermission(): Promise<boolean>;
|
|
82
|
+
|
|
78
83
|
/**
|
|
79
84
|
* Opens system settings to request the "Draw over other apps" (Overlay) permission.
|
|
80
85
|
* Note: Returns a promise that resolves to false if module is missing.
|
|
@@ -86,11 +91,6 @@ export interface CallHandlerType {
|
|
|
86
91
|
*/
|
|
87
92
|
requestFullScreenSettings(): Promise<boolean | void>;
|
|
88
93
|
|
|
89
|
-
/**
|
|
90
|
-
* Reports to the native side that the remote party has terminated the call.
|
|
91
|
-
*/
|
|
92
|
-
reportRemoteEnded(uuid: string, reason?: string): Promise<void>;
|
|
93
|
-
|
|
94
94
|
/**
|
|
95
95
|
* Forcefully dismisses the native call UI, stops the ringtone, and clears state.
|
|
96
96
|
*/
|
package/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
NativeModules,
|
|
3
3
|
NativeEventEmitter,
|
|
4
4
|
AppRegistry,
|
|
5
|
+
Platform,
|
|
5
6
|
} from 'react-native';
|
|
6
7
|
|
|
7
8
|
const { CallModule } = NativeModules;
|
|
@@ -36,38 +37,6 @@ export const CallHandler = {
|
|
|
36
37
|
});
|
|
37
38
|
},
|
|
38
39
|
|
|
39
|
-
// --- Added missing bridge methods ---
|
|
40
|
-
checkCallValidity: async (uuid) => {
|
|
41
|
-
if (!CallModule?.checkCallValidity) return { isValid: false, isCanceled: true };
|
|
42
|
-
return await CallModule.checkCallValidity(uuid.toLowerCase().trim());
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
checkCallStatus: async (uuid) => {
|
|
46
|
-
if (!CallModule?.checkCallStatus) return { isCanceled: true, isActive: false, shouldDisplay: false };
|
|
47
|
-
return await CallModule.checkCallStatus(uuid.toLowerCase().trim());
|
|
48
|
-
},
|
|
49
|
-
// ------------------------------------
|
|
50
|
-
|
|
51
|
-
reportRemoteEnded: async (uuid, endReason) => {
|
|
52
|
-
if (!CallModule?.reportRemoteEnded) return;
|
|
53
|
-
await CallModule.reportRemoteEnded(uuid.toLowerCase().trim(), endReason);
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
requestOverlayPermission: async () => {
|
|
57
|
-
if (!CallModule?.requestOverlayPermission) return false;
|
|
58
|
-
return await CallModule.requestOverlayPermission();
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
requestFullScreenSettings: async () => {
|
|
62
|
-
if (!CallModule?.requestFullScreenSettings) return false;
|
|
63
|
-
return await CallModule.requestFullScreenSettings();
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
checkOverlayPermission: async () => {
|
|
67
|
-
if (!CallModule?.checkOverlayPermission) return false;
|
|
68
|
-
return await CallModule.checkOverlayPermission();
|
|
69
|
-
},
|
|
70
|
-
|
|
71
40
|
displayCall: async (uuid, name, callType = "audio") => {
|
|
72
41
|
if (!CallModule) return false;
|
|
73
42
|
return await CallModule.displayIncomingCall(uuid.toLowerCase().trim(), name, callType);
|
|
@@ -79,7 +48,6 @@ export const CallHandler = {
|
|
|
79
48
|
}
|
|
80
49
|
},
|
|
81
50
|
|
|
82
|
-
|
|
83
51
|
getInitialCallData: async () => {
|
|
84
52
|
if (!CallModule?.getInitialCallData) return null;
|
|
85
53
|
const data = await CallModule.getInitialCallData();
|
|
@@ -98,7 +66,45 @@ export const CallHandler = {
|
|
|
98
66
|
subs.push(callEventEmitter.addListener('onCallFailed', onFailed));
|
|
99
67
|
}
|
|
100
68
|
return () => subs.forEach(s => s.remove());
|
|
101
|
-
}
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
checkCallValidity: async (uuid) => {
|
|
72
|
+
if (!CallModule?.checkCallValidity) return { isValid: false, isCanceled: true };
|
|
73
|
+
return await CallModule.checkCallValidity(uuid.toLowerCase().trim());
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
checkCallStatus: async (uuid) => {
|
|
77
|
+
if (!CallModule?.checkCallStatus) return { isCanceled: true, isActive: false, shouldDisplay: false };
|
|
78
|
+
return await CallModule.checkCallStatus(uuid.toLowerCase().trim());
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
//--------------------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
requestOverlayPermission: async () => {
|
|
84
|
+
if (Platform.OS === 'ios') return true;
|
|
85
|
+
if (!CallModule?.requestOverlayPermission) return false;
|
|
86
|
+
return await CallModule.requestOverlayPermission();
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
requestFullScreenSettings: async () => {
|
|
90
|
+
if (Platform.OS === 'ios') return true;
|
|
91
|
+
if (!CallModule?.requestFullScreenSettings) return false;
|
|
92
|
+
return await CallModule.requestFullScreenSettings();
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
checkOverlayPermission: async () => {
|
|
96
|
+
if (Platform.OS === 'ios') return true;
|
|
97
|
+
if (!CallModule?.checkOverlayPermission) return false;
|
|
98
|
+
return await CallModule.checkOverlayPermission();
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
checkFullScreenPermission: async () => {
|
|
102
|
+
if (Platform.OS === 'ios') return true;
|
|
103
|
+
if (!CallModule?.checkFullScreenPermission) return false;
|
|
104
|
+
return await CallModule.checkFullScreenPermission();
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
//--------------------------------------------------------------------------------------
|
|
102
108
|
};
|
|
103
109
|
|
|
104
110
|
export default CallHandler;
|
package/ios/CallModule.m
CHANGED
|
@@ -137,6 +137,72 @@ RCT_EXPORT_METHOD(getInitialCallData:(RCTPromiseResolveBlock)resolve reject:(RCT
|
|
|
137
137
|
resolve(result.count > 0 ? result : [NSNull null]);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
RCT_EXPORT_METHOD(checkCallValidity:(NSString *)uuidString
|
|
141
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
142
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
143
|
+
{
|
|
144
|
+
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
145
|
+
if (!uuid) {
|
|
146
|
+
resolve(@{@"isValid": @NO, @"isCanceled": @YES});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// On iOS, we check if the call is currently known to the system observer
|
|
151
|
+
CXCallObserver *callObserver = [[CXCallObserver alloc] init];
|
|
152
|
+
BOOL found = NO;
|
|
153
|
+
|
|
154
|
+
for (CXCall *call in callObserver.calls) {
|
|
155
|
+
if ([call.uuid.UUIDString isEqualToString:uuid.UUIDString]) {
|
|
156
|
+
if (!call.hasEnded) {
|
|
157
|
+
found = YES;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// isValid: True if CallKit is currently tracking this call
|
|
164
|
+
// isCanceled: False if the call is active and valid
|
|
165
|
+
resolve(@{
|
|
166
|
+
@"isValid": @(found),
|
|
167
|
+
@"isCanceled": @(!found)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
RCT_EXPORT_METHOD(checkCallStatus:(NSString *)uuidString
|
|
172
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
173
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
174
|
+
{
|
|
175
|
+
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
176
|
+
if (!uuid) {
|
|
177
|
+
resolve(@{
|
|
178
|
+
@"isCanceled": @YES,
|
|
179
|
+
@"isActive": @NO,
|
|
180
|
+
@"shouldDisplay": @NO
|
|
181
|
+
});
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
CXCallObserver *callObserver = [[CXCallObserver alloc] init];
|
|
186
|
+
BOOL isActive = NO;
|
|
187
|
+
BOOL isCanceled = YES;
|
|
188
|
+
|
|
189
|
+
for (CXCall *call in callObserver.calls) {
|
|
190
|
+
if ([call.uuid.UUIDString isEqualToString:uuid.UUIDString]) {
|
|
191
|
+
if (!call.hasEnded) {
|
|
192
|
+
isActive = YES;
|
|
193
|
+
isCanceled = NO;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
resolve(@{
|
|
200
|
+
@"isCanceled": @(isCanceled),
|
|
201
|
+
@"isActive": @(isActive),
|
|
202
|
+
@"shouldDisplay": @(isActive && !isCanceled)
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
140
206
|
// MARK: - CXProviderDelegate
|
|
141
207
|
|
|
142
208
|
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
|