refactor
This commit is contained in:
parent
12a80c4f29
commit
a3b811dcfb
4 changed files with 84 additions and 62 deletions
|
|
@ -143,7 +143,7 @@ export const handleGotoLocation = async (serverUrl: string, intl: IntlShape, loc
|
|||
const match = matchDeepLink(location, serverUrl, config?.SiteURL);
|
||||
|
||||
if (match) {
|
||||
handleDeepLink(match, intl, location);
|
||||
handleDeepLink(match.url, intl, location);
|
||||
} else {
|
||||
const {formatMessage} = intl;
|
||||
const onError = () => Alert.alert(
|
||||
|
|
|
|||
|
|
@ -77,9 +77,9 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
|
|||
const match = matchDeepLink(url, serverUrl, siteURL);
|
||||
|
||||
if (match) {
|
||||
const {error} = await handleDeepLink(match, intl);
|
||||
const {error} = await handleDeepLink(match.url, intl);
|
||||
if (error) {
|
||||
tryOpenURL(match, onError);
|
||||
tryOpenURL(match.url, onError);
|
||||
}
|
||||
} else {
|
||||
tryOpenURL(url, onError);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import urlParse from 'url-parse';
|
|||
import {makeDirectChannel, switchToChannelByName} from '@actions/remote/channel';
|
||||
import {showPermalink} from '@actions/remote/permalink';
|
||||
import {fetchUsersByUsernames} from '@actions/remote/user';
|
||||
import DeepLinkType from '@app/constants/deep_linking';
|
||||
import {DeepLink, Launch, Screens} from '@constants';
|
||||
import {getDefaultThemeByAppearance} from '@context/theme';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
|
@ -29,6 +30,8 @@ import {
|
|||
PLUGIN_ID_PATH_PATTERN,
|
||||
} from '@utils/url/path';
|
||||
|
||||
import {removeProtocol} from '../url';
|
||||
|
||||
import type {DeepLinkChannel, DeepLinkDM, DeepLinkGM, DeepLinkPermalink, DeepLinkWithData, LaunchProps} from '@typings/launch';
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
|
|
@ -124,35 +127,31 @@ const matcherOpts: Parameters<typeof match>[1] = {decode: decodeURIComponent};
|
|||
|
||||
type ChannelPathParams = {
|
||||
hostname: string;
|
||||
subpath?: string;
|
||||
serverUrl: string;
|
||||
teamName: string;
|
||||
path: 'channels' | 'messages';
|
||||
identifier: string;
|
||||
postId?: string;
|
||||
};
|
||||
|
||||
const CHANNEL_PATH = `/:subpath*/:teamName(${TEAM_NAME_PATH_PATTERN})/:path(channels|messages)/:identifier(${IDENTIFIER_PATH_PATTERN})/:postId(${ID_PATH_PATTERN})?`;
|
||||
const CHANNEL_PATH = `:serverUrl(.*)/:teamName(${TEAM_NAME_PATH_PATTERN})/:path(channels|messages)/:identifier(${IDENTIFIER_PATH_PATTERN})/:postId(${ID_PATH_PATTERN})?`;
|
||||
export const matchChannelDeeplink = match<ChannelPathParams>(CHANNEL_PATH, matcherOpts);
|
||||
|
||||
type PermalinkPathParams = {
|
||||
hostname: string;
|
||||
subpath?: string;
|
||||
serverUrl: string;
|
||||
teamName: string;
|
||||
postId: string;
|
||||
};
|
||||
const PERMALINK_PATH = `/:subpath*/:teamName(${TEAM_NAME_PATH_PATTERN})/pl/:postId(${ID_PATH_PATTERN})`;
|
||||
const PERMALINK_PATH = `:serverUrl(.*)/:teamName(${TEAM_NAME_PATH_PATTERN})/pl/:postId(${ID_PATH_PATTERN})`;
|
||||
export const matchPermalinkDeeplink = match<PermalinkPathParams>(PERMALINK_PATH, matcherOpts);
|
||||
|
||||
const getServerUrl = (host: string, subpath?: string) => host + (subpath ? '/' + subpath : '');
|
||||
|
||||
export function parseDeepLink(deepLinkUrl: string): DeepLinkWithData {
|
||||
try {
|
||||
const {host, pathname} = urlParse(deepLinkUrl);
|
||||
const url = removeProtocol(deepLinkUrl);
|
||||
|
||||
const channelMatch = matchChannelDeeplink(pathname);
|
||||
const channelMatch = matchChannelDeeplink(url);
|
||||
if (channelMatch) {
|
||||
const {params: {subpath, teamName, path, identifier, postId}} = channelMatch;
|
||||
const serverUrl = getServerUrl(host, subpath);
|
||||
const {params: {serverUrl, teamName, path, identifier, postId}} = channelMatch;
|
||||
|
||||
if (path === 'channels') {
|
||||
return {type: DeepLink.Channel, url: deepLinkUrl, data: {serverUrl, teamName, channelName: identifier, postId}};
|
||||
|
|
@ -160,43 +159,39 @@ export function parseDeepLink(deepLinkUrl: string): DeepLinkWithData {
|
|||
|
||||
if (path === 'messages') {
|
||||
if (identifier.startsWith('@')) {
|
||||
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl, teamName, userName: identifier}};
|
||||
return {type: DeepLink.DirectMessage, url: deepLinkUrl, data: {serverUrl, teamName, userName: identifier.substring(1)}};
|
||||
}
|
||||
|
||||
return {type: DeepLink.GroupMessage, url: deepLinkUrl, data: {serverUrl, teamName, channelId: identifier}};
|
||||
}
|
||||
}
|
||||
|
||||
const permalinkMatch = matchPermalinkDeeplink(pathname);
|
||||
const permalinkMatch = matchPermalinkDeeplink(url);
|
||||
if (permalinkMatch) {
|
||||
const {params: {subpath, teamName, postId}} = permalinkMatch;
|
||||
const serverUrl = getServerUrl(host, subpath);
|
||||
|
||||
const {params: {serverUrl, teamName, postId}} = permalinkMatch;
|
||||
return {type: DeepLink.Permalink, url: deepLinkUrl, data: {serverUrl, teamName, postId}};
|
||||
}
|
||||
|
||||
const pluginMatch = match<{subpath?: string; id: string}>(`/:subpath*/plugins/:id(${PLUGIN_ID_PATH_PATTERN})`, matcherOpts)(pathname);
|
||||
const pluginMatch = match<{serverUrl: string; id: string}>(`:serverUrl(.*)/plugins/:id(${PLUGIN_ID_PATH_PATTERN})`, matcherOpts)(url);
|
||||
if (pluginMatch) {
|
||||
const {params: {subpath, id}} = pluginMatch;
|
||||
const serverUrl = getServerUrl(host, subpath);
|
||||
const {params: {serverUrl, id}} = pluginMatch;
|
||||
return {type: DeepLink.Plugin, url: deepLinkUrl, data: {serverUrl, teamName: '', id}};
|
||||
}
|
||||
} catch {
|
||||
} catch (err) {
|
||||
// do nothing just return invalid deeplink
|
||||
}
|
||||
|
||||
return {type: DeepLink.Invalid, url: deepLinkUrl};
|
||||
}
|
||||
|
||||
export function matchDeepLink(url?: string, serverURL?: string, siteURL?: string) {
|
||||
export function matchDeepLink(url: string, serverURL?: string, siteURL?: string) {
|
||||
if (!url || (!serverURL && !siteURL)) {
|
||||
return '';
|
||||
return null;
|
||||
}
|
||||
|
||||
let urlToMatch = url;
|
||||
const urlBase = serverURL || siteURL || '';
|
||||
const parsedUrl = urlParse(url);
|
||||
const parsedBase = urlParse(urlBase);
|
||||
|
||||
if (!parsedUrl.protocol) {
|
||||
// If url doesn't contain site or server URL, tack it on.
|
||||
|
|
@ -207,22 +202,13 @@ export function matchDeepLink(url?: string, serverURL?: string, siteURL?: string
|
|||
}
|
||||
}
|
||||
|
||||
const finalUrl = urlParse(urlToMatch);
|
||||
const baseSubpath = parsedBase.pathname.replace('/', '');
|
||||
const baseHostname = parsedBase.hostname;
|
||||
const urlSubpath = finalUrl.pathname.split('/')[1];
|
||||
const urlHostname = finalUrl.hostname;
|
||||
const parsed = parseDeepLink(urlToMatch);
|
||||
|
||||
if (baseSubpath) {
|
||||
// if the server is in a subpath
|
||||
if (urlHostname === baseHostname && urlSubpath === baseSubpath) {
|
||||
return urlToMatch;
|
||||
}
|
||||
} else if (urlHostname === baseHostname) {
|
||||
return urlToMatch;
|
||||
if (parsed.type === DeepLinkType.Invalid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '';
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export const getLaunchPropsFromDeepLink = (deepLinkUrl: string, coldStart = false): LaunchProps => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {Linking} from 'react-native';
|
|||
|
||||
import DeepLinkType from '@constants/deep_linking';
|
||||
import TestHelper from '@test/test_helper';
|
||||
import {matchDeepLink, parseDeepLink} from '@utils/deep_link';
|
||||
import {matchDeepLink} from '@utils/deep_link';
|
||||
import * as UrlUtils from '@utils/url';
|
||||
|
||||
/* eslint-disable max-nested-callbacks */
|
||||
|
|
@ -135,26 +135,29 @@ describe('UrlUtils', () => {
|
|||
const SERVER_WITH_SUBPATH = `http://${URL_PATH_NO_PROTOCOL}`;
|
||||
const DEEPLINK_URL_ROOT = `mattermost://${URL_NO_PROTOCOL}`;
|
||||
|
||||
const DM_USER = TestHelper.fakeUserWithId();
|
||||
const GM_CHANNEL_NAME = '4862db64e76a321d167fe6677f16e96e9275dabe';
|
||||
|
||||
const tests = [
|
||||
{
|
||||
name: 'should return null if all inputs are empty',
|
||||
input: {url: '', serverURL: '', siteURL: ''},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null if any of the input is null',
|
||||
input: {url: '', serverURL: '', siteURL: null},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null if any of the input is null',
|
||||
input: {url: '', serverURL: null, siteURL: ''},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null if any of the input is null',
|
||||
input: {url: null, serverURL: '', siteURL: ''},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for not supported link',
|
||||
|
|
@ -163,12 +166,12 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null despite url subset match',
|
||||
input: {url: 'http://myserver.com', serverURL: 'http://myserver.co'},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should match despite no server URL in input link',
|
||||
|
|
@ -193,7 +196,7 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for backslash-invalid deeplink',
|
||||
|
|
@ -202,7 +205,7 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for backslash-invalid-alt deeplink',
|
||||
|
|
@ -211,7 +214,7 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for double encoded invalid deeplink',
|
||||
|
|
@ -220,7 +223,7 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should return null for double encoded backslash-invalid deeplink',
|
||||
|
|
@ -229,7 +232,7 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
{
|
||||
name: 'should match channel link',
|
||||
|
|
@ -296,9 +299,41 @@ describe('UrlUtils', () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: 'should match channel link (team name: channels, channel name: messages) with deeplink prefix',
|
||||
name: 'should match DM channel link with deeplink prefix',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/channels/channels/messages',
|
||||
url: DEEPLINK_URL_ROOT + `/pl/messages/@${DM_USER.username}`,
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
userName: DM_USER.username,
|
||||
serverUrl: URL_NO_PROTOCOL,
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.DirectMessage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match GM channel link with deeplink prefix',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + `/pl/messages/${GM_CHANNEL_NAME}`,
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
expected: {
|
||||
data: {
|
||||
channelId: GM_CHANNEL_NAME,
|
||||
serverUrl: URL_NO_PROTOCOL,
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.GroupMessage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should match channel link (team name: pl, channel name: messages) with deeplink prefix',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/pl/channels/messages',
|
||||
serverURL: SERVER_URL,
|
||||
siteURL: SITE_URL,
|
||||
},
|
||||
|
|
@ -306,7 +341,7 @@ describe('UrlUtils', () => {
|
|||
data: {
|
||||
channelName: 'messages',
|
||||
serverUrl: URL_NO_PROTOCOL,
|
||||
teamName: 'channels',
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.Channel,
|
||||
},
|
||||
|
|
@ -328,9 +363,9 @@ describe('UrlUtils', () => {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: 'should match permalink (team name: channels) with deeplink prefix on a Server hosted in a Subpath',
|
||||
name: 'should match permalink (team name: pl) with deeplink prefix on a Server hosted in a Subpath',
|
||||
input: {
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/channels/pl/qe93kkfd7783iqwuwfcwcxbsrr',
|
||||
url: DEEPLINK_URL_ROOT + '/subpath/deepsubpath/pl/pl/qe93kkfd7783iqwuwfcwcxbsrr',
|
||||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
|
|
@ -338,7 +373,7 @@ describe('UrlUtils', () => {
|
|||
data: {
|
||||
postId: 'qe93kkfd7783iqwuwfcwcxbsrr',
|
||||
serverUrl: URL_PATH_NO_PROTOCOL,
|
||||
teamName: 'channels',
|
||||
teamName: 'pl',
|
||||
},
|
||||
type: DeepLinkType.Permalink,
|
||||
},
|
||||
|
|
@ -366,7 +401,7 @@ describe('UrlUtils', () => {
|
|||
serverURL: SERVER_WITH_SUBPATH,
|
||||
siteURL: SERVER_WITH_SUBPATH,
|
||||
},
|
||||
expected: {type: DeepLinkType.Invalid},
|
||||
expected: null,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -374,10 +409,11 @@ describe('UrlUtils', () => {
|
|||
const {name, input, expected} = test;
|
||||
|
||||
it(name, () => {
|
||||
const match = matchDeepLink(input.url!, input.serverURL!, input.siteURL!);
|
||||
const parsed = parseDeepLink(match);
|
||||
Reflect.deleteProperty(parsed, 'url');
|
||||
expect(parsed).toEqual(expected);
|
||||
const matched = matchDeepLink(input.url!, input.serverURL!, input.siteURL!);
|
||||
if (matched) {
|
||||
Reflect.deleteProperty(matched, 'url');
|
||||
}
|
||||
expect(matched).toEqual(expected);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue