react-native-notify-kit 10.3.2 → 10.4.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.
Files changed (39) hide show
  1. package/README.md +102 -12
  2. package/app.plugin.js +3 -0
  3. package/cli/dist/commands/initNse.js +8 -9
  4. package/cli/dist/commands/initNse.js.map +1 -1
  5. package/cli/dist/lib/detectProject.d.ts +1 -4
  6. package/cli/dist/lib/detectProject.js +3 -32
  7. package/cli/dist/lib/detectProject.js.map +1 -1
  8. package/cli/dist/lib/initNseCore.d.ts +8 -0
  9. package/cli/dist/lib/initNseCore.js +197 -0
  10. package/cli/dist/lib/initNseCore.js.map +1 -0
  11. package/cli/dist/lib/patchPodfile.d.ts +9 -0
  12. package/cli/dist/lib/patchPodfile.js +22 -15
  13. package/cli/dist/lib/patchPodfile.js.map +1 -1
  14. package/cli/dist/lib/patchXcodeProject.d.ts +16 -0
  15. package/cli/dist/lib/patchXcodeProject.js +54 -26
  16. package/cli/dist/lib/patchXcodeProject.js.map +1 -1
  17. package/cli/dist/lib/writeTemplates.js +5 -14
  18. package/cli/dist/lib/writeTemplates.js.map +1 -1
  19. package/dist/version.d.ts +1 -1
  20. package/dist/version.js +1 -1
  21. package/ios/NotifeeCore/NotifeeCore+NSNotificationCenter.m +6 -0
  22. package/ios/NotifeeCore/NotifeeCore+UNUserNotificationCenter.m +95 -20
  23. package/ios/NotifeeCore/NotifeeCore.m +13 -0
  24. package/ios/RNNotifee/NotifeeApiModule.mm +7 -0
  25. package/ios/RNNotifee/NotifeeExtensionHelper.h +2 -2
  26. package/ios/RNNotifee/NotifeeExtensionHelper.m +1 -0
  27. package/package.json +16 -3
  28. package/plugin/build/android/withNotifyKitAndroidManifest.js +236 -0
  29. package/plugin/build/index.js +62 -0
  30. package/plugin/build/ios/withNotifyKitIosNseAppExtension.js +142 -0
  31. package/plugin/build/ios/withNotifyKitIosNseFiles.js +79 -0
  32. package/plugin/build/ios/withNotifyKitIosNsePodfile.js +130 -0
  33. package/plugin/build/ios/withNotifyKitIosNseXcodeProject.js +49 -0
  34. package/plugin/build/options.js +224 -0
  35. package/plugin/build/shared/nse/index.js +7 -0
  36. package/plugin/build/shared/nse/initNseCore.js +220 -0
  37. package/plugin/build/shared/nse/patchPodfile.js +261 -0
  38. package/plugin/build/shared/nse/patchXcodeProject.js +199 -0
  39. package/src/version.ts +1 -1
@@ -0,0 +1,261 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_PACKAGE_PATH_FROM_IOS = '../node_modules/react-native-notify-kit';
4
+ const RNFB_INFO_PLIST_INPUT_PATH = '$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)';
5
+ const RNFB_POST_INSTALL_MARKER =
6
+ 'NotifyKitNSE: avoid an Xcode build cycle between the embedded app extension';
7
+
8
+ function patchPodfileForNotifyKitNse(podfileText, options) {
9
+ let patched = podfileText;
10
+ let changed = false;
11
+ const packagePathFromIos = options.packagePathFromIos ?? DEFAULT_PACKAGE_PATH_FROM_IOS;
12
+ const placement = options.placement ?? 'nested';
13
+ const useFrameworks = options.useFrameworks ?? false;
14
+
15
+ if (!hasUncommentedTarget(patched, options.targetName)) {
16
+ const withNseTarget = insertNseTarget(
17
+ patched,
18
+ options.targetName,
19
+ packagePathFromIos,
20
+ placement,
21
+ useFrameworks,
22
+ );
23
+ if (withNseTarget === null) {
24
+ return { contents: patched, didChange: changed };
25
+ }
26
+ patched = withNseTarget;
27
+ changed = true;
28
+ }
29
+
30
+ const withRnfbPatch = ensureRnfbPostInstallPatch(patched);
31
+ if (withRnfbPatch !== patched) {
32
+ patched = withRnfbPatch;
33
+ changed = true;
34
+ }
35
+
36
+ return { contents: patched, didChange: changed };
37
+ }
38
+
39
+ function insertNseTarget(content, targetName, packagePathFromIos, placement, useFrameworks) {
40
+ const block = buildNseTargetBlock(targetName, packagePathFromIos, placement, useFrameworks);
41
+
42
+ const targetMatch = content.match(/^target\s+['"][^'"]+['"]\s+do/m);
43
+ if (!targetMatch || targetMatch.index === undefined) {
44
+ return appendNseTarget(content, block, placement);
45
+ }
46
+
47
+ const targetEndIndex = findMatchingRubyBlockEnd(content, targetMatch.index);
48
+
49
+ if (targetEndIndex === -1) {
50
+ throw new Error(
51
+ "Could not locate main app target's closing 'end' in Podfile. NSE insertion aborted. " +
52
+ 'Your Podfile may use abstract_target, unusual formatting, or nested blocks - ' +
53
+ 'add the NSE target manually per the legacy guide.',
54
+ );
55
+ }
56
+
57
+ if (placement === 'topLevel') {
58
+ const insertIndex = findLineEnd(content, targetEndIndex);
59
+ const separator = insertIndex > 0 && content[insertIndex - 1] === '\n' ? '\n' : '\n\n';
60
+ return content.slice(0, insertIndex) + separator + block + content.slice(insertIndex);
61
+ }
62
+
63
+ const insertIndex = targetEndIndex;
64
+ return content.slice(0, insertIndex) + block + content.slice(insertIndex);
65
+ }
66
+
67
+ function buildNseTargetBlock(targetName, packagePathFromIos, placement, useFrameworks) {
68
+ if (placement === 'topLevel') {
69
+ const useFrameworksLine = buildUseFrameworksLine(useFrameworks);
70
+
71
+ return (
72
+ `target '${targetName}' do\n` +
73
+ useFrameworksLine +
74
+ ` pod 'RNNotifeeCore', :path => '${packagePathFromIos}'\n` +
75
+ `end\n`
76
+ );
77
+ }
78
+
79
+ return (
80
+ `\n target '${targetName}' do\n` +
81
+ ` inherit! :search_paths\n` +
82
+ ` pod 'RNNotifeeCore', :path => '${packagePathFromIos}'\n` +
83
+ ` end\n`
84
+ );
85
+ }
86
+
87
+ function buildUseFrameworksLine(useFrameworks) {
88
+ if (useFrameworks === 'static') {
89
+ return ` use_frameworks! :linkage => :static\n`;
90
+ }
91
+
92
+ if (useFrameworks === 'dynamic') {
93
+ return ` use_frameworks! :linkage => :dynamic\n`;
94
+ }
95
+
96
+ if (useFrameworks === true) {
97
+ return ` use_frameworks!\n`;
98
+ }
99
+
100
+ return '';
101
+ }
102
+
103
+ function appendNseTarget(content, block, placement) {
104
+ if (placement === 'nested') {
105
+ return content + '\n' + block;
106
+ }
107
+
108
+ const separator = content.length === 0 ? '' : content.endsWith('\n') ? '\n' : '\n\n';
109
+ return content + separator + block;
110
+ }
111
+
112
+ function findLineEnd(content, lineStartIndex) {
113
+ const newlineIndex = content.indexOf('\n', lineStartIndex);
114
+ return newlineIndex === -1 ? content.length : newlineIndex + 1;
115
+ }
116
+
117
+ function ensureRnfbPostInstallPatch(content) {
118
+ if (content.includes(RNFB_POST_INSTALL_MARKER)) {
119
+ return content;
120
+ }
121
+
122
+ const postInstall = findPostInstallBlock(content);
123
+ if (postInstall) {
124
+ const snippet =
125
+ '\n' + buildRnfbPostInstallSnippet(postInstall.bodyIndent, postInstall.paramName);
126
+ return content.slice(0, postInstall.endIndex) + snippet + content.slice(postInstall.endIndex);
127
+ }
128
+
129
+ const separator = content.trim().length === 0 || content.endsWith('\n') ? '\n' : '\n\n';
130
+ return (
131
+ content +
132
+ separator +
133
+ 'post_install do |installer|\n' +
134
+ buildRnfbPostInstallSnippet(' ', 'installer') +
135
+ 'end\n'
136
+ );
137
+ }
138
+
139
+ function buildRnfbPostInstallSnippet(indent, installerParamName) {
140
+ return [
141
+ `${indent}# ${RNFB_POST_INSTALL_MARKER}`,
142
+ `${indent}# and React Native Firebase's Info.plist processing phase.`,
143
+ `${indent}rnfb_info_plist_input_path = '${RNFB_INFO_PLIST_INPUT_PATH}'`,
144
+ `${indent}rnfb_phase_names = [`,
145
+ `${indent} '[RNFB] Core Configuration',`,
146
+ `${indent} '[CP-User] [RNFB] Core Configuration',`,
147
+ `${indent}]`,
148
+ '',
149
+ `${indent}${installerParamName}.aggregate_targets.each do |aggregate_target|`,
150
+ `${indent} aggregate_target.target_definition.script_phases.each do |script_phase|`,
151
+ `${indent} next unless rnfb_phase_names.include?(script_phase[:name])`,
152
+ `${indent} next unless script_phase[:input_files]`,
153
+ '',
154
+ `${indent} script_phase[:input_files].delete(rnfb_info_plist_input_path)`,
155
+ `${indent} end`,
156
+ '',
157
+ `${indent} user_project = aggregate_target.user_project`,
158
+ `${indent} next unless user_project`,
159
+ '',
160
+ `${indent} rnfb_input_path_removed = false`,
161
+ '',
162
+ `${indent} user_project.targets.each do |target|`,
163
+ `${indent} target.shell_script_build_phases.each do |phase|`,
164
+ `${indent} next unless rnfb_phase_names.include?(phase.name)`,
165
+ `${indent} next unless phase.input_paths`,
166
+ '',
167
+ `${indent} if phase.input_paths.delete(rnfb_info_plist_input_path)`,
168
+ `${indent} rnfb_input_path_removed = true`,
169
+ `${indent} end`,
170
+ `${indent} end`,
171
+ `${indent} end`,
172
+ '',
173
+ `${indent} user_project.save if rnfb_input_path_removed`,
174
+ `${indent}end`,
175
+ '',
176
+ ].join('\n');
177
+ }
178
+
179
+ function findPostInstallBlock(content) {
180
+ const postInstallPattern = /^([ \t]*)post_install\s+do\s+\|([^|]+)\|\s*(?:#.*)?$/gm;
181
+ let match;
182
+
183
+ while ((match = postInstallPattern.exec(content)) !== null) {
184
+ const endIndex = findMatchingRubyBlockEnd(content, match.index);
185
+ if (endIndex !== -1) {
186
+ return {
187
+ bodyIndent: `${match[1]} `,
188
+ endIndex,
189
+ paramName: match[2].trim(),
190
+ };
191
+ }
192
+ }
193
+
194
+ return null;
195
+ }
196
+
197
+ function findMatchingRubyBlockEnd(content, startIndex) {
198
+ const afterStart = content.slice(startIndex);
199
+ const lines = afterStart.split('\n');
200
+ let depth = 0;
201
+ let charIndex = startIndex;
202
+
203
+ for (const line of lines) {
204
+ const trimmed = stripRubyLineComment(line).trim();
205
+
206
+ depth += countRubyBlockOpeners(trimmed);
207
+
208
+ if (trimmed === 'end') {
209
+ depth--;
210
+ if (depth === 0) {
211
+ return charIndex;
212
+ }
213
+ }
214
+
215
+ charIndex += line.length + 1;
216
+ }
217
+
218
+ return -1;
219
+ }
220
+
221
+ function countRubyBlockOpeners(line) {
222
+ if (line.length === 0) {
223
+ return 0;
224
+ }
225
+
226
+ const startsWithKeywordBlock = /^(if|unless|case|begin|while|until|for|def|class|module)\b/.test(
227
+ line,
228
+ );
229
+ if (startsWithKeywordBlock) {
230
+ return 1;
231
+ }
232
+
233
+ if (/\bdo\b(\s*\|[^|]*\|)?\s*$/.test(line)) {
234
+ return 1;
235
+ }
236
+
237
+ return 0;
238
+ }
239
+
240
+ function stripRubyLineComment(line) {
241
+ return line.replace(/#.*$/, '');
242
+ }
243
+
244
+ function hasUncommentedTarget(content, targetName) {
245
+ const pattern = new RegExp(`target\\s+['"]${escapeRegex(targetName)}['"]`);
246
+ for (const line of content.split('\n')) {
247
+ const stripped = line.replace(/#.*$/, '');
248
+ if (pattern.test(stripped)) {
249
+ return true;
250
+ }
251
+ }
252
+ return false;
253
+ }
254
+
255
+ function escapeRegex(s) {
256
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
257
+ }
258
+
259
+ module.exports = {
260
+ patchPodfileForNotifyKitNse,
261
+ };
@@ -0,0 +1,199 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ function patchXcodeProjectForNotifyKitNse(proj, options) {
6
+ const { targetName, bundleIdentifier, parentTargetName, deploymentTarget = '15.1' } = options;
7
+ const warnings = [];
8
+
9
+ if (targetExists(proj, targetName)) {
10
+ return { didChange: false, warnings };
11
+ }
12
+
13
+ const target = proj.addTarget(targetName, 'app_extension', targetName);
14
+
15
+ if (!target || !target.uuid) {
16
+ throw new Error(`xcode library failed to create target '${targetName}'`);
17
+ }
18
+
19
+ const targetUuid = target.uuid;
20
+ const productUuid = getTargetProductUuid(target);
21
+
22
+ fixProductFileReference(proj, targetName);
23
+
24
+ proj.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', targetUuid);
25
+ proj.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', targetUuid);
26
+ proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', targetUuid);
27
+
28
+ const group = proj.addPbxGroup([], targetName, targetName);
29
+ proj.addSourceFile(
30
+ `${targetName}/NotificationService.swift`,
31
+ {
32
+ target: targetUuid,
33
+ },
34
+ group.uuid,
35
+ );
36
+
37
+ const hostUuid = findHostTarget(proj, parentTargetName);
38
+ if (hostUuid) {
39
+ addTargetDependencyManual(proj, hostUuid, targetUuid, targetName);
40
+ stripRnfbInfoPlistInputPath(proj, hostUuid);
41
+ }
42
+
43
+ setBuildSettings(proj, targetName, bundleIdentifier, deploymentTarget);
44
+
45
+ return {
46
+ didChange: true,
47
+ targetUuid,
48
+ productUuid,
49
+ hostTargetUuid: hostUuid ?? undefined,
50
+ warnings,
51
+ };
52
+ }
53
+
54
+ function findHostTarget(proj, parentTargetName) {
55
+ if (parentTargetName) {
56
+ return findTargetByName(proj, parentTargetName);
57
+ }
58
+
59
+ const targets = proj.pbxNativeTargetSection();
60
+ for (const [key, value] of Object.entries(targets)) {
61
+ if (typeof value !== 'object' || key.endsWith('_comment')) continue;
62
+ const target = value;
63
+ const productType = String(target.productType ?? '');
64
+ if (productType.includes('application')) {
65
+ return key;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+
71
+ function findTargetByName(proj, name) {
72
+ const targets = proj.pbxNativeTargetSection();
73
+ for (const [key, value] of Object.entries(targets)) {
74
+ if (typeof value !== 'object') continue;
75
+ const target = value;
76
+ const targetName = String(target.name ?? '').replace(/"/g, '');
77
+ if (targetName === name) {
78
+ return key;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+
84
+ function targetExists(proj, name) {
85
+ return findTargetByName(proj, name) !== null;
86
+ }
87
+
88
+ function getTargetProductUuid(target) {
89
+ const productReference = target.pbxNativeTarget && target.pbxNativeTarget.productReference;
90
+ return typeof productReference === 'string' ? productReference : undefined;
91
+ }
92
+
93
+ function setBuildSettings(proj, targetName, bundleId, deploymentTarget) {
94
+ const configs = proj.pbxXCBuildConfigurationSection();
95
+
96
+ for (const [, value] of Object.entries(configs)) {
97
+ if (typeof value !== 'object') continue;
98
+ const config = value;
99
+ const settings = config.buildSettings;
100
+
101
+ if (!settings) continue;
102
+ if (settings.PRODUCT_NAME !== `"${targetName}"`) continue;
103
+
104
+ settings.INFOPLIST_FILE = `"${targetName}/Info.plist"`;
105
+ settings.PRODUCT_BUNDLE_IDENTIFIER = `"${bundleId}"`;
106
+ settings.TARGETED_DEVICE_FAMILY = `"1,2"`;
107
+ settings.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
108
+ settings.SWIFT_VERSION = '5.0';
109
+ settings.CODE_SIGN_ENTITLEMENTS = `"${targetName}/${targetName}.entitlements"`;
110
+ settings.GENERATE_INFOPLIST_FILE = 'NO';
111
+ }
112
+ }
113
+
114
+ function fixProductFileReference(proj, targetName) {
115
+ const fileRefs = proj.hash.project.objects.PBXFileReference;
116
+ if (!fileRefs) return;
117
+ for (const [, value] of Object.entries(fileRefs)) {
118
+ if (typeof value !== 'object') continue;
119
+ const ref = value;
120
+ if (String(ref.name ?? '').replace(/"/g, '') === `${targetName}.appex`) {
121
+ delete ref.fileEncoding;
122
+ delete ref.lastKnownFileType;
123
+ }
124
+ }
125
+ }
126
+
127
+ function genUuid() {
128
+ return crypto.randomBytes(12).toString('hex').toUpperCase().slice(0, 24);
129
+ }
130
+
131
+ function addTargetDependencyManual(proj, hostUuid, extensionUuid, extensionName) {
132
+ const proxyUuid = genUuid();
133
+ const depUuid = genUuid();
134
+
135
+ const objects = proj.hash.project.objects;
136
+
137
+ const rootObject = proj.hash.project.rootObject;
138
+
139
+ if (!objects.PBXContainerItemProxy) objects.PBXContainerItemProxy = {};
140
+ objects.PBXContainerItemProxy[proxyUuid] = {
141
+ isa: 'PBXContainerItemProxy',
142
+ containerPortal: rootObject,
143
+ proxyType: 1,
144
+ remoteGlobalIDString: extensionUuid,
145
+ remoteInfo: `"${extensionName}"`,
146
+ };
147
+ objects.PBXContainerItemProxy[`${proxyUuid}_comment`] = 'PBXContainerItemProxy';
148
+
149
+ if (!objects.PBXTargetDependency) objects.PBXTargetDependency = {};
150
+ objects.PBXTargetDependency[depUuid] = {
151
+ isa: 'PBXTargetDependency',
152
+ target: extensionUuid,
153
+ targetProxy: proxyUuid,
154
+ };
155
+ objects.PBXTargetDependency[`${depUuid}_comment`] = 'PBXTargetDependency';
156
+
157
+ const hostTarget = objects.PBXNativeTarget[hostUuid];
158
+ if (hostTarget && Array.isArray(hostTarget.dependencies)) {
159
+ hostTarget.dependencies.push({ value: depUuid, comment: 'PBXTargetDependency' });
160
+ }
161
+ }
162
+
163
+ function stripRnfbInfoPlistInputPath(proj, hostUuid) {
164
+ const objects = proj.hash.project.objects;
165
+ const hostTarget = objects.PBXNativeTarget?.[hostUuid];
166
+ const shellPhases = objects.PBXShellScriptBuildPhase;
167
+
168
+ if (!hostTarget || !Array.isArray(hostTarget.buildPhases) || !shellPhases) {
169
+ return;
170
+ }
171
+
172
+ for (const phaseRef of hostTarget.buildPhases) {
173
+ const phaseUuid = phaseRef?.value;
174
+ if (!phaseUuid) continue;
175
+
176
+ const phase = shellPhases[phaseUuid];
177
+ if (!phase) continue;
178
+
179
+ const phaseName = String(phase.name ?? '').replace(/"/g, '');
180
+ if (phaseName !== '[CP-User] [RNFB] Core Configuration') {
181
+ continue;
182
+ }
183
+
184
+ const inputPaths = Array.isArray(phase.inputPaths) ? phase.inputPaths : [];
185
+ const filteredInputPaths = inputPaths.filter(
186
+ entry => String(entry) !== '"$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)"',
187
+ );
188
+
189
+ if (filteredInputPaths.length > 0) {
190
+ phase.inputPaths = filteredInputPaths;
191
+ } else {
192
+ delete phase.inputPaths;
193
+ }
194
+ }
195
+ }
196
+
197
+ module.exports = {
198
+ patchXcodeProjectForNotifyKitNse,
199
+ };
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '10.3.2';
2
+ export const version = '10.4.0';