telegram-mini-apps-react

📁 nailorsh/agents_utils 📅 6 days ago
8
总安装量
4
周安装量
#34657
全站排名
安装命令
npx skills add https://github.com/nailorsh/agents_utils --skill telegram-mini-apps-react

Agent 安装分布

codex 4
gemini-cli 4
opencode 3
github-copilot 3
antigravity 2
kimi-cli 2

Skill 文档

Telegram Mini Apps with React

This skill provides comprehensive guidance for building Telegram Mini Apps using React and the @tma.js/sdk-react package.

Overview

Telegram Mini Apps are web applications displayed inside Telegram’s WebView. They integrate with Telegram’s native UI components (Back Button, Main Button) and have access to user data, theme parameters, and platform-specific features.

Key concepts:

  • Mini Apps are add-ons for Telegram Bots
  • They run inside Telegram’s WebView
  • They communicate with Telegram client via events and methods
  • They have access to launch parameters, init data, and theme settings

Quick Start

1. Installation

# For React projects, install the React-specific package
pnpm i @tma.js/sdk-react

# DO NOT install both @tma.js/sdk and @tma.js/sdk-react - this causes bugs!

Important: The @tma.js/sdk-react package fully re-exports @tma.js/sdk, so you don’t need to install them separately.

2. Create New Project (Optional)

pnpm dlx @tma.js/create-mini-app@latest
# or
npx @tma.js/create-mini-app@latest

This CLI scaffolds a complete project with proper configuration.

Project Structure

A typical Telegram Mini App React project structure:

src/
├── main.tsx          # Entry point - SDK initialization
├── init.ts           # SDK configuration and component mounting
├── mockEnv.ts        # Development environment mocking
├── App.tsx           # Main React app with routing
├── components/
│   ├── Page.tsx      # Page wrapper with back button handling
│   └── EnvUnsupported.tsx  # Fallback for non-TG environments
├── hooks/
│   └── useDeeplink.ts      # Deep linking handler
└── services/
    └── analytics.ts        # Analytics with user data

Core Concepts

SDK Initialization

The SDK must be initialized before using any features. See references/init.md for detailed implementation.

import { 
  init as initSDK,
  setDebug,
  themeParams,
  miniApp,
  viewport,
  backButton,
  swipeBehavior,
  initData
} from '@tma.js/sdk-react';

export async function init(options: {
  debug: boolean;
  eruda: boolean;
  mockForMacOS: boolean;
}): Promise<void> {
  // Enable debug mode for development
  setDebug(options.debug);
  
  // Initialize the SDK (REQUIRED before using any features)
  initSDK();

  // Mount components you'll use in the app
  backButton.mount.ifAvailable();
  initData.restore();

  // Configure swipe behavior
  if (swipeBehavior.isSupported()) {
    swipeBehavior.mount();
    swipeBehavior.disableVertical();
  }

  // Setup Mini App theming
  if (miniApp.mount.isAvailable()) {
    themeParams.mount();
    miniApp.mount();
    themeParams.bindCssVars();  // Binds theme to CSS variables
  }

  // Configure viewport
  if (viewport.mount.isAvailable()) {
    viewport.mount().then(() => {
      viewport.bindCssVars();
      viewport.requestFullscreen();
    });
  }
}

Entry Point (main.tsx)

import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import { retrieveLaunchParams } from '@tma.js/sdk-react';
import { init } from './init';
import App from "./App";
import { EnvUnsupported } from "./components/EnvUnsupported";

// Mock environment for local development
import './mockEnv';

const root = ReactDOM.createRoot(document.getElementById('root')!);

try {
  const launchParams = retrieveLaunchParams();
  const { tgWebAppPlatform: platform } = launchParams;
  const debug = (launchParams.tgWebAppStartParam || '').includes('debug')
    || import.meta.env.DEV;

  await init({
    debug,
    eruda: debug && ['ios', 'android'].includes(platform),
    mockForMacOS: platform === 'macos',
  }).then(() => {
    root.render(
      <StrictMode>
        <App/>
      </StrictMode>,
    );
  });
} catch (e) {
  // Show fallback UI when not in Telegram
  root.render(<EnvUnsupported/>);
}

Using Back Button

The Back Button is a native Telegram UI element that appears in the header.

import { useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { backButton, miniApp } from '@tma.js/sdk-react';

export function Page({ children, back = true }) {
  const navigate = useNavigate();
  const location = useLocation();

  useEffect(() => {
    if (back) {
      backButton.show();
      
      // onClick returns a cleanup function
      return backButton.onClick(() => {
        const isDeeplink = location.state?.fromDeeplink;
        const isFirstPage = !window.history.state || window.history.state.idx === 0;

        if (isDeeplink || isFirstPage) {
          miniApp.close();  // Close the Mini App
        } else {
          navigate(-1);     // Go back in history
        }
      });
    }
    
    backButton.hide();
  }, [back, navigate, location]);

  return <>{children}</>;
}

Using Signals (useSignal Hook)

Signals are reactive values that update automatically. Use useSignal to subscribe to them in React:

import { useEffect } from 'react';
import { backButton, useSignal } from '@tma.js/sdk-react';

function BackButtonStatus() {
  const isVisible = useSignal(backButton.isVisible);

  useEffect(() => {
    console.log('Back button is', isVisible ? 'visible' : 'hidden');
  }, [isVisible]);

  return null;
}

Getting Init Data (User Info)

Init data contains user information and can be used for authentication:

import { initData } from '@tma.js/sdk-react';

function getUserId(): number | undefined {
  try {
    const user = initData.user();
    return user?.id;
  } catch (e) {
    return undefined;
  }
}

// Get start parameter (for deep linking)
const startParam = initData.startParam();

Launch Parameters

Launch parameters contain platform info, theme, and app data:

import { retrieveLaunchParams, useLaunchParams } from '@tma.js/sdk-react';

// In component
function Component() {
  const launchParams = useLaunchParams();
  // launchParams.tgWebAppPlatform - 'ios', 'android', 'macos', 'tdesktop', 'web', 'weba'
  // launchParams.tgWebAppVersion - SDK version supported by client
  // launchParams.tgWebAppData - init data
  // launchParams.tgWebAppThemeParams - theme colors
  // launchParams.tgWebAppStartParam - custom start parameter
}

// Outside component
const launchParams = retrieveLaunchParams();

Theming

Theme parameters are automatically provided by Telegram. Bind them to CSS variables:

import { themeParams, miniApp } from '@tma.js/sdk-react';

// During initialization
if (miniApp.mount.isAvailable()) {
  themeParams.mount();
  miniApp.mount();
  themeParams.bindCssVars();  // Creates CSS variables like --tg-theme-bg-color
}

Available CSS variables after binding:

  • --tg-theme-bg-color
  • --tg-theme-text-color
  • --tg-theme-hint-color
  • --tg-theme-link-color
  • --tg-theme-button-color
  • --tg-theme-button-text-color
  • --tg-theme-secondary-bg-color
  • --tg-theme-header-bg-color
  • --tg-theme-accent-text-color
  • --tg-theme-section-bg-color
  • --tg-theme-section-header-text-color
  • --tg-theme-subtitle-text-color
  • --tg-theme-destructive-text-color

Viewport and Safe Areas

Handle viewport and safe areas for proper layout:

import { viewport } from '@tma.js/sdk-react';

if (viewport.mount.isAvailable()) {
  viewport.mount().then(() => {
    viewport.bindCssVars();        // Binds viewport dimensions to CSS
    viewport.requestFullscreen();   // Request fullscreen mode
  });
}

Available CSS variables:

/* Safe area insets */
padding-top: var(--tg-viewport-safe-area-inset-top, 0);
padding-bottom: var(--tg-viewport-safe-area-inset-bottom, 0);

/* Content safe area (for notch, etc.) */
padding-top: var(--tg-viewport-content-safe-area-inset-top, 0);

/* Viewport dimensions */
height: var(--tg-viewport-height);
width: var(--tg-viewport-width);

Usage in CSS:

.header {
  padding-top: max(2rem, calc(var(--tg-viewport-content-safe-area-inset-top, 0) + var(--tg-viewport-safe-area-inset-top, 0)));
}

.footer {
  padding-bottom: calc(1rem + var(--tg-viewport-safe-area-inset-bottom, 0));
}

Development Environment Mocking

For local development outside Telegram, mock the environment. See references/mock-env.md.

import { emitEvent, isTMA, mockTelegramEnv } from '@tma.js/sdk-react';

if (import.meta.env.DEV) {
  if (!await isTMA('complete')) {
    const themeParams = {
      accent_text_color: '#6ab2f2',
      bg_color: '#17212b',
      button_color: '#5288c1',
      button_text_color: '#ffffff',
      destructive_text_color: '#ec3942',
      header_bg_color: '#17212b',
      hint_color: '#708499',
      link_color: '#6ab3f3',
      secondary_bg_color: '#232e3c',
      section_bg_color: '#17212b',
      section_header_text_color: '#6ab3f3',
      subtitle_text_color: '#708499',
      text_color: '#f5f5f5',
    };

    mockTelegramEnv({
      onEvent(e) {
        if (e.name === 'web_app_request_theme') {
          return emitEvent('theme_changed', { theme_params: themeParams });
        }
        if (e.name === 'web_app_request_viewport') {
          return emitEvent('viewport_changed', {
            height: window.innerHeight,
            width: window.innerWidth,
            is_expanded: true,
            is_state_stable: true,
          });
        }
        if (e.name === 'web_app_request_safe_area') {
          return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
        }
      },
      launchParams: new URLSearchParams([
        ['tgWebAppThemeParams', JSON.stringify(themeParams)],
        ['tgWebAppData', new URLSearchParams([
          ['auth_date', (Date.now() / 1000 | 0).toString()],
          ['hash', 'mock-hash'],
          ['signature', 'mock-signature'],
          ['user', JSON.stringify({ id: 1, first_name: 'Developer' })],
        ]).toString()],
        ['tgWebAppVersion', '8.4'],
        ['tgWebAppPlatform', 'tdesktop'],
      ]),
    });

    console.info('⚠️ Running in mocked Telegram environment');
  }
}

Deep Linking

Handle start parameters for deep linking. See references/deeplink.md.

import { useEffect, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { initData } from "@tma.js/sdk-react";

export function useDeeplink() {
  const navigate = useNavigate();
  const processedRef = useRef(false);

  useEffect(() => {
    if (processedRef.current) return;

    const startParam = initData.startParam();
    if (!startParam) return;

    processedRef.current = true;

    try {
      // startParam is base64url encoded
      const base64 = startParam.replace(/-/g, '+').replace(/_/g, '/');
      const decoded = atob(base64);
      const params = new URLSearchParams(decoded);
      
      const route = params.get('route');
      if (route) {
        navigate(route, { replace: true, state: { fromDeeplink: true } });
      }
    } catch (e) {
      console.error("Failed to parse startParam:", e);
    }
  }, [navigate]);
}

Best Practices

1. Always Check Availability

Before using any method, check if it’s available:

import { backButton } from '@tma.js/sdk-react';

// Option 1: Check before calling
if (backButton.show.isAvailable()) {
  backButton.show();
}

// Option 2: Call only if available (safer, no-op if unavailable)
backButton.show.ifAvailable();

// Option 3: Mount only if available
backButton.mount.ifAvailable();

2. Mount Components Before Use

Components must be mounted before their methods can be used:

// ❌ Wrong - will throw error
backButton.show();

// ✅ Correct
backButton.mount();
backButton.show();

3. Handle macOS Bugs

Telegram for macOS has known issues:

if (platform === 'macos') {
  mockTelegramEnv({
    onEvent(event, next) {
      if (event.name === 'web_app_request_theme') {
        const tp = themeParams.state() || retrieveLaunchParams().tgWebAppThemeParams;
        return emitEvent('theme_changed', { theme_params: tp });
      }
      if (event.name === 'web_app_request_safe_area') {
        return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 });
      }
      next();
    },
  });
}

4. Don’t Install Duplicate SDKs

Never install both @tma.js/sdk and @tma.js/sdk-react:

// ❌ Wrong - causes bugs
{
  "dependencies": {
    "@tma.js/sdk": "^3.0.0",
    "@tma.js/sdk-react": "^3.0.8"
  }
}

// ✅ Correct - only the React package
{
  "dependencies": {
    "@tma.js/sdk-react": "^3.0.8"
  }
}

5. Disable Swipe When Needed

Prevent accidental navigation:

if (swipeBehavior.isSupported()) {
  swipeBehavior.mount();
  swipeBehavior.disableVertical();  // Prevents swipe-to-close
}

Sending Init Data to Server

For authentication, send init data to your server:

import { retrieveRawInitData } from '@tma.js/sdk-react';

const initDataRaw = retrieveRawInitData();

fetch('https://api.example.com/auth', {
  method: 'POST',
  headers: {
    Authorization: `tma ${initDataRaw}`,
  },
});

Server-side validation:

  • Use @tma.js/init-data-node for Node.js
  • Validate the hash using your bot token
  • Never trust init data without validation

Supported Platforms

Mini Apps work on:

  • android – Telegram for Android
  • ios – Telegram for iOS
  • macos – Telegram for macOS (has some bugs)
  • tdesktop – Telegram Desktop
  • weba – Telegram Web A
  • web – Telegram Web K

Common Issues

SDK not initialized error

Make sure to call init() before using any SDK features.

Component not mounted error

Mount the component before calling its methods:

backButton.mount();
backButton.show();

Method not available error

Check availability before calling:

if (backButton.show.isAvailable()) {
  backButton.show();
}

App crashes outside Telegram

Use environment mocking during development and provide a fallback UI.

Additional Resources

See Also