Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/react-native-voip-push-notification/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');
3 changes: 3 additions & 0 deletions packages/react-native-voip-push-notification/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### @config-plugins/react-native-voip-push-notification 1.0.0

- Expo SDK 53 support
21 changes: 21 additions & 0 deletions packages/react-native-voip-push-notification/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# config-plugins/react-native-voip-push-notification

Config plugin to auto-configure `react-native-voip-push-notification` when the native code is generated (`npx expo prebuild`).

## Versioning

Ensure you use versions that work together!

| `expo` | `react-native-voip-push-notification` | `@config-plugins/react-native-voip-push-notification` |
| ------ |---------------------------------------|-------------------------------------------------------|
| 53.0.0 | 3.3.3 | 1.0.0 |

### Add the package to your npm dependencies

> This package cannot be used in the "Expo Go" app because [it requires custom native code](https://docs.expo.io/workflow/customizing/).

First install the package with yarn, npm, or [`npx expo install`](https://docs.expo.io/workflow/expo-cli/#expo-install).

```
npx expo install react-native-voip-push-notification @config-plugins/react-native-voip-push-notification
```
1 change: 1 addition & 0 deletions packages/react-native-voip-push-notification/app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("./build/withVoipPushNotification");
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('expo-module-scripts/jest-preset-plugin');
35 changes: 35 additions & 0 deletions packages/react-native-voip-push-notification/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@config-plugins/react-native-voip-push-notification",
"version": "1.0.0",
"description": "Config plugin to auto configure react-native-voip-push-notification on prebuild",
"main": "build/withVoipPushNotification.js",
"types": "build/withVoipPushNotification.d.ts",
"sideEffects": false,
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expo/config-plugins.git",
"directory": "packages/react-native-voip-push-notification"
},
"files": [
"build",
"app.plugin.js"
],
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": [
"react-native",
"expo"
],
"peerDependencies": {
"expo": "^53"
},
"upstreamPackage": "react-native-voip-push-notification"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import {
type ConfigPlugin,
withAppDelegate,
withXcodeProject,
withInfoPlist,
} from "@expo/config-plugins";
import {
addSwiftImports,
insertContentsInsideSwiftClassBlock,
insertContentsInsideSwiftFunctionBlock,
findSwiftFunctionCodeBlock,
addObjcImports,
} from "@expo/config-plugins/build/ios/codeMod";
import fs from "fs";
import path from "path";

const withVoipAppDelegate: ConfigPlugin = (configuration) => {
return withAppDelegate(configuration, (config) => {
if (config.modResults.language !== "swift") {
console.warn(
"react-native-voip-push-notification: Objective-C AppDelegate not supported, use Swift",
);

return config;
}

try {
// Add PKPushRegistryDelegate to AppDelegate class
if (!config.modResults.contents.includes("PKPushRegistryDelegate")) {
config.modResults.contents = config.modResults.contents.replace(
/public class AppDelegate: ExpoAppDelegate/,
"public class AppDelegate: ExpoAppDelegate, PKPushRegistryDelegate",
);
}

addToSwiftBridgingHeaderFile(
config.modRequest.projectRoot,
config.modRequest.projectName,
(headerFileContents: string) =>
addObjcImports(headerFileContents, [
'"RNVoipPushNotificationManager.h"',
'"RNCallKeep.h"',
]),
);

config.modResults.contents = addSwiftImports(config.modResults.contents, [
"PushKit",
]);

config.modResults.contents = addDidFinishLaunchingWithOptionsRingingSwift(
config.modResults.contents,
);

config.modResults.contents = addDidUpdatePushCredentialsSwift(
config.modResults.contents,
);

config.modResults.contents = addDidReceiveIncomingPushCallbackSwift(
config.modResults.contents,
);

return config;
} catch (error) {
throw new Error(
`Cannot setup react-native-voip-push-notification: ${error}`,
);
}
});
};

function addDidFinishLaunchingWithOptionsRingingSwift(contents: string) {
const functionSelector = "application(_:didFinishLaunchingWithOptions:)";

// call the setup of voip push notification
const voipSetupMethod = "RNVoipPushNotificationManager.voipRegistration()";

if (!contents.includes(voipSetupMethod)) {
contents = insertContentsInsideSwiftFunctionBlock(
contents,
functionSelector,
" " /* indentation */ + voipSetupMethod,
{ position: "head" },
);
}

return contents;
}

function addDidUpdatePushCredentialsSwift(contents: string) {
const updatedPushCredentialsMethod =
"RNVoipPushNotificationManager.didUpdate(credentials, forType: type.rawValue)";

if (!contents.includes(updatedPushCredentialsMethod)) {
const functionSelector = "pushRegistry(_:didUpdate:for:)";

const codeBlock = findSwiftFunctionCodeBlock(contents, functionSelector);

if (!codeBlock) {
return insertContentsInsideSwiftClassBlock(
contents,
"class AppDelegate",
`
public func pushRegistry(
_ registry: PKPushRegistry,
didUpdate credentials: PKPushCredentials,
for type: PKPushType
) {
${updatedPushCredentialsMethod}
}
`,
{ position: "tail" },
);
} else {
return insertContentsInsideSwiftFunctionBlock(
contents,
functionSelector,
updatedPushCredentialsMethod,
{ position: "tail" },
);
}
}
return contents;
}

function addDidReceiveIncomingPushCallbackSwift(contents: string) {
const onIncomingPush = `
let uuid = payload.dictionaryPayload["uuid"] as? String ?? UUID().uuidString
let handle = payload.dictionaryPayload["handle"] as? String ?? "Unknown"
let callerName = payload.dictionaryPayload["callerName"] as? String ?? "Unknown"
let hasVideo = payload.dictionaryPayload["hasVideo"] as? Bool ?? true

RNVoipPushNotificationManager.addCompletionHandler(uuid, completionHandler: completion)
RNVoipPushNotificationManager.didReceiveIncomingPush(with: payload, forType: type.rawValue)

RNCallKeep.reportNewIncomingCall(uuid,
handle: handle,
handleType: "generic",
hasVideo: hasVideo,
localizedCallerName: callerName,
supportsHolding: false,
supportsDTMF: false,
supportsGrouping: false,
supportsUngrouping: false,
fromPushKit: true,
payload: nil,
withCompletionHandler: nil)`;
if (
!contents.includes("RNVoipPushNotificationManager.didReceiveIncomingPush")
) {
const functionSelector =
"pushRegistry(_:didReceiveIncomingPushWith:for:completion:)";

const codeBlock = findSwiftFunctionCodeBlock(contents, functionSelector);

if (!codeBlock) {
return insertContentsInsideSwiftClassBlock(
contents,
"class AppDelegate",
`
public func pushRegistry(
_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType,
completion: @escaping () -> Void
) {
${onIncomingPush}
}
`,
{ position: "tail" },
);
} else {
return insertContentsInsideSwiftFunctionBlock(
contents,
functionSelector,
onIncomingPush,
{ position: "tail" },
);
}
}

return contents;
}

function addToSwiftBridgingHeaderFile(
projectRoot: string,
projectName: string | undefined,
action: (contents: string) => string,
) {
if (!projectName) {
console.error("No project name provided");
return;
}

const bridgingHeaderPath = path.join(
projectRoot,
"ios",
projectName,
`${projectName}-Bridging-Header.h`,
);

if (!fs.existsSync(bridgingHeaderPath)) {
console.error(`File not found at: ${bridgingHeaderPath}`);
return;
}

const bridgingHeaderContents = fs.readFileSync(bridgingHeaderPath, "utf8");
const newBridgingHeaderContents = action(bridgingHeaderContents);

fs.writeFileSync(bridgingHeaderPath, newBridgingHeaderContents);
}

const withDisabledPrecompileBridgingHeader: ConfigPlugin = (config) => {
return withXcodeProject(config, (config) => {
const xcodeProject = config.modResults;

const configurations = xcodeProject.pbxXCBuildConfigurationSection();

for (const key in configurations) {
if (typeof configurations[key].buildSettings !== "undefined") {
const buildSettings = configurations[key].buildSettings;

buildSettings.SWIFT_PRECOMPILE_BRIDGING_HEADER = "NO";
}
}

return config;
});
};

const withVoipBackgroundModes: ConfigPlugin = (config) => {
return withInfoPlist(config, (config) => {
if (!config.modResults.UIBackgroundModes) {
config.modResults.UIBackgroundModes = [];
}

if (!config.modResults.UIBackgroundModes.includes("voip")) {
config.modResults.UIBackgroundModes.push("voip");
}

return config;
});
};

const withVoipPushNotification: ConfigPlugin = (config) => {
config = withDisabledPrecompileBridgingHeader(config);
config = withVoipBackgroundModes(config);
config = withVoipAppDelegate(config);
return config;
};

export default withVoipPushNotification;
9 changes: 9 additions & 0 deletions packages/react-native-voip-push-notification/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}