Open frozencap opened 1 year ago
Did you get any solution?
I have used it successfully with Expo Plugins to generate the delegate.
You can use this in your app config to give the proper permissions
ios: {
...
infoPlist: {
UIBackgroundModes: ["voip"],
}
},
I have used it successfully with Expo Plugins to generate the delegate.
@disco-panda awesome! mind sharing your config plugin ?
I have used it successfully with Expo Plugins to generate the delegate.
You can use this in your app config to give the proper permissions
ios: { ... infoPlist: { UIBackgroundModes: ["voip"], } },
Very nice. Share that bad boy?
@disco-panda well done! Could you share the plugin? I don't mind cleaning up and helping PR this repo I would save a lot of people a lot of headaches.
@ramezrafla forked the repo and created my own plugin. We dont have any contribution guide here, I don't know if I should request a PR.
Note: I implemented the RNCallKeep in the AppDelegate too. This is the exact requirements of our app.
https://github.com/jexodusmercado/react-native-voip-push-notification
@ramezrafla forked the repo and created my own plugin. We dont have any contribution guide here, I don't know if I should request a PR.
Note: I implemented the RNCallKeep in the AppDelegate too. This is the exact requirements of our app.
https://github.com/jexodusmercado/react-native-voip-push-notification
Holy mac-n-cheese. There isn't a number that would accurately represent the amount of people you are helping with this. You could request a PR here, but the main dev is in and out alot.
@AlanGreyjoy Let me clean my code first then request a PR in a few days. I need sleep. The amount of trial and error to make it work locally and in the eas-build is too much.
@AlanGreyjoy Let me clean my code first then request a PR in a few days. I need sleep. The amount of trial and error to make it work locally and in the eas-build is too much.
I know the pain... been developing a node-red plugin. Things like this make you worship whoever came up with HMR ha.
I created the PR. Lets hope the main dev review this
Dear @jexodusmercado -- well done! We worked in parallel. We used app.config.js
in our app to do the same. I am sharing here to help people:
Notes:
app.config.js
in your project rootreact-native-call-keep
package -- this code handles bothnpx expo prebuild
and inspect the output AppDelegate.mm
file (don't forget to remove folders ios
and android
before you build on expo online)self.bridge
stuff from the README -- it's for pure React-Native. Expo handles for you// app.config.js
// https://github.com/invertase/react-native-firebase/discussions/5386#discussioncomment-860295
// https://www.videosdk.live/blog/react-native-ios-video-calling-app-with-callkeep
// https://sendbird.com/docs/calls/sdk/v1/react-native/direct-call/receiving-a-call/receive-a-call-in-the-background
import { withAppDelegate } from '@expo/config-plugins'
import { addObjcImports, insertContentsInsideObjcFunctionBlock, findObjcFunctionCodeBlock } from '@expo/config-plugins/build/ios/codeMod'
const DID_FINISH_LAUNCHING = 'application:didFinishLaunchingWithOptions:'
const DID_UPDATE_PUSH_CREDENTIALS = 'pushRegistry:didUpdatePushCredentials:forType:'
const DID_RECEIVE_INCOMING_PUSH = 'pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:'
const CONTINUE_USER_ACTIVITY = 'application:continueUserActivity:restorationHandler:'
function addContinueUserActivity(contents) {
// call the setup of RNCallKeep
// https://github.com/react-native-webrtc/react-native-callkeep
// call the setup of voip push notification
// https://github.com/react-native-webrtc/react-native-voip-push-notification
const setupContinue = 'BOOL resultRNCall = [RNCallKeep application:application continueUserActivity:userActivity restorationHandler:restorationHandler];'
if (!contents.includes(setupContinue)) {
contents = insertContentsInsideObjcFunctionBlock(
contents,
CONTINUE_USER_ACTIVITY,
setupContinue,
{ position: 'head' }
)
contents = contents.replace('[super application:application continueUserActivity:userActivity restorationHandler:restorationHandler]', '[super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || resultRNCall')
}
return contents
}
// https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/expo-config-plugin/src/withPushAppDelegate.ts#L30
function addImports(contents) {
return addObjcImports(
contents,
[
'<PushKit/PushKit.h>',
'"RNVoipPushNotificationManager.h"',
'"RNCallKeep.h"'
]
)
}
// https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/expo-config-plugin/src/common/addNewLinesToAppDelegate.ts
function addNewLinesToAppDelegate(content, toAdd) {
const lines = content.split('\n')
let lineIndex = lines.findIndex((line) => line.match('@end'))
if (lineIndex < 0) throw Error('Malformed app delegate')
toAdd.unshift('')
lineIndex -= 1
for (const newLine of toAdd) {
lines.splice(lineIndex, 0, newLine)
lineIndex++
}
return lines.join('\n')
}
function addDidFinishLaunching(contents) {
// call the setup of RNCallKeep
// https://github.com/react-native-webrtc/react-native-callkeep
// call the setup of voip push notification
// https://github.com/react-native-webrtc/react-native-voip-push-notification
const setupCallKeep = `
NSString *appName = [[[NSBundle mainBundle] infoDictionary]objectForKey :@"CFBundleDisplayName"];
[RNCallKeep setup:@{
@"appName": appName,
@"supportsVideo": @NO,
@"includesCallsInRecents": @YES,
@"maximumCallGroups": @1,
@"maximumCallsPerCallGroup": @1
}];
[RNVoipPushNotificationManager voipRegistration];
`
if (!contents.includes('[RNCallKeep setup:@')) {
contents = insertContentsInsideObjcFunctionBlock(
contents,
DID_FINISH_LAUNCHING,
setupCallKeep,
{ position: 'head' }
)
}
return contents
}
function addDidUpdatePushCredentials(contents) {
if (!contents.includes('didInvalidatePushTokenForType:(PKPushType)type')) {
contents = addNewLinesToAppDelegate(contents, [
`
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type {
// --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}
`
])
}
const updatedPushCredentialsMethod = '[RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];'
if (!contents.includes(updatedPushCredentialsMethod)) {
if (!findObjcFunctionCodeBlock(contents, DID_UPDATE_PUSH_CREDENTIALS)) {
contents = addNewLinesToAppDelegate(contents, [
`- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
${updatedPushCredentialsMethod}
}`
])
}
else {
contents = insertContentsInsideObjcFunctionBlock(
contents,
DID_UPDATE_PUSH_CREDENTIALS,
updatedPushCredentialsMethod,
{ position: 'tail' }
)
}
}
return contents
}
function addDidReceiveIncomingPush(contents) {
// https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/expo-config-plugin/src/withPushAppDelegate.ts#L30
// https://github.com/react-native-webrtc/react-native-callkeep#pushkit
const onIncomingPush = `
[RNCallKeep reportNewIncomingCall: [[NSUUID UUID] UUIDString]
handle: payload.dictionaryPayload[@"handle"]
handleType: @"generic"
hasVideo: NO
localizedCallerName: payload.dictionaryPayload[@"caller"]
supportsHolding: YES
supportsDTMF: YES
supportsGrouping: YES
supportsUngrouping: YES
fromPushKit: YES
payload: nil
withCompletionHandler: completion];
[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
`
if (!contents.includes('[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload')) {
if (!findObjcFunctionCodeBlock(contents, DID_RECEIVE_INCOMING_PUSH)) {
contents = addNewLinesToAppDelegate(contents, [
`- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
${onIncomingPush}
}`
])
}
else {
contents = insertContentsInsideObjcFunctionBlock(
contents,
DID_RECEIVE_INCOMING_PUSH,
onIncomingPush,
{ position: 'tail' }
)
}
}
return contents
}
// https://www.sitepen.com/blog/doing-more-with-expo-using-custom-native-code
// https://docs.expo.dev/config-plugins/plugins-and-mods/
// https://github.com/invertase/react-native-firebase/discussions/5386
function withVoipAppDelegate(config) {
return withAppDelegate(config, (cfg) => {
const { modResults } = cfg
if (['objc', 'objcpp'].includes(modResults.language)) {
modResults.contents = addImports(modResults.contents)
modResults.contents = addDidFinishLaunching(modResults.contents)
modResults.contents = addDidUpdatePushCredentials(modResults.contents)
modResults.contents = addDidReceiveIncomingPush(modResults.contents)
modResults.contents = addContinueUserActivity(modResults.contents)
// console.log(modResults.contents)
}
return cfg
})
}
module.exports = ({ config }) => {
return withVoipAppDelegate(config)
}
I created the PR. Lets hope the main dev review this
Hey, sorry for the silly question but: How do I apply your approach? I create the plugin file, add the plugin to the config and make a build / prebuild? Or should I manually apply the modifications you outline in the AppDelegate.m modification? Or both? Thanks a lot for the help!
I created the PR. Lets hope the main dev review this
Hey, sorry for the silly question but: How do I apply your approach? I create the plugin file, add the plugin to the config and make a build / prebuild? Or should I manually apply the modifications you outline in the AppDelegate.m modification? Or both? Thanks a lot for the help!
I created the PR. Lets hope the main dev review this
Hey, sorry for the silly question but: How do I apply your approach? I create the plugin file, add the plugin to the config and make a build / prebuild? Or should I manually apply the modifications you outline in the AppDelegate.m modification? Or both? Thanks a lot for the help!
Don't manually make the modifications, that would be overwritten when you next build.
In your root folder (where you find package.json etc) create a file called voip.js, and paste the following into it (credit to @ramezrafla above):
// https://github.com/invertase/react-native-firebase/discussions/5386#discussioncomment-860295
// https://www.videosdk.live/blog/react-native-ios-video-calling-app-with-callkeep
// https://sendbird.com/docs/calls/sdk/v1/react-native/direct-call/receiving-a-call/receive-a-call-in-the-background
const { addObjcImports, insertContentsInsideObjcFunctionBlock, findObjcFunctionCodeBlock } = require('@expo/config-plugins/build/ios/codeMod')
const { withAppDelegate } = require('@expo/config-plugins')
const DID_FINISH_LAUNCHING = 'application:didFinishLaunchingWithOptions:'
const DID_UPDATE_PUSH_CREDENTIALS = 'pushRegistry:didUpdatePushCredentials:forType:'
const DID_RECEIVE_INCOMING_PUSH = 'pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:'
const CONTINUE_USER_ACTIVITY = 'application:continueUserActivity:restorationHandler:'
function addContinueUserActivity(contents) {
// call the setup of RNCallKeep
// https://github.com/react-native-webrtc/react-native-callkeep
// call the setup of voip push notification
// https://github.com/react-native-webrtc/react-native-voip-push-notification
const setupContinue = 'BOOL resultRNCall = [RNCallKeep application:application continueUserActivity:userActivity restorationHandler:restorationHandler];'
if (!contents.includes(setupContinue)) {
contents = insertContentsInsideObjcFunctionBlock(
contents,
CONTINUE_USER_ACTIVITY,
setupContinue,
{ position: 'head' }
)
contents = contents.replace('[super application:application continueUserActivity:userActivity restorationHandler:restorationHandler]', '[super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || resultRNCall')
}
return contents
}
// https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/expo-config-plugin/src/withPushAppDelegate.ts#L30
function addImports(contents) {
return addObjcImports(
contents,
[
'<PushKit/PushKit.h>',
'"RNVoipPushNotificationManager.h"',
'"RNCallKeep.h"'
]
)
}
// https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/expo-config-plugin/src/common/addNewLinesToAppDelegate.ts
function addNewLinesToAppDelegate(content, toAdd) {
const lines = content.split('\n')
let lineIndex = lines.findIndex((line) => line.match('@end'))
if (lineIndex < 0) throw Error('Malformed app delegate')
toAdd.unshift('')
lineIndex -= 1
for (const newLine of toAdd) {
lines.splice(lineIndex, 0, newLine)
lineIndex++
}
return lines.join('\n')
}
function addDidFinishLaunching(contents) {
// call the setup of RNCallKeep
// https://github.com/react-native-webrtc/react-native-callkeep
// call the setup of voip push notification
// https://github.com/react-native-webrtc/react-native-voip-push-notification
const setupCallKeep = `
NSString *appName = [[[NSBundle mainBundle] infoDictionary]objectForKey :@"CFBundleDisplayName"];
[RNCallKeep setup:@{
@"appName": appName,
@"supportsVideo": @YES,
@"includesCallsInRecents": @YES,
@"maximumCallGroups": @1,
@"maximumCallsPerCallGroup": @1
}];
[RNVoipPushNotificationManager voipRegistration];
`
if (!contents.includes('[RNCallKeep setup:@')) {
contents = insertContentsInsideObjcFunctionBlock(
contents,
DID_FINISH_LAUNCHING,
setupCallKeep,
{ position: 'head' }
)
}
return contents
}
function addDidUpdatePushCredentials(contents) {
if (!contents.includes('didInvalidatePushTokenForType:(PKPushType)type')) {
contents = addNewLinesToAppDelegate(contents, [
`
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type {
// --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}
`
])
}
const updatedPushCredentialsMethod = '[RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];'
if (!contents.includes(updatedPushCredentialsMethod)) {
if (!findObjcFunctionCodeBlock(contents, DID_UPDATE_PUSH_CREDENTIALS)) {
contents = addNewLinesToAppDelegate(contents, [
`- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
${updatedPushCredentialsMethod}
}`
])
}
else {
contents = insertContentsInsideObjcFunctionBlock(
contents,
DID_UPDATE_PUSH_CREDENTIALS,
updatedPushCredentialsMethod,
{ position: 'tail' }
)
}
}
return contents
}
function addDidReceiveIncomingPush(contents) {
// https://github.com/GetStream/stream-video-js/blob/main/packages/react-native-sdk/expo-config-plugin/src/withPushAppDelegate.ts#L30
// https://github.com/react-native-webrtc/react-native-callkeep#pushkit
const onIncomingPush = `
[RNCallKeep reportNewIncomingCall: [[NSUUID UUID] UUIDString]
handle: payload.dictionaryPayload[@"handle"]
handleType: @"generic"
hasVideo: YES
localizedCallerName: payload.dictionaryPayload[@"caller"]
supportsHolding: YES
supportsDTMF: YES
supportsGrouping: YES
supportsUngrouping: YES
fromPushKit: YES
payload: nil
withCompletionHandler: completion];
[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
`
if (!contents.includes('[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload')) {
if (!findObjcFunctionCodeBlock(contents, DID_RECEIVE_INCOMING_PUSH)) {
contents = addNewLinesToAppDelegate(contents, [
`- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
${onIncomingPush}
}`
])
}
else {
contents = insertContentsInsideObjcFunctionBlock(
contents,
DID_RECEIVE_INCOMING_PUSH,
onIncomingPush,
{ position: 'tail' }
)
}
}
return contents
}
// https://www.sitepen.com/blog/doing-more-with-expo-using-custom-native-code
// https://docs.expo.dev/config-plugins/plugins-and-mods/
// https://github.com/invertase/react-native-firebase/discussions/5386
module.exports = function withPrefixedName(config, prefix) {
//return config
return withAppDelegate(config, (cfg) => {
const { modResults } = cfg
if (['objc', 'objcpp'].includes(modResults.language)) {
modResults.contents = addImports(modResults.contents)
modResults.contents = addDidFinishLaunching(modResults.contents)
modResults.contents = addDidUpdatePushCredentials(modResults.contents)
modResults.contents = addDidReceiveIncomingPush(modResults.contents)
modResults.contents = addContinueUserActivity(modResults.contents)
// console.log(modResults.contents)
}
return cfg
})
}
Then in your app.config.js, add the following:
"plugins": [
"./voip"
]
And the following:
"ios": {
"infoPlist": {
"UIBackgroundModes": [
"voip",
"remote-notification"
]
}
},
Then just build normally.
A word of warning, this plugin assumes your VoIP notification contains a data dictionary with the properties handle, callerName and uuid. Without those, your app will crash when you receive a notification. If you want to change that, find them in voip.js and amend as needed.
Example push notification payload:
// Notification payload
const notificationPayload = {
aps: {
alert: {
// add custom data here
},
sound: 'default',
'content-available': 1 // Indicates a VoIP notification
},
type: 'voip',
data: {
handle: 'handle',
callerName: 'Caller Name',
uuid: uuid
}
};
Final point - I changed the video settings to 'YES' - as this allows a user to automatically go to your app even if their phone was locked prior to receiving the call
Has anybody managed to get an Expo Module/Config Plugin working for this lib ?
This is the only lib under
react-native-webrtc/
organization that doesn't provide this