You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将GramJS的Telegram认证拆分为两次HTTP POST请求?

问题描述

我正在基于Node.js+Express搭建服务,想要把GramJS的Telegram交互式认证流程拆分成两次HTTP POST请求:

  1. 第一次提交手机号和密码,触发Telegram发送验证码给用户
  2. 第二次提交收到的验证码,完成认证并保存会话信息

参考的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函数实现异步等待,不阻塞事件循环
  • 用pendingAuths Map存储会话状态,支持多用户同时发起认证
  • 拆分独立路由,逻辑更清晰,避免单路由分支过多
  • 增加错误处理和会话清理,防止内存泄漏和无效会话残留

内容的提问来源于stack exchange,提问作者Super Elephant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 08:54:26