diff --git a/app/actions/actionsTypes.ts b/app/actions/actionsTypes.ts index e05090434..c872099db 100644 --- a/app/actions/actionsTypes.ts +++ b/app/actions/actionsTypes.ts @@ -65,7 +65,7 @@ export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DI export const LOGOUT = 'LOGOUT'; // logout is always success export const DELETE_ACCOUNT = 'DELETE_ACCOUNT'; export const SNIPPETED_MESSAGES = createRequestTypes('SNIPPETED_MESSAGES', ['OPEN', 'READY', 'CLOSE', 'MESSAGES_RECEIVED']); -export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN']); +export const DEEP_LINKING = createRequestTypes('DEEP_LINKING', ['OPEN', 'CLICK_CALL_PUSH_NOTIFICATION']); export const SORT_PREFERENCES = createRequestTypes('SORT_PREFERENCES', ['SET_ALL', 'SET']); export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS'; export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'CLEAR']); diff --git a/app/actions/deepLinking.ts b/app/actions/deepLinking.ts index 78ea929bb..2a5aedfa3 100644 --- a/app/actions/deepLinking.ts +++ b/app/actions/deepLinking.ts @@ -23,3 +23,10 @@ export function deepLinkingOpen(params: Partial): IDeepLinkingOpen { params }; } + +export function deepLinkingClickCallPush(params: any): IDeepLinkingOpen { + return { + type: DEEP_LINKING.CLICK_CALL_PUSH_NOTIFICATION, + params + }; +} diff --git a/app/lib/methods/helpers/goRoom.ts b/app/lib/methods/helpers/goRoom.ts index 0e0be7ce6..006360ff7 100644 --- a/app/lib/methods/helpers/goRoom.ts +++ b/app/lib/methods/helpers/goRoom.ts @@ -118,3 +118,5 @@ export const goRoom = async ({ return navigate({ item, isMasterDetail, popToRoot, ...props }); }; + +export const navigateToRoom = navigate; diff --git a/app/lib/notifications/backgroundNotificationHandler.ts b/app/lib/notifications/backgroundNotificationHandler.ts index 60c6b33ca..4b627c8c7 100644 --- a/app/lib/notifications/backgroundNotificationHandler.ts +++ b/app/lib/notifications/backgroundNotificationHandler.ts @@ -7,57 +7,17 @@ import { BACKGROUND_PUSH_COLOR } from '../constants'; import { store } from '../store/auxStore'; import { deepLinkingClickCallPush } from '../../actions/deepLinking'; +interface NotificationData { + notificationType?: string; + status?: number; + rid?: string; + caller?: { + _id?: string; + name?: string; + }; +} + export const backgroundNotificationHandler = async (): Promise => { - // // 1. get info on the device and the Power Manager settings - // const powerManagerInfo = await notifee.getPowerManagerInfo(); - // if (powerManagerInfo.activity) { - // // 2. ask your users to adjust their settings - // Alert.alert( - // 'Restrictions Detected', - // 'To ensure notifications are delivered, please adjust your settings to prevent the app from being killed', - // [ - // // 3. launch intent to navigate the user to the appropriate screen - // { - // text: 'OK, open settings', - // onPress: notifee.openPowerManagerSettings - // }, - // { - // text: 'Cancel', - // onPress: () => { - // // TODO: handle cancel - // }, - // style: 'cancel' - // } - // ], - // { cancelable: false } - // ); - // } - - // const batteryOptimizationEnabled = await notifee.isBatteryOptimizationEnabled(); - // if (batteryOptimizationEnabled) { - // // 2. ask your users to disable the feature - // Alert.alert( - // 'Restrictions Detected', - // 'To ensure notifications are delivered, please disable battery optimization for the app.', - // [ - // // 3. launch intent to navigate the user to the appropriate screen - // { - // text: 'OK, open settings', - // onPress: notifee.openBatteryOptimizationSettings - // }, - // { - // text: 'Cancel', - // onPress: () => { - // // TODO: handle cancel - // }, - // style: 'cancel' - // } - // ], - // { cancelable: false } - // ); - // } - - // videoConf channel await notifee.createChannel({ id: 'video-conf-call', name: 'Video Call', @@ -67,35 +27,35 @@ export const backgroundNotificationHandler = async (): Promise => { sound: 'ringtone' }); - notifee.onBackgroundEvent( - event => - new Promise(() => { - console.log('event', event); - if (event.detail.pressAction?.id === 'accept' || event.detail.pressAction?.id === 'decline') { - store.dispatch(deepLinkingClickCallPush(event.detail?.notification?.data)); - notifee.cancelNotification( - getNumbersAndLettersOnly(event.detail?.notification?.data.rid + event.detail?.notification?.data.caller._id) - ); - } - }) - ); + notifee.onBackgroundEvent(async event => { + if (event.detail.pressAction?.id === 'accept' || event.detail.pressAction?.id === 'decline') { + const notificationData = event.detail?.notification?.data; + if (typeof notificationData?.caller === 'object' && (notificationData.caller as any)._id) { + store.dispatch(deepLinkingClickCallPush({ ...notificationData, event: event.detail.pressAction?.id })); + await notifee.cancelNotification( + getNumbersAndLettersOnly(`${notificationData.rid}${(notificationData.caller as any)._id}`) + ); + } + } + }); }; -function getNumbersAndLettersOnly(inputString: string) { - // Replace all characters that are NOT (A-Z, a-z, or 0-9) with an empty string +function getNumbersAndLettersOnly(inputString: string): string { return inputString.replace(/[^A-Za-z0-9]/g, ''); } const setBackgroundNotificationHandler = (): void => { - messaging().setBackgroundMessageHandler(async (n: any) => { - const notification = ejson.parse(n.data.ejson); + messaging().setBackgroundMessageHandler(async message => { + const notification: NotificationData = ejson.parse(message?.data?.ejson as string); + if (notification?.notificationType === 'videoconf') { + const id = getNumbersAndLettersOnly(`${notification?.rid}${notification?.caller?._id}`); if (notification.status === 0) { await notifee.displayNotification({ - id: getNumbersAndLettersOnly(notification.rid + notification.caller._id), + id, title: i18n.t('conference_call'), - body: `${i18n.t('Incoming_call_from')} ${notification.caller.name}`, - data: notification, + body: `${i18n.t('Incoming_call_from')} ${notification?.caller?.name}`, + data: notification as { [key: string]: string | number | object }, android: { channelId: 'video-conf-call', category: AndroidCategory.CALL, @@ -126,10 +86,8 @@ const setBackgroundNotificationHandler = (): void => { ongoing: true } }); - } - if (notification.status === 4) { - const notification = ejson.parse(n.data.ejson); - await notifee.cancelNotification(getNumbersAndLettersOnly(notification.rid + notification.caller._id)); + } else if (notification.status === 4) { + await notifee.cancelNotification(id); } } return null; diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 474e4a7f2..9af0cdd8f 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -1,20 +1,21 @@ -import { all, delay, put, select, take, takeLatest } from 'redux-saga/effects'; +import { all, call, delay, put, select, take, takeLatest } from 'redux-saga/effects'; -import UserPreferences from '../lib/methods/userPreferences'; import * as types from '../actions/actionsTypes'; -import { selectServerRequest, serverInitAdd } from '../actions/server'; -import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'; -import database from '../lib/database'; -import EventEmitter from '../lib/methods/helpers/events'; import { appInit, appStart } from '../actions/app'; -import { localAuthenticate } from '../lib/methods/helpers/localAuthentication'; -import { goRoom } from '../lib/methods/helpers/goRoom'; -import { getUidDirectMessage } from '../lib/methods/helpers'; +import { inviteLinksRequest, inviteLinksSetToken } from '../actions/inviteLinks'; import { loginRequest } from '../actions/login'; -import log from '../lib/methods/helpers/log'; +import { selectServerRequest, serverInitAdd } from '../actions/server'; import { RootEnum } from '../definitions'; import { CURRENT_SERVER, TOKEN_KEY } from '../lib/constants'; +import database from '../lib/database'; import { callJitsi, callJitsiWithoutServer, canOpenRoom } from '../lib/methods'; +import { getUidDirectMessage } from '../lib/methods/helpers'; +import EventEmitter from '../lib/methods/helpers/events'; +import { goRoom, navigateToRoom } from '../lib/methods/helpers/goRoom'; +import { localAuthenticate } from '../lib/methods/helpers/localAuthentication'; +import log from '../lib/methods/helpers/log'; +import UserPreferences from '../lib/methods/userPreferences'; +import { videoConfJoin } from '../lib/methods/videoConf'; import { Services } from '../lib/services'; const roomTypes = { @@ -185,7 +186,90 @@ const handleOpen = function* handleOpen({ params }) { } }; +const handleNavigateCallRoom = function* handleNavigateCallRoom({ params }) { + yield put(appStart({ root: RootEnum.ROOT_INSIDE })); + const db = database.active; + const subsCollection = db.get('subscriptions'); + const room = yield subsCollection.find(params.rid); + if (room) { + const isMasterDetail = yield select(state => state.app.isMasterDetail); + yield navigateToRoom({ item: room, isMasterDetail, popToRoot: true }); + const uid = params.caller._id; + const { rid, callId, event } = params; + if (event === 'accept') { + yield call(Services.notifyUser, `${uid}/video-conference`, { + action: 'accepted', + params: { uid, rid, callId } + }); + yield videoConfJoin(callId); + } else if (event === 'decline') { + yield call(Services.notifyUser, `${uid}/video-conference`, { + action: 'rejected', + params: { uid, rid, callId } + }); + } + } +}; + +const handleClickCallPush = function* handleOpen({ params }) { + const serversDB = database.servers; + const serversCollection = serversDB.get('servers'); + + let { host } = params; + + if (host.slice(-1) === '/') { + host = host.slice(0, host.length - 1); + } + + const [server, user] = yield all([ + UserPreferences.getString(CURRENT_SERVER), + UserPreferences.getString(`${TOKEN_KEY}-${host}`) + ]); + + if (server === host && user) { + const connected = yield select(state => state.server.connected); + if (!connected) { + yield localAuthenticate(host); + yield put(selectServerRequest(host)); + yield take(types.LOGIN.SUCCESS); + } + yield handleNavigateCallRoom({ params }); + } else { + // search if deep link's server already exists + try { + const hostServerRecord = yield serversCollection.find(host); + if (hostServerRecord && user) { + yield localAuthenticate(host); + yield put(selectServerRequest(host, hostServerRecord.version, true, true)); + yield take(types.LOGIN.SUCCESS); + yield handleNavigateCallRoom({ params }); + return; + } + } catch (e) { + // do nothing? + } + // if deep link is from a different server + const result = yield Services.getServerInfo(host); + if (!result.success) { + // Fallback to prevent the app from being stuck on splash screen + yield fallbackNavigation(); + return; + } + yield put(appStart({ root: RootEnum.ROOT_OUTSIDE })); + yield put(serverInitAdd(server)); + yield delay(1000); + EventEmitter.emit('NewServer', { server: host }); + if (params.token) { + yield take(types.SERVER.SELECT_SUCCESS); + yield put(loginRequest({ resume: params.token }, true)); + yield take(types.LOGIN.SUCCESS); + yield handleNavigateCallRoom({ params }); + } + } +}; + const root = function* root() { yield takeLatest(types.DEEP_LINKING.OPEN, handleOpen); + yield takeLatest(types.DEEP_LINKING.CLICK_CALL_PUSH_NOTIFICATION, handleClickCallPush); }; export default root;