Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrating with Expo #106

Open
frozencap opened this issue Jul 23, 2023 · 12 comments
Open

Integrating with Expo #106

frozencap opened this issue Jul 23, 2023 · 12 comments

Comments

@frozencap
Copy link

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

@Yeasiin
Copy link

Yeasiin commented Jan 24, 2024

Did you get any solution?

@disco-panda
Copy link

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"],
      }
    },
    ```

@frozencap
Copy link
Author

I have used it successfully with Expo Plugins to generate the delegate.

@disco-panda awesome! mind sharing your config plugin ?

@AlanGreyjoy
Copy link

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?

@ramezrafla
Copy link

@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.

@jexodusmercado
Copy link

jexodusmercado commented May 12, 2024

@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

@AlanGreyjoy
Copy link

@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.

@jexodusmercado
Copy link

@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
Copy link

AlanGreyjoy commented May 12, 2024

@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.

@jexodusmercado
Copy link

I created the PR. Lets hope the main dev review this

@ramezrafla
Copy link

ramezrafla commented May 16, 2024

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:

  1. Save this file as app.config.js in your project root
  2. Edit the obj-c to align with your own notification payload
  3. You also need to add react-native-call-keep package -- this code handles both
  4. Run npx expo prebuild and inspect the output AppDelegate.mm file (don't forget to remove folders ios and android before you build on expo online)
  5. During build if there is an error, look for it on the expo.dev build console and adjust
  6. No need to call completion from JS side -- it's handled here already
  7. Don't add 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)
}

@grondo89
Copy link

grondo89 commented Jun 6, 2024

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants