You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Here is my React Context Provider with OneSignal code:
import { useMutation } from '@tanstack/react-query';
import {
setNotificationHandler,
addNotificationReceivedListener,
addNotificationResponseReceivedListener,
removeNotificationSubscription,
setNotificationChannelAsync,
getExpoPushTokenAsync,
Subscription,
ExpoPushToken,
Notification,
AndroidImportance,
} from 'expo-notifications';
import React, {
createContext,
FC,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { Platform } from 'react-native';
import { OneSignal } from 'react-native-onesignal';
import { v4 as uuidv4 } from 'uuid';
import { UserResponse } from '@app-types/user';
import { DEVICE_ID, HTTP_ENDPOINTS } from '@constants';
import { useAppState } from '@hooks/app-state';
import { useAuth } from '@hooks/auth';
import { useSyncStorage } from '@hooks/sync-storage';
import {
captureException,
EXCEPTION_TAG_TYPES,
getEnvironmentConfig,
getErrorMessage,
httpClient,
} from '@utils';
import {
PushNotificationContextType,
PushNotificationProviderProps,
} from './push-notification.type';
// The below method is invoked when a user receives a push notification
setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
async function getExpoPushNotificationToken() {
let token: ExpoPushToken | null = null;
try {
if (Platform.OS === 'android') {
await setNotificationChannelAsync('default', {
name: 'default',
importance: AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
token = await getExpoPushTokenAsync({
projectId: getEnvironmentConfig().easProjectId,
});
} catch (error) {
console.error('Failed to set notification channel', getErrorMessage(error));
throw error;
}
return token;
}
const PushNotificationContext = createContext<PushNotificationContextType>({
notification: null,
notificationEnabled: false,
});
const PushNotificationProvider: FC<PushNotificationProviderProps> = ({
children,
}) => {
const [notification, setNotification] = useState<Notification | null>(null);
const notificationListener = useRef<Subscription>();
const responseListener = useRef<Subscription>();
const { get, set } = useSyncStorage();
const { setUser, setAdminUser, user, adminUser } = useAuth();
const { appState } = useAppState();
const userRef = useRef(adminUser ?? user);
const previousAppState = useRef(appState);
const [notificationEnabled, setNotificationEnabled] = useState(false);
// Function to generate a unique device ID
const generateDeviceId = async () => {
let deviceId = get<string>(DEVICE_ID);
if (!deviceId) {
// Generate a new unique ID using device information and a random string
deviceId = uuidv4();
// Store the device ID in AsyncStorage for future use
try {
await set(DEVICE_ID, deviceId);
} catch (error) {
throw new Error('Failed to store device ID in AsyncStorage');
}
}
return deviceId;
};
const { mutateAsync } = useMutation({
mutationFn: async (token: string) => {
const deviceId = await generateDeviceId();
return await httpClient.post(HTTP_ENDPOINTS.notificationTokens, {
notification_token: {
token,
platform: Platform.OS,
provider: 'expo',
device_id: deviceId,
},
});
},
});
// Function to check if the device is already registered for push notification
const isDeviceRegistered = useCallback(() => {
const deviceId = get<string>(DEVICE_ID);
const hasDeviceId = userRef.current?.notification_tokens?.find(
token => token.device_id === deviceId,
);
return !!deviceId && !!hasDeviceId;
}, [get]);
// Function to save the push notification token to the server
const savePushNotificationToken = useCallback(async () => {
try {
if (isDeviceRegistered()) {
return;
}
const token = await getExpoPushNotificationToken();
if (token?.data) {
// Generate a new device ID if the device is not already registered
// and save the push notification token to the server
await mutateAsync(token.data);
if (adminUser) {
// Fetch the admin user profile after saving the push notification token
const response = await httpClient.get<UserResponse>(
HTTP_ENDPOINTS.userProfile,
{
headers: {
'X-Use-Admin-Token': 'true',
},
},
);
setAdminUser(response.data.result);
} else {
// Fetch the user profile after saving the push notification token
const response = await httpClient.get<UserResponse>(
HTTP_ENDPOINTS.userProfile,
);
// Update the user context with the latest user profile
setUser(response.data.result);
}
}
} catch (error) {
console.error(
'Failed to initialise push notification!',
getErrorMessage(error),
);
captureException(getErrorMessage(error), {
extra: {
context: 'expo-push-notification',
method: 'savePushNotificationToken',
},
tags: {
type: EXCEPTION_TAG_TYPES.DEVICE,
},
});
}
}, [adminUser, isDeviceRegistered, mutateAsync, setAdminUser, setUser]);
// Function to handle the complete (OneSignal and Expo) notification permission flow
const handleNotificationPermissionFlow = useCallback(async () => {
try {
// Check if the user has granted permission for push notification
const hasGrantedPermission =
await OneSignal.Notifications.getPermissionAsync();
setNotificationEnabled(hasGrantedPermission);
if (hasGrantedPermission) {
return false;
}
// Check if the user can request permission for push notification
const canRequestPermission =
await OneSignal.Notifications.canRequestPermission();
// If the user can request permission, then request for permission by showing the OneSignal prompt
if (canRequestPermission) {
return OneSignal.InAppMessages.addTrigger('user_logged_in', 'true');
}
} catch (error) {
captureException(getErrorMessage(error), {
extra: {
context: 'push-notification',
method: 'handleNotificationPermissionFlow',
},
tags: {
type: EXCEPTION_TAG_TYPES.DEVICE,
},
});
}
}, []);
// Function to handle the notification permission flow when the app state changes
const onAppStateChange = useCallback(async () => {
if (previousAppState.current === 'background' && appState === 'active') {
void handleNotificationPermissionFlow();
}
previousAppState.current = appState;
}, [appState, handleNotificationPermissionFlow]);
// Function to handle the permission change event for push notification when
// OneSignal.Notification.addEventListener('permissionChange') is triggered
const onPermissionChange = useCallback(
(enabled: boolean) => {
// If the user has granted permission for push notification, then save the push notification token to the server
if (enabled) {
void savePushNotificationToken();
}
setNotificationEnabled(enabled);
},
[savePushNotificationToken],
);
/**
* Add an event listener to listen for the permission change event
*/
useEffect(() => {
OneSignal.Notifications.addEventListener(
'permissionChange',
onPermissionChange,
);
return () => {
OneSignal.Notifications.removeEventListener(
'permissionChange',
onPermissionChange,
);
};
}, [onPermissionChange]);
// Handle the notification permission flow when the component mounts
useEffect(() => {
void handleNotificationPermissionFlow();
}, [handleNotificationPermissionFlow]);
// Add event listeners to listen for notification and notification response events
useEffect(() => {
notificationListener.current = addNotificationReceivedListener(notif => {
setNotification(notif);
});
responseListener.current = addNotificationResponseReceivedListener(
response => {
console.info('addNotificationResponseReceivedListener', response);
},
);
return () => {
if (notificationListener.current) {
removeNotificationSubscription(notificationListener.current);
}
if (responseListener.current) {
removeNotificationSubscription(responseListener.current);
}
};
}, []);
// Update the user reference when the user context changes
// We are using a ref to store the user context to avoid unnecessary re-renders and calls to the handleNotificationPermissionFlow function
useEffect(() => {
userRef.current = adminUser ?? user;
}, [adminUser, user]);
// Handle the notification permission flow when the app state changes
useEffect(() => {
void onAppStateChange();
}, [onAppStateChange]);
return (
<PushNotificationContext.Provider
value={{
notification,
notificationEnabled,
}}
>
{children}
</PushNotificationContext.Provider>
);
};
export { PushNotificationContext, PushNotificationProvider };
With OneSignal configured, I receive the notifications from Expo Notification SDK and OneSignal as expected but on tapping the notification, the following lines would never execute -
They are the listeners for Expo notifications so we could register the payload.
Now, I uninstalled OneSignal, removed the OneSignal config and changed my provider code to -
import { useMutation } from '@tanstack/react-query';
import {
setNotificationHandler,
addNotificationReceivedListener,
addNotificationResponseReceivedListener,
removeNotificationSubscription,
setNotificationChannelAsync,
getExpoPushTokenAsync,
Subscription,
ExpoPushToken,
Notification,
AndroidImportance,
getPermissionsAsync,
IosAuthorizationStatus,
requestPermissionsAsync,
} from 'expo-notifications';
import React, {
createContext,
FC,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { Platform } from 'react-native';
// import { OneSignal } from 'react-native-onesignal';
import { v4 as uuidv4 } from 'uuid';
import { UserResponse } from '@app-types/user';
import { DEVICE_ID, HTTP_ENDPOINTS } from '@constants';
import { useAppState } from '@hooks/app-state';
import { useAuth } from '@hooks/auth';
import { useSyncStorage } from '@hooks/sync-storage';
import {
captureException,
EXCEPTION_TAG_TYPES,
getEnvironmentConfig,
getErrorMessage,
httpClient,
} from '@utils';
import {
PushNotificationContextType,
PushNotificationProviderProps,
} from './push-notification.type';
// The below method is invoked when a user receives a push notification
setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
async function getExpoPushNotificationToken() {
let token: ExpoPushToken | null = null;
try {
if (Platform.OS === 'android') {
await setNotificationChannelAsync('default', {
name: 'default',
importance: AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
});
}
token = await getExpoPushTokenAsync({
projectId: getEnvironmentConfig().easProjectId,
});
} catch (error) {
console.error('Failed to set notification channel', getErrorMessage(error));
throw error;
}
return token;
}
const PushNotificationContext = createContext<PushNotificationContextType>({
notification: null,
notificationEnabled: false,
});
const PushNotificationProvider: FC<PushNotificationProviderProps> = ({
children,
}) => {
const [notification, setNotification] = useState<Notification | null>(null);
const notificationListener = useRef<Subscription>();
const responseListener = useRef<Subscription>();
const { get, set } = useSyncStorage();
const { setUser, setAdminUser, user, adminUser } = useAuth();
const { appState } = useAppState();
const userRef = useRef(adminUser ?? user);
const previousAppState = useRef(appState);
const [notificationEnabled, setNotificationEnabled] = useState(false);
// Function to generate a unique device ID
const generateDeviceId = async () => {
let deviceId = get<string>(DEVICE_ID);
if (!deviceId) {
// Generate a new unique ID using device information and a random string
deviceId = uuidv4();
// Store the device ID in AsyncStorage for future use
try {
await set(DEVICE_ID, deviceId);
} catch (error) {
throw new Error('Failed to store device ID in AsyncStorage');
}
}
return deviceId;
};
const { mutateAsync } = useMutation({
mutationFn: async (token: string) => {
const deviceId = await generateDeviceId();
return await httpClient.post(HTTP_ENDPOINTS.notificationTokens, {
notification_token: {
token,
platform: Platform.OS,
provider: 'expo',
device_id: deviceId,
},
});
},
});
// Function to check if the device is already registered for push notification
const isDeviceRegistered = useCallback(() => {
const deviceId = get<string>(DEVICE_ID);
const hasDeviceId = userRef.current?.notification_tokens?.find(
token => token.device_id === deviceId,
);
return !!deviceId && !!hasDeviceId;
}, [get]);
// Function to save the push notification token to the server
const savePushNotificationToken = useCallback(async () => {
try {
if (isDeviceRegistered()) {
return;
}
const token = await getExpoPushNotificationToken();
if (token?.data) {
// Generate a new device ID if the device is not already registered
// and save the push notification token to the server
await mutateAsync(token.data);
if (adminUser) {
// Fetch the admin user profile after saving the push notification token
const response = await httpClient.get<UserResponse>(
HTTP_ENDPOINTS.userProfile,
{
headers: {
'X-Use-Admin-Token': 'true',
},
},
);
setAdminUser(response.data.result);
} else {
// Fetch the user profile after saving the push notification token
const response = await httpClient.get<UserResponse>(
HTTP_ENDPOINTS.userProfile,
);
// Update the user context with the latest user profile
setUser(response.data.result);
}
}
} catch (error) {
console.error(
'Failed to initialise push notification!',
getErrorMessage(error),
);
captureException(getErrorMessage(error), {
extra: {
context: 'expo-push-notification',
method: 'savePushNotificationToken',
},
tags: {
type: EXCEPTION_TAG_TYPES.DEVICE,
},
});
}
}, [adminUser, isDeviceRegistered, mutateAsync, setAdminUser, setUser]);
// Function to handle the complete (OneSignal and Expo) notification permission flow
const handleNotificationPermissionFlow = useCallback(async () => {
try {
// Check if the user has granted permission for push notification
// const hasGrantedPermission =
// await OneSignal.Notifications.getPermissionAsync();
const permissionSettings = await getPermissionsAsync();
const hasGrantedPermission =
permissionSettings.granted ||
permissionSettings.ios?.status === IosAuthorizationStatus.PROVISIONAL;
setNotificationEnabled(hasGrantedPermission);
if (hasGrantedPermission) {
return false;
}
const { status } = await requestPermissionsAsync();
if (status === 'granted') {
setNotificationEnabled(true);
await savePushNotificationToken();
}
// Check if the user can request permission for push notification
// const canRequestPermission =
// await OneSignal.Notifications.canRequestPermission();
// If the user can request permission, then request for permission by showing the OneSignal prompt
// if (canRequestPermission) {
// return OneSignal.InAppMessages.addTrigger('user_logged_in', 'true');
// }
} catch (error) {
captureException(getErrorMessage(error), {
extra: {
context: 'push-notification',
method: 'handleNotificationPermissionFlow',
},
tags: {
type: EXCEPTION_TAG_TYPES.DEVICE,
},
});
}
}, [savePushNotificationToken]);
// Function to handle the notification permission flow when the app state changes
const onAppStateChange = useCallback(async () => {
if (previousAppState.current === 'background' && appState === 'active') {
void handleNotificationPermissionFlow();
}
previousAppState.current = appState;
}, [appState, handleNotificationPermissionFlow]);
// Function to handle the permission change event for push notification when
// OneSignal.Notification.addEventListener('permissionChange') is triggered
// const onPermissionChange = useCallback(
// (enabled: boolean) => {
// // If the user has granted permission for push notification, then save the push notification token to the server
// if (enabled) {
// void savePushNotificationToken();
// }
// setNotificationEnabled(enabled);
// },
// [savePushNotificationToken],
// );
/**
* Add an event listener to listen to OneSignal events
*/
// useEffect(() => {
// OneSignal.Notifications.addEventListener(
// 'permissionChange',
// onPermissionChange,
// );
// return () => {
// OneSignal.Notifications.removeEventListener(
// 'permissionChange',
// onPermissionChange,
// );
// };
// }, [onPermissionChange]);
// Handle the notification permission flow when the component mounts
useEffect(() => {
void handleNotificationPermissionFlow();
}, [handleNotificationPermissionFlow]);
// Add event listeners to listen for notification and notification response events
useEffect(() => {
notificationListener.current = addNotificationReceivedListener(response => {
console.info('addNotificationReceivedListener', response);
setNotification(response);
});
responseListener.current = addNotificationResponseReceivedListener(
response => {
console.info('addNotificationResponseReceivedListener', response);
},
);
return () => {
if (notificationListener.current) {
removeNotificationSubscription(notificationListener.current);
}
if (responseListener.current) {
removeNotificationSubscription(responseListener.current);
}
};
}, []);
// Update the user reference when the user context changes
// We are using a ref to store the user context to avoid unnecessary re-renders and calls to the handleNotificationPermissionFlow function
useEffect(() => {
userRef.current = adminUser ?? user;
}, [adminUser, user]);
// Handle the notification permission flow when the app state changes
useEffect(() => {
void onAppStateChange();
}, [onAppStateChange]);
return (
<PushNotificationContext.Provider
value={{
notification,
notificationEnabled,
}}
>
{children}
</PushNotificationContext.Provider>
);
};
export { PushNotificationContext, PushNotificationProvider };
And, now the listeners are working as expected and I receive the payload. Seems like the OneSignal package is blocking the payload from reaching the listeners.
Steps to reproduce?
1. Install and configure [Expo Notifications](https://docs.expo.dev/versions/latest/sdk/notifications/#installation) as per the instructions in the Expo documentation.
2. Install and configure OneSignal on Expo.
3. Send a push notification using [Expo Notifications Tool](https://expo.dev/notifications). You can get the Expo Push Token using `getExpoPushTokenAsync`.
4. Use the context provider shared above if you'd like to.
5. The Expo notification listeners should receive the payload, but they do not.
6. Now, uninstall and remove all the OneSignal code, and everything works as expected.
It should work seamlessly and listen to Expo Notifications with OneSignal congfigures. At present, OneSignal doesn't pass the payload to the listener.
React Native OneSignal SDK version
5.1.3
Which platform(s) are affected?
iOS
Android
Relevant log output
No response
Code of Conduct
I agree to follow this project's Code of Conduct
The text was updated successfully, but these errors were encountered:
rshettyawaken
changed the title
[Bug]: Expo notification listeners stop working when I configure and start using OneSignal
[Bug]: Expo notification listeners stop working when used alongside OneSignal
Sep 18, 2024
@jennantilla I think the documentation outlines the issue related to OneSignal notifications. My issue is more related to not being able to listen to Push Notifications sent by another package called Expo Notitications. I wanna use both the packages side by side but I can't because the OneSignal SDK Push Notification listener blocks Expo Notification from listening.
What happened?
Here is my React Context Provider with OneSignal code:
With OneSignal configured, I receive the notifications from Expo Notification SDK and OneSignal as expected but on tapping the notification, the following lines would never execute -
They are the listeners for Expo notifications so we could register the payload.
Now, I uninstalled OneSignal, removed the OneSignal config and changed my provider code to -
And, now the listeners are working as expected and I receive the payload. Seems like the OneSignal package is blocking the payload from reaching the listeners.
Steps to reproduce?
What did you expect to happen?
The code below acts as a listener for Expo Push Notifications -
It should work seamlessly and listen to Expo Notifications with OneSignal congfigures. At present, OneSignal doesn't pass the payload to the listener.
React Native OneSignal SDK version
5.1.3
Which platform(s) are affected?
Relevant log output
No response
Code of Conduct
The text was updated successfully, but these errors were encountered: