Stop ticking Telegram login message
All checks were successful
Build and deploy Backend / build (push) Successful in 28s
All checks were successful
Build and deploy Backend / build (push) Successful in 28s
This commit is contained in:
@@ -38,7 +38,7 @@ type TelegramSentMessage = {
|
||||
};
|
||||
|
||||
const loginPrefix = 'login_';
|
||||
const loginTimerHandles = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
const loginExpirationHandles = new Map<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
function randomToken() {
|
||||
return randomBytes(32).toString('base64url');
|
||||
@@ -124,12 +124,12 @@ function formatRemaining(expiresAt: Date) {
|
||||
return `${minutes}:${rest}`;
|
||||
}
|
||||
|
||||
function loginReplyMarkup(token: string, expiresAt: Date) {
|
||||
function loginReplyMarkup(token: string) {
|
||||
return {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Открыть MapFlow',
|
||||
text: 'Войти',
|
||||
url: `${config.webAppUrl}?telegram_login=${encodeURIComponent(token)}`,
|
||||
},
|
||||
],
|
||||
@@ -138,12 +138,7 @@ function loginReplyMarkup(token: string, expiresAt: Date) {
|
||||
}
|
||||
|
||||
function loginMessageText(expiresAt: Date) {
|
||||
return [
|
||||
'Вход в MapFlow',
|
||||
'',
|
||||
'Перейдите по ссылке ниже.',
|
||||
`Ссылка активна: ${formatRemaining(expiresAt)}`,
|
||||
].join('\n');
|
||||
return [formatRemaining(expiresAt), '', 'Войти в MapFlow'].join('\n');
|
||||
}
|
||||
|
||||
async function sendLoginMessage(
|
||||
@@ -152,7 +147,7 @@ async function sendLoginMessage(
|
||||
token?: string,
|
||||
expiresAt?: Date,
|
||||
) {
|
||||
const replyMarkup = token && expiresAt ? loginReplyMarkup(token, expiresAt) : undefined;
|
||||
const replyMarkup = token && expiresAt ? loginReplyMarkup(token) : undefined;
|
||||
|
||||
return callTelegram<TelegramSentMessage>('sendMessage', {
|
||||
chat_id: chatId,
|
||||
@@ -165,67 +160,79 @@ async function editLoginMessage(
|
||||
chatId: string,
|
||||
messageId: number,
|
||||
text: string,
|
||||
token?: string,
|
||||
expiresAt?: Date,
|
||||
) {
|
||||
const replyMarkup = token && expiresAt
|
||||
? loginReplyMarkup(token, expiresAt)
|
||||
: { inline_keyboard: [] };
|
||||
await callTelegram('editMessageText', {
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
text,
|
||||
reply_markup: replyMarkup,
|
||||
reply_markup: { inline_keyboard: [] },
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleLoginMessageTimer(
|
||||
function scheduleLoginExpiration(
|
||||
requestId: string,
|
||||
token: string,
|
||||
chatId: string,
|
||||
messageId: number,
|
||||
expiresAt: Date,
|
||||
) {
|
||||
const existingHandle = loginTimerHandles.get(requestId);
|
||||
const existingHandle = loginExpirationHandles.get(requestId);
|
||||
if (existingHandle) {
|
||||
clearTimeout(existingHandle);
|
||||
}
|
||||
|
||||
const tick = async () => {
|
||||
const request = await prisma.telegramLoginRequest.findUnique({
|
||||
where: { id: requestId },
|
||||
select: { status: true },
|
||||
});
|
||||
|
||||
if (!request || request.status !== 'CONFIRMED') {
|
||||
loginTimerHandles.delete(requestId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (expiresAt <= new Date()) {
|
||||
const handle = setTimeout(() => {
|
||||
void (async () => {
|
||||
const updated = await prisma.telegramLoginRequest.updateMany({
|
||||
where: { id: requestId, status: 'CONFIRMED' },
|
||||
data: { status: 'EXPIRED' },
|
||||
});
|
||||
|
||||
loginTimerHandles.delete(requestId);
|
||||
loginExpirationHandles.delete(requestId);
|
||||
if (updated.count > 0) {
|
||||
await editLoginMessage(chatId, messageId, 'Ссылка входа устарела.');
|
||||
}
|
||||
return;
|
||||
}
|
||||
})();
|
||||
}, Math.max(0, expiresAt.getTime() - Date.now()));
|
||||
loginExpirationHandles.set(requestId, handle);
|
||||
}
|
||||
|
||||
await editLoginMessage(chatId, messageId, loginMessageText(expiresAt), token, expiresAt);
|
||||
const handle = setTimeout(() => {
|
||||
void tick();
|
||||
}, 1000);
|
||||
loginTimerHandles.set(requestId, handle);
|
||||
};
|
||||
function cancelLoginExpiration(requestId: string) {
|
||||
const handle = loginExpirationHandles.get(requestId);
|
||||
if (handle) {
|
||||
clearTimeout(handle);
|
||||
loginExpirationHandles.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
const handle = setTimeout(() => {
|
||||
void tick();
|
||||
}, 1000);
|
||||
loginTimerHandles.set(requestId, handle);
|
||||
async function expireLoginMessageNow(
|
||||
requestId: string,
|
||||
chatId: string,
|
||||
messageId: number,
|
||||
) {
|
||||
cancelLoginExpiration(requestId);
|
||||
const updated = await prisma.telegramLoginRequest.updateMany({
|
||||
where: { id: requestId, status: 'CONFIRMED' },
|
||||
data: { status: 'EXPIRED' },
|
||||
});
|
||||
|
||||
if (updated.count > 0) {
|
||||
await editLoginMessage(chatId, messageId, 'Ссылка входа устарела.');
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshExpiredLoginMessage(
|
||||
requestId: string,
|
||||
chatId: string,
|
||||
messageId: number,
|
||||
) {
|
||||
const request = await prisma.telegramLoginRequest.findUnique({
|
||||
where: { id: requestId },
|
||||
select: { status: true },
|
||||
});
|
||||
|
||||
if (request?.status === 'CONFIRMED') {
|
||||
await expireLoginMessageNow(requestId, chatId, messageId);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTelegramBotLogin() {
|
||||
@@ -257,10 +264,18 @@ export async function completeTelegramBotLogin(token: string) {
|
||||
}
|
||||
|
||||
if (request.expiresAt <= new Date()) {
|
||||
await prisma.telegramLoginRequest.update({
|
||||
where: { id: request.id },
|
||||
data: { status: 'EXPIRED' },
|
||||
});
|
||||
if (request.telegramChatId && request.telegramMessageId) {
|
||||
await refreshExpiredLoginMessage(
|
||||
request.id,
|
||||
request.telegramChatId,
|
||||
request.telegramMessageId,
|
||||
);
|
||||
} else {
|
||||
await prisma.telegramLoginRequest.update({
|
||||
where: { id: request.id },
|
||||
data: { status: 'EXPIRED' },
|
||||
});
|
||||
}
|
||||
throw new Error('Telegram login token is expired.');
|
||||
}
|
||||
|
||||
@@ -268,6 +283,7 @@ export async function completeTelegramBotLogin(token: string) {
|
||||
throw new Error('Telegram login is not confirmed.');
|
||||
}
|
||||
|
||||
cancelLoginExpiration(request.id);
|
||||
await prisma.telegramLoginRequest.update({
|
||||
where: { id: request.id },
|
||||
data: { status: 'USED' },
|
||||
@@ -314,7 +330,10 @@ export async function handleTelegramBotWebhook(
|
||||
});
|
||||
|
||||
if (!request || request.status !== 'PENDING' || request.expiresAt <= new Date()) {
|
||||
await sendLoginMessage(chatId, 'Ссылка входа устарела.\nВернитесь на сайт и начните вход заново.');
|
||||
await sendLoginMessage(
|
||||
chatId,
|
||||
'Ссылка входа устарела.\nВернитесь на сайт и начните вход заново.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,9 +368,8 @@ export async function handleTelegramBotWebhook(
|
||||
},
|
||||
});
|
||||
|
||||
scheduleLoginMessageTimer(
|
||||
scheduleLoginExpiration(
|
||||
request.id,
|
||||
token,
|
||||
chatId.toString(),
|
||||
sentMessage.message_id,
|
||||
request.expiresAt,
|
||||
|
||||
Reference in New Issue
Block a user