Add alert when deeplink is invalid (#7538)

This commit is contained in:
Elias Nahum 2023-09-12 07:26:05 -03:00 committed by GitHub
parent 397744dc72
commit df52740752
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 71 additions and 37 deletions

View file

@ -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;

View file

@ -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};
@ -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));
}
}
};

View file

@ -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';
@ -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);
}
});
}
}
}, []);

View file

@ -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';
@ -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('(.*)\\/plugins\\/([^\\/]+)\\/(\\S+)').exec(url);
if (match) {
return {type: DeepLink.Plugin, url: deepLinkUrl, data: {serverUrl: match[1], id: match[2], teamName: ''}};
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: ''}};
}
} catch {
// do nothing just return invalid deeplink
}
return {type: DeepLink.Invalid, url: deepLinkUrl};
@ -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);
}

View file

@ -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

View file

@ -3,7 +3,7 @@
import {Alert} from 'react-native';
import {getIntlShape} from '@test/intl-test-helper';
import {getIntlShape} from '@utils/general';
import {unsupportedServer} from '.';

View file

@ -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",

View file

@ -4,7 +4,7 @@
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';
@ -12,13 +12,6 @@ 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 (