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 +2 -0
- package/index.js +14 -52
- package/ios/CallModule.m +35 -10
- package/package.json +1 -1
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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.
|
|
90
|
-
|
|
91
|
-
[
|
|
92
|
-
|
|
93
|
-
|
|
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.
|
|
97
|
-
|
|
98
|
-
|
|
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