onesignal-expo-plugin 2.3.0 → 2.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.
package/README.md CHANGED
@@ -81,7 +81,7 @@ You can pass props to the plugin config object to configure:
81
81
  | Plugin Prop | | |
82
82
  | ------------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
83
83
  | `mode` | **required** | Used to configure [APNs environment](https://developer.apple.com/documentation/bundleresources/entitlements/aps-environment) entitlement. `"development"` or `"production"` |
84
- | `devTeam` | optional | Used to configure Apple Team ID. You can find your Apple Team ID by running `expo credentials:manager` e.g: `"91SW8A37CR"` |
84
+ | `devTeam` | **deprecated** | Use [`ios.appleTeamId`](https://docs.expo.dev/versions/latest/config/app/#appleteamid) in your Expo config instead. Falls back to this value if `appleTeamId` is not set. e.g: `"91SW8A37CR"` |
85
85
  | `iPhoneDeploymentTarget` | optional | Target `IPHONEOS_DEPLOYMENT_TARGET` value to be used when adding the iOS [NSE](https://documentation.onesignal.com/docs/service-extensions). A deployment target is nothing more than the minimum version of the operating system the application can run on. This value should match the value in your Podfile e.g: `"12.0"`. |
86
86
  | `smallIcons` | optional | An array of local paths to small notification icons for Android. Image should be white, transparent, and 96x96 in size. Input images will be automatically scaled down and placed in the appropriate resource folders. e.g: `["./assets/ic_stat_onesignal_default.png"]`. See https://documentation.onesignal.com/docs/customize-notification-icons#small-notification-icons. |
87
87
  | `largeIcons` | optional | An array of local paths to large notification icons for Android. Image should be white, transparent, and 256x256 in size. e.g: `["./assets/ic_onesignal_large_icon_default.png"]`. See https://documentation.onesignal.com/docs/customize-notification-icons#large-notification-icons. |
@@ -89,6 +89,8 @@ You can pass props to the plugin config object to configure:
89
89
  | `iosNSEFilePath` | optional | The local path to a custom Notification Service Extension (NSE), written in Objective-C. The NSE will typically start as a copy of the [default NSE](https://github.com/OneSignal/onesignal-expo-plugin/blob/main/support/serviceExtensionFiles/NotificationService.m), then altered to support any custom logic required. e.g: `"./assets/NotificationService.m"`. |
90
90
  | `appGroupName` | optional | Used to configure a custom iOS [App Group](https://documentation.onesignal.com/docs/ios-sdk-setup#step-3-create-an-app-group) name. If not provided, defaults to `"group.{ios.bundleIdentifier}.onesignal"`. e.g: `"group.com.example.myapp.onesignal2"`. |
91
91
  | `nseBundleIdentifier` | optional | Used to configure a custom bundle identifier suffix for the iOS Notification Service Extension. The full bundle identifier will be `"{ios.bundleIdentifier}.{nseBundleIdentifier}"`. If not provided, defaults to `"OneSignalNotificationServiceExtension"`. |
92
+ | `disableNSE` | optional | If `true`, the iOS Notification Service Extension (NSE) will not be added to the project. The NSE is required for badges, confirmed delivery, media attachments, and action buttons. Only disable this if you only need basic push notifications. |
93
+ | `sounds` | optional | An array of local paths to custom notification sound files (`.wav` only, ≤30 seconds). Files are copied into the app bundle on iOS and `res/raw/` on Android. e.g: `["./assets/notification_sound.wav"]`. See https://documentation.onesignal.com/docs/customize-notification-sounds. |
92
94
 
93
95
  ### OneSignal App ID
94
96
 
@@ -8,6 +8,7 @@ exports.withOneSignalAndroid = void 0;
8
8
  const config_plugins_1 = require("@expo/config-plugins");
9
9
  const image_utils_1 = require("@expo/image-utils");
10
10
  const OneSignalLog_1 = require("../support/OneSignalLog");
11
+ const helpers_1 = require("../support/helpers");
11
12
  const path_1 = require("path");
12
13
  const fs_1 = require("fs");
13
14
  const RESOURCE_ROOT_PATH = 'android/app/src/main/res/';
@@ -61,18 +62,20 @@ const withSmallIconAccentColor = (config, onesignalProps) => {
61
62
  return config;
62
63
  }
63
64
  return (0, config_plugins_1.withStringsXml)(config, (config) => {
64
- const colorInARGB = `FF${onesignalProps.smallIconAccentColor?.replace('#', '')}`;
65
+ const colorInARGB = (0, helpers_1.parseColorToARGB)(onesignalProps.smallIconAccentColor ?? '');
65
66
  const strings = config.modResults.resources.string ?? [];
66
- // Check if the accent color entry already exists
67
- const hasAccentColor = strings.some((stringEntry) => stringEntry.$?.name === 'onesignal_notification_accent_color' &&
68
- stringEntry._ === colorInARGB);
69
- if (!hasAccentColor) {
70
- const accentColorEntry = {
71
- $: { name: 'onesignal_notification_accent_color' },
72
- _: colorInARGB,
73
- };
74
- config.modResults.resources.string = [...strings, accentColorEntry];
67
+ const existingIndex = strings.findIndex((stringEntry) => stringEntry.$?.name === 'onesignal_notification_accent_color');
68
+ const accentColorEntry = {
69
+ $: { name: 'onesignal_notification_accent_color' },
70
+ _: colorInARGB,
71
+ };
72
+ if (existingIndex !== -1) {
73
+ strings[existingIndex] = accentColorEntry;
75
74
  }
75
+ else {
76
+ strings.push(accentColorEntry);
77
+ }
78
+ config.modResults.resources.string = strings;
76
79
  return config;
77
80
  });
78
81
  };
@@ -101,26 +104,33 @@ async function saveIconAsync(icon, projectRoot, dirsToSize, outputName) {
101
104
  (0, fs_1.writeFileSync)((0, path_1.resolve)(path, name + '.png'), resizedIcon);
102
105
  }
103
106
  }
104
- const OKHTTP_FORCE_FIX = `
105
- subprojects {
106
- project.configurations.all {
107
- resolutionStrategy.force 'com.squareup.okhttp3:okhttp:4.12.0'
107
+ const withSoundFiles = (config, onesignalProps) => {
108
+ if (!onesignalProps.sounds) {
109
+ return config;
108
110
  }
109
- }`;
110
- const withOkHttpFix = (config) => {
111
- return (0, config_plugins_1.withProjectBuildGradle)(config, (config) => {
112
- if (config.modResults.contents.includes("force 'com.squareup.okhttp3:okhttp:4.12.0'")) {
111
+ return (0, config_plugins_1.withDangerousMod)(config, [
112
+ 'android',
113
+ async (config) => {
114
+ const projectRoot = config.modRequest.projectRoot;
115
+ const rawDir = (0, path_1.resolve)(projectRoot, RESOURCE_ROOT_PATH, 'raw');
116
+ if (!(0, fs_1.existsSync)(rawDir)) {
117
+ (0, fs_1.mkdirSync)(rawDir, { recursive: true });
118
+ }
119
+ for (const soundFile of onesignalProps.sounds) {
120
+ const sourcePath = (0, path_1.resolve)(projectRoot, soundFile);
121
+ const fileName = (0, path_1.basename)(sourcePath).toLowerCase();
122
+ OneSignalLog_1.OneSignalLog.log(`Copying sound file ${soundFile} to res/raw/${fileName}`);
123
+ (0, fs_1.copyFileSync)(sourcePath, (0, path_1.resolve)(rawDir, fileName));
124
+ }
113
125
  return config;
114
- }
115
- config.modResults.contents += OKHTTP_FORCE_FIX;
116
- return config;
117
- });
126
+ },
127
+ ]);
118
128
  };
119
129
  const withOneSignalAndroid = (config, props) => {
120
130
  config = withSmallIcons(config, props);
121
131
  config = withLargeIcons(config, props);
122
132
  config = withSmallIconAccentColor(config, props);
123
- config = withOkHttpFix(config);
133
+ config = withSoundFiles(config, props);
124
134
  return config;
125
135
  };
126
136
  exports.withOneSignalAndroid = withOneSignalAndroid;
@@ -4,4 +4,10 @@
4
4
  */
5
5
  import { ConfigPlugin } from '@expo/config-plugins';
6
6
  import { OneSignalPluginProps } from '../types/types';
7
+ import { ExpoConfig } from '@expo/config-types';
8
+ /**
9
+ * Resolve the Apple development team ID. Prefers `ios.appleTeamId` from the
10
+ * Expo config, falling back to the plugin's deprecated `devTeam` prop.
11
+ */
12
+ export declare function resolveDevTeam(config: ExpoConfig, props: OneSignalPluginProps): string | undefined;
7
13
  export declare const withOneSignalIos: ConfigPlugin<OneSignalPluginProps>;
@@ -41,6 +41,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
41
41
  };
42
42
  Object.defineProperty(exports, "__esModule", { value: true });
43
43
  exports.withOneSignalIos = void 0;
44
+ exports.resolveDevTeam = resolveDevTeam;
44
45
  const config_plugins_1 = require("@expo/config-plugins");
45
46
  const fs = __importStar(require("fs"));
46
47
  const path = __importStar(require("path"));
@@ -165,10 +166,30 @@ const withOneSignalNSE = (config, props) => {
165
166
  },
166
167
  ]);
167
168
  };
169
+ /**
170
+ * Resolve the Apple development team ID. Prefers `ios.appleTeamId` from the
171
+ * Expo config, falling back to the plugin's deprecated `devTeam` prop.
172
+ */
173
+ function resolveDevTeam(config, props) {
174
+ if (config.ios?.appleTeamId) {
175
+ if (props.devTeam) {
176
+ OneSignalLog_1.OneSignalLog.log('Warning: Both "ios.appleTeamId" and the deprecated "devTeam" plugin prop are set. ' +
177
+ '"devTeam" will be ignored. Remove "devTeam" from your plugin config.');
178
+ }
179
+ return config.ios.appleTeamId;
180
+ }
181
+ if (props.devTeam) {
182
+ OneSignalLog_1.OneSignalLog.log('Warning: The "devTeam" plugin prop is deprecated and will be removed in a future major release. ' +
183
+ 'Set "ios.appleTeamId" in your Expo config instead.');
184
+ return props.devTeam;
185
+ }
186
+ return undefined;
187
+ }
168
188
  const withOneSignalXcodeProject = (config, props) => {
169
189
  return (0, config_plugins_1.withXcodeProject)(config, (newConfig) => {
170
190
  const xcodeProject = newConfig.modResults;
171
191
  const nseBundleId = `${config.ios?.bundleIdentifier}.${props.nseBundleIdentifier ?? iosConstants_1.NSE_TARGET_NAME}`;
192
+ const devTeam = resolveDevTeam(config, props);
172
193
  if (xcodeProject.pbxTargetByName(iosConstants_1.NSE_TARGET_NAME)) {
173
194
  OneSignalLog_1.OneSignalLog.log(`${iosConstants_1.NSE_TARGET_NAME} already exists in project. Skipping...`);
174
195
  return newConfig;
@@ -208,7 +229,7 @@ const withOneSignalXcodeProject = (config, props) => {
208
229
  if (typeof configurations[key].buildSettings !== 'undefined' &&
209
230
  configurations[key].buildSettings.PRODUCT_NAME == `"${iosConstants_1.NSE_TARGET_NAME}"`) {
210
231
  const buildSettingsObj = configurations[key].buildSettings;
211
- buildSettingsObj.DEVELOPMENT_TEAM = props?.devTeam;
232
+ buildSettingsObj.DEVELOPMENT_TEAM = devTeam;
212
233
  buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET =
213
234
  props?.iPhoneDeploymentTarget ?? iosConstants_1.IPHONEOS_DEPLOYMENT_TARGET;
214
235
  buildSettingsObj.TARGETED_DEVICE_FAMILY = iosConstants_1.TARGETED_DEVICE_FAMILY;
@@ -217,11 +238,39 @@ const withOneSignalXcodeProject = (config, props) => {
217
238
  }
218
239
  }
219
240
  // Add development teams to both your target and the original project
220
- xcodeProject.addTargetAttribute('DevelopmentTeam', props?.devTeam, nseTarget);
221
- xcodeProject.addTargetAttribute('DevelopmentTeam', props?.devTeam);
241
+ xcodeProject.addTargetAttribute('DevelopmentTeam', devTeam, nseTarget);
242
+ xcodeProject.addTargetAttribute('DevelopmentTeam', devTeam);
222
243
  return newConfig;
223
244
  });
224
245
  };
246
+ const withSoundFiles = (config, onesignalProps) => {
247
+ if (!onesignalProps.sounds) {
248
+ return config;
249
+ }
250
+ return (0, config_plugins_1.withXcodeProject)(config, (config) => {
251
+ const projectRoot = config.modRequest.projectRoot;
252
+ const projectName = config.modRequest.projectName;
253
+ const sourceRoot = config_plugins_1.IOSConfig.Paths.getSourceRoot(projectRoot);
254
+ let project = config.modResults;
255
+ for (const soundFile of onesignalProps.sounds) {
256
+ const fileName = path.basename(soundFile);
257
+ const sourcePath = path.resolve(projectRoot, soundFile);
258
+ const destPath = path.resolve(sourceRoot, fileName);
259
+ OneSignalLog_1.OneSignalLog.log(`Copying sound file ${soundFile} to iOS project`);
260
+ fs.copyFileSync(sourcePath, destPath);
261
+ if (!project.hasFile(`${projectName}/${fileName}`)) {
262
+ project = config_plugins_1.IOSConfig.XcodeUtils.addResourceFileToGroup({
263
+ filepath: `${projectName}/${fileName}`,
264
+ groupName: projectName,
265
+ project,
266
+ isBuildFile: true,
267
+ });
268
+ }
269
+ }
270
+ config.modResults = project;
271
+ return config;
272
+ });
273
+ };
225
274
  const withOneSignalIos = (config, props) => {
226
275
  config = withAppEnvironment(config, props);
227
276
  config = withRemoteNotificationsPermissions(config, props);
@@ -233,6 +282,7 @@ const withOneSignalIos = (config, props) => {
233
282
  config = withOneSignalXcodeProject(config, props);
234
283
  config = withEasManagedCredentials(config, props);
235
284
  }
285
+ config = withSoundFiles(config, props);
236
286
  return config;
237
287
  };
238
288
  exports.withOneSignalIos = withOneSignalIos;
@@ -1,2 +1,7 @@
1
+ /**
2
+ * Converts a hex color string (#RGB, #RRGGBB, or #AARRGGBB) to an
3
+ * 8-character ARGB hex string (no '#') expected by the OneSignal Android SDK.
4
+ */
5
+ export declare function parseColorToARGB(color: string): string;
1
6
  export declare function validatePluginProps(props: any): void;
2
7
  export declare function getAppGroupIdentifier(bundleIdentifier: string, customAppGroupName?: string): string;
@@ -1,8 +1,33 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseColorToARGB = parseColorToARGB;
3
4
  exports.validatePluginProps = validatePluginProps;
4
5
  exports.getAppGroupIdentifier = getAppGroupIdentifier;
6
+ const path_1 = require("path");
5
7
  const types_1 = require("../types/types");
8
+ const HEX_COLOR_6_REGEX = /^#?[0-9A-Fa-f]{6}$/;
9
+ const HEX_COLOR_8_REGEX = /^#?[0-9A-Fa-f]{8}$/;
10
+ /**
11
+ * Converts a hex color string (#RGB, #RRGGBB, or #AARRGGBB) to an
12
+ * 8-character ARGB hex string (no '#') expected by the OneSignal Android SDK.
13
+ */
14
+ function parseColorToARGB(color) {
15
+ const hex = color.replace('#', '');
16
+ if (HEX_COLOR_8_REGEX.test(hex)) {
17
+ return hex.toUpperCase();
18
+ }
19
+ if (HEX_COLOR_6_REGEX.test(hex)) {
20
+ return `FF${hex.toUpperCase()}`;
21
+ }
22
+ if (/^[0-9A-Fa-f]{3}$/.test(hex)) {
23
+ const expanded = hex
24
+ .split('')
25
+ .map((c) => c + c)
26
+ .join('');
27
+ return `FF${expanded.toUpperCase()}`;
28
+ }
29
+ throw new Error(`OneSignal Expo Plugin: 'smallIconAccentColor' must be a valid hex color (e.g. "#FF0000"), got "${color}".`);
30
+ }
6
31
  function validatePluginProps(props) {
7
32
  // check the type of each property
8
33
  if (typeof props.mode !== 'string') {
@@ -18,9 +43,11 @@ function validatePluginProps(props) {
18
43
  if (props.smallIcons && !Array.isArray(props.smallIcons)) {
19
44
  throw new Error("OneSignal Expo Plugin: 'smallIcons' must be an array.");
20
45
  }
21
- if (props.smallIconAccentColor &&
22
- typeof props.smallIconAccentColor !== 'string') {
23
- throw new Error("OneSignal Expo Plugin: 'smallIconAccentColor' must be a string.");
46
+ if (props.smallIconAccentColor != null) {
47
+ if (typeof props.smallIconAccentColor !== 'string') {
48
+ throw new Error("OneSignal Expo Plugin: 'smallIconAccentColor' must be a string.");
49
+ }
50
+ parseColorToARGB(props.smallIconAccentColor);
24
51
  }
25
52
  if (props.largeIcons && !Array.isArray(props.largeIcons)) {
26
53
  throw new Error("OneSignal Expo Plugin: 'largeIcons' must be an array.");
@@ -38,6 +65,19 @@ function validatePluginProps(props) {
38
65
  if (props.disableNSE !== undefined && typeof props.disableNSE !== 'boolean') {
39
66
  throw new Error("OneSignal Expo Plugin: 'disableNSE' must be a boolean.");
40
67
  }
68
+ if (props.sounds !== undefined) {
69
+ if (!Array.isArray(props.sounds)) {
70
+ throw new Error("OneSignal Expo Plugin: 'sounds' must be an array.");
71
+ }
72
+ for (const sound of props.sounds) {
73
+ if (typeof sound !== 'string') {
74
+ throw new Error("OneSignal Expo Plugin: each entry in 'sounds' must be a string path.");
75
+ }
76
+ if ((0, path_1.extname)(sound).toLowerCase() !== '.wav') {
77
+ throw new Error(`OneSignal Expo Plugin: sound file "${sound}" must be a .wav file. Only .wav is supported (works on both iOS and Android).`);
78
+ }
79
+ }
80
+ }
41
81
  // check for extra properties
42
82
  const inputProps = Object.keys(props);
43
83
  for (const prop of inputProps) {
@@ -8,6 +8,7 @@ export type OneSignalPluginProps = {
8
8
  mode: 'development' | 'production';
9
9
  /**
10
10
  * (optional) Used to configure Apple Team ID. You can find your Apple Team ID by running expo credentials:manager e.g: "91SW8A37CR"
11
+ * @deprecated Use `ios.appleTeamId` in your Expo config instead. This prop will be removed in a future major release.
11
12
  */
12
13
  devTeam?: string;
13
14
  /**
@@ -51,5 +52,11 @@ export type OneSignalPluginProps = {
51
52
  * Only disable if you only need basic push notifications.
52
53
  */
53
54
  disableNSE?: boolean;
55
+ /**
56
+ * (optional) An array of local paths to custom notification sound files (.wav only).
57
+ * Files are added to the iOS app bundle and Android res/raw/.
58
+ * @see https://documentation.onesignal.com/docs/customize-notification-sounds
59
+ */
60
+ sounds?: string[];
54
61
  };
55
62
  export declare const ONESIGNAL_PLUGIN_PROPS: string[];
@@ -12,4 +12,5 @@ exports.ONESIGNAL_PLUGIN_PROPS = [
12
12
  'appGroupName',
13
13
  'nseBundleIdentifier',
14
14
  'disableNSE',
15
+ 'sounds',
15
16
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onesignal-expo-plugin",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Some Changez",
5
5
  "main": "./app.plugin.js",
6
6
  "types": "./build/types/types.d.ts",