15 апреля команда телеграм предоставила нам возможность разрабатывать веб ботов.

Теперь взаимодействие с ботами стало очень интерактивным, так как мы обладаем возможностью интегрировать в ботов полноценные веб-приложения.

В процессе разработки веб апп бота я столкнулся с проблемой, что после закрытия интерфейса веб апп и повторном его открытии мы теряем последнюю ссылку на которой находился пользователь, и веб приложение открывается с главной страницы.

Сперва, может показаться, что это не является проблемой, но на самом деле очень легко случайно закрыть веб-окно, так как оно не открывается на весь экран, а чтобы закрыть его, достаточно потянуть его вниз и мы теряем всё предыдущее состояние приложения.

В своём приложении я использую React, mobX и React Router Dom v6. Первое, что приходит в голову, это подписаться на изменение навигации и сохранять последнее значение в localStorage или cookies, я выбрал cookie, с временем жизни 10 минут.

Давайте посмотрим на код. Создаём location-provider.tsx

import React from "react"
import { useLocation, useNavigate } from "react-router-dom"
import Cookies from "js-cookie"

interface LocationProviderProps {
	children: React.ReactNode
}

const LocationProvider: React.FC<LocationProviderProps> = ({ children }) => {
  const location = useLocation()
  const navigate = useNavigate()

  React.useEffect(() => {
    const currentLocation = Cookies.get("location_app")
    if (!currentLocation) return
    navigate(currentLocation)
  }, [])

  React.useEffect(() => {
    const tenMinutes = new Date(new Date().getTime() + 10 * 60 * 1000)
    Cookies.set("location_app", location.pathname, {
      expires: tenMinutes,
    })
  }, [location])

	return <>{children}</>
}

export default LocationProvider

Импортируем его в App.tsx и оборачиваем наши роуты

import React from "react"
import { BrowserRouter, Route, Routes } from "react-router-dom"
import LocationProvider from "providers/location-provider"

import { AppsPage } from "./pages/apps"
import { DevelopersPage } from "./pages/developers"
import { FavoritesPage } from "./pages/favorites"
import { SearchPage } from "./pages/search"

function App() {
  return (
    <BrowserRouter>
      <LocationProvider>
        <Routes>
          <Route index element={<AppsPage />} />
          <Route path="/apps" element={<AppsPage />} />
          <Route path="/apps/:appId" element={<AppDetailPage />} />
          <Route path="/favorites" element={<FavoritesPage />} />
          <Route path="/search" element={<SearchPage />} />
          <Route path="/developers" element={<DevelopersPage />} />
        </Routes>
      </LocationProvider>
    </BrowserRouter>
  )
}

export default App

Отлично, теперь после закрытия и открытия веб приложения у нас будет открываться последняя страница, но!

Тут есть проблема, это то, что если при открытом приложении перейти на другую страницу через строку ввода URL, нас будет редиректить назад. Но у веб ботов нет адресной строки, значит на этом можно остановиться?

Да, если вам этого достаточно, то это будет работать в веб ботах. Но у вас будут проблемы при открытии вашего веб приложения вне веб ботов, например у вас есть онлайн магазин, вы бы хотели взаимодействовать с ним и в телеграм веб апп и просто как веб приложение доступное по адресу, что тогда?

Думаем дальше, в DOM API у объекта window есть event onbeforeunload, кажется это то, что нам нужно, будем сохранять последний URL перед закрытием веб приложения, пробуем.

Обновим наш location-provider.tsx

import React from "react"
import { useLocation, useNavigate } from "react-router-dom"
import Cookies from "js-cookie"

interface LocationProviderProps {
	children: React.ReactNode
}

const LocationProvider: React.FC<LocationProviderProps> = ({ children }) => {
  const location = useLocation()
  const navigate = useNavigate()
  
  React.useEffect(() => {
    const currentLocation = Cookies.get("location_app")
    if (!currentLocation) return
    navigate(currentLocation)
  }, [])

  React.useEffect(() => {
    window.onbeforeunload = (e: BeforeUnloadEvent) => {
    	e.preventDefault()
    	const tenMinutes = new Date(new Date().getTime() + 10 * 60 * 1000)
      Cookies.set("location_app", location.pathname, {
      	expires: tenMinutes,
      })
    }
  }, [location])
  return <>{children}</>
}

export default LocationProvider

Пробуем гулять по приложению через адресную строку, редиректа назад нет, отлично, так же сохраняется ссылка при закрытии вкладки с приложением, то что мы и хотели! Пробуем в телеграм, бац и тут это совсем не работает, почему?

Дело в том, что телеграм открывает браузерное окно в так называемом WebView, я не силен в разработке мобильных приложений, но знаю, что эта штука позволяет открывать браузер внутри мобильных приложений, но у них есть свои ограничения и это как раз одно из них.

Что тогда? Отличным решением будет вернуться к первому варианту, но включить кеширование только для телеграм веб апп. Давайте посмотрим на то, что у нас получилось.

import React from "react"
import { useLocation, useNavigate } from "react-router-dom"
import Cookies from "js-cookie"

interface LocationProviderProps {
	children: React.ReactNode
}

const LocationProvider: React.FC<LocationProviderProps> = ({ children }) => {
	if (!window.Telegram.WebApp.initData.length) {
		return <>{children}</>
	} else {
		return <LocationProviderInner>{children}</LocationProviderInner>
	}
}

const LocationProviderInner: React.FC<LocationProviderProps> = ({
	children,
}) => {
  const location = useLocation()
  const navigate = useNavigate()

  const redirectToLastPage = () => {
    const currentLocation = Cookies.get("location_app")
    if (!currentLocation) return
    navigate(currentLocation)
  }
  
  React.useEffect(() => {
    if (location.key === "default") redirectToLastPage()

    Cookies.set("location_app", location.pathname, {
      expires: new Date(new Date().getTime() + 10 * 60 * 1000), // 10 min
    })
  }, [location.key])

	return <>{children}</>
}

export default LocationProvider

Теперь у нас кеширование будет работать только в том случае, если приложение запущено из телеграм веб бота.

Спасибо за чтение!

Комментарии (2)


  1. Saiv46
    09.07.2022 08:10

    Telegram Web Apps? Скорее похоже на проприетарный PWA (как у WeChat, VK и других).


  1. homerboy19520
    11.07.2022 07:23

    Не исключаю что есть более логичный вариант, но как один из - кэшировать актуальную страницу в localStorage, и перед инициализацией проверять стор на актуальную страницу, если есть - открываем ее, если нет открываем "/"