react-native-qpush 0.0.0-stage → 1.0.0
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/README.md +439 -2
- package/android/build.gradle +77 -0
- package/android/gradle.properties +5 -0
- package/android/proguard-rules.pro +5 -0
- package/android/src/main/AndroidManifest.xml +29 -0
- package/android/src/main/java/com/qpush/reactnative/QPushDeviceInfo.kt +96 -0
- package/android/src/main/java/com/qpush/reactnative/QPushEventBus.kt +153 -0
- package/android/src/main/java/com/qpush/reactnative/QPushFcm.kt +108 -0
- package/android/src/main/java/com/qpush/reactnative/QPushHttp.kt +92 -0
- package/android/src/main/java/com/qpush/reactnative/QPushImages.kt +103 -0
- package/android/src/main/java/com/qpush/reactnative/QPushLog.kt +39 -0
- package/android/src/main/java/com/qpush/reactnative/QPushMessagingService.kt +77 -0
- package/android/src/main/java/com/qpush/reactnative/QPushNotificationOpenActivity.kt +121 -0
- package/android/src/main/java/com/qpush/reactnative/QPushNotificationRenderer.kt +256 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPayload.kt +114 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPushModule.kt +368 -0
- package/android/src/main/java/com/qpush/reactnative/QPushPushPackage.kt +15 -0
- package/android/src/main/java/com/qpush/reactnative/QPushStore.kt +108 -0
- package/android/src/main/res/values/qpush_strings.xml +6 -0
- package/app.plugin.js +1 -0
- package/ios/Extension/QPushNotificationServiceExtension.swift +261 -0
- package/ios/Extension/QPushPushExtension.podspec +34 -0
- package/ios/QPushPush.m +62 -0
- package/ios/QPushPush.swift +186 -0
- package/ios/QPushPushBridge.h +5 -0
- package/ios/QPushPushCore.swift +457 -0
- package/ios/QPushPushHooks.m +237 -0
- package/ios/QPushPushSupport.swift +249 -0
- package/ios_nse_template/Info.plist +38 -0
- package/ios_nse_template/NotificationService.entitlements +10 -0
- package/ios_nse_template/NotificationService.swift +44 -0
- package/lib/NativeQPushPush.d.ts +44 -0
- package/lib/NativeQPushPush.js +4 -0
- package/lib/QPush.d.ts +117 -0
- package/lib/QPush.js +206 -0
- package/lib/core/QPushCore.d.ts +148 -0
- package/lib/core/QPushCore.js +1387 -0
- package/lib/core/constants.d.ts +37 -0
- package/lib/core/constants.js +40 -0
- package/lib/core/events.d.ts +11 -0
- package/lib/core/events.js +40 -0
- package/lib/core/http.d.ts +41 -0
- package/lib/core/http.js +123 -0
- package/lib/core/logger.d.ts +18 -0
- package/lib/core/logger.js +43 -0
- package/lib/core/operationQueue.d.ts +117 -0
- package/lib/core/operationQueue.js +264 -0
- package/lib/core/operations.d.ts +80 -0
- package/lib/core/operations.js +88 -0
- package/lib/core/payload.d.ts +20 -0
- package/lib/core/payload.js +187 -0
- package/lib/core/platform.d.ts +72 -0
- package/lib/core/platform.js +7 -0
- package/lib/core/state.d.ts +51 -0
- package/lib/core/state.js +74 -0
- package/lib/core/storage.d.ts +34 -0
- package/lib/core/storage.js +68 -0
- package/lib/core/subscription.d.ts +67 -0
- package/lib/core/subscription.js +98 -0
- package/lib/core/tags.d.ts +39 -0
- package/lib/core/tags.js +123 -0
- package/lib/core/url.d.ts +17 -0
- package/lib/core/url.js +64 -0
- package/lib/core/uuid.d.ts +2 -0
- package/lib/core/uuid.js +18 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +15 -0
- package/lib/notification.d.ts +67 -0
- package/lib/notification.js +73 -0
- package/lib/reactNativePlatform.d.ts +3 -0
- package/lib/reactNativePlatform.js +164 -0
- package/lib/types.d.ts +157 -0
- package/lib/types.js +31 -0
- package/package.json +70 -4
- package/plugin/withQPush.js +330 -0
- package/react-native-qpush.podspec +30 -0
- package/react-native.config.js +18 -0
- package/src/NativeQPushPush.ts +57 -0
- package/src/QPush.ts +292 -0
- package/src/core/QPushCore.ts +1515 -0
- package/src/core/constants.ts +42 -0
- package/src/core/events.ts +34 -0
- package/src/core/http.ts +146 -0
- package/src/core/logger.ts +42 -0
- package/src/core/operationQueue.ts +324 -0
- package/src/core/operations.ts +166 -0
- package/src/core/payload.ts +175 -0
- package/src/core/platform.ts +84 -0
- package/src/core/state.ts +127 -0
- package/src/core/storage.ts +76 -0
- package/src/core/subscription.ts +133 -0
- package/src/core/tags.ts +116 -0
- package/src/core/url.ts +58 -0
- package/src/core/uuid.ts +13 -0
- package/src/index.ts +23 -0
- package/src/notification.ts +113 -0
- package/src/reactNativePlatform.ts +172 -0
- package/src/types.ts +174 -0
package/package.json
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-qpush",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React Native and Expo push notification SDK for qpush: OneSignal-style API, rich notifications, delivery and click reports, and the qpush cloud API.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"react-native": "src/index.ts",
|
|
8
|
+
"source": "src/index.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"lib",
|
|
12
|
+
"android/build.gradle",
|
|
13
|
+
"android/gradle.properties",
|
|
14
|
+
"android/src",
|
|
15
|
+
"android/proguard-rules.pro",
|
|
16
|
+
"ios/*.h",
|
|
17
|
+
"ios/*.m",
|
|
18
|
+
"ios/*.swift",
|
|
19
|
+
"ios/Extension",
|
|
20
|
+
"ios_nse_template",
|
|
21
|
+
"plugin",
|
|
22
|
+
"app.plugin.js",
|
|
23
|
+
"react-native.config.js",
|
|
24
|
+
"react-native-qpush.podspec",
|
|
25
|
+
"!**/__tests__",
|
|
26
|
+
"!android/build",
|
|
27
|
+
"!android/.gradle"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.build.json",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"test": "jest",
|
|
33
|
+
"prepare": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"react-native",
|
|
37
|
+
"push",
|
|
38
|
+
"notifications",
|
|
39
|
+
"fcm",
|
|
40
|
+
"apns",
|
|
41
|
+
"onesignal",
|
|
42
|
+
"qpush",
|
|
43
|
+
"expo"
|
|
44
|
+
],
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"author": "qpush (https://qpush.dev)",
|
|
47
|
+
"homepage": "https://qpush.dev",
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"expo": ">=50.0.0",
|
|
50
|
+
"react": "*",
|
|
51
|
+
"react-native": ">=0.71.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"expo": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@expo/config-plugins": "^58.0.9",
|
|
60
|
+
"@types/jest": "^29.5.14",
|
|
61
|
+
"@types/node": "^20.17.0",
|
|
62
|
+
"@types/react": "^19.1.4",
|
|
63
|
+
"jest": "^29.7.0",
|
|
64
|
+
"react": "^19.1.4",
|
|
65
|
+
"react-native": "0.81.6",
|
|
66
|
+
"ts-jest": "^29.4.5",
|
|
67
|
+
"typescript": "~5.9.3"
|
|
68
|
+
},
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo config plugin for react-native-qpush (mirrors onesignal-expo-plugin).
|
|
3
|
+
*
|
|
4
|
+
* iOS:
|
|
5
|
+
* - entitlements: aps-environment + App Group `group.{bundle_id}.qpush`
|
|
6
|
+
* - Info.plist: UIBackgroundModes `remote-notification`
|
|
7
|
+
* - Notification Service Extension target `QPushNotificationServiceExtension`
|
|
8
|
+
* created from ios_nse_template/ (unless `disableNSE`), its Podfile entry and
|
|
9
|
+
* the EAS credentials config for the extension.
|
|
10
|
+
* Android:
|
|
11
|
+
* - optional default small icon (`smallIcon`) and accent color (`accentColor`).
|
|
12
|
+
*
|
|
13
|
+
* Usage (app.json):
|
|
14
|
+
* "plugins": [["react-native-qpush", { "mode": "development" }]]
|
|
15
|
+
*/
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
let configPlugins;
|
|
20
|
+
try {
|
|
21
|
+
configPlugins = require('expo/config-plugins');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
configPlugins = require('@expo/config-plugins');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
AndroidConfig,
|
|
28
|
+
createRunOncePlugin,
|
|
29
|
+
withAndroidManifest,
|
|
30
|
+
withDangerousMod,
|
|
31
|
+
withEntitlementsPlist,
|
|
32
|
+
withInfoPlist,
|
|
33
|
+
withXcodeProject,
|
|
34
|
+
} = configPlugins;
|
|
35
|
+
|
|
36
|
+
const pkg = require('../package.json');
|
|
37
|
+
|
|
38
|
+
const NSE_TARGET_NAME = 'QPushNotificationServiceExtension';
|
|
39
|
+
const NSE_SOURCE_FILE = 'NotificationService.swift';
|
|
40
|
+
const NSE_INFO_PLIST = `${NSE_TARGET_NAME}-Info.plist`;
|
|
41
|
+
const NSE_ENTITLEMENTS = `${NSE_TARGET_NAME}.entitlements`;
|
|
42
|
+
const NSE_EXT_FILES = [NSE_SOURCE_FILE, NSE_INFO_PLIST, NSE_ENTITLEMENTS];
|
|
43
|
+
const TEMPLATE_DIR = path.join(__dirname, '..', 'ios_nse_template');
|
|
44
|
+
const PACKAGE_ROOT = path.join(__dirname, '..');
|
|
45
|
+
const DEFAULT_NSE_DEPLOYMENT_TARGET = '15.1';
|
|
46
|
+
const PODFILE_MARKER = `target '${NSE_TARGET_NAME}' do`;
|
|
47
|
+
const ANDROID_ICON_NAME = 'ic_stat_qpush_default';
|
|
48
|
+
const ANDROID_ACCENT_META = 'com.qpush.NotificationAccentColor.DEFAULT';
|
|
49
|
+
|
|
50
|
+
function log(message) {
|
|
51
|
+
console.log(`› [react-native-qpush] ${message}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateProps(props) {
|
|
55
|
+
const options = props || {};
|
|
56
|
+
const mode = options.mode || 'development';
|
|
57
|
+
if (mode !== 'development' && mode !== 'production') {
|
|
58
|
+
throw new Error(`react-native-qpush: "mode" must be "development" or "production" (got "${mode}")`);
|
|
59
|
+
}
|
|
60
|
+
return { ...options, mode };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function requireBundleId(config) {
|
|
64
|
+
const bundleId = config.ios && config.ios.bundleIdentifier;
|
|
65
|
+
if (!bundleId) {
|
|
66
|
+
throw new Error('react-native-qpush: "ios.bundleIdentifier" is required in your app config');
|
|
67
|
+
}
|
|
68
|
+
return bundleId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function appGroupName(config, props) {
|
|
72
|
+
return props.appGroup || `group.${requireBundleId(config)}.qpush`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// -----------------------------------------------------------------------------
|
|
76
|
+
// iOS: entitlements & Info.plist
|
|
77
|
+
// -----------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
const withQPushEntitlements = (config, props) =>
|
|
80
|
+
withEntitlementsPlist(config, (cfg) => {
|
|
81
|
+
cfg.modResults['aps-environment'] = props.mode;
|
|
82
|
+
const key = 'com.apple.security.application-groups';
|
|
83
|
+
const groups = Array.isArray(cfg.modResults[key]) ? cfg.modResults[key] : [];
|
|
84
|
+
const group = appGroupName(cfg, props);
|
|
85
|
+
if (!groups.includes(group)) groups.push(group);
|
|
86
|
+
cfg.modResults[key] = groups;
|
|
87
|
+
return cfg;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const withQPushInfoPlist = (config, props) =>
|
|
91
|
+
withInfoPlist(config, (cfg) => {
|
|
92
|
+
const modes = Array.isArray(cfg.modResults.UIBackgroundModes) ? cfg.modResults.UIBackgroundModes : [];
|
|
93
|
+
if (!modes.includes('remote-notification')) modes.push('remote-notification');
|
|
94
|
+
cfg.modResults.UIBackgroundModes = modes;
|
|
95
|
+
if (props.appGroup) cfg.modResults.QPushAppGroup = props.appGroup;
|
|
96
|
+
if (props.suppressLaunchUrls === true) cfg.modResults.QPush_suppress_launch_urls = true;
|
|
97
|
+
if (props.mode === 'development') {
|
|
98
|
+
// Development builds often talk to a plain-HTTP QPush server on the
|
|
99
|
+
// LAN (e.g. http://my-mac.local:8080). IP addresses are already exempt
|
|
100
|
+
// from App Transport Security; this also allows .local hostnames.
|
|
101
|
+
const ats = cfg.modResults.NSAppTransportSecurity && typeof cfg.modResults.NSAppTransportSecurity === 'object'
|
|
102
|
+
? cfg.modResults.NSAppTransportSecurity
|
|
103
|
+
: {};
|
|
104
|
+
ats.NSAllowsLocalNetworking = true;
|
|
105
|
+
cfg.modResults.NSAppTransportSecurity = ats;
|
|
106
|
+
}
|
|
107
|
+
return cfg;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// -----------------------------------------------------------------------------
|
|
111
|
+
// iOS: Notification Service Extension
|
|
112
|
+
// -----------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
function nsePodfileSnippet(iosRoot) {
|
|
115
|
+
// The helper podspec is in ios/Extension/, not in the package root (see the podspec).
|
|
116
|
+
const podspecDir = path.join(fs.realpathSync(PACKAGE_ROOT), 'ios', 'Extension');
|
|
117
|
+
let podPath = path.relative(iosRoot, podspecDir).split(path.sep).join('/');
|
|
118
|
+
if (!podPath.startsWith('.')) podPath = `./${podPath}`;
|
|
119
|
+
return `
|
|
120
|
+
${PODFILE_MARKER}
|
|
121
|
+
pod 'QPushPushExtension', :path => '${podPath}'
|
|
122
|
+
end
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function updatePodfile(iosRoot) {
|
|
127
|
+
const podfilePath = path.join(iosRoot, 'Podfile');
|
|
128
|
+
if (!fs.existsSync(podfilePath)) {
|
|
129
|
+
log('Podfile not found, skipping the Notification Service Extension pod');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const podfile = fs.readFileSync(podfilePath, 'utf8');
|
|
133
|
+
if (podfile.includes(PODFILE_MARKER)) {
|
|
134
|
+
log(`Podfile already contains ${NSE_TARGET_NAME}, skipping`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
fs.appendFileSync(podfilePath, nsePodfileSnippet(iosRoot));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function fillTemplate(text, values) {
|
|
141
|
+
return Object.keys(values).reduce((result, key) => result.split(`{{${key}}}`).join(values[key]), text);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const withQPushNseFiles = (config, props) =>
|
|
145
|
+
withDangerousMod(config, [
|
|
146
|
+
'ios',
|
|
147
|
+
async (cfg) => {
|
|
148
|
+
const iosRoot = cfg.modRequest.platformProjectRoot;
|
|
149
|
+
const targetDir = path.join(iosRoot, NSE_TARGET_NAME);
|
|
150
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
151
|
+
|
|
152
|
+
const sourceFile = props.nseFilePath
|
|
153
|
+
? path.resolve(cfg.modRequest.projectRoot, props.nseFilePath)
|
|
154
|
+
: path.join(TEMPLATE_DIR, NSE_SOURCE_FILE);
|
|
155
|
+
if (!fs.existsSync(sourceFile)) {
|
|
156
|
+
throw new Error(`react-native-qpush: NSE source file not found: ${sourceFile}`);
|
|
157
|
+
}
|
|
158
|
+
fs.copyFileSync(sourceFile, path.join(targetDir, NSE_SOURCE_FILE));
|
|
159
|
+
|
|
160
|
+
const values = {
|
|
161
|
+
VERSION: String(cfg.version || '1.0.0'),
|
|
162
|
+
BUILD_NUMBER: String((cfg.ios && cfg.ios.buildNumber) || '1'),
|
|
163
|
+
APP_GROUP: appGroupName(cfg, props),
|
|
164
|
+
};
|
|
165
|
+
const infoPlist = fs.readFileSync(path.join(TEMPLATE_DIR, 'Info.plist'), 'utf8');
|
|
166
|
+
fs.writeFileSync(path.join(targetDir, NSE_INFO_PLIST), fillTemplate(infoPlist, values));
|
|
167
|
+
const entitlements = fs.readFileSync(path.join(TEMPLATE_DIR, 'NotificationService.entitlements'), 'utf8');
|
|
168
|
+
fs.writeFileSync(path.join(targetDir, NSE_ENTITLEMENTS), fillTemplate(entitlements, values));
|
|
169
|
+
|
|
170
|
+
updatePodfile(iosRoot);
|
|
171
|
+
return cfg;
|
|
172
|
+
},
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
const withQPushXcodeProject = (config, props) =>
|
|
176
|
+
withXcodeProject(config, (cfg) => {
|
|
177
|
+
const project = cfg.modResults;
|
|
178
|
+
const bundleId = requireBundleId(cfg);
|
|
179
|
+
|
|
180
|
+
// xcode stores names created by addTarget with quotes: check both forms.
|
|
181
|
+
if (project.pbxTargetByName(NSE_TARGET_NAME) || project.pbxTargetByName(`"${NSE_TARGET_NAME}"`)) {
|
|
182
|
+
log(`${NSE_TARGET_NAME} already exists in the Xcode project, skipping`);
|
|
183
|
+
return cfg;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Group with the extension files, attached to the project's main group.
|
|
187
|
+
const extensionGroup = project.addPbxGroup(NSE_EXT_FILES, NSE_TARGET_NAME, NSE_TARGET_NAME);
|
|
188
|
+
const groups = project.hash.project.objects.PBXGroup;
|
|
189
|
+
Object.keys(groups).forEach((key) => {
|
|
190
|
+
const group = groups[key];
|
|
191
|
+
if (typeof group === 'object' && group.name === undefined && group.path === undefined) {
|
|
192
|
+
project.addToPbxGroup(extensionGroup.uuid, key);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// xcode's addTarget expects these sections to exist (missing in single-target projects).
|
|
197
|
+
const objects = project.hash.project.objects;
|
|
198
|
+
objects.PBXTargetDependency = objects.PBXTargetDependency || {};
|
|
199
|
+
objects.PBXContainerItemProxy = objects.PBXContainerItemProxy || {};
|
|
200
|
+
|
|
201
|
+
// Adds the target, its build configurations, the "embed" copy phase in the
|
|
202
|
+
// app target and the target dependency.
|
|
203
|
+
const target = project.addTarget(NSE_TARGET_NAME, 'app_extension', NSE_TARGET_NAME, `${bundleId}.${NSE_TARGET_NAME}`);
|
|
204
|
+
project.addBuildPhase([NSE_SOURCE_FILE], 'PBXSourcesBuildPhase', 'Sources', target.uuid);
|
|
205
|
+
project.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', target.uuid);
|
|
206
|
+
project.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', target.uuid);
|
|
207
|
+
|
|
208
|
+
const configurations = project.pbxXCBuildConfigurationSection();
|
|
209
|
+
Object.keys(configurations).forEach((key) => {
|
|
210
|
+
const buildSettings = configurations[key].buildSettings;
|
|
211
|
+
if (!buildSettings || buildSettings.PRODUCT_NAME !== `"${NSE_TARGET_NAME}"`) return;
|
|
212
|
+
buildSettings.IPHONEOS_DEPLOYMENT_TARGET = props.iPhoneDeploymentTarget || DEFAULT_NSE_DEPLOYMENT_TARGET;
|
|
213
|
+
buildSettings.TARGETED_DEVICE_FAMILY = '"1,2"';
|
|
214
|
+
buildSettings.CODE_SIGN_ENTITLEMENTS = `${NSE_TARGET_NAME}/${NSE_ENTITLEMENTS}`;
|
|
215
|
+
buildSettings.CODE_SIGN_STYLE = 'Automatic';
|
|
216
|
+
buildSettings.SWIFT_VERSION = '5.0';
|
|
217
|
+
buildSettings.MARKETING_VERSION = String(cfg.version || '1.0.0');
|
|
218
|
+
buildSettings.CURRENT_PROJECT_VERSION = String((cfg.ios && cfg.ios.buildNumber) || '1');
|
|
219
|
+
if (props.devTeam) buildSettings.DEVELOPMENT_TEAM = props.devTeam;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (props.devTeam) {
|
|
223
|
+
project.addTargetAttribute('DevelopmentTeam', props.devTeam, target);
|
|
224
|
+
project.addTargetAttribute('DevelopmentTeam', props.devTeam);
|
|
225
|
+
}
|
|
226
|
+
return cfg;
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
/** Lets EAS Build manage credentials (provisioning profile) of the extension. */
|
|
230
|
+
const withQPushEasCredentials = (config, props) => {
|
|
231
|
+
const bundleId = requireBundleId(config);
|
|
232
|
+
const extra = config.extra || {};
|
|
233
|
+
const eas = extra.eas || {};
|
|
234
|
+
const build = eas.build || {};
|
|
235
|
+
const experimental = build.experimental || {};
|
|
236
|
+
const ios = experimental.ios || {};
|
|
237
|
+
const appExtensions = Array.isArray(ios.appExtensions) ? ios.appExtensions : [];
|
|
238
|
+
if (!appExtensions.some((extension) => extension.targetName === NSE_TARGET_NAME)) {
|
|
239
|
+
appExtensions.push({
|
|
240
|
+
targetName: NSE_TARGET_NAME,
|
|
241
|
+
bundleIdentifier: `${bundleId}.${NSE_TARGET_NAME}`,
|
|
242
|
+
entitlements: {
|
|
243
|
+
'com.apple.security.application-groups': [appGroupName(config, props)],
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
config.extra = {
|
|
248
|
+
...extra,
|
|
249
|
+
eas: {
|
|
250
|
+
...eas,
|
|
251
|
+
build: {
|
|
252
|
+
...build,
|
|
253
|
+
experimental: {
|
|
254
|
+
...experimental,
|
|
255
|
+
ios: { ...ios, appExtensions },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
return config;
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// -----------------------------------------------------------------------------
|
|
264
|
+
// Android (optional)
|
|
265
|
+
// -----------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
const withQPushAndroidIcon = (config, props) => {
|
|
268
|
+
if (!props.smallIcon) return config;
|
|
269
|
+
return withDangerousMod(config, [
|
|
270
|
+
'android',
|
|
271
|
+
async (cfg) => {
|
|
272
|
+
const source = path.resolve(cfg.modRequest.projectRoot, props.smallIcon);
|
|
273
|
+
if (!fs.existsSync(source)) {
|
|
274
|
+
throw new Error(`react-native-qpush: smallIcon not found: ${source}`);
|
|
275
|
+
}
|
|
276
|
+
const extension = path.extname(source).toLowerCase() || '.png';
|
|
277
|
+
const drawableDir = path.join(cfg.modRequest.platformProjectRoot, 'app', 'src', 'main', 'res', 'drawable');
|
|
278
|
+
fs.mkdirSync(drawableDir, { recursive: true });
|
|
279
|
+
fs.copyFileSync(source, path.join(drawableDir, `${ANDROID_ICON_NAME}${extension}`));
|
|
280
|
+
return cfg;
|
|
281
|
+
},
|
|
282
|
+
]);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const withQPushAndroidAccentColor = (config, props) => {
|
|
286
|
+
if (!props.accentColor) return config;
|
|
287
|
+
return withAndroidManifest(config, (cfg) => {
|
|
288
|
+
const application = AndroidConfig.Manifest.getMainApplicationOrThrow(cfg.modResults);
|
|
289
|
+
AndroidConfig.Manifest.addMetaDataItemToMainApplication(
|
|
290
|
+
application,
|
|
291
|
+
ANDROID_ACCENT_META,
|
|
292
|
+
String(props.accentColor).replace(/^#/, ''),
|
|
293
|
+
);
|
|
294
|
+
return cfg;
|
|
295
|
+
});
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// -----------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @param {object} config Expo config
|
|
302
|
+
* @param {{
|
|
303
|
+
* mode?: 'development' | 'production',
|
|
304
|
+
* devTeam?: string,
|
|
305
|
+
* iPhoneDeploymentTarget?: string,
|
|
306
|
+
* disableNSE?: boolean,
|
|
307
|
+
* nseFilePath?: string,
|
|
308
|
+
* appGroup?: string,
|
|
309
|
+
* suppressLaunchUrls?: boolean,
|
|
310
|
+
* smallIcon?: string,
|
|
311
|
+
* accentColor?: string,
|
|
312
|
+
* }} rawProps
|
|
313
|
+
*/
|
|
314
|
+
const withQPush = (config, rawProps) => {
|
|
315
|
+
const props = validateProps(rawProps);
|
|
316
|
+
config = withQPushEntitlements(config, props);
|
|
317
|
+
config = withQPushInfoPlist(config, props);
|
|
318
|
+
if (!props.disableNSE) {
|
|
319
|
+
config = withQPushNseFiles(config, props);
|
|
320
|
+
config = withQPushXcodeProject(config, props);
|
|
321
|
+
config = withQPushEasCredentials(config, props);
|
|
322
|
+
}
|
|
323
|
+
config = withQPushAndroidIcon(config, props);
|
|
324
|
+
config = withQPushAndroidAccentColor(config, props);
|
|
325
|
+
return config;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
module.exports = createRunOncePlugin(withQPush, pkg.name, pkg.version);
|
|
329
|
+
module.exports.withQPush = withQPush;
|
|
330
|
+
module.exports.NSE_TARGET_NAME = NSE_TARGET_NAME;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "react-native-qpush"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => "13.0" }
|
|
14
|
+
s.source = { :git => package["repository"]["url"], :tag => "v#{s.version}" }
|
|
15
|
+
|
|
16
|
+
# The Notification Service Extension helper lives in its own pod
|
|
17
|
+
# (ios/Extension/QPushPushExtension.podspec) because extensions cannot link React Native.
|
|
18
|
+
s.source_files = "ios/*.{h,m,mm,swift}"
|
|
19
|
+
s.public_header_files = "ios/QPushPushBridge.h"
|
|
20
|
+
s.swift_version = "5.0"
|
|
21
|
+
s.frameworks = "UserNotifications", "UIKit"
|
|
22
|
+
|
|
23
|
+
s.pod_target_xcconfig = {
|
|
24
|
+
"DEFINES_MODULE" => "YES",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Classic bridge module: works on the old architecture and, through React
|
|
28
|
+
# Native's interop layer, on the New Architecture / bridgeless mode.
|
|
29
|
+
s.dependency "React-Core"
|
|
30
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native CLI autolinking config.
|
|
3
|
+
* The NSE helper pod (QPushPushExtension) is not autolinked: it belongs to the
|
|
4
|
+
* extension target (added by the Expo plugin or manually, see README).
|
|
5
|
+
*/
|
|
6
|
+
module.exports = {
|
|
7
|
+
dependency: {
|
|
8
|
+
platforms: {
|
|
9
|
+
android: {
|
|
10
|
+
packageImportPath: 'import com.qpush.reactnative.QPushPushPackage;',
|
|
11
|
+
packageInstance: 'new QPushPushPackage()',
|
|
12
|
+
},
|
|
13
|
+
ios: {
|
|
14
|
+
podspecPath: require('path').join(__dirname, 'react-native-qpush.podspec'),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TurboModule spec of the native module `QPushPush`.
|
|
3
|
+
*
|
|
4
|
+
* The native implementations are classic bridge modules
|
|
5
|
+
* (`ReactContextBaseJavaModule` / `RCTEventEmitter`). They work on the old
|
|
6
|
+
* architecture and, through React Native's interop layer, on the New
|
|
7
|
+
* Architecture / bridgeless mode. This spec documents the contract and gives
|
|
8
|
+
* typed access through `TurboModuleRegistry` (which falls back to
|
|
9
|
+
* `NativeModules` on the old architecture). Codegen is intentionally not
|
|
10
|
+
* configured in package.json, so no generated code is required at build time.
|
|
11
|
+
*/
|
|
12
|
+
import type { TurboModule } from 'react-native';
|
|
13
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
14
|
+
|
|
15
|
+
export interface Spec extends TurboModule {
|
|
16
|
+
// Key/value storage (SharedPreferences / UserDefaults). `app_id`,
|
|
17
|
+
// `server_url` and `subscription_id` are also read by native code and, on
|
|
18
|
+
// iOS, mirrored into the App Group `group.{bundle_id}.qpush`.
|
|
19
|
+
getItem(key: string): Promise<string | null>;
|
|
20
|
+
setItem(key: string, value: string): Promise<void>;
|
|
21
|
+
removeItem(key: string): Promise<void>;
|
|
22
|
+
|
|
23
|
+
/** DeviceInfo (see src/core/subscription.ts). */
|
|
24
|
+
getDeviceInfo(): Promise<Object>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Android: FCM params from android_params.js (or null for the default
|
|
28
|
+
* FirebaseApp). iOS: ignored. Resolves `{ token, notificationTypes?, error? }`.
|
|
29
|
+
*/
|
|
30
|
+
registerForPush(fcmParams: Object | null): Promise<Object>;
|
|
31
|
+
|
|
32
|
+
/** 'notDetermined' | 'denied' | 'granted' | 'provisional' | 'ephemeral' */
|
|
33
|
+
getPermissionStatus(): Promise<string>;
|
|
34
|
+
requestPermission(fallbackToSettings: boolean): Promise<boolean>;
|
|
35
|
+
registerForProvisionalAuthorization(): Promise<boolean>;
|
|
36
|
+
canRequestPermission(): Promise<boolean>;
|
|
37
|
+
|
|
38
|
+
clearAll(): void;
|
|
39
|
+
removeNotification(notificationId: string): void;
|
|
40
|
+
removeGroupedNotifications(groupKey: string): void;
|
|
41
|
+
clearBadge(): Promise<number>;
|
|
42
|
+
|
|
43
|
+
setJsReady(): void;
|
|
44
|
+
displayForegroundNotification(displayId: string): void;
|
|
45
|
+
preventForegroundDisplay(displayId: string): void;
|
|
46
|
+
setAutoOpenUrls(enabled: boolean): void;
|
|
47
|
+
setLogLevel(level: number): void;
|
|
48
|
+
|
|
49
|
+
beginBackgroundTask(): Promise<number>;
|
|
50
|
+
endBackgroundTask(taskId: number): void;
|
|
51
|
+
|
|
52
|
+
// NativeEventEmitter plumbing.
|
|
53
|
+
addListener(eventName: string): void;
|
|
54
|
+
removeListeners(count: number): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default TurboModuleRegistry.get<Spec>('QPushPush');
|