@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.
Files changed (88) hide show
  1. package/lib/module/core/Clix.js +40 -95
  2. package/lib/module/core/Clix.js.map +1 -1
  3. package/lib/module/core/ClixInitCoordinator.js +3 -14
  4. package/lib/module/core/ClixInitCoordinator.js.map +1 -1
  5. package/lib/module/core/ClixNotification.js +25 -28
  6. package/lib/module/core/ClixNotification.js.map +1 -1
  7. package/lib/module/models/ClixDevice.js +0 -6
  8. package/lib/module/models/ClixDevice.js.map +1 -1
  9. package/lib/module/models/ClixPushNotificationPayload.js +0 -19
  10. package/lib/module/models/ClixPushNotificationPayload.js.map +1 -1
  11. package/lib/module/services/ClixAPIClient.js +50 -99
  12. package/lib/module/services/ClixAPIClient.js.map +1 -1
  13. package/lib/module/services/DeviceAPIService.js +37 -45
  14. package/lib/module/services/DeviceAPIService.js.map +1 -1
  15. package/lib/module/services/DeviceService.js +97 -116
  16. package/lib/module/services/DeviceService.js.map +1 -1
  17. package/lib/module/services/EventAPIService.js +3 -5
  18. package/lib/module/services/EventAPIService.js.map +1 -1
  19. package/lib/module/services/EventService.js +13 -20
  20. package/lib/module/services/EventService.js.map +1 -1
  21. package/lib/module/services/NotificationService.js +253 -402
  22. package/lib/module/services/NotificationService.js.map +1 -1
  23. package/lib/module/services/TokenService.js +3 -59
  24. package/lib/module/services/TokenService.js.map +1 -1
  25. package/lib/module/utils/http/HTTPClient.js +109 -0
  26. package/lib/module/utils/http/HTTPClient.js.map +1 -0
  27. package/lib/module/utils/http/HTTPMethod.js +10 -0
  28. package/lib/module/utils/http/HTTPMethod.js.map +1 -0
  29. package/lib/module/utils/http/HTTPRequest.js +4 -0
  30. package/lib/module/utils/http/HTTPRequest.js.map +1 -0
  31. package/lib/module/utils/http/HTTPResponse.js +2 -0
  32. package/lib/module/utils/http/HTTPResponse.js.map +1 -0
  33. package/lib/module/utils/types.js +2 -0
  34. package/lib/module/utils/types.js.map +1 -0
  35. package/lib/typescript/src/core/Clix.d.ts +13 -15
  36. package/lib/typescript/src/core/Clix.d.ts.map +1 -1
  37. package/lib/typescript/src/core/ClixConfig.d.ts +3 -3
  38. package/lib/typescript/src/core/ClixConfig.d.ts.map +1 -1
  39. package/lib/typescript/src/core/ClixInitCoordinator.d.ts +0 -3
  40. package/lib/typescript/src/core/ClixInitCoordinator.d.ts.map +1 -1
  41. package/lib/typescript/src/core/ClixNotification.d.ts +6 -5
  42. package/lib/typescript/src/core/ClixNotification.d.ts.map +1 -1
  43. package/lib/typescript/src/models/ClixDevice.d.ts +0 -2
  44. package/lib/typescript/src/models/ClixDevice.d.ts.map +1 -1
  45. package/lib/typescript/src/models/ClixPushNotificationPayload.d.ts +8 -21
  46. package/lib/typescript/src/models/ClixPushNotificationPayload.d.ts.map +1 -1
  47. package/lib/typescript/src/services/ClixAPIClient.d.ts +6 -22
  48. package/lib/typescript/src/services/ClixAPIClient.d.ts.map +1 -1
  49. package/lib/typescript/src/services/DeviceAPIService.d.ts +1 -1
  50. package/lib/typescript/src/services/DeviceAPIService.d.ts.map +1 -1
  51. package/lib/typescript/src/services/DeviceService.d.ts +10 -5
  52. package/lib/typescript/src/services/DeviceService.d.ts.map +1 -1
  53. package/lib/typescript/src/services/EventAPIService.d.ts.map +1 -1
  54. package/lib/typescript/src/services/EventService.d.ts +1 -0
  55. package/lib/typescript/src/services/EventService.d.ts.map +1 -1
  56. package/lib/typescript/src/services/NotificationService.d.ts +50 -57
  57. package/lib/typescript/src/services/NotificationService.d.ts.map +1 -1
  58. package/lib/typescript/src/services/TokenService.d.ts +1 -7
  59. package/lib/typescript/src/services/TokenService.d.ts.map +1 -1
  60. package/lib/typescript/src/utils/http/HTTPClient.d.ts +15 -0
  61. package/lib/typescript/src/utils/http/HTTPClient.d.ts.map +1 -0
  62. package/lib/typescript/src/utils/http/HTTPMethod.d.ts +7 -0
  63. package/lib/typescript/src/utils/http/HTTPMethod.d.ts.map +1 -0
  64. package/lib/typescript/src/utils/http/HTTPRequest.d.ts +9 -0
  65. package/lib/typescript/src/utils/http/HTTPRequest.d.ts.map +1 -0
  66. package/lib/typescript/src/utils/http/HTTPResponse.d.ts +6 -0
  67. package/lib/typescript/src/utils/http/HTTPResponse.d.ts.map +1 -0
  68. package/lib/typescript/src/utils/types.d.ts +5 -0
  69. package/lib/typescript/src/utils/types.d.ts.map +1 -0
  70. package/package.json +1 -1
  71. package/src/core/Clix.ts +62 -115
  72. package/src/core/ClixConfig.ts +3 -3
  73. package/src/core/ClixInitCoordinator.ts +5 -17
  74. package/src/core/ClixNotification.ts +36 -37
  75. package/src/models/ClixDevice.ts +17 -25
  76. package/src/models/ClixPushNotificationPayload.ts +8 -37
  77. package/src/services/ClixAPIClient.ts +84 -144
  78. package/src/services/DeviceAPIService.ts +39 -47
  79. package/src/services/DeviceService.ts +122 -156
  80. package/src/services/EventAPIService.ts +3 -5
  81. package/src/services/EventService.ts +26 -33
  82. package/src/services/NotificationService.ts +321 -534
  83. package/src/services/TokenService.ts +4 -71
  84. package/src/utils/http/HTTPClient.ts +154 -0
  85. package/src/utils/http/HTTPMethod.ts +6 -0
  86. package/src/utils/http/HTTPRequest.ts +9 -0
  87. package/src/utils/http/HTTPResponse.ts +5 -0
  88. 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
- static instance = null;
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
- messagingService = messaging();
22
- isInitialized = false;
23
- currentPushToken = null;
24
- processedMessageIds = new Set();
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
- static resetInstance() {
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, returning existing instance');
42
- return this;
28
+ ClixLogger.debug('Notification service already initialized');
29
+ return;
43
30
  }
44
31
  try {
45
- ClixLogger.debug('Initializing notification service');
46
- this.eventService = eventService;
47
- this.storageService = storageService;
48
- this.deviceService = deviceService;
49
- this.tokenService = tokenService;
50
- await this.initializeMessageService();
51
- await this.initializeNotificationDisplayService();
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.unsubscribeForegroundMessage?.();
72
- this.unsubscribeNotificationOpened?.();
47
+ this.unsubscribeMessage?.();
48
+ this.unsubscribeNotificationOpenedApp?.();
73
49
  this.unsubscribeTokenRefresh?.();
74
- this.unsubscribeNotificationEvents?.();
50
+ this.unsubscribeForegroundEvent?.();
75
51
  this.isInitialized = false;
76
52
  this.processedMessageIds.clear();
77
53
  ClixLogger.debug('Notification service cleaned up');
78
54
  }
79
- setMessageHandler(handler) {
80
- this.messageHandler = handler;
81
- }
82
- setBackgroundMessageHandler(handler) {
83
- this.backgroundMessageHandler = handler;
84
- }
85
- setNotificationOpenedHandler(handler) {
86
- this.openedHandler = handler;
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
- setFcmTokenErrorHandler(handler) {
89
- this.fcmTokenErrorHandler = handler;
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
- setAutoHandleLandingUrl(enable) {
92
- this.autoHandleLandingUrl = enable;
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
- async initializeNotificationDisplayService() {
95
- if (Platform.OS === 'android') {
96
- await this.createNotificationChannels();
97
- }
98
- this.setupNotificationEventListeners();
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
- setupNotificationEventListeners() {
109
- this.unsubscribeNotificationEvents = notifee.onForegroundEvent(async event => {
110
- await this.handleNotificationEvent(event);
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 messageId = remoteMessage.messageId;
200
- if (!messageId) {
201
- ClixLogger.warn('No messageId found in foreground message');
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
- if (this.processedMessageIds.has(messageId)) {
205
- ClixLogger.debug('Message already processed, skipping duplicate:', messageId);
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
- const data = remoteMessage.data ?? {};
209
- const clixPayload = this.parseClixPayload(data);
210
- if (clixPayload) {
211
- ClixLogger.debug('Parsed Clix payload:', clixPayload);
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');
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
- async requestPermission() {
230
- const settings = await notifee.requestPermission({
231
- alert: true,
232
- badge: true,
233
- sound: true,
234
- provisional: false,
235
- announcement: false,
236
- carPlay: false,
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
- ClixLogger.debug('Push notification permission status:', settings);
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.deviceService.upsertIsPushPermissionGranted(isGranted);
251
- ClixLogger.debug(`Push permission status reported to server: ${isGranted ? 'granted' : 'denied'}`);
213
+ await this.notificationOpenedAppHandler?.(data);
252
214
  } catch (error) {
253
- ClixLogger.warn('Failed to upsert push permission status', error);
215
+ ClixLogger.error('Notification opened app handler failed', error);
254
216
  }
255
- }
256
- setupTokenRefreshListener() {
257
- this.unsubscribeTokenRefresh = this.messagingService.onTokenRefresh(async token => {
258
- try {
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
- async checkInitialNotification() {
229
+
230
+ /**
231
+ * iOS: app launched from a quit state via a notification
232
+ */
233
+ async handleInitialNotification() {
269
234
  try {
270
- const initialNotification = await this.messagingService.getInitialNotification();
235
+ const initialNotification = await messaging().getInitialNotification();
271
236
  if (initialNotification) {
272
237
  ClixLogger.debug('App launched from notification:', initialNotification.messageId);
273
- await this.handleNotificationTap(initialNotification.data ?? {});
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
- async getAndUpdateToken() {
280
- try {
281
- const token = await this.getCurrentToken();
282
- if (token) {
283
- await this.registerTokenWithServer(token);
284
- }
285
- } catch (error) {
286
- ClixLogger.error('Failed to update push token', error);
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
- async getOrFetchToken() {
290
- if (this.tokenService) {
291
- const savedToken = this.tokenService.getCurrentToken();
292
- if (savedToken) return savedToken;
293
- }
294
- const token = await this.messagingService.getToken();
295
- if (token) {
296
- ClixLogger.debug('Got push token:', token.substring(0, 20) + '...');
297
- this.tokenService?.saveToken(token);
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
- return token;
291
+ await this.handleNotificationEvent(event);
300
292
  }
301
- async saveAndRegisterToken(token) {
302
- if (this.tokenService) {
303
- this.tokenService.saveToken(token);
304
- ClixLogger.debug('New push token saved via TokenService');
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 registerTokenWithServer(token) {
309
- if (this.deviceService) {
310
- await this.deviceService.upsertToken(token);
311
- ClixLogger.debug('Push token registered with server');
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: notificationContent.title,
319
- body: notificationContent.body,
320
- hasImage: !!notificationContent.imageUrl,
321
- imageUrl: notificationContent.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, notificationContent, NotificationService.DEFAULT_CHANNEL.id);
317
+ const notificationConfig = await this.createNotificationConfig(remoteMessage, clixPayload, NotificationService.DEFAULT_CHANNEL.id);
324
318
  await notifee.displayNotification(notificationConfig);
325
- ClixLogger.debug('Notification displayed successfully:', notificationContent.title);
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, notificationContent, channelId) {
340
- const imageUrl = notificationContent.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: notificationContent.title,
344
- body: notificationContent.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: notificationContent.body,
356
- actions: this.createNotificationActions(clixPayload),
349
+ ticker: clixPayload.body,
357
350
  style: {
358
351
  type: AndroidStyle.BIGTEXT,
359
- text: notificationContent.body ?? ''
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 payload = userInfo;
508
- if (userInfo.clix) {
509
- if (typeof userInfo.clix === 'object') {
510
- payload = userInfo.clix;
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
- const toCamel = s => s.replace(/_([a-z])/g, g => (g[1] ?? '').toUpperCase());
516
- const result = {};
517
- if (!payload) return null;
518
- for (const key in payload) {
519
- result[toCamel(key)] = payload[key];
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('Parsed Clix payload result:', result);
522
- if (!result.messageId) return null;
523
-
524
- // Extract title and body into customProperties if they exist
525
- const customProperties = {};
526
- if (result.title) customProperties.title = result.title;
527
- if (result.body) customProperties.body = result.body;
528
- const finalResult = {
529
- ...result,
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('Final Clix payload result:', finalResult);
533
- return new ClixPushNotificationPayload(finalResult);
534
- } catch (error) {
535
- ClixLogger.error('Failed to parse Clix payload', error);
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
- let deviceId = this.storageService.get('clix_device_id');
565
- if (!deviceId) {
566
- ClixLogger.warn('No device ID found in storage, generating new device ID');
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
- const properties = this.extractTrackingProperties(clixPayload);
571
- await this.eventService.trackEvent('PUSH_NOTIFICATION_RECEIVED', properties, messageId);
572
- ClixLogger.debug('PUSH_NOTIFICATION_RECEIVED event tracked in background');
573
- } catch (error) {
574
- ClixLogger.error('Error tracking event in background', error);
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.warn('Foreground message handler failed, displaying notification by default', error);
583
- return true;
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
  }