Add comprehensive documentation for application architecture, state management, services layer, MCP system, pages and routing, components, and providers

- Introduced detailed architecture overview outlining system components and entry points.
- Documented state management using Redux Toolkit and Redux-Persist, including store configuration and slice structures.
- Described services layer architecture, detailing API client, socket service, and MTProto service functionalities.
- Explained the Model Context Protocol (MCP) system, including tool categories and implementation examples.
- Outlined routing structure and page components, emphasizing protected and public routes.
- Organized reusable components by feature, detailing their structure and usage.
- Provided insights into provider management for service lifecycle and shared state.

This documentation enhances understanding of the application's structure and improves onboarding for new developers, ensuring clarity in the system's design and functionality.
This commit is contained in:
M3gA-Mind
2026-01-29 21:21:07 +05:30
parent 28480ab531
commit 632ecc05b7
34 changed files with 5874 additions and 78 deletions
+175
View File
@@ -0,0 +1,175 @@
# Architecture Overview
## System Architecture
The Outsourced platform is built on a layered architecture supporting:
- Redux-based state management with persistence
- Socket.io real-time communication
- Telegram MTProto integration via service layer
- 81-tool MCP (Model Context Protocol) system for AI interactions
- Multi-step onboarding flow
- URL-based settings modal system
- Deep link authentication handoff
- Cross-platform compatibility (desktop + mobile)
## Entry Points
| File | Purpose |
|------|---------|
| `main.tsx` | React root, polyfill imports, lazy deep link listener init |
| `App.tsx` | Provider chain: Redux → PersistGate → Socket → Telegram → HashRouter |
| `AppRoutes.tsx` | Route definitions with route guards |
| `polyfills.ts` | Node.js polyfills (Buffer, process, util) for telegram npm package |
## Provider Chain
The application wraps components in a specific order due to dependencies:
```
Redux Provider
└─ PersistGate (rehydrate auth + telegram state from localStorage)
└─ UserProvider
└─ SocketProvider (manages Socket.io connection + MCP init)
└─ TelegramProvider (manages MTProto connection)
└─ HashRouter
└─ AppRoutes (route definitions + SettingsModal overlay)
```
**Why this order matters:**
1. Redux must be outermost for state access
2. PersistGate rehydrates persisted state before rendering
3. SocketProvider depends on Redux auth token
4. TelegramProvider depends on Redux telegram state
5. HashRouter provides navigation context to all routes
## Module Relationships
```
main.tsx (entry)
App.tsx (providers chain)
├─ Redux Store ←→ Persist
├─ SocketProvider
│ ├─ listens to auth.token changes
│ ├─ calls socketService.connect(token)
│ └─ init MCP server when socket connected
├─ TelegramProvider
│ ├─ listens to telegram state
│ ├─ calls mtprotoService.initialize(userId)
│ └─ exposes useTelegram hook
└─ HashRouter + AppRoutes
├─ ProtectedRoute ← checks Redux auth + onboarded
├─ PublicRoute ← redirects authenticated users
├─ pages/Home
│ ├─ uses useUser hook → calls userApi
│ └─ uses useNavigate → opens /settings
├─ pages/Login
│ ├─ uses TelegramLoginButton
│ └─ calls deeplink.ts functions
└─ SettingsModal
├─ listens to location.pathname
├─ uses useSettingsNavigation hook
└─ renders panels with Redux state
```
## Services Layer
```
Services Layer:
├─ apiClient (singleton)
│ ├─ reads auth.token from Redux
│ └─ makes HTTP requests to BACKEND_URL
├─ socketService (singleton)
│ ├─ manages Socket.io connection
│ └─ emits/listens for MCP messages
└─ mtprotoService (singleton)
├─ manages TelegramClient
└─ stores session in Redux telegram.byUser[userId].sessionString
```
## MCP System
```
MCP System:
├─ TelegramMCPServer (instantiated in SocketProvider)
├─ SocketIOMCPTransport (wraps socketService)
└─ 81 tools in telegram/tools/
├─ each tool calls mtprotoService
└─ results returned via socket transport to backend MCP client
```
## Data Flow
### Authentication Flow (Deep Link)
1. User authenticates in browser → receives `loginToken`
2. Browser redirects to `outsourced://auth?token=<loginToken>`
3. Desktop app catches deep link via Tauri plugin
4. `desktopDeepLinkListener` invokes Rust `exchange_token` command
5. Rust calls backend `POST /auth/desktop-exchange` (CORS-free)
6. Backend returns `{ sessionToken, user }`
7. App stores session in Redux, navigates to `/onboarding` or `/home`
### Socket.io Connection Flow
1. SocketProvider detects `auth.token` change
2. Calls `socketService.connect(token)`
3. On successful connection, initializes MCP server
4. MCP server registers 81 Telegram tools
5. Backend can invoke tools via JSON-RPC over Socket.io
### Telegram Connection Flow
1. TelegramProvider detects auth state
2. Calls `mtprotoService.initialize(userId)` and `connect()` in parallel
3. MTProto client authenticates with Telegram servers
4. Session string stored in Redux `telegram.byUser[userId].sessionString`
5. Chats/messages fetched and stored in Redux
## Key Patterns
### No localStorage Usage
- **Rule**: Avoid `localStorage`; use Redux with persistence instead
- **Exceptions**: Redux-persist's storage adapter (persistence layer)
- **Telegram session**: Stored in `telegram.byUser[userId].sessionString`
### Route Guard Pattern
- **PublicRoute**: Redirects authenticated users away
- **ProtectedRoute**: Requires `token` and optionally `isOnboarded`
- **DefaultRedirect**: Fallback based on auth state
### Settings Modal Pattern
- Renders via `createPortal` when `location.pathname.startsWith('/settings')`
- URL-based navigation without affecting main route
- Uses `useSettingsNavigation` hook
### MCP Tool Pattern
Each tool exports a handler:
```typescript
export const toolName: TelegramMCPToolHandler = {
call: async (args, { telegramClient, userId }) => {
// Perform Telegram API operation
return { success: true, data: result };
}
}
```
## File Organization
### Feature-Based Structure
- `pages/` - Full-page route components
- `pages/onboarding/` - Onboarding flow with steps
- `components/settings/` - Settings modal system
- `lib/mcp/telegram/tools/` - Individual MCP tools
### Slice-Based State
- `store/authSlice.ts` - Authentication
- `store/socketSlice.ts` - Socket connection
- `store/userSlice.ts` - User profile
- `store/telegram/` - Complex Telegram state (5 files)
### Singleton Services
- `services/socketService.ts` - Socket.io
- `services/mtprotoService.ts` - Telegram MTProto
- `services/apiClient.ts` - HTTP REST
---
*Next: [State Management](./02-state-management.md)*
+243
View File
@@ -0,0 +1,243 @@
# State Management
The application uses Redux Toolkit with Redux-Persist for robust state management.
## Store Configuration
**File:** `store/index.ts`
```typescript
// Combines all slices with persistence
const persistConfig = {
key: 'root',
storage,
whitelist: ['auth', 'telegram'] // Persisted slices
};
```
## Redux State Structure
```typescript
RootState = {
auth: {
token: string | null, // JWT (persisted)
isOnboardedByUser: Record<string, boolean> // Per-user flag (persisted)
},
socket: {
byUser: Record<string, { // Per user ID
status: "connecting" | "connected" | "disconnected",
socketId: string | null
}>
},
user: {
profile: User | null,
loading: boolean,
error: string | null
},
telegram: {
byUser: Record<string, TelegramState> // Per Telegram user (persisted)
}
}
```
## Slices
### Auth Slice (`store/authSlice.ts`)
Manages JWT token and per-user onboarding status.
**State:**
```typescript
interface AuthState {
token: string | null;
isOnboardedByUser: Record<string, boolean>;
}
```
**Actions:**
- `setToken(token: string)` - Store JWT after login
- `clearToken()` - Remove token on logout
- `setOnboarded({ userId, isOnboarded })` - Mark user as onboarded
**Selectors (`store/authSelectors.ts`):**
- `selectToken` - Get current JWT
- `selectIsOnboarded(userId)` - Check if user completed onboarding
### Socket Slice (`store/socketSlice.ts`)
Tracks Socket.io connection status per user.
**State:**
```typescript
interface SocketState {
byUser: Record<string, {
status: 'connecting' | 'connected' | 'disconnected';
socketId: string | null;
}>;
}
```
**Actions:**
- `setSocketStatus({ userId, status })` - Update connection status
- `setSocketId({ userId, socketId })` - Store socket ID
- `clearSocketState(userId)` - Clear user's socket state
**Selectors (`store/socketSelectors.ts`):**
- `selectSocketStatus(userId)` - Get connection status
- `selectIsSocketConnected(userId)` - Boolean connected check
### User Slice (`store/userSlice.ts`)
Stores user profile data.
**State:**
```typescript
interface UserState {
profile: User | null;
loading: boolean;
error: string | null;
}
```
**Actions:**
- `setUser(user)` - Store user profile
- `setUserLoading(loading)` - Set loading state
- `setUserError(error)` - Set error state
- `clearUser()` - Clear profile on logout
### Telegram Slice (`store/telegram/`)
Complex nested state management for Telegram integration.
**Files:**
- `index.ts` - Slice exports (actions, thunks)
- `types.ts` - Entity and state interfaces
- `reducers.ts` - Synchronous reducers
- `extraReducers.ts` - Async thunk handlers
- `thunks.ts` - Async operations
**State Structure:**
```typescript
telegram.byUser[telegramUserId] = {
connectionStatus: "disconnected" | "connecting" | "connected" | "error",
authStatus: "not_authenticated" | "authenticating" | "authenticated" | "error",
currentUser: TelegramUser | null,
sessionString: string | null, // Stored here, NOT localStorage
chats: Record<string, TelegramChat>,
chatsOrder: string[],
messages: Record<chatId, Record<msgId, TelegramMessage>>,
threads: Record<chatId, TelegramThread[]>
}
```
**Reducers:**
- `setCurrentUser` - Store authenticated Telegram user
- `setSessionString` - Store MTProto session (for persistence)
- `setConnectionStatus` - Update connection state
- `setAuthStatus` - Update authentication state
- `addChat` / `updateChat` - Manage chat list
- `addMessage` / `updateMessage` - Manage message history
- `setThreads` - Store thread data
**Thunks (`store/telegram/thunks.ts`):**
- `initializeTelegram(userId)` - Initialize MTProto client
- `connectTelegram(userId)` - Establish Telegram connection
- `fetchChats(userId)` - Load chat list
- `fetchMessages({ userId, chatId })` - Load message history
- `disconnectTelegram(userId)` - Clean disconnect
**Selectors (`store/telegramSelectors.ts`):**
- `selectTelegramState(userId)` - Get full Telegram state
- `selectTelegramConnectionStatus(userId)` - Get connection status
- `selectTelegramAuthStatus(userId)` - Get auth status
- `selectTelegramChats(userId)` - Get chat list
- `selectTelegramMessages(userId, chatId)` - Get messages for chat
## Typed Hooks
**File:** `store/hooks.ts`
```typescript
// Use these instead of plain useDispatch/useSelector
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
```
## Persistence Configuration
### What's Persisted
- `auth.token` - JWT for authentication
- `auth.isOnboardedByUser` - Per-user onboarding status
- `telegram.byUser` - Telegram state (sessions, chats, etc.)
### What's NOT Persisted
- `socket` - Connection state (reconnects on app start)
- `user.loading` / `user.error` - Transient UI states
- Telegram loading/error states
### Storage Backend
Redux-Persist uses localStorage adapter by default. This is the ONLY acceptable use of localStorage in the application.
## Usage Examples
### Reading State
```typescript
import { useAppSelector } from '../store/hooks';
function MyComponent() {
const token = useAppSelector((state) => state.auth.token);
const isConnected = useAppSelector((state) =>
state.socket.byUser[userId]?.status === 'connected'
);
const chats = useAppSelector((state) =>
state.telegram.byUser[userId]?.chats
);
}
```
### Dispatching Actions
```typescript
import { useAppDispatch } from '../store/hooks';
import { setToken, clearToken } from '../store/authSlice';
import { initializeTelegram } from '../store/telegram/thunks';
function MyComponent() {
const dispatch = useAppDispatch();
// Sync action
const handleLogin = (token: string) => {
dispatch(setToken(token));
};
// Async thunk
const handleConnect = async () => {
await dispatch(initializeTelegram(userId)).unwrap();
};
}
```
### Using Selectors
```typescript
import { useAppSelector } from '../store/hooks';
import { selectIsOnboarded } from '../store/authSelectors';
import { selectTelegramConnectionStatus } from '../store/telegramSelectors';
function MyComponent({ userId }) {
const isOnboarded = useAppSelector((state) => selectIsOnboarded(state, userId));
const connectionStatus = useAppSelector((state) =>
selectTelegramConnectionStatus(state, userId)
);
}
```
## Best Practices
1. **Always use typed hooks** - `useAppDispatch` and `useAppSelector`
2. **Use selectors for derived state** - Memoized and testable
3. **Keep thunks in separate files** - Better organization
4. **Per-user state scoping** - Key state by user ID
5. **Avoid localStorage** - Use Redux-Persist instead
---
*Previous: [Architecture Overview](./01-architecture.md) | Next: [Services Layer](./03-services.md)*
+297
View File
@@ -0,0 +1,297 @@
# Services Layer
The application uses singleton services for external communication. This prevents connection leaks and provides consistent API access.
## Service Architecture
```
Services Layer
├─ apiClient (HTTP REST)
│ ├─ reads auth.token from Redux
│ └─ makes requests to BACKEND_URL
├─ socketService (Socket.io)
│ ├─ manages real-time connection
│ └─ emits/listens for MCP messages
└─ mtprotoService (Telegram)
├─ manages TelegramClient
└─ stores session in Redux
```
## API Client (`services/apiClient.ts`)
HTTP REST client for backend communication.
### Features
- Fetch-based implementation
- Auto-injects JWT from Redux store
- Typed request/response handling
- Error handling with typed errors
### Usage
```typescript
import apiClient from '../services/apiClient';
// GET request
const user = await apiClient.get<User>('/users/me');
// POST request
const result = await apiClient.post<LoginResponse>('/auth/login', {
email,
password
});
// With custom headers
const data = await apiClient.get<Data>('/endpoint', {
headers: { 'X-Custom': 'value' }
});
```
### Configuration
Reads `VITE_BACKEND_URL` from environment or uses default:
```typescript
const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.example.com';
```
## API Endpoints (`services/api/`)
### Auth API (`services/api/authApi.ts`)
Authentication-related endpoints.
```typescript
import { authApi } from '../services/api/authApi';
// Login
const { token, user } = await authApi.login(credentials);
// Token exchange (for deep link flow)
const { sessionToken, user } = await authApi.exchangeToken(loginToken);
// Logout
await authApi.logout();
```
### User API (`services/api/userApi.ts`)
User profile endpoints.
```typescript
import { userApi } from '../services/api/userApi';
// Get current user
const user = await userApi.getCurrentUser();
// Update profile
const updated = await userApi.updateProfile({ firstName, lastName });
// Get settings
const settings = await userApi.getSettings();
```
## Socket Service (`services/socketService.ts`)
Socket.io client singleton for real-time communication.
### Features
- Singleton pattern - single connection per app
- Auth token passed in socket `auth` object
- Transports: polling first, then WebSocket upgrade
- Auto-reconnection handling
### API
```typescript
import socketService from '../services/socketService';
// Connect with auth token
socketService.connect(token);
// Disconnect
socketService.disconnect();
// Emit event
socketService.emit('event-name', data);
// Listen for events
socketService.on('event-name', (data) => {
// Handle event
});
// Remove listener
socketService.off('event-name', handler);
// One-time listener
socketService.once('event-name', (data) => {
// Handle once
});
// Get socket instance
const socket = socketService.getSocket();
// Check connection status
const isConnected = socketService.isConnected();
```
### Connection Flow
```typescript
// In SocketProvider.tsx
useEffect(() => {
if (token) {
socketService.connect(token);
socketService.on('connect', () => {
dispatch(setSocketStatus({ userId, status: 'connected' }));
dispatch(setSocketId({ userId, socketId: socket.id }));
// Initialize MCP server
initMCPServer(socketService.getSocket());
});
socketService.on('disconnect', () => {
dispatch(setSocketStatus({ userId, status: 'disconnected' }));
});
}
return () => {
socketService.disconnect();
};
}, [token]);
```
### Configuration
```typescript
const socket = io(BACKEND_URL, {
auth: { token },
transports: ['polling', 'websocket'],
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000
});
```
## MTProto Service (`services/mtprotoService.ts`)
Telegram MTProto client singleton.
### Features
- Singleton pattern - one client per user
- Session persistence via Redux (not localStorage)
- Auto-retry for FLOOD_WAIT up to 60s
- Supports QR login and phone auth
### Initialization
```typescript
import mtprotoService from '../services/mtprotoService';
// Get or create instance for user
const client = await mtprotoService.getInstance().initialize(userId);
// Set session string (from Redux)
await client.setSession(sessionString);
// Connect to Telegram
await client.connect();
```
### Session Management
```typescript
// Session is stored in Redux, not localStorage
// In TelegramProvider:
const sessionString = useAppSelector((state) =>
state.telegram.byUser[userId]?.sessionString
);
// When session updates
useEffect(() => {
if (client && sessionString) {
client.setSession(sessionString);
}
}, [sessionString]);
// Save session after auth
const newSession = await client.getSession();
dispatch(setSessionString({ userId, sessionString: newSession }));
```
### API Operations
```typescript
// Get current user
const me = await client.getMe();
// Get dialogs (chats)
const dialogs = await client.getDialogs({ limit: 20 });
// Send message
await client.sendMessage(peer, { message: 'Hello!' });
// Get history
const messages = await client.getMessages(peer, { limit: 50 });
```
### Error Handling
```typescript
try {
await client.connect();
} catch (error) {
if (error.message.includes('FLOOD_WAIT')) {
const seconds = parseInt(error.message.match(/\d+/)?.[0] || '60');
if (seconds <= 60) {
// Auto-retry after wait
await new Promise(r => setTimeout(r, seconds * 1000));
await client.connect();
}
}
throw error;
}
```
## Service Integration with Providers
### SocketProvider
```typescript
// providers/SocketProvider.tsx
export function SocketProvider({ children }) {
const token = useAppSelector((state) => state.auth.token);
useEffect(() => {
if (token) {
socketService.connect(token);
// On connect, initialize MCP
}
return () => socketService.disconnect();
}, [token]);
return <SocketContext.Provider value={...}>{children}</SocketContext.Provider>;
}
```
### TelegramProvider
```typescript
// providers/TelegramProvider.tsx
export function TelegramProvider({ children }) {
const dispatch = useAppDispatch();
const userId = useAppSelector((state) => state.user.profile?.id);
useEffect(() => {
if (userId) {
// Parallel init + connect for faster startup
Promise.all([
dispatch(initializeTelegram(userId)),
dispatch(connectTelegram(userId))
]);
}
}, [userId]);
return <TelegramContext.Provider value={...}>{children}</TelegramContext.Provider>;
}
```
## Best Practices
1. **Use singletons** - Never create multiple service instances
2. **Store sessions in Redux** - Not localStorage
3. **Clean up on unmount** - Disconnect in useEffect cleanup
4. **Handle errors gracefully** - Retry for transient failures
5. **Pass auth via proper channels** - Socket auth object, not query string
---
*Previous: [State Management](./02-state-management.md) | Next: [MCP System](./04-mcp-system.md)*
+437
View File
@@ -0,0 +1,437 @@
# MCP System
The Model Context Protocol (MCP) system enables AI-driven Telegram interactions through 81 specialized tools.
## Overview
```
MCP System Architecture
├── lib/mcp/
│ ├── index.ts # Singleton lifecycle management
│ ├── types.ts # MCP interfaces
│ ├── transport.ts # Socket.IO JSON-RPC 2.0 transport
│ ├── logger.ts # Logging utilities
│ ├── errorHandler.ts # Error handling
│ ├── validation.ts # Input validation
│ │
│ └── telegram/
│ ├── index.ts # Server initialization
│ ├── server.ts # TelegramMCPServer (81 tools)
│ ├── types.ts # Tool name types
│ ├── telegramApi.ts # Telegram API layer
│ ├── apiCastHelpers.ts # Type casting
│ ├── apiResultTypes.ts # Result types
│ ├── args.ts # Argument schemas
│ ├── toolActionParser.ts # Human-readable parsing
│ │
│ └── tools/ # 81 individual tool files
```
## Core Components
### MCP Types (`lib/mcp/types.ts`)
```typescript
interface MCPTool {
name: string;
description: string;
inputSchema: JSONSchema;
}
interface MCPRequest {
jsonrpc: '2.0';
id: string | number;
method: string;
params?: unknown;
}
interface MCPResponse {
jsonrpc: '2.0';
id: string | number;
result?: unknown;
error?: MCPError;
}
interface MCPError {
code: number;
message: string;
data?: unknown;
}
```
### Transport Layer (`lib/mcp/transport.ts`)
Socket.IO-based JSON-RPC 2.0 transport.
**Features:**
- Request ID tracking
- 30-second timeout
- Error handling
- Event emission
```typescript
class SocketIOMCPTransport {
constructor(socket: Socket);
// Send request and await response
async send(request: MCPRequest): Promise<MCPResponse>;
// Register handler for incoming requests
onRequest(handler: (req: MCPRequest) => Promise<MCPResponse>): void;
// Clean up listeners
dispose(): void;
}
```
### MCP Singleton (`lib/mcp/index.ts`)
Lifecycle management for MCP server.
```typescript
// Initialize MCP server with socket
initMCPServer(socket: Socket): TelegramMCPServer;
// Get existing server instance
getMCPServer(): TelegramMCPServer | null;
// Update socket reference
updateMCPSocket(socket: Socket): void;
// Clean up
cleanupMCP(): void;
```
## Telegram MCP Server
### Server Class (`lib/mcp/telegram/server.ts`)
```typescript
class TelegramMCPServer {
constructor(transport: SocketIOMCPTransport, userId: string);
// Register all 81 tools
async initialize(): Promise<void>;
// List available tools
listTools(): MCPTool[];
// Execute a tool
async executeTool(name: string, args: unknown): Promise<ToolResult>;
// Handle incoming JSON-RPC request
async handleRequest(request: MCPRequest): Promise<MCPResponse>;
// Clean up
dispose(): void;
}
```
### Tool Handler Interface
Each tool file exports a handler:
```typescript
interface TelegramMCPToolHandler {
name: string;
description: string;
inputSchema: JSONSchema;
call: (
args: unknown,
context: {
telegramClient: TelegramClient;
userId: string;
}
) => Promise<ToolResult>;
}
interface ToolResult {
success: boolean;
data?: unknown;
error?: string;
}
```
## Tool Categories
### 81 Telegram Tools
#### User & Profile (5 tools)
| Tool | Description |
|------|-------------|
| `getMe` | Get current authenticated user |
| `getUserInfo` | Get info about any user |
| `getUserPhotos` | Get user profile photos |
| `getUserStatus` | Get online status |
| `setUserStatus` | Update own status |
#### Chats & Dialogs (12 tools)
| Tool | Description |
|------|-------------|
| `getChats` | Fetch chat list |
| `getChatInfo` | Get chat details |
| `createGroup` | Create new group |
| `createChannel` | Create new channel |
| `leaveChat` | Leave chat/group/channel |
| `deleteChat` | Delete chat |
| `muteChat` | Mute notifications |
| `unmuteChat` | Unmute notifications |
| `pinChat` | Pin chat to top |
| `unpinChat` | Unpin chat |
| `archiveChat` | Archive chat |
| `unarchiveChat` | Unarchive chat |
#### Messages (15 tools)
| Tool | Description |
|------|-------------|
| `getHistory` | Fetch message history |
| `sendMessage` | Send text message |
| `replyToMessage` | Reply to specific message |
| `editMessage` | Edit sent message |
| `deleteMessage` | Delete message |
| `forwardMessage` | Forward to another chat |
| `pinMessage` | Pin message in chat |
| `unpinMessage` | Unpin message |
| `markAsRead` | Mark messages as read |
| `searchMessages` | Search in chat |
| `translateMessage` | Translate message text |
| `getMessageReactions` | Get reactions |
| `addReaction` | React to message |
| `removeReaction` | Remove reaction |
| `reportMessage` | Report spam/abuse |
#### Media (10 tools)
| Tool | Description |
|------|-------------|
| `sendPhoto` | Send image |
| `sendVideo` | Send video |
| `sendDocument` | Send file |
| `sendVoice` | Send voice message |
| `sendAudio` | Send audio file |
| `sendSticker` | Send sticker |
| `sendGif` | Send animation |
| `sendLocation` | Send location |
| `sendContact` | Send contact card |
| `downloadMedia` | Download media file |
#### Contacts (8 tools)
| Tool | Description |
|------|-------------|
| `addContact` | Add new contact |
| `deleteContact` | Remove contact |
| `getContacts` | Get contact list |
| `searchContacts` | Search contacts |
| `importContacts` | Bulk import |
| `exportContacts` | Export contact list |
| `blockUser` | Block user |
| `unblockUser` | Unblock user |
#### Groups & Channels (15 tools)
| Tool | Description |
|------|-------------|
| `getAdmins` | Get admin list |
| `getMembers` | Get member list |
| `addMember` | Add user to group |
| `removeMember` | Remove from group |
| `banUser` | Ban user |
| `unbanUser` | Unban user |
| `promoteAdmin` | Promote to admin |
| `demoteAdmin` | Remove admin rights |
| `setGroupTitle` | Change group name |
| `setGroupPhoto` | Change group photo |
| `setGroupDescription` | Change description |
| `subscribePublicChannel` | Join public channel |
| `inviteToChannel` | Invite user |
| `getInviteLink` | Get invite link |
| `revokeInviteLink` | Revoke invite link |
#### Polls & Interactive (5 tools)
| Tool | Description |
|------|-------------|
| `createPoll` | Create poll |
| `votePoll` | Vote on poll |
| `stopPoll` | Close poll |
| `pressInlineButton` | Click inline button |
| `answerCallback` | Respond to callback |
#### Drafts (3 tools)
| Tool | Description |
|------|-------------|
| `saveDraft` | Save draft message |
| `getDrafts` | Get all drafts |
| `deleteDraft` | Delete draft |
#### Privacy & Settings (5 tools)
| Tool | Description |
|------|-------------|
| `getBlockedUsers` | Get blocked list |
| `getPrivacySettings` | Get privacy config |
| `updatePrivacy` | Update privacy |
| `get2FAStatus` | Check 2FA status |
| `getActiveSessions` | Get login sessions |
#### Misc (3 tools)
| Tool | Description |
|------|-------------|
| `resolveUsername` | Resolve @username |
| `checkUsername` | Check availability |
| `getWebPage` | Get link preview |
## Tool Implementation Example
```typescript
// lib/mcp/telegram/tools/sendMessage.ts
import { TelegramMCPToolHandler } from '../types';
export const sendMessage: TelegramMCPToolHandler = {
name: 'sendMessage',
description: 'Send a text message to a chat',
inputSchema: {
type: 'object',
properties: {
chatId: {
type: 'string',
description: 'Chat ID to send message to'
},
text: {
type: 'string',
description: 'Message text'
},
replyToMsgId: {
type: 'string',
description: 'Optional message ID to reply to'
}
},
required: ['chatId', 'text']
},
call: async (args, { telegramClient, userId }) => {
const { chatId, text, replyToMsgId } = args as {
chatId: string;
text: string;
replyToMsgId?: string;
};
try {
// Use big-integer for chat ID (Telegram IDs exceed Number.MAX_SAFE_INTEGER)
const peer = await telegramClient.getEntity(bigInt(chatId));
const result = await telegramClient.sendMessage(peer, {
message: text,
replyTo: replyToMsgId ? parseInt(replyToMsgId) : undefined
});
return {
success: true,
data: {
messageId: result.id.toString(),
date: result.date
}
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
};
```
## MCP Initialization Flow
```typescript
// In SocketProvider.tsx
useEffect(() => {
if (socket && socket.connected) {
// Initialize MCP server
const mcpServer = initMCPServer(socket);
// Server registers all 81 tools
mcpServer.initialize();
// Listen for tool execution requests
mcpServer.onRequest(async (request) => {
if (request.method === 'tools/call') {
const { name, arguments: args } = request.params;
return mcpServer.executeTool(name, args);
}
return { error: { code: -32601, message: 'Method not found' } };
});
}
return () => cleanupMCP();
}, [socket?.connected]);
```
## JSON-RPC Protocol
### Request Format
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "sendMessage",
"arguments": {
"chatId": "123456789",
"text": "Hello from AI!"
}
}
}
```
### Response Format (Success)
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"success": true,
"data": {
"messageId": "12345",
"date": 1706540000
}
}
}
```
### Response Format (Error)
```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "Chat not found",
"data": { "chatId": "invalid" }
}
}
```
## Big Integer Handling
Telegram IDs exceed JavaScript's `Number.MAX_SAFE_INTEGER`. Use `big-integer` library:
```typescript
import bigInt from 'big-integer';
// Convert string ID to big integer
const chatId = bigInt(args.chatId);
// Convert big integer back to string
const idString = chatId.toString();
```
## Best Practices
1. **Use big-integer for IDs** - All Telegram IDs should use big-integer
2. **Validate inputs** - Use JSON Schema validation before execution
3. **Handle errors gracefully** - Return structured error responses
4. **Log operations** - Use MCP logger for debugging
5. **Timeout long operations** - 30s timeout for transport
---
*Previous: [Services Layer](./03-services.md) | Next: [Pages & Routing](./05-pages-routing.md)*
+403
View File
@@ -0,0 +1,403 @@
# Pages & Routing
The application uses HashRouter with protected and public route guards.
## Route Structure
```
/ → Welcome (public)
/login → Login (public)
/onboarding → Onboarding (protected, requires auth, not yet onboarded)
/home → Home (protected, requires auth + onboarded)
/settings → Settings modal overlay
/settings/* → Settings sub-panels
* → DefaultRedirect (fallback)
```
## Route Configuration (`AppRoutes.tsx`)
```typescript
export function AppRoutes() {
return (
<>
<Routes>
{/* Public routes - redirect if authenticated */}
<Route element={<PublicRoute />}>
<Route path="/" element={<Welcome />} />
<Route path="/login" element={<Login />} />
</Route>
{/* Protected routes - require authentication */}
<Route element={<ProtectedRoute />}>
<Route path="/onboarding/*" element={<Onboarding />} />
</Route>
{/* Protected + onboarded routes */}
<Route element={<ProtectedRoute requireOnboarded />}>
<Route path="/home" element={<Home />} />
</Route>
{/* Fallback redirect */}
<Route path="*" element={<DefaultRedirect />} />
</Routes>
{/* Settings modal overlay - renders on top of routes */}
<SettingsModal />
</>
);
}
```
## Route Guards
### PublicRoute (`components/PublicRoute.tsx`)
Redirects authenticated users away from public pages.
```typescript
export function PublicRoute() {
const token = useAppSelector((state) => state.auth.token);
const isOnboarded = useAppSelector((state) =>
selectIsOnboarded(state, userId)
);
if (token) {
// Authenticated - redirect to appropriate page
return <Navigate to={isOnboarded ? '/home' : '/onboarding'} replace />;
}
return <Outlet />;
}
```
### ProtectedRoute (`components/ProtectedRoute.tsx`)
Enforces authentication and optionally onboarding status.
```typescript
interface ProtectedRouteProps {
requireOnboarded?: boolean;
}
export function ProtectedRoute({ requireOnboarded = false }) {
const token = useAppSelector((state) => state.auth.token);
const isOnboarded = useAppSelector((state) =>
selectIsOnboarded(state, userId)
);
if (!token) {
return <Navigate to="/login" replace />;
}
if (requireOnboarded && !isOnboarded) {
return <Navigate to="/onboarding" replace />;
}
return <Outlet />;
}
```
### DefaultRedirect (`components/DefaultRedirect.tsx`)
Fallback route that redirects based on auth state.
```typescript
export function DefaultRedirect() {
const token = useAppSelector((state) => state.auth.token);
const isOnboarded = useAppSelector((state) =>
selectIsOnboarded(state, userId)
);
if (!token) {
return <Navigate to="/" replace />;
}
if (!isOnboarded) {
return <Navigate to="/onboarding" replace />;
}
return <Navigate to="/home" replace />;
}
```
## Pages
### Welcome Page (`pages/Welcome.tsx`)
Landing page for unauthenticated users.
**Features:**
- App introduction and branding
- CTA to login/signup
- Public route (redirects if authenticated)
### Login Page (`pages/Login.tsx`)
Authentication page.
**Features:**
- Telegram OAuth button
- Opens `/auth/telegram?platform=desktop` in browser
- Handles deep link callback
```typescript
export function Login() {
const handleTelegramLogin = () => {
// Opens Telegram OAuth in system browser
openUrl(`${BACKEND_URL}/auth/telegram?platform=desktop`);
};
return (
<div className="login-page">
<TelegramLoginButton onClick={handleTelegramLogin} />
</div>
);
}
```
### Home Page (`pages/Home.tsx`)
Main dashboard after authentication.
**Features:**
- Protected route (requires auth + onboarded)
- Connection status indicators
- Navigation to settings modal
- Future: Chat list, messages, etc.
```typescript
export function Home() {
const navigate = useNavigate();
const user = useAppSelector((state) => state.user.profile);
const telegramStatus = useAppSelector((state) =>
selectTelegramConnectionStatus(state, user?.id)
);
return (
<div className="home-page">
<header>
<h1>Welcome, {user?.firstName}</h1>
<button onClick={() => navigate('/settings')}>Settings</button>
</header>
<TelegramConnectionIndicator status={telegramStatus} />
<ConnectionIndicator />
{/* Main content */}
</div>
);
}
```
## Onboarding Flow (`pages/onboarding/`)
Multi-step onboarding process.
### Structure
```
pages/onboarding/
├── Onboarding.tsx # Flow controller
└── steps/
├── GetStartedStep.tsx # Welcome
├── PrivacyStep.tsx # Privacy policy
├── AnalyticsStep.tsx # Analytics opt-in
├── ConnectStep.tsx # Telegram connection
└── FeaturesStep.tsx # Features overview
```
### Onboarding Controller (`Onboarding.tsx`)
```typescript
const STEPS = [
{ id: 'get-started', component: GetStartedStep },
{ id: 'privacy', component: PrivacyStep },
{ id: 'analytics', component: AnalyticsStep },
{ id: 'connect', component: ConnectStep },
{ id: 'features', component: FeaturesStep }
];
export function Onboarding() {
const [currentStep, setCurrentStep] = useState(0);
const dispatch = useAppDispatch();
const navigate = useNavigate();
const handleNext = () => {
if (currentStep < STEPS.length - 1) {
setCurrentStep(currentStep + 1);
} else {
// Complete onboarding
dispatch(setOnboarded({ userId, isOnboarded: true }));
navigate('/home');
}
};
const handleBack = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const StepComponent = STEPS[currentStep].component;
return (
<div className="onboarding">
<ProgressIndicator current={currentStep} total={STEPS.length} />
<StepComponent onNext={handleNext} onBack={handleBack} />
</div>
);
}
```
### Step Components
Each step receives `onNext` and `onBack` callbacks:
```typescript
interface StepProps {
onNext: () => void;
onBack: () => void;
}
export function ConnectStep({ onNext, onBack }: StepProps) {
const [showModal, setShowModal] = useState(false);
const telegramStatus = useAppSelector(/* ... */);
return (
<div className="step">
<h2>Connect Your Accounts</h2>
{connectOptions.map((option) => (
<ConnectionOption
key={option.id}
{...option}
onClick={() => option.id === 'telegram' && setShowModal(true)}
/>
))}
<TelegramConnectionModal
isOpen={showModal}
onClose={() => setShowModal(false)}
/>
<div className="actions">
<button onClick={onBack}>Back</button>
<button onClick={onNext}>Continue</button>
</div>
</div>
);
}
```
## Settings Modal Routing
The settings modal overlays existing content using URL-based routing.
### Modal Detection
```typescript
// In SettingsModal.tsx
const location = useLocation();
const isOpen = location.pathname.startsWith('/settings');
```
### Sub-Routes
```
/settings → SettingsHome (main menu)
/settings/connections → ConnectionsPanel
/settings/messaging → MessagingPanel (future)
/settings/privacy → PrivacyPanel (future)
/settings/profile → ProfilePanel (future)
/settings/advanced → AdvancedPanel (future)
/settings/billing → BillingPanel (future)
```
### Navigation
```typescript
import { useSettingsNavigation } from './hooks/useSettingsNavigation';
function SettingsHome() {
const { navigateTo, closeModal } = useSettingsNavigation();
return (
<div>
<SettingsMenuItem
label="Connections"
onClick={() => navigateTo('connections')}
/>
<button onClick={closeModal}>Close</button>
</div>
);
}
```
## HashRouter vs BrowserRouter
The app uses HashRouter for desktop compatibility:
```typescript
// App.tsx
import { HashRouter } from 'react-router-dom';
// URLs look like: app://localhost/#/home
// Instead of: app://localhost/home
```
**Why HashRouter:**
1. Tauri deep links work with hash-based URLs
2. No server configuration needed
3. Works with file:// protocol
4. Prevents 404 on direct URL access
## Deep Link Handling
Deep links are handled before routing:
```typescript
// main.tsx
import('./utils/desktopDeepLinkListener').then((m) => {
m.setupDesktopDeepLinkListener().catch(console.error);
});
```
The listener intercepts `outsourced://auth?token=...` and:
1. Exchanges token via Rust command
2. Stores session in Redux
3. Navigates to `/onboarding` or `/home`
## Navigation Patterns
### Programmatic Navigation
```typescript
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
// Navigate to route
navigate('/home');
// Replace history entry
navigate('/login', { replace: true });
// Go back
navigate(-1);
```
### Link Component
```typescript
import { Link } from 'react-router-dom';
<Link to="/settings">Settings</Link>
```
### State Transfer
```typescript
// Pass state to route
navigate('/details', { state: { itemId: 123 } });
// Receive state
const location = useLocation();
const { itemId } = location.state;
```
---
*Previous: [MCP System](./04-mcp-system.md) | Next: [Components](./06-components.md)*
+511
View File
@@ -0,0 +1,511 @@
# Components
Reusable React components organized by feature.
## Component Structure
```
components/
├── Route Guards
│ ├── ProtectedRoute.tsx
│ ├── PublicRoute.tsx
│ └── DefaultRedirect.tsx
├── Authentication
│ └── TelegramLoginButton.tsx
├── Connection Status
│ ├── ConnectionIndicator.tsx
│ ├── TelegramConnectionIndicator.tsx
│ ├── TelegramConnectionModal.tsx
│ └── GmailConnectionIndicator.tsx
├── Onboarding
│ ├── ProgressIndicator.tsx
│ └── LottieAnimation.tsx
├── Settings Modal (16 files)
│ ├── SettingsModal.tsx
│ ├── SettingsLayout.tsx
│ ├── SettingsHome.tsx
│ ├── panels/
│ ├── components/
│ └── hooks/
└── Development
└── DesignSystemShowcase.tsx
```
## Route Guard Components
### ProtectedRoute
Requires authentication and optionally onboarding.
```typescript
interface ProtectedRouteProps {
requireOnboarded?: boolean;
}
// Usage in AppRoutes.tsx
<Route element={<ProtectedRoute />}>
<Route path="/onboarding/*" element={<Onboarding />} />
</Route>
<Route element={<ProtectedRoute requireOnboarded />}>
<Route path="/home" element={<Home />} />
</Route>
```
### PublicRoute
Redirects authenticated users away.
```typescript
// Usage in AppRoutes.tsx
<Route element={<PublicRoute />}>
<Route path="/" element={<Welcome />} />
<Route path="/login" element={<Login />} />
</Route>
```
### DefaultRedirect
Fallback that routes based on auth state.
```typescript
// Redirects to:
// - "/" if not authenticated
// - "/onboarding" if authenticated but not onboarded
// - "/home" if authenticated and onboarded
```
## Authentication Components
### TelegramLoginButton
OAuth login button for Telegram.
```typescript
interface TelegramLoginButtonProps {
onClick: () => void;
disabled?: boolean;
}
// Usage
<TelegramLoginButton
onClick={() => openUrl(`${BACKEND_URL}/auth/telegram?platform=desktop`)}
/>
```
## Connection Status Components
### ConnectionIndicator
Generic connection status badge.
```typescript
interface ConnectionIndicatorProps {
status: 'connected' | 'connecting' | 'disconnected' | 'error';
label?: string;
}
<ConnectionIndicator status="connected" label="Socket" />
```
### TelegramConnectionIndicator
Telegram-specific status display.
```typescript
interface TelegramConnectionIndicatorProps {
status: 'connected' | 'connecting' | 'disconnected' | 'error';
}
// Usage with Redux state
const telegramStatus = useAppSelector((state) =>
selectTelegramConnectionStatus(state, userId)
);
<TelegramConnectionIndicator status={telegramStatus} />
```
### TelegramConnectionModal
Modal for setting up Telegram connection.
```typescript
interface TelegramConnectionModalProps {
isOpen: boolean;
onClose: () => void;
}
// Usage in onboarding/settings
const [showModal, setShowModal] = useState(false);
<TelegramConnectionModal
isOpen={showModal}
onClose={() => setShowModal(false)}
/>
```
**Features:**
- QR code login flow
- Phone number login flow
- Connection status display
- Error handling
### GmailConnectionIndicator
Gmail status badge (future integration).
```typescript
<GmailConnectionIndicator status="coming-soon" />
```
## Onboarding Components
### ProgressIndicator
Visual progress through onboarding steps.
```typescript
interface ProgressIndicatorProps {
current: number;
total: number;
}
<ProgressIndicator current={2} total={5} />
```
### LottieAnimation
Lottie animation player for onboarding.
```typescript
interface LottieAnimationProps {
animationData: object;
loop?: boolean;
autoplay?: boolean;
className?: string;
}
import welcomeAnimation from '../assets/animations/welcome.json';
<LottieAnimation
animationData={welcomeAnimation}
loop={true}
autoplay={true}
/>
```
## Settings Modal System
Complete modal system with URL-based routing.
### File Structure
```
components/settings/
├── SettingsModal.tsx # Route-based container
├── SettingsLayout.tsx # Portal + backdrop wrapper
├── SettingsHome.tsx # Main menu with profile
├── panels/
│ ├── ConnectionsPanel.tsx # Connection management
│ ├── MessagingPanel.tsx # (Future)
│ ├── PrivacyPanel.tsx # (Future)
│ ├── ProfilePanel.tsx # (Future)
│ ├── AdvancedPanel.tsx # (Future)
│ └── BillingPanel.tsx # (Future)
├── components/
│ ├── SettingsHeader.tsx # User profile section
│ ├── SettingsMenuItem.tsx # Menu item component
│ ├── SettingsBackButton.tsx # Back navigation
│ └── SettingsPanelLayout.tsx# Panel wrapper
└── hooks/
├── useSettingsNavigation.ts # URL routing
└── useSettingsAnimation.ts # Animation state
```
### SettingsModal
Main container that renders based on URL.
```typescript
export function SettingsModal() {
const location = useLocation();
const isOpen = location.pathname.startsWith('/settings');
if (!isOpen) return null;
return (
<SettingsLayout>
{/* Route to appropriate panel */}
{location.pathname === '/settings' && <SettingsHome />}
{location.pathname === '/settings/connections' && <ConnectionsPanel />}
{/* ... more panels */}
</SettingsLayout>
);
}
```
### SettingsLayout
Portal-based modal wrapper.
```typescript
export function SettingsLayout({ children }) {
const { closeModal } = useSettingsNavigation();
return createPortal(
<div className="fixed inset-0 z-50">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
onClick={closeModal}
/>
{/* Modal */}
<div className="absolute inset-4 flex items-center justify-center">
<div className="bg-white rounded-2xl w-full max-w-[520px] shadow-xl">
{children}
</div>
</div>
</div>,
document.body
);
}
```
### SettingsHome
Main menu with user profile.
```typescript
export function SettingsHome() {
const { navigateTo, closeModal } = useSettingsNavigation();
const user = useAppSelector((state) => state.user.profile);
const menuItems = [
{ id: 'connections', label: 'Connections', icon: LinkIcon },
{ id: 'messaging', label: 'Messaging', icon: MessageIcon },
{ id: 'privacy', label: 'Privacy', icon: ShieldIcon },
// ... more items
];
return (
<div>
<SettingsHeader user={user} onClose={closeModal} />
{menuItems.map((item) => (
<SettingsMenuItem
key={item.id}
{...item}
onClick={() => navigateTo(item.id)}
/>
))}
</div>
);
}
```
### ConnectionsPanel
Connection management interface.
```typescript
export function ConnectionsPanel() {
const { navigateBack } = useSettingsNavigation();
const [telegramModalOpen, setTelegramModalOpen] = useState(false);
const telegramStatus = useAppSelector((state) =>
selectTelegramConnectionStatus(state, userId)
);
// Reuses connectOptions from onboarding
const connections = connectOptions.map((opt) => ({
...opt,
status: opt.id === 'telegram' ? telegramStatus : 'coming-soon'
}));
return (
<SettingsPanelLayout title="Connections" onBack={navigateBack}>
{connections.map((conn) => (
<ConnectionItem
key={conn.id}
{...conn}
onConnect={() => conn.id === 'telegram' && setTelegramModalOpen(true)}
/>
))}
<TelegramConnectionModal
isOpen={telegramModalOpen}
onClose={() => setTelegramModalOpen(false)}
/>
</SettingsPanelLayout>
);
}
```
### Settings Hooks
#### useSettingsNavigation
URL-based navigation for settings modal.
```typescript
interface UseSettingsNavigationReturn {
currentRoute: string;
navigateTo: (panel: string) => void;
navigateBack: () => void;
closeModal: () => void;
}
const { navigateTo, navigateBack, closeModal } = useSettingsNavigation();
// Navigate to panel
navigateTo('connections'); // → /settings/connections
// Go back
navigateBack(); // → /settings
// Close modal
closeModal(); // → previous non-settings route
```
#### useSettingsAnimation
Animation state management.
```typescript
interface UseSettingsAnimationReturn {
isEntering: boolean;
isExiting: boolean;
animationClass: string;
}
const { animationClass } = useSettingsAnimation();
<div className={`modal ${animationClass}`}>
{/* Content */}
</div>
```
### Settings Components
#### SettingsHeader
User profile section at top of settings.
```typescript
interface SettingsHeaderProps {
user: User | null;
onClose: () => void;
}
<SettingsHeader user={user} onClose={handleClose} />
```
#### SettingsMenuItem
Individual menu item with icon and chevron.
```typescript
interface SettingsMenuItemProps {
label: string;
icon: React.ComponentType;
onClick: () => void;
badge?: string;
disabled?: boolean;
}
<SettingsMenuItem
label="Connections"
icon={LinkIcon}
onClick={() => navigateTo('connections')}
badge="2"
/>
```
#### SettingsBackButton
Back navigation button.
```typescript
interface SettingsBackButtonProps {
onClick: () => void;
}
<SettingsBackButton onClick={navigateBack} />
```
#### SettingsPanelLayout
Wrapper for settings panels.
```typescript
interface SettingsPanelLayoutProps {
title: string;
onBack: () => void;
children: React.ReactNode;
}
<SettingsPanelLayout title="Connections" onBack={navigateBack}>
{/* Panel content */}
</SettingsPanelLayout>
```
## Component Patterns
### Reusing Connection Options
The `connectOptions` array is shared between onboarding and settings:
```typescript
// Defined in ConnectStep.tsx, imported elsewhere
export const connectOptions = [
{
id: 'telegram',
label: 'Telegram',
icon: TelegramIcon,
description: 'Connect your Telegram account'
},
{
id: 'gmail',
label: 'Gmail',
icon: GmailIcon,
description: 'Connect your Gmail account',
comingSoon: true
}
];
```
### Modal via Portal
Settings modal uses `createPortal` to render outside the component tree:
```typescript
return createPortal(
<div className="modal-container">
{/* Modal content */}
</div>,
document.body
);
```
### Controlled vs Uncontrolled
Connection modals are controlled components:
```typescript
// Parent controls open state
const [isOpen, setIsOpen] = useState(false);
<TelegramConnectionModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
/>
```
---
*Previous: [Pages & Routing](./05-pages-routing.md) | Next: [Providers](./07-providers.md)*
+404
View File
@@ -0,0 +1,404 @@
# Providers
React context providers manage service lifecycle and provide shared state.
## Provider Chain
The providers wrap the application in a specific order:
```typescript
// App.tsx
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<UserProvider>
<SocketProvider>
<TelegramProvider>
<HashRouter>
<AppRoutes />
</HashRouter>
</TelegramProvider>
</SocketProvider>
</UserProvider>
</PersistGate>
</Provider>
```
**Order matters because:**
1. Redux must be outermost for state access
2. PersistGate rehydrates state before rendering children
3. SocketProvider depends on Redux auth token
4. TelegramProvider depends on Redux telegram state
5. HashRouter provides navigation to all routes
## SocketProvider (`providers/SocketProvider.tsx`)
Manages Socket.io connection lifecycle and MCP initialization.
### Responsibilities
- Auto-connect when auth token is available
- Auto-disconnect when token is cleared
- Initialize MCP server when socket connects
- Update Redux with connection status
### Implementation
```typescript
interface SocketContextValue {
socket: Socket | null;
isConnected: boolean;
emit: (event: string, data: unknown) => void;
on: (event: string, handler: Function) => void;
off: (event: string, handler: Function) => void;
}
export function SocketProvider({ children }) {
const token = useAppSelector((state) => state.auth.token);
const userId = useAppSelector((state) => state.user.profile?.id);
const dispatch = useAppDispatch();
useEffect(() => {
if (!token || !userId) {
socketService.disconnect();
dispatch(setSocketStatus({ userId, status: 'disconnected' }));
return;
}
// Connect with auth token
socketService.connect(token);
dispatch(setSocketStatus({ userId, status: 'connecting' }));
// Handle connection events
socketService.on('connect', () => {
dispatch(setSocketStatus({ userId, status: 'connected' }));
dispatch(setSocketId({ userId, socketId: socketService.getSocket()?.id }));
// Initialize MCP server
initMCPServer(socketService.getSocket());
});
socketService.on('disconnect', () => {
dispatch(setSocketStatus({ userId, status: 'disconnected' }));
cleanupMCP();
});
socketService.on('connect_error', (error) => {
console.error('Socket connection error:', error);
dispatch(setSocketStatus({ userId, status: 'disconnected' }));
});
return () => {
socketService.disconnect();
cleanupMCP();
};
}, [token, userId]);
const contextValue: SocketContextValue = {
socket: socketService.getSocket(),
isConnected: socketService.isConnected(),
emit: socketService.emit.bind(socketService),
on: socketService.on.bind(socketService),
off: socketService.off.bind(socketService)
};
return (
<SocketContext.Provider value={contextValue}>
{children}
</SocketContext.Provider>
);
}
```
### Usage
```typescript
import { useSocket } from '../providers/SocketProvider';
function MyComponent() {
const { socket, isConnected, emit, on, off } = useSocket();
useEffect(() => {
const handler = (data) => console.log('Received:', data);
on('event-name', handler);
return () => off('event-name', handler);
}, [on, off]);
const sendMessage = () => {
emit('send-message', { text: 'Hello!' });
};
return (
<div>
<span>Status: {isConnected ? 'Connected' : 'Disconnected'}</span>
<button onClick={sendMessage}>Send</button>
</div>
);
}
```
## TelegramProvider (`providers/TelegramProvider.tsx`)
Manages Telegram MTProto connection lifecycle.
### Responsibilities
- Initialize MTProto client when user is authenticated
- Connect to Telegram servers
- Store session string in Redux
- Provide Telegram context to children
### Implementation
```typescript
interface TelegramContextValue {
client: TelegramClient | null;
connectionStatus: ConnectionStatus;
authStatus: AuthStatus;
connect: () => Promise<void>;
disconnect: () => Promise<void>;
}
export function TelegramProvider({ children }) {
const dispatch = useAppDispatch();
const userId = useAppSelector((state) => state.user.profile?.id);
const telegramState = useAppSelector((state) =>
state.telegram.byUser[userId]
);
useEffect(() => {
if (!userId) return;
// Parallel initialization for faster startup
const init = async () => {
try {
// Initialize and connect in parallel
await Promise.all([
dispatch(initializeTelegram(userId)).unwrap(),
dispatch(connectTelegram(userId)).unwrap()
]);
} catch (error) {
console.error('Telegram initialization failed:', error);
}
};
init();
return () => {
dispatch(disconnectTelegram(userId));
};
}, [userId]);
// Restore session from persisted state
useEffect(() => {
if (telegramState?.sessionString) {
const client = mtprotoService.getInstance().getClient();
if (client) {
client.setSession(telegramState.sessionString);
}
}
}, [telegramState?.sessionString]);
const contextValue: TelegramContextValue = {
client: mtprotoService.getInstance().getClient(),
connectionStatus: telegramState?.connectionStatus || 'disconnected',
authStatus: telegramState?.authStatus || 'not_authenticated',
connect: () => dispatch(connectTelegram(userId)).unwrap(),
disconnect: () => dispatch(disconnectTelegram(userId)).unwrap()
};
return (
<TelegramContext.Provider value={contextValue}>
{children}
</TelegramContext.Provider>
);
}
```
### Usage
```typescript
import { useTelegram } from '../providers/TelegramProvider';
function ChatList() {
const { client, connectionStatus, authStatus } = useTelegram();
const [chats, setChats] = useState([]);
useEffect(() => {
if (connectionStatus === 'connected' && authStatus === 'authenticated') {
const fetchChats = async () => {
const dialogs = await client.getDialogs({ limit: 20 });
setChats(dialogs);
};
fetchChats();
}
}, [client, connectionStatus, authStatus]);
if (connectionStatus !== 'connected') {
return <div>Connecting to Telegram...</div>;
}
return (
<ul>
{chats.map((chat) => (
<li key={chat.id}>{chat.title}</li>
))}
</ul>
);
}
```
## UserProvider (`providers/UserProvider.tsx`)
Minimal user context provider (most user state is in Redux).
### Responsibilities
- Legacy user context for compatibility
- May be deprecated in favor of Redux
### Implementation
```typescript
interface UserContextValue {
user: User | null;
loading: boolean;
}
export function UserProvider({ children }) {
const user = useAppSelector((state) => state.user.profile);
const loading = useAppSelector((state) => state.user.loading);
return (
<UserContext.Provider value={{ user, loading }}>
{children}
</UserContext.Provider>
);
}
```
### Usage
```typescript
import { useUserContext } from '../providers/UserProvider';
function Header() {
const { user, loading } = useUserContext();
if (loading) return <Skeleton />;
if (!user) return null;
return <span>Welcome, {user.firstName}</span>;
}
```
## Provider Patterns
### Effect-Based Lifecycle
Providers use `useEffect` to manage service lifecycle:
```typescript
useEffect(() => {
// Setup on mount or dependency change
service.connect();
// Cleanup on unmount or dependency change
return () => {
service.disconnect();
};
}, [dependencies]);
```
### Redux Integration
Providers read from and dispatch to Redux:
```typescript
// Read state
const token = useAppSelector((state) => state.auth.token);
// Dispatch actions
const dispatch = useAppDispatch();
dispatch(setStatus({ userId, status: 'connected' }));
```
### Parallel Initialization
TelegramProvider runs init and connect in parallel:
```typescript
await Promise.all([
dispatch(initializeTelegram(userId)).unwrap(),
dispatch(connectTelegram(userId)).unwrap()
]);
```
This reduces startup time compared to sequential operations.
### Session Restoration
Providers restore persisted state on mount:
```typescript
useEffect(() => {
if (persistedSession) {
service.restoreSession(persistedSession);
}
}, [persistedSession]);
```
## Context vs Redux
| Use Context For | Use Redux For |
|-----------------|---------------|
| Service instances (socket, client) | Serializable state (status, data) |
| Methods (emit, on, off) | Persisted state (sessions, tokens) |
| Derived values | Complex state logic |
Example:
- `SocketContext` provides `socket` instance and `emit` method
- Redux stores `socketStatus` and `socketId`
## Testing Providers
### Mock Provider for Tests
```typescript
// test-utils.tsx
const mockSocketContext: SocketContextValue = {
socket: null,
isConnected: true,
emit: jest.fn(),
on: jest.fn(),
off: jest.fn()
};
export function TestProviders({ children }) {
return (
<Provider store={testStore}>
<SocketContext.Provider value={mockSocketContext}>
{children}
</SocketContext.Provider>
</Provider>
);
}
```
### Testing Provider Effects
```typescript
test('SocketProvider connects when token is available', () => {
const store = createTestStore({ auth: { token: 'test-token' } });
render(
<Provider store={store}>
<SocketProvider>
<TestComponent />
</SocketProvider>
</Provider>
);
expect(socketService.connect).toHaveBeenCalledWith('test-token');
});
```
---
*Previous: [Components](./06-components.md) | Next: [Hooks & Utils](./08-hooks-utils.md)*
+505
View File
@@ -0,0 +1,505 @@
# Hooks & Utilities
Custom React hooks and utility functions.
## Custom Hooks
### useSocket (`hooks/useSocket.ts`)
Access Socket.io functionality from any component.
```typescript
interface UseSocketReturn {
socket: Socket | null;
isConnected: boolean;
emit: (event: string, data: unknown) => void;
on: (event: string, handler: Function) => void;
off: (event: string, handler: Function) => void;
once: (event: string, handler: Function) => void;
}
function useSocket(): UseSocketReturn;
```
**Usage:**
```typescript
import { useSocket } from '../hooks/useSocket';
function ChatInput() {
const { emit, isConnected } = useSocket();
const sendMessage = (text: string) => {
if (isConnected) {
emit('chat:message', { text });
}
};
return (
<input
disabled={!isConnected}
onKeyDown={(e) => e.key === 'Enter' && sendMessage(e.target.value)}
/>
);
}
```
**With event listeners:**
```typescript
function Notifications() {
const { on, off } = useSocket();
const [notifications, setNotifications] = useState([]);
useEffect(() => {
const handler = (notification) => {
setNotifications((prev) => [...prev, notification]);
};
on('notification', handler);
return () => off('notification', handler);
}, [on, off]);
return <NotificationList items={notifications} />;
}
```
### useUser (`hooks/useUser.ts`)
Access user profile data and loading state.
```typescript
interface UseUserReturn {
user: User | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
function useUser(): UseUserReturn;
```
**Usage:**
```typescript
import { useUser } from '../hooks/useUser';
function ProfileHeader() {
const { user, loading, error, refetch } = useUser();
if (loading) return <Skeleton />;
if (error) return <Error message={error} onRetry={refetch} />;
if (!user) return null;
return (
<div className="profile">
<Avatar src={user.avatar} />
<span>{user.firstName} {user.lastName}</span>
</div>
);
}
```
### Settings Modal Hooks
#### useSettingsNavigation (`components/settings/hooks/useSettingsNavigation.ts`)
URL-based navigation for settings modal.
```typescript
interface UseSettingsNavigationReturn {
currentRoute: string; // Current settings path
navigateTo: (panel: string) => void; // Navigate to panel
navigateBack: () => void; // Go back one level
closeModal: () => void; // Close settings entirely
}
function useSettingsNavigation(): UseSettingsNavigationReturn;
```
**Usage:**
```typescript
import { useSettingsNavigation } from './hooks/useSettingsNavigation';
function SettingsMenu() {
const { navigateTo, closeModal } = useSettingsNavigation();
return (
<nav>
<button onClick={() => navigateTo('connections')}>
Connections
</button>
<button onClick={() => navigateTo('privacy')}>
Privacy
</button>
<button onClick={closeModal}>Close</button>
</nav>
);
}
```
#### useSettingsAnimation (`components/settings/hooks/useSettingsAnimation.ts`)
Animation state management for settings modal.
```typescript
interface UseSettingsAnimationReturn {
isEntering: boolean; // Modal is animating in
isExiting: boolean; // Modal is animating out
animationClass: string; // CSS class for current state
}
function useSettingsAnimation(): UseSettingsAnimationReturn;
```
**Usage:**
```typescript
import { useSettingsAnimation } from './hooks/useSettingsAnimation';
function SettingsModal() {
const { animationClass, isExiting } = useSettingsAnimation();
return (
<div className={`modal ${animationClass}`}>
{/* Content */}
</div>
);
}
```
## Utilities
### Configuration (`utils/config.ts`)
Environment variable access with defaults.
```typescript
// Backend URL
export const BACKEND_URL =
import.meta.env.VITE_BACKEND_URL || 'https://api.example.com';
// Telegram configuration
export const TELEGRAM_API_ID = import.meta.env.VITE_TELEGRAM_API_ID;
export const TELEGRAM_API_HASH = import.meta.env.VITE_TELEGRAM_API_HASH;
export const TELEGRAM_BOT_USERNAME = import.meta.env.VITE_TELEGRAM_BOT_USERNAME;
export const TELEGRAM_BOT_ID = import.meta.env.VITE_TELEGRAM_BOT_ID;
// Debug mode
export const DEBUG = import.meta.env.VITE_DEBUG === 'true';
```
**Usage:**
```typescript
import { BACKEND_URL, DEBUG } from '../utils/config';
const response = await fetch(`${BACKEND_URL}/api/users`);
if (DEBUG) {
console.log('Response:', response);
}
```
### Deep Link (`utils/deeplink.ts`)
Build deep link URLs for authentication handoff.
```typescript
// Build auth deep link
function buildAuthDeepLink(token: string): string;
// Parse deep link URL
function parseDeepLink(url: string): { path: string; params: URLSearchParams };
```
**Usage:**
```typescript
import { buildAuthDeepLink } from '../utils/deeplink';
// Build URL for browser redirect
const deepLink = buildAuthDeepLink(loginToken);
// → "outsourced://auth?token=abc123"
// In web frontend after auth:
window.location.href = deepLink;
```
### Desktop Deep Link Listener (`utils/desktopDeepLinkListener.ts`)
Handle incoming deep links in desktop app.
```typescript
// Setup listener for deep link events
async function setupDesktopDeepLinkListener(): Promise<void>;
```
**Called in main.tsx:**
```typescript
// Lazy import to ensure Tauri IPC is ready
import('./utils/desktopDeepLinkListener').then((m) => {
m.setupDesktopDeepLinkListener().catch(console.error);
});
```
**What it does:**
1. Listens for `onOpenUrl` events from Tauri deep-link plugin
2. Parses `outsourced://auth?token=...` URLs
3. Calls Rust `exchange_token` command (bypasses CORS)
4. Stores session in Redux
5. Navigates to `/onboarding` or `/home`
**Loop prevention:**
```typescript
// Set flag before navigation to prevent reprocessing
localStorage.setItem('deepLinkHandled', 'true');
window.location.replace('/');
// On next load, clear flag
if (localStorage.getItem('deepLinkHandled') === 'true') {
localStorage.removeItem('deepLinkHandled');
return; // Don't process again
}
```
### URL Opener (`utils/openUrl.ts`)
Cross-platform URL opening.
```typescript
// Open URL in system browser
async function openUrl(url: string): Promise<void>;
```
**Usage:**
```typescript
import { openUrl } from '../utils/openUrl';
// Opens in system browser (not in-app WebView)
await openUrl('https://telegram.org/auth');
```
**Implementation:**
```typescript
export async function openUrl(url: string): Promise<void> {
try {
// Try Tauri opener plugin first
const { open } = await import('@tauri-apps/plugin-opener');
await open(url);
} catch {
// Fallback to browser API
window.open(url, '_blank');
}
}
```
## Polyfills (`polyfills.ts`)
Node.js polyfills for browser environment.
The `telegram` npm package requires Node.js APIs. These are polyfilled:
```typescript
// polyfills.ts
import { Buffer } from 'buffer';
import process from 'process';
import util from 'util';
window.Buffer = Buffer;
window.process = process;
window.util = util;
```
**Imported at app entry:**
```typescript
// main.tsx
import './polyfills';
// ... rest of app
```
**Vite configuration:**
```typescript
// vite.config.ts
export default defineConfig({
resolve: {
alias: {
buffer: 'buffer',
process: 'process/browser',
util: 'util'
}
},
define: {
'process.env': {},
global: 'globalThis'
}
});
```
## Types
### API Types (`types/api.ts`)
```typescript
// API response wrapper
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
}
// API error
interface ApiError {
code: string;
message: string;
details?: unknown;
}
// User interface
interface User {
id: string;
firstName: string;
lastName?: string;
username?: string;
email?: string;
avatar?: string;
telegramId?: string;
subscription?: SubscriptionInfo;
usage?: UsageInfo;
createdAt: string;
updatedAt: string;
}
```
### Onboarding Types (`types/onboarding.ts`)
```typescript
// Onboarding step definition
interface OnboardingStep {
id: string;
title: string;
component: React.ComponentType<StepProps>;
}
// Step component props
interface StepProps {
onNext: () => void;
onBack: () => void;
}
// Connection option
interface ConnectionOption {
id: string;
label: string;
icon: React.ComponentType;
description: string;
comingSoon?: boolean;
}
```
## Static Data
### Countries (`data/countries.ts`)
Country list for phone number input.
```typescript
interface Country {
code: string; // "US"
name: string; // "United States"
dialCode: string; // "+1"
flag: string; // "🇺🇸"
}
export const countries: Country[];
```
**Usage:**
```typescript
import { countries } from '../data/countries';
function PhoneInput() {
const [country, setCountry] = useState(countries[0]);
return (
<div>
<select
value={country.code}
onChange={(e) => setCountry(
countries.find((c) => c.code === e.target.value)
)}
>
{countries.map((c) => (
<option key={c.code} value={c.code}>
{c.flag} {c.name} ({c.dialCode})
</option>
))}
</select>
<input placeholder="Phone number" />
</div>
);
}
```
## Best Practices
### Hook Dependencies
Always include dependencies in useEffect:
```typescript
// Good
useEffect(() => {
on('event', handler);
return () => off('event', handler);
}, [on, off, handler]);
// Bad - missing dependencies
useEffect(() => {
on('event', handler);
return () => off('event', handler);
}, []);
```
### Cleanup Functions
Always clean up subscriptions:
```typescript
useEffect(() => {
const subscription = subscribe();
return () => subscription.unsubscribe();
}, []);
```
### Error Boundaries
Wrap utility calls in try-catch:
```typescript
try {
await openUrl(url);
} catch (error) {
console.error('Failed to open URL:', error);
// Fallback behavior
}
```
### Type Safety
Use TypeScript generics for API calls:
```typescript
const user = await apiClient.get<User>('/users/me');
// user is typed as User
```
---
*Previous: [Providers](./07-providers.md) | [Back to Index](./README.md)*
+78
View File
@@ -0,0 +1,78 @@
# Source Code Documentation
This documentation covers the `/src` folder structure of the Outsourced Crypto Community Platform.
## Quick Reference
| Document | Description |
|----------|-------------|
| [Architecture Overview](./01-architecture.md) | High-level system architecture and provider chain |
| [State Management](./02-state-management.md) | Redux Toolkit slices, persistence, and selectors |
| [Services Layer](./03-services.md) | Singleton services (Socket.io, MTProto, API client) |
| [MCP System](./04-mcp-system.md) | Model Context Protocol with 81 Telegram tools |
| [Pages & Routing](./05-pages-routing.md) | Route definitions, guards, and page components |
| [Components](./06-components.md) | Reusable UI components and settings modal system |
| [Providers](./07-providers.md) | React context providers and lifecycle management |
| [Hooks & Utils](./08-hooks-utils.md) | Custom hooks and utility functions |
## File Count Summary
| Category | Files | Purpose |
|----------|-------|---------|
| Entry & Configuration | 7 | App init, routing, styles, types |
| State Management | 13 | Redux slices, selectors, hooks |
| Providers | 3 | Socket, Telegram, User contexts |
| Services | 5 | Singleton API clients |
| Pages | 9 | Full-page route components |
| Components | 28 | Reusable UI + settings modal (16 files) |
| MCP Core | 14 | MCP interfaces, transport, logging |
| MCP Telegram Tools | 81 | Individual Telegram API operations |
| Hooks | 2 | Custom React hooks |
| Types | 2 | TypeScript interfaces |
| Utils | 4 | Config, deep link, URL utilities |
| Data | 1 | Static data (countries) |
| Assets | 10+ | Icons and images |
| **TOTAL** | **171+** | Complete frontend application |
## Directory Structure
```
src/
├── App.tsx # Root component with provider chain
├── AppRoutes.tsx # Route definitions
├── main.tsx # Entry point
├── polyfills.ts # Node.js polyfills for browser
├── index.css # Global styles
├── store/ # Redux state management (13 files)
├── providers/ # Context providers (3 files)
├── services/ # Singleton services (5 files)
├── lib/mcp/ # MCP system (95+ files)
├── pages/ # Page components (9 files)
├── components/ # UI components (28 files)
├── hooks/ # Custom hooks (2 files)
├── types/ # TypeScript types (2 files)
├── utils/ # Utilities (4 files)
├── data/ # Static data (1 file)
└── assets/ # Icons and images
```
## Key Architectural Decisions
1. **HashRouter over BrowserRouter** - Required for Tauri deep link compatibility
2. **Redux Toolkit with Persistence** - Robust state management with rehydration
3. **Singleton Services** - Prevents connection leaks for Socket.io and MTProto
4. **Per-User State Scoping** - Telegram/socket state keyed by user ID
5. **Portal-Based Settings Modal** - URL routing without affecting main routes
6. **81-Tool MCP System** - Comprehensive Telegram API coverage
## Getting Started
1. Read [Architecture Overview](./01-architecture.md) for the big picture
2. Understand [State Management](./02-state-management.md) for data flow
3. Review [Services Layer](./03-services.md) for backend communication
4. Explore [MCP System](./04-mcp-system.md) for AI tool integration
---
*Documentation maintained by stevenbaba*