Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backend Integration, Optimization, and Documentation #5

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,67 @@
<p align="center">
<br>
<img width="240" src="./assets/tapps.png" alt="logo of telegram web apps">
<img width="240" src="./assets/img/tapps.png" alt="logo of telegram web apps">
<br>
<br>
</p>

# Telegram Mini Apps Basic Example
This is a basic and straightforward Telegram Mini App(TMA) implemented using plain JavaScript, HTML, and CSS. This project aims to provide a minimalistic example of how to create a simple TWA and launch it within Telegram without relying on complex build tools or bleeding-edge libraries.

- App is available via direct link: https://t.me/simple_telegram_mini_app_bot/app
- Or you can launch app with a bot menu button: https://t.me/simple_telegram_mini_app_bot
- Deployment URL: [https://twa-dev.github.io/simple-telegram-web-app/](https://telegram-mini-apps-dev.github.io/vanilla-js-boilerplate/)
# Telegram Mini Apps Basic Example With BackEnd
This is a basic and straightforward Telegram Mini App(TMA) implemented using plain JavaScript, HTML, CSS and PHP. This project aims to provide a minimalistic example of how to create a simple TMA with back-end and launch it within Telegram without relying on complex build tools or bleeding-edge libraries.

## Features

- **Validation of initData**: Ensures that the initData sent by the Telegram MiniApp is valid by checking the HMAC hash and database for registered users.
- **Modal Alerts and Confirmations**: Displays different types of modals such as alerts, confirmations, and popups.
- **Link Management**: Opens links either within Telegram or in an external browser.
- **Dynamic UI Elements**: Adjusts the UI based on Telegram's webview properties like viewport size and expansion.
- **Eruda Integration**: Includes Eruda for mobile debugging.
- Minimalistic user interface.
- No external libraries or frameworks used.
- Easy to understand and modify.

## Getting Started
## Project Structure

- **/api/api.php**: Handles the server-side validation of initData from the Telegram MiniApp.
- **/assets/js/main.js**: Initializes and interacts with Telegram MiniApp features.
- **index.html**: Contains the front-end structure with basic interaction capabilities.

### Prerequisites

To run this example, you'll need a modern web browser with JavaScript enabled.
- A Telegram bot token with access to the MiniApp.
- A MySQL database for storing user information.

### Installation

1. Clone this repository to your local machine:

```bash
git clone https://github.com/Telegram-Mini-Apps-Dev/vanilla-js-boilerplate
git clone https://github.com/h0z3yn/basic-telegram-mini-app.git

2. Navigate to the project directory:

```bash
cd vanilla-js-boilerplate
cd basic-telegram-mini-app

3. Set up your database by creating a table for storing Telegram user data:

Open index.html in your preferred code editor or IDE.
```sql
CREATE TABLE `users` ( `telegramChatId` BIGINT NOT NULL PRIMARY KEY );

4. Edit `/api/api.php` to include your bot token and database credentials:

5. Serve the project on a local or remote server (e.g., using Apache, Nginx, or PHP's built-in server):

```bash
php -S localhost:8000

### Usage
1. Open index.html in your preferred code editor or IDE.
1. Open `index.html` and `/api/api.php` and `/assets/js/main.js` in your preferred code editor or IDE.
2. Make your changes
3. Create your own GitHub repository, commit and push your updates.
4. Go to your repository GitHub page and open Settings. Check the Pages tab and Build and deployment section. If GitHub Actions option was selected, assets should be deployed to Pages and there will be an URL like `https://<username>.github.io/simple-telegram-mini-app/`. You can copy this URL and use it with [BotFather](https://tg.me/BotFather) bot to create your very own TWA.
3. Integrate this app with your Telegram bot by adding the MiniApp URL (e.g., `https://yourserver.com/index.html`) to your bot's settings.
4. When users open the MiniApp through Telegram, their data will be validated, and appropriate content will be displayed based on the validation result.

### Customization
Feel free to customize this web app to suit your needs. You can modify the HTML, CSS, and JavaScript files as required.
Feel free to customize this web app to suit your needs. You can modify the PHP, HTML, CSS, and JavaScript files as required.

## Contributing
If you'd like to contribute to this project, please follow these steps:
Expand Down
85 changes: 85 additions & 0 deletions api/api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php
// Check if the request is a POST request and the 'initData' parameter is set
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['initData'])) {

// Telegram bot token and database connection details
$botToken = 'real telegram bot token'; // The bot token provided by Telegram
$hostname = 'host name'; // Hostname of the MySQL server
$database = 'database name'; // Database name
$username = 'database user'; // MySQL username
$password = 'database password'; // MySQL password

// PDO options for MySQL connection
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Enable exception handling for errors
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // Fetch results as associative arrays
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci" // Set character encoding
];

try {
// Create a new PDO connection to the MySQL database
$pdo = new PDO("mysql:host=$hostname;dbname=$database", $username, $password, $options);
} catch (PDOException $e) {
// Return a JSON response with a failure message if the database connection fails
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database connection failed']);
exit;
}

// Retrieve and process 'initData' sent via POST
$initData = $_POST['initData'];
parse_str($initData, $params); // Parse the 'initData' string into an associative array
$hash = $params['hash']; // Extract the hash from the parameters
unset($params['hash']); // Remove the hash from the parameters for further processing

// Sort parameters alphabetically and create a data string for verification
ksort($params);
$dataCheckString = urldecode(http_build_query($params)); // Generate a string for hash comparison

// Generate a HMAC signature using the bot token and WebAppData as the key
$secretKey = hash_hmac('sha256', $botToken, 'WebAppData', true); // Generate the secret key
$hmac = hash_hmac('sha256', $dataCheckString, $secretKey); // Create a hash of the data string

$response = []; // Initialize an empty array for the response

// Verify that the generated HMAC matches the hash provided by the client
if (hash_equals($hmac, $hash)) {
$authDate = (int)$params['auth_date']; // Extract the 'auth_date' parameter
if (time() - $authDate < 3600) { // Check if the auth date is within the last hour
$user = json_decode($params['user'], true); // Parse the 'user' parameter as a JSON object
$userId = $user['id']; // Get the user ID from the parsed user object

try {
// Query the database to check if the user exists based on their Telegram chat ID
$stmt = $pdo->prepare('SELECT count(*) FROM `users` WHERE telegramChatId = ?');
$stmt->execute([$userId]); // Execute the query with the user ID
$numberOfUsers = $stmt->fetchColumn(); // Get the number of users found with this ID
} catch (PDOException $e) {
// Return a JSON response with a failure message if there's a database error
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database error: ' . $e->getMessage()]);
exit;
}

// If the user exists in the database, return a success response
if ($numberOfUsers > 0) {
$response = ['success' => true];
} else {
// If the user is not found, return a failure message
$response = ['success' => false, 'message' => 'User not found'];
}
} else {
// If the authentication date is too old, return a failure message
$response = ['success' => false, 'message' => 'Data is outdated'];
}
} else {
// If the HMAC verification fails, return a failure message
$response = ['success' => false, 'message' => 'Data is invalid'];
}

// Set the response content type to JSON and return the response
header('Content-Type: application/json');
echo json_encode($response);
exit; // Terminate the script after sending the response
}
?>
47 changes: 47 additions & 0 deletions assets/css/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
body {
--bg-color: var(--tg-theme-bg-color);
font: 12px/18px "Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, Verdana, sans-serif;
background-color: var(--bg-color);
color: var(--tg-theme-text-color);
margin: 48px 24px;
padding: 0;
color-scheme: var(--tg-color-scheme);
}

a {
color: var(--tg-theme-link-color);
}

#viewport {
position: fixed;
left: 0;
right: 0;
top: 0;
height: var(--tg-viewport-stable-height, 100vh);
pointer-events: none;
transition: all 0.2s ease;
}

#loader {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border: 16px solid #f3f3f3;
border-top: 16px solid #3498db;
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
z-index: 9999;
}

@keyframes spin {
0% {
transform: rotate(0deg);
}

100% {
transform: rotate(360deg);
}
}
File renamed without changes
145 changes: 145 additions & 0 deletions assets/js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Initialize the Telegram Web App (TWA) when the page is ready
Telegram.WebApp.ready();

// Get references to various buttons and links from the HTML page
const alertBtn = document.getElementById('alertBtn'); // Button to show an alert
const confirmBtn = document.getElementById('confirmBtn'); // Button to show a confirmation dialog
const popupBtn = document.getElementById('popupBtn'); // Button to show a popup
const telegramLink = document.getElementById('telegramLink'); // Button to open a Telegram link
const externalLink = document.getElementById('externalLink'); // Button to open an external link
const instantViewLink = document.getElementById('instantViewLink'); // Button to open a link with instant view
const expandBtn = document.getElementById('expandBtn'); // Button to expand the WebApp view
const toggleMainBtn = document.getElementById('toggleMainBtn'); // Button to toggle the visibility of the main button
const validateBtn = document.getElementById('validateBtn'); // Button to validate initData

// Event listener to show an alert when 'alertBtn' is clicked
alertBtn.addEventListener('click', () => {
Telegram.WebApp.showAlert('Hello World!'); // Displays a simple alert with a message
});

// Event listener to show a confirmation dialog when 'confirmBtn' is clicked
confirmBtn.addEventListener('click', showConfirm);

// Event listener to show a popup when 'popupBtn' is clicked
popupBtn.addEventListener('click', showPopup);

// Event listener to open a specific Telegram link when 'telegramLink' is clicked
telegramLink.addEventListener('click', () => {
Telegram.WebApp.openTelegramLink('https://t.me/trendingapps');
});

// Event listener to open an external link in the browser when 'externalLink' is clicked
externalLink.addEventListener('click', () => {
Telegram.WebApp.openLink('https://ton.org/');
});

// Event listener to open a link with instant view when 'instantViewLink' is clicked
instantViewLink.addEventListener('click', () => {
Telegram.WebApp.openLink('https://telegra.ph/api', {
try_instant_view: true // Tries to open the link in Telegram Instant View mode
});
});

// Event listener to expand the WebApp view when 'expandBtn' is clicked
expandBtn.addEventListener('click', () => {
Telegram.WebApp.expand(); // Expands the web app to full screen within Telegram
});

// Event listener to toggle the visibility of the Telegram main button
toggleMainBtn.addEventListener('click', toggleMainButton);

// Event listener to validate the initData when 'validateBtn' is clicked
validateBtn.addEventListener('click', validateInitData);

// Function to show a popup with multiple buttons
function showPopup() {
Telegram.WebApp.showPopup({
title: 'Title', // Popup title
message: 'Some message', // Popup message
buttons: [
{
id: 'link', // Button ID
type: 'default', // Button type
text: 'Open ton.org' // Button text
},
{
type: 'cancel' // A cancel button
}
]
}, (btn) => {
// If the 'link' button is clicked, open ton.org in the browser
if (btn === 'link') {
Telegram.WebApp.openLink('https://ton.org/');
}
});
}

// Function to show a confirmation dialog with a callback
function showConfirm() {
Telegram.WebApp.showConfirm('Do you want to open ton.org?', (result) => {
// If the user confirms, open ton.org in the browser
if (result) {
Telegram.WebApp.openLink('https://ton.org/');
}
});
}

// Function to toggle the visibility of the Telegram MainButton
function toggleMainButton() {
// If the main button is visible, hide it. Otherwise, show it.
if (Telegram.WebApp.MainButton.isVisible) {
Telegram.WebApp.MainButton.hide(); // Hide the main button
} else {
Telegram.WebApp.MainButton.show(); // Show the main button
}
}

// Function to set viewport data, displaying the window size and whether the app is expanded
function setViewportData() {
const sizeEl = document.getElementById('viewport-params-size');
// Display the current width and height of the viewport
sizeEl.innerText = `width: ${window.innerWidth} x height: ${Telegram.WebApp.viewportStableHeight}`;

const expandEl = document.querySelector('#viewport-params-expand');
// Display whether the WebApp is expanded or not
expandEl.innerText = `Is Expanded: ${Telegram.WebApp.isExpanded ? 'true' : 'false'}`;
}

// Set the header color for the WebApp
Telegram.WebApp.setHeaderColor('secondary_bg_color');

// Set the initial viewport data when the app loads
setViewportData();

// Listen for viewport changes and update the display when the size changes
Telegram.WebApp.onEvent('viewportChanged', setViewportData);

// Function to validate the Telegram WebApp initData
async function validateInitData() {
const initData = Telegram.WebApp.initData; // Get the initData from the WebApp
try {
// Send initData to the server for validation via a POST request
const response = await fetch('/api/api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded' // Set the content type
},
body: `initData=${encodeURIComponent(initData)}` // Send the initData as the POST body
});

const data = await response.json(); // Parse the response as JSON
document.getElementById('loader').style.display = 'none'; // Hide the loader
if (data.success) {
// If the validation is successful, show the content
document.getElementById('content').style.display = 'block';
} else {
// If validation fails, show an alert with the error message
Telegram.WebApp.showAlert('Data is invalid: ' + data.message);
}
} catch (error) {
// Log the error and show an alert if the validation fails
console.error('Error:', error);
document.getElementById('loader').style.display = 'none'; // Hide the loader
Telegram.WebApp.showAlert('An error occurred during validation');
}
}
Loading