@kode-frontend/react-native-push-notification
Version:
Push notification service for React Native apps
609 lines (601 loc) • 20.1 kB
JavaScript
import { createStore, createEvent } from 'effector';
import { persist } from 'effector-storage/rn/async';
import { Platform, Linking, AppState, Image } from 'react-native';
import { useStore, useEvent } from 'effector-react';
import React2, { useMemo, useCallback, useEffect, useState } from 'react';
import notifee, { AndroidStyle, EventType, AndroidImportance, AuthorizationStatus } from '@notifee/react-native';
import messaging from '@react-native-firebase/messaging';
import { v4 } from 'uuid';
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
var isPermisionsGrantedByDefault = Platform.OS === "android" && Platform.Version < 33;
// src/model/push-settings.ts
var initialState = {
fcmToken: null,
isNotificationEnabled: isPermisionsGrantedByDefault,
needFirstRequestPermission: true
};
var $pushSettings = createStore(initialState);
var updateFcmToken = createEvent();
var enablePush = createEvent();
var disablePush = createEvent();
var skipRequestPermissions = createEvent();
var resetPushSettings = createEvent();
$pushSettings.on(updateFcmToken, (state, fcmToken) => __spreadProps(__spreadValues({}, state), {
fcmToken
})).on(enablePush, (state) => __spreadProps(__spreadValues({}, state), {
isNotificationEnabled: true,
needFirstRequestPermission: false
})).on(disablePush, (state) => __spreadProps(__spreadValues({}, state), { isNotificationEnabled: false })).on(skipRequestPermissions, (state) => __spreadProps(__spreadValues({}, state), {
isNotificationEnabled: false,
needFirstRequestPermission: false
})).on(resetPushSettings, (state) => __spreadProps(__spreadValues({}, initialState), {
needFirstRequestPermission: state.needFirstRequestPermission,
isNotificationEnabled: state.isNotificationEnabled
}));
persist({
store: $pushSettings,
key: "pushSettings"
});
var $pushEnabled = $pushSettings.map(
(settings) => Boolean(settings.isNotificationEnabled && settings.fcmToken)
);
var $fcmToken = $pushSettings.map((settings) => settings.fcmToken);
var $needFirstRequestPermission = $pushSettings.map(
(settings) => settings.needFirstRequestPermission
);
var PushNotificationServiceContext = React2.createContext({
subscribe: () => null,
unsubscribe: () => null
});
// src/push-notification-service/utils.ts
var utils_exports = {};
__export(utils_exports, {
cancelAllNotifications: () => cancelAllNotifications,
cancelNotification: () => cancelNotification,
checkPermission: () => checkPermission,
decrementApplicationIconBadgeNumber: () => decrementApplicationIconBadgeNumber,
getAndroidAttachment: () => getAndroidAttachment,
getInitialLocalPush: () => getInitialLocalPush,
getInitialPush: () => getInitialPush,
getIosAttachment: () => getIosAttachment,
getScheduledLocalNotifications: () => getScheduledLocalNotifications,
getToken: () => getToken,
hasPermission: () => hasPermission,
incrementApplicationIconBadgeNumber: () => incrementApplicationIconBadgeNumber,
mapRemotePushToLocalPush: () => mapRemotePushToLocalPush,
openSettings: () => openSettings,
removeToken: () => removeToken,
requestPermission: () => requestPermission,
setApplicationIconBadgeNumber: () => setApplicationIconBadgeNumber
});
var initialRemotePushWasGetting = false;
var initialLocalPushWasGetting = false;
var isPermitted = (authorizationStatus) => {
return authorizationStatus === AuthorizationStatus.AUTHORIZED || authorizationStatus === AuthorizationStatus.PROVISIONAL;
};
function hasPermission() {
return __async(this, null, function* () {
const status = yield notifee.getNotificationSettings();
return isPermitted(status.authorizationStatus);
});
}
function requestPermission() {
return __async(this, null, function* () {
const status = yield notifee.requestPermission();
return isPermitted(status.authorizationStatus);
});
}
function checkPermission() {
return __async(this, null, function* () {
const enabledNotifications = yield hasPermission();
if (!enabledNotifications && !isPermisionsGrantedByDefault) {
return yield requestPermission();
}
return enabledNotifications;
});
}
var createUUIDService = () => {
let id = 0;
return {
getUuid: () => {
return String(++id);
}
};
};
var UUIDService = createUUIDService();
function getScheduledLocalNotifications() {
return new Promise((resolve, reject) => {
try {
const notifications = notifee.getTriggerNotifications();
resolve(notifications);
} catch (e) {
reject(e);
}
});
}
function cancelAllNotifications() {
return new Promise((resolve, reject) => __async(this, null, function* () {
try {
const notifications = yield getScheduledLocalNotifications();
const notificationIds = notifications.map(
(item) => {
var _a;
return (_a = item.notification.id) != null ? _a : "";
}
);
resolve(notifee.cancelAllNotifications(notificationIds));
} catch (e) {
reject(e);
}
}));
}
function cancelNotification(notificationId) {
return new Promise((resolve, reject) => {
try {
resolve(notifee.cancelNotification(notificationId));
} catch (e) {
reject(e);
}
});
}
function getInitialPush() {
return new Promise((resolve, reject) => {
try {
if (initialRemotePushWasGetting) {
return resolve(null);
}
const initialPush = messaging().getInitialNotification();
resolve(initialPush);
initialRemotePushWasGetting = true;
} catch (e) {
reject(e);
}
});
}
function getInitialLocalPush() {
return new Promise((resolve, reject) => {
try {
if (initialLocalPushWasGetting) {
return resolve(null);
}
const initialPush = notifee.getInitialNotification();
resolve(initialPush);
initialLocalPushWasGetting = true;
} catch (e) {
reject(e);
}
});
}
var getToken = () => __async(void 0, null, function* () {
try {
const enabledNotifications = yield checkPermission();
if (!enabledNotifications) {
return null;
}
} catch (e) {
return null;
}
let fcmToken = null;
try {
fcmToken = yield messaging().getToken();
return fcmToken;
} catch (e) {
}
return fcmToken;
});
var removeToken = () => __async(void 0, null, function* () {
yield messaging().deleteToken();
});
var openSettings = Platform.select({
ios: Linking.openSettings,
android: notifee.openNotificationSettings
});
var setApplicationIconBadgeNumber = (number) => {
try {
notifee.setBadgeCount(number);
} catch (e) {
}
};
var incrementApplicationIconBadgeNumber = () => {
try {
notifee.incrementBadgeCount();
} catch (e) {
}
};
var decrementApplicationIconBadgeNumber = () => {
try {
notifee.decrementBadgeCount();
} catch (e) {
}
};
var getIosAttachment = (message) => __async(void 0, null, function* () {
var _a, _b, _c, _d;
if ((_b = (_a = message.data) == null ? void 0 : _a.fcm_options) == null ? void 0 : _b.image) {
try {
yield Image.prefetch(message.data.fcm_options.image);
} catch (e) {
delete message.data.fcm_options.image;
}
}
const attachment = ((_d = (_c = message.data) == null ? void 0 : _c.fcm_options) == null ? void 0 : _d.image) ? [
{
url: message.data.fcm_options.image
}
] : [];
return attachment;
});
var getAndroidAttachment = (message) => __async(void 0, null, function* () {
var _a, _b, _c, _d;
if ((_b = (_a = message.notification) == null ? void 0 : _a.android) == null ? void 0 : _b.imageUrl) {
try {
yield Image.prefetch(message.notification.android.imageUrl);
} catch (e) {
delete message.notification.android.imageUrl;
}
}
const attachment = ((_d = (_c = message.notification) == null ? void 0 : _c.android) == null ? void 0 : _d.imageUrl) ? {
type: AndroidStyle.BIGPICTURE,
picture: message.notification.android.imageUrl
} : void 0;
return attachment;
});
var mapRemotePushToLocalPush = (remotePush) => {
var _a, _b, _c, _d;
const pushNotification = {
id: UUIDService.getUuid(),
title: (_b = (_a = remotePush.notification) == null ? void 0 : _a.title) != null ? _b : "",
body: (_d = (_c = remotePush.notification) == null ? void 0 : _c.body) != null ? _d : "",
data: remotePush.data
};
return pushNotification;
};
// src/push-notification-service/push-notification-service.tsx
var isAndroid = Platform.OS === "android";
var PushNotificationService = ({
channelId,
channelName,
children
}) => {
const subscriptions = React2.useRef({});
const lastOpened = React2.useRef(null);
const lastReceived = React2.useRef(null);
const onOpenNotification = (pushNotification) => __async(void 0, null, function* () {
lastOpened.current = pushNotification;
const onOpenResults = Object.values(subscriptions.current).map(
(e) => e.onOpen(pushNotification)
);
if ((yield Promise.all(onOpenResults)).filter(Boolean).length) {
lastOpened.current = null;
}
});
const onReceiveNotification = (pushNotification) => __async(void 0, null, function* () {
lastReceived.current = pushNotification;
const onReceiveResults = Object.values(subscriptions.current).map(
(e) => e.onReceive(pushNotification)
);
if ((yield Promise.all(onReceiveResults)).filter(Boolean).length) {
lastReceived.current = null;
}
});
React2.useEffect(() => {
const init = () => __async(void 0, null, function* () {
const initialPush = yield getInitialPush();
if (initialPush && isAndroid) {
const pushNotification = mapRemotePushToLocalPush(initialPush);
onOpenNotification(pushNotification);
}
const initialLocalPush = yield getInitialLocalPush();
if (initialLocalPush && isAndroid) {
onOpenNotification(initialLocalPush.notification);
}
const onNotificationOpenedAppUnsubscribe = messaging().onNotificationOpenedApp((remoteMessage) => {
if (remoteMessage && isAndroid) {
const pushNotification = mapRemotePushToLocalPush(remoteMessage);
onOpenNotification(pushNotification);
}
});
const onForegroundEventUnsubscribe = notifee.onForegroundEvent(
(_0) => __async(void 0, [_0], function* ({ type, detail }) {
if (!detail.notification) {
return;
}
const pushNotification = detail.notification;
switch (type) {
case EventType.DELIVERED:
onReceiveNotification(pushNotification);
break;
case EventType.PRESS:
onOpenNotification(pushNotification);
break;
}
})
);
notifee.onBackgroundEvent((_0) => __async(void 0, [_0], function* ({ type, detail }) {
if (!detail.notification) {
return;
}
const pushNotification = detail.notification;
switch (type) {
case EventType.DELIVERED:
onReceiveNotification(pushNotification);
break;
case EventType.PRESS:
onOpenNotification(pushNotification);
break;
}
}));
yield notifee.createChannel({
id: channelId,
name: channelName,
sound: "default",
lights: true,
vibration: true,
importance: AndroidImportance.HIGH
});
const onMessageReceived = (remoteMessage) => __async(void 0, null, function* () {
var _a, _b, _c;
if (!remoteMessage.notification) {
return;
}
const pushNotification = mapRemotePushToLocalPush(remoteMessage);
const iosAttachment = yield getIosAttachment(remoteMessage);
const androidAttachment = yield getAndroidAttachment(remoteMessage);
const iosBadgeCount = Number((_b = (_a = remoteMessage.notification.ios) == null ? void 0 : _a.badge) != null ? _b : 0);
notifee.displayNotification(__spreadProps(__spreadValues({}, pushNotification), {
ios: {
attachments: iosAttachment,
badgeCount: iosBadgeCount
},
android: __spreadProps(__spreadValues({
channelId,
importance: AndroidImportance.HIGH,
smallIcon: "ic_small_icon"
}, androidAttachment ? {
largeIcon: androidAttachment.picture,
style: androidAttachment
} : {
style: {
type: AndroidStyle.BIGTEXT,
text: (_c = pushNotification.body) != null ? _c : ""
}
}), {
// pressAction is needed if you want the notification to open the app when pressed
pressAction: {
id: "default"
}
})
}));
});
const onMessageReceivedUnsubscribe = messaging().onMessage(onMessageReceived);
const unsubscribe2 = () => {
onNotificationOpenedAppUnsubscribe();
onForegroundEventUnsubscribe();
onMessageReceivedUnsubscribe();
};
return unsubscribe2;
});
const unsubscribe = init();
return () => {
unsubscribe.then((result) => result());
};
}, [channelId, channelName]);
const value = useMemo(
() => ({
subscribe: (uuid, mounted, events) => __async(void 0, null, function* () {
subscriptions.current[uuid] = events;
if (lastOpened.current && mounted && !lastReceived.current && (yield events.onOpen(lastOpened.current))) {
lastOpened.current = null;
}
if (lastReceived.current && mounted && (yield events.onReceive(lastReceived.current))) {
lastReceived.current = null;
}
}),
unsubscribe: (uuid) => {
delete subscriptions.current[uuid];
}
}),
[]
);
return /* @__PURE__ */ React2.createElement(PushNotificationServiceContext.Provider, { value }, children);
};
function usePush({ condition, onReceived, onOpened }) {
const mounted = React2.useRef(false);
const { subscribe, unsubscribe } = React2.useContext(
PushNotificationServiceContext
);
const uuid = React2.useMemo(() => v4(), []);
const onReceive = useCallback(
(notification) => __async(this, null, function* () {
if (condition(notification)) {
yield onReceived(notification);
return true;
}
return false;
}),
[condition, onReceived]
);
const onOpen = useCallback(
(notification) => __async(this, null, function* () {
if (condition(notification)) {
yield onOpened(notification);
return true;
}
return false;
}),
[condition, onOpened]
);
React2.useEffect(() => {
subscribe(uuid, !mounted.current, {
onReceive,
onOpen
});
mounted.current = true;
return () => {
unsubscribe(uuid);
};
}, [onReceive, onOpen, subscribe, unsubscribe, uuid]);
return null;
}
// src/model/hooks/use-register-device.ts
var useRegisterDevice = ({
isNotificationsAvailable,
updatePushToken
}) => {
const { fcmToken, isNotificationEnabled } = useStore($pushSettings);
const register = useCallback(
(..._0) => __async(void 0, [..._0], function* ({ force } = {}) {
if (!isNotificationsAvailable || !isNotificationEnabled) {
return;
}
try {
const token = yield utils_exports.getToken();
if (!token) {
return;
}
if (fcmToken !== token || force) {
updatePushToken(token, {
onSuccess: () => {
updateFcmToken(token);
}
});
}
} catch (e) {
}
}),
[
isNotificationsAvailable,
isNotificationEnabled,
fcmToken,
updatePushToken
]
);
useEffect(() => {
register();
const focusEvent = Platform.OS === "android" ? "focus" : "change";
const onChangeAppState = () => {
if (AppState.currentState === "active") {
register();
}
};
const subscription = AppState.addEventListener(focusEvent, onChangeAppState);
return () => {
subscription.remove();
};
}, [register]);
return {
registerDevice: register
};
};
// src/model/hooks/use-unregister-device.ts
var unregister = () => __async(void 0, null, function* () {
try {
yield utils_exports.removeToken();
updateFcmToken(null);
} catch (e) {
}
});
var useUnregisterDevice = () => {
return {
unregister
};
};
var usePushAvailable = ({ onFailEnabledPush }) => {
const [isLoading, setLoaderState] = useState(false);
const enabledPush = useStore($pushEnabled);
const disablePush2 = useEvent(disablePush);
const enablePush2 = useEvent(enablePush);
const [hasPermissions, setHasPermissions] = useState(enabledPush);
const [enabledPushNotifications, setEnabledPushNotifications] = useState(enabledPush);
const { unregister: unregister2 } = useUnregisterDevice();
useEffect(() => {
setEnabledPushNotifications(hasPermissions && enabledPush);
}, [hasPermissions, enabledPush]);
useEffect(() => {
const checkPermissions = () => __async(void 0, null, function* () {
const hasPermission2 = yield utils_exports.hasPermission();
setHasPermissions(hasPermission2);
});
checkPermissions();
const callback = (state) => {
if (state === "active") {
checkPermissions();
}
};
const subscription = AppState.addEventListener("change", callback);
return () => {
subscription.remove();
};
}, []);
const changeEnabledPushesHandler = useCallback(
(state) => __async(void 0, null, function* () {
setLoaderState(true);
try {
if (!state) {
setEnabledPushNotifications(false);
disablePush2();
unregister2();
utils_exports.cancelAllNotifications();
return;
}
const hasPermission2 = yield utils_exports.checkPermission();
if (hasPermission2) {
setEnabledPushNotifications(true);
} else {
onFailEnabledPush();
}
enablePush2();
} finally {
setLoaderState(false);
}
}),
[onFailEnabledPush, enablePush2, unregister2, disablePush2]
);
return {
enabled: enabledPushNotifications,
isLoading,
changeNotificationEnabled: changeEnabledPushesHandler
};
};
export { $fcmToken, $needFirstRequestPermission, $pushEnabled, $pushSettings, PushNotificationService, disablePush, enablePush, utils_exports as pushService, resetPushSettings, skipRequestPermissions, updateFcmToken, usePush, usePushAvailable, useRegisterDevice, useUnregisterDevice };