Product launch on messenger apps: Telegram web app opportunities for businesses
10 minutes
Frontend
Mar 4, 2025
15 minutes
<html lang={locale} className={`${GeologicaFont.variable}`}>
<body>
<TelegramSDKInitProvider>
<AppInitProvider>
<div className={styles.layout}>{children}</div>
</AppInitProvider>
</TelegramSDKInitProvider>
</body>
</html>
'use client'
import { type PropsWithChildren } from 'react'
import { useClientOnce } from '@/src/hooks/useClientOnce'
import { useTelegramMock } from '@/src/hooks/useTelegramMock'
import { telegramSDKInit } from './init'
export function TelegramSDKInitProvider({ children }: PropsWithChildren) {
if (process.env.NODE_ENV === 'dev') {
useTelegramMock()
}
useClientOnce(() => {
telegramSDKInit(false)
})
return children
}
import { isTMA, mockTelegramEnv, parseInitData } from '@telegram-apps/sdk-react'
import { useClientOnce } from './useClientOnce'
export function useTelegramMock(): void {
useClientOnce(() => {
let shouldMock: boolean
const MOCK_KEY = '____mocked'
if (isTMA('simple')) {
shouldMock = !!sessionStorage.getItem(MOCK_KEY)
} else {
shouldMock = true
}
if (!shouldMock) {
return
}
const initDataRaw = new URLSearchParams([
[
'user',
JSON.stringify({
id: 99281932,
first_name: 'Arthur',
last_name: 'Mocked',
username: 'arthurmocked',
language_code: 'en',
is_premium: true,
allows_write_to_pm: true,
photoUrl:
'https://t.me/i/userpic/320/haNXSpRmeucJyo-oXFbNrifxZ-Au0PjvhpJ2l6h4ozcTbTT8yvNKIZABSCLpjtIp.svg',
}),
],
['hash', '89d6079ad6762351f38c6dbbc41bb53048019256a9443988af7a48bcad16ba31'],
['auth_date', '1716922846'],
['start_param', 'debug'],
['chat_type', 'sender'],
['chat_instance', '8428209589180549439'],
]).toString()
mockTelegramEnv({
themeParams: {
accentTextColor: '#6ab2f2',
bgColor: '#17212b',
buttonColor: '#5288c1',
buttonTextColor: '#ffffff',
destructiveTextColor: '#ec3942',
headerBgColor: '#17212b',
hintColor: '#708499',
linkColor: '#6ab3f3',
secondaryBgColor: '#232e3c',
sectionBgColor: '#17212b',
sectionHeaderTextColor: '#6ab3f3',
subtitleTextColor: '#708499',
textColor: '#f5f5f5',
},
initData: parseInitData(initDataRaw),
initDataRaw,
version: '8',
platform: 'tdesktop',
})
sessionStorage.setItem(MOCK_KEY, '1')
console.info(
'⚠️ As long as the current environment was not considered as the Telegram-based one, it was mocked. Take a note, that you should not do it in production and current behavior is only specific to the development process. Environment mocking is also applied only in development mode. So, after building the application, you will not see this behavior and related warning, leading to crashing the application outside Telegram.',
)
})
}
import {
$debug,
backButton,
expandViewport,
init as initSDK,
initData,
miniApp,
swipeBehavior,
themeParams,
viewport,
} from '@telegram-apps/sdk-react'
export function telegramSDKInit(debug: boolean): void {
$debug.set(debug)
initSDK()
expandViewport()
if (backButton.isSupported()) {
backButton.mount()
}
miniApp.mount()
themeParams.mount()
swipeBehavior.mount()
initData.restore()
void viewport
.mount()
.then(() => {
viewport.bindCssVars()
miniApp.bindCssVars()
themeParams.bindCssVars()
viewport.requestFullscreen()
swipeBehavior.disableVertical()
})
.catch((e: unknown) => {
console.error('Something went wrong mounting the viewport', e)
})
debug && import('eruda').then((lib) => lib.default.init()).catch(console.error)
}
'use client'
import { initData, useSignal } from '@telegram-apps/sdk-react'
import { setCookie } from 'cookies-next'
import type { PropsWithChildren } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { postAuthLogin } from '@/src/api/schema'
import TelegramMiniAppLoader from '@/src/components/ui/TelegramMiniAppLoader'
import Title from '@/src/components/ui/Title'
import { AUTH_TOKEN, TELEGRAM_USER_ID } from '@/src/constants'
const AppInitProvider = ({ children }: PropsWithChildren) => {
const [isAppReady, setIsAppReady] = useState(false)
const [error, setError] = useState<string | null>(null)
const initDataState = useSignal(initData.state)
const initDataStateRaw = useSignal(initData.raw)
const userId = initDataState?.user?.id
const logError = useCallback((message: string) => {
console.error(message)
setError(message)
}, [])
const authenticateUser = useCallback(
async (rawData: string) => {
try {
const { data } = await postAuthLogin({ init_data: rawData })
if (data) {
setCookie(AUTH_TOKEN, data.token)
setIsAppReady(true)
} else {
logError(`Auth token is missing. Response: ${JSON.stringify(data)}`)
}
} catch (err) {
logError(
`Authentication failed: ${err instanceof Error ? err.message : JSON.stringify(err, null, 2)}`,
)
}
},
[logError],
)
const initApp = useCallback(() => {
if (!userId) {
logError(`Telegram user ID is absent. User ID: ${userId}`)
return
}
setCookie(TELEGRAM_USER_ID, userId)
if (!initDataStateRaw) {
logError('Telegram raw data is undefined.')
return
}
authenticateUser(initDataStateRaw)
}, [userId, initDataStateRaw, authenticateUser, logError])
useEffect(() => {
console.log('Initializing app with raw data:', initDataStateRaw)
initApp()
}, [initApp, initDataStateRaw])
if (error) {
return <Title>{error}</Title>
}
return isAppReady ? <>{children}</> : <TelegramMiniAppLoader />
}
export default AppInitProvider
https://t.me/botusername/appname?startapp=param1value__param2value__par
am3value
const [param1, param2, param3] = lp.startParam.split("__");