Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
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
Теперь у нас кеширование будет работать только в том случае, если приложение запущено из телеграм веб бота.
Спасибо за чтение!