@inngageregistry/inngage-react 4.0.0 → 4.0.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/dist/Inngage.d.ts CHANGED
@@ -19,7 +19,7 @@ declare class Inngage {
19
19
  private apiService;
20
20
  constructor();
21
21
  static getInstance(): Inngage;
22
- static notificationListener(firebaseListenCallback?: any): void;
22
+ static notificationListener(firebaseListenCallback?: (data: Record<string, string>) => void, imageNotification?: string, colorNotification?: string): void;
23
23
  static subscribe({ appToken, friendlyIdentifier, customFields, phoneNumber, email, }: SubscriptionProps): Promise<Response>;
24
24
  static sendEvent({ eventName, conversionEvent, conversionValue, conversionNotId, eventValues, }: SendEventProps): Promise<Response>;
25
25
  static addUserData(customFields: any): Promise<any>;
package/dist/Inngage.js CHANGED
@@ -18,12 +18,16 @@ const getFirebaseAccess = async () => {
18
18
  };
19
19
  const handleNotificationsPermission = async () => {
20
20
  const options = ['alert', 'badge', 'sound'];
21
+ const isAndroid = Platform.OS === 'android';
21
22
  const apiLevel = Platform.OS === 'android' ? await DeviceInfo.getApiLevel() : 0;
22
23
  let permissionGranted = false;
23
- if (Platform.OS === 'android') {
24
+ if (isAndroid) {
24
25
  if (apiLevel >= API_LEVEL_33) {
25
26
  const result = await RNPermissions.requestNotifications(options);
26
27
  permissionGranted = result.status === RESULTS.GRANTED;
28
+ if (!permissionGranted) {
29
+ console.log('[INNGAGE] Notification permission denied by user on Android (API >= 33)');
30
+ }
27
31
  }
28
32
  else {
29
33
  permissionGranted = true;
@@ -34,32 +38,40 @@ const handleNotificationsPermission = async () => {
34
38
  permissionGranted =
35
39
  status === AuthorizationStatus.AUTHORIZED ||
36
40
  status === AuthorizationStatus.PROVISIONAL;
41
+ if (!permissionGranted) {
42
+ console.log('[INNGAGE] Notification permission denied by user on iOS');
43
+ }
37
44
  if (notifee) {
38
45
  try {
39
46
  await notifee.requestPermission();
40
47
  }
41
- catch { }
48
+ catch {
49
+ // silenciosamente ignora erro do notifee
50
+ }
42
51
  }
43
52
  }
44
- if (permissionGranted) {
45
- return getFirebaseToken();
53
+ if (!permissionGranted) {
54
+ return null;
46
55
  }
47
- throw new Error('Notification permission not granted');
56
+ return getFirebaseToken();
48
57
  };
49
58
  const getFirebaseToken = async () => {
50
59
  let fcmToken = await AsyncStorage.getItem('fcmToken');
51
- if (!fcmToken) {
52
- const isRegistered = (isDeviceRegisteredForRemoteMessages(messagingInstance) ?? false);
53
- if (!isRegistered && typeof registerDeviceForRemoteMessages === 'function') {
54
- await registerDeviceForRemoteMessages(messagingInstance);
55
- }
56
- const newFcmToken = typeof getFcmTokenFromMessaging(messagingInstance) === 'function' ? await getFcmTokenFromMessaging(messagingInstance) : null;
57
- if (newFcmToken) {
58
- await AsyncStorage.setItem('fcmToken', newFcmToken);
59
- return newFcmToken;
60
- }
60
+ if (fcmToken) {
61
+ return fcmToken;
62
+ }
63
+ const isRegistered = isDeviceRegisteredForRemoteMessages(messagingInstance) ?? false;
64
+ if (!isRegistered) {
65
+ await registerDeviceForRemoteMessages(messagingInstance);
66
+ }
67
+ const newFcmToken = (await getFcmTokenFromMessaging(messagingInstance)) ?? null;
68
+ if (!newFcmToken) {
69
+ console.log('[INNGAGE] Failed to obtain FCM token');
70
+ return null;
61
71
  }
62
- return fcmToken || null;
72
+ await AsyncStorage.setItem('fcmToken', newFcmToken);
73
+ console.log('[INNGAGE] FCM token obtained:', newFcmToken);
74
+ return newFcmToken;
63
75
  };
64
76
  class Inngage {
65
77
  constructor() {
@@ -71,9 +83,9 @@ class Inngage {
71
83
  }
72
84
  return Inngage.instance;
73
85
  }
74
- static notificationListener(firebaseListenCallback) {
86
+ static notificationListener(firebaseListenCallback, imageNotification, colorNotification) {
75
87
  try {
76
- InngageNotificationMessage(firebaseListenCallback);
88
+ InngageNotificationMessage(firebaseListenCallback, imageNotification, colorNotification);
77
89
  }
78
90
  catch (e) {
79
91
  console.log(e);
@@ -0,0 +1,3 @@
1
+ export declare const ANDROID_CHANNEL_ID = "inngage_default_channel";
2
+ export declare const messagingInstance: import("@react-native-firebase/messaging").FirebaseMessagingTypes.Module;
3
+ export declare const ensureAndroidChannel: () => Promise<void>;
@@ -0,0 +1,30 @@
1
+ import { Platform } from 'react-native';
2
+ import { getApp } from '@react-native-firebase/app';
3
+ import { getMessaging } from '@react-native-firebase/messaging';
4
+ import notifee, { AndroidImportance } from '@notifee/react-native';
5
+ export const ANDROID_CHANNEL_ID = 'inngage_default_channel';
6
+ const app = getApp();
7
+ export const messagingInstance = getMessaging(app);
8
+ let androidChannelCreated = false;
9
+ export const ensureAndroidChannel = async () => {
10
+ if (Platform.OS !== 'android') {
11
+ return;
12
+ }
13
+ if (androidChannelCreated) {
14
+ return;
15
+ }
16
+ try {
17
+ await notifee.createChannel({
18
+ id: ANDROID_CHANNEL_ID,
19
+ name: 'Inngage Notifications',
20
+ importance: AndroidImportance.HIGH,
21
+ // você pode ajustar sound, vibration, lights etc. se quiser:
22
+ // sound: 'default',
23
+ // vibration: true,
24
+ });
25
+ androidChannelCreated = true;
26
+ }
27
+ catch (error) {
28
+ console.error('[Inngage SDK] Erro ao criar canal do Notifee:', error);
29
+ }
30
+ };
@@ -3,4 +3,6 @@ export declare const useInAppHandler: () => {
3
3
  showInApp: boolean;
4
4
  setShowInApp: import("react").Dispatch<import("react").SetStateAction<boolean>>;
5
5
  };
6
- export declare const InngageNotificationMessage: (firebaseListenCallback?: (data: Record<string, string>) => void) => void;
6
+ export declare const InngageNotificationMessage: (firebaseListenCallback?: (data: Record<string, string>) => void, imageNotification?: string, colorNotification?: string) => {
7
+ unsubscribe: () => void;
8
+ };
@@ -1,21 +1,13 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { AppState, Linking, Platform } from 'react-native';
3
3
  import AsyncStorage from '@react-native-async-storage/async-storage';
4
- import { getApp } from '@react-native-firebase/app';
5
- import { getMessaging, setBackgroundMessageHandler, onMessage, onNotificationOpenedApp, getInitialNotification, } from '@react-native-firebase/messaging';
4
+ import { setBackgroundMessageHandler, onMessage, onNotificationOpenedApp, getInitialNotification, } from '@react-native-firebase/messaging';
6
5
  import InAppBrowser from 'react-native-inappbrowser-reborn';
7
6
  import { InngageProperties } from '../models/inngage_properties';
8
7
  import * as ApiService from '../services/api_services';
9
8
  import notifee, { EventType } from '@notifee/react-native';
10
- const ANDROID_CHANNEL_ID = 'inngage_default';
11
- async function ensureAndroidChannel() {
12
- if (!notifee || Platform.OS !== 'android')
13
- return;
14
- await notifee.createChannel({ id: ANDROID_CHANNEL_ID, name: 'Inngage' });
15
- }
16
- const toStringValue = (v) => typeof v === 'string' ? v : JSON.stringify(v ?? '');
17
- const app = getApp();
18
- const messagingInstance = getMessaging(app);
9
+ import { toStringValue } from '../utils';
10
+ import { ANDROID_CHANNEL_ID, messagingInstance, ensureAndroidChannel, } from './notifications_config';
19
11
  export const messagingHeadlessTask = () => {
20
12
  return setBackgroundMessageHandler(messagingInstance, async (remoteMessage) => {
21
13
  if (InngageProperties.getDebugMode()) {
@@ -45,23 +37,112 @@ export const useInAppHandler = () => {
45
37
  }, []);
46
38
  return { showInApp, setShowInApp };
47
39
  };
48
- export const InngageNotificationMessage = (firebaseListenCallback) => {
49
- const [notificationMessage, setNotificationMessage] = useState(null);
50
- useEffect(() => {
40
+ let currentFirebaseListenCallback;
41
+ let currentImageNotification;
42
+ let currentColorNotification;
43
+ let foregroundUnsubscribe = null;
44
+ let openedAppUnsubscribe = null;
45
+ let initialNotificationHandled = false;
46
+ const normalizeAndroidImageResource = (res) => {
47
+ if (!res)
48
+ return undefined;
49
+ if (res.startsWith('@drawable/')) {
50
+ return res.replace('@drawable/', '');
51
+ }
52
+ if (res.startsWith('@mipmap/')) {
53
+ return res.replace('@mipmap/', '');
54
+ }
55
+ return res;
56
+ };
57
+ const handleNotification = async (remoteMessage) => {
58
+ const data = remoteMessage?.data ?? {};
59
+ const notId = data?.notId;
60
+ const request = {
61
+ notificationRequest: {
62
+ app_token: InngageProperties.appToken,
63
+ id: notId,
64
+ notId: notId,
65
+ },
66
+ };
67
+ const url = data?.url;
68
+ const type = data?.type;
69
+ 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
+ });
80
+ }
81
+ else {
82
+ Linking.openURL(url);
83
+ }
84
+ }
85
+ catch (error) {
86
+ console.error(error);
87
+ }
88
+ }
89
+ else if (type === 'deep') {
90
+ Linking.openURL(url).catch((err) => console.error('Erro ao abrir o link:', err));
91
+ }
92
+ }
93
+ await ApiService.sendNotification(request);
94
+ };
95
+ const handleUniqueRemoteMessage = async (remoteMessage) => {
96
+ try {
97
+ const lastId = await AsyncStorage.getItem('LAST_REMOTE_MESSAGE_ID');
98
+ const newId = remoteMessage?.messageId;
99
+ if (newId && lastId !== newId) {
100
+ await AsyncStorage.setItem('LAST_REMOTE_MESSAGE_ID', newId);
101
+ await handleNotification(remoteMessage);
102
+ }
103
+ }
104
+ catch (e) {
105
+ console.error(e);
106
+ }
107
+ };
108
+ export const InngageNotificationMessage = (firebaseListenCallback, imageNotification, colorNotification) => {
109
+ currentFirebaseListenCallback = firebaseListenCallback;
110
+ currentImageNotification = imageNotification;
111
+ currentColorNotification = colorNotification;
112
+ ensureAndroidChannel().catch(() => { });
113
+ if (!foregroundUnsubscribe) {
51
114
  const handleMessage = async (remoteMessage) => {
52
115
  const notificationData = remoteMessage.data ?? {};
53
116
  if (notificationData.additional_data != null) {
54
117
  await AsyncStorage.setItem('inapp', toStringValue(notificationData.additional_data));
55
118
  }
56
119
  if (notifee) {
57
- const title = remoteMessage.notification?.title ?? notificationData.title ?? '';
58
- const body = remoteMessage.notification?.body ?? notificationData.body ?? '';
120
+ const title = remoteMessage.notification?.title ??
121
+ notificationData.title ??
122
+ '';
123
+ const body = remoteMessage.notification?.body ??
124
+ notificationData.body ??
125
+ '';
126
+ const normalizedImage = normalizeAndroidImageResource(currentImageNotification);
127
+ const resolvedColor = currentColorNotification;
59
128
  await notifee.displayNotification({
60
129
  title,
61
130
  body,
62
- android: Platform.OS === 'android' ? { channelId: ANDROID_CHANNEL_ID } : undefined,
131
+ android: Platform.OS === 'android'
132
+ ? {
133
+ channelId: ANDROID_CHANNEL_ID,
134
+ ...(normalizedImage ? { smallIcon: normalizedImage } : {}),
135
+ ...(resolvedColor ? { color: resolvedColor } : {}),
136
+ }
137
+ : undefined,
63
138
  ios: Platform.OS === 'ios'
64
- ? { foregroundPresentationOptions: { banner: true, list: true, sound: true } }
139
+ ? {
140
+ foregroundPresentationOptions: {
141
+ banner: true,
142
+ list: true,
143
+ sound: true,
144
+ },
145
+ }
65
146
  : undefined,
66
147
  });
67
148
  return notifee.onForegroundEvent(({ type, detail }) => {
@@ -85,86 +166,37 @@ export const InngageNotificationMessage = (firebaseListenCallback) => {
85
166
  if (InngageProperties.getDebugMode()) {
86
167
  console.log('Remote message received in foreground: ', remoteMessage);
87
168
  }
88
- if (firebaseListenCallback && remoteMessage) {
89
- firebaseListenCallback(notificationData);
169
+ if (currentFirebaseListenCallback && remoteMessage) {
170
+ currentFirebaseListenCallback(notificationData);
90
171
  }
91
- setNotificationMessage(remoteMessage);
92
172
  };
93
- ensureAndroidChannel().catch(() => { });
94
- const unsubscribe = onMessage(messagingInstance, handleMessage);
95
- return unsubscribe;
96
- }, [firebaseListenCallback]);
97
- useEffect(() => {
98
- const unsubscribe = onNotificationOpenedApp(messagingInstance, (remoteMessage) => {
173
+ foregroundUnsubscribe = onMessage(messagingInstance, handleMessage);
174
+ }
175
+ if (!openedAppUnsubscribe) {
176
+ openedAppUnsubscribe = onNotificationOpenedApp(messagingInstance, (remoteMessage) => {
99
177
  if (!remoteMessage)
100
178
  return;
101
- firebaseListenCallback?.(remoteMessage.data ?? {});
179
+ currentFirebaseListenCallback?.(remoteMessage.data ?? {});
102
180
  handleNotification(remoteMessage);
103
181
  });
104
- return unsubscribe;
105
- }, [firebaseListenCallback]);
106
- useEffect(() => {
107
- let mounted = true;
182
+ }
183
+ if (!initialNotificationHandled) {
184
+ initialNotificationHandled = true;
108
185
  getInitialNotification(messagingInstance)
109
186
  .then(async (value) => {
110
- if (!mounted || !value)
187
+ if (!value)
111
188
  return;
112
189
  await handleUniqueRemoteMessage(value);
113
190
  })
114
191
  .catch((e) => console.error(e));
115
- return () => {
116
- mounted = false;
117
- };
118
- }, []);
119
- const handleUniqueRemoteMessage = async (remoteMessage) => {
120
- try {
121
- const lastId = await AsyncStorage.getItem('LAST_REMOTE_MESSAGE_ID');
122
- const newId = remoteMessage?.messageId;
123
- if (newId && lastId !== newId) {
124
- await AsyncStorage.setItem('LAST_REMOTE_MESSAGE_ID', newId);
125
- handleNotification(remoteMessage);
126
- }
127
- }
128
- catch (e) {
129
- console.error(e);
130
- }
131
- };
132
- async function handleNotification(remoteMessage) {
133
- const data = remoteMessage?.data ?? {};
134
- const notId = data?.notId;
135
- const request = {
136
- notificationRequest: {
137
- app_token: InngageProperties.appToken,
138
- id: notId,
139
- notId: notId
140
- },
141
- };
142
- const url = data?.url;
143
- const type = data?.type;
144
- if (url) {
145
- if (type === 'inapp') {
146
- try {
147
- if (await InAppBrowser.isAvailable()) {
148
- await InAppBrowser.open(url, {
149
- dismissButtonStyle: 'close',
150
- preferredBarTintColor: '#453AA4',
151
- preferredControlTintColor: 'white',
152
- enableDefaultShare: true,
153
- enableBarCollapsing: true,
154
- });
155
- }
156
- else {
157
- Linking.openURL(url);
158
- }
159
- }
160
- catch (error) {
161
- console.error(error);
162
- }
163
- }
164
- else if (type === 'deep') {
165
- Linking.openURL(url).catch((err) => console.error('Erro ao abrir o link:', err));
166
- }
167
- }
168
- await ApiService.sendNotification(request);
169
192
  }
193
+ return {
194
+ unsubscribe: () => {
195
+ foregroundUnsubscribe?.();
196
+ openedAppUnsubscribe?.();
197
+ foregroundUnsubscribe = null;
198
+ openedAppUnsubscribe = null;
199
+ initialNotificationHandled = false;
200
+ },
201
+ };
170
202
  };
package/dist/utils.d.ts CHANGED
@@ -2,3 +2,4 @@ export declare const formatDate: (timestamp: any) => string | null;
2
2
  export declare const showAlert: (title: string, body: string) => void;
3
3
  export declare const showAlertLink: (title: string, body: string, appName: string, link: string) => Promise<unknown>;
4
4
  export declare const isEmpty: (obj: any) => boolean;
5
+ export declare const toStringValue: (v: unknown) => string;
package/dist/utils.js CHANGED
@@ -23,3 +23,4 @@ export const isEmpty = (obj) => {
23
23
  }
24
24
  return JSON.stringify(obj) === JSON.stringify({});
25
25
  };
26
+ export const toStringValue = (v) => typeof v === 'string' ? v : JSON.stringify(v ?? '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inngageregistry/inngage-react",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
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",