* MM_35115: ADDED select_server screen * MM_35115: ADDED select_server screen * MM_35115: ADDED files on which select_server is dependent * MM_35115: ADDED react-native-button * MM_35115: Fixing TS issues [IN PROGRESS] * MM_35115: Started withObservables [IN PROGRESS] * MM_35115: Started withObservables [IN PROGRESS] * MM_35115: withObservables - defaulting when no connection is available [IN PROGRESS] * MM_35115: withObservables - some code clean up [IN PROGRESS] * MM_35115: withObservables - some code clean up [IN PROGRESS] * MM_35115: withObservables - some code clean up [IN PROGRESS] * MM_35115: Substituting mapDispatchToProps [IN PROGRESS] * MM_35115: Substituting mapDispatchToProps [IN PROGRESS] * MM_35115: Substituting mapDispatchToProps [IN PROGRESS] * MM_35115: Removed resetPing action [IN PROGRESS] * MM_35115: ADDED app/client * MM_35115: Preparing scheduleExpiredNotification * MM_35115: Adding some todos * Server & LoginOptions * Use default server if available and autoconnect if configured * Fix login header & manual server url * MM_35115: Login Options[IN PROGRESS] * MM_35115: Login screen - email [IN PROGRESS] * MM_35115: Login screen - email [IN PROGRESS] * MM_35115: Login screen - email - login api call [IN PROGRESS] * MM_35115: Login screen - email - login api call [IN PROGRESS] * MM_35115: Login screen - email - saving to server db [IN PROGRESS] * MM_35115: Login screen - email - saving to System, Preferences to db [IN PROGRESS] * MM_35115: Login screen - enforcing unique check on System entity [IN PROGRESS] * MM_35115: Login screen - writing TeamMembership [IN PROGRESS] * MM_35115: Login screen - writing Teams [IN PROGRESS] * MM_35115: Login screen [IN PROGRESS] * MM_35115: Login screen- Refactored DataOperator handlers [IN PROGRESS] * MM_35115: Login screen - Proper clean up [IN PROGRESS] * MM_35115: Login screen - completeLogin [IN PROGRESS] * MM_35115: Improving DataOperator * MM_35115: Improving DataOperator * MM_35115: 80% DONE - login with email and password - some todos * MM_35115: 80% DONE - login with email and password - some todos * MM_35115: 80% DONE - login with email and password - some todos * MM_35115: Removing unused app/queries folder * MM_35115: Clean up * MM_35115: Clean up * MM_35115: Clean up * MM_35115: Clean up * MM_35115: Clean up * MM_35115: Adding roles for MYTEAM * MM_35115: Code clean up * MM_35115: Code clean up * MM_35115: Code clean up * MM_35115: Adding rn-fetch-blob for Android * MM_35115: Code clean up * MM_35115: Code clean up * MM_35115: Added test setup * MM_35115: Fix database utils * MM_35115: ADDED loadRolesIfNeeded * MM_35115: Fix TS issue * MM_35115: ADDED Tests setup * MM_35115: Fix TS issues * MM_35115: Fix TS issues * MM_35115: Fix TS issues * MM_35115: Added alternative to site name * MM_35115: Added alternative to site name * MM_35115: Removed hardcoded values * MM_35115: Clean up * MM_35115 - Fixed Android platform check instead of hermes * MM_35115 - Replaced emptyErrorHandlingFunction with emptyFunction * MM_35115 : Implemented TS fixes * Update index.ts * MM-35115 - Fix react-test-renderer issue * MM_35115 - Optimizing DatabaseManager * MM_35115 : Implemented getDatabaseConnection * MM_35115 : Refactoring set/getActiveDatabase to use flag record * MM_35115 : Refactored active database to use flag in Global entity * MM_35115 : Updated manual database manager test * MM_35115 : Fix operator/utils/test * MM_35115 : Fix for base_handler * MM_35115 : Fix test issues with Handlers * MM_35115 : Fix test issues with prepareRecords * MM_35115 : Fix wrapper test issue * MM_35115 : Updated getMostRecentServerConnection to return the serverUrl as well as the connection * MM_35115 : Refactored the way we call DataOperator * MM_35115 : Updated database manager mock * Add getMostRecentServerUrl function (#5440) * fix: add getMostRecentServerUrl func * fix: add ts and tsx to editorconfig * fix: rename functions * fix: return type * Fix unit test setup * fix login screen unit tests * MM-36205 [GEKIDOU] Login Flow SSO (#5454) * MM_35115: Starting LoginOptions SSO * MM_36205: SSO [IN PROGRESS] * MM_36205 : SSO [ IN PROGRESS ] * Update sso_with_redirect_url.tsx * MM_36205 : SSO Tests [ IN PROGRESS ] * MM_36205 : Passing serverUrl to SSO screen * Update sso.test.tsx * Fix ViewTypes imports and keyMirror method * MM_36205 : Code clean up * Fix : Clean up imports * Update: Aligning with PR 5452 * Fix: AndroidManifest file to include redirection ofr scheme mmauthbeta * refactor: SSO Login method via Gitlab now navigates to Channel screen * refactor: SSO Login without redirectURL is also working * feat: SSO - main test completed * feat: ADDED test for sso_with_redirect_url * fix : eslint correction * fix: Updated Loading component name * fix : code clean up from reviews * fix: reviews check * fix: Added mmauthbeta into info.plist * Revert "fix: Added mmauthbeta into info.plist" This reverts commit d87cc23f5b451a49d9ffa9de4745224b7ecfc977. * Update Info.plist * Update AppDelegate.m * feat: ADDED Forgot Password - Test [ IN PROGRESS ] * feat: Forgot Password - Completed & Tested * fix: Including MFA screen [ IN PROGRESS ] * MFA - Properly tested * Properly testing forgot_password screen * Fix login.test.tsx * Fix SSO method calls chain * Update index.tsx * Sort imports for sceen/navigation * fix: Reviews * Update signing + act in test * Removed todo comment on MFA * feedback review * fix login tests Co-authored-by: Avinash Lingaloo <> Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * App initialization refactor (#5430) * fix: initial init refactor * fix: await isServerPresent * fix: more refactor * fix: move out launch functions * fix: remove comment * fix: update credential functions * fix: refactor launch functions * fix: deep link parsing * fix: lint change * fix: update deeplink and notification handlers * fix: indentation * fix: add relaunchApp * fix eslint * refactor launchProps and autoconnect server for deeplink * fix: use undefined * fix: define OptionalLaunchProps * fix: Android - handle server URL in push notification * fix: rename func * fix: use boolean launchError instead * fix: use DatabaseModule * fix: use DatabaseHelper instead * fix: remove unnecessary null check * fix: iOS - support for serverUrl * fix: iOS - extract serverUrl in reply action * fix: iOS - expose objc specific func * fix: remove unnecessary deviceToken param * fix: return if device is untrusted Co-authored-by: Elias Nahum <nahumhbl@gmail.com> * fix: bye bye modulePaths * fix: ios build * chore: remove unused aliases from babel.config * chore: fix dependency format in package-lock.json * chore: remove transparent window background color for android AppTheme * chore: remove mattermost.js and use index.ts as app entry * fix: login flow screens theme * fix: Launch types * chore: remove OptionalLaunchProps type * fix: url utils unit tests * chore: update en.json Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Avinash Lingaloo <> Co-authored-by: Elias Nahum <nahumhbl@gmail.com> Co-authored-by: Miguel Alatzar <migbot@users.noreply.github.com>
247 lines
8.7 KiB
TypeScript
247 lines
8.7 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {DEFAULT_LOCALE, getLocalizedMessage, t} from '@i18n';
|
|
import {getActiveServerDatabase} from '@utils/database';
|
|
import {AppState, AppStateStatus, DeviceEventEmitter, EmitterSubscription, Platform} from 'react-native';
|
|
import DeviceInfo from 'react-native-device-info';
|
|
import {
|
|
Notification,
|
|
NotificationAction,
|
|
NotificationBackgroundFetchResult,
|
|
NotificationCategory,
|
|
NotificationCompletion,
|
|
Notifications,
|
|
NotificationTextInput,
|
|
Registered,
|
|
} from 'react-native-notifications';
|
|
|
|
import {Device, Navigation, View} from '@constants';
|
|
import Operator from '@database/operator';
|
|
import {getLaunchPropsFromNotification, relaunchApp} from '@init/launch';
|
|
import NativeNotifications from '@notifications';
|
|
import {showOverlay} from '@screens/navigation';
|
|
import {IsolatedEntities} from '@typings/database/enums';
|
|
|
|
const CATEGORY = 'CAN_REPLY';
|
|
const REPLY_ACTION = 'REPLY_ACTION';
|
|
const NOTIFICATION_TYPE = {
|
|
CLEAR: 'clear',
|
|
MESSAGE: 'message',
|
|
SESSION: 'session',
|
|
};
|
|
|
|
//todo: Do we need Ephemeral store? Should we refactor this file ?
|
|
|
|
class PushNotifications {
|
|
configured = false;
|
|
pushNotificationListener: EmitterSubscription | undefined;
|
|
|
|
constructor() {
|
|
Notifications.registerRemoteNotifications();
|
|
Notifications.events().registerNotificationOpened(this.onNotificationOpened);
|
|
Notifications.events().registerRemoteNotificationsRegistered(this.onRemoteNotificationsRegistered);
|
|
Notifications.events().registerNotificationReceivedBackground(this.onNotificationReceivedBackground);
|
|
Notifications.events().registerNotificationReceivedForeground(this.onNotificationReceivedForeground);
|
|
AppState.addEventListener('change', this.onAppStateChange);
|
|
}
|
|
|
|
cancelAllLocalNotifications = () => {
|
|
Notifications.cancelAllLocalNotifications();
|
|
};
|
|
|
|
clearNotifications = () => {
|
|
this.setApplicationIconBadgeNumber(0);
|
|
|
|
// TODO: Only cancel the local notifications that belong to this server
|
|
this.cancelAllLocalNotifications();
|
|
};
|
|
|
|
clearChannelNotifications = async (channelId: string) => {
|
|
const notifications = await NativeNotifications.getDeliveredNotifications();
|
|
if (Platform.OS === 'android') {
|
|
const notificationForChannel = notifications.find(
|
|
(n: NotificationWithChannel) => n.channel_id === channelId,
|
|
);
|
|
if (notificationForChannel) {
|
|
NativeNotifications.removeDeliveredNotifications(
|
|
notificationForChannel.identifier,
|
|
channelId,
|
|
);
|
|
}
|
|
} else {
|
|
const ids: string[] = [];
|
|
const badgeCount = notifications.length;
|
|
|
|
// TODO: Set the badgeCount with default database mention count aggregate
|
|
|
|
for (const notification of notifications) {
|
|
if (notification.channel_id === channelId) {
|
|
ids.push(notification.identifier);
|
|
}
|
|
}
|
|
|
|
if (ids.length) {
|
|
NativeNotifications.removeDeliveredNotifications(ids);
|
|
}
|
|
|
|
this.setApplicationIconBadgeNumber(badgeCount);
|
|
}
|
|
};
|
|
|
|
createReplyCategory = () => {
|
|
const locale = DEFAULT_LOCALE; // TODO: Get the current user locale to replace the old getCurrentLocale(state);
|
|
|
|
const replyTitle = getLocalizedMessage(locale, t('mobile.push_notification_reply.title'));
|
|
const replyButton = getLocalizedMessage(locale, t('mobile.push_notification_reply.button'));
|
|
const replyPlaceholder = getLocalizedMessage(locale, t('mobile.push_notification_reply.placeholder'));
|
|
const replyTextInput: NotificationTextInput = {buttonTitle: replyButton, placeholder: replyPlaceholder};
|
|
const replyAction = new NotificationAction(REPLY_ACTION, 'background', replyTitle, true, replyTextInput);
|
|
return new NotificationCategory(CATEGORY, [replyAction]);
|
|
};
|
|
|
|
handleNotification = async (notification: NotificationWithData) => {
|
|
const {payload, foreground, userInteraction} = notification;
|
|
|
|
if (payload) {
|
|
switch (payload.type) {
|
|
case NOTIFICATION_TYPE.CLEAR:
|
|
// Mark the channel as read
|
|
break;
|
|
case NOTIFICATION_TYPE.MESSAGE:
|
|
// fetch the posts for the channel
|
|
|
|
if (foreground) {
|
|
// Show the in-app notification
|
|
} else if (userInteraction && !payload.userInfo?.local) {
|
|
const props = getLaunchPropsFromNotification(notification);
|
|
relaunchApp(props);
|
|
}
|
|
break;
|
|
case NOTIFICATION_TYPE.SESSION:
|
|
// eslint-disable-next-line no-console
|
|
console.log('Session expired notification');
|
|
|
|
// Logout the user from the server that matches the notification
|
|
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
handleInAppNotification = async (notification: NotificationWithData) => {
|
|
const {payload} = notification;
|
|
|
|
// TODO: Get current channel from the database
|
|
const currentChannelId = '';
|
|
|
|
if (payload?.channel_id !== currentChannelId) {
|
|
const screen = 'Notification';
|
|
const passProps = {
|
|
notification,
|
|
};
|
|
|
|
DeviceEventEmitter.emit(Navigation.NAVIGATION_SHOW_OVERLAY);
|
|
showOverlay(screen, passProps);
|
|
}
|
|
};
|
|
|
|
localNotification = (notification: Notification) => {
|
|
Notifications.postLocalNotification(notification);
|
|
};
|
|
|
|
onAppStateChange = (appState: AppStateStatus) => {
|
|
const isActive = appState === 'active';
|
|
const isBackground = appState === 'background';
|
|
|
|
if (isActive) {
|
|
if (!this.pushNotificationListener) {
|
|
this.pushNotificationListener = DeviceEventEmitter.addListener(
|
|
View.NOTIFICATION_IN_APP,
|
|
this.handleInAppNotification,
|
|
);
|
|
}
|
|
} else if (isBackground) {
|
|
this.pushNotificationListener?.remove();
|
|
this.pushNotificationListener = undefined;
|
|
}
|
|
};
|
|
|
|
onNotificationOpened = (notification: NotificationWithData, completion: () => void) => {
|
|
notification.userInteraction = true;
|
|
this.handleNotification(notification);
|
|
completion();
|
|
};
|
|
|
|
onNotificationReceivedBackground = (notification: NotificationWithData, completion: (response: NotificationBackgroundFetchResult) => void) => {
|
|
this.handleNotification(notification);
|
|
completion(NotificationBackgroundFetchResult.NO_DATA);
|
|
};
|
|
|
|
onNotificationReceivedForeground = (notification: NotificationWithData, completion: (response: NotificationCompletion) => void) => {
|
|
notification.foreground = AppState.currentState === 'active';
|
|
completion({alert: false, sound: true, badge: true});
|
|
this.handleNotification(notification);
|
|
};
|
|
|
|
onRemoteNotificationsRegistered = async (event: Registered) => {
|
|
if (!this.configured) {
|
|
this.configured = true;
|
|
const {deviceToken} = event;
|
|
let prefix;
|
|
|
|
if (Platform.OS === 'ios') {
|
|
prefix = Device.PUSH_NOTIFY_APPLE_REACT_NATIVE;
|
|
if (DeviceInfo.getBundleId().includes('rnbeta')) {
|
|
prefix = `${prefix}beta`;
|
|
}
|
|
} else {
|
|
prefix = Device.PUSH_NOTIFY_ANDROID_REACT_NATIVE;
|
|
}
|
|
|
|
const {activeServerDatabase, error} = await getActiveServerDatabase();
|
|
|
|
if (!activeServerDatabase) {
|
|
return {error};
|
|
}
|
|
|
|
const operator = new Operator(activeServerDatabase);
|
|
|
|
operator.handleIsolatedEntity({
|
|
tableName: IsolatedEntities.GLOBAL,
|
|
values: [{name: 'deviceToken', value: `${prefix}:${deviceToken}`}],
|
|
prepareRecordsOnly: false,
|
|
});
|
|
|
|
// Store the device token in the default database
|
|
this.requestNotificationReplyPermissions();
|
|
}
|
|
return null;
|
|
};
|
|
|
|
requestNotificationReplyPermissions = () => {
|
|
if (Platform.OS === 'ios') {
|
|
const replyCategory = this.createReplyCategory();
|
|
Notifications.setCategories([replyCategory]);
|
|
}
|
|
};
|
|
|
|
setApplicationIconBadgeNumber = (value: number) => {
|
|
if (Platform.OS === 'ios') {
|
|
const count = value < 0 ? 0 : value;
|
|
Notifications.ios.setBadgeCount(count);
|
|
}
|
|
};
|
|
|
|
scheduleNotification = (notification: Notification) => {
|
|
if (notification.fireDate) {
|
|
if (Platform.OS === 'ios') {
|
|
notification.fireDate = new Date(notification.fireDate).toISOString();
|
|
}
|
|
|
|
Notifications.postLocalNotification(notification);
|
|
}
|
|
};
|
|
}
|
|
|
|
export default new PushNotifications();
|