@janiscommerce/app-push-notification 0.0.1-beta.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.
- package/CHANGELOG.md +8 -0
- package/README.md +118 -0
- package/lib/NotificationContext.js +18 -0
- package/lib/NotificationProvider/index.js +116 -0
- package/lib/index.js +6 -0
- package/lib/utils/api/SubscribeNotifications/index.js +43 -0
- package/lib/utils/index.js +366 -0
- package/package.json +77 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @janiscommerce/app-push-notification
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Library for receiving notifications issued from firebase/janis.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## PeerDependencies installation:
|
|
9
|
+
|
|
10
|
+
In order to receive notifications it is necessary to include the dependency
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
npm install @react-native-firebase/messaging
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Installation:
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
npm install @janis-commerce/app-push-notification
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## What is received from firebase?
|
|
26
|
+
|
|
27
|
+
What is received is an object called RemoteMessage, which contains the data emitted from Firebase and is what triggers the notification listeners.
|
|
28
|
+
Inside remoteMessage you get the notifications object that contains the information that we could use to render a component with the application in the foreground
|
|
29
|
+
|
|
30
|
+
For more information about this, read https://rnfirebase.io/reference/messaging/remotemessage
|
|
31
|
+
|
|
32
|
+
This library provides the following components and methods:
|
|
33
|
+
|
|
34
|
+
## Functions
|
|
35
|
+
|
|
36
|
+
<dl>
|
|
37
|
+
<dt><a href="#useNotification">useNotification()</a> ⇒ <code>object</code></dt>
|
|
38
|
+
<dd><p>is a hook, which returns the elements contained within the notifications context. Returns an object containing:</p>
|
|
39
|
+
<table>
|
|
40
|
+
<thead>
|
|
41
|
+
<tr>
|
|
42
|
+
<th>name</th>
|
|
43
|
+
<th>description</th>
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
<tbody><tr>
|
|
47
|
+
<td>deviceToken</td>
|
|
48
|
+
<td>is the token linked to the device, which we use to subscribe it to notifications.</td>
|
|
49
|
+
</tr>
|
|
50
|
+
</tbody></table>
|
|
51
|
+
</dd>
|
|
52
|
+
<dt><a href="#NotificationProvider">NotificationProvider(children, foregroundCallback, backgroundCallback, config, events, environment)</a> ⇒ <code>null</code> | <code>React.element</code></dt>
|
|
53
|
+
<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>
|
|
54
|
+
</dd>
|
|
55
|
+
</dl>
|
|
56
|
+
|
|
57
|
+
<a name="useNotification"></a>
|
|
58
|
+
|
|
59
|
+
## useNotification() ⇒ <code>object</code>
|
|
60
|
+
is a hook, which returns the elements contained within the notifications context. Returns an object containing:
|
|
61
|
+
| name | description |
|
|
62
|
+
|----------|----------|
|
|
63
|
+
| deviceToken | is the token linked to the device, which we use to subscribe it to notifications. |
|
|
64
|
+
|
|
65
|
+
**Kind**: global function
|
|
66
|
+
**Example**
|
|
67
|
+
```js
|
|
68
|
+
import {useNotification} from '@janiscommerce/app-push-notification'
|
|
69
|
+
|
|
70
|
+
const {} = useNotification()
|
|
71
|
+
```
|
|
72
|
+
<a name="NotificationProvider"></a>
|
|
73
|
+
|
|
74
|
+
## NotificationProvider(children, foregroundCallback, backgroundCallback, config, events, environment) ⇒ <code>null</code> \| <code>React.element</code>
|
|
75
|
+
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.
|
|
76
|
+
|
|
77
|
+
**Kind**: global function
|
|
78
|
+
**Throws**:
|
|
79
|
+
|
|
80
|
+
- null when not receive a children argument
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
| Param | Type | Description |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| children | <code>React.element</code> | Component that will be rendered within the HOC, and about which the notification will be displayed |
|
|
86
|
+
| foregroundCallback | <code>function</code> | function that will be executed when a foreground notification is received. |
|
|
87
|
+
| backgroundCallback | <code>function</code> | function that will be executed when a background notification is received. |
|
|
88
|
+
| config | <code>object</code> | It is an object that contains the user's data, which will be used to subscribe the user to notifications. |
|
|
89
|
+
| config.appName | <code>string</code> | name of the aplication |
|
|
90
|
+
| config.accessToken | <code>string</code> | accessToken provided by janis |
|
|
91
|
+
| config.client | <code>string</code> | client provided by janis |
|
|
92
|
+
| events | <code>Array.<string></code> | is an array that will contain the events to which the user wants to subscribe |
|
|
93
|
+
| environment | <code>string</code> | The environment is necessary for the API that we are going to use to subscribe the device to notifications. |
|
|
94
|
+
|
|
95
|
+
**Example**
|
|
96
|
+
```js
|
|
97
|
+
import NotificationProvider from '@janiscommerce/app-push-notification'
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
//...
|
|
101
|
+
|
|
102
|
+
const foregroundCallback = (remoteMessage) => console.log('a new FCM:',remoteMessage)
|
|
103
|
+
const backgrounCallback = (remoteMessage) => {
|
|
104
|
+
console.log('a new FCM was received in background', remoteMessage)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<NotificationProvider
|
|
109
|
+
foregroundCallback={foregroundCallback}
|
|
110
|
+
backgroundCallback={backgroundCallback}
|
|
111
|
+
config={client:'fizzmod', accessToken:'access_token_push', appName:'janisAppName'}
|
|
112
|
+
events={['Notification','events','janis']}
|
|
113
|
+
environment='beta'
|
|
114
|
+
>
|
|
115
|
+
<MyComponent/>
|
|
116
|
+
</NotificationProvider>
|
|
117
|
+
)
|
|
118
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export const NotificationContext = React.createContext(null);
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @function useNotification
|
|
7
|
+
* @description is a hook, which returns the elements contained within the notifications context. Returns an object containing:
|
|
8
|
+
* | name | description |
|
|
9
|
+
* |----------|----------|
|
|
10
|
+
* | deviceToken | is the token linked to the device, which we use to subscribe it to notifications. |
|
|
11
|
+
* @returns {object}
|
|
12
|
+
* @example
|
|
13
|
+
* import {useNotification} from '@janiscommerce/app-push-notification'
|
|
14
|
+
*
|
|
15
|
+
* const {} = useNotification()
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const useNotification = () => React.useContext(NotificationContext);
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, {useEffect, useState} from 'react';
|
|
2
|
+
import {NotificationContext} from '../NotificationContext';
|
|
3
|
+
import {
|
|
4
|
+
getFCMToken,
|
|
5
|
+
setupBackgroundMessageHandler,
|
|
6
|
+
setupForegroundMessageHandler,
|
|
7
|
+
DefaultAlert,
|
|
8
|
+
isFunction,
|
|
9
|
+
isString,
|
|
10
|
+
isObject,
|
|
11
|
+
isArray,
|
|
12
|
+
topicsSubscription,
|
|
13
|
+
} from '../utils';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @function NotificationProvider
|
|
17
|
+
* @description 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.
|
|
18
|
+
* @param {React.element} children Component that will be rendered within the HOC, and about which the notification will be displayed
|
|
19
|
+
* @param {function} foregroundCallback function that will be executed when a foreground notification is received.
|
|
20
|
+
* @param {function} backgroundCallback function that will be executed when a background notification is received.
|
|
21
|
+
* @param {object} config It is an object that contains the user's data, which will be used to subscribe the user to notifications.
|
|
22
|
+
* @param {string} config.appName name of the aplication
|
|
23
|
+
* @param {string} config.accessToken accessToken provided by janis
|
|
24
|
+
* @param {string} config.client client provided by janis
|
|
25
|
+
* @param {Array<string>} events is an array that will contain the events to which the user wants to subscribe
|
|
26
|
+
* @param {string} environment The environment is necessary for the API that we are going to use to subscribe the device to notifications.
|
|
27
|
+
* @throws null when not receive a children argument
|
|
28
|
+
* @returns {null | React.element}
|
|
29
|
+
* @example
|
|
30
|
+
*
|
|
31
|
+
* import NotificationProvider from '@janiscommerce/app-push-notification'
|
|
32
|
+
*
|
|
33
|
+
*
|
|
34
|
+
* //...
|
|
35
|
+
*
|
|
36
|
+
* const foregroundCallback = (remoteMessage) => console.log('a new FCM:',remoteMessage)
|
|
37
|
+
* const backgrounCallback = (remoteMessage) => {
|
|
38
|
+
* console.log('a new FCM was received in background', remoteMessage)
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* return (
|
|
42
|
+
* <NotificationProvider
|
|
43
|
+
* foregroundCallback={foregroundCallback}
|
|
44
|
+
* backgroundCallback={backgroundCallback}
|
|
45
|
+
* config={client:'fizzmod', accessToken:'access_token_push', appName:'janisAppName'}
|
|
46
|
+
* events={['Notification','events','janis']}
|
|
47
|
+
* environment='beta'
|
|
48
|
+
* >
|
|
49
|
+
* <MyComponent/>
|
|
50
|
+
* </NotificationProvider>
|
|
51
|
+
* )
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
const NotificationProvider = ({
|
|
55
|
+
children,
|
|
56
|
+
foregroundCallback,
|
|
57
|
+
backgroundCallback,
|
|
58
|
+
config,
|
|
59
|
+
events,
|
|
60
|
+
environment,
|
|
61
|
+
}) => {
|
|
62
|
+
if (!children) return null;
|
|
63
|
+
|
|
64
|
+
const [deviceToken, setDeviceToken] = useState(null);
|
|
65
|
+
const validForegroundCallback = isFunction(foregroundCallback)
|
|
66
|
+
? foregroundCallback
|
|
67
|
+
: DefaultAlert;
|
|
68
|
+
const validBackgroundCallback = isFunction(backgroundCallback)
|
|
69
|
+
? backgroundCallback
|
|
70
|
+
: /* istanbul ignore next */ (remoteMessage) => remoteMessage;
|
|
71
|
+
const validConfig = config && isObject(config) ? config : {};
|
|
72
|
+
const validEvents = events && isArray(events) ? events : [];
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line no-unused-vars
|
|
75
|
+
const parsedConfig = {...validConfig, environment, events: validEvents};
|
|
76
|
+
|
|
77
|
+
const getDeviceToken = async () => {
|
|
78
|
+
const fcmToken = await getFCMToken();
|
|
79
|
+
if (!fcmToken || !isString(fcmToken)) return null;
|
|
80
|
+
|
|
81
|
+
setDeviceToken(fcmToken)
|
|
82
|
+
return fcmToken;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line
|
|
86
|
+
const registerDevice = async () => {
|
|
87
|
+
const newDeviceToken = await getDeviceToken();
|
|
88
|
+
|
|
89
|
+
if (!newDeviceToken) return null;
|
|
90
|
+
// eslint-disable-next-line
|
|
91
|
+
validEvents.forEach(async (event) => {
|
|
92
|
+
if (!event || !isString(event)) return null;
|
|
93
|
+
await topicsSubscription(event);
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
registerDevice();
|
|
99
|
+
const foregroundMessageHandler = setupForegroundMessageHandler(
|
|
100
|
+
validForegroundCallback,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
foregroundMessageHandler();
|
|
105
|
+
setupBackgroundMessageHandler(validBackgroundCallback);
|
|
106
|
+
};
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<NotificationContext.Provider value={{deviceToken}}>
|
|
111
|
+
{children}
|
|
112
|
+
</NotificationContext.Provider>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default NotificationProvider;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import NotificationProvider from './NotificationProvider';
|
|
2
|
+
import {useNotification} from './NotificationContext';
|
|
3
|
+
import {topicsSubscription, topicsUnsubscription} from './utils';
|
|
4
|
+
|
|
5
|
+
export {useNotification, topicsSubscription, topicsUnsubscription};
|
|
6
|
+
export default NotificationProvider;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import {isString, isArray, validateOauthData, getHeaders} from '../../index';
|
|
3
|
+
|
|
4
|
+
const SubscribeNotifications = async (params = {}) => {
|
|
5
|
+
try {
|
|
6
|
+
if (!params || !Object.keys(params).length)
|
|
7
|
+
throw new Error('params is not a valid object');
|
|
8
|
+
|
|
9
|
+
const {client, accessToken, deviceToken, events, appName, environment} =
|
|
10
|
+
params;
|
|
11
|
+
|
|
12
|
+
if (!validateOauthData(accessToken, client))
|
|
13
|
+
throw new Error('accessToken and client are required');
|
|
14
|
+
if (!deviceToken || !isString(deviceToken))
|
|
15
|
+
throw new Error('device token is invalid or null');
|
|
16
|
+
if (!events || !isArray(events))
|
|
17
|
+
throw new Error('events to be subscribed to are null');
|
|
18
|
+
if (!appName || !isString(appName))
|
|
19
|
+
throw new Error('application name are invalid or null');
|
|
20
|
+
if (!environment || !isString(environment))
|
|
21
|
+
throw new Error('environment is invalid or null');
|
|
22
|
+
|
|
23
|
+
const parsedEvents = events.filter((event) => !!event && isString(event));
|
|
24
|
+
if (!parsedEvents.length)
|
|
25
|
+
throw new Error('events to be suscribed are invalids');
|
|
26
|
+
|
|
27
|
+
const headers = getHeaders({client, accessToken});
|
|
28
|
+
const validUrl = `https://notifications.${environment}.in/api/push/register`;
|
|
29
|
+
const body = {
|
|
30
|
+
deviceToken,
|
|
31
|
+
events: parsedEvents,
|
|
32
|
+
platformApplicationName: appName,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const {data} = await axios.post(validUrl, body, {headers});
|
|
36
|
+
|
|
37
|
+
return data;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return Promise.reject(error?.response?.data?.message || error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default SubscribeNotifications;
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import {Alert} from 'react-native';
|
|
2
|
+
import messaging from '@react-native-firebase/messaging';
|
|
3
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
4
|
+
import DeviceInfo from 'react-native-device-info';
|
|
5
|
+
|
|
6
|
+
// Helpers
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @function isFunction
|
|
10
|
+
* @param {function} fn
|
|
11
|
+
* @description return true or false if arg is a valid function
|
|
12
|
+
* @returns {bool}
|
|
13
|
+
* @example
|
|
14
|
+
* import {isFunction} from '@janiscommerce/apps-helpers'
|
|
15
|
+
* isFunction(() => true) // true
|
|
16
|
+
*/
|
|
17
|
+
export const isFunction = (fn) =>
|
|
18
|
+
!!({}.toString.call(fn) === '[object Function]');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @function isString
|
|
22
|
+
* @param {string} str - String to validate.
|
|
23
|
+
* @description If the type of the argument is a string, return true, otherwise return false.
|
|
24
|
+
* @returns {bool}
|
|
25
|
+
* @example
|
|
26
|
+
* import {isString} from '@janiscommerce/apps-helpers'
|
|
27
|
+
* isString('Janis') // true
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export const isString = (str) => !!(typeof str === 'string');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @function isObject
|
|
34
|
+
* @param {object} obj
|
|
35
|
+
* @description return true or false if arg is a valid object
|
|
36
|
+
* @returns {bool}
|
|
37
|
+
* @example
|
|
38
|
+
* import {isObject} from '@janiscommerce/apps-helpers'
|
|
39
|
+
* isObject('Janis') // false
|
|
40
|
+
*/
|
|
41
|
+
export const isObject = (obj) => !!(obj && obj.constructor === Object);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @function isNumber
|
|
45
|
+
* @param {number} num
|
|
46
|
+
* @description return true or false if arg is a valid number
|
|
47
|
+
* @returns {bool}
|
|
48
|
+
* @example
|
|
49
|
+
* import {isNumber} from '@janiscommerce/apps-helpers'
|
|
50
|
+
* isNumber('Janis') // false
|
|
51
|
+
*/
|
|
52
|
+
export const isNumber = (num) =>
|
|
53
|
+
typeof num === 'number' && !Number.isNaN(Number(num));
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @function isBoolean
|
|
57
|
+
* @param {boolean} fn
|
|
58
|
+
* @description return true or false if arg is a valid boolean
|
|
59
|
+
* @returns {bool}
|
|
60
|
+
* @example
|
|
61
|
+
* import {isBoolean} from '@janiscommerce/apps-helpers'
|
|
62
|
+
* isBoolean((true) // true
|
|
63
|
+
*/
|
|
64
|
+
export const isBoolean = (bool) => typeof bool === 'boolean';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @function isArray
|
|
68
|
+
* @param {array} arr
|
|
69
|
+
* @description return true or false if arg is a valid array
|
|
70
|
+
* @returns {bool}
|
|
71
|
+
* @example
|
|
72
|
+
* import {isArray} from '@janiscommerce/apps-helpers'
|
|
73
|
+
* isArray(['Janis']) // true
|
|
74
|
+
*/
|
|
75
|
+
export const isArray = (arr) => !!(arr instanceof Array);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @function validateOauthData
|
|
79
|
+
* @param {string} accessToken
|
|
80
|
+
* @param {string} client
|
|
81
|
+
* @returns {boolean} - true or false
|
|
82
|
+
* @example validateOauthData([], 'fizzmodarg') => false
|
|
83
|
+
* @example validateOauthData('34234sdfrdf', 'fizzmodarg') => true
|
|
84
|
+
*/
|
|
85
|
+
export const validateOauthData = (accessToken, client) => {
|
|
86
|
+
if (!accessToken || !client) return false;
|
|
87
|
+
if (!isString(accessToken) || !isString(client)) return false;
|
|
88
|
+
return true;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const formatDeviceDataForUserAgent = (deviceData) => {
|
|
92
|
+
if (!isObject(deviceData) || !Object.keys(deviceData).length) return {};
|
|
93
|
+
|
|
94
|
+
const keysToCheck = [
|
|
95
|
+
'janis-app-package-name',
|
|
96
|
+
'janis-app-version',
|
|
97
|
+
'janis-app-name',
|
|
98
|
+
'janis-app-build',
|
|
99
|
+
'janis-app-device-os-name',
|
|
100
|
+
'janis-app-device-os-version',
|
|
101
|
+
'janis-app-device-id',
|
|
102
|
+
'janis-app-device-name',
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const hasSomeValidValues = keysToCheck.some(
|
|
106
|
+
(key) => isString(deviceData[key]) && !!deviceData[key],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (!hasSomeValidValues) return {};
|
|
110
|
+
|
|
111
|
+
const userAgentParts = [];
|
|
112
|
+
|
|
113
|
+
keysToCheck.forEach((key) => {
|
|
114
|
+
const value =
|
|
115
|
+
!deviceData[key] || !isString(deviceData[key])
|
|
116
|
+
? `unknown ${key}`
|
|
117
|
+
: deviceData[key];
|
|
118
|
+
userAgentParts.push(value);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
'user-agent': `${userAgentParts[0]}/${userAgentParts[1]} (${userAgentParts[2]}; ${userAgentParts[3]}) ${userAgentParts[4]}/${userAgentParts[5]} (${userAgentParts[6]}; ${userAgentParts[7]})`,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @function getDeviceData
|
|
128
|
+
* @description return data from device user
|
|
129
|
+
* @returns {{
|
|
130
|
+
* 'application-name': string,
|
|
131
|
+
* 'build-number': string,
|
|
132
|
+
* 'app-version': string,
|
|
133
|
+
* 'bundle-id': string,
|
|
134
|
+
* 'os-name': string,
|
|
135
|
+
* 'device-id': string,
|
|
136
|
+
* 'device-name': string
|
|
137
|
+
* }} - Object with device data
|
|
138
|
+
* @example getDeviceData() => {applicationName: 'AppName', buildNumber: '434', appVersion: '1.5.0', bundleId: 'com.janis.appname', osName: 'android', osVersion: '11', deviceId: '34hf83hf89ahfjo', deviceName: 'Pixel 2'}
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
export const getDeviceData = () => {
|
|
142
|
+
const applicationName = DeviceInfo.getApplicationName() || '';
|
|
143
|
+
const buildNumber = DeviceInfo.getBuildNumber() || '';
|
|
144
|
+
const appVersion = DeviceInfo.getVersion() || '';
|
|
145
|
+
const bundleId = DeviceInfo.getBundleId() || '';
|
|
146
|
+
const osName = DeviceInfo.getSystemName() || '';
|
|
147
|
+
const osVersion = DeviceInfo.getSystemVersion() || '';
|
|
148
|
+
const deviceId = DeviceInfo.getUniqueId() || '';
|
|
149
|
+
const deviceName = DeviceInfo.getModel() || '';
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
'janis-app-name': applicationName,
|
|
153
|
+
'janis-app-build': buildNumber,
|
|
154
|
+
'janis-app-version': appVersion,
|
|
155
|
+
'janis-app-package-name': bundleId,
|
|
156
|
+
'janis-app-device-os-name': osName,
|
|
157
|
+
'janis-app-device-os-version': osVersion,
|
|
158
|
+
'janis-app-device-id': deviceId,
|
|
159
|
+
'janis-app-device-name': deviceName,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const filterValidHeaders = (headers) => {
|
|
164
|
+
if (!headers || !isObject(headers) || !Object.keys(headers).length) return {};
|
|
165
|
+
|
|
166
|
+
return Object.fromEntries(
|
|
167
|
+
Object.entries(headers).filter(([, value]) => !!value && !!isString(value)),
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @function getHeaders
|
|
173
|
+
* @param {object} [params={}] - object with params
|
|
174
|
+
* @param {object} [deviceDataHeaders={}] - headers with the device info
|
|
175
|
+
* @param {object} [customHeaders={}] - extra custom headers
|
|
176
|
+
* @param {string} params.client - client name for janis api
|
|
177
|
+
* @param {string} params.accessToken - access token for janis api
|
|
178
|
+
* @param {number} params.page - number of page
|
|
179
|
+
* @param {number} params.pageSize - quantity per page
|
|
180
|
+
* @param {boolean} params.getTotals - request api totals
|
|
181
|
+
* @param {boolean} params.getOnlyTotals - request api totals without body response
|
|
182
|
+
* @description get correct headers for janis api
|
|
183
|
+
* @returns {object}
|
|
184
|
+
* @example
|
|
185
|
+
* const params = {
|
|
186
|
+
* client: 'my-client',
|
|
187
|
+
* accessToken: 'my-access-token',
|
|
188
|
+
* page: 1,
|
|
189
|
+
* pageSize: 10,
|
|
190
|
+
* getTotals: true,
|
|
191
|
+
* getOnlyTotals: false
|
|
192
|
+
* };
|
|
193
|
+
* const deviceDataHeaders = {
|
|
194
|
+
* 'janis-app-name': 'MyApp',
|
|
195
|
+
* 'janis-app-version': '1.0.0',
|
|
196
|
+
* 'janis-app-device-os-name': 'iOS',
|
|
197
|
+
* 'janis-app-device-os-version': '14.5',
|
|
198
|
+
* 'janis-app-device-name': 'iPhone 12',
|
|
199
|
+
* 'janis-app-device-id': '123456789'
|
|
200
|
+
* };
|
|
201
|
+
* const customHeaders = {
|
|
202
|
+
* 'custom-header': 'custom-value'
|
|
203
|
+
* };
|
|
204
|
+
* const headers = getHeaders(params, deviceDataHeaders, customHeaders);
|
|
205
|
+
* // {
|
|
206
|
+
* // 'content-Type': 'application/json',
|
|
207
|
+
* // 'janis-api-key': 'Bearer',
|
|
208
|
+
* // 'janis-client': 'my-client',
|
|
209
|
+
* // 'janis-api-secret': 'my-access-token',
|
|
210
|
+
* // 'x-janis-page': 1,
|
|
211
|
+
* // 'x-janis-page-size': 10,
|
|
212
|
+
* // 'x-janis-totals': true,
|
|
213
|
+
* // 'x-janis-only-totals': false,
|
|
214
|
+
* // 'user-agent': 'MyApp/1.0.0 (iOS 14.5; iPhone 12; 123456789)',
|
|
215
|
+
* // 'custom-header': 'custom-value'
|
|
216
|
+
* // }
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
export const getHeaders = (params = {}, customHeaders = {}) => {
|
|
220
|
+
const deviceDataHeaders = getDeviceData();
|
|
221
|
+
|
|
222
|
+
const validCustomHeaders = filterValidHeaders(customHeaders);
|
|
223
|
+
const validDeviceDataHeaders = filterValidHeaders(deviceDataHeaders);
|
|
224
|
+
const validUserAgentHeader = formatDeviceDataForUserAgent(
|
|
225
|
+
validDeviceDataHeaders,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const baseHeaders = {
|
|
229
|
+
'content-Type': 'application/json',
|
|
230
|
+
'janis-api-key': 'Bearer',
|
|
231
|
+
...validUserAgentHeader,
|
|
232
|
+
...validDeviceDataHeaders,
|
|
233
|
+
...validCustomHeaders,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (!isObject(params)) return baseHeaders;
|
|
237
|
+
const {client, accessToken, page, pageSize, getTotals, getOnlyTotals} =
|
|
238
|
+
params;
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
...baseHeaders,
|
|
242
|
+
...(isString(client) && client && {'janis-client': client}),
|
|
243
|
+
...(isString(accessToken) &&
|
|
244
|
+
accessToken && {'janis-api-secret': accessToken}),
|
|
245
|
+
...(isNumber(page) && page && {'x-janis-page': page}),
|
|
246
|
+
...(isNumber(pageSize) && pageSize && {'x-janis-page-size': pageSize}),
|
|
247
|
+
...(isBoolean(getTotals) && getTotals && {'x-janis-totals': getTotals}),
|
|
248
|
+
...(isBoolean(getOnlyTotals) &&
|
|
249
|
+
getOnlyTotals && {'x-janis-only-totals': getOnlyTotals}),
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// MESSAGING UTILS
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* @function getFCMToken
|
|
257
|
+
* @description This function is responsible for generating an fmc token or obtaining it from storage if one already exists. If it cannot be obtained, it will return an empty string
|
|
258
|
+
* @returns {Promise<string>}
|
|
259
|
+
* @example
|
|
260
|
+
*
|
|
261
|
+
* getFCMToken() => JDF6GJS364uhaGGe384gJHIQs23nbRNFG2859gJSD9gBivajeSJD
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
export const getFCMToken = async () => {
|
|
265
|
+
try {
|
|
266
|
+
const fcmToken = await AsyncStorage.getItem('fcmtoken');
|
|
267
|
+
|
|
268
|
+
if (!fcmToken) {
|
|
269
|
+
const newFcmToken = await messaging().getToken();
|
|
270
|
+
|
|
271
|
+
if (newFcmToken) {
|
|
272
|
+
await AsyncStorage.setItem('fcmtoken', newFcmToken);
|
|
273
|
+
return newFcmToken;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return fcmToken || '';
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('error', error.message);
|
|
280
|
+
|
|
281
|
+
return '';
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// esto actualmente no funciona con la versión RN de picking. Pero será necesario eventualemnte.
|
|
286
|
+
// A partir de la versión 13 (api >= 33 ) de android el usuario tiene que otorgar los permisos manualmente.
|
|
287
|
+
// por lo que deberemos agregarlo dentro del package futuro y sumarlo a picking una vez que esté actualizado.
|
|
288
|
+
|
|
289
|
+
// export const requestUserPermission = async () => {
|
|
290
|
+
// const permission = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
|
|
291
|
+
|
|
292
|
+
// return permission;
|
|
293
|
+
// };
|
|
294
|
+
|
|
295
|
+
/** *
|
|
296
|
+
* @function setupForegroundMessageHandler
|
|
297
|
+
* @description This function is responsible for handling any callbacks from Firebase cloud messaging in the foreground and rendering them using a callback
|
|
298
|
+
* @param {Function} callback is the function that will receive the payload and render it as appropriate
|
|
299
|
+
*/
|
|
300
|
+
|
|
301
|
+
export const setupForegroundMessageHandler = (callback) =>
|
|
302
|
+
messaging().onMessage(async (remoteMessage) => {
|
|
303
|
+
callback(remoteMessage);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
/** *
|
|
307
|
+
* @function setupBackgroundMessageHandler
|
|
308
|
+
* @description This function is responsible for handling any callbacks from Firebase cloud messaging in the background or with the application closed
|
|
309
|
+
* @param {Function} callback is the function that will receive the payload and render it as appropriate
|
|
310
|
+
*/
|
|
311
|
+
|
|
312
|
+
export const setupBackgroundMessageHandler = (callback) =>
|
|
313
|
+
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
|
|
314
|
+
callback(remoteMessage);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/** *
|
|
318
|
+
* @name DefaultAlert
|
|
319
|
+
* @description a default alert component to be used when a foregroundCallback function is not passed by parameters
|
|
320
|
+
* @param {object} remoteMessage the object that was received from fcm
|
|
321
|
+
* @throws null when not receive a valid object as remoteMessage
|
|
322
|
+
* @returns an alert with title and body received from FCM remoteMessage
|
|
323
|
+
*
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
export const DefaultAlert = (remoteMessage = {}) => {
|
|
327
|
+
if (!remoteMessage || !Object.keys(remoteMessage).length) return null;
|
|
328
|
+
|
|
329
|
+
const {notification = {}} = remoteMessage;
|
|
330
|
+
const {title, body} = notification;
|
|
331
|
+
|
|
332
|
+
const validTitle = isString(title) ? title : 'A new FCM message arrived!';
|
|
333
|
+
const validateBody = isString(body) ? body : undefined;
|
|
334
|
+
|
|
335
|
+
return Alert.alert(validTitle, validateBody);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/* istanbul ignore next */
|
|
339
|
+
export const topicsSubscription = async (topics) => {
|
|
340
|
+
try {
|
|
341
|
+
if (!topics || !isString(topics)) return null;
|
|
342
|
+
|
|
343
|
+
await messaging().subscribeToTopic(topics);
|
|
344
|
+
|
|
345
|
+
return {message: `suscribed to ${topics} topic!`};
|
|
346
|
+
} catch (reason) {
|
|
347
|
+
console.error(reason?.message);
|
|
348
|
+
|
|
349
|
+
return Promise.reject(reason?.message);
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/* istanbul ignore next */
|
|
354
|
+
export const topicsUnsubscription = async (topic) => {
|
|
355
|
+
try {
|
|
356
|
+
if (!topic || !isString(topic)) return null;
|
|
357
|
+
|
|
358
|
+
await messaging().unsubscribeFromTopic(topic);
|
|
359
|
+
|
|
360
|
+
return {message: `unsuscribed to ${topic} topic!`};
|
|
361
|
+
} catch (reason) {
|
|
362
|
+
console.error(reason?.message);
|
|
363
|
+
|
|
364
|
+
return Promise.reject(reason?.message);
|
|
365
|
+
}
|
|
366
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@janiscommerce/app-push-notification",
|
|
3
|
+
"version": "0.0.1-beta.1",
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"description": "This package will take care of performing the main actions for registration to receive notifications in the foreground and background.",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "jest",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"build-docs": "jsdoc2md --template template-readme.hbs --files lib/*.js lib/NotificationProvider/*.js > README.md",
|
|
11
|
+
"test:coverage": "jest --collectCoverage",
|
|
12
|
+
"validate:code": "npm run lint -- --fix && jest --collectCoverage"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/janis-commerce/app-push-notification.git"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"lib/"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"janis",
|
|
23
|
+
"janiscommerce",
|
|
24
|
+
"app",
|
|
25
|
+
"pushnotification"
|
|
26
|
+
],
|
|
27
|
+
"author": "Janis",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@react-native-async-storage/async-storage": "^1.18.1",
|
|
31
|
+
"@react-native-firebase/app": "^18.3.1",
|
|
32
|
+
"@react-native-firebase/messaging": "^18.3.1",
|
|
33
|
+
"axios": "^1.3.6",
|
|
34
|
+
"react-native-device-info": "^10.12.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=17.0.2 <=18.2.0",
|
|
38
|
+
"react-native": ">=0.67.5 <=0.72.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@babel/core": "^7.23.6",
|
|
42
|
+
"@babel/eslint-parser": "^7.5.4",
|
|
43
|
+
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
44
|
+
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
|
|
45
|
+
"@babel/preset-env": "^7.22.10",
|
|
46
|
+
"@babel/runtime": "^7.12.5",
|
|
47
|
+
"@react-native-community/eslint-config": "^2.0.0",
|
|
48
|
+
"@react-native-firebase/app": "^18.3.1",
|
|
49
|
+
"@react-native-firebase/messaging": "^18.3.1",
|
|
50
|
+
"@testing-library/react-native": "^12.0.1",
|
|
51
|
+
"babel-jest": "^28.0.0",
|
|
52
|
+
"babel-loader": "8.2.4",
|
|
53
|
+
"eslint": "^8.56.0",
|
|
54
|
+
"eslint-config-airbnb": "^18.2.1",
|
|
55
|
+
"eslint-config-prettier": "^8.1.0",
|
|
56
|
+
"eslint-plugin-import": "^2.22.1",
|
|
57
|
+
"eslint-plugin-jsx-a11y": "^6.4.1",
|
|
58
|
+
"eslint-plugin-prettier": "^3.3.1",
|
|
59
|
+
"eslint-plugin-react": "^7.22.0",
|
|
60
|
+
"eslint-plugin-react-hooks": "^4.2.0",
|
|
61
|
+
"husky": "^4.3.8",
|
|
62
|
+
"jest": "^28.0.0",
|
|
63
|
+
"jsdoc-babel": "^0.5.0",
|
|
64
|
+
"jsdoc-to-markdown": "^8.0.0",
|
|
65
|
+
"lint-staged": "^10.5.4",
|
|
66
|
+
"metro-react-native-babel-preset": "^0.66.2",
|
|
67
|
+
"nock": "^13.0.11",
|
|
68
|
+
"prettier": "^2.4.1",
|
|
69
|
+
"react": "^17.0.2",
|
|
70
|
+
"react-native": "^0.67.5",
|
|
71
|
+
"react-test-renderer": "^17.0.2"
|
|
72
|
+
},
|
|
73
|
+
"bugs": {
|
|
74
|
+
"url": "https://github.com/janis-commerce/app-push-notification/issues"
|
|
75
|
+
},
|
|
76
|
+
"homepage": "https://github.com/janis-commerce/app-push-notification#readme"
|
|
77
|
+
}
|