diff --git a/app/composables/useMessengerBotLink.ts b/app/composables/useMessengerBotLink.ts index e3f1e38..d788b9d 100644 --- a/app/composables/useMessengerBotLink.ts +++ b/app/composables/useMessengerBotLink.ts @@ -1,21 +1,10 @@ -function toBase64Url(value: string) { - if (typeof Buffer !== 'undefined') { - return Buffer.from(value, 'utf8').toString('base64url'); - } - - return btoa(value) - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=+$/g, ''); -} - -export function buildMessengerBotStartUrl(baseUrl: string, email: string) { - const normalizedEmail = email.trim().toLowerCase(); - if (!baseUrl || !normalizedEmail) { +export function buildMessengerBotStartUrl(baseUrl: string, startToken: string) { + const normalizedToken = startToken.trim(); + if (!baseUrl || !normalizedToken) { return ''; } - const payload = encodeURIComponent(toBase64Url(`login:${normalizedEmail}`)); + const payload = encodeURIComponent(normalizedToken); const separator = baseUrl.includes('?') ? '&' : '?'; return `${baseUrl}${separator}start=${payload}`; } diff --git a/app/composables/useMessengerStart.ts b/app/composables/useMessengerStart.ts new file mode 100644 index 0000000..a88d68f --- /dev/null +++ b/app/composables/useMessengerStart.ts @@ -0,0 +1,49 @@ +import { buildMessengerBotStartUrl } from '~/composables/useMessengerBotLink'; + +type MessengerChannel = 'TELEGRAM' | 'MAX'; + +type MessengerStartResponse = { + ok: true; + startToken: string; + expiresAt: string; + mode: 'login' | 'connect'; +}; + +type MessengerStartInput = { + channel: MessengerChannel; + baseUrl: string; + email?: string; +}; + +export function useMessengerStart() { + const pendingChannel = ref(null); + + async function openMessengerBot({ channel, baseUrl, email }: MessengerStartInput) { + pendingChannel.value = channel; + + const payloadPromise = $fetch('/api/auth/messenger-start', { + method: 'POST', + body: { + channel, + email, + }, + }); + payloadPromise.finally(() => { + pendingChannel.value = null; + }); + + const payload = await payloadPromise; + + const startUrl = buildMessengerBotStartUrl(baseUrl, payload.startToken); + if (import.meta.client) { + window.open(startUrl, '_blank', 'noopener,noreferrer'); + } + + return payload; + } + + return { + pendingChannel, + openMessengerBot, + }; +} diff --git a/app/pages/login.vue b/app/pages/login.vue index 2f4d8b2..12e6035 100644 --- a/app/pages/login.vue +++ b/app/pages/login.vue @@ -5,7 +5,7 @@ import { RequestLoginCodeDocument, VerifyLoginCodeDocument, } from '~/composables/graphql/generated'; -import { buildMessengerBotStartUrl } from '~/composables/useMessengerBotLink'; +import { useMessengerStart } from '~/composables/useMessengerStart'; const config = useRuntimeConfig(); const route = useRoute(); @@ -29,6 +29,7 @@ const lastRequestedEmail = ref(''); const requestCodeMutation = useMutation(RequestLoginCodeDocument, { throws: 'never' }); const verifyCodeMutation = useMutation(VerifyLoginCodeDocument, { throws: 'never' }); const consumeLoginTokenMutation = useMutation(ConsumeLoginTokenDocument, { throws: 'never' }); +const { openMessengerBot, pendingChannel } = useMessengerStart(); const telegramBotUrl = computed(() => config.public.telegramBotUrl || ''); const maxBotUrl = computed(() => config.public.maxBotUrl || ''); @@ -36,13 +37,6 @@ const maxBotUrl = computed(() => config.public.maxBotUrl || ''); const normalizedEmail = computed(() => email.value.trim().toLowerCase()); const isEmailReady = computed(() => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail.value)); -const telegramLoginUrl = computed(() => - isEmailReady.value ? buildMessengerBotStartUrl(telegramBotUrl.value, normalizedEmail.value) : '', -); -const maxLoginUrl = computed(() => - isEmailReady.value ? buildMessengerBotStartUrl(maxBotUrl.value, normalizedEmail.value) : '', -); - async function finalizeSession(accessToken: string) { authCookie.value = accessToken; } @@ -167,6 +161,28 @@ async function consumeLoginToken(loginToken: string) { await navigateAfterLogin(payload.user); } +async function startMessengerLogin(channel: 'TELEGRAM' | 'MAX') { + if (!isEmailReady.value) { + feedback.value = 'Введите корректный email.'; + feedbackTone.value = 'error'; + return; + } + + const baseUrl = channel === 'TELEGRAM' ? telegramBotUrl.value : maxBotUrl.value; + if (!baseUrl) { + feedback.value = 'Ссылка на бота пока не настроена.'; + feedbackTone.value = 'error'; + return; + } + + feedback.value = ''; + await openMessengerBot({ + channel, + baseUrl, + email: normalizedEmail.value, + }); +} + function scheduleAutoRequest() { clearAutoRequestTimer(); @@ -242,24 +258,22 @@ onBeforeUnmount(() => {
- - Войти через Telegram - - +

diff --git a/app/pages/notifications.vue b/app/pages/notifications.vue index 04c6e3d..613c655 100644 --- a/app/pages/notifications.vue +++ b/app/pages/notifications.vue @@ -6,12 +6,13 @@ import { MyNotificationHistoryDocument, SendTestMessengerMessageDocument, } from '~/composables/graphql/generated'; -import { buildMessengerBotStartUrl } from '~/composables/useMessengerBotLink'; +import { useMessengerStart } from '~/composables/useMessengerStart'; const selectedChannel = ref<'TELEGRAM' | 'MAX'>('TELEGRAM'); const customMessage = ref('Тест канала уведомлений Fregat'); const feedback = ref(''); const config = useRuntimeConfig(); +const { openMessengerBot, pendingChannel } = useMessengerStart(); const meQuery = useQuery(MeDocument); const connectionsQuery = useQuery(MyMessengerConnectionsDocument); @@ -56,12 +57,24 @@ function buildBotConnectUrl(baseUrl: string) { return ''; } - return buildMessengerBotStartUrl(baseUrl, email); + return baseUrl; } const telegramConnectUrl = computed(() => buildBotConnectUrl(config.public.telegramBotUrl || '')); const maxConnectUrl = computed(() => buildBotConnectUrl(config.public.maxBotUrl || '')); +async function connectMessenger(channel: 'TELEGRAM' | 'MAX') { + const baseUrl = channel === 'TELEGRAM' ? telegramConnectUrl.value : maxConnectUrl.value; + if (!baseUrl) { + return; + } + + await openMessengerBot({ + channel, + baseUrl, + }); +} + async function sendTest() { feedback.value = ''; if (!activeConnection.value) { @@ -114,15 +127,20 @@ async function sendTest() {

- - {{ telegramConnection ? 'Переподключить Telegram' : 'Подключить Telegram' }} - + {{ + pendingChannel === 'TELEGRAM' + ? 'Открываем Telegram…' + : telegramConnection + ? 'Переподключить Telegram' + : 'Подключить Telegram' + }} + @@ -139,15 +157,20 @@ async function sendTest() {

- - {{ maxConnection ? 'Переподключить Max' : 'Подключить Max' }} - + {{ + pendingChannel === 'MAX' + ? 'Открываем Max…' + : maxConnection + ? 'Переподключить Max' + : 'Подключить Max' + }} + diff --git a/app/pages/profile/notifications.vue b/app/pages/profile/notifications.vue index 09152fb..9782687 100644 --- a/app/pages/profile/notifications.vue +++ b/app/pages/profile/notifications.vue @@ -4,7 +4,7 @@ import { MeDocument, MyMessengerConnectionsDocument, } from '~/composables/graphql/generated'; -import { buildMessengerBotStartUrl } from '~/composables/useMessengerBotLink'; +import { useMessengerStart } from '~/composables/useMessengerStart'; type MessengerItem = { type: 'TELEGRAM' | 'MAX'; @@ -15,6 +15,7 @@ type MessengerItem = { const config = useRuntimeConfig(); const meQuery = useQuery(MeDocument); const connectionsQuery = useQuery(MyMessengerConnectionsDocument); +const { openMessengerBot, pendingChannel } = useMessengerStart(); const telegramConnection = computed(() => connectionsQuery.result.value?.myMessengerConnections?.find( @@ -34,11 +35,23 @@ function buildBotConnectUrl(baseUrl: string) { return ''; } - return buildMessengerBotStartUrl(baseUrl, accountEmail); + return baseUrl; } const telegramConnectUrl = computed(() => buildBotConnectUrl(config.public.telegramBotUrl || '')); const maxConnectUrl = computed(() => buildBotConnectUrl(config.public.maxBotUrl || '')); + +async function connectMessenger(channel: 'TELEGRAM' | 'MAX') { + const baseUrl = channel === 'TELEGRAM' ? telegramConnectUrl.value : maxConnectUrl.value; + if (!baseUrl) { + return; + } + + await openMessengerBot({ + channel, + baseUrl, + }); +}