-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
Link.ts
124 lines (106 loc) · 5.98 KB
/
Link.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types';
import asyncOpenURL from '@libs/asyncOpenURL';
import * as Environment from '@libs/Environment/Environment';
import Navigation from '@libs/Navigation/Navigation';
import * as Url from '@libs/Url';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import * as Session from './Session';
let isNetworkOffline = false;
Onyx.connect({
key: ONYXKEYS.NETWORK,
callback: (value) => (isNetworkOffline = value?.isOffline ?? false),
});
let currentUserEmail = '';
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (value) => (currentUserEmail = value?.email ?? ''),
});
function buildOldDotURL(url: string, shortLivedAuthToken?: string): Promise<string> {
const hasHashParams = url.indexOf('#') !== -1;
const hasURLParams = url.indexOf('?') !== -1;
const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : '';
const emailParam = `email=${encodeURIComponent(currentUserEmail)}`;
const paramsArray = [authTokenParam, emailParam];
const params = paramsArray.filter(Boolean).join('&');
return Environment.getOldDotEnvironmentURL().then((environmentURL) => {
const oldDotDomain = Url.addTrailingForwardSlash(environmentURL);
// If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters.
return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`;
});
}
/**
* @param shouldSkipCustomSafariLogic When true, we will use `Linking.openURL` even if the browser is Safari.
*/
function openExternalLink(url: string, shouldSkipCustomSafariLogic = false) {
asyncOpenURL(Promise.resolve(), url, shouldSkipCustomSafariLogic);
}
function openOldDotLink(url: string) {
if (isNetworkOffline) {
buildOldDotURL(url).then((oldDotURL) => openExternalLink(oldDotURL));
return;
}
// If shortLivedAuthToken is not accessible, fallback to opening the link without the token.
asyncOpenURL(
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.OPEN_OLD_DOT_LINK, {}, {})
.then((response) => (response ? buildOldDotURL(url, response.shortLivedAuthToken) : buildOldDotURL(url)))
.catch(() => buildOldDotURL(url)),
(oldDotURL) => oldDotURL,
);
}
function getInternalNewExpensifyPath(href: string) {
const attrPath = Url.getPathFromURL(href);
return (Url.hasSameExpensifyOrigin(href, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONST.STAGING_NEW_EXPENSIFY_URL) || href.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) &&
!CONST.PATHS_TO_TREAT_AS_EXTERNAL.find((path) => attrPath.startsWith(path))
? attrPath
: '';
}
function getInternalExpensifyPath(href: string) {
const attrPath = Url.getPathFromURL(href);
const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.STAGING_API_ROOT);
if (!hasExpensifyOrigin || attrPath.startsWith(CONFIG.EXPENSIFY.CONCIERGE_URL_PATHNAME) || attrPath.startsWith(CONFIG.EXPENSIFY.DEVPORTAL_URL_PATHNAME)) {
return '';
}
return attrPath;
}
function openLink(href: string, environmentURL: string, isAttachment = false) {
const hasSameOrigin = Url.hasSameExpensifyOrigin(href, environmentURL);
const hasExpensifyOrigin = Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONFIG.EXPENSIFY.STAGING_API_ROOT);
const internalNewExpensifyPath = getInternalNewExpensifyPath(href);
const internalExpensifyPath = getInternalExpensifyPath(href);
// There can be messages from Concierge with links to specific NewDot reports. Those URLs look like this:
// https://www.expensify.com.dev/newdotreport?reportID=3429600449838908 and they have a target="_blank" attribute. This is so that when a user is on OldDot,
// clicking on the link will open the chat in NewDot. However, when a user is in NewDot and clicks on the concierge link, the link needs to be handled differently.
// Normally, the link would be sent to Link.openOldDotLink() and opened in a new tab, and that's jarring to the user. Since the intention is to link to a specific NewDot chat,
// the reportID is extracted from the URL and then opened as an internal link, taking the user straight to the chat in the same tab.
if (hasExpensifyOrigin && href.indexOf('newdotreport?reportID=') > -1) {
const reportID = href.split('newdotreport?reportID=').pop();
const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID ?? '');
Navigation.navigate(reportRoute);
return;
}
// If we are handling a New Expensify link then we will assume this should be opened by the app internally. This ensures that the links are opened internally via react-navigation
// instead of in a new tab or with a page refresh (which is the default behavior of an anchor tag)
if (internalNewExpensifyPath && hasSameOrigin) {
if (Session.isAnonymousUser() && !Session.canAnonymousUserAccessRoute(internalNewExpensifyPath)) {
Session.signOutAndRedirectToSignIn();
return;
}
Navigation.navigate(internalNewExpensifyPath as Route);
return;
}
// If we are handling an old dot Expensify link we need to open it with openOldDotLink() so we can navigate to it with the user already logged in.
// As attachments also use expensify.com we don't want it working the same as links.
if (internalExpensifyPath && !isAttachment) {
openOldDotLink(internalExpensifyPath);
return;
}
openExternalLink(href);
}
export {buildOldDotURL, openOldDotLink, openExternalLink, openLink, getInternalNewExpensifyPath, getInternalExpensifyPath};