rns-nativecall 0.2.3 → 0.2.4

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/index.d.ts CHANGED
@@ -32,6 +32,8 @@ export interface CallHandlerType {
32
32
  */
33
33
  destroyNativeCallUI(uuid: string): void;
34
34
 
35
+
36
+ getInitialCallData(): Promise<CallData | null>;
35
37
  /**
36
38
  * Subscribe to call events. Automatically checks for cold-start data
37
39
  * if the app was opened via the Answer button.
package/index.js CHANGED
@@ -1,50 +1,16 @@
1
1
  import {
2
2
  NativeModules,
3
3
  NativeEventEmitter,
4
- PermissionsAndroid,
5
- Platform,
6
4
  } from 'react-native';
7
5
 
8
6
  const { CallModule } = NativeModules;
9
7
 
10
- if (!CallModule && __DEV__) {
11
- console.warn(
12
- "rns-nativecall: NativeModule.CallModule is undefined. " +
13
- "Make sure you have rebuilt your native project."
14
- );
15
- }
16
-
17
8
  const callEventEmitter = CallModule ? new NativeEventEmitter(CallModule) : null;
18
9
 
19
- const REQUIRED_PERMISSIONS = Platform.OS === 'android' ? [
20
- PermissionsAndroid.PERMISSIONS.READ_PHONE_NUMBERS,
21
- PermissionsAndroid.PERMISSIONS.CALL_PHONE,
22
- ] : [];
23
-
24
- export async function ensureAndroidPermissions() {
25
- if (Platform.OS !== 'android') return true;
26
- try {
27
- const result = await PermissionsAndroid.requestMultiple(REQUIRED_PERMISSIONS);
28
- const isAccountEnabled = await CallModule.checkTelecomPermissions();
29
- if (!isAccountEnabled) return false;
30
- return Object.values(result).every(status => status === PermissionsAndroid.RESULTS.GRANTED);
31
- } catch (e) {
32
- return false;
33
- }
34
- }
35
-
36
10
  export const CallHandler = {
37
11
  displayCall: async (uuid, name, callType = "audio") => {
38
12
  if (!CallModule) return false;
39
- try {
40
- return await CallModule.displayIncomingCall(
41
- uuid.toLowerCase().trim(),
42
- name,
43
- callType
44
- );
45
- } catch (e) {
46
- return false;
47
- }
13
+ return await CallModule.displayIncomingCall(uuid.toLowerCase().trim(), name, callType);
48
14
  },
49
15
 
50
16
  destroyNativeCallUI: (uuid) => {
@@ -54,34 +20,30 @@ export const CallHandler = {
54
20
  },
55
21
 
56
22
  /**
57
- * Subscribes to call events and checks for initial cold-start data.
23
+ * Manually check for cold-start data (App killed -> Answered).
24
+ * Call this in your App.js useEffect.
58
25
  */
26
+ getInitialCallData: async () => {
27
+ if (!CallModule?.getInitialCallData) return null;
28
+ return await CallModule.getInitialCallData();
29
+ },
30
+
59
31
  subscribe: (onAccept, onReject, onFailed) => {
60
32
  if (!callEventEmitter) return () => { };
61
33
 
62
34
  const subs = [
63
- callEventEmitter.addListener('onCallAccepted', (data) => {
64
- onAccept(data);
65
- }),
66
- callEventEmitter.addListener('onCallRejected', (data) => {
67
- onReject(data);
68
- }),
35
+ callEventEmitter.addListener('onCallAccepted', (data) => onAccept(data)),
36
+ callEventEmitter.addListener('onCallRejected', (data) => onReject(data)),
69
37
  ];
70
38
 
71
39
  if (onFailed) {
72
40
  subs.push(callEventEmitter.addListener('onCallFailed', onFailed));
73
41
  }
74
42
 
75
- // --- COLD START LOGIC ---
76
- // If the app was killed and opened via the Answer button,
77
- // the event might have fired before this listener was ready.
78
- if (Platform.OS === 'android' && CallModule.getInitialCallData) {
79
- CallModule.getInitialCallData().then((data) => {
80
- if (data) {
81
- onAccept(data);
82
- }
83
- });
84
- }
43
+ // Auto-check on subscribe for convenience
44
+ CallHandler.getInitialCallData().then((data) => {
45
+ if (data) onAccept(data);
46
+ });
85
47
 
86
48
  return () => subs.forEach(s => s.remove());
87
49
  }
package/ios/CallModule.m CHANGED
@@ -1,5 +1,11 @@
1
1
  #import "CallModule.h"
2
2
 
3
+ // Private interface to hold the pending UUID for cold starts
4
+ @interface CallModule ()
5
+ @property (nonatomic, strong) NSString *pendingCallUuid;
6
+ @property (nonatomic, strong) NSUUID *currentCallUUID;
7
+ @end
8
+
3
9
  @implementation CallModule
4
10
 
5
11
  RCT_EXPORT_MODULE();
@@ -73,10 +79,17 @@ RCT_EXPORT_METHOD(endNativeCall:(NSString *)uuidString)
73
79
  endedAtDate:[NSDate date]
74
80
  reason:CXCallEndedReasonRemoteEnded];
75
81
  self.currentCallUUID = nil;
82
+ self.pendingCallUuid = nil;
76
83
  }
77
84
 
85
+ // Correct implementation for cold-start check
78
86
  RCT_EXPORT_METHOD(getInitialCallData:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
79
- resolve([NSNull null]);
87
+ if (self.pendingCallUuid) {
88
+ resolve(@{@"callUuid": self.pendingCallUuid});
89
+ self.pendingCallUuid = nil; // Clear after it's been consumed
90
+ } else {
91
+ resolve([NSNull null]);
92
+ }
80
93
  }
81
94
 
82
95
  RCT_EXPORT_METHOD(checkTelecomPermissions:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) {
@@ -86,26 +99,38 @@ RCT_EXPORT_METHOD(checkTelecomPermissions:(RCTPromiseResolveBlock)resolve reject
86
99
  // MARK: - CXProviderDelegate
87
100
 
88
101
  - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
89
- // 1. Activate Audio Session
90
- // This transition tells iOS the app is now the active audio owner, which triggers the foregrounding
91
- [[AVAudioSession sharedInstance] setActive:YES error:nil];
92
-
93
- // 2. Fulfill the action
102
+ // 1. Force audio session active
103
+ AVAudioSession *session = [AVAudioSession sharedInstance];
104
+ [session setCategory:AVAudioSessionCategoryPlayAndRecord
105
+ mode:AVAudioSessionModeVoiceChat
106
+ options:AVAudioSessionCategoryOptionAllowBluetooth | AVAudioSessionCategoryOptionDefaultToSpeaker
107
+ error:nil];
108
+ [session setActive:YES error:nil];
109
+
110
+ // 2. Fulfill to tell system we answered
94
111
  [action fulfill];
95
-
96
- // 3. Notify JavaScript
97
- // Once JS receives this, the navigationRef in App.js handles the UI transition
98
- [self sendEventWithName:@"onCallAccepted" body:@{@"callUuid": [action.callUUID.UUIDString lowercaseString]}];
112
+
113
+ // 3. Save the UUID for JS to poll if the app was killed
114
+ NSString *uuidStr = [action.callUUID.UUIDString lowercaseString];
115
+ self.pendingCallUuid = uuidStr;
116
+
117
+ // 4. Dispatch event for when app is backgrounded (not killed)
118
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
119
+ [self sendEventWithName:@"onCallAccepted" body:@{@"callUuid": uuidStr}];
120
+ });
99
121
  }
100
122
 
101
123
  - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
102
124
  [action fulfill];
103
125
  self.currentCallUUID = nil;
126
+ self.pendingCallUuid = nil;
104
127
  [self sendEventWithName:@"onCallRejected" body:@{@"callUuid": [action.callUUID.UUIDString lowercaseString]}];
105
128
  }
106
129
 
107
130
  - (void)providerDidReset:(CXProvider *)provider {
108
131
  [[AVAudioSession sharedInstance] setActive:NO error:nil];
132
+ self.currentCallUUID = nil;
133
+ self.pendingCallUuid = nil;
109
134
  }
110
135
 
111
136
  @end
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "RNS nativecall component with native Android/iOS for handling native call ui, when app is not open or open.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",