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 CHANGED
@@ -132,32 +132,28 @@ export default function App() {
132
132
 
133
133
  | Method | Platform | Description |
134
134
  | :--- | :--- | :--- |
135
- | **registerHeadlessTask(callback)** | Android | Registers background logic. `eventType` is `INCOMING_CALL`, `BUSY`, or `ABORTED_CALL`. |
136
- | **checkOverlayPermission()** | Android | Returns true if the app can draw over other apps (Required for overlay UI). |
137
- | **requestOverlayPermission()** | Android | Opens System Settings to let the user enable "Draw over other apps". |
138
- | **checkFullScreenPermission()** | Android | [Android 14+] Checks if the app is allowed to show full-screen intents on the lockscreen. |
139
- | **requestFullScreenSettings()** | Android | [Android 14+] Opens System Settings to enable the Full Screen Intent permission. |
140
- | **displayCall(uuid, name, type)** | Android | Shows the native full-screen call UI and starts the ringtone. |
141
- | **checkCallValidity(uuid)** | Android | Returns `{isValid, isCanceled}` based on persistent state and CANCEL signals. |
142
- | **checkCallStatus(uuid)** | Android | Returns `{isCanceled, isActive, shouldDisplay}` for UI synchronization. |
143
- | **reportRemoteEnded(uuid, reason)** | Android | Tells the native side the caller hung up so the UI can dismiss automatically. |
144
- | **destroyNativeCallUI(uuid)** | Android | Forcefully dismisses the native interface, stops audio, and clears the current call state. |
145
- | **getInitialCallData()** | Android | Returns the call payload if the app was cold-started by a notification interaction. |
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 Persistence:
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
- 3. iOS CallKit:
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
- 4. Single Call Gate:
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 checkFullScreenIntentPermission(promise: Promise) {
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "High-performance React Native module for handling native VoIP call UI on Android and iOS.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",