mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Add Node.js polyfills and enhance Telegram connection handling
- Introduced polyfills for Node.js globals (Buffer, process, util) to support Telegram package functionality in the browser. - Updated Vite configuration to include node polyfills, ensuring compatibility with Node.js APIs. - Enhanced TelegramConnectionModal to manage connection states, including loading and error handling, improving user experience during authentication. - Implemented QR code generation and handling for Telegram login, with robust error management and user feedback. These changes establish a solid foundation for Telegram integration, enhancing the application's ability to handle real-time communication and user authentication effectively.
This commit is contained in:
+8
-1
@@ -15,7 +15,11 @@
|
||||
"@tauri-apps/plugin-deep-link": "^2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"buffer": "^6.0.3",
|
||||
"lottie-react": "^2.4.1",
|
||||
"os-browserify": "^0.3.0",
|
||||
"process": "^0.11.10",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-redux": "^9.2.0",
|
||||
@@ -24,12 +28,14 @@
|
||||
"redux-persist": "^6.0.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"telegram": "^2.26.22",
|
||||
"util": "^0.12.5",
|
||||
"zustand": "^5.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@types/node": "^25.0.10",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/redux-logger": "^3.0.13",
|
||||
@@ -38,6 +44,7 @@
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4"
|
||||
"vite": "^7.0.4",
|
||||
"vite-plugin-node-polyfills": "^0.25.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { QRCodeSVG } from 'qrcode.react';
|
||||
import { useAppDispatch, useAppSelector } from '../store/hooks';
|
||||
import {
|
||||
initializeTelegram,
|
||||
connectTelegram,
|
||||
checkAuthStatus,
|
||||
setAuthStatus,
|
||||
setAuthError,
|
||||
setConnectionStatus,
|
||||
} from '../store/telegramSlice';
|
||||
import { selectIsInitialized, selectConnectionStatus, selectAuthStatus } from '../store/telegramSelectors';
|
||||
import { mtprotoService } from '../services/mtprotoService';
|
||||
|
||||
interface TelegramConnectionModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -6,38 +18,176 @@ interface TelegramConnectionModalProps {
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
type ConnectionStep = 'qr' | '2fa';
|
||||
type ConnectionStep = 'qr' | '2fa' | 'loading' | 'error';
|
||||
|
||||
const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnectionModalProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const isInitialized = useAppSelector(selectIsInitialized);
|
||||
const connectionStatus = useAppSelector(selectConnectionStatus);
|
||||
const authStatus = useAppSelector(selectAuthStatus);
|
||||
|
||||
const [currentStep, setCurrentStep] = useState<ConnectionStep>('qr');
|
||||
const [password, setPassword] = useState('');
|
||||
const [passwordHint, setPasswordHint] = useState<string | undefined>();
|
||||
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
|
||||
const [qrCodeExpires, setQrCodeExpires] = useState<number | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
|
||||
// Store password promise resolver
|
||||
const passwordResolverRef = useRef<((password: string) => void) | null>(null);
|
||||
|
||||
if (!isOpen) return null;
|
||||
// Initialize and connect when modal opens
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
const handleQRNext = () => {
|
||||
// In a real app, this would check if 2FA is required
|
||||
// For now, we'll simulate it by showing 2FA screen
|
||||
setCurrentStep('2fa');
|
||||
};
|
||||
const init = async () => {
|
||||
try {
|
||||
setCurrentStep('loading');
|
||||
if (!isInitialized) {
|
||||
await dispatch(initializeTelegram()).unwrap();
|
||||
}
|
||||
if (connectionStatus !== 'connected') {
|
||||
await dispatch(connectTelegram()).unwrap();
|
||||
}
|
||||
// Check if already authenticated
|
||||
const authCheck = await dispatch(checkAuthStatus()).unwrap();
|
||||
if (authCheck) {
|
||||
onComplete();
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
// Start QR code flow
|
||||
startQrCodeFlow();
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to initialize Telegram';
|
||||
setError(errorMessage);
|
||||
setCurrentStep('error');
|
||||
dispatch(setConnectionStatus('error'));
|
||||
}
|
||||
};
|
||||
|
||||
const handle2FASubmit = () => {
|
||||
// In a real app, this would verify the password
|
||||
onComplete();
|
||||
onClose();
|
||||
// Reset state
|
||||
setCurrentStep('qr');
|
||||
setPassword('');
|
||||
init();
|
||||
}, [isOpen, isInitialized, connectionStatus, dispatch, onComplete, onClose]);
|
||||
|
||||
const startQrCodeFlow = useCallback(async () => {
|
||||
try {
|
||||
setIsAuthenticating(true);
|
||||
setError(null);
|
||||
setCurrentStep('qr');
|
||||
setPassword('');
|
||||
setPasswordHint(undefined);
|
||||
setQrCodeUrl(null);
|
||||
setQrCodeExpires(null);
|
||||
|
||||
await mtprotoService.signInWithQrCode(
|
||||
(qrCode) => {
|
||||
// Convert Buffer/Uint8Array to base64url for QR code URL
|
||||
let tokenBase64: string;
|
||||
if (qrCode.token instanceof Uint8Array) {
|
||||
// Convert Uint8Array to base64url
|
||||
const binary = Array.from(qrCode.token)
|
||||
.map((byte) => String.fromCharCode(byte))
|
||||
.join('');
|
||||
tokenBase64 = btoa(binary)
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=/g, '');
|
||||
} else {
|
||||
// If it's a Buffer, use toString
|
||||
const buffer = qrCode.token as { toString: (encoding: string) => string };
|
||||
tokenBase64 = buffer.toString('base64url');
|
||||
}
|
||||
const url = `tg://login?token=${tokenBase64}`;
|
||||
setQrCodeUrl(url);
|
||||
setQrCodeExpires(qrCode.expires);
|
||||
},
|
||||
async (hint) => {
|
||||
// 2FA password required
|
||||
setPasswordHint(hint);
|
||||
setCurrentStep('2fa');
|
||||
setIsAuthenticating(false);
|
||||
|
||||
// Wait for user to enter password
|
||||
return new Promise<string>((resolve) => {
|
||||
passwordResolverRef.current = resolve;
|
||||
});
|
||||
},
|
||||
async (err) => {
|
||||
// Handle errors
|
||||
const errorMessage = err.message || 'Authentication error';
|
||||
setError(errorMessage);
|
||||
dispatch(setAuthError(errorMessage));
|
||||
|
||||
// Check if it's a cancellation
|
||||
if (errorMessage.includes('AUTH_USER_CANCEL') || errorMessage.includes('cancel')) {
|
||||
setCurrentStep('qr');
|
||||
setIsAuthenticating(false);
|
||||
return true; // Stop authentication
|
||||
}
|
||||
|
||||
return false; // Continue
|
||||
}
|
||||
);
|
||||
|
||||
// Authentication successful
|
||||
setIsAuthenticating(false);
|
||||
await dispatch(checkAuthStatus()).unwrap();
|
||||
dispatch(setAuthStatus('authenticated'));
|
||||
onComplete();
|
||||
onClose();
|
||||
} catch (err) {
|
||||
setIsAuthenticating(false);
|
||||
const errorMessage = err instanceof Error ? err.message : 'Authentication failed';
|
||||
setError(errorMessage);
|
||||
setCurrentStep('error');
|
||||
dispatch(setAuthError(errorMessage));
|
||||
}
|
||||
}, [dispatch, onComplete, onClose]);
|
||||
|
||||
const handle2FASubmit = async () => {
|
||||
if (!password.trim() || !passwordResolverRef.current) return;
|
||||
|
||||
try {
|
||||
setIsAuthenticating(true);
|
||||
setError(null);
|
||||
|
||||
// Resolve the password promise to continue authentication
|
||||
passwordResolverRef.current(password);
|
||||
passwordResolverRef.current = null;
|
||||
} catch (err) {
|
||||
setIsAuthenticating(false);
|
||||
const errorMessage = err instanceof Error ? err.message : 'Password verification failed';
|
||||
setError(errorMessage);
|
||||
dispatch(setAuthError(errorMessage));
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (currentStep === '2fa') {
|
||||
setCurrentStep('qr');
|
||||
setPassword('');
|
||||
setPasswordHint(undefined);
|
||||
if (passwordResolverRef.current) {
|
||||
passwordResolverRef.current('');
|
||||
passwordResolverRef.current = null;
|
||||
}
|
||||
} else {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRetry = () => {
|
||||
setError(null);
|
||||
setPassword('');
|
||||
setPasswordHint(undefined);
|
||||
setQrCodeUrl(null);
|
||||
setQrCodeExpires(null);
|
||||
startQrCodeFlow();
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
<div className="relative w-full max-w-md mx-4">
|
||||
@@ -52,7 +202,39 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{currentStep === 'qr' ? (
|
||||
{currentStep === 'loading' ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-500 mx-auto mb-4"></div>
|
||||
<p className="opacity-70">Initializing Telegram connection...</p>
|
||||
</div>
|
||||
) : currentStep === 'error' ? (
|
||||
<>
|
||||
{/* Error Screen */}
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold mb-2">Connection Error</h2>
|
||||
<p className="opacity-70 text-sm mb-6">{error || 'An error occurred'}</p>
|
||||
<div className="flex space-x-3">
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className="flex-1 py-2.5 px-4 bg-stone-800/50 hover:bg-stone-700/50 border border-stone-700 rounded-xl text-sm font-medium transition-all duration-200"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleRetry}
|
||||
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white rounded-xl text-sm font-medium transition-all duration-200"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : currentStep === 'qr' ? (
|
||||
<>
|
||||
{/* QR Code Screen */}
|
||||
<div className="text-center">
|
||||
@@ -61,41 +243,54 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
|
||||
{/* QR Code Container */}
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="bg-white p-4 rounded-2xl shadow-large">
|
||||
<div className="w-64 h-64 bg-white rounded-xl flex items-center justify-center relative overflow-hidden">
|
||||
{/* QR code pattern - black squares on white background */}
|
||||
<div className="absolute inset-0 p-2">
|
||||
<div className="w-full h-full grid grid-cols-[repeat(25,minmax(0,1fr))] gap-0">
|
||||
{Array.from({ length: 625 }).map((_, i) => {
|
||||
const row = Math.floor(i / 25);
|
||||
const col = i % 25;
|
||||
// Create QR-like pattern with finder patterns in corners
|
||||
const isFinder =
|
||||
(row < 7 && col < 7) || // Top-left
|
||||
(row < 7 && col >= 18) || // Top-right
|
||||
(row >= 18 && col < 7); // Bottom-left
|
||||
// More realistic QR pattern - alternating pattern
|
||||
const isBlack = isFinder || ((row + col) % 3 === 0 && Math.random() > 0.4) || (row * col % 7 < 3);
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={`w-full h-full ${isBlack ? 'bg-black' : 'bg-white'}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{qrCodeUrl ? (
|
||||
<div className="relative w-64 h-64 flex items-center justify-center">
|
||||
<QRCodeSVG
|
||||
value={qrCodeUrl}
|
||||
size={256}
|
||||
level="H"
|
||||
includeMargin={true}
|
||||
marginSize={1}
|
||||
bgColor="#FFFFFF"
|
||||
fgColor="#000000"
|
||||
className="w-full h-full"
|
||||
/>
|
||||
{/* Telegram logo overlay */}
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<div className="w-16 h-16 bg-[#0088CC] rounded-full flex items-center justify-center shadow-lg">
|
||||
<svg className="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Telegram logo in center */}
|
||||
<div className="absolute inset-0 flex items-center justify-center z-10 pointer-events-none">
|
||||
<div className="w-16 h-16 bg-blue-500 rounded-full flex items-center justify-center shadow-lg">
|
||||
<svg className="w-10 h-10 text-white" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
||||
</svg>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-64 h-64 bg-gray-100 rounded-xl flex items-center justify-center">
|
||||
{isAuthenticating ? (
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600 text-sm">Generating QR code...</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-600 text-sm">Loading QR code...</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{qrCodeExpires && (
|
||||
<p className="text-xs opacity-70 mb-4">
|
||||
This code expires in {Math.max(0, Math.floor((qrCodeExpires * 1000 - Date.now()) / 1000))} seconds
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-xl">
|
||||
<p className="text-red-400 text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Instructions */}
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="flex items-start space-x-3 text-left">
|
||||
@@ -129,10 +324,11 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleQRNext}
|
||||
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white rounded-xl text-sm font-medium transition-all duration-200"
|
||||
onClick={handleRetry}
|
||||
disabled={isAuthenticating}
|
||||
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl text-sm font-medium transition-all duration-200"
|
||||
>
|
||||
I've Scanned
|
||||
{isAuthenticating ? 'Connecting...' : 'Refresh QR Code'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,18 +339,32 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold mb-2">Enter Your Password</h2>
|
||||
<p className="opacity-70 text-sm mb-6">
|
||||
Your account is protected with two-step verification. Please enter your password to continue.
|
||||
{passwordHint
|
||||
? `Your account is protected with two-step verification. Hint: ${passwordHint}`
|
||||
: 'Your account is protected with two-step verification. Please enter your password to continue.'}
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-500/20 border border-red-500/50 rounded-xl">
|
||||
<p className="text-red-400 text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Password input */}
|
||||
<div className="mb-6">
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && password.trim() && !isAuthenticating) {
|
||||
handle2FASubmit();
|
||||
}
|
||||
}}
|
||||
placeholder="Enter your password"
|
||||
className="w-full px-4 py-3 bg-black/50 border border-stone-700 rounded-xl text-white placeholder-opacity-50 focus:outline-none focus:border-primary-500 transition-colors"
|
||||
autoFocus
|
||||
disabled={isAuthenticating}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -162,16 +372,17 @@ const TelegramConnectionModal = ({ isOpen, onClose, onComplete }: TelegramConnec
|
||||
<div className="flex space-x-3">
|
||||
<button
|
||||
onClick={handleBack}
|
||||
className="flex-1 py-2.5 px-4 bg-stone-800/50 hover:bg-stone-700/50 border border-stone-700 rounded-xl text-sm font-medium transition-all duration-200"
|
||||
disabled={isAuthenticating}
|
||||
className="flex-1 py-2.5 px-4 bg-stone-800/50 hover:bg-stone-700/50 border border-stone-700 rounded-xl text-sm font-medium transition-all duration-200 disabled:opacity-50"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
onClick={handle2FASubmit}
|
||||
disabled={!password.trim()}
|
||||
disabled={!password.trim() || isAuthenticating}
|
||||
className="flex-1 py-2.5 px-4 bg-primary-500 hover:bg-primary-600 active:bg-primary-700 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-xl text-sm font-medium transition-all duration-200"
|
||||
>
|
||||
Continue
|
||||
{isAuthenticating ? 'Verifying...' : 'Continue'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// IMPORTANT: Polyfills must be imported FIRST
|
||||
import "./polyfills";
|
||||
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// Polyfill Node.js globals for browser (required by telegram package)
|
||||
// This must be imported FIRST before any other imports that use Node.js APIs
|
||||
import { Buffer } from 'buffer';
|
||||
import process from 'process';
|
||||
import * as util from 'util';
|
||||
|
||||
// Immediately set Buffer on all global objects synchronously
|
||||
// This must happen before any other code runs
|
||||
(function setupNodePolyfills() {
|
||||
const buffer = Buffer;
|
||||
|
||||
// Set Buffer on all global objects
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
(globalThis as any).Buffer = buffer;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).Buffer = buffer;
|
||||
}
|
||||
|
||||
if (typeof global !== 'undefined') {
|
||||
(global as any).Buffer = buffer;
|
||||
(global as any).global = globalThis;
|
||||
}
|
||||
|
||||
if (typeof self !== 'undefined') {
|
||||
(self as any).Buffer = buffer;
|
||||
}
|
||||
|
||||
// Set process on global objects
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
(globalThis as any).process = process;
|
||||
(globalThis as any).util = util;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).process = process;
|
||||
(window as any).util = util;
|
||||
}
|
||||
|
||||
if (typeof global !== 'undefined') {
|
||||
(global as any).process = process;
|
||||
(global as any).util = util;
|
||||
}
|
||||
})();
|
||||
|
||||
// Export for use in modules
|
||||
export { Buffer, process, util };
|
||||
export default Buffer;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { TelegramClient } from 'telegram';
|
||||
import { StringSession } from 'telegram/sessions';
|
||||
import type { UserAuthParams, BotAuthParams } from 'telegram/client/auth';
|
||||
import { TELEGRAM_API_ID, TELEGRAM_API_HASH } from '../utils/config';
|
||||
|
||||
type LoginOptions = UserAuthParams | BotAuthParams;
|
||||
|
||||
@@ -10,9 +11,17 @@ class MTProtoService {
|
||||
private isInitialized = false;
|
||||
private isConnected = false;
|
||||
private sessionString = '';
|
||||
private readonly apiId: number;
|
||||
private readonly apiHash: string;
|
||||
|
||||
private constructor() {
|
||||
// Private constructor to enforce singleton
|
||||
// Load API credentials from config once
|
||||
if (!TELEGRAM_API_ID || !TELEGRAM_API_HASH) {
|
||||
throw new Error('TELEGRAM_API_ID and TELEGRAM_API_HASH must be configured');
|
||||
}
|
||||
this.apiId = TELEGRAM_API_ID;
|
||||
this.apiHash = TELEGRAM_API_HASH;
|
||||
}
|
||||
|
||||
static getInstance(): MTProtoService {
|
||||
@@ -31,19 +40,13 @@ class MTProtoService {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiId = import.meta.env.VITE_TELEGRAM_API_ID;
|
||||
const apiHash = import.meta.env.VITE_TELEGRAM_API_HASH;
|
||||
const sessionString = this.loadSession() || '';
|
||||
|
||||
if (!apiId || !apiHash) {
|
||||
throw new Error('VITE_TELEGRAM_API_ID and VITE_TELEGRAM_API_HASH must be configured');
|
||||
}
|
||||
|
||||
try {
|
||||
const stringSession = new StringSession(sessionString);
|
||||
this.sessionString = sessionString;
|
||||
|
||||
this.client = new TelegramClient(stringSession, Number(apiId), String(apiHash), {
|
||||
this.client = new TelegramClient(stringSession, this.apiId, this.apiHash, {
|
||||
connectionRetries: 5,
|
||||
});
|
||||
|
||||
@@ -110,6 +113,51 @@ class MTProtoService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign in using QR code
|
||||
*/
|
||||
async signInWithQrCode(
|
||||
qrCodeCallback: (qrCode: { token: Buffer; expires: number }) => void,
|
||||
passwordCallback?: (hint?: string) => Promise<string>,
|
||||
onError?: (err: Error) => Promise<boolean> | void
|
||||
): Promise<unknown> {
|
||||
if (!this.client) {
|
||||
throw new Error('MTProto client not initialized. Call initialize() first.');
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await this.client.signInUserWithQrCode(
|
||||
{
|
||||
apiId: this.apiId,
|
||||
apiHash: this.apiHash,
|
||||
},
|
||||
{
|
||||
qrCode: async (qrCode) => {
|
||||
qrCodeCallback(qrCode);
|
||||
},
|
||||
password: passwordCallback,
|
||||
onError: onError || ((err: Error) => {
|
||||
console.error('QR code auth error:', err);
|
||||
return false;
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
// Save session after successful login
|
||||
const newSessionString = this.client.session.save();
|
||||
if (newSessionString && newSessionString !== this.sessionString) {
|
||||
this.sessionString = newSessionString;
|
||||
this.saveSession(newSessionString);
|
||||
console.log('QR code authentication successful, session saved');
|
||||
}
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('QR code authentication failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Telegram client instance
|
||||
* @throws Error if client is not initialized
|
||||
|
||||
Vendored
+12
@@ -1 +1,13 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
// Node.js polyfills for browser
|
||||
declare global {
|
||||
interface Window {
|
||||
Buffer: typeof Buffer;
|
||||
process: typeof process;
|
||||
util: typeof import("util");
|
||||
}
|
||||
var Buffer: typeof import("buffer").Buffer;
|
||||
var process: typeof import("process");
|
||||
var util: typeof import("util");
|
||||
}
|
||||
|
||||
+13
-2
@@ -1,12 +1,23 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
// @ts-expect-error process is a nodejs global
|
||||
import { nodePolyfills } from "vite-plugin-node-polyfills";
|
||||
|
||||
const host = process.env.TAURI_DEV_HOST;
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [react()],
|
||||
plugins: [
|
||||
nodePolyfills({
|
||||
include: ["buffer", "process", "util", "crypto", "stream"],
|
||||
globals: {
|
||||
Buffer: true,
|
||||
process: true,
|
||||
global: true,
|
||||
},
|
||||
}),
|
||||
react(),
|
||||
],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user