如何将GramJS的Telegram认证拆分为两次HTTP POST请求?
问题描述
我正在基于Node.js+Express搭建服务,想要把GramJS的Telegram交互式认证流程拆分成两次HTTP POST请求:
- 第一次提交手机号和密码,触发Telegram发送验证码给用户
- 第二次提交收到的验证码,完成认证并保存会话信息
参考的GramJS官方交互式认证示例代码:
const { TelegramClient } = require('telegram') const { StringSession } = require('telegram/sessions') const input = require('input') // npm i input const apiId = 123456 const apiHash = '123456abcdfg' const stringSession = new StringSession(''); // 后续用session.save()的值填充 (async () => { console.log('Loading interactive example...') const client = new TelegramClient(stringSession, apiId, apiHash, { connectionRetries: 5 }) await client.start({ phoneNumber: async () => await input.text('number ?'), password: async () => await input.text('password?'), phoneCode: async () => await input.text('Code ?'), onError: (err) => console.log(err), }); console.log('You should now be connected.') console.log(client.session.save()) // 保存这个字符串避免重复登录 await client.sendMessage('me', { message: 'Hello!' }); })()
尝试用Promise实现分步骤认证,但第二次调用POST /setup接口时会卡住,无法完成验证码解析,我的实现代码如下:
let code = null; async function waitForPhoneCode() { return new Promise(function(resolve, reject) { while(code==null){ // 等待下一次HTTP请求设置code } resolve(code); }); } app.post('/setup', async (req, res) => { const setupStep = req.body.setupStep; const apiId = req.body.apiId; const apiHash = req.body.apiHash; const phoneNumber = req.body.phoneNumber; const password = req.body.password; const obtainedCode = req.body.code; credentials = { "apiId": apiId, "apiHash": apiHash } if(setupStep == 1){ client = new TelegramClient(new StringSession(""), credentials.apiId, credentials.apiHash, { connectionRetries: 5, }); res.json({ "status": "ok", }) client.start({ phoneNumber: phoneNumber, password: password, phoneCode: waitForPhoneCode(), onError: (err) => console.log(err), }); } else if(setupStep == 2){ code = obtainedCode; console.log("You should now be connected."); credentials = { apiId: apiId, apiHash: apiHash, stringSession: new sessionStorage(client.session.save()) } fs.writeFileSync(credentialsPath, JSON.stringify({ apiId: apiId, apiHash: apiHash, stringSession: client.session.save(), })); await client.sendMessage("me", { message: "Setup Completed!" }); res.redirect('/'); } })
请问有没有办法让第一次HTTP请求返回响应后,通过第二次请求传递数据完成Promise的resolve,实现分两次的认证流程?
解决方案
你的问题核心在于waitForPhoneCode里的同步while循环——它会完全阻塞Node.js的事件循环,导致后续的HTTP请求(第二次提交验证码)根本无法被处理,自然无法完成Promise的resolve。
要实现分步骤的认证,需要用异步等待机制替代同步阻塞,同时要考虑多用户场景(不能用全局变量存储状态),具体实现如下:
1. 初始化全局会话存储
创建一个全局Map,用来存储每个待认证会话的resolve函数、Telegram客户端实例及相关凭证,键建议用用户提交的手机号(或生成唯一会话ID):
const { TelegramClient } = require('telegram'); const { StringSession } = require('telegram/sessions'); const fs = require('fs'); const express = require('express'); const app = express(); app.use(express.json()); // 存储等待验证码的会话:key为手机号,value为{ resolve, client, apiId, apiHash } const pendingAuths = new Map(); const credentialsPath = './credentials.json';
2. 第一步:提交手机号和密码,触发验证码发送
拆分独立路由处理第一步请求,创建Telegram客户端并启动认证流程,将phoneCode对应的Promise resolve函数存入全局Map后立即返回响应:
app.post('/setup/step1', async (req, res) => { const { apiId, apiHash, phoneNumber, password } = req.body; try { const stringSession = new StringSession(''); const client = new TelegramClient(stringSession, apiId, apiHash, { connectionRetries: 5, }); // 创建Promise,将resolve函数存入pendingAuths const codePromise = new Promise((resolve) => { pendingAuths.set(phoneNumber, { resolve, client, apiId, apiHash }); }); // 启动认证流程(不await,避免阻塞响应返回) client.start({ phoneNumber: () => phoneNumber, password: () => password, phoneCode: () => codePromise, onError: (err) => { console.error('认证错误:', err); pendingAuths.delete(phoneNumber); } }).then(() => { // 认证完成后清理会话 pendingAuths.delete(phoneNumber); }); res.json({ status: 'ok', message: '验证码已发送,请查收' }); } catch (err) { res.status(400).json({ status: 'error', message: err.message }); } });
3. 第二步:提交验证码,完成认证
拆分独立路由处理第二步请求,根据手机号从全局Map中取出对应会话,调用resolve函数传递验证码,完成认证后保存会话信息:
app.post('/setup/step2', async (req, res) => { const { phoneNumber, code } = req.body; const pendingAuth = pendingAuths.get(phoneNumber); if (!pendingAuth) { return res.status(400).json({ status: 'error', message: '未找到待认证会话,请先完成第一步' }); } try { // 触发Promise的resolve,传递验证码 pendingAuth.resolve(code); // 等待认证完成并连接客户端 await pendingAuth.client.connect(); const sessionString = pendingAuth.client.session.save(); // 保存凭证到本地文件 fs.writeFileSync(credentialsPath, JSON.stringify({ apiId: pendingAuth.apiId, apiHash: pendingAuth.apiHash, stringSession: sessionString, })); await pendingAuth.client.sendMessage('me', { message: 'Setup Completed!' }); res.redirect('/'); } catch (err) { res.status(400).json({ status: 'error', message: err.message }); } finally { // 无论成功失败,清理会话避免内存泄漏 pendingAuths.delete(phoneNumber); } });
关键改进点
- 移除同步阻塞的
while循环,改用Promise的resolve函数实现异步等待,不阻塞事件循环 - 用
pendingAuthsMap存储会话状态,支持多用户同时发起认证 - 拆分独立路由,逻辑更清晰,避免单路由分支过多
- 增加错误处理和会话清理,防止内存泄漏和无效会话残留
内容的提问来源于stack exchange,提问作者Super Elephant
相关产品推荐
相关产品推荐

