@inngageregistry/inngage-react 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Inngage.d.ts CHANGED
@@ -14,15 +14,21 @@ interface SendEventProps {
14
14
  conversionNotId?: string;
15
15
  eventValues?: any;
16
16
  }
17
+ interface AddUserDataProps {
18
+ identifier?: string;
19
+ customFields?: Record<string, any>;
20
+ email?: string;
21
+ phoneNumber?: string;
22
+ }
17
23
  declare class Inngage {
18
24
  private static instance;
19
25
  private apiService;
20
26
  constructor();
21
27
  static getInstance(): Inngage;
22
- static notificationListener(firebaseListenCallback?: (data: Record<string, string>) => void, imageNotification?: string, colorNotification?: string): void;
28
+ static notificationListener(firebaseListenCallback?: (data: Record<string, string>) => void, imageNotification?: string, colorNotification?: string, blockDeepLink?: boolean): void;
23
29
  static subscribe({ appToken, friendlyIdentifier, customFields, phoneNumber, email, }: SubscriptionProps): Promise<Response>;
24
30
  static sendEvent({ eventName, conversionEvent, conversionValue, conversionNotId, eventValues, }: SendEventProps): Promise<Response>;
25
- static addUserData(customFields: any): Promise<any>;
31
+ static addUserData({ identifier, customFields, email, phoneNumber }: AddUserDataProps): Promise<Response>;
26
32
  static setDebugMode(value: boolean): void;
27
33
  }
28
34
  export default Inngage;
package/dist/Inngage.js CHANGED
@@ -83,9 +83,9 @@ class Inngage {
83
83
  }
84
84
  return Inngage.instance;
85
85
  }
86
- static notificationListener(firebaseListenCallback, imageNotification, colorNotification) {
86
+ static notificationListener(firebaseListenCallback, imageNotification, colorNotification, blockDeepLink) {
87
87
  try {
88
- InngageNotificationMessage(firebaseListenCallback, imageNotification, colorNotification);
88
+ InngageNotificationMessage(firebaseListenCallback, imageNotification, colorNotification, { blockDeepLink });
89
89
  }
90
90
  catch (e) {
91
91
  console.log(e);
@@ -106,22 +106,22 @@ class Inngage {
106
106
  const subscription = {
107
107
  registerSubscriberRequest: {
108
108
  app_token: appToken,
109
- identifier: friendlyIdentifier,
109
+ identifier: friendlyIdentifier ?? uuid,
110
110
  registration: respToken,
111
111
  platform: DeviceInfo.getSystemName(),
112
112
  sdk: DeviceInfo.getBuildNumber(),
113
- deviceModel: DeviceInfo.getModel(),
114
- deviceManufacturer,
115
- osLocale,
116
- osLanguage,
113
+ device_model: DeviceInfo.getModel(),
114
+ device_manufacturer: deviceManufacturer,
115
+ os_locale: osLocale,
116
+ os_language: osLanguage,
117
117
  os_version: DeviceInfo.getReadableVersion(),
118
118
  app_version: DeviceInfo.getBuildNumber(),
119
- appInstalledIn,
120
- appUpdatedIn,
119
+ app_installed_in: appInstalledIn,
120
+ app_updated_in: appUpdatedIn,
121
121
  uuid,
122
- phone_Number: phoneNumber,
123
- email: email,
124
- customFields: customFields,
122
+ phone_Number: phoneNumber ?? '',
123
+ email: email ?? '',
124
+ custom_field: customFields ?? {},
125
125
  },
126
126
  };
127
127
  return inngage.apiService.subscribe(subscription);
@@ -143,13 +143,15 @@ class Inngage {
143
143
  };
144
144
  return inngage.apiService.sendEvent(request);
145
145
  }
146
- static async addUserData(customFields) {
146
+ static async addUserData({ identifier, customFields, email, phoneNumber }) {
147
147
  const inngage = Inngage.getInstance();
148
148
  const request = {
149
149
  fieldsRequest: {
150
150
  appToken: InngageProperties.appToken,
151
- identifier: InngageProperties.identifier,
152
- customField: customFields,
151
+ identifier: identifier,
152
+ custom_field: customFields,
153
+ email: email,
154
+ phone_number: phoneNumber
153
155
  },
154
156
  };
155
157
  return inngage.apiService.addUserData(request);
@@ -1,3 +1,3 @@
1
1
  export declare const ANDROID_CHANNEL_ID = "inngage_default_channel";
2
- export declare const messagingInstance: import("@react-native-firebase/messaging").FirebaseMessagingTypes.Module;
2
+ export declare const messagingInstance: import("@react-native-firebase/messaging").Messaging;
3
3
  export declare const ensureAndroidChannel: () => Promise<void>;
@@ -18,9 +18,6 @@ export const ensureAndroidChannel = async () => {
18
18
  id: ANDROID_CHANNEL_ID,
19
19
  name: 'Inngage Notifications',
20
20
  importance: AndroidImportance.HIGH,
21
- // você pode ajustar sound, vibration, lights etc. se quiser:
22
- // sound: 'default',
23
- // vibration: true,
24
21
  });
25
22
  androidChannelCreated = true;
26
23
  }
@@ -1,8 +1,12 @@
1
+ type ListenCallback = (data: Record<string, string>) => void;
1
2
  export declare const messagingHeadlessTask: () => void;
2
3
  export declare const useInAppHandler: () => {
3
4
  showInApp: boolean;
4
5
  setShowInApp: import("react").Dispatch<import("react").SetStateAction<boolean>>;
5
6
  };
6
- export declare const InngageNotificationMessage: (firebaseListenCallback?: (data: Record<string, string>) => void, imageNotification?: string, colorNotification?: string) => {
7
+ export declare const InngageNotificationMessage: (firebaseListenCallback?: ListenCallback, imageNotification?: string, colorNotification?: string, options?: {
8
+ blockDeepLink?: boolean;
9
+ }) => {
7
10
  unsubscribe: () => void;
8
11
  };
12
+ export {};
@@ -3,46 +3,48 @@ import { AppState, Linking, Platform } from 'react-native';
3
3
  import AsyncStorage from '@react-native-async-storage/async-storage';
4
4
  import { setBackgroundMessageHandler, onMessage, onNotificationOpenedApp, getInitialNotification, } from '@react-native-firebase/messaging';
5
5
  import InAppBrowser from 'react-native-inappbrowser-reborn';
6
+ import notifee, { EventType } from '@notifee/react-native';
6
7
  import { InngageProperties } from '../models/inngage_properties';
7
8
  import * as ApiService from '../services/api_services';
8
- import notifee, { EventType } from '@notifee/react-native';
9
9
  import { toStringValue } from '../utils';
10
10
  import { ANDROID_CHANNEL_ID, messagingInstance, ensureAndroidChannel, } from './notifications_config';
11
- export const messagingHeadlessTask = () => {
12
- return setBackgroundMessageHandler(messagingInstance, async (remoteMessage) => {
13
- if (InngageProperties.getDebugMode()) {
14
- console.log('INNGAGE BACKGROUND AND CLOSED DATA: ', remoteMessage);
15
- }
16
- const additional = remoteMessage?.data?.additional_data;
17
- if (additional != null) {
18
- await AsyncStorage.setItem('inapp', toStringValue(additional));
19
- }
20
- return Promise.resolve();
21
- });
22
- };
23
- export const useInAppHandler = () => {
24
- const [showInApp, setShowInApp] = useState(false);
25
- useEffect(() => {
26
- const checkInAppData = async () => {
27
- const inAppData = await AsyncStorage.getItem('inapp');
28
- if (inAppData)
29
- setShowInApp(true);
30
- };
31
- checkInAppData();
32
- const subscription = AppState.addEventListener('change', (next) => {
33
- if (next === 'active')
34
- checkInAppData();
35
- });
36
- return () => subscription.remove();
37
- }, []);
38
- return { showInApp, setShowInApp };
39
- };
11
+ const KEY_INAPP = 'inapp';
12
+ const KEY_PENDING_REMOTE_MESSAGES = 'PENDING_REMOTE_MESSAGES_QUEUE';
13
+ const KEY_LAST_DELIVERED_MESSAGE_ID = 'LAST_DELIVERED_MESSAGE_ID';
14
+ const KEY_LAST_CLICK_HANDLED_MESSAGE_ID = 'LAST_CLICK_HANDLED_MESSAGE_ID';
15
+ const KEY_LAST_INITIAL_HANDLED_ID = 'LAST_INITIAL_HANDLED_ID';
16
+ const KEY_LAST_CALLBACK_DELIVERED_ID = 'LAST_CALLBACK_DELIVERED_ID';
17
+ let currentBlockDeepLink = false;
40
18
  let currentFirebaseListenCallback;
41
19
  let currentImageNotification;
42
20
  let currentColorNotification;
43
21
  let foregroundUnsubscribe = null;
44
22
  let openedAppUnsubscribe = null;
45
23
  let initialNotificationHandled = false;
24
+ let notifeeForegroundUnsubscribe = null;
25
+ let appStateSubscription = null;
26
+ const safeJsonParse = (raw) => {
27
+ if (!raw)
28
+ return null;
29
+ try {
30
+ return JSON.parse(raw);
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ };
36
+ const deliverToCallbackOnce = async (msg) => {
37
+ if (!currentFirebaseListenCallback)
38
+ return;
39
+ const id = msg.messageId ?? msg.data?.notId ?? '';
40
+ if (id) {
41
+ const last = await AsyncStorage.getItem(KEY_LAST_CALLBACK_DELIVERED_ID);
42
+ if (last === id)
43
+ return;
44
+ await AsyncStorage.setItem(KEY_LAST_CALLBACK_DELIVERED_ID, id);
45
+ }
46
+ currentFirebaseListenCallback((msg.data ?? {}));
47
+ };
46
48
  const normalizeAndroidImageResource = (res) => {
47
49
  if (!res)
48
50
  return undefined;
@@ -54,6 +56,53 @@ const normalizeAndroidImageResource = (res) => {
54
56
  }
55
57
  return res;
56
58
  };
59
+ const enqueuePendingMessage = async (msg, maxItems = 20) => {
60
+ try {
61
+ const raw = await AsyncStorage.getItem(KEY_PENDING_REMOTE_MESSAGES);
62
+ const queue = safeJsonParse(raw) ?? [];
63
+ queue.push(msg);
64
+ const trimmed = queue.length > maxItems ? queue.slice(queue.length - maxItems) : queue;
65
+ await AsyncStorage.setItem(KEY_PENDING_REMOTE_MESSAGES, JSON.stringify(trimmed));
66
+ }
67
+ catch (e) {
68
+ console.error(e);
69
+ }
70
+ };
71
+ const dequeueAllPendingMessages = async () => {
72
+ try {
73
+ const raw = await AsyncStorage.getItem(KEY_PENDING_REMOTE_MESSAGES);
74
+ const queue = safeJsonParse(raw) ?? [];
75
+ if (queue.length > 0) {
76
+ await AsyncStorage.removeItem(KEY_PENDING_REMOTE_MESSAGES);
77
+ }
78
+ return queue;
79
+ }
80
+ catch (e) {
81
+ console.error(e);
82
+ return [];
83
+ }
84
+ };
85
+ const flushPendingQueue = async () => {
86
+ try {
87
+ if (!currentFirebaseListenCallback)
88
+ return;
89
+ const queue = await dequeueAllPendingMessages();
90
+ if (queue.length === 0)
91
+ return;
92
+ const lastDeliveredId = await AsyncStorage.getItem(KEY_LAST_DELIVERED_MESSAGE_ID);
93
+ for (const item of queue) {
94
+ if (item.messageId && item.messageId === lastDeliveredId)
95
+ continue;
96
+ if (item.messageId) {
97
+ await AsyncStorage.setItem(KEY_LAST_DELIVERED_MESSAGE_ID, item.messageId);
98
+ }
99
+ await deliverToCallbackOnce({ messageId: item.messageId, data: item.data });
100
+ }
101
+ }
102
+ catch (e) {
103
+ console.error(e);
104
+ }
105
+ };
57
106
  const handleNotification = async (remoteMessage) => {
58
107
  const data = remoteMessage?.data ?? {};
59
108
  const notId = data?.notId;
@@ -67,55 +116,111 @@ const handleNotification = async (remoteMessage) => {
67
116
  const url = data?.url;
68
117
  const type = data?.type;
69
118
  if (url) {
70
- if (type === 'inapp') {
71
- try {
72
- if (await InAppBrowser.isAvailable()) {
73
- await InAppBrowser.open(url, {
74
- dismissButtonStyle: 'close',
75
- preferredBarTintColor: '#453AA4',
76
- preferredControlTintColor: 'white',
77
- enableDefaultShare: true,
78
- enableBarCollapsing: true,
79
- });
119
+ if (currentBlockDeepLink) {
120
+ if (InngageProperties.getDebugMode()) {
121
+ console.log('[SDK] blockDeepLink ativo; ignorando abertura de url:', { url, type });
122
+ }
123
+ }
124
+ else {
125
+ if (type === 'inapp') {
126
+ try {
127
+ if (await InAppBrowser.isAvailable()) {
128
+ await InAppBrowser.open(url, {
129
+ dismissButtonStyle: 'close',
130
+ preferredBarTintColor: '#453AA4',
131
+ preferredControlTintColor: 'white',
132
+ enableDefaultShare: true,
133
+ enableBarCollapsing: true,
134
+ });
135
+ }
136
+ else {
137
+ Linking.openURL(url);
138
+ }
80
139
  }
81
- else {
82
- Linking.openURL(url);
140
+ catch (error) {
141
+ console.error(error);
83
142
  }
84
143
  }
85
- catch (error) {
86
- console.error(error);
144
+ else if (type === 'deep') {
145
+ Linking.openURL(url).catch((err) => console.error('Erro ao abrir o link:', err));
87
146
  }
88
147
  }
89
- else if (type === 'deep') {
90
- Linking.openURL(url).catch((err) => console.error('Erro ao abrir o link:', err));
91
- }
92
148
  }
93
149
  await ApiService.sendNotification(request);
94
150
  };
95
151
  const handleUniqueRemoteMessage = async (remoteMessage) => {
96
152
  try {
97
- const lastId = await AsyncStorage.getItem('LAST_REMOTE_MESSAGE_ID');
98
153
  const newId = remoteMessage?.messageId;
99
- if (newId && lastId !== newId) {
100
- await AsyncStorage.setItem('LAST_REMOTE_MESSAGE_ID', newId);
154
+ const lastClickId = await AsyncStorage.getItem(KEY_LAST_CLICK_HANDLED_MESSAGE_ID);
155
+ await deliverToCallbackOnce({ messageId: remoteMessage.messageId, data: remoteMessage.data });
156
+ if (!newId) {
101
157
  await handleNotification(remoteMessage);
158
+ return;
102
159
  }
160
+ if (lastClickId === newId)
161
+ return;
162
+ await AsyncStorage.setItem(KEY_LAST_CLICK_HANDLED_MESSAGE_ID, newId);
163
+ await handleNotification(remoteMessage);
103
164
  }
104
165
  catch (e) {
105
166
  console.error(e);
106
167
  }
107
168
  };
108
- export const InngageNotificationMessage = (firebaseListenCallback, imageNotification, colorNotification) => {
169
+ export const messagingHeadlessTask = () => {
170
+ return setBackgroundMessageHandler(messagingInstance, async (remoteMessage) => {
171
+ if (InngageProperties.getDebugMode()) {
172
+ console.log('INNGAGE BACKGROUND AND CLOSED DATA: ', remoteMessage);
173
+ }
174
+ const additional = remoteMessage?.data?.additional_data;
175
+ if (additional != null) {
176
+ await AsyncStorage.setItem(KEY_INAPP, toStringValue(additional));
177
+ }
178
+ const data = (remoteMessage?.data ?? {});
179
+ await enqueuePendingMessage({
180
+ messageId: remoteMessage?.messageId,
181
+ data,
182
+ ts: Date.now(),
183
+ });
184
+ return Promise.resolve();
185
+ });
186
+ };
187
+ export const useInAppHandler = () => {
188
+ const [showInApp, setShowInApp] = useState(false);
189
+ useEffect(() => {
190
+ const checkInAppData = async () => {
191
+ const inAppData = await AsyncStorage.getItem(KEY_INAPP);
192
+ if (inAppData)
193
+ setShowInApp(true);
194
+ };
195
+ checkInAppData();
196
+ const subscription = AppState.addEventListener('change', (next) => {
197
+ if (next === 'active')
198
+ checkInAppData();
199
+ });
200
+ return () => subscription.remove();
201
+ }, []);
202
+ return { showInApp, setShowInApp };
203
+ };
204
+ export const InngageNotificationMessage = (firebaseListenCallback, imageNotification, colorNotification, options) => {
109
205
  currentFirebaseListenCallback = firebaseListenCallback;
110
206
  currentImageNotification = imageNotification;
111
207
  currentColorNotification = colorNotification;
208
+ currentBlockDeepLink = options?.blockDeepLink ?? false;
112
209
  ensureAndroidChannel().catch(() => { });
210
+ if (!appStateSubscription) {
211
+ appStateSubscription = AppState.addEventListener('change', (next) => {
212
+ if (next === 'active') {
213
+ flushPendingQueue().catch(() => { });
214
+ }
215
+ });
216
+ }
113
217
  if (!foregroundUnsubscribe) {
114
218
  const handleMessage = async (remoteMessage) => {
115
219
  const notificationData = remoteMessage.data ?? {};
116
220
  if (notificationData.additional_data != null) {
117
- await AsyncStorage.setItem('inapp', toStringValue(notificationData.additional_data));
221
+ await AsyncStorage.setItem(KEY_INAPP, toStringValue(notificationData.additional_data));
118
222
  }
223
+ currentFirebaseListenCallback?.(notificationData);
119
224
  if (notifee) {
120
225
  const title = remoteMessage.notification?.title ??
121
226
  notificationData.title ??
@@ -145,39 +250,35 @@ export const InngageNotificationMessage = (firebaseListenCallback, imageNotifica
145
250
  }
146
251
  : undefined,
147
252
  });
148
- return notifee.onForegroundEvent(({ type, detail }) => {
149
- switch (type) {
150
- case EventType.DISMISSED:
151
- if (InngageProperties.getDebugMode()) {
152
- console.log('User dismissed notification', detail.notification);
153
- }
154
- break;
155
- case EventType.PRESS:
156
- handleNotification(remoteMessage);
157
- break;
158
- }
159
- });
160
- }
161
- else {
162
- if (InngageProperties.getDebugMode()) {
163
- console.log('[SDK] Notifee não instalado; mensagem em foreground: ', remoteMessage);
253
+ if (!notifeeForegroundUnsubscribe) {
254
+ notifeeForegroundUnsubscribe = notifee.onForegroundEvent(({ type, detail }) => {
255
+ switch (type) {
256
+ case EventType.DISMISSED:
257
+ if (InngageProperties.getDebugMode()) {
258
+ console.log('User dismissed notification', detail.notification);
259
+ }
260
+ break;
261
+ case EventType.PRESS:
262
+ handleNotification(remoteMessage);
263
+ break;
264
+ }
265
+ });
164
266
  }
165
267
  }
268
+ else if (InngageProperties.getDebugMode()) {
269
+ console.log('[SDK] Notifee não instalado; mensagem em foreground: ', remoteMessage);
270
+ }
166
271
  if (InngageProperties.getDebugMode()) {
167
272
  console.log('Remote message received in foreground: ', remoteMessage);
168
273
  }
169
- if (currentFirebaseListenCallback && remoteMessage) {
170
- currentFirebaseListenCallback(notificationData);
171
- }
172
274
  };
173
275
  foregroundUnsubscribe = onMessage(messagingInstance, handleMessage);
174
276
  }
175
277
  if (!openedAppUnsubscribe) {
176
- openedAppUnsubscribe = onNotificationOpenedApp(messagingInstance, (remoteMessage) => {
278
+ openedAppUnsubscribe = onNotificationOpenedApp(messagingInstance, async (remoteMessage) => {
177
279
  if (!remoteMessage)
178
280
  return;
179
- currentFirebaseListenCallback?.(remoteMessage.data ?? {});
180
- handleNotification(remoteMessage);
281
+ await handleUniqueRemoteMessage(remoteMessage);
181
282
  });
182
283
  }
183
284
  if (!initialNotificationHandled) {
@@ -186,6 +287,15 @@ export const InngageNotificationMessage = (firebaseListenCallback, imageNotifica
186
287
  .then(async (value) => {
187
288
  if (!value)
188
289
  return;
290
+ const id = value.messageId ??
291
+ value.data?.notId ??
292
+ '';
293
+ if (id) {
294
+ const last = await AsyncStorage.getItem(KEY_LAST_INITIAL_HANDLED_ID);
295
+ if (last === id)
296
+ return;
297
+ await AsyncStorage.setItem(KEY_LAST_INITIAL_HANDLED_ID, id);
298
+ }
189
299
  await handleUniqueRemoteMessage(value);
190
300
  })
191
301
  .catch((e) => console.error(e));
@@ -194,8 +304,12 @@ export const InngageNotificationMessage = (firebaseListenCallback, imageNotifica
194
304
  unsubscribe: () => {
195
305
  foregroundUnsubscribe?.();
196
306
  openedAppUnsubscribe?.();
307
+ notifeeForegroundUnsubscribe?.();
308
+ appStateSubscription?.remove();
197
309
  foregroundUnsubscribe = null;
198
310
  openedAppUnsubscribe = null;
311
+ notifeeForegroundUnsubscribe = null;
312
+ appStateSubscription = null;
199
313
  initialNotificationHandled = false;
200
314
  },
201
315
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inngageregistry/inngage-react",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "Inngage Plugin for React Native applications for marketing campaign optimization using Push Notification.",
5
5
  "author": "Inngage Developer",
6
6
  "license": "ISC",