rns-nativecall 1.1.9 → 1.2.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 +11 -10
- package/android/src/main/java/com/rnsnativecall/CallModule.kt +2 -2
- package/android/src/main/java/com/rnsnativecall/CallPackage.kt +3 -8
- package/index.d.ts +11 -39
- package/index.js +44 -59
- package/ios/CallModule.m +66 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,16 +132,17 @@ 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
|
-
|
|
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. |
|
|
145
146
|
---
|
|
146
147
|
|
|
147
148
|
# Implementation Notes
|
|
@@ -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())
|
|
@@ -107,7 +107,7 @@ class CallModule(
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
@ReactMethod
|
|
110
|
-
fun
|
|
110
|
+
fun requestFullScreenSettings() {
|
|
111
111
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
112
112
|
try {
|
|
113
113
|
val intent =
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/////Users/bush/Desktop/Apps/Raiidr/package/android/src/main/java/com/rnsnativecall/CallPackage.kt
|
|
2
1
|
package com.rnsnativecall
|
|
3
2
|
|
|
4
3
|
import com.facebook.react.ReactPackage
|
|
@@ -7,11 +6,7 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
7
6
|
import com.facebook.react.uimanager.ViewManager
|
|
8
7
|
|
|
9
8
|
class CallPackage : ReactPackage {
|
|
10
|
-
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule>
|
|
11
|
-
return listOf(CallModule(reactContext))
|
|
12
|
-
}
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = listOf(CallModule(reactContext))
|
|
13
10
|
|
|
14
|
-
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>>
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
}
|
|
11
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
|
|
12
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -48,7 +48,6 @@ export interface CallHandlerType {
|
|
|
48
48
|
/**
|
|
49
49
|
* Registers the background "Headless" task.
|
|
50
50
|
* This is the bridge between a Native push notification and your React Native UI.
|
|
51
|
-
* @param callback Async function receiving call data and the event type.
|
|
52
51
|
*/
|
|
53
52
|
registerHeadlessTask(
|
|
54
53
|
callback: (data: CallData, eventType: CallEventType) => Promise<void>
|
|
@@ -56,11 +55,6 @@ export interface CallHandlerType {
|
|
|
56
55
|
|
|
57
56
|
/**
|
|
58
57
|
* Displays the full-screen Native Incoming Call UI.
|
|
59
|
-
* Usually called inside registerHeadlessTask after validating the call.
|
|
60
|
-
* @param uuid The unique call ID.
|
|
61
|
-
* @param name Name to display on the call screen.
|
|
62
|
-
* @param callType Defaults to 'audio'.
|
|
63
|
-
* @returns Promise resolving to true if the UI was successfully launched.
|
|
64
58
|
*/
|
|
65
59
|
displayCall(
|
|
66
60
|
uuid: string,
|
|
@@ -69,69 +63,47 @@ export interface CallHandlerType {
|
|
|
69
63
|
): Promise<boolean>;
|
|
70
64
|
|
|
71
65
|
/** * Checks if a call is still valid and has not been canceled.
|
|
72
|
-
* Useful for Splash screen "Interceptors" to prevent ghost calls.
|
|
73
66
|
*/
|
|
74
67
|
checkCallValidity(uuid: string): Promise<ValidityStatus>;
|
|
75
68
|
|
|
76
69
|
/** * Detailed check of a call's status.
|
|
77
|
-
* Useful for determining if the UI should remain visible.
|
|
78
70
|
*/
|
|
79
71
|
checkCallStatus(uuid: string): Promise<CallStatus>;
|
|
80
72
|
|
|
81
73
|
/**
|
|
82
|
-
*
|
|
83
|
-
* This is required for the call to show over the lockscreen.
|
|
84
|
-
*/
|
|
85
|
-
checkFullScreenPermission(): Promise<boolean>;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* [Android 14+] Opens the specific system settings page for the user to
|
|
89
|
-
* manually enable "Full Screen Intent" if it was revoked.
|
|
74
|
+
* Checks if the app has permission to draw over other apps (Overlay).
|
|
90
75
|
*/
|
|
91
|
-
|
|
76
|
+
checkOverlayPermission(): Promise<boolean>;
|
|
92
77
|
|
|
93
78
|
/**
|
|
94
|
-
* Checks if the app has permission to
|
|
95
|
-
* Necessary for showing the "Pill" or answer buttons while the phone is unlocked.
|
|
79
|
+
* Checks if the app has permission to use full screen intent.
|
|
96
80
|
*/
|
|
97
|
-
|
|
81
|
+
checkFullScreenPermission(): Promise<boolean>;
|
|
98
82
|
|
|
99
83
|
/**
|
|
100
84
|
* Opens system settings to request the "Draw over other apps" (Overlay) permission.
|
|
85
|
+
* Note: Returns a promise that resolves to false if module is missing.
|
|
101
86
|
*/
|
|
102
|
-
requestOverlayPermission(): void
|
|
87
|
+
requestOverlayPermission(): Promise<boolean | void>;
|
|
103
88
|
|
|
104
89
|
/**
|
|
105
|
-
*
|
|
106
|
-
* @param uuid The unique call ID.
|
|
107
|
-
* @param reason Custom string reason (e.g., "RemoteEnded", "TimedOut").
|
|
90
|
+
* [Android 14+] Opens the specific system settings page for Full Screen Intent.
|
|
108
91
|
*/
|
|
109
|
-
|
|
92
|
+
requestFullScreenSettings(): Promise<boolean | void>;
|
|
110
93
|
|
|
111
94
|
/**
|
|
112
95
|
* Forcefully dismisses the native call UI, stops the ringtone, and clears state.
|
|
113
|
-
* Use this when the call is hung up or timed out.
|
|
114
96
|
*/
|
|
115
97
|
destroyNativeCallUI(uuid: string): void;
|
|
116
98
|
|
|
117
99
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* remove the persistent system notification.
|
|
121
|
-
*/
|
|
122
|
-
stopForegroundService(): Promise<void>;
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Retrieves call data if the app was launched directly from an interaction
|
|
126
|
-
* with a notification while the app was in a "Killed" state.
|
|
100
|
+
* Retrieves call data if the app was launched directly from an interaction.
|
|
101
|
+
* Returns the 'default' payload or the raw data object.
|
|
127
102
|
*/
|
|
128
103
|
getInitialCallData(): Promise<any | null>;
|
|
129
104
|
|
|
130
105
|
/**
|
|
131
106
|
* Subscribes to user interactions on the Native Call UI.
|
|
132
|
-
* @param onAccept Callback when user presses the Answer button.
|
|
133
|
-
* @param onReject Callback when user presses the Decline button.
|
|
134
|
-
* @param onFailed Callback for system errors (optional).
|
|
135
107
|
* @returns A cleanup function to unsubscribe.
|
|
136
108
|
*/
|
|
137
109
|
subscribe(
|
|
@@ -144,5 +116,5 @@ export interface CallHandlerType {
|
|
|
144
116
|
/**
|
|
145
117
|
* Main handler for Native VoIP Call interactions.
|
|
146
118
|
*/
|
|
147
|
-
|
|
119
|
+
export const CallHandler: CallHandlerType;
|
|
148
120
|
export default CallHandler;
|
package/index.js
CHANGED
|
@@ -8,12 +8,8 @@ import {
|
|
|
8
8
|
const { CallModule } = NativeModules;
|
|
9
9
|
const callEventEmitter = CallModule ? new NativeEventEmitter(CallModule) : null;
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
export const CallHandler = {
|
|
12
|
-
/**
|
|
13
|
-
* REGISTER HEADLESS TASK
|
|
14
|
-
* This is the "Engine" that listens for incoming push notifications
|
|
15
|
-
* while the app is killed or in the background.
|
|
16
|
-
*/
|
|
17
13
|
registerHeadlessTask: (onAction) => {
|
|
18
14
|
AppRegistry.registerHeadlessTask('ColdStartCallTask', () => async (data) => {
|
|
19
15
|
const { callUuid, isBusySignal } = data;
|
|
@@ -25,73 +21,53 @@ export const CallHandler = {
|
|
|
25
21
|
return;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
//
|
|
24
|
+
// CallModule.checkCallValidity is now accessible via the native bridge
|
|
29
25
|
const status = await CallModule.checkCallValidity(uuid);
|
|
30
26
|
if (!status.isValid) {
|
|
31
27
|
if (onAction) await onAction(data, 'ABORTED_CALL');
|
|
32
28
|
return;
|
|
33
29
|
}
|
|
34
30
|
|
|
35
|
-
if (onAction)
|
|
31
|
+
if (onAction) {
|
|
32
|
+
await onAction(data, 'INCOMING_CALL');
|
|
33
|
+
}
|
|
36
34
|
} catch (error) {
|
|
37
35
|
console.error('[RNSNativeCall] Headless Task Error:', error);
|
|
38
36
|
}
|
|
39
37
|
});
|
|
40
38
|
},
|
|
41
39
|
|
|
42
|
-
// --- PERMISSION HEALTH CHECKS ---
|
|
43
|
-
|
|
44
|
-
checkFullScreenPermission: async () => {
|
|
45
|
-
if (Platform.OS !== 'android' || !CallModule?.checkFullScreenIntentPermission) return true;
|
|
46
|
-
return await CallModule.checkFullScreenIntentPermission();
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
openFullScreenSettings: () => {
|
|
50
|
-
if (Platform.OS === 'android' && CallModule?.openFullScreenIntentSettings) {
|
|
51
|
-
CallModule.openFullScreenIntentSettings();
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
checkOverlayPermission: async () => {
|
|
56
|
-
if (Platform.OS !== 'android' || !CallModule?.checkOverlayPermission) return true;
|
|
57
|
-
return await CallModule.checkOverlayPermission();
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
requestFullScreenSettings: () => {
|
|
61
|
-
if (Platform.OS === 'android' && CallModule?.requestOverlayPermission) {
|
|
62
|
-
CallModule.requestOverlayPermission();
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
// --- CALL LIFECYCLE ACTIONS ---
|
|
67
|
-
|
|
68
40
|
displayCall: async (uuid, name, callType = "audio") => {
|
|
69
41
|
if (!CallModule) return false;
|
|
70
42
|
return await CallModule.displayIncomingCall(uuid.toLowerCase().trim(), name, callType);
|
|
71
43
|
},
|
|
72
44
|
|
|
73
|
-
/** Stops the UI and the Foreground Service */
|
|
74
45
|
destroyNativeCallUI: (uuid) => {
|
|
75
46
|
if (CallModule?.endNativeCall) {
|
|
76
47
|
CallModule.endNativeCall(uuid.toLowerCase().trim());
|
|
77
48
|
}
|
|
78
49
|
},
|
|
79
50
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
51
|
+
getInitialCallData: async () => {
|
|
52
|
+
if (!CallModule?.getInitialCallData) return null;
|
|
53
|
+
const data = await CallModule.getInitialCallData();
|
|
54
|
+
|
|
55
|
+
// Return the 'default' call data if it exists, otherwise the whole object
|
|
56
|
+
return data?.default || data;
|
|
85
57
|
},
|
|
86
58
|
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
|
|
59
|
+
subscribe: (onAccept, onReject, onFailed) => {
|
|
60
|
+
if (!callEventEmitter) return () => { };
|
|
61
|
+
const subs = [
|
|
62
|
+
callEventEmitter.addListener('onCallAccepted', (data) => onAccept(data)),
|
|
63
|
+
callEventEmitter.addListener('onCallRejected', (data) => onReject(data)),
|
|
64
|
+
];
|
|
65
|
+
if (onFailed) {
|
|
66
|
+
subs.push(callEventEmitter.addListener('onCallFailed', onFailed));
|
|
90
67
|
}
|
|
68
|
+
return () => subs.forEach(s => s.remove());
|
|
91
69
|
},
|
|
92
70
|
|
|
93
|
-
// --- STATE VERIFICATION ---
|
|
94
|
-
|
|
95
71
|
checkCallValidity: async (uuid) => {
|
|
96
72
|
if (!CallModule?.checkCallValidity) return { isValid: false, isCanceled: true };
|
|
97
73
|
return await CallModule.checkCallValidity(uuid.toLowerCase().trim());
|
|
@@ -102,24 +78,33 @@ export const CallHandler = {
|
|
|
102
78
|
return await CallModule.checkCallStatus(uuid.toLowerCase().trim());
|
|
103
79
|
},
|
|
104
80
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
81
|
+
//--------------------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
requestOverlayPermission: async () => {
|
|
84
|
+
if (Platform.OS === 'ios') return true;
|
|
85
|
+
if (!CallModule?.requestOverlayPermission) return false;
|
|
86
|
+
return await CallModule.requestOverlayPermission();
|
|
108
87
|
},
|
|
109
88
|
|
|
110
|
-
|
|
89
|
+
requestFullScreenSettings: async () => {
|
|
90
|
+
if (Platform.OS === 'ios') return true;
|
|
91
|
+
if (!CallModule?.requestFullScreenSettings) return false;
|
|
92
|
+
return await CallModule.requestFullScreenSettings();
|
|
93
|
+
},
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return
|
|
122
|
-
}
|
|
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
|
+
//--------------------------------------------------------------------------------------
|
|
123
108
|
};
|
|
124
109
|
|
|
125
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 {
|