rns-nativecall 0.0.4 → 0.0.6

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.
@@ -22,20 +22,28 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
22
22
 
23
23
  override fun getName() = "CallModule"
24
24
 
25
+ private fun getPhoneAccountHandle(): PhoneAccountHandle {
26
+ val componentName = ComponentName(reactApplicationContext, MyConnectionService::class.java)
27
+ // Using the app's package name for the ID makes it unique for every app using this package
28
+ return PhoneAccountHandle(componentName, "${reactApplicationContext.packageName}.voip")
29
+ }
30
+
25
31
  private fun registerPhoneAccount() {
26
32
  val telecomManager = reactApplicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
27
- val componentName = ComponentName(reactApplicationContext, MyConnectionService::class.java)
28
- val phoneAccountHandle = PhoneAccountHandle(componentName, "RaiidrVoip")
33
+ val phoneAccountHandle = getPhoneAccountHandle()
29
34
 
30
- // Combine all critical capabilities into one mask this is important!!!
31
- val capabilities = PhoneAccount.CAPABILITY_VIDEO_CALLING or
32
- PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING
35
+ // Dynamically get the app's display name from the Android Manifest
36
+ val appName = reactApplicationContext.applicationInfo.loadLabel(reactApplicationContext.packageManager).toString()
33
37
 
34
- val phoneAccount = PhoneAccount.builder(phoneAccountHandle, "Raiidr")
35
- .setCapabilities(capabilities)
36
- .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
37
- .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
38
- .build()
38
+ // Correct bitmasking: Combine all capabilities into a single integer
39
+ val capabilities = PhoneAccount.CAPABILITY_SELF_MANAGED or
40
+ PhoneAccount.CAPABILITY_CALL_PROVIDER or
41
+ PhoneAccount.CAPABILITY_VIDEO_CALLING or
42
+ PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING
43
+
44
+ val phoneAccount = PhoneAccount.builder(phoneAccountHandle, appName)
45
+ .setCapabilities(capabilities)
46
+ .build()
39
47
 
40
48
  telecomManager.registerPhoneAccount(phoneAccount)
41
49
  }
@@ -50,8 +58,7 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
50
58
  promise: Promise
51
59
  ) {
52
60
  val telecomManager = reactApplicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
53
- val componentName = ComponentName(reactApplicationContext, MyConnectionService::class.java)
54
- val phoneAccountHandle = PhoneAccountHandle(componentName, "RaiidrVoip")
61
+ val phoneAccountHandle = getPhoneAccountHandle()
55
62
 
56
63
  val extras = Bundle().apply {
57
64
  putParcelable(
@@ -85,13 +92,13 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
85
92
  @ReactMethod
86
93
  fun checkTelecomPermissions(promise: Promise) {
87
94
  val telecomManager = reactApplicationContext.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
88
- val componentName = ComponentName(reactApplicationContext, MyConnectionService::class.java)
89
- val phoneAccountHandle = PhoneAccountHandle(componentName, "RaiidrVoip")
95
+ val phoneAccountHandle = getPhoneAccountHandle()
90
96
 
91
97
  val account = telecomManager.getPhoneAccount(phoneAccountHandle)
92
98
  if (account != null && account.isEnabled) {
93
99
  promise.resolve(true)
94
100
  } else {
101
+ // Opens the system settings for "Calling Accounts" so the user can enable the app
95
102
  val intent = Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS)
96
103
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
97
104
  reactApplicationContext.startActivity(intent)
@@ -114,4 +121,4 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
114
121
  ?.emit(eventName, params)
115
122
  }
116
123
  }
117
- }
124
+ }
package/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  ///Users/bush/Desktop/Apps/Raiidr/package/index.d.ts
2
+
2
3
  export interface CallData {
3
4
  callUUID: string;
4
5
  }
@@ -7,14 +8,20 @@ export type CallAcceptedCallback = (data: CallData) => void;
7
8
  export type CallRejectedCallback = (data: CallData) => void;
8
9
  export type CallFailedCallback = (data: CallData) => void;
9
10
 
11
+ /**
12
+ * Manually request/check Android permissions for Telecom.
13
+ * @returns Promise resolving to true if granted
14
+ */
15
+ export function ensureAndroidPermissions(): Promise<boolean>;
16
+
10
17
  export interface CallHandlerType {
11
18
  /**
12
- * Display an incoming call UI.
19
+ * Display an incoming call UI using ConnectionService (Android) or CallKit (iOS).
13
20
  * @param uuid Unique call identifier
14
- * @param number Caller number
21
+ * @param number Caller number (or URI)
15
22
  * @param name Caller display name
16
- * @param hasVideo True if video call
17
- * @param shouldRing True to play native ringtone
23
+ * @param hasVideo True if video call (default: false)
24
+ * @param shouldRing True to play native ringtone (default: true)
18
25
  * @returns Promise resolving to true if successfully displayed
19
26
  */
20
27
  displayCall(
@@ -26,16 +33,16 @@ export interface CallHandlerType {
26
33
  ): Promise<boolean>;
27
34
 
28
35
  /**
29
- * Dismiss native call UI
36
+ * Dismiss the native call UI (e.g., if the caller hangs up before the user answers).
30
37
  * @param uuid Call identifier
31
38
  */
32
39
  destroyNativeCallUI(uuid: string): void;
33
40
 
34
41
  /**
35
- * Subscribe to call events
36
- * @param onAccept Callback for accepted calls
37
- * @param onReject Callback for rejected calls
38
- * @param onFailed Optional callback for failed calls
42
+ * Subscribe to call events from the native side.
43
+ * @param onAccept Callback when user presses Answer
44
+ * @param onReject Callback when user presses Decline
45
+ * @param onFailed Optional callback for system-level failures
39
46
  * @returns Function to unsubscribe all listeners
40
47
  */
41
48
  subscribe(
@@ -47,4 +54,4 @@ export interface CallHandlerType {
47
54
 
48
55
  declare const CallHandler: CallHandlerType;
49
56
 
50
- export default CallHandler;
57
+ export default CallHandler;
package/index.js CHANGED
@@ -9,6 +9,15 @@ import {
9
9
  } from 'react-native';
10
10
 
11
11
  const { CallModule } = NativeModules;
12
+
13
+ // Safety check for the Native Module
14
+ if (!CallModule && __DEV__) {
15
+ console.warn(
16
+ "rns-nativecall: NativeModule.CallModule is undefined. " +
17
+ "Make sure you have rebuilt your native project (npx expo run:android / ios)."
18
+ );
19
+ }
20
+
12
21
  const callEventEmitter = CallModule ? new NativeEventEmitter(CallModule) : null;
13
22
 
14
23
  const REQUIRED_PERMISSIONS = Platform.OS === 'android' ? [
@@ -19,84 +28,57 @@ const REQUIRED_PERMISSIONS = Platform.OS === 'android' ? [
19
28
  export async function ensureAndroidPermissions() {
20
29
  if (Platform.OS !== 'android') return true;
21
30
  const result = await PermissionsAndroid.requestMultiple(REQUIRED_PERMISSIONS);
22
- const deniedPermissions = Object.entries(result)
23
- .filter(([_, status]) => status !== PermissionsAndroid.RESULTS.GRANTED)
24
- .map(([perm, _]) => perm);
25
-
26
- if (deniedPermissions.length > 0) {
27
- Alert.alert(
28
- 'Permissions Required',
29
- `The following permissions are still missing: \n${deniedPermissions.join('\n')}`,
30
- [{ text: 'Open Settings', onPress: () => Linking.openSettings() }]
31
- );
32
- return false;
33
- }
34
- return true;
31
+ return Object.values(result).every(status => status === PermissionsAndroid.RESULTS.GRANTED);
35
32
  }
36
33
 
37
34
  export const CallHandler = {
38
-
39
- /**
40
- * @param {string} uuid
41
- * @param {string} number
42
- * @param {string} name
43
- * @param {boolean} hasVideo
44
- * @param {boolean} shouldRing - NEW: Controls native ringtone/vibration
45
- */
46
35
  displayCall: async (uuid, number, name, hasVideo = false, shouldRing = true) => {
47
- if (!CallModule) {
48
- console.log("CallModule is null. Check native linking.");
49
- return false;
50
- }
36
+ if (!CallModule) return false;
51
37
 
52
- // 1. Android Specific Checks
53
38
  if (Platform.OS === 'android') {
54
39
  const hasPerms = await ensureAndroidPermissions();
55
40
  if (!hasPerms) return false;
56
41
 
42
+ // This triggers the "Calling Accounts" system settings if not enabled
57
43
  const isAccountEnabled = await CallModule.checkTelecomPermissions();
58
44
  if (!isAccountEnabled) return false;
59
45
  }
60
46
 
61
- const cleanUuid = uuid.toLowerCase().trim();
62
-
63
- // 2. Cross-platform execution
64
47
  try {
65
- // We now pass 5 arguments to the native side
66
- // uuid, number, name, hasVideo, shouldRing
67
- const success = await CallModule.displayIncomingCall(
68
- cleanUuid,
48
+ return await CallModule.displayIncomingCall(
49
+ uuid.toLowerCase().trim(),
69
50
  number,
70
51
  name,
71
52
  hasVideo,
72
53
  shouldRing
73
54
  );
74
- return success;
75
55
  } catch (e) {
76
- console.log("Native Call Error:", e);
56
+ console.error("Native Call Error:", e);
77
57
  return false;
78
58
  }
79
59
  },
80
60
 
81
61
  destroyNativeCallUI: (uuid) => {
82
- if (CallModule && CallModule.endNativeCall) {
62
+ if (CallModule?.endNativeCall) {
83
63
  CallModule.endNativeCall(uuid.toLowerCase());
84
64
  }
85
65
  },
86
66
 
87
67
  subscribe: (onAccept, onReject, onFailed) => {
88
- if (!callEventEmitter) {
89
- return () => { };
90
- }
68
+ if (!callEventEmitter) return () => { };
91
69
 
92
- const acceptSub = callEventEmitter.addListener('onCallAccepted', (data) => onAccept(data));
93
- const rejectSub = callEventEmitter.addListener('onCallRejected', (data) => onReject(data));
94
- const failSub = onFailed ? callEventEmitter.addListener('onCallFailed', (data) => onFailed(data)) : null;
70
+ const subs = [
71
+ callEventEmitter.addListener('onCallAccepted', onAccept),
72
+ callEventEmitter.addListener('onCallRejected', onReject),
73
+ ];
95
74
 
96
- return () => {
97
- acceptSub.remove();
98
- rejectSub.remove();
99
- if (failSub) failSub.remove();
100
- };
75
+ if (onFailed) {
76
+ subs.push(callEventEmitter.addListener('onCallFailed', onFailed));
77
+ }
78
+
79
+ return () => subs.forEach(s => s.remove());
101
80
  }
102
- };
81
+ };
82
+
83
+ // IMPORTANT: Exporting as default ensures "import CallHandler from '...'" works
84
+ export default CallHandler;
package/ios/CallModule.m CHANGED
@@ -11,20 +11,22 @@ RCT_EXPORT_MODULE();
11
11
  - (instancetype)init {
12
12
  self = [super init];
13
13
  if (self) {
14
+ // Dynamically get the App Name from the bundle so it's not hardcoded
15
+ NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
16
+ if (!appName) {
17
+ appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
18
+ }
19
+
14
20
  #pragma clang diagnostic push
15
21
  #pragma clang diagnostic ignored "-Wdeprecated-declarations"
16
- CXProviderConfiguration *config = [[CXProviderConfiguration alloc] initWithLocalizedName:@"Raiidr"];
22
+ CXProviderConfiguration *config = [[CXProviderConfiguration alloc] initWithLocalizedName:appName];
17
23
  #pragma clang diagnostic pop
18
24
 
19
25
  config.supportsVideo = YES;
20
26
  config.maximumCallGroups = 1;
21
27
  config.maximumCallsPerCallGroup = 1;
22
28
  config.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypeGeneric)];
23
- config.includesCallsInRecents = NO; // <-- prevent log
24
-
25
- // If you had a custom file like "Ringtone.aif" in your project,
26
- // you would set it here:
27
- // config.ringtoneSound = @"Ringtone.aif";
29
+ config.includesCallsInRecents = NO;
28
30
 
29
31
  self.provider = [[CXProvider alloc] initWithConfiguration:config];
30
32
  [self.provider setDelegate:self queue:nil];
@@ -32,7 +34,7 @@ RCT_EXPORT_MODULE();
32
34
  return self;
33
35
  }
34
36
 
35
- // Updated with showRing (BOOL)
37
+ // Matches the 5 arguments in your JS/Android implementation
36
38
  RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
37
39
  number:(NSString *)number
38
40
  name:(NSString *)name
@@ -48,12 +50,12 @@ RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
48
50
  }
49
51
 
50
52
  CXCallUpdate *update = [[CXCallUpdate alloc] init];
53
+ // We use 'name' here so the system UI shows the caller's name instead of just their number
51
54
  update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:name];
52
55
  update.hasVideo = hasVideo;
53
-
54
- // Note: CallKit handles ringing based on the CXProviderConfiguration.
55
- // iOS doesn't allow toggling 'ring' per-call via reportNewIncomingCall.
56
- // However, including it here ensures your JS bridge call matches.
56
+ update.supportsGrouping = NO;
57
+ update.supportsUngrouping = NO;
58
+ update.supportsHolding = NO;
57
59
 
58
60
  [self.provider reportNewIncomingCallWithUUID:uuid
59
61
  update:update
@@ -72,10 +74,15 @@ RCT_EXPORT_METHOD(endNativeCall:(NSString *)uuidString) {
72
74
  [self.provider reportCallWithUUID:uuid
73
75
  endedAtDate:[NSDate date]
74
76
  reason:CXCallEndedReasonRemoteEnded];
75
-
76
77
  }
77
78
  }
78
79
 
80
+ // Placeholder for Android parity
81
+ RCT_EXPORT_METHOD(checkTelecomPermissions:(RCTPromiseResolveBlock)resolve
82
+ reject:(RCTPromiseResolveBlock)reject) {
83
+ resolve(@YES); // iOS permissions are handled via Info.plist and system prompts
84
+ }
85
+
79
86
  // MARK: - CXProviderDelegate
80
87
 
81
88
  - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
@@ -92,4 +99,4 @@ RCT_EXPORT_METHOD(endNativeCall:(NSString *)uuidString) {
92
99
 
93
100
  - (void)providerDidReset:(CXProvider *)provider { }
94
101
 
95
- @end
102
+ @end
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "0.0.4",
4
- "description": "Raiidr nativecall component with native Android/iOS for handling native call ui, when app is not open or open.",
3
+ "version": "0.0.6",
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",
7
7
  "homepage": "https://github.com/raiidr/rns-nativecall",
package/withRaiidrVoip.js CHANGED
@@ -8,11 +8,10 @@ function withAndroidConfig(config) {
8
8
  return withAndroidManifest(config, (config) => {
9
9
  const manifest = config.modResults;
10
10
 
11
- // 1. Add required permissions
12
11
  const permissions = [
13
12
  'android.permission.READ_PHONE_NUMBERS',
14
13
  'android.permission.CALL_PHONE',
15
- 'android.permission.MANAGE_OWN_CALLS' // Required for Self-Managed ConnectionService
14
+ 'android.permission.MANAGE_OWN_CALLS'
16
15
  ];
17
16
 
18
17
  if (!manifest.manifest['uses-permission']) {
@@ -25,7 +24,6 @@ function withAndroidConfig(config) {
25
24
  }
26
25
  });
27
26
 
28
- // 2. Add MyConnectionService service
29
27
  const application = manifest.manifest.application[0];
30
28
  application.service = application.service || [];
31
29
 
@@ -69,15 +67,16 @@ function withIosConfig(config) {
69
67
  }
70
68
  });
71
69
 
72
- // 2. Add descriptions for permissions
70
+ // 2. Dynamic Microphone Description
71
+ // config.name pulls the name from the user's app.json automatically
72
+ const appName = config.name || 'this app';
73
73
  infoPlist.NSMicrophoneUsageDescription =
74
- infoPlist.NSMicrophoneUsageDescription || 'Allow Raiidr to access your microphone for calls.';
74
+ infoPlist.NSMicrophoneUsageDescription || `Allow ${appName} to access your microphone for calls.`;
75
75
 
76
76
  return config;
77
77
  });
78
78
  }
79
79
 
80
- // Export the combined plugin
81
80
  module.exports = (config) => {
82
81
  return withPlugins(config, [
83
82
  withAndroidConfig,