Skip to content

Commit

Permalink
Add alert when deeplink is invalid (#7538)
Browse files Browse the repository at this point in the history
  • Loading branch information
enahum authored Sep 12, 2023
1 parent 397744d commit df52740
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 37 deletions.
2 changes: 1 addition & 1 deletion app/init/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const launchApp = async (props: LaunchProps) => {
let serverUrl: string | undefined;
switch (props?.launchType) {
case Launch.DeepLink:
if (props.extra?.type !== DeepLink.Invalid) {
if (props.extra && props.extra.type !== DeepLink.Invalid) {
const extra = props.extra as DeepLinkWithData;
const existingServer = DatabaseManager.searchUrl(extra.data!.serverUrl);
serverUrl = existingServer;
Expand Down
10 changes: 7 additions & 3 deletions app/managers/global_event_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {queryTeamDefaultChannel} from '@queries/servers/channel';
import {getCommonSystemValues} from '@queries/servers/system';
import {getTeamChannelHistory} from '@queries/servers/team';
import {setScreensOrientation} from '@screens/navigation';
import {handleDeepLink} from '@utils/deep_link';
import {alertInvalidDeepLink, handleDeepLink} from '@utils/deep_link';
import {getIntlShape} from '@utils/general';

type LinkingCallbackArg = {url: string};

Expand Down Expand Up @@ -50,13 +51,16 @@ class GlobalEventHandler {
}
};

onDeepLink = (event: LinkingCallbackArg) => {
onDeepLink = async (event: LinkingCallbackArg) => {
if (event.url?.startsWith(Sso.REDIRECT_URL_SCHEME) || event.url?.startsWith(Sso.REDIRECT_URL_SCHEME_DEV)) {
return;
}

if (event.url) {
handleDeepLink(event.url);
const {error} = await handleDeepLink(event.url);
if (error) {
alertInvalidDeepLink(getIntlShape(DEFAULT_LOCALE));
}
}
};

Expand Down
13 changes: 11 additions & 2 deletions app/screens/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {useAppState} from '@hooks/device';
import {getAllServers} from '@queries/app/servers';
import {findChannels, popToRoot} from '@screens/navigation';
import NavigationStore from '@store/navigation_store';
import {handleDeepLink} from '@utils/deep_link';
import {alertInvalidDeepLink, handleDeepLink} from '@utils/deep_link';
import {logError} from '@utils/log';
import {alertChannelArchived, alertChannelRemove, alertTeamRemove} from '@utils/navigation';
import {notificationError} from '@utils/notification';
Expand Down Expand Up @@ -121,9 +121,18 @@ export default function HomeScreen(props: HomeProps) {

useEffect(() => {
if (props.launchType === 'deeplink') {
if (props.launchError) {
alertInvalidDeepLink(intl);
return;
}

const deepLink = props.extra as DeepLinkWithData;
if (deepLink?.url) {
handleDeepLink(deepLink.url);
handleDeepLink(deepLink.url).then((result) => {
if (result.error) {
alertInvalidDeepLink(intl);
}
});
}
}
}, []);
Expand Down
61 changes: 39 additions & 22 deletions app/utils/deep_link/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import {fetchUsersByUsernames} from '@actions/remote/user';
import {DeepLink, Launch, Screens} from '@constants';
import {getDefaultThemeByAppearance} from '@context/theme';
import DatabaseManager from '@database/manager';
import {DEFAULT_LOCALE, getTranslations} from '@i18n';
import {DEFAULT_LOCALE, getTranslations, t} from '@i18n';
import WebsocketManager from '@managers/websocket_manager';
import {getActiveServerUrl} from '@queries/app/servers';
import {getCurrentUser, queryUsersByUsername} from '@queries/servers/user';
import {dismissAllModalsAndPopToRoot} from '@screens/navigation';
import EphemeralStore from '@store/ephemeral_store';
import NavigationStore from '@store/navigation_store';
import {errorBadChannel, errorUnkownUser} from '@utils/draft';
import {alertErrorWithFallback, errorBadChannel, errorUnkownUser} from '@utils/draft';
import {logError} from '@utils/log';
import {escapeRegex} from '@utils/markdown';
import {addNewServer} from '@utils/server';
Expand Down Expand Up @@ -115,31 +115,39 @@ export async function handleDeepLink(deepLinkUrl: string, intlShape?: IntlShape,
}

export function parseDeepLink(deepLinkUrl: string): DeepLinkWithData {
const url = removeProtocol(deepLinkUrl);
try {
const url = removeProtocol(decodeURIComponent(deepLinkUrl));

let match = new RegExp('(.*)\\/([^\\/]+)\\/channels\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLink.Channel, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], channelName: match[3]}};
}
if (url.includes('../') || url.includes('/..')) {
return {type: DeepLink.Invalid, url: deepLinkUrl};
}

match = new RegExp('(.*)\\/([^\\/]+)\\/pl\\/(\\w+)').exec(url);
if (match) {
return {type: DeepLink.Permalink, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], postId: match[3]}};
}
let match = new RegExp('(.*)\\/([^\\/]+)\\/channels\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLink.Channel, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], channelName: match[3]}};
}

match = new RegExp('(.*)\\/([^\\/]+)\\/messages\\/@(\\S+)').exec(url);
if (match) {
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], userName: match[3]}};
}
match = new RegExp('(.*)\\/([^\\/]+)\\/pl\\/(\\w+)').exec(url);
if (match) {
return {type: DeepLink.Permalink, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], postId: match[3]}};
}

match = new RegExp('(.*)\\/([^\\/]+)\\/messages\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLink.GroupMessage, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], channelId: match[3]}};
}
match = new RegExp('(.*)\\/([^\\/]+)\\/messages\\/@(\\S+)').exec(url);
if (match) {
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], userName: match[3]}};
}

match = new RegExp('(.*)\\/([^\\/]+)\\/messages\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLink.GroupMessage, url: deepLinkUrl, data: {serverUrl: match[1], teamName: match[2], channelId: match[3]}};
}

match = new RegExp('(.*)\\/plugins\\/([^\\/]+)\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLink.Plugin, url: deepLinkUrl, data: {serverUrl: match[1], id: match[2], teamName: ''}};
match = new RegExp('(.*)\\/plugins\\/([^\\/]+)\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLink.Plugin, url: deepLinkUrl, data: {serverUrl: match[1], id: match[2], teamName: ''}};
}
} catch {
// do nothing just return invalid deeplink
}

return {type: DeepLink.Invalid, url: deepLinkUrl};
Expand Down Expand Up @@ -201,3 +209,12 @@ export const getLaunchPropsFromDeepLink = (deepLinkUrl: string, coldStart = fals

return launchProps;
};

export function alertInvalidDeepLink(intl: IntlShape) {
const message = {
id: t('mobile.deep_link.invalid'),
defaultMessage: 'This link you are trying to open is invalid.',
};

return alertErrorWithFallback(intl, {}, message);
}
10 changes: 10 additions & 0 deletions app/utils/general/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

import {createIntl} from 'react-intl';
import DeviceInfo from 'react-native-device-info';
import ReactNativeHapticFeedback, {HapticFeedbackTypes} from 'react-native-haptic-feedback';

import {getTranslations} from '@i18n';

type SortByCreatAt = (Session | Channel | Team | Post) & {
create_at: number;
}

export function getIntlShape(locale = 'en') {
return createIntl({
locale,
messages: getTranslations(locale),
});
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function emptyFunction(..._args: any[]) {
// do nothing
Expand Down
2 changes: 1 addition & 1 deletion app/utils/server/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import {Alert} from 'react-native';

import {getIntlShape} from '@test/intl-test-helper';
import {getIntlShape} from '@utils/general';

import {unsupportedServer} from '.';

Expand Down
1 change: 1 addition & 0 deletions assets/base/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@
"mobile.custom_status.clear_after": "Clear After",
"mobile.custom_status.clear_after.title": "Clear Custom Status After",
"mobile.custom_status.modal_confirm": "Done",
"mobile.deep_link.invalid": "This link you are trying to open is invalid.",
"mobile.diagnostic_id.empty": "A DiagnosticId value is missing for this server. Contact your system admin to review this value and restart the server.",
"mobile.direct_message.error": "We couldn't open a DM with {displayName}.",
"mobile.display_settings.clockDisplay": "Clock Display",
Expand Down
9 changes: 1 addition & 8 deletions test/intl-test-helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@
import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider';
import {render} from '@testing-library/react-native';
import React, {type ReactElement} from 'react';
import {createIntl, IntlProvider} from 'react-intl';
import {IntlProvider} from 'react-intl';
import {SafeAreaProvider} from 'react-native-safe-area-context';

import {ThemeContext, getDefaultThemeByAppearance} from '@context/theme';
import {getTranslations} from '@i18n';

import type Database from '@nozbe/watermelondb/Database';

export function getIntlShape(locale = 'en') {
return createIntl({
locale,
messages: getTranslations(locale),
});
}

export function renderWithIntl(ui: ReactElement, {locale = 'en', ...renderOptions} = {}) {
function Wrapper({children}: {children: ReactElement}) {
return (
Expand Down

0 comments on commit df52740

Please sign in to comment.