mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 05:12:33 +00:00
* feat: add managed Telegram login flow and API endpoints - Introduced a new managed Telegram login flow, allowing users to link their Telegram accounts via a deep link. - Added `telegram_login_start` and `telegram_login_check` functions to handle the creation of link tokens and status checks for user linking. - Updated the API with new endpoints for managing Telegram login, including detailed response structures for link token creation and status verification. - Enhanced the `.env.example` file to include a configuration option for the Telegram bot username, facilitating easier setup for developers. * refactor: update Telegram integration to use core RPC for login flow - Replaced the managed DM API with core RPC calls for initiating and checking Telegram login status. - Introduced new API methods `telegramLoginStart` and `telegramLoginCheck` to handle link token creation and verification. - Updated the TelegramConfig and MessagingPanel components to utilize the new login flow, enhancing the user experience and simplifying the codebase. - Adjusted tests to reflect changes in the login process and ensure proper functionality. * docs: update TODO list with new user interaction registration task for memory skill * feat: add ChannelSetupModal for configuring channel integrations - Introduced a new reusable modal component, ChannelSetupModal, for configuring channel integrations such as Telegram and Discord. - The modal can be opened from the Skills page or Settings, enhancing user experience by providing a centralized configuration interface. - Updated MessagingPanel and Skills components to integrate the new modal, allowing users to easily manage their channel settings. - Implemented channel-specific configuration components for better modularity and maintainability. * feat: add ChannelSetupModal for configuring channel integrations - Introduced a new reusable modal component, ChannelSetupModal, for configuring channel integrations such as Telegram and Discord. - The modal can be opened from the Skills page or Settings, enhancing user experience by providing a centralized configuration interface. - Updated MessagingPanel and Skills components to integrate the new modal, allowing users to easily manage their channel settings. - Implemented channel-specific configuration components for better modularity and maintainability. * style: update channel components for improved UI consistency - Refactored styles across various channel configuration components, including ChannelCapabilities, ChannelConfigPanel, ChannelFieldInput, ChannelSelector, DiscordConfig, TelegramConfig, and WebChannelConfig. - Enhanced color schemes and layout for better readability and visual appeal, ensuring a cohesive design across the application. - Updated status badge styles in definitions.ts to align with the new design standards. - Improved error message visibility and loading indicators in Channels page for a more user-friendly experience. * refactor: update WebChannelConfig and tests for improved clarity and functionality - Renamed the `definition` prop in WebChannelConfig to `_definition` for clarity. - Updated test cases in DiscordConfig and TelegramConfig to reflect changes in auth mode labels, ensuring accurate rendering of UI elements. - Adjusted the TelegramConfig test to fix the click event on the connect button, enhancing test reliability. - Modified channel definitions to streamline the managed DM auth mode, improving code organization and maintainability. * fix: enhance WebGL error handling in MeshGradient and RotatingTetrahedronCanvas components - Added error handling in the MeshGradient component to gracefully catch WebGL initialization failures, ensuring the app remains functional when the GPU context is unavailable. - Updated the RotatingTetrahedronCanvas component to verify WebGL context availability before initializing the renderer, preventing crashes and improving user experience. - Modified channel definitions to update auth mode labels for clarity and consistency across the application. * style: update RotatingTetrahedronCanvas colors and animation speed - Changed fill and edge colors in the RotatingTetrahedronCanvas component for improved visual appeal. - Adjusted opacity of the fill material to enhance transparency effects. - Introduced a speed multiplier for rotation animations, allowing for more dynamic visual effects. * refactor: improve WebGL error handling and cleanup in MeshGradient component - Removed unused canvas reference and WebGL context probing to streamline the MeshGradient component. - Enhanced error handling during shader compilation to throw an error when WebGL context may be lost, improving robustness. - Wrapped initialization logic in a try-catch block to gracefully handle failures, ensuring the gradient functionality is disabled if initialization fails. * refactor: simplify Telegram login flow by removing unused link token check - Removed the `check_channel_link_status` method from the `BackendOAuthClient`, streamlining the authentication process. - Updated `telegram_login_check` to directly fetch the user profile and determine link status based on the presence of `telegramId`, enhancing clarity and reducing complexity. - Adjusted logging to reflect the new flow and ensure accurate debugging information. * feat: enhance JSON-RPC method invocation with session expiration handling - Added logic to automatically clear stored session on receiving a 401 error from the backend, improving user experience by ensuring users are redirected to the login page when their session expires. - Introduced a helper function to identify session expiration errors, enhancing code clarity and maintainability. - Refactored the `invoke_method` function to incorporate this new error handling mechanism, ensuring robust session management during RPC calls. * style: update app-dotted-canvas background gradient for improved visibility - Changed the radial gradient background color in the app-dotted-canvas from rgba(0, 0, 0, 0.2) to rgba(0, 0, 0, 0.5), enhancing the contrast and overall visual appeal of the component. * feat: implement channel messaging API with end-to-end testing script - Added a new script `test-channel-messaging.sh` for end-to-end testing of message sending to Telegram via the backend API. - Implemented new backend API methods for sending messages, reactions, creating threads, updating threads, and listing threads in channels. - Enhanced the `BackendOAuthClient` with methods to handle channel messaging operations. - Updated controller schemas and handlers to support the new messaging functionalities, ensuring robust interaction with the backend. - Improved logging for better debugging and tracking of channel operations. * feat: add test-channel-receive script for real-time channel message listening - Introduced a new script `test-channel-receive.mjs` that connects to the backend Socket.IO server and listens for incoming channel messages. - Implemented session token retrieval and validation against the backend, ensuring secure message handling. - Added options for debugging and sending test messages, enhancing the script's usability for testing purposes. - Improved logging for better visibility of connection status and incoming messages. * feat: implement inbound channel message handling for real-time responses - Added functionality to handle inbound messages from channels (e.g., Telegram, Discord) by introducing the `handle_channel_inbound_message` function. - Implemented agent inference loop to process messages and send replies back to the originating channel via the REST API. - Enhanced logging for better tracking of message reception and response handling. - Integrated timeout handling to manage long-running requests effectively. * style: fix prettier formatting for channel components Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
268 lines
10 KiB
JavaScript
Executable File
268 lines
10 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
// ──────────────────────────────────────────────────────────────────────
|
|
// test-channel-receive.mjs
|
|
//
|
|
// Connects to the backend Socket.IO server, authenticates with the
|
|
// stored session JWT, and listens for incoming channel messages.
|
|
//
|
|
// Usage:
|
|
// node scripts/test-channel-receive.mjs
|
|
// node scripts/test-channel-receive.mjs --timeout 120
|
|
// node scripts/test-channel-receive.mjs --debug # verbose logging
|
|
// node scripts/test-channel-receive.mjs --send-test # also send a test msg
|
|
// ──────────────────────────────────────────────────────────────────────
|
|
|
|
import { execSync } from 'child_process';
|
|
import { existsSync, readFileSync } from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
|
|
const args = process.argv.slice(2);
|
|
const DEBUG = args.includes('--debug');
|
|
const SEND_TEST = args.includes('--send-test');
|
|
|
|
// ── Load env ────────────────────────────────────────────────────────
|
|
function loadEnv(filepath) {
|
|
if (!existsSync(filepath)) return;
|
|
const lines = readFileSync(filepath, 'utf-8').split('\n');
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const eqIdx = trimmed.indexOf('=');
|
|
if (eqIdx < 0) continue;
|
|
const key = trimmed.slice(0, eqIdx).trim();
|
|
const val = trimmed.slice(eqIdx + 1).trim();
|
|
if (!process.env[key]) process.env[key] = val;
|
|
}
|
|
}
|
|
|
|
loadEnv(path.join(ROOT, '.env'));
|
|
loadEnv(path.join(ROOT, 'app', '.env.local'));
|
|
|
|
const BACKEND_URL = (
|
|
process.env.BACKEND_URL ||
|
|
process.env.VITE_BACKEND_URL ||
|
|
'https://staging-api.alphahuman.xyz'
|
|
).replace(/\/+$/, '');
|
|
|
|
const TIMEOUT_SECS = parseInt(args.find((_, i, a) => a[i - 1] === '--timeout') || '60', 10);
|
|
|
|
function dbg(...args) {
|
|
if (DEBUG) console.log(' [debug]', ...args);
|
|
}
|
|
|
|
// ── Get session token from core ─────────────────────────────────────
|
|
function getSessionToken() {
|
|
const coreBin = path.join(ROOT, 'target', 'debug', 'openhuman-core');
|
|
try {
|
|
const output = execSync(`"${coreBin}" auth get_session_token`, {
|
|
cwd: ROOT,
|
|
encoding: 'utf-8',
|
|
timeout: 10_000,
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
});
|
|
const match = output.match(/"token":\s*"([^"]+)"/);
|
|
return match?.[1] || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
console.log('📡 Channel Receive Listener');
|
|
console.log('────────────────────────────────────────────────');
|
|
console.log(`Backend: ${BACKEND_URL}`);
|
|
console.log(`Timeout: ${TIMEOUT_SECS}s`);
|
|
console.log(`Debug: ${DEBUG}`);
|
|
console.log(`Send test: ${SEND_TEST}`);
|
|
console.log('');
|
|
|
|
// ── Resolve token ───────────────────────────────────────────────────
|
|
console.log('🔑 Resolving session token...');
|
|
let token = getSessionToken();
|
|
if (!token) {
|
|
console.error(' ❌ No session token found. Login via the app first.');
|
|
process.exit(1);
|
|
}
|
|
console.log(` ✅ Token: ${token.slice(0, 20)}...`);
|
|
|
|
// ── Validate token against backend ──────────────────────────────────
|
|
console.log('');
|
|
console.log('🔍 Validating token against backend...');
|
|
try {
|
|
const resp = await fetch(`${BACKEND_URL}/auth/me`, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
const data = await resp.json();
|
|
if (data.success && data.data) {
|
|
const u = data.data;
|
|
console.log(` ✅ User: ${u.username || u.firstName || u._id}`);
|
|
console.log(` ✅ Telegram ID: ${u.telegramId || 'NOT LINKED'}`);
|
|
if (!u.telegramId) {
|
|
console.log(' ⚠️ No Telegram linked — incoming messages won\'t route to you');
|
|
}
|
|
} else {
|
|
console.log(` ❌ Token invalid: ${JSON.stringify(data)}`);
|
|
process.exit(1);
|
|
}
|
|
} catch (err) {
|
|
console.log(` ❌ Backend unreachable: ${err.message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// ── Connect Socket.IO ───────────────────────────────────────────────
|
|
const socketIoPath = path.join(ROOT, 'node_modules', 'socket.io-client', 'dist', 'socket.io.esm.min.js');
|
|
let io;
|
|
try {
|
|
const mod = await import(socketIoPath);
|
|
io = mod.io || mod.default;
|
|
} catch {
|
|
try {
|
|
const mod = await import('socket.io-client');
|
|
io = mod.io || mod.default;
|
|
} catch (err) {
|
|
console.error(' ❌ Cannot load socket.io-client:', err.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
console.log('🔌 Connecting to Socket.IO...');
|
|
dbg('URL:', BACKEND_URL);
|
|
dbg('Transport: websocket + polling');
|
|
dbg('Path: /socket.io/');
|
|
|
|
const socket = io(BACKEND_URL, {
|
|
auth: { token },
|
|
transports: ['websocket', 'polling'],
|
|
path: '/socket.io/',
|
|
reconnection: true,
|
|
reconnectionAttempts: 3,
|
|
timeout: 10_000,
|
|
});
|
|
|
|
let messageCount = 0;
|
|
|
|
// ── In debug mode, log ALL events ───────────────────────────────────
|
|
if (DEBUG) {
|
|
const origOnevent = socket.onevent;
|
|
socket.onevent = function (packet) {
|
|
const eventName = packet.data?.[0];
|
|
const eventData = packet.data?.slice(1);
|
|
console.log(` [debug] EVENT: ${eventName}`, JSON.stringify(eventData).slice(0, 200));
|
|
origOnevent.call(this, packet);
|
|
};
|
|
}
|
|
|
|
socket.on('connect', () => {
|
|
console.log(` ✅ Connected (socket id: ${socket.id})`);
|
|
dbg('Transport:', socket.io.engine?.transport?.name);
|
|
console.log('');
|
|
console.log('────────────────────────────────────────────────');
|
|
console.log('👂 Listening for incoming channel messages...');
|
|
console.log(' Send a message to the Telegram bot now!');
|
|
console.log(` (auto-exit after ${TIMEOUT_SECS}s, or Ctrl+C)`);
|
|
console.log('────────────────────────────────────────────────');
|
|
console.log('');
|
|
|
|
// If --send-test, fire off a test message after connecting
|
|
if (SEND_TEST) {
|
|
setTimeout(async () => {
|
|
console.log('📤 Sending test message via backend REST API...');
|
|
try {
|
|
const resp = await fetch(`${BACKEND_URL}/channels/telegram/messages`, {
|
|
method: 'POST',
|
|
headers: {
|
|
Authorization: `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ text: '🧪 Round-trip test from receive listener script' }),
|
|
});
|
|
const data = await resp.json();
|
|
console.log(` Response: ${JSON.stringify(data)}`);
|
|
console.log('');
|
|
} catch (err) {
|
|
console.log(` ❌ Send failed: ${err.message}`);
|
|
}
|
|
}, 1000);
|
|
}
|
|
});
|
|
|
|
socket.on('ready', () => {
|
|
console.log(' 🟢 Server ready');
|
|
dbg('Socket authenticated and registered on server');
|
|
});
|
|
|
|
socket.on('connect_error', (err) => {
|
|
console.error(` ❌ Connection error: ${err.message}`);
|
|
dbg('Full error:', err);
|
|
});
|
|
|
|
socket.on('error', (data) => {
|
|
console.error(` ❌ Server error: ${JSON.stringify(data)}`);
|
|
});
|
|
|
|
socket.on('disconnect', (reason) => {
|
|
console.log(` 🔴 Disconnected: ${reason}`);
|
|
});
|
|
|
|
socket.io.on('reconnect_attempt', (attempt) => {
|
|
dbg(`Reconnect attempt #${attempt}`);
|
|
});
|
|
|
|
socket.io.on('reconnect', (attempt) => {
|
|
console.log(` 🔄 Reconnected after ${attempt} attempt(s)`);
|
|
});
|
|
|
|
// ── Channel message events ──────────────────────────────────────────
|
|
|
|
// Inbound: Telegram user → bot → backend → socket → here
|
|
socket.on('channel:message', (data) => {
|
|
messageCount++;
|
|
const ts = new Date().toLocaleTimeString();
|
|
console.log(`┌─ 📨 INBOUND #${messageCount} [${ts}]`);
|
|
console.log(`│ Channel: ${data.channel || 'unknown'}`);
|
|
console.log(`│ UserId: ${data.userId || 'unknown'}`);
|
|
console.log(`│ Message: ${data.message || '(empty)'}`);
|
|
console.log(`│ Time: ${data.receivedAt || 'unknown'}`);
|
|
console.log(`└──────────────────────────────────`);
|
|
console.log('');
|
|
dbg('Full payload:', JSON.stringify(data));
|
|
});
|
|
|
|
// Outbound confirmation: app sent message → backend → Telegram, socket notified
|
|
socket.on('channel:message:sent', (data) => {
|
|
const ts = new Date().toLocaleTimeString();
|
|
console.log(`┌─ 📤 OUTBOUND CONFIRMED [${ts}]`);
|
|
console.log(`│ Channel: ${data.channel || 'unknown'}`);
|
|
console.log(`│ Success: ${data.result?.success}`);
|
|
console.log(`│ MsgId: ${data.result?.messageId || 'n/a'}`);
|
|
if (data.message?.text) {
|
|
console.log(`│ Text: ${data.message.text.slice(0, 80)}`);
|
|
}
|
|
console.log(`└──────────────────────────────────`);
|
|
console.log('');
|
|
dbg('Full payload:', JSON.stringify(data));
|
|
});
|
|
|
|
// ── Timeout ─────────────────────────────────────────────────────────
|
|
const timer = setTimeout(() => {
|
|
console.log('');
|
|
console.log('────────────────────────────────────────────────');
|
|
console.log(`⏰ Timeout (${TIMEOUT_SECS}s). Received ${messageCount} inbound message(s).`);
|
|
socket.disconnect();
|
|
process.exit(0);
|
|
}, TIMEOUT_SECS * 1000);
|
|
|
|
process.on('SIGINT', () => {
|
|
clearTimeout(timer);
|
|
console.log('');
|
|
console.log('────────────────────────────────────────────────');
|
|
console.log(`👋 Stopped. Received ${messageCount} inbound message(s).`);
|
|
socket.disconnect();
|
|
process.exit(0);
|
|
});
|