Skip to content

Create shared types crate for frontend-backend compatibility #148

@RAprogramm

Description

@RAprogramm

Problem

Currently, Telegram Mini Apps require using two separate crates for frontend and backend:

  • telegram-webapp-sdk - WASM frontend SDK with TelegramInitData, TelegramUser, etc.
  • init-data-rs - Backend validation with InitData, User, etc.

These crates have duplicate type definitions for the same Telegram data structures, leading to:

  1. Code duplication across crates
  2. Potential inconsistencies in field naming or types
  3. Manual conversion between frontend and backend types
  4. Increased maintenance burden when Telegram updates the API

Use Case

Full-stack Telegram Mini App development:

Frontend (WASM):

use telegram_webapp_sdk::TelegramInitData;

// Parse on client
let init_data = /* from SDK */;
send_to_backend(init_data);

Backend:

use init_data_rs::InitData;

// Re-parse and validate on server
let init_data = init_data_rs::parse(raw_string)?;
init_data_rs::validate(raw_string, bot_token)?;

The types are semantically identical but structurally separate.

Proposed Solution

Create a shared-types crate containing platform-agnostic Telegram type definitions:

[package]
name = "telegram-miniapp-types"
version = "0.1.0"

[dependencies]
serde = { version = "1", features = ["derive"] }

[features]
default = []
wasm = ["wasm-bindgen"]  # Optional WASM serialization

Shared types:

#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(wasm_bindgen::JsValue))]
pub struct InitData {
    pub auth_date: u64,
    pub hash: String,
    pub user: Option<User>,
    pub receiver: Option<User>,
    pub chat: Option<Chat>,
    pub chat_type: Option<ChatType>,
    pub chat_instance: Option<String>,
    pub query_id: Option<String>,
    pub start_param: Option<String>,
    pub can_send_after: Option<u32>,
    pub signature: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    pub id: u64,
    pub is_bot: Option<bool>,
    pub first_name: String,
    pub last_name: Option<String>,
    pub username: Option<String>,
    pub language_code: Option<String>,
    pub is_premium: Option<bool>,
    pub added_to_attachment_menu: Option<bool>,
    pub allows_write_to_pm: Option<bool>,
    pub photo_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chat {
    pub id: i64,
    pub r#type: ChatType,
    pub title: String,
    pub username: Option<String>,
    pub photo_url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChatType {
    #[serde(rename = "sender")]
    Sender,
    #[serde(rename = "private")]
    Private,
    #[serde(rename = "group")]
    Group,
    #[serde(rename = "supergroup")]
    Supergroup,
    #[serde(rename = "channel")]
    Channel,
}

telegram-webapp-sdk integration:

[dependencies]
telegram-miniapp-types = "0.1"
pub use telegram_miniapp_types::{InitData, User, Chat, ChatType};

// SDK-specific extensions
impl InitData {
    pub fn from_telegram_context() -> Result<Self, JsValue> {
        // Existing parsing logic
    }
}

init-data-rs integration:

[dependencies]
telegram-miniapp-types = "0.1"
pub use telegram_miniapp_types::{InitData, User, Chat, ChatType};

pub fn parse(init_data: &str) -> Result<InitData, ParseError> {
    // Existing parsing logic
}

pub fn validate(init_data: &str, bot_token: &str) -> Result<InitData, ValidationError> {
    // Existing validation logic
}

Benefits

  1. Single source of truth for Telegram type definitions
  2. Consistent API across frontend and backend
  3. Reduced maintenance - update once, works everywhere
  4. Better tooling - shared types enable better IDE support and type checking
  5. Easier testing - mock data works identically on both sides
  6. Future-proof - Telegram API updates require changes in one place

Migration Path

  1. Create telegram-miniapp-types crate with core types
  2. Both telegram-webapp-sdk and init-data-rs depend on it
  3. Re-export types from shared crate
  4. Mark old type definitions as deprecated with migration guide
  5. Remove deprecated types in next major version

Alternative: Feature Flag Approach

If creating a separate crate is too complex initially, add validation to SDK as optional feature:

[features]
server-validation = ["hmac-sha256", "ed25519-dalek"]

However, this pollutes the WASM bundle with unnecessary crypto code, so shared crate is preferred.

Ecosystem Impact

This pattern benefits the entire Telegram Mini Apps Rust ecosystem by establishing standard types that other crates can depend on.

Related Work

Similar pattern exists in other ecosystems:

  • jsonwebtoken crate for JWT types shared across auth libraries
  • http crate providing types for both client and server HTTP libraries

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions