Deep Link: Fix bot link (#4266)
This commit is contained in:
parent
deede8cb6f
commit
3d4ba7c47b
@ -92,7 +92,7 @@ const UsernameInput: FC<OwnProps> = ({
|
||||
|
||||
setUsername(newUsername);
|
||||
|
||||
const isValid = isUsernameValid(newUsername);
|
||||
const isValid = newUsername === '' ? true : isUsernameValid(newUsername);
|
||||
if (!isValid) return;
|
||||
|
||||
onChange?.(newUsername);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { ThreadId } from '../types';
|
||||
|
||||
import { RE_TG_LINK, RE_TME_LINK } from '../config';
|
||||
import { isUsernameValid } from './username';
|
||||
import { ensureProtocol } from './ensureProtocol';
|
||||
|
||||
export type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
||||
'setlanguage' | 'addtheme' | 'confirmphone' | 'socks' | 'proxy' | 'privatepost' | 'bg' | 'share' | 'msg' | 'msg_url' |
|
||||
@ -55,26 +55,33 @@ interface TelegramPassportLink {
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
interface PublicUsernameOrBotLink {
|
||||
type: 'publicUsernameOrBotLink';
|
||||
username: string;
|
||||
parameter?: string;
|
||||
}
|
||||
|
||||
type DeepLink =
|
||||
TelegramPassportLink |
|
||||
LoginCodeLink |
|
||||
PublicMessageLink |
|
||||
PrivateMessageLink |
|
||||
ShareLink |
|
||||
ChatFolderLink;
|
||||
ChatFolderLink |
|
||||
PublicUsernameOrBotLink;
|
||||
|
||||
type BuilderParams<T extends DeepLink> = Record<keyof Omit<T, 'type'>, string>;
|
||||
type BuilderParams<T extends DeepLink> = Record<keyof Omit<T, 'type'>, string | undefined>;
|
||||
type BuilderReturnType<T extends DeepLink> = T | undefined;
|
||||
type DeepLinkType = DeepLink['type'] | 'unknown';
|
||||
|
||||
type PrivateMessageLinkBuilderParams = Omit<BuilderParams<PrivateMessageLink>, 'isSingle' | 'isBoost'> & {
|
||||
single: string;
|
||||
boost: string;
|
||||
single: string | undefined;
|
||||
boost: string | undefined;
|
||||
};
|
||||
|
||||
type PublicMessageLinkBuilderParams = Omit<BuilderParams<PublicMessageLink>, 'isSingle' | 'isBoost'> & {
|
||||
single: string;
|
||||
boost: string;
|
||||
single: string | undefined;
|
||||
boost: string | undefined;
|
||||
};
|
||||
|
||||
const ELIGIBLE_HOSTNAMES = new Set(['t.me', 'telegram.me', 'telegram.dog']);
|
||||
@ -84,6 +91,9 @@ export function isDeepLink(link: string): boolean {
|
||||
}
|
||||
|
||||
export function tryParseDeepLink(link: string): DeepLink | undefined {
|
||||
if (!isDeepLink(link)) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return parseDeepLink(link);
|
||||
} catch (err) {
|
||||
@ -92,19 +102,23 @@ export function tryParseDeepLink(link: string): DeepLink | undefined {
|
||||
}
|
||||
|
||||
function parseDeepLink(url: string) {
|
||||
if (url.startsWith('https:')) {
|
||||
const urlParsed = new URL(url);
|
||||
return handleHttpLink(urlParsed);
|
||||
const correctUrl = ensureProtocol(url);
|
||||
if (!correctUrl) {
|
||||
return undefined;
|
||||
}
|
||||
if (url.startsWith('tg:')) {
|
||||
if (correctUrl.startsWith('https:')) {
|
||||
const urlParsed = new URL(correctUrl);
|
||||
return parseHttpLink(urlParsed);
|
||||
}
|
||||
if (correctUrl.startsWith('tg:')) {
|
||||
// Chrome parse url with tg: protocol incorrectly
|
||||
const urlParsed = new URL(url.replace(/^tg:/, 'http:'));
|
||||
return handleTgLink(urlParsed);
|
||||
const urlParsed = new URL(correctUrl.replace(/^tg:/, 'http:'));
|
||||
return parseTgLink(urlParsed);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function handleTgLink(url: URL) {
|
||||
function parseTgLink(url: URL) {
|
||||
const { hostname } = url;
|
||||
const queryParams = getQueryParams(url);
|
||||
const pathParams = getPathParams(url);
|
||||
@ -155,13 +169,18 @@ function handleTgLink(url: URL) {
|
||||
callbackUrl: queryParams.callback_url,
|
||||
payload: queryParams.payload,
|
||||
});
|
||||
case 'publicUsernameOrBotLink':
|
||||
return buildPublicUsernameOrBotLink({
|
||||
username: queryParams.domain,
|
||||
parameter: queryParams.start,
|
||||
});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function handleHttpLink(url: URL) {
|
||||
function parseHttpLink(url: URL) {
|
||||
if (!ELIGIBLE_HOSTNAMES.has(url.hostname)) {
|
||||
return undefined;
|
||||
}
|
||||
@ -231,6 +250,11 @@ function handleHttpLink(url: URL) {
|
||||
return buildChatFolderLink({ slug: pathParams[1] });
|
||||
case 'loginCodeLink':
|
||||
return buildLoginCodeLink({ code: pathParams[1] });
|
||||
case 'publicUsernameOrBotLink':
|
||||
return buildPublicUsernameOrBotLink({
|
||||
username: pathParams[0],
|
||||
parameter: queryParams.start,
|
||||
});
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -247,6 +271,9 @@ function getHttpDeepLinkType(
|
||||
if (method === 'share') {
|
||||
return 'shareLink';
|
||||
}
|
||||
if (isUsernameValid(method)) {
|
||||
return 'publicUsernameOrBotLink';
|
||||
}
|
||||
} else if (len === 2) {
|
||||
if (method === 'addlist') {
|
||||
return 'chatFolderLink';
|
||||
@ -289,6 +316,9 @@ function getTgDeepLinkType(
|
||||
if (domain && post) {
|
||||
return 'publicMessageLink';
|
||||
}
|
||||
if (isUsernameValid(domain)) {
|
||||
return 'publicUsernameOrBotLink';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'privatepost': {
|
||||
@ -431,6 +461,26 @@ function buildTelegramPassportLink(
|
||||
};
|
||||
}
|
||||
|
||||
function buildPublicUsernameOrBotLink(
|
||||
params: BuilderParams<PublicUsernameOrBotLink>,
|
||||
): BuilderReturnType<PublicUsernameOrBotLink> {
|
||||
const {
|
||||
username,
|
||||
parameter,
|
||||
} = params;
|
||||
if (!username) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isUsernameValid(username)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
type: 'publicUsernameOrBotLink',
|
||||
username,
|
||||
parameter,
|
||||
};
|
||||
}
|
||||
|
||||
function isNumber(s: string) {
|
||||
return /^-?\d+$/.test(s);
|
||||
}
|
||||
@ -442,3 +492,7 @@ function getPathParams(url: URL) {
|
||||
function getQueryParams(url: URL) {
|
||||
return Object.fromEntries(url.searchParams);
|
||||
}
|
||||
|
||||
function isUsernameValid(username: string) {
|
||||
return /^\D([a-zA-Z0-9_]){1,64}$/.test(username);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { getActions } from '../global';
|
||||
import type { ApiChatType } from '../api/types';
|
||||
import type { DeepLinkMethod, PrivateMessageLink } from './deepLinkParser';
|
||||
|
||||
import { API_CHAT_TYPES } from '../config';
|
||||
import { API_CHAT_TYPES, RE_TG_LINK } from '../config';
|
||||
import { toChannelId } from '../global/helpers';
|
||||
import { tryParseDeepLink } from './deepLinkParser';
|
||||
import { IS_SAFARI } from './windowEnvironment';
|
||||
@ -17,11 +17,21 @@ export const processDeepLink = (url: string): boolean => {
|
||||
case 'privateMessageLink':
|
||||
handlePrivateMessageLink(parsedLink, actions);
|
||||
return true;
|
||||
case 'publicUsernameOrBotLink':
|
||||
actions.openChatByUsername({
|
||||
username: parsedLink.username,
|
||||
startParam: parsedLink.parameter,
|
||||
});
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!url.match(RE_TG_LINK)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
protocol, searchParams, pathname, hostname,
|
||||
} = new URL(url);
|
||||
|
||||
@ -3,9 +3,7 @@ export const MAX_USERNAME_LENGTH = 32;
|
||||
export const USERNAME_REGEX = /^\D([a-zA-Z0-9_]+)$/;
|
||||
|
||||
export function isUsernameValid(username: string) {
|
||||
return username.length === 0 || (
|
||||
username.length >= MIN_USERNAME_LENGTH
|
||||
return username.length >= MIN_USERNAME_LENGTH
|
||||
&& username.length <= MAX_USERNAME_LENGTH
|
||||
&& USERNAME_REGEX.test(username)
|
||||
);
|
||||
&& USERNAME_REGEX.test(username);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user