@clix-so/react-native-sdk 1.0.0 → 1.1.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +253 -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 +109 -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 +321 -534
- package/src/services/TokenService.ts +4 -71
- package/src/utils/http/HTTPClient.ts +154 -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,66 +131,13 @@ 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);
|
|
140
|
+
setTimeout(() => ClixLogger.debug('still alive after 3s'), 3000);
|
|
168
141
|
const data = remoteMessage.data ?? {};
|
|
169
142
|
try {
|
|
170
143
|
await this.backgroundMessageHandler?.(data);
|
|
@@ -177,152 +150,173 @@ export class NotificationService {
|
|
|
177
150
|
ClixLogger.warn('No Clix payload found in background message');
|
|
178
151
|
return;
|
|
179
152
|
}
|
|
180
|
-
this.storageService.set('last_background_notification', {
|
|
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);
|
|
189
153
|
if (!remoteMessage.notification) {
|
|
190
154
|
await this.displayNotification(remoteMessage, clixPayload);
|
|
191
155
|
}
|
|
156
|
+
await this.trackPushReceivedEvent(clixPayload);
|
|
192
157
|
} catch (error) {
|
|
193
158
|
ClixLogger.error('Background message handler error:', error);
|
|
194
159
|
}
|
|
195
160
|
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* iOS & Android: foreground message handler
|
|
164
|
+
*/
|
|
196
165
|
async handleForegroundMessage(remoteMessage) {
|
|
197
166
|
ClixLogger.debug('Handling foreground message:', remoteMessage.messageId);
|
|
167
|
+
const messageId = remoteMessage.messageId;
|
|
168
|
+
if (!messageId) {
|
|
169
|
+
ClixLogger.warn('No messageId found in foreground message');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (this.processedMessageIds.has(messageId)) {
|
|
173
|
+
ClixLogger.debug('Message already processed, skipping duplicate:', messageId);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const data = remoteMessage.data ?? {};
|
|
198
177
|
try {
|
|
199
|
-
const
|
|
200
|
-
if (
|
|
201
|
-
ClixLogger.
|
|
178
|
+
const result = await this.messageHandler?.(data);
|
|
179
|
+
if (result === false) {
|
|
180
|
+
ClixLogger.debug('Foreground message suppressed by user handler:', remoteMessage.messageId);
|
|
202
181
|
return;
|
|
203
182
|
}
|
|
204
|
-
|
|
205
|
-
|
|
183
|
+
} catch (error) {
|
|
184
|
+
ClixLogger.error('Foreground message handler failed', error);
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const clixPayload = this.parseClixPayload(data);
|
|
188
|
+
if (!clixPayload) {
|
|
189
|
+
ClixLogger.warn('No Clix payload found in background message');
|
|
206
190
|
return;
|
|
207
191
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
this.
|
|
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');
|
|
192
|
+
this.processedMessageIds.add(messageId);
|
|
193
|
+
await this.displayNotification(remoteMessage, clixPayload);
|
|
194
|
+
if (Platform.OS === 'android') {
|
|
195
|
+
// NOTE(nyanxyz): on iOS, Received event is tracked in Notification Service Extension
|
|
196
|
+
await this.trackPushReceivedEvent(clixPayload);
|
|
224
197
|
}
|
|
225
198
|
} catch (error) {
|
|
226
199
|
ClixLogger.error('Failed to handle foreground message', error);
|
|
227
200
|
}
|
|
228
201
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
criticalAlert: false
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* iOS: background notification tap handler
|
|
205
|
+
*/
|
|
206
|
+
async handleNotificationOpenedApp(remoteMessage) {
|
|
207
|
+
ClixLogger.debug('Handling notification opened from background:', {
|
|
208
|
+
messageId: remoteMessage.messageId,
|
|
209
|
+
data: remoteMessage.data
|
|
238
210
|
});
|
|
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
|
-
}
|
|
211
|
+
const data = remoteMessage.data ?? {};
|
|
249
212
|
try {
|
|
250
|
-
await this.
|
|
251
|
-
ClixLogger.debug(`Push permission status reported to server: ${isGranted ? 'granted' : 'denied'}`);
|
|
213
|
+
await this.notificationOpenedAppHandler?.(data);
|
|
252
214
|
} catch (error) {
|
|
253
|
-
ClixLogger.
|
|
215
|
+
ClixLogger.error('Notification opened app handler failed', error);
|
|
254
216
|
}
|
|
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);
|
|
217
|
+
try {
|
|
218
|
+
const clixPayload = this.parseClixPayload(data);
|
|
219
|
+
if (clixPayload) {
|
|
220
|
+
await this.trackPushTappedEvent(clixPayload);
|
|
265
221
|
}
|
|
266
|
-
|
|
222
|
+
if (this.autoHandleLandingUrl) {
|
|
223
|
+
await this.handleUrlNavigation(data);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
ClixLogger.error('Failed to handle notification opened from background', error);
|
|
227
|
+
}
|
|
267
228
|
}
|
|
268
|
-
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* iOS: app launched from a quit state via a notification
|
|
232
|
+
*/
|
|
233
|
+
async handleInitialNotification() {
|
|
269
234
|
try {
|
|
270
|
-
const initialNotification = await
|
|
235
|
+
const initialNotification = await messaging().getInitialNotification();
|
|
271
236
|
if (initialNotification) {
|
|
272
237
|
ClixLogger.debug('App launched from notification:', initialNotification.messageId);
|
|
273
|
-
|
|
238
|
+
const data = initialNotification.data ?? {};
|
|
239
|
+
const clixPayload = this.parseClixPayload(data);
|
|
240
|
+
if (clixPayload) {
|
|
241
|
+
await this.trackPushTappedEvent(clixPayload);
|
|
242
|
+
}
|
|
243
|
+
if (this.autoHandleLandingUrl) {
|
|
244
|
+
await this.handleUrlNavigation(data);
|
|
245
|
+
}
|
|
274
246
|
}
|
|
275
247
|
} catch (error) {
|
|
276
248
|
ClixLogger.error('Failed to handle initial notification', error);
|
|
277
249
|
}
|
|
278
250
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Android: background notification tap handler
|
|
254
|
+
*/
|
|
255
|
+
async handleNotificationEvent(event) {
|
|
256
|
+
const {
|
|
257
|
+
type,
|
|
258
|
+
detail
|
|
259
|
+
} = event;
|
|
260
|
+
switch (type) {
|
|
261
|
+
case EventType.PRESS:
|
|
262
|
+
case EventType.ACTION_PRESS:
|
|
263
|
+
{
|
|
264
|
+
const data = detail.notification?.data || {};
|
|
265
|
+
const clixPayload = this.parseClixPayload(data);
|
|
266
|
+
if (clixPayload) {
|
|
267
|
+
await this.trackPushTappedEvent(clixPayload);
|
|
268
|
+
}
|
|
269
|
+
if (this.autoHandleLandingUrl) {
|
|
270
|
+
await this.handleUrlNavigation(data);
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case EventType.DISMISSED:
|
|
275
|
+
ClixLogger.debug('Notification dismissed');
|
|
276
|
+
break;
|
|
277
|
+
default:
|
|
278
|
+
ClixLogger.debug('Unhandled notification event type:', type);
|
|
287
279
|
}
|
|
288
280
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* iOS & Android: foreground notification tap handler
|
|
284
|
+
*/
|
|
285
|
+
async handleForegroundNotificationEvent(event) {
|
|
286
|
+
try {
|
|
287
|
+
await this.foregroundEventHandler?.(event);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
ClixLogger.error('Foreground notification event handler failed', error);
|
|
298
290
|
}
|
|
299
|
-
|
|
291
|
+
await this.handleNotificationEvent(event);
|
|
300
292
|
}
|
|
301
|
-
async
|
|
302
|
-
|
|
303
|
-
this.
|
|
304
|
-
ClixLogger.debug('
|
|
293
|
+
async trackPushReceivedEvent(payload) {
|
|
294
|
+
try {
|
|
295
|
+
await this.eventService.trackEvent('PUSH_NOTIFICATION_RECEIVED', {}, payload.messageId, payload.userJourneyId, payload.userJourneyNodeId);
|
|
296
|
+
ClixLogger.debug('PUSH_NOTIFICATION_RECEIVED event tracked:', payload.messageId);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
ClixLogger.error('Failed to track PUSH_NOTIFICATION_RECEIVED event', error);
|
|
305
299
|
}
|
|
306
|
-
await this.registerTokenWithServer(token);
|
|
307
300
|
}
|
|
308
|
-
async
|
|
309
|
-
|
|
310
|
-
await this.
|
|
311
|
-
ClixLogger.debug('
|
|
301
|
+
async trackPushTappedEvent(payload) {
|
|
302
|
+
try {
|
|
303
|
+
await this.eventService.trackEvent('PUSH_NOTIFICATION_TAPPED', {}, payload.messageId, payload.userJourneyId, payload.userJourneyNodeId);
|
|
304
|
+
ClixLogger.debug('PUSH_NOTIFICATION_TAPPED event tracked:', payload.messageId);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
ClixLogger.error('Failed to track PUSH_NOTIFICATION_TAPPED event', error);
|
|
312
307
|
}
|
|
313
308
|
}
|
|
314
309
|
async displayNotification(remoteMessage, clixPayload) {
|
|
315
310
|
try {
|
|
316
|
-
const notificationContent = this.extractNotificationContent(remoteMessage.notification, clixPayload);
|
|
317
311
|
ClixLogger.debug('Creating notification config with content:', {
|
|
318
|
-
title:
|
|
319
|
-
body:
|
|
320
|
-
hasImage: !!
|
|
321
|
-
imageUrl:
|
|
312
|
+
title: clixPayload.title,
|
|
313
|
+
body: clixPayload.body,
|
|
314
|
+
hasImage: !!clixPayload.imageUrl,
|
|
315
|
+
imageUrl: clixPayload.imageUrl
|
|
322
316
|
});
|
|
323
|
-
const notificationConfig = await this.createNotificationConfig(remoteMessage, clixPayload,
|
|
317
|
+
const notificationConfig = await this.createNotificationConfig(remoteMessage, clixPayload, NotificationService.DEFAULT_CHANNEL.id);
|
|
324
318
|
await notifee.displayNotification(notificationConfig);
|
|
325
|
-
ClixLogger.debug('Notification displayed successfully:',
|
|
319
|
+
ClixLogger.debug('Notification displayed successfully:', clixPayload.title);
|
|
326
320
|
} catch (error) {
|
|
327
321
|
ClixLogger.error('Failed to display notification', error);
|
|
328
322
|
if (error instanceof Error) {
|
|
@@ -336,12 +330,12 @@ export class NotificationService {
|
|
|
336
330
|
}
|
|
337
331
|
}
|
|
338
332
|
}
|
|
339
|
-
async createNotificationConfig(remoteMessage, clixPayload,
|
|
340
|
-
const imageUrl =
|
|
333
|
+
async createNotificationConfig(remoteMessage, clixPayload, channelId) {
|
|
334
|
+
const imageUrl = clixPayload.imageUrl;
|
|
341
335
|
const config = {
|
|
342
336
|
id: remoteMessage.messageId || Date.now().toString(),
|
|
343
|
-
title:
|
|
344
|
-
body:
|
|
337
|
+
title: clixPayload.title,
|
|
338
|
+
body: clixPayload.body,
|
|
345
339
|
data: remoteMessage.data ?? {},
|
|
346
340
|
android: {
|
|
347
341
|
channelId,
|
|
@@ -352,11 +346,10 @@ export class NotificationService {
|
|
|
352
346
|
groupSummary: false,
|
|
353
347
|
groupAlertBehavior: AndroidGroupAlertBehavior.CHILDREN,
|
|
354
348
|
sound: 'default',
|
|
355
|
-
ticker:
|
|
356
|
-
actions: this.createNotificationActions(clixPayload),
|
|
349
|
+
ticker: clixPayload.body,
|
|
357
350
|
style: {
|
|
358
351
|
type: AndroidStyle.BIGTEXT,
|
|
359
|
-
text:
|
|
352
|
+
text: clixPayload.body
|
|
360
353
|
},
|
|
361
354
|
pressAction: {
|
|
362
355
|
id: 'default'
|
|
@@ -377,103 +370,6 @@ export class NotificationService {
|
|
|
377
370
|
}
|
|
378
371
|
return config;
|
|
379
372
|
}
|
|
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
373
|
async handleUrlNavigation(data) {
|
|
478
374
|
try {
|
|
479
375
|
let url;
|
|
@@ -504,91 +400,46 @@ export class NotificationService {
|
|
|
504
400
|
}
|
|
505
401
|
parseClixPayload(userInfo) {
|
|
506
402
|
try {
|
|
507
|
-
let
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
} else if (typeof userInfo.clix === 'string') {
|
|
512
|
-
payload = JSON.parse(userInfo.clix);
|
|
513
|
-
}
|
|
403
|
+
let data = userInfo?.clix;
|
|
404
|
+
if (data == null) {
|
|
405
|
+
ClixLogger.debug("No 'clix' entry found in notification data");
|
|
406
|
+
return null;
|
|
514
407
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
408
|
+
if (typeof data === 'string') {
|
|
409
|
+
try {
|
|
410
|
+
data = JSON.parse(data);
|
|
411
|
+
} catch (parseError) {
|
|
412
|
+
ClixLogger.error('Failed to parse Clix payload JSON string', parseError);
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
520
415
|
}
|
|
521
|
-
ClixLogger.debug('
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
customProperties: Object.keys(customProperties).length > 0 ? customProperties : undefined
|
|
416
|
+
ClixLogger.debug('Parsing Clix payload from notification data:', data);
|
|
417
|
+
const payload = {
|
|
418
|
+
messageId: data.message_id,
|
|
419
|
+
title: data.title,
|
|
420
|
+
body: data.body,
|
|
421
|
+
imageUrl: data.image_url || undefined,
|
|
422
|
+
landingUrl: data.landing_url || undefined,
|
|
423
|
+
userJourneyId: data.user_journey_id || undefined,
|
|
424
|
+
userJourneyNodeId: data.user_journey_node_id || undefined
|
|
531
425
|
};
|
|
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;
|
|
426
|
+
ClixLogger.debug('Constructed Clix payload:', payload);
|
|
427
|
+
if (!payload.messageId) {
|
|
428
|
+
ClixLogger.error('No messageId found in Clix payload');
|
|
429
|
+
return null;
|
|
563
430
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
deviceId = UUID.generate();
|
|
568
|
-
this.storageService.set('clix_device_id', deviceId);
|
|
431
|
+
if (!payload.title) {
|
|
432
|
+
ClixLogger.error('No title found in Clix payload');
|
|
433
|
+
return null;
|
|
569
434
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
async shouldDisplayForegroundNotification(data) {
|
|
578
|
-
try {
|
|
579
|
-
const result = await this.messageHandler?.(data);
|
|
580
|
-
return result !== false;
|
|
435
|
+
if (!payload.body) {
|
|
436
|
+
ClixLogger.error('No body found in Clix payload');
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
return payload;
|
|
581
440
|
} 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);
|
|
441
|
+
ClixLogger.error('Failed to parse Clix payload', error);
|
|
442
|
+
return null;
|
|
592
443
|
}
|
|
593
444
|
}
|
|
594
445
|
}
|