@clix-so/react-native-sdk 0.0.2-beta.5 → 1.1.0
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/lib/module/core/Clix.js +40 -95
- package/lib/module/core/Clix.js.map +1 -1
- package/lib/module/core/ClixInitCoordinator.js +3 -14
- package/lib/module/core/ClixInitCoordinator.js.map +1 -1
- package/lib/module/core/ClixNotification.js +25 -28
- package/lib/module/core/ClixNotification.js.map +1 -1
- package/lib/module/models/ClixDevice.js +0 -6
- package/lib/module/models/ClixDevice.js.map +1 -1
- package/lib/module/models/ClixPushNotificationPayload.js +0 -19
- package/lib/module/models/ClixPushNotificationPayload.js.map +1 -1
- package/lib/module/services/ClixAPIClient.js +50 -99
- package/lib/module/services/ClixAPIClient.js.map +1 -1
- package/lib/module/services/DeviceAPIService.js +37 -45
- package/lib/module/services/DeviceAPIService.js.map +1 -1
- package/lib/module/services/DeviceService.js +97 -116
- package/lib/module/services/DeviceService.js.map +1 -1
- package/lib/module/services/EventAPIService.js +3 -5
- package/lib/module/services/EventAPIService.js.map +1 -1
- package/lib/module/services/EventService.js +13 -20
- package/lib/module/services/EventService.js.map +1 -1
- package/lib/module/services/NotificationService.js +252 -402
- package/lib/module/services/NotificationService.js.map +1 -1
- package/lib/module/services/TokenService.js +3 -59
- package/lib/module/services/TokenService.js.map +1 -1
- package/lib/module/utils/http/HTTPClient.js +101 -0
- package/lib/module/utils/http/HTTPClient.js.map +1 -0
- package/lib/module/utils/http/HTTPMethod.js +10 -0
- package/lib/module/utils/http/HTTPMethod.js.map +1 -0
- package/lib/module/utils/http/HTTPRequest.js +4 -0
- package/lib/module/utils/http/HTTPRequest.js.map +1 -0
- package/lib/module/utils/http/HTTPResponse.js +2 -0
- package/lib/module/utils/http/HTTPResponse.js.map +1 -0
- package/lib/module/utils/types.js +2 -0
- package/lib/module/utils/types.js.map +1 -0
- package/lib/typescript/src/core/Clix.d.ts +13 -15
- package/lib/typescript/src/core/Clix.d.ts.map +1 -1
- package/lib/typescript/src/core/ClixConfig.d.ts +3 -3
- package/lib/typescript/src/core/ClixConfig.d.ts.map +1 -1
- package/lib/typescript/src/core/ClixInitCoordinator.d.ts +0 -3
- package/lib/typescript/src/core/ClixInitCoordinator.d.ts.map +1 -1
- package/lib/typescript/src/core/ClixNotification.d.ts +6 -5
- package/lib/typescript/src/core/ClixNotification.d.ts.map +1 -1
- package/lib/typescript/src/models/ClixDevice.d.ts +0 -2
- package/lib/typescript/src/models/ClixDevice.d.ts.map +1 -1
- package/lib/typescript/src/models/ClixPushNotificationPayload.d.ts +8 -21
- package/lib/typescript/src/models/ClixPushNotificationPayload.d.ts.map +1 -1
- package/lib/typescript/src/services/ClixAPIClient.d.ts +6 -22
- package/lib/typescript/src/services/ClixAPIClient.d.ts.map +1 -1
- package/lib/typescript/src/services/DeviceAPIService.d.ts +1 -1
- package/lib/typescript/src/services/DeviceAPIService.d.ts.map +1 -1
- package/lib/typescript/src/services/DeviceService.d.ts +10 -5
- package/lib/typescript/src/services/DeviceService.d.ts.map +1 -1
- package/lib/typescript/src/services/EventAPIService.d.ts.map +1 -1
- package/lib/typescript/src/services/EventService.d.ts +1 -0
- package/lib/typescript/src/services/EventService.d.ts.map +1 -1
- package/lib/typescript/src/services/NotificationService.d.ts +50 -57
- package/lib/typescript/src/services/NotificationService.d.ts.map +1 -1
- package/lib/typescript/src/services/TokenService.d.ts +1 -7
- package/lib/typescript/src/services/TokenService.d.ts.map +1 -1
- package/lib/typescript/src/utils/http/HTTPClient.d.ts +15 -0
- package/lib/typescript/src/utils/http/HTTPClient.d.ts.map +1 -0
- package/lib/typescript/src/utils/http/HTTPMethod.d.ts +7 -0
- package/lib/typescript/src/utils/http/HTTPMethod.d.ts.map +1 -0
- package/lib/typescript/src/utils/http/HTTPRequest.d.ts +9 -0
- package/lib/typescript/src/utils/http/HTTPRequest.d.ts.map +1 -0
- package/lib/typescript/src/utils/http/HTTPResponse.d.ts +6 -0
- package/lib/typescript/src/utils/http/HTTPResponse.d.ts.map +1 -0
- package/lib/typescript/src/utils/types.d.ts +5 -0
- package/lib/typescript/src/utils/types.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/Clix.ts +62 -115
- package/src/core/ClixConfig.ts +3 -3
- package/src/core/ClixInitCoordinator.ts +5 -17
- package/src/core/ClixNotification.ts +36 -37
- package/src/models/ClixDevice.ts +17 -25
- package/src/models/ClixPushNotificationPayload.ts +8 -37
- package/src/services/ClixAPIClient.ts +84 -144
- package/src/services/DeviceAPIService.ts +39 -47
- package/src/services/DeviceService.ts +122 -156
- package/src/services/EventAPIService.ts +3 -5
- package/src/services/EventService.ts +26 -33
- package/src/services/NotificationService.ts +318 -533
- package/src/services/TokenService.ts +4 -71
- package/src/utils/http/HTTPClient.ts +141 -0
- package/src/utils/http/HTTPMethod.ts +6 -0
- package/src/utils/http/HTTPRequest.ts +9 -0
- package/src/utils/http/HTTPResponse.ts +5 -0
- package/src/utils/types.ts +7 -0
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
import notifee, { AndroidCategory, AndroidGroupAlertBehavior, AndroidImportance, AndroidStyle, AndroidVisibility, AuthorizationStatus, EventType } from '@notifee/react-native';
|
|
4
4
|
import messaging from '@react-native-firebase/messaging';
|
|
5
5
|
import { Linking, Platform } from 'react-native';
|
|
6
|
-
import { ClixPushNotificationPayload } from "../models/ClixPushNotificationPayload.js";
|
|
7
6
|
import { ClixLogger } from "../utils/logging/ClixLogger.js";
|
|
8
|
-
import { UUID } from "../utils/UUID.js";
|
|
9
7
|
export class NotificationService {
|
|
10
|
-
|
|
8
|
+
autoHandleLandingUrl = true;
|
|
9
|
+
isInitialized = false;
|
|
10
|
+
processedMessageIds = new Set();
|
|
11
11
|
static DEFAULT_CHANNEL = {
|
|
12
12
|
id: 'clix_channel',
|
|
13
13
|
name: 'Clix Notifications',
|
|
@@ -18,84 +18,110 @@ export class NotificationService {
|
|
|
18
18
|
lights: true
|
|
19
19
|
};
|
|
20
20
|
static ANDROID_GROUP_ID = 'clix_notification_group';
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
autoHandleLandingUrl = true;
|
|
26
|
-
constructor() {}
|
|
27
|
-
static getInstance() {
|
|
28
|
-
if (!NotificationService.instance) {
|
|
29
|
-
NotificationService.instance = new NotificationService();
|
|
30
|
-
}
|
|
31
|
-
return NotificationService.instance;
|
|
21
|
+
constructor(deviceService, tokenService, eventService) {
|
|
22
|
+
this.deviceService = deviceService;
|
|
23
|
+
this.tokenService = tokenService;
|
|
24
|
+
this.eventService = eventService;
|
|
32
25
|
}
|
|
33
|
-
|
|
34
|
-
if (NotificationService.instance) {
|
|
35
|
-
NotificationService.instance.cleanup();
|
|
36
|
-
NotificationService.instance = null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
async initialize(eventService, storageService, deviceService, tokenService) {
|
|
26
|
+
async initialize() {
|
|
40
27
|
if (this.isInitialized) {
|
|
41
|
-
ClixLogger.debug('Notification service already initialized
|
|
42
|
-
return
|
|
28
|
+
ClixLogger.debug('Notification service already initialized');
|
|
29
|
+
return;
|
|
43
30
|
}
|
|
44
31
|
try {
|
|
45
|
-
ClixLogger.debug('Initializing notification service');
|
|
46
|
-
this.
|
|
47
|
-
this.
|
|
48
|
-
this.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
ClixLogger.debug('Initializing notification service...');
|
|
33
|
+
this.setupTokenRefreshListener();
|
|
34
|
+
this.setupPushReceivedHandler(); // NOTE(nyanxyz): must be set up before any await calls
|
|
35
|
+
await this.setupPushTappedHandler();
|
|
36
|
+
if (Platform.OS === 'android') {
|
|
37
|
+
await this.createNotificationChannels();
|
|
38
|
+
}
|
|
52
39
|
this.isInitialized = true;
|
|
53
40
|
ClixLogger.debug('Notification service initialized successfully');
|
|
54
|
-
return this;
|
|
55
41
|
} catch (error) {
|
|
56
42
|
ClixLogger.error('Failed to initialize notification service', error);
|
|
57
43
|
throw error;
|
|
58
44
|
}
|
|
59
45
|
}
|
|
60
|
-
async getCurrentToken() {
|
|
61
|
-
try {
|
|
62
|
-
this.currentPushToken = await this.getOrFetchToken();
|
|
63
|
-
return this.currentPushToken;
|
|
64
|
-
} catch (error) {
|
|
65
|
-
ClixLogger.error('Failed to get push token', error);
|
|
66
|
-
await this.handleFcmTokenError(error);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
46
|
cleanup() {
|
|
71
|
-
this.
|
|
72
|
-
this.
|
|
47
|
+
this.unsubscribeMessage?.();
|
|
48
|
+
this.unsubscribeNotificationOpenedApp?.();
|
|
73
49
|
this.unsubscribeTokenRefresh?.();
|
|
74
|
-
this.
|
|
50
|
+
this.unsubscribeForegroundEvent?.();
|
|
75
51
|
this.isInitialized = false;
|
|
76
52
|
this.processedMessageIds.clear();
|
|
77
53
|
ClixLogger.debug('Notification service cleaned up');
|
|
78
54
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
55
|
+
async requestPermission() {
|
|
56
|
+
const settings = await notifee.requestPermission({
|
|
57
|
+
alert: true,
|
|
58
|
+
badge: true,
|
|
59
|
+
sound: true,
|
|
60
|
+
provisional: false,
|
|
61
|
+
announcement: false,
|
|
62
|
+
carPlay: false,
|
|
63
|
+
criticalAlert: false
|
|
64
|
+
});
|
|
65
|
+
ClixLogger.debug('Push notification permission status:', settings);
|
|
66
|
+
const isGranted = settings.authorizationStatus === AuthorizationStatus.AUTHORIZED || settings.authorizationStatus === AuthorizationStatus.PROVISIONAL;
|
|
67
|
+
await this.setPermissionGranted(isGranted);
|
|
68
|
+
return settings;
|
|
87
69
|
}
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
async setPermissionGranted(isGranted) {
|
|
71
|
+
try {
|
|
72
|
+
await this.deviceService.updatePushPermission(isGranted);
|
|
73
|
+
ClixLogger.debug(`Push permission status reported to server: ${isGranted ? 'granted' : 'denied'}`);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
ClixLogger.warn('Failed to upsert push permission status', error);
|
|
76
|
+
}
|
|
90
77
|
}
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
setupPushReceivedHandler() {
|
|
79
|
+
/**
|
|
80
|
+
* Android: background message handler
|
|
81
|
+
*/
|
|
82
|
+
messaging().setBackgroundMessageHandler(this.handleBackgroundMessage.bind(this));
|
|
83
|
+
/**
|
|
84
|
+
* iOS & Android: foreground message handler
|
|
85
|
+
*/
|
|
86
|
+
this.unsubscribeMessage = messaging().onMessage(this.handleForegroundMessage.bind(this));
|
|
87
|
+
/**
|
|
88
|
+
* iOS: background messages are handled in the Notification Service Extension
|
|
89
|
+
*/
|
|
90
|
+
}
|
|
91
|
+
async setupPushTappedHandler() {
|
|
92
|
+
/**
|
|
93
|
+
* Android: background notification tap handler
|
|
94
|
+
* & app launched from quit state via a notification
|
|
95
|
+
*/
|
|
96
|
+
notifee.onBackgroundEvent(this.handleNotificationEvent.bind(this));
|
|
97
|
+
/**
|
|
98
|
+
* iOS & Android: foreground notification tap handler
|
|
99
|
+
*/
|
|
100
|
+
this.unsubscribeForegroundEvent = notifee.onForegroundEvent(this.handleForegroundNotificationEvent.bind(this));
|
|
101
|
+
/**
|
|
102
|
+
* iOS: background notification tap handler
|
|
103
|
+
*/
|
|
104
|
+
this.unsubscribeNotificationOpenedApp = messaging().onNotificationOpenedApp(this.handleNotificationOpenedApp.bind(this));
|
|
105
|
+
/**
|
|
106
|
+
* iOS: app launched from a quit state via a notification
|
|
107
|
+
*/
|
|
108
|
+
await this.handleInitialNotification();
|
|
93
109
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
setupTokenRefreshListener() {
|
|
111
|
+
this.unsubscribeTokenRefresh = messaging().onTokenRefresh(async token => {
|
|
112
|
+
try {
|
|
113
|
+
await this.tokenRefreshHandler?.(token);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
ClixLogger.error('Token refresh handler failed', error);
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
ClixLogger.debug(`Push token refreshed: ${token}`);
|
|
119
|
+
this.tokenService.saveToken(token);
|
|
120
|
+
await this.deviceService.updatePushToken(token, 'FCM');
|
|
121
|
+
} catch (error) {
|
|
122
|
+
ClixLogger.error('Failed to handle token refresh', error);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
99
125
|
}
|
|
100
126
|
async createNotificationChannels() {
|
|
101
127
|
try {
|
|
@@ -105,64 +131,10 @@ export class NotificationService {
|
|
|
105
131
|
ClixLogger.error('Failed to create notification channels', error);
|
|
106
132
|
}
|
|
107
133
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
notifee.onBackgroundEvent(async event => {
|
|
113
|
-
await this.handleNotificationEvent(event);
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
async handleNotificationEvent(event) {
|
|
117
|
-
const {
|
|
118
|
-
type,
|
|
119
|
-
detail
|
|
120
|
-
} = event;
|
|
121
|
-
switch (type) {
|
|
122
|
-
case EventType.PRESS:
|
|
123
|
-
if (detail.notification?.data) {
|
|
124
|
-
await this.handleNotificationTap(detail.notification.data);
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
case EventType.ACTION_PRESS:
|
|
128
|
-
if (detail.pressAction?.id) {
|
|
129
|
-
await this.handleActionPress(detail.pressAction.id, detail.notification?.data);
|
|
130
|
-
}
|
|
131
|
-
break;
|
|
132
|
-
case EventType.DISMISSED:
|
|
133
|
-
ClixLogger.debug('Notification dismissed');
|
|
134
|
-
break;
|
|
135
|
-
default:
|
|
136
|
-
ClixLogger.debug('Unhandled notification event type:', type);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
async handleActionPress(actionId, data) {
|
|
140
|
-
ClixLogger.debug('Action pressed:', actionId);
|
|
141
|
-
if (data) {
|
|
142
|
-
await this.handleNotificationTap(data);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
async initializeMessageService() {
|
|
146
|
-
this.setupMessageHandlers();
|
|
147
|
-
await this.getAndUpdateToken();
|
|
148
|
-
this.setupTokenRefreshListener();
|
|
149
|
-
}
|
|
150
|
-
setupMessageHandlers() {
|
|
151
|
-
this.messagingService.setBackgroundMessageHandler(async remoteMessage => {
|
|
152
|
-
await this.handleBackgroundMessage(remoteMessage);
|
|
153
|
-
});
|
|
154
|
-
this.unsubscribeForegroundMessage = this.messagingService.onMessage(async remoteMessage => {
|
|
155
|
-
await this.handleForegroundMessage(remoteMessage);
|
|
156
|
-
});
|
|
157
|
-
this.unsubscribeNotificationOpened = this.messagingService.onNotificationOpenedApp(async remoteMessage => {
|
|
158
|
-
ClixLogger.debug('Notification opened from background state:', {
|
|
159
|
-
messageId: remoteMessage.messageId,
|
|
160
|
-
data: remoteMessage.data
|
|
161
|
-
});
|
|
162
|
-
await this.handleNotificationTap(remoteMessage.data ?? {});
|
|
163
|
-
});
|
|
164
|
-
this.checkInitialNotification();
|
|
165
|
-
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Android: background message handler
|
|
137
|
+
*/
|
|
166
138
|
async handleBackgroundMessage(remoteMessage) {
|
|
167
139
|
ClixLogger.debug('Handling background message:', remoteMessage.messageId);
|
|
168
140
|
const data = remoteMessage.data ?? {};
|
|
@@ -177,15 +149,7 @@ export class NotificationService {
|
|
|
177
149
|
ClixLogger.warn('No Clix payload found in background message');
|
|
178
150
|
return;
|
|
179
151
|
}
|
|
180
|
-
this.
|
|
181
|
-
messageId: remoteMessage.messageId,
|
|
182
|
-
data: data,
|
|
183
|
-
timestamp: Date.now(),
|
|
184
|
-
clixMessageId: clixPayload.messageId,
|
|
185
|
-
campaignId: clixPayload.campaignId,
|
|
186
|
-
trackingId: clixPayload.trackingId
|
|
187
|
-
});
|
|
188
|
-
await this.trackEventInBackground(clixPayload);
|
|
152
|
+
await this.trackPushReceivedEvent(clixPayload);
|
|
189
153
|
if (!remoteMessage.notification) {
|
|
190
154
|
await this.displayNotification(remoteMessage, clixPayload);
|
|
191
155
|
}
|
|
@@ -193,136 +157,165 @@ export class NotificationService {
|
|
|
193
157
|
ClixLogger.error('Background message handler error:', error);
|
|
194
158
|
}
|
|
195
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* iOS & Android: foreground message handler
|
|
163
|
+
*/
|
|
196
164
|
async handleForegroundMessage(remoteMessage) {
|
|
197
165
|
ClixLogger.debug('Handling foreground message:', remoteMessage.messageId);
|
|
166
|
+
const messageId = remoteMessage.messageId;
|
|
167
|
+
if (!messageId) {
|
|
168
|
+
ClixLogger.warn('No messageId found in foreground message');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (this.processedMessageIds.has(messageId)) {
|
|
172
|
+
ClixLogger.debug('Message already processed, skipping duplicate:', messageId);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const data = remoteMessage.data ?? {};
|
|
198
176
|
try {
|
|
199
|
-
const
|
|
200
|
-
if (
|
|
201
|
-
ClixLogger.
|
|
177
|
+
const result = await this.messageHandler?.(data);
|
|
178
|
+
if (result === false) {
|
|
179
|
+
ClixLogger.debug('Foreground message suppressed by user handler:', remoteMessage.messageId);
|
|
202
180
|
return;
|
|
203
181
|
}
|
|
204
|
-
|
|
205
|
-
|
|
182
|
+
} catch (error) {
|
|
183
|
+
ClixLogger.error('Foreground message handler failed', error);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const clixPayload = this.parseClixPayload(data);
|
|
187
|
+
if (!clixPayload) {
|
|
188
|
+
ClixLogger.warn('No Clix payload found in background message');
|
|
206
189
|
return;
|
|
207
190
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.processedMessageIds.add(messageId);
|
|
213
|
-
if (Platform.OS === 'android') {
|
|
214
|
-
// NOTE(nyanxyz): on iOS, Received event is tracked in NSE
|
|
215
|
-
await this.handlePushReceived(data);
|
|
216
|
-
}
|
|
217
|
-
if (await this.shouldDisplayForegroundNotification(data)) {
|
|
218
|
-
await this.displayNotification(remoteMessage, clixPayload);
|
|
219
|
-
} else {
|
|
220
|
-
ClixLogger.debug('Foreground message suppressed by user handler:', messageId);
|
|
221
|
-
}
|
|
222
|
-
} else {
|
|
223
|
-
ClixLogger.warn('No Clix payload found in foreground message');
|
|
191
|
+
this.processedMessageIds.add(messageId);
|
|
192
|
+
if (Platform.OS === 'android') {
|
|
193
|
+
// NOTE(nyanxyz): on iOS, Received event is tracked in Notification Service Extension
|
|
194
|
+
await this.trackPushReceivedEvent(clixPayload);
|
|
224
195
|
}
|
|
196
|
+
await this.displayNotification(remoteMessage, clixPayload);
|
|
225
197
|
} catch (error) {
|
|
226
198
|
ClixLogger.error('Failed to handle foreground message', error);
|
|
227
199
|
}
|
|
228
200
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
criticalAlert: false
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* iOS: background notification tap handler
|
|
204
|
+
*/
|
|
205
|
+
async handleNotificationOpenedApp(remoteMessage) {
|
|
206
|
+
ClixLogger.debug('Handling notification opened from background:', {
|
|
207
|
+
messageId: remoteMessage.messageId,
|
|
208
|
+
data: remoteMessage.data
|
|
238
209
|
});
|
|
239
|
-
|
|
240
|
-
const isGranted = settings.authorizationStatus === AuthorizationStatus.AUTHORIZED || settings.authorizationStatus === AuthorizationStatus.PROVISIONAL;
|
|
241
|
-
await this.setPermissionGranted(isGranted);
|
|
242
|
-
return settings;
|
|
243
|
-
}
|
|
244
|
-
async setPermissionGranted(isGranted) {
|
|
245
|
-
if (!this.deviceService) {
|
|
246
|
-
ClixLogger.debug('Device service is not initialized, skipping push permission upsert');
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
210
|
+
const data = remoteMessage.data ?? {};
|
|
249
211
|
try {
|
|
250
|
-
await this.
|
|
251
|
-
ClixLogger.debug(`Push permission status reported to server: ${isGranted ? 'granted' : 'denied'}`);
|
|
212
|
+
await this.notificationOpenedAppHandler?.(data);
|
|
252
213
|
} catch (error) {
|
|
253
|
-
ClixLogger.
|
|
214
|
+
ClixLogger.error('Notification opened app handler failed', error);
|
|
254
215
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
ClixLogger.debug('Push token refreshed');
|
|
260
|
-
this.currentPushToken = token;
|
|
261
|
-
await this.saveAndRegisterToken(token);
|
|
262
|
-
} catch (error) {
|
|
263
|
-
ClixLogger.error('Failed to handle token refresh', error);
|
|
264
|
-
await this.handleFcmTokenError(error);
|
|
216
|
+
try {
|
|
217
|
+
const clixPayload = this.parseClixPayload(data);
|
|
218
|
+
if (clixPayload) {
|
|
219
|
+
await this.trackPushTappedEvent(clixPayload);
|
|
265
220
|
}
|
|
266
|
-
|
|
221
|
+
if (this.autoHandleLandingUrl) {
|
|
222
|
+
await this.handleUrlNavigation(data);
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
ClixLogger.error('Failed to handle notification opened from background', error);
|
|
226
|
+
}
|
|
267
227
|
}
|
|
268
|
-
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* iOS: app launched from a quit state via a notification
|
|
231
|
+
*/
|
|
232
|
+
async handleInitialNotification() {
|
|
269
233
|
try {
|
|
270
|
-
const initialNotification = await
|
|
234
|
+
const initialNotification = await messaging().getInitialNotification();
|
|
271
235
|
if (initialNotification) {
|
|
272
236
|
ClixLogger.debug('App launched from notification:', initialNotification.messageId);
|
|
273
|
-
|
|
237
|
+
const data = initialNotification.data ?? {};
|
|
238
|
+
const clixPayload = this.parseClixPayload(data);
|
|
239
|
+
if (clixPayload) {
|
|
240
|
+
await this.trackPushTappedEvent(clixPayload);
|
|
241
|
+
}
|
|
242
|
+
if (this.autoHandleLandingUrl) {
|
|
243
|
+
await this.handleUrlNavigation(data);
|
|
244
|
+
}
|
|
274
245
|
}
|
|
275
246
|
} catch (error) {
|
|
276
247
|
ClixLogger.error('Failed to handle initial notification', error);
|
|
277
248
|
}
|
|
278
249
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Android: background notification tap handler
|
|
253
|
+
*/
|
|
254
|
+
async handleNotificationEvent(event) {
|
|
255
|
+
const {
|
|
256
|
+
type,
|
|
257
|
+
detail
|
|
258
|
+
} = event;
|
|
259
|
+
switch (type) {
|
|
260
|
+
case EventType.PRESS:
|
|
261
|
+
case EventType.ACTION_PRESS:
|
|
262
|
+
{
|
|
263
|
+
const data = detail.notification?.data || {};
|
|
264
|
+
const clixPayload = this.parseClixPayload(data);
|
|
265
|
+
if (clixPayload) {
|
|
266
|
+
await this.trackPushTappedEvent(clixPayload);
|
|
267
|
+
}
|
|
268
|
+
if (this.autoHandleLandingUrl) {
|
|
269
|
+
await this.handleUrlNavigation(data);
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case EventType.DISMISSED:
|
|
274
|
+
ClixLogger.debug('Notification dismissed');
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
ClixLogger.debug('Unhandled notification event type:', type);
|
|
287
278
|
}
|
|
288
279
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* iOS & Android: foreground notification tap handler
|
|
283
|
+
*/
|
|
284
|
+
async handleForegroundNotificationEvent(event) {
|
|
285
|
+
try {
|
|
286
|
+
await this.foregroundEventHandler?.(event);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
ClixLogger.error('Foreground notification event handler failed', error);
|
|
298
289
|
}
|
|
299
|
-
|
|
290
|
+
await this.handleNotificationEvent(event);
|
|
300
291
|
}
|
|
301
|
-
async
|
|
302
|
-
|
|
303
|
-
this.
|
|
304
|
-
ClixLogger.debug('
|
|
292
|
+
async trackPushReceivedEvent(payload) {
|
|
293
|
+
try {
|
|
294
|
+
await this.eventService.trackEvent('PUSH_NOTIFICATION_RECEIVED', {}, payload.messageId, payload.userJourneyId, payload.userJourneyNodeId);
|
|
295
|
+
ClixLogger.debug('PUSH_NOTIFICATION_RECEIVED event tracked:', payload.messageId);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
ClixLogger.error('Failed to track PUSH_NOTIFICATION_RECEIVED event', error);
|
|
305
298
|
}
|
|
306
|
-
await this.registerTokenWithServer(token);
|
|
307
299
|
}
|
|
308
|
-
async
|
|
309
|
-
|
|
310
|
-
await this.
|
|
311
|
-
ClixLogger.debug('
|
|
300
|
+
async trackPushTappedEvent(payload) {
|
|
301
|
+
try {
|
|
302
|
+
await this.eventService.trackEvent('PUSH_NOTIFICATION_TAPPED', {}, payload.messageId, payload.userJourneyId, payload.userJourneyNodeId);
|
|
303
|
+
ClixLogger.debug('PUSH_NOTIFICATION_TAPPED event tracked:', payload.messageId);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
ClixLogger.error('Failed to track PUSH_NOTIFICATION_TAPPED event', error);
|
|
312
306
|
}
|
|
313
307
|
}
|
|
314
308
|
async displayNotification(remoteMessage, clixPayload) {
|
|
315
309
|
try {
|
|
316
|
-
const notificationContent = this.extractNotificationContent(remoteMessage.notification, clixPayload);
|
|
317
310
|
ClixLogger.debug('Creating notification config with content:', {
|
|
318
|
-
title:
|
|
319
|
-
body:
|
|
320
|
-
hasImage: !!
|
|
321
|
-
imageUrl:
|
|
311
|
+
title: clixPayload.title,
|
|
312
|
+
body: clixPayload.body,
|
|
313
|
+
hasImage: !!clixPayload.imageUrl,
|
|
314
|
+
imageUrl: clixPayload.imageUrl
|
|
322
315
|
});
|
|
323
|
-
const notificationConfig = await this.createNotificationConfig(remoteMessage, clixPayload,
|
|
316
|
+
const notificationConfig = await this.createNotificationConfig(remoteMessage, clixPayload, NotificationService.DEFAULT_CHANNEL.id);
|
|
324
317
|
await notifee.displayNotification(notificationConfig);
|
|
325
|
-
ClixLogger.debug('Notification displayed successfully:',
|
|
318
|
+
ClixLogger.debug('Notification displayed successfully:', clixPayload.title);
|
|
326
319
|
} catch (error) {
|
|
327
320
|
ClixLogger.error('Failed to display notification', error);
|
|
328
321
|
if (error instanceof Error) {
|
|
@@ -336,12 +329,12 @@ export class NotificationService {
|
|
|
336
329
|
}
|
|
337
330
|
}
|
|
338
331
|
}
|
|
339
|
-
async createNotificationConfig(remoteMessage, clixPayload,
|
|
340
|
-
const imageUrl =
|
|
332
|
+
async createNotificationConfig(remoteMessage, clixPayload, channelId) {
|
|
333
|
+
const imageUrl = clixPayload.imageUrl;
|
|
341
334
|
const config = {
|
|
342
335
|
id: remoteMessage.messageId || Date.now().toString(),
|
|
343
|
-
title:
|
|
344
|
-
body:
|
|
336
|
+
title: clixPayload.title,
|
|
337
|
+
body: clixPayload.body,
|
|
345
338
|
data: remoteMessage.data ?? {},
|
|
346
339
|
android: {
|
|
347
340
|
channelId,
|
|
@@ -352,11 +345,10 @@ export class NotificationService {
|
|
|
352
345
|
groupSummary: false,
|
|
353
346
|
groupAlertBehavior: AndroidGroupAlertBehavior.CHILDREN,
|
|
354
347
|
sound: 'default',
|
|
355
|
-
ticker:
|
|
356
|
-
actions: this.createNotificationActions(clixPayload),
|
|
348
|
+
ticker: clixPayload.body,
|
|
357
349
|
style: {
|
|
358
350
|
type: AndroidStyle.BIGTEXT,
|
|
359
|
-
text:
|
|
351
|
+
text: clixPayload.body
|
|
360
352
|
},
|
|
361
353
|
pressAction: {
|
|
362
354
|
id: 'default'
|
|
@@ -377,103 +369,6 @@ export class NotificationService {
|
|
|
377
369
|
}
|
|
378
370
|
return config;
|
|
379
371
|
}
|
|
380
|
-
createNotificationActions(clixPayload) {
|
|
381
|
-
const actions = [];
|
|
382
|
-
actions.push({
|
|
383
|
-
title: 'Open',
|
|
384
|
-
pressAction: {
|
|
385
|
-
id: 'default'
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
if (clixPayload.customProperties?.actions) {
|
|
389
|
-
const customActions = clixPayload.customProperties.actions;
|
|
390
|
-
if (Array.isArray(customActions)) {
|
|
391
|
-
customActions.forEach(action => {
|
|
392
|
-
if (action.title && action.actionId) {
|
|
393
|
-
actions.push({
|
|
394
|
-
title: action.title,
|
|
395
|
-
pressAction: {
|
|
396
|
-
id: action.actionId
|
|
397
|
-
}
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return actions;
|
|
404
|
-
}
|
|
405
|
-
isValidImageUrl(url) {
|
|
406
|
-
return url.trim() !== '' && url.startsWith('http');
|
|
407
|
-
}
|
|
408
|
-
extractNotificationContent(fcmNotification, clixPayload) {
|
|
409
|
-
ClixLogger.debug('Extracting notification content from payload:', {
|
|
410
|
-
fcmTitle: fcmNotification?.title,
|
|
411
|
-
fcmBody: fcmNotification?.body,
|
|
412
|
-
customProperties: clixPayload.customProperties,
|
|
413
|
-
messageId: clixPayload.messageId,
|
|
414
|
-
imageUrl: clixPayload.imageUrl
|
|
415
|
-
});
|
|
416
|
-
const title = fcmNotification?.title || clixPayload.customProperties?.title || 'New Message';
|
|
417
|
-
const body = fcmNotification?.body || clixPayload.customProperties?.body || '';
|
|
418
|
-
ClixLogger.debug('Extracted notification content:', {
|
|
419
|
-
title,
|
|
420
|
-
body
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
// Validate imageUrl before using it
|
|
424
|
-
let imageUrl;
|
|
425
|
-
if (clixPayload.imageUrl) {
|
|
426
|
-
ClixLogger.debug('Processing image URL:', clixPayload.imageUrl);
|
|
427
|
-
if (this.isValidImageUrl(clixPayload.imageUrl)) {
|
|
428
|
-
imageUrl = clixPayload.imageUrl;
|
|
429
|
-
ClixLogger.debug('Image URL validated successfully');
|
|
430
|
-
} else {
|
|
431
|
-
ClixLogger.warn('Invalid image URL, skipping:', clixPayload.imageUrl);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
return {
|
|
435
|
-
title,
|
|
436
|
-
body,
|
|
437
|
-
imageUrl
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
async handlePushReceived(data) {
|
|
441
|
-
try {
|
|
442
|
-
const clixPayload = this.parseClixPayload(data);
|
|
443
|
-
if (clixPayload) {
|
|
444
|
-
await this.trackPushEvent('PUSH_NOTIFICATION_RECEIVED', clixPayload);
|
|
445
|
-
}
|
|
446
|
-
ClixLogger.debug('Push notification received and processed');
|
|
447
|
-
} catch (error) {
|
|
448
|
-
ClixLogger.error('Failed to handle push received', error);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
async handlePushTapped(data) {
|
|
452
|
-
try {
|
|
453
|
-
const clixPayload = this.parseClixPayload(data);
|
|
454
|
-
if (clixPayload) {
|
|
455
|
-
await this.trackPushEvent('PUSH_NOTIFICATION_TAPPED', clixPayload);
|
|
456
|
-
}
|
|
457
|
-
if (this.autoHandleLandingUrl) {
|
|
458
|
-
await this.handleUrlNavigation(data);
|
|
459
|
-
}
|
|
460
|
-
ClixLogger.debug('Push notification tapped and processed');
|
|
461
|
-
} catch (error) {
|
|
462
|
-
ClixLogger.error('Failed to handle push tapped', error);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
async handleNotificationTap(data) {
|
|
466
|
-
try {
|
|
467
|
-
await this.openedHandler?.(data);
|
|
468
|
-
} catch (error) {
|
|
469
|
-
ClixLogger.error('Failed to handle notification tap', error);
|
|
470
|
-
}
|
|
471
|
-
try {
|
|
472
|
-
await this.handlePushTapped(data);
|
|
473
|
-
} catch (error) {
|
|
474
|
-
ClixLogger.error('Failed to handle notification tap', error);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
372
|
async handleUrlNavigation(data) {
|
|
478
373
|
try {
|
|
479
374
|
let url;
|
|
@@ -504,91 +399,46 @@ export class NotificationService {
|
|
|
504
399
|
}
|
|
505
400
|
parseClixPayload(userInfo) {
|
|
506
401
|
try {
|
|
507
|
-
let
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
} else if (typeof userInfo.clix === 'string') {
|
|
512
|
-
payload = JSON.parse(userInfo.clix);
|
|
513
|
-
}
|
|
402
|
+
let data = userInfo?.clix;
|
|
403
|
+
if (data == null) {
|
|
404
|
+
ClixLogger.debug("No 'clix' entry found in notification data");
|
|
405
|
+
return null;
|
|
514
406
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
407
|
+
if (typeof data === 'string') {
|
|
408
|
+
try {
|
|
409
|
+
data = JSON.parse(data);
|
|
410
|
+
} catch (parseError) {
|
|
411
|
+
ClixLogger.error('Failed to parse Clix payload JSON string', parseError);
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
520
414
|
}
|
|
521
|
-
ClixLogger.debug('
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
customProperties: Object.keys(customProperties).length > 0 ? customProperties : undefined
|
|
415
|
+
ClixLogger.debug('Parsing Clix payload from notification data:', data);
|
|
416
|
+
const payload = {
|
|
417
|
+
messageId: data.message_id,
|
|
418
|
+
title: data.title,
|
|
419
|
+
body: data.body,
|
|
420
|
+
imageUrl: data.image_url || undefined,
|
|
421
|
+
landingUrl: data.landing_url || undefined,
|
|
422
|
+
userJourneyId: data.user_journey_id || undefined,
|
|
423
|
+
userJourneyNodeId: data.user_journey_node_id || undefined
|
|
531
424
|
};
|
|
532
|
-
ClixLogger.debug('
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
return null;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
async trackPushEvent(eventType, clixPayload) {
|
|
540
|
-
const properties = this.extractTrackingProperties(clixPayload);
|
|
541
|
-
const messageId = clixPayload.messageId;
|
|
542
|
-
await this.eventService.trackEvent(eventType, properties, messageId);
|
|
543
|
-
ClixLogger.debug(`${eventType} tracked:`, messageId);
|
|
544
|
-
}
|
|
545
|
-
extractTrackingProperties(clixPayload) {
|
|
546
|
-
const properties = {};
|
|
547
|
-
if (clixPayload.messageId) properties.messageId = clixPayload.messageId;
|
|
548
|
-
if (clixPayload.campaignId) properties.campaignId = clixPayload.campaignId;
|
|
549
|
-
if (clixPayload.trackingId) properties.trackingId = clixPayload.trackingId;
|
|
550
|
-
return properties;
|
|
551
|
-
}
|
|
552
|
-
async trackEventInBackground(clixPayload) {
|
|
553
|
-
const messageId = clixPayload.messageId;
|
|
554
|
-
if (!messageId) {
|
|
555
|
-
ClixLogger.warn('No messageId found in payload, skipping event tracking');
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
try {
|
|
559
|
-
const configData = this.storageService.get('clix_config');
|
|
560
|
-
if (!configData) {
|
|
561
|
-
ClixLogger.error('No Clix config found in storage');
|
|
562
|
-
return;
|
|
425
|
+
ClixLogger.debug('Constructed Clix payload:', payload);
|
|
426
|
+
if (!payload.messageId) {
|
|
427
|
+
ClixLogger.error('No messageId found in Clix payload');
|
|
428
|
+
return null;
|
|
563
429
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
deviceId = UUID.generate();
|
|
568
|
-
this.storageService.set('clix_device_id', deviceId);
|
|
430
|
+
if (!payload.title) {
|
|
431
|
+
ClixLogger.error('No title found in Clix payload');
|
|
432
|
+
return null;
|
|
569
433
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
async shouldDisplayForegroundNotification(data) {
|
|
578
|
-
try {
|
|
579
|
-
const result = await this.messageHandler?.(data);
|
|
580
|
-
return result !== false;
|
|
434
|
+
if (!payload.body) {
|
|
435
|
+
ClixLogger.error('No body found in Clix payload');
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
return payload;
|
|
581
439
|
} catch (error) {
|
|
582
|
-
ClixLogger.
|
|
583
|
-
return
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
async handleFcmTokenError(error) {
|
|
587
|
-
try {
|
|
588
|
-
const errorInstance = error instanceof Error ? error : new Error(String(error));
|
|
589
|
-
await this.fcmTokenErrorHandler?.(errorInstance);
|
|
590
|
-
} catch (handlerError) {
|
|
591
|
-
ClixLogger.warn('FCM token error handler failed', handlerError);
|
|
440
|
+
ClixLogger.error('Failed to parse Clix payload', error);
|
|
441
|
+
return null;
|
|
592
442
|
}
|
|
593
443
|
}
|
|
594
444
|
}
|