notifly-sdk 2.0.0 → 2.1.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/.eslintrc.cjs +40 -0
- package/README.md +33 -1
- package/index.js +76 -228
- package/package.json +19 -3
- package/src/auth.js +42 -0
- package/src/constant.js +8 -0
- package/src/in_app_message.js +225 -0
- package/src/log_event.js +184 -0
- package/src/user.js +51 -0
- package/src/utils.js +114 -0
- package/namespace.js +0 -6
package/.eslintrc.cjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
env: {
|
|
4
|
+
es6: true,
|
|
5
|
+
jest: true,
|
|
6
|
+
node: true,
|
|
7
|
+
'react-native/react-native': true,
|
|
8
|
+
},
|
|
9
|
+
parser: '@babel/eslint-parser',
|
|
10
|
+
plugins: ['react', 'react-native'],
|
|
11
|
+
extends: ['eslint:recommended', 'plugin:react/recommended'],
|
|
12
|
+
rules: {
|
|
13
|
+
'react/prop-types': 'off',
|
|
14
|
+
'react/react-in-jsx-scope': 'off',
|
|
15
|
+
'no-console': 'off',
|
|
16
|
+
'no-unused-vars': 'warn',
|
|
17
|
+
'react-native/no-unused-styles': 'warn',
|
|
18
|
+
'react-native/split-platform-components': 'warn',
|
|
19
|
+
'react-native/no-inline-styles': 'warn',
|
|
20
|
+
'react-native/no-color-literals': 'warn',
|
|
21
|
+
'react-native/no-raw-text': 'warn',
|
|
22
|
+
},
|
|
23
|
+
settings: {
|
|
24
|
+
react: {
|
|
25
|
+
version: 'detect',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
parserOptions: {
|
|
29
|
+
requireConfigFile: false,
|
|
30
|
+
ecmaVersion: 2021,
|
|
31
|
+
sourceType: 'module',
|
|
32
|
+
ecmaFeatures: {
|
|
33
|
+
jsx: true,
|
|
34
|
+
},
|
|
35
|
+
babelOptions: {
|
|
36
|
+
presets: ['@babel/preset-env', '@babel/preset-react'],
|
|
37
|
+
plugins: ['@babel/plugin-syntax-import-assertions'],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
package/README.md
CHANGED
|
@@ -1 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Notifly React Native SDK
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
_Notifly SDK for React Native_ is an easy-to-use package for integrating Notifly with your React Native applications. It supports both Android and iOS platforms.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
To install the package, run the following command:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
npm install notifly-sdk@latest --save
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
or
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
yarn add notifly-sdk@latest
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Also, please install the peer dependencies manually:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
npm install @react-native-async-storage/async-storage@^1.17.11 @react-native-firebase/app@^16.4.3 @react-native-firebase/messaging@^16.4.3 react-native-device-info@^8.1.4 react-native-modal@^13.0.1 react-native-root-siblings@^4.1.1 react-native-webview@^11.26.1 uuid@^8.3.0
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
Please refer to our official documentation:
|
|
31
|
+
|
|
32
|
+
* [English documentation](https://docs.notifly.tech/client-sdk/react-native)
|
|
33
|
+
* [Korean documentation](https://docs.notifly.tech/ko/client-sdk/react-native)
|
package/index.js
CHANGED
|
@@ -1,269 +1,117 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Linking } from 'react-native';
|
|
2
|
+
import RootSiblings from 'react-native-root-siblings';
|
|
3
3
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
4
|
+
import messaging from '@react-native-firebase/messaging';
|
|
5
|
+
import logEvent from './src/log_event';
|
|
6
|
+
import { setUserProperties, removeUserId } from './src/user';
|
|
7
|
+
import { clickHandler, sessionStart } from './src/utils';
|
|
8
|
+
import { showInAppMessage } from './src/in_app_message';
|
|
7
9
|
|
|
8
10
|
exports.trackEvent = logEvent;
|
|
9
|
-
|
|
10
|
-
exports.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
exports.setUserProperties = setUserProperties;
|
|
12
|
+
exports.clickHandler = clickHandler;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sets the external user ID for the user.
|
|
16
|
+
*
|
|
17
|
+
* @async
|
|
18
|
+
* @param {string} userID - The external user ID for the user.
|
|
19
|
+
* @returns {Promise<void>} - A promise that resolves when the user ID is set.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* await setUserId('123456789');
|
|
23
|
+
*/
|
|
14
24
|
exports.setUserId = async function (userID) {
|
|
15
25
|
try {
|
|
16
26
|
if (userID) {
|
|
17
|
-
await
|
|
27
|
+
await setUserProperties({
|
|
18
28
|
external_user_id: userID,
|
|
19
|
-
})
|
|
29
|
+
});
|
|
20
30
|
return;
|
|
21
31
|
}
|
|
22
|
-
await
|
|
32
|
+
await removeUserId();
|
|
23
33
|
return;
|
|
24
34
|
} catch (err) {
|
|
25
|
-
console.warn('[Notifly] setUserId
|
|
35
|
+
console.warn('[Notifly] setUserId failed.');
|
|
26
36
|
}
|
|
27
|
-
}
|
|
28
|
-
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Initializes the Notifly SDK with the given project ID, user name, and password.
|
|
41
|
+
*
|
|
42
|
+
* @async
|
|
43
|
+
* @param {string} prjId - The project ID to use for initialization.
|
|
44
|
+
* @param {string} userName - The user name to use for authentication.
|
|
45
|
+
* @param {string} password - The password to use for authentication.
|
|
46
|
+
* @param {boolean} [useCustomClickHandler=false] - A flag indicating whether to use a custom click handler for push notifications.
|
|
47
|
+
* @returns {Promise<void>} A promise that resolves when initialization is complete, or rejects with an error.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* await initialize('myProjectId', 'myUserName', 'myPassword');
|
|
51
|
+
*/
|
|
29
52
|
exports.initialize = async function (prjId, userName, password, useCustomClickHandler = false) {
|
|
30
53
|
try {
|
|
31
54
|
await messaging().requestPermission();
|
|
32
55
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
await logEvent("push_click", { "campaign_id": remoteMessage.data.campaign_id, "status": "background" }, null, true);
|
|
56
|
+
// in-app-message
|
|
57
|
+
const openedInAppWebViewCount = { count: 0 };
|
|
58
|
+
messaging().onMessage((remoteMessage) => {
|
|
59
|
+
new RootSiblings(null).destroy();
|
|
60
|
+
handleInAppMessage(remoteMessage, openedInAppWebViewCount);
|
|
39
61
|
});
|
|
40
62
|
|
|
63
|
+
// push
|
|
64
|
+
messaging().onNotificationOpenedApp(handleNotificationOpened);
|
|
65
|
+
|
|
66
|
+
// custom push click handler
|
|
41
67
|
if (!useCustomClickHandler) {
|
|
42
|
-
messaging()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await _clickHandler(remoteMessage)
|
|
47
|
-
}
|
|
48
|
-
});
|
|
68
|
+
const initialNotification = await messaging().getInitialNotification();
|
|
69
|
+
if (initialNotification) {
|
|
70
|
+
await clickHandler(initialNotification);
|
|
71
|
+
}
|
|
49
72
|
}
|
|
50
73
|
|
|
51
|
-
messaging().getToken()
|
|
74
|
+
messaging().getToken();
|
|
52
75
|
|
|
53
76
|
await Promise.all([
|
|
54
77
|
AsyncStorage.setItem('notiflyProjectId', prjId),
|
|
55
78
|
AsyncStorage.setItem('notiflyUserName', userName),
|
|
56
79
|
AsyncStorage.setItem('notiflyUserPassword', password),
|
|
57
|
-
])
|
|
58
|
-
await
|
|
80
|
+
]);
|
|
81
|
+
await sessionStart();
|
|
59
82
|
} catch (err) {
|
|
60
|
-
console.warn(
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function _sessionStart() {
|
|
65
|
-
const apiLevel = rnDeviceInfo.getApiLevel(); // only android
|
|
66
|
-
const brand = rnDeviceInfo.getBrand(); // apple samsung
|
|
67
|
-
const model = rnDeviceInfo.getModel(); // 기종 SM-G960N or iPhone 8
|
|
68
|
-
const userAgent = rnDeviceInfo.getUserAgent() // User Agent String
|
|
69
|
-
const platform = Platform.OS;
|
|
70
|
-
const deviceInfo = await Promise.all([model, brand, apiLevel, userAgent]);
|
|
71
|
-
const openAppEventParams = {
|
|
72
|
-
"platform": platform,
|
|
73
|
-
"device_model": deviceInfo[0],
|
|
74
|
-
"properties": {
|
|
75
|
-
"device_brand": deviceInfo[1],
|
|
76
|
-
"api_level": deviceInfo[2],
|
|
77
|
-
"user_agent": deviceInfo[3],
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
await logEvent("session_start", openAppEventParams, null, true)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async function getCognitoIdToken(userName, password) {
|
|
84
|
-
const myHeaders = new Headers();
|
|
85
|
-
myHeaders.append("X-Amz-Target", "AWSCognitoIdentityProviderService.InitiateAuth");
|
|
86
|
-
myHeaders.append("Content-Type", "application/x-amz-json-1.1");
|
|
87
|
-
|
|
88
|
-
const raw = `{\n \"AuthFlow\":\"USER_PASSWORD_AUTH\",\n \"AuthParameters\": {\n \"PASSWORD\": \"${password}\",\n \"USERNAME\":\"${userName}\"\n },\n \"ClientId\":\"2pc5pce21ec53csf8chafknqve\"\n}`;
|
|
89
|
-
|
|
90
|
-
const requestOptions = {
|
|
91
|
-
method: 'POST',
|
|
92
|
-
headers: myHeaders,
|
|
93
|
-
body: raw,
|
|
94
|
-
redirect: 'follow'
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const token = fetch("https://cognito-idp.ap-northeast-2.amazonaws.com/", requestOptions)
|
|
98
|
-
.then(response => response.text())
|
|
99
|
-
.then(result => {
|
|
100
|
-
const tokens = JSON.parse(result).AuthenticationResult.IdToken
|
|
101
|
-
return tokens;
|
|
102
|
-
})
|
|
103
|
-
.catch(error => console.warn('[Notifly]: ', error));
|
|
104
|
-
|
|
105
|
-
return token;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function _getNotiflyUserId() {
|
|
109
|
-
const encodedUserId = await AsyncStorage.getItem('notiflyUserId');
|
|
110
|
-
if (encodedUserId) {
|
|
111
|
-
return encodedUserId;
|
|
83
|
+
console.warn('[Notifly]: ', err);
|
|
112
84
|
}
|
|
113
|
-
|
|
114
|
-
let notiflyUserId
|
|
115
|
-
if (externalUserId) {
|
|
116
|
-
notiflyUserId = v5(`${prjId}${externalUserId}`, NAMESPACE.REGISTERED_USERID).replace(/-/g, '');
|
|
117
|
-
} else {
|
|
118
|
-
notiflyUserId = v5(`${prjId}${await messaging().getToken()}`, NAMESPACE.UNREGISTERED_USERID).replace(/-/g, '');
|
|
119
|
-
}
|
|
120
|
-
return notiflyUserId;
|
|
121
|
-
}
|
|
85
|
+
};
|
|
122
86
|
|
|
123
|
-
async function
|
|
87
|
+
async function handleInAppMessage(remoteMessage, openedInAppWebViewCount) {
|
|
124
88
|
try {
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
_getNotiflyUserId(),
|
|
128
|
-
AsyncStorage.getItem('notiflyExternalUserId')
|
|
129
|
-
]);
|
|
130
|
-
params['previous_notifly_user_id'] = previousNotiflyUserID
|
|
131
|
-
params['previous_external_user_id'] = previousExternalUserID
|
|
132
|
-
await Promise.all([
|
|
133
|
-
AsyncStorage.setItem('notiflyExternalUserId', params.external_user_id),
|
|
134
|
-
AsyncStorage.removeItem('notiflyUserId'),
|
|
135
|
-
]);
|
|
89
|
+
if (remoteMessage.data?.notifly_message_type === 'in-app-message' && remoteMessage.data.url) {
|
|
90
|
+
showInAppMessage(remoteMessage.data, openedInAppWebViewCount);
|
|
136
91
|
}
|
|
137
|
-
return (await logEvent("set_user_properties", params, null, true));
|
|
138
|
-
} catch (err) {
|
|
139
|
-
console.warn('[Notifly] Failed to remove userID');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function _removeUserId() {
|
|
144
|
-
try {
|
|
145
|
-
await Promise.all([AsyncStorage.removeItem('notiflyExternalUserId'), AsyncStorage.removeItem('notiflyUserId')]);
|
|
146
|
-
return await logEvent('remove_external_user_id', {}, null, true);
|
|
147
92
|
} catch (err) {
|
|
148
|
-
console.warn('[Notifly]
|
|
93
|
+
console.warn('[Notifly] In-app message handling failed:', err);
|
|
149
94
|
}
|
|
150
95
|
}
|
|
151
96
|
|
|
152
|
-
async function
|
|
97
|
+
async function handleNotificationOpened(remoteMessage) {
|
|
153
98
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
let token;
|
|
159
|
-
const [savedCognitoToken, notiflyUserId, externalUserId, prjId, externalDeviceId, osVersion, appVersion] = await Promise.all([
|
|
160
|
-
AsyncStorage.getItem('notiflyCognitoIdToken'),
|
|
161
|
-
_getNotiflyUserId(),
|
|
162
|
-
AsyncStorage.getItem('notiflyExternalUserId'),
|
|
163
|
-
AsyncStorage.getItem('notiflyProjectId'),
|
|
164
|
-
rnDeviceInfo.getUniqueId(),
|
|
165
|
-
rnDeviceInfo.getSystemVersion(),
|
|
166
|
-
rnDeviceInfo.getVersion(),
|
|
167
|
-
]);
|
|
168
|
-
|
|
169
|
-
const [eventId, notiflyDeviceId, deviceToken] = await Promise.all([
|
|
170
|
-
v5(`${notiflyUserId}${eventName}${new Date().valueOf()}`, NAMESPACE.EVENTID).replace(/-/g, ''),
|
|
171
|
-
v5(externalDeviceId, NAMESPACE.DEVICEID).replace(/-/g, ''),
|
|
172
|
-
messaging().getToken(),
|
|
173
|
-
]);
|
|
174
|
-
|
|
175
|
-
if (!savedCognitoToken) {
|
|
176
|
-
const [userName, password] = await Promise.all([AsyncStorage.getItem('notiflyUserName'), AsyncStorage.getItem('notiflyUserPassword')])
|
|
177
|
-
newToken = await getCognitoIdToken(userName, password);
|
|
178
|
-
await AsyncStorage.setItem("notiflyCognitoIdToken", newToken);
|
|
179
|
-
token = newToken;
|
|
180
|
-
} else {
|
|
181
|
-
token = savedCognitoToken;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!token || !notiflyUserId || !prjId || !eventId || !externalDeviceId || !notiflyDeviceId || !deviceToken) {
|
|
185
|
-
throw new Error("null value");
|
|
186
|
-
}
|
|
187
|
-
const myHeaders = new Headers();
|
|
188
|
-
myHeaders.append("Authorization", token);
|
|
189
|
-
myHeaders.append("Content-Type", "application/json");
|
|
190
|
-
|
|
191
|
-
const eventDataWithoutExternalUserId = {
|
|
192
|
-
"event_params": eventParams,
|
|
193
|
-
"id": eventId,
|
|
194
|
-
"name": eventName,
|
|
195
|
-
"notifly_user_id": notiflyUserId,
|
|
196
|
-
"time": parseInt(new Date().valueOf() / 1000),
|
|
197
|
-
"notifly_device_id": notiflyDeviceId,
|
|
198
|
-
"external_device_id": externalDeviceId,
|
|
199
|
-
"device_token": deviceToken,
|
|
200
|
-
"is_internal_event": isInternalEvent,
|
|
201
|
-
"segmentation_event_param_keys": segmentation_event_param_keys,
|
|
202
|
-
"project_id": prjId,
|
|
203
|
-
"platform": Platform.OS,
|
|
204
|
-
"os_version": osVersion,
|
|
205
|
-
"app_version": appVersion,
|
|
99
|
+
const link = remoteMessage.data?.link;
|
|
100
|
+
if (link) {
|
|
101
|
+
Linking.openURL(link);
|
|
206
102
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
:
|
|
214
|
-
|
|
103
|
+
await logEvent(
|
|
104
|
+
'push_click',
|
|
105
|
+
{
|
|
106
|
+
type: 'message_event',
|
|
107
|
+
channel: 'push-notification',
|
|
108
|
+
campaign_id: remoteMessage.data.campaign_id,
|
|
109
|
+
status: 'background',
|
|
110
|
+
},
|
|
111
|
+
null,
|
|
112
|
+
true
|
|
215
113
|
);
|
|
216
|
-
|
|
217
|
-
const body = JSON.stringify({
|
|
218
|
-
"records": [{
|
|
219
|
-
"data": eventData,
|
|
220
|
-
"partitionKey": notiflyUserId,
|
|
221
|
-
}]
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
const requestOptions = {
|
|
225
|
-
method: 'POST',
|
|
226
|
-
headers: myHeaders,
|
|
227
|
-
body: body,
|
|
228
|
-
redirect: 'follow'
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const response = await _apiCall("https://12lnng07q2.execute-api.ap-northeast-2.amazonaws.com/prod/records", requestOptions);
|
|
232
|
-
const result = JSON.parse(response)
|
|
233
|
-
if (result.message == "The incoming token has expired") {
|
|
234
|
-
const [userName, password] = await Promise.all([AsyncStorage.getItem('notiflyUserName'), AsyncStorage.getItem('notiflyUserPassword')])
|
|
235
|
-
const newToken = await getCognitoIdToken(userName, password);
|
|
236
|
-
await AsyncStorage.setItem("notiflyCognitoIdToken", newToken);
|
|
237
|
-
await logEvent(eventName, eventParams, segmentation_event_param_keys, isInternalEvent)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
114
|
} catch (err) {
|
|
241
|
-
console.warn('[Notifly]
|
|
242
|
-
if (err == 'Error: null value') {
|
|
243
|
-
throw new Error("[Notifly] Fail to log the event. Please retry the initialization.");
|
|
244
|
-
}
|
|
115
|
+
console.warn('[Notifly] Notification opened handling failed:', err);
|
|
245
116
|
}
|
|
246
117
|
}
|
|
247
|
-
|
|
248
|
-
async function _clickHandler(remoteMessage) {
|
|
249
|
-
try {
|
|
250
|
-
if (remoteMessage) {
|
|
251
|
-
const link = remoteMessage.data?.link
|
|
252
|
-
if (link) {
|
|
253
|
-
Linking.openURL(link);
|
|
254
|
-
}
|
|
255
|
-
await logEvent("push_click", { "campaign_id": remoteMessage.data?.campaign_id, "status": "quit" }, null, true);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
console.warn('[Notifly] clickHandler receives a null remoteMessage.');
|
|
259
|
-
return;
|
|
260
|
-
} catch (err) {
|
|
261
|
-
console.warn('[Notifly] custom click handler registration failed.');
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async function _apiCall(apiUrl, requestOptions) {
|
|
266
|
-
const result = fetch(apiUrl, requestOptions)
|
|
267
|
-
.then(response => response.text());
|
|
268
|
-
return result;
|
|
269
|
-
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "notifly-sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"pretty": "prettier --write \"**/*.{js,jsx,json}\"",
|
|
9
|
+
"lint": "eslint ."
|
|
8
10
|
},
|
|
9
11
|
"peerDependencies": {
|
|
10
12
|
"@react-native-async-storage/async-storage": "^1.17.11",
|
|
11
13
|
"@react-native-firebase/app": "^16.4.3",
|
|
12
14
|
"@react-native-firebase/messaging": "^16.4.3",
|
|
13
15
|
"react-native-device-info": "^8.1.4",
|
|
16
|
+
"react-native-modal": "^13.0.1",
|
|
17
|
+
"react-native-root-siblings": "^4.1.1",
|
|
18
|
+
"react-native-webview": "^11.26.1",
|
|
14
19
|
"uuid": "^8.3.0"
|
|
15
20
|
},
|
|
16
21
|
"author": "daeseongKim",
|
|
17
22
|
"license": "ISC",
|
|
18
|
-
"type": "module"
|
|
23
|
+
"type": "module",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@babel/core": "^7.21.4",
|
|
26
|
+
"@babel/eslint-parser": "^7.21.3",
|
|
27
|
+
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
|
28
|
+
"@babel/preset-env": "^7.21.4",
|
|
29
|
+
"@babel/preset-react": "^7.18.6",
|
|
30
|
+
"eslint": "^8.38.0",
|
|
31
|
+
"eslint-plugin-react": "^7.32.2",
|
|
32
|
+
"eslint-plugin-react-native": "^4.0.0",
|
|
33
|
+
"prettier": "^2.8.7"
|
|
34
|
+
}
|
|
19
35
|
}
|
package/src/auth.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches a Cognito ID token for the given user name and password.
|
|
3
|
+
*
|
|
4
|
+
* @async
|
|
5
|
+
* @param {string} userName - The user name to use for authentication.
|
|
6
|
+
* @param {string} password - The password to use for authentication.
|
|
7
|
+
* @returns {Promise<string>} A promise that resolves with the Cognito ID token, or rejects with an error.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const token = await getCognitoIdToken('myUserName', 'myPassword');
|
|
11
|
+
*/
|
|
12
|
+
export async function getCognitoIdToken(userName, password) {
|
|
13
|
+
const headers = new Headers({
|
|
14
|
+
'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth',
|
|
15
|
+
'Content-Type': 'application/x-amz-json-1.1',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const body = JSON.stringify({
|
|
19
|
+
AuthFlow: 'USER_PASSWORD_AUTH',
|
|
20
|
+
AuthParameters: {
|
|
21
|
+
PASSWORD: password,
|
|
22
|
+
USERNAME: userName,
|
|
23
|
+
},
|
|
24
|
+
ClientId: '2pc5pce21ec53csf8chafknqve',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const requestOptions = {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers,
|
|
30
|
+
body,
|
|
31
|
+
redirect: 'follow',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await fetch('https://cognito-idp.ap-northeast-2.amazonaws.com/', requestOptions);
|
|
36
|
+
const result = await response.text();
|
|
37
|
+
const token = JSON.parse(result).AuthenticationResult.IdToken;
|
|
38
|
+
return token;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.warn('[Notifly]: ', error);
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/constant.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const SDK_VERSION = '2.1.1';
|
|
2
|
+
|
|
3
|
+
export const NAMESPACE = {
|
|
4
|
+
'EVENTID': '830b5f7b-e392-43db-a17b-d835f0bcab2b',
|
|
5
|
+
'REGISTERED_USERID': 'ce7c62f9-e8ae-4009-8fd6-468e9581fa21',
|
|
6
|
+
'UNREGISTERED_USERID': 'a6446dcf-c057-4de7-a360-56af8659d52f',
|
|
7
|
+
'DEVICEID': '830848b3-2444-467d-9cd8-3430d2738c57',
|
|
8
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { View, Linking, Dimensions } from 'react-native';
|
|
2
|
+
import RootSiblings from 'react-native-root-siblings';
|
|
3
|
+
import Modal from 'react-native-modal';
|
|
4
|
+
import WebView from 'react-native-webview';
|
|
5
|
+
import logEvent from './log_event';
|
|
6
|
+
import { setUserProperties } from './user';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Displays an in-app message in a modal WebView.
|
|
10
|
+
*
|
|
11
|
+
* @async
|
|
12
|
+
* @param {Object} data - An object containing information about the in-app message to display.
|
|
13
|
+
* @param {Object} openedInAppWebViewCount - An object containing the count of opened in-app web views.
|
|
14
|
+
* @returns {Promise<void>} A promise that resolves when the in-app message has been displayed, or rejects with an error.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const data = { campaign_id: 'myCampaign', url: 'https://example.com' };
|
|
18
|
+
* const openedInAppWebViewCount = { count: 0 };
|
|
19
|
+
* await showInAppMessage(data, openedInAppWebViewCount);
|
|
20
|
+
*/
|
|
21
|
+
export async function showInAppMessage(data, openedInAppWebViewCount) {
|
|
22
|
+
if (openedInAppWebViewCount.count > 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!(data?.url && data?.modal_properties)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const modalProperties = JSON.parse(data.modal_properties || '{}');
|
|
29
|
+
const screenWidth = Dimensions.get('window').width;
|
|
30
|
+
const screenHeight = Dimensions.get('window').height;
|
|
31
|
+
const webViewProps = _translateWebviewProps(modalProperties, screenWidth, screenHeight);
|
|
32
|
+
const link = data.url; // default html link for only testing.
|
|
33
|
+
const injectedJavaScript = `
|
|
34
|
+
const button_trigger = document.getElementById('notifly-button-trigger');
|
|
35
|
+
button_trigger.addEventListener('click', function(event){
|
|
36
|
+
if (!event.notifly_button_click_type) return;
|
|
37
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
38
|
+
type: event.notifly_button_click_type,
|
|
39
|
+
button_name: event.notifly_button_name,
|
|
40
|
+
link: event.notifly_button_click_link,
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const webviewMessageHandler = (e) => {
|
|
46
|
+
const message = JSON.parse(e.nativeEvent.data);
|
|
47
|
+
if (message.type === 'close') {
|
|
48
|
+
modal.destroy();
|
|
49
|
+
openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
|
|
50
|
+
logEvent(
|
|
51
|
+
'close_button_click',
|
|
52
|
+
{
|
|
53
|
+
type: 'message_event',
|
|
54
|
+
channel: 'in-app-message',
|
|
55
|
+
button_name: message.button_name,
|
|
56
|
+
campaign_id: data.campaign_id,
|
|
57
|
+
},
|
|
58
|
+
null,
|
|
59
|
+
true
|
|
60
|
+
);
|
|
61
|
+
} else if (message.type === 'main_button') {
|
|
62
|
+
logEvent(
|
|
63
|
+
'main_button_click',
|
|
64
|
+
{
|
|
65
|
+
type: 'message_event',
|
|
66
|
+
channel: 'in-app-message',
|
|
67
|
+
button_name: message.button_name,
|
|
68
|
+
campaign_id: data.campaign_id,
|
|
69
|
+
},
|
|
70
|
+
null,
|
|
71
|
+
true
|
|
72
|
+
);
|
|
73
|
+
if (message.link) {
|
|
74
|
+
modal.destroy();
|
|
75
|
+
openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
|
|
76
|
+
Linking.openURL(message.link);
|
|
77
|
+
}
|
|
78
|
+
} else if (message.type === 'hide_in_app_message') {
|
|
79
|
+
modal.destroy();
|
|
80
|
+
openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
|
|
81
|
+
logEvent(
|
|
82
|
+
'hide_in_app_message_button_click',
|
|
83
|
+
{
|
|
84
|
+
type: 'message_event',
|
|
85
|
+
channel: 'in-app-message',
|
|
86
|
+
button_name: message.button_name,
|
|
87
|
+
campaign_id: data.campaign_id,
|
|
88
|
+
},
|
|
89
|
+
null,
|
|
90
|
+
true
|
|
91
|
+
);
|
|
92
|
+
if (modalProperties.template_name) {
|
|
93
|
+
const key = `hide_in_app_message_${modalProperties.template_name}`;
|
|
94
|
+
setUserProperties({
|
|
95
|
+
[key]: true,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
openedInAppWebViewCount.count = openedInAppWebViewCount.count + 1;
|
|
102
|
+
logEvent(
|
|
103
|
+
'in_app_message_show',
|
|
104
|
+
{ type: 'message_event', channel: 'in-app-message', campaign: data.campaign_id },
|
|
105
|
+
null,
|
|
106
|
+
true
|
|
107
|
+
); // logging in app messaging delivered
|
|
108
|
+
const modal = new RootSiblings(
|
|
109
|
+
(
|
|
110
|
+
<Modal
|
|
111
|
+
isVisible={true}
|
|
112
|
+
transparent={true}
|
|
113
|
+
onBackdropPress={() => {
|
|
114
|
+
openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
|
|
115
|
+
modal.destroy();
|
|
116
|
+
}}
|
|
117
|
+
onBackButtonPress={() => {
|
|
118
|
+
openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
|
|
119
|
+
modal.destroy();
|
|
120
|
+
}}
|
|
121
|
+
backdropOpacity={0}
|
|
122
|
+
style={webViewProps.modalStyle}
|
|
123
|
+
>
|
|
124
|
+
<View>
|
|
125
|
+
<View style={webViewProps.viewStyle}>
|
|
126
|
+
<WebView
|
|
127
|
+
originWhitelist={['*']}
|
|
128
|
+
source={{
|
|
129
|
+
uri: link,
|
|
130
|
+
}}
|
|
131
|
+
style={{
|
|
132
|
+
width: screenWidth,
|
|
133
|
+
height: screenHeight,
|
|
134
|
+
}}
|
|
135
|
+
onMessage={webviewMessageHandler}
|
|
136
|
+
javaScriptEnabled={true}
|
|
137
|
+
injectedJavaScript={injectedJavaScript}
|
|
138
|
+
/>
|
|
139
|
+
</View>
|
|
140
|
+
</View>
|
|
141
|
+
</Modal>
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function _translateWebviewProps(modalProps, screenWidth, screenHeight) {
|
|
147
|
+
const modalStyle = {
|
|
148
|
+
margin: 0,
|
|
149
|
+
justifyContent: 'center',
|
|
150
|
+
alignItems: 'center',
|
|
151
|
+
...(modalProps?.position === 'bottom' && {
|
|
152
|
+
position: 'absolute',
|
|
153
|
+
bottom: 0,
|
|
154
|
+
}),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const viewStyle = {
|
|
158
|
+
borderTopLeftRadius: modalProps?.borderTopLeftRadius || 0,
|
|
159
|
+
borderTopRightRadius: modalProps?.borderTopRightRadius || 0,
|
|
160
|
+
borderBottomLeftRadius: modalProps?.borderBottomLeftRadius || 0,
|
|
161
|
+
borderBottomRightRadius: modalProps?.borderBottomRightRadius || 0,
|
|
162
|
+
overflow: 'hidden',
|
|
163
|
+
width: _getViewWidth(modalProps, screenWidth, screenHeight),
|
|
164
|
+
height: _getViewHeight(modalProps, screenWidth, screenHeight),
|
|
165
|
+
justifyContent: 'center',
|
|
166
|
+
alignItems: 'center',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
modalStyle,
|
|
171
|
+
viewStyle,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function _getViewWidth(modalProps, screenWidth, screenHeight) {
|
|
176
|
+
let viewWidth;
|
|
177
|
+
|
|
178
|
+
if (modalProps.width) {
|
|
179
|
+
viewWidth = modalProps.width;
|
|
180
|
+
} else if (!screenWidth) {
|
|
181
|
+
console.error('screenWidth is not defined');
|
|
182
|
+
viewWidth = '100%';
|
|
183
|
+
} else if (modalProps.width_vw) {
|
|
184
|
+
viewWidth = screenWidth * (modalProps.width_vw / 100);
|
|
185
|
+
} else if (modalProps.width_vh && screenHeight) {
|
|
186
|
+
viewWidth = screenHeight * (modalProps.width_vh / 100);
|
|
187
|
+
} else {
|
|
188
|
+
viewWidth = '100%';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (modalProps.min_width && viewWidth < modalProps.min_width) {
|
|
192
|
+
viewWidth = modalProps.minWidth;
|
|
193
|
+
}
|
|
194
|
+
if (modalProps.max_width && viewWidth > modalProps.max_width) {
|
|
195
|
+
viewWidth = modalProps.maxWidth;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return viewWidth;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function _getViewHeight(modalProps, screenWidth, screenHeight) {
|
|
202
|
+
let viewHeight;
|
|
203
|
+
|
|
204
|
+
if (modalProps.height) {
|
|
205
|
+
viewHeight = modalProps.height;
|
|
206
|
+
} else if (!screenHeight) {
|
|
207
|
+
console.error('screenHeight is not defined');
|
|
208
|
+
viewHeight = '100%';
|
|
209
|
+
} else if (modalProps.height_vh) {
|
|
210
|
+
viewHeight = screenHeight * (modalProps.height_vh / 100);
|
|
211
|
+
} else if (modalProps.height_vw && screenWidth) {
|
|
212
|
+
viewHeight = screenWidth * (modalProps.height_vw / 100);
|
|
213
|
+
} else {
|
|
214
|
+
viewHeight = '100%';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (modalProps.min_height && viewHeight < modalProps.min_height) {
|
|
218
|
+
viewHeight = modalProps.min_height;
|
|
219
|
+
}
|
|
220
|
+
if (modalProps.max_height && viewHeight > modalProps.max_height) {
|
|
221
|
+
viewHeight = modalProps.max_height;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return viewHeight;
|
|
225
|
+
}
|
package/src/log_event.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import rnDeviceInfo from 'react-native-device-info';
|
|
3
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
4
|
+
import messaging from '@react-native-firebase/messaging';
|
|
5
|
+
import { v5 as uuidv5 } from 'uuid';
|
|
6
|
+
import { NAMESPACE, SDK_VERSION } from './constant';
|
|
7
|
+
import { getNotiflyUserId } from './utils';
|
|
8
|
+
import { getCognitoIdToken } from './auth';
|
|
9
|
+
|
|
10
|
+
const NOTIFLY_LOG_EVENT_URL = 'https://12lnng07q2.execute-api.ap-northeast-2.amazonaws.com/prod/records';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Logs an event to Notifly for the current user.
|
|
14
|
+
*
|
|
15
|
+
* @async
|
|
16
|
+
* @param {string} eventName - The name of the event to log.
|
|
17
|
+
* @param {Object} eventParams - The parameters to include in the event log.
|
|
18
|
+
* @param {string[]} [segmentation_event_param_keys=null] - The segmentation event parameter keys.
|
|
19
|
+
* @param {boolean} [isInternalEvent=false] - A flag indicating whether the event is for Notifly internal use.
|
|
20
|
+
* @returns {Promise<void>} A promise that resolves when the event has been logged, or rejects with an error.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* await logEvent('button_clicked', { 'button_name': 'myButton' });
|
|
24
|
+
*/
|
|
25
|
+
export default async function logEvent(
|
|
26
|
+
eventName,
|
|
27
|
+
eventParams,
|
|
28
|
+
segmentation_event_param_keys = null,
|
|
29
|
+
isInternalEvent = false
|
|
30
|
+
) {
|
|
31
|
+
if (!eventName) {
|
|
32
|
+
console.warn('[Notifly] eventName must be provided.');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const [
|
|
38
|
+
cognitoToken,
|
|
39
|
+
notiflyUserId,
|
|
40
|
+
externalUserId,
|
|
41
|
+
prjId,
|
|
42
|
+
externalDeviceId,
|
|
43
|
+
osVersion,
|
|
44
|
+
appVersion,
|
|
45
|
+
deviceToken,
|
|
46
|
+
] = await Promise.all([
|
|
47
|
+
AsyncStorage.getItem('notiflyCognitoIdToken'),
|
|
48
|
+
getNotiflyUserId(),
|
|
49
|
+
AsyncStorage.getItem('notiflyExternalUserId'),
|
|
50
|
+
AsyncStorage.getItem('notiflyProjectId'),
|
|
51
|
+
rnDeviceInfo.getUniqueId(),
|
|
52
|
+
rnDeviceInfo.getSystemVersion(),
|
|
53
|
+
rnDeviceInfo.getVersion(),
|
|
54
|
+
messaging().getToken(),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const eventId = uuidv5(`${notiflyUserId}${eventName}${new Date().valueOf()}`, NAMESPACE.EVENTID).replace(
|
|
58
|
+
/-/g,
|
|
59
|
+
''
|
|
60
|
+
);
|
|
61
|
+
const notiflyDeviceId = uuidv5(externalDeviceId, NAMESPACE.DEVICEID).replace(/-/g, '');
|
|
62
|
+
|
|
63
|
+
let token = cognitoToken;
|
|
64
|
+
if (!token) {
|
|
65
|
+
const [userName, password] = await Promise.all([
|
|
66
|
+
AsyncStorage.getItem('notiflyUserName'),
|
|
67
|
+
AsyncStorage.getItem('notiflyUserPassword'),
|
|
68
|
+
]);
|
|
69
|
+
token = await getCognitoIdToken(userName, password);
|
|
70
|
+
await AsyncStorage.setItem('notiflyCognitoIdToken', token);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!token || !notiflyUserId || !prjId || !eventId || !externalDeviceId || !notiflyDeviceId || !deviceToken) {
|
|
74
|
+
const requiredParams = [
|
|
75
|
+
'token',
|
|
76
|
+
'notiflyUserId',
|
|
77
|
+
'prjId',
|
|
78
|
+
'eventId',
|
|
79
|
+
'externalDeviceId',
|
|
80
|
+
'notiflyDeviceId',
|
|
81
|
+
'deviceToken',
|
|
82
|
+
];
|
|
83
|
+
const missingParam = requiredParams.find((param) => !eval(param));
|
|
84
|
+
throw new Error(`[Notifly] Missing required parameter in logEvent: ${missingParam}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const body = _getBodyForLogEvent(
|
|
88
|
+
notiflyUserId,
|
|
89
|
+
eventId,
|
|
90
|
+
eventName,
|
|
91
|
+
notiflyDeviceId,
|
|
92
|
+
externalDeviceId,
|
|
93
|
+
deviceToken,
|
|
94
|
+
isInternalEvent,
|
|
95
|
+
segmentation_event_param_keys,
|
|
96
|
+
prjId,
|
|
97
|
+
osVersion,
|
|
98
|
+
appVersion,
|
|
99
|
+
externalUserId,
|
|
100
|
+
eventParams
|
|
101
|
+
);
|
|
102
|
+
const requestOptions = _getRequestOptionsForLogEvent(token, body);
|
|
103
|
+
|
|
104
|
+
const response = await _apiCall(NOTIFLY_LOG_EVENT_URL, requestOptions);
|
|
105
|
+
const result = JSON.parse(response);
|
|
106
|
+
|
|
107
|
+
// If the token is expired, get a new token and retry the logEvent.
|
|
108
|
+
if (result.message == 'The incoming token has expired') {
|
|
109
|
+
const [userName, password] = await Promise.all([
|
|
110
|
+
AsyncStorage.getItem('notiflyUserName'),
|
|
111
|
+
AsyncStorage.getItem('notiflyUserPassword'),
|
|
112
|
+
]);
|
|
113
|
+
const newToken = await getCognitoIdToken(userName, password);
|
|
114
|
+
await AsyncStorage.setItem('notiflyCognitoIdToken', newToken);
|
|
115
|
+
await logEvent(eventName, eventParams, segmentation_event_param_keys, isInternalEvent);
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.warn('[Notifly] Failed logging the event. Please retry the initialization. ', err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function _apiCall(apiUrl, requestOptions) {
|
|
123
|
+
const result = fetch(apiUrl, requestOptions).then((response) => response.text());
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function _getRequestOptionsForLogEvent(token, body) {
|
|
128
|
+
const myHeaders = new Headers();
|
|
129
|
+
myHeaders.append('Authorization', token);
|
|
130
|
+
myHeaders.append('Content-Type', 'application/json');
|
|
131
|
+
|
|
132
|
+
const requestOptions = {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: myHeaders,
|
|
135
|
+
body: body,
|
|
136
|
+
redirect: 'follow',
|
|
137
|
+
};
|
|
138
|
+
return requestOptions;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function _getBodyForLogEvent(
|
|
142
|
+
notiflyUserId,
|
|
143
|
+
eventId,
|
|
144
|
+
eventName,
|
|
145
|
+
notiflyDeviceId,
|
|
146
|
+
externalDeviceId,
|
|
147
|
+
deviceToken,
|
|
148
|
+
isInternalEvent,
|
|
149
|
+
segmentation_event_param_keys,
|
|
150
|
+
prjId,
|
|
151
|
+
osVersion,
|
|
152
|
+
appVersion,
|
|
153
|
+
externalUserId,
|
|
154
|
+
eventParams
|
|
155
|
+
) {
|
|
156
|
+
const eventData = JSON.stringify({
|
|
157
|
+
event_params: eventParams,
|
|
158
|
+
id: eventId,
|
|
159
|
+
name: eventName,
|
|
160
|
+
notifly_user_id: notiflyUserId,
|
|
161
|
+
time: parseInt(new Date().valueOf() / 1000),
|
|
162
|
+
notifly_device_id: notiflyDeviceId,
|
|
163
|
+
external_device_id: externalDeviceId,
|
|
164
|
+
device_token: deviceToken,
|
|
165
|
+
is_internal_event: isInternalEvent,
|
|
166
|
+
segmentation_event_param_keys: segmentation_event_param_keys,
|
|
167
|
+
project_id: prjId,
|
|
168
|
+
platform: Platform.OS,
|
|
169
|
+
os_version: osVersion,
|
|
170
|
+
app_version: appVersion,
|
|
171
|
+
sdk_version: SDK_VERSION,
|
|
172
|
+
external_user_id: externalUserId || undefined,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const body = JSON.stringify({
|
|
176
|
+
'records': [
|
|
177
|
+
{
|
|
178
|
+
'data': eventData,
|
|
179
|
+
'partitionKey': notiflyUserId,
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
return body;
|
|
184
|
+
}
|
package/src/user.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
2
|
+
import { getNotiflyUserId } from './utils';
|
|
3
|
+
import logEvent from './log_event';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sets user properties for the current user.
|
|
7
|
+
*
|
|
8
|
+
* @async
|
|
9
|
+
* @param {Object} params - An object containing the user properties to set.
|
|
10
|
+
* @returns {Promise<void>} A promise that resolves when the user properties have been set, or rejects with an error.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* await setUserProperties({ external_user_id: 'myUserID' });
|
|
14
|
+
*/
|
|
15
|
+
export async function setUserProperties(params) {
|
|
16
|
+
try {
|
|
17
|
+
if (params.external_user_id) {
|
|
18
|
+
const [previousNotiflyUserID, previousExternalUserID] = await Promise.all([
|
|
19
|
+
getNotiflyUserId(),
|
|
20
|
+
AsyncStorage.getItem('notiflyExternalUserId'),
|
|
21
|
+
]);
|
|
22
|
+
await Promise.all([
|
|
23
|
+
AsyncStorage.setItem('notiflyExternalUserId', params.external_user_id),
|
|
24
|
+
AsyncStorage.removeItem('notiflyUserId'),
|
|
25
|
+
]);
|
|
26
|
+
params['previous_notifly_user_id'] = previousNotiflyUserID;
|
|
27
|
+
params['previous_external_user_id'] = previousExternalUserID;
|
|
28
|
+
}
|
|
29
|
+
return await logEvent('set_user_properties', params, null, true);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.warn('[Notifly] Failed to set user properties');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Removes the external user ID and Notifly user ID from storage.
|
|
37
|
+
*
|
|
38
|
+
* @async
|
|
39
|
+
* @returns {Promise<void>} A promise that resolves when the user IDs have been removed, or rejects with an error.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* await removeUserId();
|
|
43
|
+
*/
|
|
44
|
+
export async function removeUserId() {
|
|
45
|
+
try {
|
|
46
|
+
await Promise.all([AsyncStorage.removeItem('notiflyExternalUserId'), AsyncStorage.removeItem('notiflyUserId')]);
|
|
47
|
+
return await logEvent('remove_external_user_id', {}, null, true);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.warn('[Notifly] Failed to remove userID');
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Platform, Linking } from 'react-native';
|
|
2
|
+
import rnDeviceInfo from 'react-native-device-info';
|
|
3
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
4
|
+
import messaging from '@react-native-firebase/messaging';
|
|
5
|
+
import { v5 as uuidv5 } from 'uuid';
|
|
6
|
+
import { NAMESPACE } from './constant';
|
|
7
|
+
import logEvent from './log_event';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Gets the Notifly user ID for the current user.
|
|
11
|
+
*
|
|
12
|
+
* @async
|
|
13
|
+
* @returns {Promise<string>} A promise that resolves with the Notifly user ID, or rejects with an error.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const notiflyUserId = await getNotiflyUserId();
|
|
17
|
+
*/
|
|
18
|
+
export async function getNotiflyUserId() {
|
|
19
|
+
const encodedUserId = await AsyncStorage.getItem('notiflyUserId');
|
|
20
|
+
if (encodedUserId) {
|
|
21
|
+
return encodedUserId;
|
|
22
|
+
}
|
|
23
|
+
const [prjId, externalUserId] = await Promise.all([
|
|
24
|
+
AsyncStorage.getItem('notiflyProjectId'),
|
|
25
|
+
AsyncStorage.getItem('notiflyExternalUserId'),
|
|
26
|
+
]);
|
|
27
|
+
let notiflyUserId;
|
|
28
|
+
if (externalUserId) {
|
|
29
|
+
notiflyUserId = uuidv5(`${prjId}${externalUserId}`, NAMESPACE.REGISTERED_USERID).replace(/-/g, '');
|
|
30
|
+
} else {
|
|
31
|
+
notiflyUserId = uuidv5(`${prjId}${await messaging().getToken()}`, NAMESPACE.UNREGISTERED_USERID).replace(
|
|
32
|
+
/-/g,
|
|
33
|
+
''
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return notiflyUserId;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Logs a 'session_start' event with device information.
|
|
41
|
+
*
|
|
42
|
+
* @async
|
|
43
|
+
* @returns {Promise<void>} A promise that resolves when the event is logged, or rejects with an error.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* await sessionStart();
|
|
47
|
+
*/
|
|
48
|
+
export async function sessionStart() {
|
|
49
|
+
const platform = Platform.OS;
|
|
50
|
+
const apiLevelPromise = rnDeviceInfo.getApiLevel();
|
|
51
|
+
const brandPromise = rnDeviceInfo.getBrand();
|
|
52
|
+
const modelPromise = rnDeviceInfo.getModel();
|
|
53
|
+
const userAgentPromise = rnDeviceInfo.getUserAgent();
|
|
54
|
+
|
|
55
|
+
const [deviceModel, deviceBrand, apiLevel, userAgent] = await Promise.all([
|
|
56
|
+
modelPromise,
|
|
57
|
+
brandPromise,
|
|
58
|
+
apiLevelPromise,
|
|
59
|
+
userAgentPromise,
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
const openAppEventParams = {
|
|
63
|
+
platform,
|
|
64
|
+
type: 'session_start_event',
|
|
65
|
+
device_model: deviceModel,
|
|
66
|
+
properties: {
|
|
67
|
+
device_brand: deviceBrand,
|
|
68
|
+
api_level: apiLevel,
|
|
69
|
+
user_agent: userAgent,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
await logEvent('session_start', openAppEventParams, null, true);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handles a click event on a push notification.
|
|
77
|
+
*
|
|
78
|
+
* @async
|
|
79
|
+
* @function
|
|
80
|
+
* @param {Object} remoteMessage - The remote message object containing the notification data.
|
|
81
|
+
* @returns {Promise<void>} - A promise that resolves when the click event is handled.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* // Usage:
|
|
85
|
+
* const remoteMessage = {
|
|
86
|
+
* data: {
|
|
87
|
+
* link: 'https://example.com',
|
|
88
|
+
* campaign_id: '1234'
|
|
89
|
+
* }
|
|
90
|
+
* };
|
|
91
|
+
* await clickHandler(remoteMessage);
|
|
92
|
+
*/
|
|
93
|
+
export async function clickHandler(remoteMessage) {
|
|
94
|
+
if (!remoteMessage) {
|
|
95
|
+
console.warn('[Notifly] clickHandler receives a null remoteMessage.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const { data: { link, campaign_id } = {} } = remoteMessage;
|
|
101
|
+
|
|
102
|
+
if (link) {
|
|
103
|
+
Linking.openURL(link);
|
|
104
|
+
}
|
|
105
|
+
await logEvent(
|
|
106
|
+
'push_click',
|
|
107
|
+
{ channel: 'push-notification', type: 'message_event', campaign_id, status: 'quit' },
|
|
108
|
+
null,
|
|
109
|
+
true
|
|
110
|
+
);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.warn('[Notifly] custom click handler registration failed.');
|
|
113
|
+
}
|
|
114
|
+
}
|
package/namespace.js
DELETED