@fishonfire/bubbles-expo 0.1.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.
@@ -0,0 +1,6 @@
1
+ import {
2
+ registerBubblesBackgroundHandlers
3
+ } from "./chunk-6NPXFGQX.mjs";
4
+
5
+ // src/register-task.ts
6
+ registerBubblesBackgroundHandlers();
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@fishonfire/bubbles-expo",
3
+ "version": "0.1.0",
4
+ "description": "Expo config plugin, explicit background registration API, and React provider for Bubbles notifications.",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ },
15
+ "./register-task": {
16
+ "types": "./dist/register-task.d.ts",
17
+ "import": "./dist/register-task.mjs",
18
+ "require": "./dist/register-task.js"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "plugin/build",
24
+ "app.plugin.js",
25
+ "README.md"
26
+ ],
27
+ "sideEffects": [
28
+ "./src/register-task.ts",
29
+ "./dist/register-task.js",
30
+ "./dist/register-task.mjs"
31
+ ],
32
+ "scripts": {
33
+ "build": "npm run build:runtime && npm run build:plugin",
34
+ "build:runtime": "tsup src/index.ts src/register-task.ts --format cjs,esm --dts --clean",
35
+ "build:plugin": "rm -rf plugin/build && tsc -p plugin/tsconfig.json",
36
+ "clean": "rm -rf dist plugin/build",
37
+ "lint": "expo lint src --no-cache --max-warnings 0",
38
+ "prepare": "npm run build",
39
+ "test": "vitest run",
40
+ "typecheck": "npm run typecheck:runtime && npm run typecheck:plugin && npm run typecheck:test",
41
+ "typecheck:test": "tsc -p tsconfig.test.json --noEmit",
42
+ "typecheck:runtime": "tsc --noEmit",
43
+ "typecheck:plugin": "tsc -p plugin/tsconfig.json --noEmit"
44
+ },
45
+ "keywords": [
46
+ "bubbles",
47
+ "expo",
48
+ "expo-notifications",
49
+ "config-plugin",
50
+ "react-native",
51
+ "background-task",
52
+ "push-notifications",
53
+ "fcm",
54
+ "apns"
55
+ ],
56
+ "peerDependencies": {
57
+ "@react-native-firebase/app": ">=26.3.3",
58
+ "@react-native-firebase/installations": ">=26.3.3",
59
+ "@react-native-firebase/messaging": ">=26.3.3",
60
+ "expo": ">=57.0.0",
61
+ "expo-notifications": ">=0.28.0",
62
+ "react": ">=19.0.0",
63
+ "react-native": ">=0.72.0"
64
+ },
65
+ "peerDependenciesMeta": {
66
+ "expo": {
67
+ "optional": true
68
+ }
69
+ },
70
+ "dependencies": {
71
+ "@fishonfire/bubbles-js": "0.1.0",
72
+ "expo-constants": "~57.0.16",
73
+ "expo-file-system": "~57.0.6"
74
+ },
75
+ "devDependencies": {
76
+ "@react-native-firebase/app": "^26.3.3",
77
+ "@react-native-firebase/installations": "^26.3.3",
78
+ "@react-native-firebase/messaging": "^26.3.3",
79
+ "@types/react": "^19.2.18",
80
+ "expo": "~57.0.11",
81
+ "expo-notifications": "~57.0.9",
82
+ "react": "^19.2.8",
83
+ "react-native": "^0.87.1",
84
+ "eslint": "^9.0.0",
85
+ "eslint-config-expo": "~57.0.1",
86
+ "tsup": "^8.2.4",
87
+ "tsx": "^4.19.2",
88
+ "typescript": "^5.5.4",
89
+ "vitest": "^3.0.0"
90
+ },
91
+ "publishConfig": {
92
+ "access": "public"
93
+ }
94
+ }
@@ -0,0 +1,18 @@
1
+ import { type AndroidChannelImportance } from './shared-config';
2
+ export type BubblesNotificationsExpoPluginConfig = {
3
+ defaultChannelId?: string;
4
+ defaultChannelName?: string;
5
+ androidChannelImportance?: AndroidChannelImportance;
6
+ androidNotificationIcon?: string;
7
+ androidNotificationColor?: string;
8
+ enableBackgroundRemoteNotifications?: boolean;
9
+ };
10
+ export type NormalizedBubblesNotificationsExpoPluginConfig = {
11
+ defaultChannelId: string;
12
+ defaultChannelName: string;
13
+ androidChannelImportance: AndroidChannelImportance;
14
+ androidNotificationIcon?: string;
15
+ androidNotificationColor?: string;
16
+ enableBackgroundRemoteNotifications: boolean;
17
+ };
18
+ export declare function normalizePluginConfig(input?: BubblesNotificationsExpoPluginConfig | void): NormalizedBubblesNotificationsExpoPluginConfig;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizePluginConfig = normalizePluginConfig;
4
+ const shared_config_1 = require("./shared-config");
5
+ function fail(message) {
6
+ throw new Error(`[@fishonfire/bubbles-expo] ${message}`);
7
+ }
8
+ function assertPlainObjectOrUndefined(value) {
9
+ if (value === undefined) {
10
+ return;
11
+ }
12
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
13
+ fail('Plugin options must be an object, for example ["@fishonfire/bubbles-expo", { "defaultChannelId": "default" }].');
14
+ }
15
+ }
16
+ function getOptionalNonEmptyString(value, propertyName) {
17
+ if (value === undefined) {
18
+ return undefined;
19
+ }
20
+ if (typeof value !== 'string') {
21
+ fail(`"${propertyName}" must be a string.`);
22
+ }
23
+ const trimmedValue = value.trim();
24
+ if (!trimmedValue) {
25
+ fail(`"${propertyName}" must not be empty.`);
26
+ }
27
+ return trimmedValue;
28
+ }
29
+ function getBooleanWithDefault(value, propertyName, fallback) {
30
+ if (value === undefined) {
31
+ return fallback;
32
+ }
33
+ if (typeof value !== 'boolean') {
34
+ fail(`"${propertyName}" must be a boolean.`);
35
+ }
36
+ return value;
37
+ }
38
+ function getAndroidChannelImportance(value) {
39
+ if (value === undefined) {
40
+ return 'max';
41
+ }
42
+ if (typeof value !== 'string' ||
43
+ !shared_config_1.ANDROID_CHANNEL_IMPORTANCE_VALUES.includes(value)) {
44
+ fail(`"androidChannelImportance" must be one of: ${shared_config_1.ANDROID_CHANNEL_IMPORTANCE_VALUES.join(', ')}.`);
45
+ }
46
+ return value;
47
+ }
48
+ function getOptionalNotificationColor(value) {
49
+ const color = getOptionalNonEmptyString(value, 'androidNotificationColor');
50
+ if (color === undefined) {
51
+ return undefined;
52
+ }
53
+ if (!/^#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(color)) {
54
+ fail('"androidNotificationColor" must be a hex color in #RRGGBB or #AARRGGBB format.');
55
+ }
56
+ return color;
57
+ }
58
+ function normalizePluginConfig(input) {
59
+ assertPlainObjectOrUndefined(input);
60
+ const options = input ?? {};
61
+ return {
62
+ defaultChannelId: getOptionalNonEmptyString(options.defaultChannelId, 'defaultChannelId') ?? 'default',
63
+ defaultChannelName: getOptionalNonEmptyString(options.defaultChannelName, 'defaultChannelName') ?? 'Default',
64
+ androidChannelImportance: getAndroidChannelImportance(options.androidChannelImportance),
65
+ androidNotificationIcon: getOptionalNonEmptyString(options.androidNotificationIcon, 'androidNotificationIcon'),
66
+ androidNotificationColor: getOptionalNotificationColor(options.androidNotificationColor),
67
+ enableBackgroundRemoteNotifications: getBooleanWithDefault(options.enableBackgroundRemoteNotifications, 'enableBackgroundRemoteNotifications', true),
68
+ };
69
+ }
@@ -0,0 +1,4 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import { BubblesNotificationsExpoPluginConfig } from './config';
3
+ declare const withBubblesNotificationsExpo: ConfigPlugin<BubblesNotificationsExpoPluginConfig | void>;
4
+ export default withBubblesNotificationsExpo;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const config_1 = require("./config");
7
+ const with_firebase_messaging_manifest_1 = __importDefault(require("./with-firebase-messaging-manifest"));
8
+ const with_expo_notifications_1 = __importDefault(require("./with-expo-notifications"));
9
+ const with_runtime_defaults_1 = __importDefault(require("./with-runtime-defaults"));
10
+ const with_rnfirebase_disable_spm_1 = __importDefault(require("./with-rnfirebase-disable-spm"));
11
+ const withBubblesNotificationsExpo = (config, input) => {
12
+ const options = (0, config_1.normalizePluginConfig)(input);
13
+ config = (0, with_runtime_defaults_1.default)(config, options);
14
+ config = (0, with_expo_notifications_1.default)(config, options);
15
+ config = (0, with_firebase_messaging_manifest_1.default)(config, options);
16
+ config = (0, with_rnfirebase_disable_spm_1.default)(config);
17
+ return config;
18
+ };
19
+ exports.default = withBubblesNotificationsExpo;
@@ -0,0 +1,3 @@
1
+ export declare const BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY = "bubblesNotificationsExpo";
2
+ export declare const ANDROID_CHANNEL_IMPORTANCE_VALUES: readonly ["min", "low", "default", "high", "max"];
3
+ export type AndroidChannelImportance = (typeof ANDROID_CHANNEL_IMPORTANCE_VALUES)[number];
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ANDROID_CHANNEL_IMPORTANCE_VALUES = exports.BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY = void 0;
4
+ exports.BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY = 'bubblesNotificationsExpo';
5
+ exports.ANDROID_CHANNEL_IMPORTANCE_VALUES = [
6
+ 'min',
7
+ 'low',
8
+ 'default',
9
+ 'high',
10
+ 'max',
11
+ ];
@@ -0,0 +1,3 @@
1
+ import type { ExpoConfig } from '@expo/config-types';
2
+ import type { NormalizedBubblesNotificationsExpoPluginConfig } from './config';
3
+ export default function withExpoNotifications(config: ExpoConfig, options: NormalizedBubblesNotificationsExpoPluginConfig): ExpoConfig;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = withExpoNotifications;
4
+ const config_plugins_1 = require("expo/config-plugins");
5
+ function withExpoNotifications(config, options) {
6
+ const pluginOptions = {
7
+ defaultChannel: options.defaultChannelId,
8
+ enableBackgroundRemoteNotifications: options.enableBackgroundRemoteNotifications,
9
+ };
10
+ if (options.androidNotificationIcon) {
11
+ pluginOptions.icon = options.androidNotificationIcon;
12
+ }
13
+ if (options.androidNotificationColor) {
14
+ pluginOptions.color = options.androidNotificationColor;
15
+ }
16
+ return (0, config_plugins_1.withStaticPlugin)(config, {
17
+ plugin: ['expo-notifications', pluginOptions],
18
+ });
19
+ }
@@ -0,0 +1,4 @@
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
+ import type { NormalizedBubblesNotificationsExpoPluginConfig } from './config';
3
+ declare const withFirebaseMessagingManifest: ConfigPlugin<NormalizedBubblesNotificationsExpoPluginConfig>;
4
+ export default withFirebaseMessagingManifest;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("expo/config-plugins");
4
+ const FCM_DEFAULT_CHANNEL = 'com.google.firebase.messaging.default_notification_channel_id';
5
+ const FCM_DEFAULT_COLOR = 'com.google.firebase.messaging.default_notification_color';
6
+ const withFirebaseMessagingManifest = (config, options) => {
7
+ return (0, config_plugins_1.withAndroidManifest)(config, config => {
8
+ const manifest = config.modResults;
9
+ const application = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(manifest);
10
+ config_plugins_1.AndroidConfig.Manifest.ensureToolsAvailable(manifest);
11
+ // Always define the default channel ourselves.
12
+ //
13
+ // This makes the compatibility fix independent of whether
14
+ // expo-notifications' manifest mod runs before or after this one.
15
+ config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(application, FCM_DEFAULT_CHANNEL, options.defaultChannelId, 'value');
16
+ const channel = application['meta-data']?.find(item => item.$['android:name'] === FCM_DEFAULT_CHANNEL);
17
+ if (channel) {
18
+ setToolsReplace(channel.$, 'android:value');
19
+ }
20
+ // Expo only creates the FCM color metadata when a notification
21
+ // color has actually been configured.
22
+ if (options.androidNotificationColor) {
23
+ config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(application, FCM_DEFAULT_COLOR, '@color/notification_icon_color', 'resource');
24
+ const color = application['meta-data']?.find(item => item.$['android:name'] === FCM_DEFAULT_COLOR);
25
+ if (color) {
26
+ setToolsReplace(color.$, 'android:resource');
27
+ }
28
+ }
29
+ return config;
30
+ });
31
+ };
32
+ function setToolsReplace(attributes, value) {
33
+ attributes['tools:replace'] = value;
34
+ }
35
+ exports.default = withFirebaseMessagingManifest;
@@ -0,0 +1,3 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ declare const withRNFirebaseDisableSPM: ConfigPlugin;
3
+ export default withRNFirebaseDisableSPM;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config_plugins_1 = require("@expo/config-plugins");
4
+ const withRNFirebaseDisableSPM = (config) => {
5
+ return (0, config_plugins_1.withPodfile)(config, (config) => {
6
+ const flag = '$RNFirebaseDisableSPM = true';
7
+ if (!config.modResults.contents.includes(flag)) {
8
+ config.modResults.contents =
9
+ `${flag}\n${config.modResults.contents}`;
10
+ }
11
+ return config;
12
+ });
13
+ };
14
+ exports.default = withRNFirebaseDisableSPM;
@@ -0,0 +1,3 @@
1
+ import type { ExpoConfig } from '@expo/config-types';
2
+ import type { NormalizedBubblesNotificationsExpoPluginConfig } from './config';
3
+ export default function withRuntimeDefaults(config: ExpoConfig, options: NormalizedBubblesNotificationsExpoPluginConfig): ExpoConfig;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = withRuntimeDefaults;
4
+ const shared_config_1 = require("./shared-config");
5
+ function fail(message) {
6
+ throw new Error(`[@fishonfire/bubbles-expo] ${message}`);
7
+ }
8
+ function isPlainObject(value) {
9
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
10
+ }
11
+ function withRuntimeDefaults(config, options) {
12
+ const existingExtra = config.extra ?? {};
13
+ const existingRuntimeDefaults = existingExtra[shared_config_1.BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY];
14
+ if (existingRuntimeDefaults !== undefined &&
15
+ !isPlainObject(existingRuntimeDefaults)) {
16
+ fail(`"expo.extra.${shared_config_1.BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY}" must be an object when it is defined.`);
17
+ }
18
+ const runtimeDefaults = {
19
+ defaultChannelId: options.defaultChannelId,
20
+ defaultChannelName: options.defaultChannelName,
21
+ androidChannelImportance: options.androidChannelImportance,
22
+ };
23
+ return {
24
+ ...config,
25
+ extra: {
26
+ ...existingExtra,
27
+ [shared_config_1.BUBBLES_NOTIFICATIONS_EXPO_EXTRA_KEY]: {
28
+ ...(existingRuntimeDefaults ?? {}),
29
+ ...runtimeDefaults,
30
+ },
31
+ },
32
+ };
33
+ }