Skip to content

Commit

Permalink
added notification channels configurations
Browse files Browse the repository at this point in the history
  • Loading branch information
Gonzalo Lopez committed Jun 13, 2024
1 parent 9a70086 commit 3118a96
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 7 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ For more information about this, read https://rnfirebase.io/reference/messaging/
## Functions

<dl>
<dt><a href="#NotificationProvider">NotificationProvider(children, appName, events, environment)</a> ⇒ <code>null</code> | <code>React.element</code></dt>
<dt><a href="#NotificationProvider">NotificationProvider(children, appName, events, environment, channelConfigs)</a> ⇒ <code>null</code> | <code>React.element</code></dt>
<dd><p>It is the main component of the package, it is a HOC that is responsible for handling the logic of subscribing to notifications and receiving messages from the Firebase console. The HOC contains listeners to listen to notifications in the foreground and background, so (unless we cancel the subscription), we will receive notifications from the app even when it is closed.</p>
</dd>
<dt><a href="#setupBackgroundMessageHandler">setupBackgroundMessageHandler(callback)</a></dt>
Expand Down Expand Up @@ -87,7 +87,7 @@ For more information about this, read https://rnfirebase.io/reference/messaging/

<a name="NotificationProvider"></a>

## NotificationProvider(children, appName, events, environment) ⇒ <code>null</code> \| <code>React.element</code>
## NotificationProvider(children, appName, events, environment, channelConfigs) ⇒ <code>null</code> \| <code>React.element</code>
It is the main component of the package, it is a HOC that is responsible for handling the logic of subscribing to notifications and receiving messages from the Firebase console. The HOC contains listeners to listen to notifications in the foreground and background, so (unless we cancel the subscription), we will receive notifications from the app even when it is closed.

**Kind**: global function
Expand All @@ -102,6 +102,7 @@ It is the main component of the package, it is a HOC that is responsible for han
| appName | <code>string</code> | name of the aplication |
| events | <code>Array.&lt;string&gt;</code> | is an array that will contain the events to which the user wants to subscribe |
| environment | <code>string</code> | The environment is necessary for the API that we are going to use to subscribe the device to notifications. |
| channelConfigs | <code>Array.&lt;(string\|object)&gt;</code> | is the configuration that will be used to create new notification channels |

**Example**
```js
Expand Down
32 changes: 31 additions & 1 deletion lib/NotificationProvider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import {
isObject,
} from '../utils';
import usePushNotification from '../usePushNotification';
import {
makeDefaultChannel,
makeNotificationChannels,
parseNotificationChannel,
} from '../utils/channel';

/**
* @function NotificationProvider
Expand All @@ -17,6 +22,7 @@ import usePushNotification from '../usePushNotification';
* @param {string} appName name of the aplication
* @param {Array<string>} events is an array that will contain the events to which the user wants to subscribe
* @param {string} environment The environment is necessary for the API that we are going to use to subscribe the device to notifications.
* @param {Array<string | object>} channelConfigs is the configuration that will be used to create new notification channels
* @throws null when not receive a children argument
* @returns {null | React.element}
* @example
Expand All @@ -34,13 +40,21 @@ import usePushNotification from '../usePushNotification';
* )
*/

const NotificationProvider = ({children, appName, events, environment}) => {
const NotificationProvider = ({
children,
appName,
events,
environment,
channelConfigs = [],
}) => {
if (!children) return null;

const validAppName = !!appName && isString(appName) ? appName : '';
const validEnvironment =
!!environment && isString(environment) ? environment : '';
const validEvents = !!events && isArray(events) ? events : [];
const validChannelConfigs =
!!channelConfigs && isArray(channelConfigs) ? channelConfigs : [];

const isRegistered = useRef(false);
const {
Expand Down Expand Up @@ -87,13 +101,29 @@ const NotificationProvider = ({children, appName, events, environment}) => {
return updateNotificationState({backgroundNotification: data});
};

const createNotificationChannels = async () => {
/* istanbul ignore else */
if (validChannelConfigs) {
const parsedChannelConfigs = validChannelConfigs
?.map((config) => parseNotificationChannel(config))
.filter(Boolean);

await makeNotificationChannels(parsedChannelConfigs);
}
await makeDefaultChannel();
};

useEffect(() => {
const alreadySuscribed = isRegistered.current;
if (environment && appName && !!pushEvents.length && !alreadySuscribed) {
registerDeviceToNotifications();
}
}, [pushEvents]);

useEffect(() => {
createNotificationChannels();
}, []);

/* istanbul ignore next */
useEffect(() => {
const foregroundMessageHandler = setupForegroundMessageHandler(
Expand Down
71 changes: 71 additions & 0 deletions lib/utils/channel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import notifee, {AndroidImportance} from '@notifee/react-native';
import {isObject, isString} from '..';

export const parseChannelConfiguration = (params = {}) => {
if (!params || !isObject(params)) return null;

const {name, id = '', description = ''} = params;

if (!name || !isString(name)) return null;

const isValidDescription = !!description && isString(description);
const hasValidId = !!id && isString(id);

return {
name,
id: hasValidId ? id : name,
importance: AndroidImportance.HIGH,
...(isValidDescription && {
description,
}),
};
};

export const parseNotificationChannel = (channel) => {
const channelType = typeof channel;
const allowedConfigs = ['string', 'object'];

if (!allowedConfigs.includes(channelType)) return null;
const channelData = channelType === 'string' ? {name: channel} : channel;

const parsedChannel = parseChannelConfiguration(channelData);

return parsedChannel;
};

/* eslint-disable consistent-return */
export const makeNotificationChannel = async (channelConfig = {}) => {
const {id, name} = channelConfig;

if (!id || !name || !isString(id) || !isString(name)) return null;

try {
await notifee.createChannel(channelConfig);
} catch (error) {
return Promise.reject(error);
}
};

/* eslint-disable consistent-return */
export const makeNotificationChannels = async (channelConfigs) => {
try {
await notifee.createChannels(channelConfigs);
} catch (error) {
return Promise.reject(error);
}
};

/* eslint-disable consistent-return */
export const makeDefaultChannel = async () => {
try {
const parsedChannel = parseChannelConfiguration({
id: 'default_channel',
name: 'Common notifications',
description: 'Default channel to receive notifications',
});

await makeNotificationChannel(parsedChannel);
} catch (error) {
return Promise.reject(error);
}
};
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"license": "ISC",
"dependencies": {
"@janiscommerce/app-request": "^2.2.0",
"@notifee/react-native": "^7.8.2",
"@react-native-async-storage/async-storage": "^1.18.1",
"@react-native-firebase/app": "^18.3.1",
"@react-native-firebase/messaging": "^18.3.1",
Expand Down
11 changes: 11 additions & 0 deletions setupTest/jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ jest.mock('@janiscommerce/app-request', () => ({
})),
}));

jest.mock('@notifee/react-native', () => ({
__esModule: true,
default: {
createChannel: jest.fn(),
createChannels: jest.fn(),
},
AndroidImportance: {
HIGH: 4,
},
}));

jest.mock('react-native-device-info', () => {
const RNDeviceInfo = jest.requireActual(
'react-native-device-info/jest/react-native-device-info-mock',
Expand Down
23 changes: 19 additions & 4 deletions test/NotificationProvider/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,24 @@ describe('NotificationWrapper', () => {
useRefSpy.mockReturnValueOnce({current: false});
spySubscribeNotification.mockResolvedValueOnce({result: {}});

jest.spyOn(React, 'useEffect').mockImplementationOnce((f) => f());
jest
.spyOn(React, 'useEffect')
.mockImplementationOnce((f) => f())
.mockImplementationOnce((f) => f());

testRenderer.create(
<NotificationProvider
appName="PickingApp"
events={['picking', 'notifications', 'janis']}
environment="beta">
environment="beta"
channelConfigs={[
'channel',
{
name: 'common channel 2',
id: 'channel_2',
description: 'second channel',
},
]}>
<View />
</NotificationProvider>,
);
Expand All @@ -99,15 +110,19 @@ describe('NotificationWrapper', () => {
useRefSpy.mockReturnValueOnce({current: false});
spySubscribeNotification.mockRejectedValueOnce({message: 'error'});

jest.spyOn(React, 'useEffect').mockImplementationOnce((f) => f());
jest
.spyOn(React, 'useEffect')
.mockImplementationOnce((f) => f())
.mockImplementationOnce((f) => f());

spyGetToken.mockReturnValueOnce('fcmToken');

testRenderer.create(
<NotificationProvider
appName="PickingApp"
events={['picking', 'notifications', 'janis']}
environment="beta">
environment="beta"
channelConfigs={{}}>
<View />
</NotificationProvider>,
);
Expand Down
Loading

0 comments on commit 3118a96

Please sign in to comment.