Files
mattermost-mobile/app/init/launch.ts
2022-11-11 22:30:36 +01:00

277 lines
10 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import Emm from '@mattermost/react-native-emm';
import {Alert, DeviceEventEmitter, Linking, Platform} from 'react-native';
import {Notifications} from 'react-native-notifications';
import {appEntry, pushNotificationEntry, upgradeEntry} from '@actions/remote/entry';
import {Screens, DeepLink, Events, Launch, PushNotification} from '@constants';
import DatabaseManager from '@database/manager';
import {getActiveServerUrl, getServerCredentials, removeServerCredentials} from '@init/credentials';
import {getThemeForCurrentTeam} from '@queries/servers/preference';
import {getCurrentUserId} from '@queries/servers/system';
import {queryMyTeams} from '@queries/servers/team';
import {goToScreen, resetToHome, resetToSelectServer, resetToTeams, resetToOnboarding} from '@screens/navigation';
import EphemeralStore from '@store/ephemeral_store';
import {logInfo} from '@utils/log';
import {convertToNotificationData} from '@utils/notification';
import {parseDeepLink} from '@utils/url';
import type {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkWithData, LaunchProps} from '@typings/launch';
import { fetchConfigAndLicense } from '@actions/remote/systems';
const initialNotificationTypes = [PushNotification.NOTIFICATION_TYPE.MESSAGE, PushNotification.NOTIFICATION_TYPE.SESSION];
export const initialLaunch = async () => {
const deepLinkUrl = await Linking.getInitialURL();
if (deepLinkUrl) {
launchAppFromDeepLink(deepLinkUrl);
return;
}
const notification = await Notifications.getInitialNotification();
let tapped = Platform.select({android: true, ios: false})!;
if (Platform.OS === 'ios' && notification) {
// when a notification is received on iOS, getInitialNotification, will return the notification
// as the app will initialized cause we are using background fetch,
// that does not necessarily mean that the app was opened cause of the notification was tapped.
// Here we are going to dettermine if the notification still exists in NotificationCenter to determine if
// the app was opened because of a tap or cause of the background fetch init
const delivered = await Notifications.ios.getDeliveredNotifications();
tapped = delivered.find((d) => (d as unknown as NotificationData).ack_id === notification?.payload.ack_id) == null;
}
if (initialNotificationTypes.includes(notification?.payload?.type) && tapped) {
launchAppFromNotification(convertToNotificationData(notification!));
return;
}
launchApp({launchType: Launch.Normal});
};
const launchAppFromDeepLink = (deepLinkUrl: string) => {
const props = getLaunchPropsFromDeepLink(deepLinkUrl);
launchApp(props);
};
const launchAppFromNotification = async (notification: NotificationWithData) => {
const props = await getLaunchPropsFromNotification(notification);
launchApp(props);
};
// pablo - here I need to validate the logic for launching the onboarding screen
const launchApp = async (props: LaunchProps, resetNavigation = true) => {
let serverUrl: string | undefined;
switch (props?.launchType) {
case Launch.DeepLink:
if (props.extra?.type !== DeepLink.Invalid) {
const extra = props.extra as DeepLinkWithData;
serverUrl = extra.data?.serverUrl;
}
break;
case Launch.Notification: {
serverUrl = props.serverUrl;
const extra = props.extra as NotificationWithData;
const sessionExpiredNotification = Boolean(props.serverUrl && extra.payload?.type === PushNotification.NOTIFICATION_TYPE.SESSION);
if (sessionExpiredNotification) {
DeviceEventEmitter.emit(Events.SESSION_EXPIRED, serverUrl);
return '';
}
break;
}
default:
serverUrl = await getActiveServerUrl();
break;
}
if (props.launchError && !serverUrl) {
serverUrl = await getActiveServerUrl();
}
if (serverUrl) {
const credentials = await getServerCredentials(serverUrl);
if (credentials) {
const database = DatabaseManager.serverDatabases[serverUrl]?.database;
let hasCurrentUser = false;
if (database) {
EphemeralStore.theme = await getThemeForCurrentTeam(database);
const currentUserId = await getCurrentUserId(database);
hasCurrentUser = Boolean(currentUserId);
}
// if (!onboardingAlreadyShown) {
// here, check if there is not an active session and redirect to onboarding with a flag, so the sign in button will
// redirect to the sign in
// return launchToOnboarding(props, goToLoginPage);
// }
console.log('\n\n first launch to onboarding \n\n', serverUrl);
return launchToOnboarding(props, resetNavigation, false, false, true, serverUrl);
let launchType = props.launchType;
if (!hasCurrentUser) {
// migrating from v1
if (launchType === Launch.Normal) {
launchType = Launch.Upgrade;
}
const result = await upgradeEntry(serverUrl);
if (result.error) {
Alert.alert(
'Error Upgrading',
`An error occurred while upgrading the app to the new version.\n\nDetails: ${result.error}\n\nThe app will now quit.`,
[{
text: 'OK',
onPress: async () => {
await DatabaseManager.destroyServerDatabase(serverUrl!);
await removeServerCredentials(serverUrl!);
Emm.exitApp();
},
}],
);
return '';
}
}
return launchToHome({...props, launchType, serverUrl});
}
}
// if (onboardingAlreadyShown) {
// // launchToServer(props, resetNavigation);
// }
console.log('\n\n second launch to onboarding \n\n', serverUrl);
return launchToOnboarding(props, resetNavigation);
};
const launchToHome = async (props: LaunchProps) => {
let openPushNotification = false;
switch (props.launchType) {
case Launch.DeepLink:
// TODO:
// deepLinkEntry({props.serverUrl, props.extra});
break;
case Launch.Notification: {
const extra = props.extra as NotificationWithData;
openPushNotification = Boolean(props.serverUrl && !props.launchError && extra.userInteraction && extra.payload?.channel_id && !extra.payload?.userInfo?.local);
if (openPushNotification) {
pushNotificationEntry(props.serverUrl!, extra);
} else {
appEntry(props.serverUrl!);
}
break;
}
case Launch.Normal:
appEntry(props.serverUrl!);
break;
}
let nTeams = 0;
if (props.serverUrl) {
const database = DatabaseManager.serverDatabases[props.serverUrl]?.database;
if (database) {
nTeams = await queryMyTeams(database).fetchCount();
}
}
if (nTeams) {
logInfo('Launch app in Home screen');
return resetToHome(props);
}
logInfo('Launch app in Select Teams screen');
return resetToTeams();
};
const launchToServer = (props: LaunchProps, resetNavigation: Boolean) => {
if (resetNavigation) {
return resetToSelectServer(props);
}
// This is being called for Deeplinks, but needs to be revisited when
// the implementation of deep links is complete
const title = '';
return goToScreen(Screens.SERVER, title, {...props});
};
const launchToOnboarding = (
props: LaunchProps,
resetNavigation = true,
notActiveSession = true,
whiteLabeledApp = false,
goToLogIn = true,
serverUrl = '',
) => {
// here, if there is an active session, redirect to home
// if there is a whitelabeled app, redirect to either SERVER or LOGIN but don't show the onboarding
if (resetNavigation) {
launchToServer(props, resetNavigation);
}
// if there is not an active session, pass the prop and redirect to the LOGIN page (keep in mind all the redirection login to check for SSO stuff)
return resetToOnboarding(props, true, serverUrl);
};
export const relaunchApp = (props: LaunchProps, resetNavigation = false) => {
return launchApp(props, resetNavigation);
};
export const getLaunchPropsFromDeepLink = (deepLinkUrl: string): LaunchProps => {
const parsed = parseDeepLink(deepLinkUrl);
const launchProps: LaunchProps = {
launchType: Launch.DeepLink,
};
switch (parsed.type) {
case DeepLink.Invalid:
launchProps.launchError = true;
break;
case DeepLink.Channel: {
const parsedData = parsed.data as DeepLinkChannel;
(launchProps.extra as DeepLinkWithData).data = parsedData;
break;
}
case DeepLink.DirectMessage: {
const parsedData = parsed.data as DeepLinkDM;
(launchProps.extra as DeepLinkWithData).data = parsedData;
break;
}
case DeepLink.GroupMessage: {
const parsedData = parsed.data as DeepLinkGM;
(launchProps.extra as DeepLinkWithData).data = parsedData;
break;
}
case DeepLink.Permalink: {
const parsedData = parsed.data as DeepLinkPermalink;
(launchProps.extra as DeepLinkWithData).data = parsedData;
break;
}
}
return launchProps;
};
export const getLaunchPropsFromNotification = async (notification: NotificationWithData): Promise<LaunchProps> => {
const launchProps: LaunchProps = {
launchType: Launch.Notification,
};
const {payload} = notification;
launchProps.extra = notification;
if (payload?.server_url) {
launchProps.serverUrl = payload.server_url;
} else if (payload?.server_id) {
const serverUrl = await DatabaseManager.getServerUrlFromIdentifier(payload.server_id);
if (serverUrl) {
launchProps.serverUrl = serverUrl;
} else {
launchProps.launchError = true;
}
} else {
launchProps.launchError = true;
}
return launchProps;
};