onesignal-expo-plugin 2.4.0 → 2.5.1

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 (35) hide show
  1. package/README.md +66 -16
  2. package/dist/index.d.ts +101 -0
  3. package/dist/index.js +711 -0
  4. package/package.json +43 -46
  5. package/serviceExtensionFiles/NotificationService.swift +32 -0
  6. package/{build/support/serviceExtensionFiles → serviceExtensionFiles}/OneSignalNotificationServiceExtension-Info.plist +1 -1
  7. package/widgetExtensionFiles/OneSignalWidget-Info.plist +29 -0
  8. package/widgetExtensionFiles/OneSignalWidgetBundle.swift +9 -0
  9. package/widgetExtensionFiles/OneSignalWidgetLiveActivity.swift +58 -0
  10. package/app.plugin.js +0 -1
  11. package/build/onesignal/withOneSignal.d.ts +0 -8
  12. package/build/onesignal/withOneSignal.js +0 -20
  13. package/build/onesignal/withOneSignalAndroid.d.ts +0 -7
  14. package/build/onesignal/withOneSignalAndroid.js +0 -136
  15. package/build/onesignal/withOneSignalIos.d.ts +0 -13
  16. package/build/onesignal/withOneSignalIos.js +0 -288
  17. package/build/support/FileManager.d.ts +0 -9
  18. package/build/support/FileManager.js +0 -75
  19. package/build/support/NseUpdaterManager.d.ts +0 -8
  20. package/build/support/NseUpdaterManager.js +0 -38
  21. package/build/support/OneSignalLog.d.ts +0 -4
  22. package/build/support/OneSignalLog.js +0 -12
  23. package/build/support/eas/getEasManagedCredentialsConfigExtra.d.ts +0 -4
  24. package/build/support/eas/getEasManagedCredentialsConfigExtra.js +0 -36
  25. package/build/support/helpers.d.ts +0 -7
  26. package/build/support/helpers.js +0 -91
  27. package/build/support/iosConstants.d.ts +0 -12
  28. package/build/support/iosConstants.js +0 -23
  29. package/build/support/serviceExtensionFiles/NotificationService.m +0 -34
  30. package/build/support/updatePodfile.d.ts +0 -1
  31. package/build/support/updatePodfile.js +0 -20
  32. package/build/types/types.d.ts +0 -62
  33. package/build/types/types.js +0 -16
  34. /package/{build/support/serviceExtensionFiles → serviceExtensionFiles}/NotificationService.h +0 -0
  35. /package/{build/support/serviceExtensionFiles → serviceExtensionFiles}/OneSignalNotificationServiceExtension.entitlements +0 -0
package/dist/index.js ADDED
@@ -0,0 +1,711 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+ //#endregion
23
+ let path = require("path");
24
+ path = __toESM(path);
25
+ let fs = require("fs");
26
+ fs = __toESM(fs);
27
+ let _expo_image_utils = require("@expo/image-utils");
28
+ let expo_config_plugins_js = require("expo/config-plugins.js");
29
+ let fs_promises = require("fs/promises");
30
+ fs_promises = __toESM(fs_promises);
31
+ //#region src/types/types.ts
32
+ const ONESIGNAL_PLUGIN_PROPS = [
33
+ "mode",
34
+ "devTeam",
35
+ "iPhoneDeploymentTarget",
36
+ "smallIcons",
37
+ "largeIcons",
38
+ "iosNSEFilePath",
39
+ "smallIconAccentColor",
40
+ "appGroupName",
41
+ "nseBundleIdentifier",
42
+ "disableNSE",
43
+ "sounds",
44
+ "liveActivities"
45
+ ];
46
+ const TARGETED_DEVICE_FAMILY = `"1,2"`;
47
+ const GROUP_IDENTIFIER_TEMPLATE_REGEX = /{{GROUP_IDENTIFIER}}/gm;
48
+ const BUNDLE_SHORT_VERSION_TEMPLATE_REGEX = /{{BUNDLE_SHORT_VERSION}}/gm;
49
+ const BUNDLE_VERSION_TEMPLATE_REGEX = /{{BUNDLE_VERSION}}/gm;
50
+ const NSE_PRINCIPAL_CLASS_TEMPLATE_REGEX = /{{NSE_PRINCIPAL_CLASS}}/gm;
51
+ const NSE_TARGET_NAME = "OneSignalNotificationServiceExtension";
52
+ /** Default Swift template shipped under serviceExtensionFiles/. */
53
+ const NSE_SOURCE_FILE = "NotificationService.swift";
54
+ const NSE_PODFILE_SNIPPET = `
55
+ target '${NSE_TARGET_NAME}' do
56
+ pod 'OneSignalXCFramework', '>= 5.0', '< 6.0'
57
+ use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
58
+ end`;
59
+ const NSE_PODFILE_REGEX = new RegExp(`target '${NSE_TARGET_NAME}'`);
60
+ const NSE_EXT_FILES = [`${NSE_TARGET_NAME}.entitlements`, `${NSE_TARGET_NAME}-Info.plist`];
61
+ const LIVE_ACTIVITY_TARGET_NAME = "OneSignalWidget";
62
+ const LIVE_ACTIVITY_DEPLOYMENT_TARGET = "16.2";
63
+ const LIVE_ACTIVITY_BUNDLE_FILE = "OneSignalWidgetBundle.swift";
64
+ const LIVE_ACTIVITY_WIDGET_FILE = "OneSignalWidgetLiveActivity.swift";
65
+ const LIVE_ACTIVITY_INFO_PLIST_FILE = `${LIVE_ACTIVITY_TARGET_NAME}-Info.plist`;
66
+ const liveActivityPodfileSnippet = (targetName) => `
67
+ target '${targetName}' do
68
+ pod 'OneSignalXCFramework', '>= 5.0', '< 6.0'
69
+ use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
70
+ end`;
71
+ const liveActivityPodfileRegex = (targetName) => new RegExp(`target '${targetName}'`);
72
+ //#endregion
73
+ //#region src/support/OneSignalLog.ts
74
+ var OneSignalLog = class {
75
+ static log(str) {
76
+ console.log(`\tonesignal-expo-plugin: ${str}`);
77
+ }
78
+ static error(str) {
79
+ console.error(`\tonesignal-expo-plugin: ${str}`);
80
+ }
81
+ };
82
+ //#endregion
83
+ //#region src/support/helpers.ts
84
+ const HEX_COLOR_6_REGEX = /^#?[0-9A-Fa-f]{6}$/;
85
+ const HEX_COLOR_8_REGEX = /^#?[0-9A-Fa-f]{8}$/;
86
+ /**
87
+ * Converts a hex color string (#RGB, #RRGGBB, or #AARRGGBB) to an
88
+ * 8-character ARGB hex string (no '#') expected by the OneSignal Android SDK.
89
+ */
90
+ function parseColorToARGB(color) {
91
+ const hex = color.replace("#", "");
92
+ if (HEX_COLOR_8_REGEX.test(hex)) return hex.toUpperCase();
93
+ if (HEX_COLOR_6_REGEX.test(hex)) return `FF${hex.toUpperCase()}`;
94
+ if (/^[0-9A-Fa-f]{3}$/.test(hex)) return `FF${hex.split("").map((c) => c + c).join("").toUpperCase()}`;
95
+ throw new Error(`OneSignal Expo Plugin: 'smallIconAccentColor' must be a valid hex color (e.g. "#FF0000"), got "${color}".`);
96
+ }
97
+ function validatePluginProps(props) {
98
+ if (typeof props.mode !== "string") throw new Error("OneSignal Expo Plugin: 'mode' must be a string.");
99
+ if (props.devTeam && typeof props.devTeam !== "string") throw new Error("OneSignal Expo Plugin: 'devTeam' must be a string.");
100
+ if (props.iPhoneDeploymentTarget && typeof props.iPhoneDeploymentTarget !== "string") throw new Error("OneSignal Expo Plugin: 'iPhoneDeploymentTarget' must be a string.");
101
+ if (props.smallIcons && !Array.isArray(props.smallIcons)) throw new Error("OneSignal Expo Plugin: 'smallIcons' must be an array.");
102
+ if (props.smallIconAccentColor != null) {
103
+ if (typeof props.smallIconAccentColor !== "string") throw new Error("OneSignal Expo Plugin: 'smallIconAccentColor' must be a string.");
104
+ parseColorToARGB(props.smallIconAccentColor);
105
+ }
106
+ if (props.largeIcons && !Array.isArray(props.largeIcons)) throw new Error("OneSignal Expo Plugin: 'largeIcons' must be an array.");
107
+ if (props.iosNSEFilePath && typeof props.iosNSEFilePath !== "string") throw new Error("OneSignal Expo Plugin: 'iosNSEFilePath' must be a string.");
108
+ if (props.appGroupName && typeof props.appGroupName !== "string") throw new Error("OneSignal Expo Plugin: 'appGroupName' must be a string.");
109
+ if (props.nseBundleIdentifier && typeof props.nseBundleIdentifier !== "string") throw new Error("OneSignal Expo Plugin: 'nseBundleIdentifier' must be a string.");
110
+ if (props.disableNSE !== void 0 && typeof props.disableNSE !== "boolean") throw new Error("OneSignal Expo Plugin: 'disableNSE' must be a boolean.");
111
+ if (props.sounds !== void 0) {
112
+ if (!Array.isArray(props.sounds)) throw new Error("OneSignal Expo Plugin: 'sounds' must be an array.");
113
+ for (const sound of props.sounds) {
114
+ if (typeof sound !== "string") throw new Error("OneSignal Expo Plugin: each entry in 'sounds' must be a string path.");
115
+ if ((0, path.extname)(sound).toLowerCase() !== ".wav") throw new Error(`OneSignal Expo Plugin: sound file "${sound}" must be a .wav file. Only .wav is supported (works on both iOS and Android).`);
116
+ }
117
+ }
118
+ if (props.liveActivities !== void 0) {
119
+ if (props.liveActivities === null || Array.isArray(props.liveActivities) || typeof props.liveActivities !== "object") throw new Error("OneSignal Expo Plugin: 'liveActivities' must be an object.");
120
+ const liveActivities = props.liveActivities;
121
+ const liveActivityProps = [
122
+ "targetName",
123
+ "bundleIdentifierSuffix",
124
+ "widgetFilePath",
125
+ "deploymentTarget"
126
+ ];
127
+ for (const prop of Object.keys(liveActivities)) {
128
+ if (!liveActivityProps.includes(prop)) throw new Error(`OneSignal Expo Plugin: You have provided an invalid property "${prop}" to 'liveActivities'.`);
129
+ if (typeof liveActivities[prop] !== "string") throw new Error(`OneSignal Expo Plugin: 'liveActivities.${prop}' must be a string.`);
130
+ }
131
+ }
132
+ const inputProps = Object.keys(props);
133
+ for (const prop of inputProps) if (!ONESIGNAL_PLUGIN_PROPS.includes(prop)) throw new Error(`OneSignal Expo Plugin: You have provided an invalid property "${prop}" to the OneSignal plugin.`);
134
+ }
135
+ /**
136
+ * Detect whether the customer's NSE is Swift or Objective-C from `iosNSEFilePath`.
137
+ * Defaults to Swift when no custom path is provided.
138
+ */
139
+ function resolveNseConfig(iosNSEFilePath) {
140
+ const ext = iosNSEFilePath ? (0, path.extname)(iosNSEFilePath).toLowerCase() : ".swift";
141
+ if (ext === ".swift" || ext === "") return {
142
+ language: "swift",
143
+ sourceFile: NSE_SOURCE_FILE,
144
+ principalClass: "$(PRODUCT_MODULE_NAME).NotificationService"
145
+ };
146
+ if (ext === ".m") return {
147
+ language: "objc",
148
+ sourceFile: "NotificationService.m",
149
+ headerFile: "NotificationService.h",
150
+ principalClass: "NotificationService"
151
+ };
152
+ throw new Error(`OneSignal Expo Plugin: 'iosNSEFilePath' must point to a .swift or .m file. Got "${iosNSEFilePath}".`);
153
+ }
154
+ function getAppGroupIdentifier(bundleIdentifier, customAppGroupName) {
155
+ return customAppGroupName ?? `group.${bundleIdentifier}.onesignal`;
156
+ }
157
+ /**
158
+ * Resolve the Apple development team ID. Prefers `ios.appleTeamId` from the
159
+ * Expo config, falling back to the plugin's deprecated `devTeam` prop.
160
+ */
161
+ function resolveDevTeam(config, props) {
162
+ if (config.ios?.appleTeamId) {
163
+ if (props.devTeam) OneSignalLog.log("Warning: Both \"ios.appleTeamId\" and the deprecated \"devTeam\" plugin prop are set. \"devTeam\" will be ignored. Remove \"devTeam\" from your plugin config.");
164
+ return config.ios.appleTeamId;
165
+ }
166
+ if (props.devTeam) {
167
+ OneSignalLog.log("Warning: The \"devTeam\" plugin prop is deprecated and will be removed in a future major release. Set \"ios.appleTeamId\" in your Expo config instead.");
168
+ return props.devTeam;
169
+ }
170
+ }
171
+ //#endregion
172
+ //#region src/onesignal/withOneSignalAndroid.ts
173
+ /**
174
+ * Expo config plugin for OneSignal (Android)
175
+ * @see https://documentation.onesignal.com/docs/react-native-sdk-setup#step-4-install-for-ios-using-cocoapods-for-ios-apps
176
+ */
177
+ const RESOURCE_ROOT_PATH = "android/app/src/main/res/";
178
+ const SMALL_ICON_DIRS_TO_SIZE = {
179
+ "drawable-mdpi": 24,
180
+ "drawable-hdpi": 36,
181
+ "drawable-xhdpi": 48,
182
+ "drawable-xxhdpi": 72,
183
+ "drawable-xxxhdpi": 96
184
+ };
185
+ const LARGE_ICON_DIRS_TO_SIZE = { "drawable-xxxhdpi": 256 };
186
+ const SMALL_ICON_DEFAULT_NAME = "ic_stat_onesignal_default";
187
+ const withSmallIcons = (config, onesignalProps) => {
188
+ const expoConfig = config;
189
+ if (!onesignalProps.smallIcons && !expoConfig.notification?.icon) return config;
190
+ return (0, expo_config_plugins_js.withDangerousMod)(config, ["android", async (config) => {
191
+ const modConfig = config;
192
+ if (modConfig.notification?.icon) await saveIconAsync(modConfig.notification.icon, config.modRequest.projectRoot, SMALL_ICON_DIRS_TO_SIZE, SMALL_ICON_DEFAULT_NAME);
193
+ if (onesignalProps.smallIcons) await saveIconsArrayAsync(config.modRequest.projectRoot, onesignalProps.smallIcons, SMALL_ICON_DIRS_TO_SIZE, SMALL_ICON_DEFAULT_NAME);
194
+ return config;
195
+ }]);
196
+ };
197
+ const LARGE_ICON_DEFAULT_NAME = "ic_onesignal_large_icon_default";
198
+ const withLargeIcons = (config, onesignalProps) => {
199
+ if (!onesignalProps.largeIcons) return config;
200
+ return (0, expo_config_plugins_js.withDangerousMod)(config, ["android", async (config) => {
201
+ if (onesignalProps.largeIcons) await saveIconsArrayAsync(config.modRequest.projectRoot, onesignalProps.largeIcons, LARGE_ICON_DIRS_TO_SIZE, LARGE_ICON_DEFAULT_NAME);
202
+ return config;
203
+ }]);
204
+ };
205
+ const withSmallIconAccentColor = (config, onesignalProps) => {
206
+ if (!onesignalProps.smallIconAccentColor) return config;
207
+ return (0, expo_config_plugins_js.withStringsXml)(config, (config) => {
208
+ const colorInARGB = parseColorToARGB(onesignalProps.smallIconAccentColor ?? "");
209
+ const strings = config.modResults.resources.string ?? [];
210
+ const existingIndex = strings.findIndex((stringEntry) => stringEntry.$?.name === "onesignal_notification_accent_color");
211
+ const accentColorEntry = {
212
+ $: { name: "onesignal_notification_accent_color" },
213
+ _: colorInARGB
214
+ };
215
+ if (existingIndex !== -1) strings[existingIndex] = accentColorEntry;
216
+ else strings.push(accentColorEntry);
217
+ config.modResults.resources.string = strings;
218
+ return config;
219
+ });
220
+ };
221
+ async function saveIconsArrayAsync(projectRoot, icons, dirsToSize, defaultName) {
222
+ for (let i = 0; i < icons.length; i++) {
223
+ const outputName = i === 0 ? defaultName : void 0;
224
+ await saveIconAsync(icons[i], projectRoot, dirsToSize, outputName);
225
+ }
226
+ }
227
+ async function saveIconAsync(icon, projectRoot, dirsToSize, outputName) {
228
+ const name = outputName ?? (0, path.parse)(icon).name;
229
+ OneSignalLog.log("Saving icon " + icon + " as drawable resource " + name);
230
+ for (const iconResourceDir in dirsToSize) {
231
+ const path$1 = (0, path.resolve)(projectRoot, RESOURCE_ROOT_PATH, iconResourceDir);
232
+ if (!(0, fs.existsSync)(path$1)) (0, fs.mkdirSync)(path$1, { recursive: true });
233
+ const resizedIcon = (await (0, _expo_image_utils.generateImageAsync)({
234
+ projectRoot,
235
+ cacheType: "onesignal-icon"
236
+ }, {
237
+ src: icon,
238
+ width: dirsToSize[iconResourceDir],
239
+ height: dirsToSize[iconResourceDir],
240
+ resizeMode: "cover",
241
+ backgroundColor: "transparent"
242
+ })).source;
243
+ (0, fs.writeFileSync)((0, path.resolve)(path$1, name + ".png"), resizedIcon);
244
+ }
245
+ }
246
+ const withSoundFiles$1 = (config, onesignalProps) => {
247
+ if (!onesignalProps.sounds) return config;
248
+ return (0, expo_config_plugins_js.withDangerousMod)(config, ["android", async (config) => {
249
+ const projectRoot = config.modRequest.projectRoot;
250
+ const rawDir = (0, path.resolve)(projectRoot, RESOURCE_ROOT_PATH, "raw");
251
+ if (!(0, fs.existsSync)(rawDir)) (0, fs.mkdirSync)(rawDir, { recursive: true });
252
+ for (const soundFile of onesignalProps.sounds) {
253
+ const sourcePath = (0, path.resolve)(projectRoot, soundFile);
254
+ const fileName = (0, path.basename)(sourcePath).toLowerCase();
255
+ OneSignalLog.log(`Copying sound file ${soundFile} to res/raw/${fileName}`);
256
+ (0, fs.copyFileSync)(sourcePath, (0, path.resolve)(rawDir, fileName));
257
+ }
258
+ return config;
259
+ }]);
260
+ };
261
+ const withOneSignalAndroid = (config, props) => {
262
+ config = withSmallIcons(config, props);
263
+ config = withLargeIcons(config, props);
264
+ config = withSmallIconAccentColor(config, props);
265
+ config = withSoundFiles$1(config, props);
266
+ return config;
267
+ };
268
+ //#endregion
269
+ //#region src/support/eas/getEasManagedCredentialsConfigExtra.ts
270
+ function getEasManagedCredentialsConfigExtra(config, appGroupName, nseBundleIdentifier, liveActivities, disableNSE) {
271
+ const bundleIdentifier = config?.ios?.bundleIdentifier ?? "";
272
+ const appExtensions = [...config.extra?.eas?.build?.experimental?.ios?.appExtensions ?? []];
273
+ if (!disableNSE) {
274
+ const bundleId = `${bundleIdentifier}.${nseBundleIdentifier ?? "OneSignalNotificationServiceExtension"}`;
275
+ appExtensions.push({
276
+ targetName: NSE_TARGET_NAME,
277
+ bundleIdentifier: bundleId,
278
+ entitlements: { "com.apple.security.application-groups": [getAppGroupIdentifier(bundleIdentifier, appGroupName)] }
279
+ });
280
+ }
281
+ if (liveActivities) {
282
+ const targetName = liveActivities.targetName ?? "OneSignalWidget";
283
+ const suffix = liveActivities.bundleIdentifierSuffix ?? targetName;
284
+ appExtensions.push({
285
+ targetName,
286
+ bundleIdentifier: `${bundleIdentifier}.${suffix}`,
287
+ entitlements: {}
288
+ });
289
+ }
290
+ return {
291
+ ...config.extra,
292
+ eas: {
293
+ ...config.extra?.eas,
294
+ build: {
295
+ ...config.extra?.eas?.build,
296
+ experimental: {
297
+ ...config.extra?.eas?.build?.experimental,
298
+ ios: {
299
+ ...config.extra?.eas?.build?.experimental?.ios,
300
+ appExtensions
301
+ }
302
+ }
303
+ }
304
+ }
305
+ };
306
+ }
307
+ //#endregion
308
+ //#region src/support/FileManager.ts
309
+ /**
310
+ * FileManager contains static *awaitable* file-system functions
311
+ */
312
+ var FileManager = class FileManager {
313
+ static async readFile(path) {
314
+ return new Promise((resolve, reject) => {
315
+ fs.readFile(path, "utf8", (err, data) => {
316
+ if (err || !data) {
317
+ OneSignalLog.error("Couldn't read file:" + path);
318
+ reject(err);
319
+ return;
320
+ }
321
+ resolve(data);
322
+ });
323
+ });
324
+ }
325
+ static async writeFile(path, contents) {
326
+ return new Promise((resolve, reject) => {
327
+ fs.writeFile(path, contents, "utf8", (err) => {
328
+ if (err) {
329
+ OneSignalLog.error("Couldn't write file:" + path);
330
+ reject(err);
331
+ return;
332
+ }
333
+ resolve();
334
+ });
335
+ });
336
+ }
337
+ static async copyFile(path1, path2) {
338
+ const fileContents = await FileManager.readFile(path1);
339
+ await FileManager.writeFile(path2, fileContents);
340
+ }
341
+ static dirExists(path) {
342
+ return fs.existsSync(path);
343
+ }
344
+ };
345
+ //#endregion
346
+ //#region src/support/NseUpdaterManager.ts
347
+ const entitlementsFileName = `${NSE_TARGET_NAME}.entitlements`;
348
+ const plistFileName = `${NSE_TARGET_NAME}-Info.plist`;
349
+ var NseUpdaterManager = class {
350
+ constructor(iosPath) {
351
+ this.nsePath = `${iosPath}/${NSE_TARGET_NAME}`;
352
+ }
353
+ async updateNSEEntitlements(groupIdentifier) {
354
+ const entitlementsFilePath = `${this.nsePath}/${entitlementsFileName}`;
355
+ let entitlementsFile = await FileManager.readFile(entitlementsFilePath);
356
+ entitlementsFile = entitlementsFile.replace(GROUP_IDENTIFIER_TEMPLATE_REGEX, groupIdentifier);
357
+ await FileManager.writeFile(entitlementsFilePath, entitlementsFile);
358
+ }
359
+ async updateNSEBundleVersion(version) {
360
+ const plistFilePath = `${this.nsePath}/${plistFileName}`;
361
+ let plistFile = await FileManager.readFile(plistFilePath);
362
+ plistFile = plistFile.replace(BUNDLE_VERSION_TEMPLATE_REGEX, version);
363
+ await FileManager.writeFile(plistFilePath, plistFile);
364
+ }
365
+ async updateNSEBundleShortVersion(version) {
366
+ const plistFilePath = `${this.nsePath}/${plistFileName}`;
367
+ let plistFile = await FileManager.readFile(plistFilePath);
368
+ plistFile = plistFile.replace(BUNDLE_SHORT_VERSION_TEMPLATE_REGEX, version);
369
+ await FileManager.writeFile(plistFilePath, plistFile);
370
+ }
371
+ /** Replace the {{NSE_PRINCIPAL_CLASS}} placeholder with the language-appropriate value. */
372
+ async updateNSEPrincipalClass(principalClass) {
373
+ const plistFilePath = `${this.nsePath}/${plistFileName}`;
374
+ let plistFile = await FileManager.readFile(plistFilePath);
375
+ plistFile = plistFile.replace(NSE_PRINCIPAL_CLASS_TEMPLATE_REGEX, principalClass);
376
+ await FileManager.writeFile(plistFilePath, plistFile);
377
+ }
378
+ async updateNSEInfoPlistAppGroupKey(appGroupName) {
379
+ const plistFilePath = `${this.nsePath}/${plistFileName}`;
380
+ let plistFile = await FileManager.readFile(plistFilePath);
381
+ const appGroupEntry = `\t<key>OneSignal_app_groups_key</key>\n\t<string>${appGroupName}</string>\n</dict>\n</plist>`;
382
+ plistFile = plistFile.replace("</dict>\n</plist>", appGroupEntry);
383
+ await FileManager.writeFile(plistFilePath, plistFile);
384
+ }
385
+ };
386
+ //#endregion
387
+ //#region src/support/updatePodfile.ts
388
+ async function updatePodfile(iosPath) {
389
+ if ((await FileManager.readFile(`${iosPath}/Podfile`)).match(NSE_PODFILE_REGEX)) {
390
+ OneSignalLog.log(`${NSE_TARGET_NAME} target already added to Podfile. Skipping...`);
391
+ return;
392
+ }
393
+ await fs_promises.default.appendFile(`${iosPath}/Podfile`, NSE_PODFILE_SNIPPET);
394
+ OneSignalLog.log(`${NSE_TARGET_NAME} target added to Podfile.`);
395
+ }
396
+ //#endregion
397
+ //#region src/onesignal/withOneSignalLiveActivity.ts
398
+ /**
399
+ * Expo config plugin that scaffolds an iOS Widget Extension target for OneSignal Live Activities.
400
+ * @see https://documentation.onesignal.com/docs/cross-platform-live-activity-setup
401
+ */
402
+ /** Resolve the widget extension target name, defaulting when unset. */
403
+ function resolveLiveActivityTargetName(props) {
404
+ return props.liveActivities?.targetName ?? "OneSignalWidget";
405
+ }
406
+ /** Build the widget extension's full bundle identifier. */
407
+ function resolveLiveActivityBundleId(bundleIdentifier, props) {
408
+ return `${bundleIdentifier}.${props.liveActivities?.bundleIdentifierSuffix ?? resolveLiveActivityTargetName(props)}`;
409
+ }
410
+ /** Resolve the IPHONEOS_DEPLOYMENT_TARGET for the widget target (must be >= 16.2). */
411
+ function resolveLiveActivityDeploymentTarget(props) {
412
+ const requested = props.liveActivities?.deploymentTarget;
413
+ if (!requested) return LIVE_ACTIVITY_DEPLOYMENT_TARGET;
414
+ const requestedNum = parseFloat(requested);
415
+ const minNum = parseFloat(LIVE_ACTIVITY_DEPLOYMENT_TARGET);
416
+ if (Number.isFinite(requestedNum) && requestedNum < minNum) {
417
+ OneSignalLog.log(`Warning: liveActivities.deploymentTarget "${requested}" is below the minimum required for Live Activities (${LIVE_ACTIVITY_DEPLOYMENT_TARGET}). The widget extension will use ${LIVE_ACTIVITY_DEPLOYMENT_TARGET}.`);
418
+ return LIVE_ACTIVITY_DEPLOYMENT_TARGET;
419
+ }
420
+ return requested;
421
+ }
422
+ /** Add NSSupportsLiveActivities=true to the main target's Info.plist. */
423
+ const withLiveActivityInfoPlist = (config) => {
424
+ return (0, expo_config_plugins_js.withInfoPlist)(config, (newConfig) => {
425
+ newConfig.modResults.NSSupportsLiveActivities = true;
426
+ return newConfig;
427
+ });
428
+ };
429
+ /** Append a Widget Extension target block to the Podfile (idempotent). */
430
+ const withLiveActivityPodfile = (config, props) => {
431
+ return (0, expo_config_plugins_js.withDangerousMod)(config, ["ios", async (config) => {
432
+ const targetName = resolveLiveActivityTargetName(props);
433
+ const podfilePath = path.join(config.modRequest.projectRoot, "ios", "Podfile");
434
+ const podfile = await FileManager.readFile(podfilePath);
435
+ if (liveActivityPodfileRegex(targetName).test(podfile)) {
436
+ OneSignalLog.log(`${targetName} target already added to Podfile. Skipping...`);
437
+ return config;
438
+ }
439
+ await fs.promises.appendFile(podfilePath, liveActivityPodfileSnippet(targetName));
440
+ OneSignalLog.log(`${targetName} target added to Podfile.`);
441
+ return config;
442
+ }]);
443
+ };
444
+ /** Copy widget extension template files (or the user-supplied widget file) into the iOS project. */
445
+ const withLiveActivityFiles = (config, props) => {
446
+ return (0, expo_config_plugins_js.withDangerousMod)(config, ["ios", async (config) => {
447
+ const targetName = resolveLiveActivityTargetName(props);
448
+ const pluginDir = require.resolve("onesignal-expo-plugin/package.json");
449
+ const sourceDir = path.join(pluginDir, "../widgetExtensionFiles/");
450
+ const iosPath = path.join(config.modRequest.projectRoot, "ios");
451
+ const targetDir = path.join(iosPath, targetName);
452
+ fs.mkdirSync(targetDir, { recursive: true });
453
+ const bundleSource = path.join(sourceDir, LIVE_ACTIVITY_BUNDLE_FILE);
454
+ const bundleTarget = path.join(targetDir, LIVE_ACTIVITY_BUNDLE_FILE);
455
+ await FileManager.copyFile(bundleSource, bundleTarget);
456
+ const widgetSource = props.liveActivities?.widgetFilePath ?? path.join(sourceDir, "OneSignalWidgetLiveActivity.swift");
457
+ const widgetTarget = path.join(targetDir, LIVE_ACTIVITY_WIDGET_FILE);
458
+ await FileManager.copyFile(widgetSource, widgetTarget);
459
+ const plistSource = path.join(sourceDir, LIVE_ACTIVITY_INFO_PLIST_FILE);
460
+ const plistTarget = path.join(targetDir, `${targetName}-Info.plist`);
461
+ let plistContents = await FileManager.readFile(plistSource);
462
+ plistContents = plistContents.replace(BUNDLE_VERSION_TEMPLATE_REGEX, config.ios?.buildNumber ?? "1");
463
+ plistContents = plistContents.replace(BUNDLE_SHORT_VERSION_TEMPLATE_REGEX, config.version ?? "1.0");
464
+ await FileManager.writeFile(plistTarget, plistContents);
465
+ return config;
466
+ }]);
467
+ };
468
+ /** Register the widget extension target, group, build phases, and build settings in the Xcode project. */
469
+ const withLiveActivityXcodeProject = (config, props) => {
470
+ return (0, expo_config_plugins_js.withXcodeProject)(config, (newConfig) => {
471
+ const xcodeProject = newConfig.modResults;
472
+ const targetName = resolveLiveActivityTargetName(props);
473
+ const widgetBundleId = resolveLiveActivityBundleId(config.ios?.bundleIdentifier ?? "", props);
474
+ const deploymentTarget = resolveLiveActivityDeploymentTarget(props);
475
+ const devTeam = resolveDevTeam(config, props);
476
+ if (xcodeProject.pbxTargetByName(targetName)) {
477
+ OneSignalLog.log(`${targetName} already exists in project. Skipping...`);
478
+ return newConfig;
479
+ }
480
+ const widgetFiles = [
481
+ LIVE_ACTIVITY_BUNDLE_FILE,
482
+ LIVE_ACTIVITY_WIDGET_FILE,
483
+ `${targetName}-Info.plist`
484
+ ];
485
+ const extGroup = xcodeProject.addPbxGroup(widgetFiles, targetName, targetName);
486
+ const groups = xcodeProject.hash.project.objects["PBXGroup"];
487
+ Object.keys(groups).forEach(function(key) {
488
+ if (typeof groups[key] === "object" && groups[key].name === void 0 && groups[key].path === void 0) xcodeProject.addToPbxGroup(extGroup.uuid, key);
489
+ });
490
+ const projObjects = xcodeProject.hash.project.objects;
491
+ projObjects["PBXTargetDependency"] = projObjects["PBXTargetDependency"] || {};
492
+ projObjects["PBXContainerItemProxy"] = projObjects["PBXContainerItemProxy"] || {};
493
+ const widgetTarget = xcodeProject.addTarget(targetName, "app_extension", targetName, widgetBundleId);
494
+ xcodeProject.addBuildPhase([LIVE_ACTIVITY_BUNDLE_FILE, LIVE_ACTIVITY_WIDGET_FILE], "PBXSourcesBuildPhase", "Sources", widgetTarget.uuid);
495
+ xcodeProject.addBuildPhase([], "PBXResourcesBuildPhase", "Resources", widgetTarget.uuid);
496
+ xcodeProject.addBuildPhase([], "PBXFrameworksBuildPhase", "Frameworks", widgetTarget.uuid);
497
+ const configurations = xcodeProject.pbxXCBuildConfigurationSection();
498
+ for (const key in configurations) if (typeof configurations[key].buildSettings !== "undefined" && configurations[key].buildSettings.PRODUCT_NAME == `"${targetName}"`) {
499
+ const buildSettingsObj = configurations[key].buildSettings;
500
+ buildSettingsObj.DEVELOPMENT_TEAM = devTeam;
501
+ buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
502
+ buildSettingsObj.TARGETED_DEVICE_FAMILY = TARGETED_DEVICE_FAMILY;
503
+ buildSettingsObj.SWIFT_VERSION = "5.0";
504
+ buildSettingsObj.INFOPLIST_FILE = `${targetName}/${targetName}-Info.plist`;
505
+ buildSettingsObj.CODE_SIGN_STYLE = "Automatic";
506
+ }
507
+ if (devTeam) {
508
+ xcodeProject.addTargetAttribute("DevelopmentTeam", devTeam, widgetTarget);
509
+ xcodeProject.addTargetAttribute("DevelopmentTeam", devTeam);
510
+ }
511
+ return newConfig;
512
+ });
513
+ };
514
+ const withOneSignalLiveActivity = (config, props) => {
515
+ if (!props.liveActivities) return config;
516
+ config = withLiveActivityInfoPlist(config, props);
517
+ config = withLiveActivityPodfile(config, props);
518
+ config = withLiveActivityFiles(config, props);
519
+ config = withLiveActivityXcodeProject(config, props);
520
+ return config;
521
+ };
522
+ //#endregion
523
+ //#region src/onesignal/withOneSignalIos.ts
524
+ /**
525
+ * Expo config plugin for One Signal (iOS)
526
+ * @see https://documentation.onesignal.com/docs/react-native-sdk-setup#step-4-install-for-ios-using-cocoapods-for-ios-apps
527
+ */
528
+ /**
529
+ * Add 'aps-environment' record with current environment to '<project-name>.entitlements' file
530
+ * @see https://documentation.onesignal.com/docs/react-native-sdk-setup#step-4-install-for-ios-using-cocoapods-for-ios-apps
531
+ */
532
+ const withAppEnvironment = (config, onesignalProps) => {
533
+ return (0, expo_config_plugins_js.withEntitlementsPlist)(config, (newConfig) => {
534
+ if (onesignalProps?.mode == null) throw new Error(`
535
+ Missing required "mode" key in your app.json or app.config.js file for "onesignal-expo-plugin".
536
+ "mode" can be either "development" or "production".
537
+ Please see onesignal-expo-plugin's README.md for more details.`);
538
+ newConfig.modResults["aps-environment"] = onesignalProps.mode;
539
+ return newConfig;
540
+ });
541
+ };
542
+ /**
543
+ * Add "Background Modes -> Remote notifications" and "App Group" permissions
544
+ * @see https://documentation.onesignal.com/docs/react-native-sdk-setup#step-4-install-for-ios-using-cocoapods-for-ios-apps
545
+ */
546
+ const withRemoteNotificationsPermissions = (config) => {
547
+ const BACKGROUND_MODE_KEYS = ["remote-notification"];
548
+ return (0, expo_config_plugins_js.withInfoPlist)(config, (newConfig) => {
549
+ if (!Array.isArray(newConfig.modResults.UIBackgroundModes)) newConfig.modResults.UIBackgroundModes = [];
550
+ for (const key of BACKGROUND_MODE_KEYS) if (!newConfig.modResults.UIBackgroundModes.includes(key)) newConfig.modResults.UIBackgroundModes.push(key);
551
+ return newConfig;
552
+ });
553
+ };
554
+ /**
555
+ * Add "App Group" permission
556
+ * @see https://documentation.onesignal.com/docs/react-native-sdk-setup#step-4-install-for-ios-using-cocoapods-for-ios-apps (step 4.4)
557
+ */
558
+ const withAppGroupPermissions = (config, props) => {
559
+ const APP_GROUP_KEY = "com.apple.security.application-groups";
560
+ return (0, expo_config_plugins_js.withEntitlementsPlist)(config, (newConfig) => {
561
+ if (!Array.isArray(newConfig.modResults[APP_GROUP_KEY])) newConfig.modResults[APP_GROUP_KEY] = [];
562
+ const modResultsArray = newConfig.modResults[APP_GROUP_KEY];
563
+ const entitlement = getAppGroupIdentifier(newConfig?.ios?.bundleIdentifier || "", props?.appGroupName);
564
+ if (modResultsArray.indexOf(entitlement) !== -1) return newConfig;
565
+ modResultsArray.push(entitlement);
566
+ return newConfig;
567
+ });
568
+ };
569
+ /**
570
+ * Add OneSignal_app_groups_key to Info.plist when a custom app group name is provided.
571
+ * @see https://documentation.onesignal.com/docs/ios-sdk-setup#step-3-create-an-app-group
572
+ */
573
+ const withCustomAppGroupsKey = (config, props) => {
574
+ return (0, expo_config_plugins_js.withInfoPlist)(config, (newConfig) => {
575
+ if (props?.appGroupName) newConfig.modResults.OneSignal_app_groups_key = props.appGroupName;
576
+ return newConfig;
577
+ });
578
+ };
579
+ const withEasManagedCredentials = (config, props) => {
580
+ if (!config.ios?.bundleIdentifier) return config;
581
+ config.extra = getEasManagedCredentialsConfigExtra(config, props?.appGroupName, props?.nseBundleIdentifier, props?.liveActivities, props?.disableNSE);
582
+ return config;
583
+ };
584
+ const withOneSignalPodfile = (config) => {
585
+ return (0, expo_config_plugins_js.withDangerousMod)(config, ["ios", async (config) => {
586
+ await updatePodfile(path.join(config.modRequest.projectRoot, "ios"));
587
+ return config;
588
+ }]);
589
+ };
590
+ const withOneSignalNSE = (config, props) => {
591
+ return (0, expo_config_plugins_js.withDangerousMod)(config, ["ios", async (config) => {
592
+ const pluginDir = require.resolve("onesignal-expo-plugin/package.json");
593
+ const sourceDir = path.join(pluginDir, "../serviceExtensionFiles/");
594
+ const iosPath = path.join(config.modRequest.projectRoot, "ios");
595
+ fs.mkdirSync(`${iosPath}/${NSE_TARGET_NAME}`, { recursive: true });
596
+ for (const file of NSE_EXT_FILES) {
597
+ const targetFile = `${iosPath}/${NSE_TARGET_NAME}/${file}`;
598
+ await FileManager.copyFile(`${sourceDir}${file}`, targetFile);
599
+ }
600
+ const nseConfig = resolveNseConfig(props.iosNSEFilePath);
601
+ const sourcePath = props.iosNSEFilePath ?? `${sourceDir}${nseConfig.sourceFile}`;
602
+ const targetFile = `${iosPath}/${NSE_TARGET_NAME}/${nseConfig.sourceFile}`;
603
+ await FileManager.copyFile(`${sourcePath}`, targetFile);
604
+ if (nseConfig.headerFile) {
605
+ const siblingHeader = props.iosNSEFilePath ? path.join(path.dirname(props.iosNSEFilePath), nseConfig.headerFile) : void 0;
606
+ const headerSource = siblingHeader && fs.existsSync(siblingHeader) ? siblingHeader : `${sourceDir}${nseConfig.headerFile}`;
607
+ const headerTarget = `${iosPath}/${NSE_TARGET_NAME}/${nseConfig.headerFile}`;
608
+ await FileManager.copyFile(headerSource, headerTarget);
609
+ }
610
+ const nseUpdater = new NseUpdaterManager(iosPath);
611
+ const appGroupId = getAppGroupIdentifier(config.ios?.bundleIdentifier ?? "", props.appGroupName);
612
+ await nseUpdater.updateNSEEntitlements(appGroupId);
613
+ await nseUpdater.updateNSEPrincipalClass(nseConfig.principalClass);
614
+ if (props.appGroupName) await nseUpdater.updateNSEInfoPlistAppGroupKey(props.appGroupName);
615
+ await nseUpdater.updateNSEBundleVersion(config.ios?.buildNumber ?? "1");
616
+ await nseUpdater.updateNSEBundleShortVersion(config?.version ?? "1.0");
617
+ return config;
618
+ }]);
619
+ };
620
+ const withOneSignalXcodeProject = (config, props) => {
621
+ return (0, expo_config_plugins_js.withXcodeProject)(config, (newConfig) => {
622
+ const xcodeProject = newConfig.modResults;
623
+ const nseBundleId = `${config.ios?.bundleIdentifier}.${props.nseBundleIdentifier ?? "OneSignalNotificationServiceExtension"}`;
624
+ const devTeam = resolveDevTeam(config, props);
625
+ const nseConfig = resolveNseConfig(props.iosNSEFilePath);
626
+ const groupFiles = [
627
+ ...NSE_EXT_FILES,
628
+ nseConfig.sourceFile,
629
+ ...nseConfig.headerFile ? [nseConfig.headerFile] : []
630
+ ];
631
+ if (xcodeProject.pbxTargetByName("OneSignalNotificationServiceExtension")) {
632
+ OneSignalLog.log(`${NSE_TARGET_NAME} already exists in project. Skipping...`);
633
+ return newConfig;
634
+ }
635
+ const extGroup = xcodeProject.addPbxGroup(groupFiles, NSE_TARGET_NAME, NSE_TARGET_NAME);
636
+ const groups = xcodeProject.hash.project.objects["PBXGroup"];
637
+ Object.keys(groups).forEach(function(key) {
638
+ if (typeof groups[key] === "object" && groups[key].name === void 0 && groups[key].path === void 0) xcodeProject.addToPbxGroup(extGroup.uuid, key);
639
+ });
640
+ const projObjects = xcodeProject.hash.project.objects;
641
+ projObjects["PBXTargetDependency"] = projObjects["PBXTargetDependency"] || {};
642
+ projObjects["PBXContainerItemProxy"] = projObjects["PBXTargetDependency"] || {};
643
+ const nseTarget = xcodeProject.addTarget(NSE_TARGET_NAME, "app_extension", NSE_TARGET_NAME, nseBundleId);
644
+ xcodeProject.addBuildPhase([nseConfig.sourceFile], "PBXSourcesBuildPhase", "Sources", nseTarget.uuid);
645
+ xcodeProject.addBuildPhase([], "PBXResourcesBuildPhase", "Resources", nseTarget.uuid);
646
+ xcodeProject.addBuildPhase([], "PBXFrameworksBuildPhase", "Frameworks", nseTarget.uuid);
647
+ const configurations = xcodeProject.pbxXCBuildConfigurationSection();
648
+ for (const key in configurations) if (typeof configurations[key].buildSettings !== "undefined" && configurations[key].buildSettings.PRODUCT_NAME == `"OneSignalNotificationServiceExtension"`) {
649
+ const buildSettingsObj = configurations[key].buildSettings;
650
+ buildSettingsObj.DEVELOPMENT_TEAM = devTeam;
651
+ buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = props?.iPhoneDeploymentTarget ?? "11.0";
652
+ buildSettingsObj.TARGETED_DEVICE_FAMILY = TARGETED_DEVICE_FAMILY;
653
+ buildSettingsObj.SWIFT_VERSION = "5.0";
654
+ buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `${NSE_TARGET_NAME}/${NSE_TARGET_NAME}.entitlements`;
655
+ buildSettingsObj.CODE_SIGN_STYLE = "Automatic";
656
+ }
657
+ xcodeProject.addTargetAttribute("DevelopmentTeam", devTeam, nseTarget);
658
+ xcodeProject.addTargetAttribute("DevelopmentTeam", devTeam);
659
+ return newConfig;
660
+ });
661
+ };
662
+ const withSoundFiles = (config, onesignalProps) => {
663
+ if (!onesignalProps.sounds) return config;
664
+ return (0, expo_config_plugins_js.withXcodeProject)(config, (config) => {
665
+ const projectRoot = config.modRequest.projectRoot;
666
+ const projectName = config.modRequest.projectName;
667
+ const sourceRoot = expo_config_plugins_js.IOSConfig.Paths.getSourceRoot(projectRoot);
668
+ let project = config.modResults;
669
+ for (const soundFile of onesignalProps.sounds) {
670
+ const fileName = path.basename(soundFile);
671
+ const sourcePath = path.resolve(projectRoot, soundFile);
672
+ const destPath = path.resolve(sourceRoot, fileName);
673
+ OneSignalLog.log(`Copying sound file ${soundFile} to iOS project`);
674
+ fs.copyFileSync(sourcePath, destPath);
675
+ if (!project.hasFile(`${projectName}/${fileName}`)) project = expo_config_plugins_js.IOSConfig.XcodeUtils.addResourceFileToGroup({
676
+ filepath: `${projectName}/${fileName}`,
677
+ groupName: projectName,
678
+ project,
679
+ isBuildFile: true
680
+ });
681
+ }
682
+ config.modResults = project;
683
+ return config;
684
+ });
685
+ };
686
+ const withOneSignalIos = (config, props) => {
687
+ config = withAppEnvironment(config, props);
688
+ config = withRemoteNotificationsPermissions(config, props);
689
+ if (!props.disableNSE) {
690
+ config = withCustomAppGroupsKey(config, props);
691
+ config = withAppGroupPermissions(config, props);
692
+ config = withOneSignalPodfile(config, props);
693
+ config = withOneSignalNSE(config, props);
694
+ config = withOneSignalXcodeProject(config, props);
695
+ }
696
+ config = withSoundFiles(config, props);
697
+ if (props.liveActivities) config = withOneSignalLiveActivity(config, props);
698
+ if (!props.disableNSE || props.liveActivities) config = withEasManagedCredentials(config, props);
699
+ return config;
700
+ };
701
+ //#endregion
702
+ //#region src/onesignal/withOneSignal.ts
703
+ const withOneSignal = (config, props) => {
704
+ if (!props) throw new Error("You are trying to use the OneSignal plugin without any props. Property \"mode\" is required. Please see https://github.com/OneSignal/onesignal-expo-plugin for more info.");
705
+ validatePluginProps(props);
706
+ config = withOneSignalIos(config, props);
707
+ config = withOneSignalAndroid(config, props);
708
+ return config;
709
+ };
710
+ //#endregion
711
+ module.exports = withOneSignal;