@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.
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 +252 -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 +101 -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 +318 -533
  83. package/src/services/TokenService.ts +4 -71
  84. package/src/utils/http/HTTPClient.ts +141 -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,64 +131,10 @@ 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);
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.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);
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 messageId = remoteMessage.messageId;
200
- if (!messageId) {
201
- ClixLogger.warn('No messageId found in foreground message');
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
- if (this.processedMessageIds.has(messageId)) {
205
- ClixLogger.debug('Message already processed, skipping duplicate:', messageId);
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
- 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');
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
- 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
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
- 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
- }
210
+ const data = remoteMessage.data ?? {};
249
211
  try {
250
- await this.deviceService.upsertIsPushPermissionGranted(isGranted);
251
- ClixLogger.debug(`Push permission status reported to server: ${isGranted ? 'granted' : 'denied'}`);
212
+ await this.notificationOpenedAppHandler?.(data);
252
213
  } catch (error) {
253
- ClixLogger.warn('Failed to upsert push permission status', error);
214
+ ClixLogger.error('Notification opened app handler failed', error);
254
215
  }
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);
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
- async checkInitialNotification() {
228
+
229
+ /**
230
+ * iOS: app launched from a quit state via a notification
231
+ */
232
+ async handleInitialNotification() {
269
233
  try {
270
- const initialNotification = await this.messagingService.getInitialNotification();
234
+ const initialNotification = await messaging().getInitialNotification();
271
235
  if (initialNotification) {
272
236
  ClixLogger.debug('App launched from notification:', initialNotification.messageId);
273
- await this.handleNotificationTap(initialNotification.data ?? {});
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
- 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);
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
- 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);
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
- return token;
290
+ await this.handleNotificationEvent(event);
300
291
  }
301
- async saveAndRegisterToken(token) {
302
- if (this.tokenService) {
303
- this.tokenService.saveToken(token);
304
- ClixLogger.debug('New push token saved via TokenService');
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 registerTokenWithServer(token) {
309
- if (this.deviceService) {
310
- await this.deviceService.upsertToken(token);
311
- ClixLogger.debug('Push token registered with server');
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: notificationContent.title,
319
- body: notificationContent.body,
320
- hasImage: !!notificationContent.imageUrl,
321
- imageUrl: notificationContent.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, notificationContent, NotificationService.DEFAULT_CHANNEL.id);
316
+ const notificationConfig = await this.createNotificationConfig(remoteMessage, clixPayload, NotificationService.DEFAULT_CHANNEL.id);
324
317
  await notifee.displayNotification(notificationConfig);
325
- ClixLogger.debug('Notification displayed successfully:', notificationContent.title);
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, notificationContent, channelId) {
340
- const imageUrl = notificationContent.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: notificationContent.title,
344
- body: notificationContent.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: notificationContent.body,
356
- actions: this.createNotificationActions(clixPayload),
348
+ ticker: clixPayload.body,
357
349
  style: {
358
350
  type: AndroidStyle.BIGTEXT,
359
- text: notificationContent.body ?? ''
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 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
- }
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
- 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];
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('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
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('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;
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
- 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);
430
+ if (!payload.title) {
431
+ ClixLogger.error('No title found in Clix payload');
432
+ return null;
569
433
  }
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;
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.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);
440
+ ClixLogger.error('Failed to parse Clix payload', error);
441
+ return null;
592
442
  }
593
443
  }
594
444
  }