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