notifly-sdk 2.3.4 → 2.3.6

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.
Files changed (48) hide show
  1. package/.eslintrc.cjs +40 -0
  2. package/.prettierrc.yml +14 -0
  3. package/README.md +8 -2
  4. package/index.js +191 -0
  5. package/package.json +28 -158
  6. package/src/auth.js +45 -0
  7. package/src/constant.js +8 -0
  8. package/src/in_app_message.js +258 -0
  9. package/src/log_event.js +189 -0
  10. package/src/user.js +51 -0
  11. package/src/utils.js +145 -0
  12. package/android/build.gradle +0 -100
  13. package/android/gradle.properties +0 -5
  14. package/android/src/main/AndroidManifest.xml +0 -4
  15. package/android/src/main/java/com/notiflysdk/NotiflySdkModule.kt +0 -100
  16. package/android/src/main/java/com/notiflysdk/NotiflySdkPackage.kt +0 -35
  17. package/android/src/newarch/NotiflySdkSpec.kt +0 -7
  18. package/android/src/oldarch/NotiflySdkSpec.kt +0 -24
  19. package/ios/NotiflySdk-Bridging-Header.h +0 -2
  20. package/ios/NotiflySdk.m +0 -22
  21. package/ios/NotiflySdk.swift +0 -39
  22. package/ios/NotiflySdk.xcodeproj/project.pbxproj +0 -273
  23. package/ios/NotiflySdk.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  24. package/ios/NotiflySdk.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  25. package/ios/NotiflySdk.xcodeproj/project.xcworkspace/xcuserdata/eden.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  26. package/ios/NotiflySdk.xcodeproj/xcuserdata/eden.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
  27. package/lib/commonjs/NativeNotiflySdk.js +0 -10
  28. package/lib/commonjs/NativeNotiflySdk.js.map +0 -1
  29. package/lib/commonjs/index.js +0 -85
  30. package/lib/commonjs/index.js.map +0 -1
  31. package/lib/commonjs/types.js +0 -2
  32. package/lib/commonjs/types.js.map +0 -1
  33. package/lib/module/NativeNotiflySdk.js +0 -3
  34. package/lib/module/NativeNotiflySdk.js.map +0 -1
  35. package/lib/module/index.js +0 -70
  36. package/lib/module/index.js.map +0 -1
  37. package/lib/module/types.js +0 -2
  38. package/lib/module/types.js.map +0 -1
  39. package/lib/typescript/NativeNotiflySdk.d.ts +0 -15
  40. package/lib/typescript/NativeNotiflySdk.d.ts.map +0 -1
  41. package/lib/typescript/index.d.ts +0 -26
  42. package/lib/typescript/index.d.ts.map +0 -1
  43. package/lib/typescript/types.d.ts +0 -7
  44. package/lib/typescript/types.d.ts.map +0 -1
  45. package/notifly_react_native_sdk.podspec +0 -36
  46. package/src/NativeNotiflySdk.ts +0 -25
  47. package/src/index.tsx +0 -105
  48. package/src/types.ts +0 -7
@@ -0,0 +1,258 @@
1
+ import React from 'react';
2
+ import { View, Linking, Dimensions } from 'react-native';
3
+ import RootSiblings from 'react-native-root-siblings';
4
+ import Modal from 'react-native-modal';
5
+ import WebView from 'react-native-webview';
6
+ import logEvent from './log_event';
7
+ import { setUserProperties } from './user';
8
+
9
+ /**
10
+ * Displays an in-app message in a modal WebView.
11
+ *
12
+ * @async
13
+ * @param {Object} data - An object containing information about the in-app message to display.
14
+ * @param {Object} openedInAppWebViewCount - An object containing the count of opened in-app web views.
15
+ * @returns {Promise<void>} A promise that resolves when the in-app message has been displayed, or rejects with an error.
16
+ *
17
+ * @example
18
+ * const data = { campaign_id: 'myCampaign', url: 'https://example.com' };
19
+ * const openedInAppWebViewCount = { count: 0 };
20
+ * await showInAppMessage(data, openedInAppWebViewCount);
21
+ */
22
+ export async function showInAppMessage(data, openedInAppWebViewCount) {
23
+ if (openedInAppWebViewCount.count > 0) {
24
+ return;
25
+ }
26
+ if (!(data?.url && data?.modal_properties)) {
27
+ return;
28
+ }
29
+ const modalProperties = data.modal_properties || {};
30
+ const screenWidth = Dimensions.get('window').width;
31
+ const screenHeight = Dimensions.get('window').height;
32
+ const webViewProps = _translateWebviewProps(modalProperties, screenWidth, screenHeight);
33
+ const link = data.url; // default html link for only testing.
34
+ const injectedJavaScript = `
35
+ const button_trigger = document.getElementById('notifly-button-trigger');
36
+ button_trigger.addEventListener('click', function(event){
37
+ if (!event.notifly_button_click_type) return;
38
+ window.ReactNativeWebView.postMessage(JSON.stringify({
39
+ type: event.notifly_button_click_type,
40
+ button_name: event.notifly_button_name,
41
+ link: event.notifly_button_click_link,
42
+ extra_data: event.notifly_extra_data,
43
+ }));
44
+ });
45
+ `;
46
+ const backdropOpacity = _getBackgroundOpacity(modalProperties?.backgroundOpacity);
47
+
48
+ const webviewMessageHandler = (e) => {
49
+ const message = JSON.parse(e.nativeEvent.data);
50
+ if (message.type === 'close') {
51
+ modal.destroy();
52
+ openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
53
+ logEvent(
54
+ 'close_button_click',
55
+ {
56
+ type: 'message_event',
57
+ channel: 'in-app-message',
58
+ button_name: message.button_name,
59
+ campaign_id: data.campaign_id,
60
+ notifly_message_id: data.notifly_message_id,
61
+ },
62
+ null,
63
+ true
64
+ );
65
+ } else if (message.type === 'main_button') {
66
+ logEvent(
67
+ 'main_button_click',
68
+ {
69
+ type: 'message_event',
70
+ channel: 'in-app-message',
71
+ button_name: message.button_name,
72
+ campaign_id: data.campaign_id,
73
+ notifly_message_id: data.notifly_message_id,
74
+ },
75
+ null,
76
+ true
77
+ );
78
+ if (message.link) {
79
+ modal.destroy();
80
+ openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
81
+ Linking.openURL(message.link);
82
+ }
83
+ } else if (message.type === 'hide_in_app_message') {
84
+ modal.destroy();
85
+ openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
86
+ logEvent(
87
+ 'hide_in_app_message_button_click',
88
+ {
89
+ type: 'message_event',
90
+ channel: 'in-app-message',
91
+ button_name: message.button_name,
92
+ campaign_id: data.campaign_id,
93
+ notifly_message_id: data.notifly_message_id,
94
+ },
95
+ null,
96
+ true
97
+ );
98
+ if (modalProperties.template_name) {
99
+ const key = `hide_in_app_message_${modalProperties.template_name}`;
100
+ setUserProperties({
101
+ [key]: true,
102
+ });
103
+ }
104
+ } else if (message.type === 'survey_submit_button') {
105
+ modal.destroy();
106
+ openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
107
+ logEvent(
108
+ 'survey_submit_button_click', {
109
+ type: 'message_event',
110
+ channel: 'in-app-message',
111
+ button_name: message.button_name,
112
+ campaign_id: data.campaign_id,
113
+ notifly_message_id: data.notifly_message_id,
114
+ notifly_extra_data: message.extra_data,
115
+ },
116
+ null,
117
+ true
118
+ );
119
+ }
120
+ };
121
+
122
+ openedInAppWebViewCount.count = openedInAppWebViewCount.count + 1;
123
+ logEvent(
124
+ 'in_app_message_show',
125
+ { type: 'message_event', channel: 'in-app-message', campaign_id: data.campaign_id, notifly_message_id: data.notifly_message_id },
126
+ null,
127
+ true
128
+ ); // logging in app messaging delivered
129
+ const modal = new RootSiblings(
130
+ (
131
+ <Modal
132
+ isVisible={true}
133
+ transparent={true}
134
+ onBackdropPress={() => {
135
+ openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
136
+ modal.destroy();
137
+ }}
138
+ onBackButtonPress={() => {
139
+ openedInAppWebViewCount.count = openedInAppWebViewCount.count - 1;
140
+ modal.destroy();
141
+ }}
142
+ backdropOpacity={backdropOpacity}
143
+ style={webViewProps.modalStyle}
144
+ >
145
+ <View>
146
+ <View style={webViewProps.viewStyle}>
147
+ <WebView
148
+ originWhitelist={['*']}
149
+ source={{
150
+ uri: link,
151
+ }}
152
+ style={{
153
+ width: screenWidth,
154
+ height: screenHeight,
155
+ }}
156
+ onMessage={webviewMessageHandler}
157
+ javaScriptEnabled={true}
158
+ injectedJavaScript={injectedJavaScript}
159
+ />
160
+ </View>
161
+ </View>
162
+ </Modal>
163
+ )
164
+ );
165
+ }
166
+
167
+ function _translateWebviewProps(modalProps, screenWidth, screenHeight) {
168
+ const modalStyle = {
169
+ margin: 0,
170
+ justifyContent: 'center',
171
+ alignItems: 'center',
172
+ ...(modalProps?.position === 'bottom' && {
173
+ position: 'absolute',
174
+ bottom: 0,
175
+ }),
176
+ };
177
+
178
+ const viewStyle = {
179
+ borderTopLeftRadius: modalProps?.borderTopLeftRadius || 0,
180
+ borderTopRightRadius: modalProps?.borderTopRightRadius || 0,
181
+ borderBottomLeftRadius: modalProps?.borderBottomLeftRadius || 0,
182
+ borderBottomRightRadius: modalProps?.borderBottomRightRadius || 0,
183
+ overflow: 'hidden',
184
+ width: _getViewWidth(modalProps, screenWidth, screenHeight),
185
+ height: _getViewHeight(modalProps, screenWidth, screenHeight),
186
+ justifyContent: 'center',
187
+ alignItems: 'center',
188
+ };
189
+
190
+ return {
191
+ modalStyle,
192
+ viewStyle,
193
+ };
194
+ }
195
+
196
+ function _getViewWidth(modalProps, screenWidth, screenHeight) {
197
+ let viewWidth;
198
+
199
+ if (modalProps.width) {
200
+ viewWidth = modalProps.width;
201
+ } else if (!screenWidth) {
202
+ console.error('screenWidth is not defined');
203
+ viewWidth = '100%';
204
+ } else if (modalProps.width_vw) {
205
+ viewWidth = screenWidth * (modalProps.width_vw / 100);
206
+ } else if (modalProps.width_vh && screenHeight) {
207
+ viewWidth = screenHeight * (modalProps.width_vh / 100);
208
+ } else {
209
+ viewWidth = '100%';
210
+ }
211
+
212
+ if (modalProps.min_width && viewWidth < modalProps.min_width) {
213
+ viewWidth = modalProps.minWidth;
214
+ }
215
+ if (modalProps.max_width && viewWidth > modalProps.max_width) {
216
+ viewWidth = modalProps.maxWidth;
217
+ }
218
+
219
+ return viewWidth;
220
+ }
221
+
222
+ function _getViewHeight(modalProps, screenWidth, screenHeight) {
223
+ let viewHeight;
224
+
225
+ if (modalProps.height) {
226
+ viewHeight = modalProps.height;
227
+ } else if (!screenHeight) {
228
+ console.error('screenHeight is not defined');
229
+ viewHeight = '100%';
230
+ } else if (modalProps.height_vh) {
231
+ viewHeight = screenHeight * (modalProps.height_vh / 100);
232
+ } else if (modalProps.height_vw && screenWidth) {
233
+ viewHeight = screenWidth * (modalProps.height_vw / 100);
234
+ } else {
235
+ viewHeight = '100%';
236
+ }
237
+
238
+ if (modalProps.min_height && viewHeight < modalProps.min_height) {
239
+ viewHeight = modalProps.min_height;
240
+ }
241
+ if (modalProps.max_height && viewHeight > modalProps.max_height) {
242
+ viewHeight = modalProps.max_height;
243
+ }
244
+
245
+ return viewHeight;
246
+ }
247
+
248
+ function _getBackgroundOpacity(backgroundOpacity) {
249
+ // if backgroundOpacity is not defined, default value is 0.2
250
+ if (backgroundOpacity === undefined) {
251
+ return 0.2;
252
+ }
253
+ // if backgroundOpacity is not between 0 and 1, default value is 0.2
254
+ if (backgroundOpacity < 0 || backgroundOpacity > 1) {
255
+ return 0.2;
256
+ }
257
+ return backgroundOpacity;
258
+ }
@@ -0,0 +1,189 @@
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
+ * Logs an event to Notifly for the current user.
13
+ *
14
+ * @async
15
+ * @param {string} eventName - The name of the event to log.
16
+ * @param {Object} eventParams - The parameters to include in the event log.
17
+ * @param {string[]} [segmentationEventParamKeys=null] - The segmentation event parameter keys.
18
+ * @param {boolean} [isInternalEvent=false] - A flag indicating whether the event is for Notifly internal use.
19
+ * @returns {Promise<void>} A promise that resolves when the event has been logged, or rejects with an error.
20
+ *
21
+ * @example
22
+ * await logEvent('button_clicked', { 'button_name': 'myButton' });
23
+ */
24
+ export default async function logEvent(
25
+ eventName,
26
+ eventParams,
27
+ segmentationEventParamKeys = null,
28
+ isInternalEvent = false
29
+ ) {
30
+ if (!eventName) {
31
+ console.warn('[Notifly] eventName must be provided.');
32
+ return;
33
+ }
34
+
35
+ try {
36
+ const [
37
+ cognitoToken,
38
+ notiflyUserId,
39
+ externalUserId,
40
+ prjId,
41
+ externalDeviceId,
42
+ osVersion,
43
+ appVersion,
44
+ deviceToken,
45
+ ] = await Promise.all([
46
+ AsyncStorage.getItem('notiflyCognitoIdToken'),
47
+ getNotiflyUserId(),
48
+ AsyncStorage.getItem('notiflyExternalUserId'),
49
+ AsyncStorage.getItem('notiflyProjectId'),
50
+ rnDeviceInfo.getUniqueId(),
51
+ rnDeviceInfo.getSystemVersion(),
52
+ rnDeviceInfo.getVersion(),
53
+ messaging().getToken(),
54
+ ]);
55
+
56
+ const eventId = uuidv5(`${notiflyUserId}${eventName}${new Date().valueOf()}`, NAMESPACE.EVENTID).replace(
57
+ /-/g,
58
+ ''
59
+ );
60
+ const notiflyDeviceId = uuidv5(externalDeviceId, NAMESPACE.DEVICEID).replace(/-/g, '');
61
+
62
+ let token = cognitoToken;
63
+ if (!token) {
64
+ const [userName, password] = await Promise.all([
65
+ AsyncStorage.getItem('notiflyUserName'),
66
+ AsyncStorage.getItem('notiflyUserPassword'),
67
+ ]);
68
+ token = await getCognitoIdToken(userName, password);
69
+ if (!token) {
70
+ console.warn('[Notifly] Failed to get cognito token.');
71
+ return;
72
+ }
73
+ await AsyncStorage.setItem('notiflyCognitoIdToken', token);
74
+ }
75
+
76
+ if (!token || !notiflyUserId || !prjId || !eventId || !externalDeviceId || !notiflyDeviceId || !deviceToken) {
77
+ const requiredParams = [
78
+ 'token',
79
+ 'notiflyUserId',
80
+ 'prjId',
81
+ 'eventId',
82
+ 'externalDeviceId',
83
+ 'notiflyDeviceId',
84
+ 'deviceToken',
85
+ ];
86
+ const missingParam = requiredParams.find((param) => !eval(param));
87
+ console.warn(`[Notifly] Missing required parameter in logEvent: ${missingParam}`);
88
+ return;
89
+ }
90
+
91
+ const body = _getBodyForLogEvent(
92
+ notiflyUserId,
93
+ eventId,
94
+ eventName,
95
+ notiflyDeviceId,
96
+ externalDeviceId,
97
+ deviceToken,
98
+ isInternalEvent,
99
+ segmentationEventParamKeys,
100
+ prjId,
101
+ osVersion,
102
+ appVersion,
103
+ externalUserId,
104
+ eventParams
105
+ );
106
+ const requestOptions = _getRequestOptionsForLogEvent(token, body);
107
+
108
+ const response = await _apiCall(NOTIFLY_LOG_EVENT_URL, requestOptions);
109
+ const result = JSON.parse(response);
110
+
111
+ // If the token is expired, get a new token and retry the logEvent.
112
+ if (result.message == 'The incoming token has expired') {
113
+ const [userName, password] = await Promise.all([
114
+ AsyncStorage.getItem('notiflyUserName'),
115
+ AsyncStorage.getItem('notiflyUserPassword'),
116
+ ]);
117
+ const newToken = await getCognitoIdToken(userName, password);
118
+ await AsyncStorage.setItem('notiflyCognitoIdToken', newToken);
119
+ await logEvent(eventName, eventParams, segmentationEventParamKeys, isInternalEvent);
120
+ }
121
+ } catch (err) {
122
+ console.warn('[Notifly] Failed logging the event. Please retry the initialization. ', err);
123
+ }
124
+ }
125
+
126
+ async function _apiCall(apiUrl, requestOptions) {
127
+ const result = fetch(apiUrl, requestOptions).then((response) => response.text());
128
+ return result;
129
+ }
130
+
131
+ function _getRequestOptionsForLogEvent(token, body) {
132
+ const myHeaders = new Headers();
133
+ myHeaders.append('Authorization', token);
134
+ myHeaders.append('Content-Type', 'application/json');
135
+
136
+ const requestOptions = {
137
+ method: 'POST',
138
+ headers: myHeaders,
139
+ body: body,
140
+ redirect: 'follow',
141
+ };
142
+ return requestOptions;
143
+ }
144
+
145
+ function _getBodyForLogEvent(
146
+ notiflyUserId,
147
+ eventId,
148
+ eventName,
149
+ notiflyDeviceId,
150
+ externalDeviceId,
151
+ deviceToken,
152
+ isInternalEvent,
153
+ segmentationEventParamKeys,
154
+ prjId,
155
+ osVersion,
156
+ appVersion,
157
+ externalUserId,
158
+ eventParams
159
+ ) {
160
+ const eventData = JSON.stringify({
161
+ event_params: eventParams,
162
+ id: eventId,
163
+ name: eventName,
164
+ notifly_user_id: notiflyUserId,
165
+ time: parseInt(new Date().valueOf() / 1000),
166
+ notifly_device_id: notiflyDeviceId,
167
+ external_device_id: externalDeviceId,
168
+ device_token: deviceToken,
169
+ is_internal_event: isInternalEvent,
170
+ segmentationEventParamKeys: segmentationEventParamKeys,
171
+ project_id: prjId,
172
+ platform: Platform.OS,
173
+ os_version: osVersion,
174
+ app_version: appVersion,
175
+ sdk_version: SDK_VERSION,
176
+ sdk_type: "react_native",
177
+ external_user_id: externalUserId || undefined,
178
+ });
179
+
180
+ const body = JSON.stringify({
181
+ 'records': [
182
+ {
183
+ 'data': eventData,
184
+ 'partitionKey': notiflyUserId,
185
+ },
186
+ ],
187
+ });
188
+ return body;
189
+ }
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,145 @@
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
+ const notifAuthStatusPromise = messaging().hasPermission();
55
+
56
+ const [deviceModel, deviceBrand, apiLevel, userAgent, notifAuthStatus] = await Promise.all([
57
+ modelPromise,
58
+ brandPromise,
59
+ apiLevelPromise,
60
+ userAgentPromise,
61
+ notifAuthStatusPromise,
62
+ ]);
63
+
64
+ const sessionStartEventParams = {
65
+ platform,
66
+ type: 'session_start_event',
67
+ device_model: deviceModel,
68
+ properties: {
69
+ device_brand: deviceBrand,
70
+ api_level: apiLevel,
71
+ user_agent: userAgent,
72
+ },
73
+ notif_auth_status: notifAuthStatus,
74
+ };
75
+ await logEvent('session_start', sessionStartEventParams, null, true);
76
+ }
77
+
78
+ /**
79
+ * Handles a click event on a push notification.
80
+ *
81
+ * @async
82
+ * @function
83
+ * @param {Object} remoteMessage - The remote message object containing the notification data.
84
+ * @returns {Promise<void>} - A promise that resolves when the click event is handled.
85
+ *
86
+ * @example
87
+ * // Usage:
88
+ * const remoteMessage = {
89
+ * data: {
90
+ * link: 'https://example.com',
91
+ * campaign_id: '1234'
92
+ * }
93
+ * };
94
+ * await clickHandler(remoteMessage);
95
+ */
96
+ export async function clickHandler(remoteMessage) {
97
+ if (!remoteMessage) {
98
+ console.warn('[Notifly] clickHandler receives a null remoteMessage.');
99
+ return;
100
+ }
101
+ if (remoteMessage.data?.notifly_message_type != 'push-notification') {
102
+ return;
103
+ }
104
+
105
+ try {
106
+ const { data: { link, campaign_id, notifly_message_id } = {} } = remoteMessage;
107
+
108
+ if (link) {
109
+ Linking.openURL(link);
110
+ }
111
+ await logEvent(
112
+ 'push_click',
113
+ { channel: 'push-notification', type: 'message_event', campaign_id, notifly_message_id, status: 'quit' },
114
+ null,
115
+ true
116
+ );
117
+ } catch (err) {
118
+ console.warn('[Notifly] custom click handler registration failed.');
119
+ }
120
+ }
121
+
122
+ // React Native does not support Buffer out of the box, so we need to implement our own base64 decoder.
123
+ export function base64Decode(input) {
124
+ const chars =
125
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
126
+ let str = '';
127
+ let output = '';
128
+
129
+ for (let i = 0; i < input.length; i += 4) {
130
+ const encoded1 = chars.indexOf(input[i]);
131
+ const encoded2 = chars.indexOf(input[i + 1]);
132
+ const encoded3 = chars.indexOf(input[i + 2]);
133
+ const encoded4 = chars.indexOf(input[i + 3]);
134
+
135
+ const decoded1 = (encoded1 << 2) | (encoded2 >> 4);
136
+ const decoded2 = ((encoded2 & 15) << 4) | (encoded3 >> 2);
137
+ const decoded3 = ((encoded3 & 3) << 6) | encoded4;
138
+
139
+ output += String.fromCharCode(decoded1);
140
+ if (encoded3 !== 64) output += String.fromCharCode(decoded2);
141
+ if (encoded4 !== 64) output += String.fromCharCode(decoded3);
142
+ }
143
+
144
+ return output;
145
+ }