mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-10 06:30:46 +08:00
fix(telegram): isolate long polling transport
This commit is contained in:
parent
02ddea0054
commit
14519060f0
13 changed files with 603 additions and 181 deletions
|
|
@ -105,19 +105,23 @@ export const COMMANDS_MENU_BUTTON = Object.freeze({ type: 'commands' });
|
|||
export class TelegramApi {
|
||||
#token;
|
||||
#fetch;
|
||||
#FormDataImpl;
|
||||
#baseUrl;
|
||||
#fileUploadTimeoutMs;
|
||||
|
||||
constructor({
|
||||
token,
|
||||
fetchImpl = fetch,
|
||||
FormDataImpl = globalThis.FormData,
|
||||
baseUrl = DEFAULT_BASE_URL,
|
||||
fileUploadTimeoutMs = DEFAULT_FILE_UPLOAD_TIMEOUT_MS,
|
||||
}) {
|
||||
if (!validTelegramToken(token)) throw new TypeError('Telegram Bot Token is invalid');
|
||||
if (typeof fetchImpl !== 'function') throw new TypeError('TelegramApi requires fetch');
|
||||
if (typeof FormDataImpl !== 'function') throw new TypeError('TelegramApi requires FormData');
|
||||
this.#token = token.trim();
|
||||
this.#fetch = fetchImpl;
|
||||
this.#FormDataImpl = FormDataImpl;
|
||||
this.#baseUrl = new URL(baseUrl);
|
||||
this.#fileUploadTimeoutMs = positiveTimeout(fileUploadTimeoutMs, 'fileUploadTimeoutMs');
|
||||
}
|
||||
|
|
@ -273,7 +277,7 @@ export class TelegramApi {
|
|||
|| !Buffer.isBuffer(file.bytes)) {
|
||||
throw new TypeError(`A Telegram ${mediaLabel} is required`);
|
||||
}
|
||||
const payload = new FormData();
|
||||
const payload = new this.#FormDataImpl();
|
||||
payload.append('chat_id', String(chatId));
|
||||
payload.append(
|
||||
fieldName,
|
||||
|
|
|
|||
21
src/channels/telegram/telegram-http.mjs
Normal file
21
src/channels/telegram/telegram-http.mjs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import {
|
||||
EnvHttpProxyAgent,
|
||||
FormData as UndiciFormData,
|
||||
fetch as undiciFetch,
|
||||
} from 'undici';
|
||||
|
||||
const TELEGRAM_CONNECTIONS = 4;
|
||||
|
||||
export function createTelegramHttpTransport() {
|
||||
const dispatcher = new EnvHttpProxyAgent({ connections: TELEGRAM_CONNECTIONS });
|
||||
let destroyed = false;
|
||||
return {
|
||||
fetchImpl: (url, options = {}) => undiciFetch(url, { ...options, dispatcher }),
|
||||
FormDataImpl: UndiciFormData,
|
||||
async destroy() {
|
||||
if (destroyed) return;
|
||||
destroyed = true;
|
||||
await dispatcher.destroy();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { createTextDeliveryBlock } from '../shared/semantic/delivery.mjs';
|
|||
import { t } from '../shared/i18n.mjs';
|
||||
import { captureContextEnhancement } from '../shared/context-enhancement.mjs';
|
||||
import { COMMANDS_MENU_BUTTON, TelegramApi } from './telegram-api.mjs';
|
||||
import { createTelegramHttpTransport } from './telegram-http.mjs';
|
||||
import { createTelegramBridgeStatus, TelegramHarnessBridge } from './telegram-bridge.mjs';
|
||||
import {
|
||||
splitTelegramRegularText,
|
||||
|
|
@ -671,9 +672,11 @@ export class TelegramRuntime {
|
|||
#logger;
|
||||
#replyTimeoutMs;
|
||||
#createApi;
|
||||
#createHttpTransport;
|
||||
#accessMode;
|
||||
#allowedPrivateUserIds;
|
||||
#status = createTelegramRuntimeStatus();
|
||||
#httpTransport = null;
|
||||
#api = null;
|
||||
#bridge = null;
|
||||
#abortController = null;
|
||||
|
|
@ -689,6 +692,7 @@ export class TelegramRuntime {
|
|||
logger = console,
|
||||
replyTimeoutMs = 600_000,
|
||||
createApi = (options) => new TelegramApi(options),
|
||||
createHttpTransport = createTelegramHttpTransport,
|
||||
}) {
|
||||
if (!config || !token || !harness || !state) {
|
||||
throw new TypeError('TelegramRuntime requires config, token, Harness, and state');
|
||||
|
|
@ -701,6 +705,7 @@ export class TelegramRuntime {
|
|||
this.#logger = logger;
|
||||
this.#replyTimeoutMs = replyTimeoutMs;
|
||||
this.#createApi = createApi;
|
||||
this.#createHttpTransport = createHttpTransport;
|
||||
const accessPolicy = normalizeTelegramAccessPolicy(config);
|
||||
this.#accessMode = accessPolicy.accessMode;
|
||||
this.#allowedPrivateUserIds = new Set(accessPolicy.allowedUsers);
|
||||
|
|
@ -762,9 +767,15 @@ export class TelegramRuntime {
|
|||
|
||||
const controller = new AbortController();
|
||||
this.#abortController = controller;
|
||||
const api = this.#createApi({ token: this.#token });
|
||||
this.#api = api;
|
||||
try {
|
||||
const transport = this.#createHttpTransport();
|
||||
this.#httpTransport = transport;
|
||||
const api = this.#createApi({
|
||||
token: this.#token,
|
||||
fetchImpl: transport.fetchImpl,
|
||||
FormDataImpl: transport.FormDataImpl,
|
||||
});
|
||||
this.#api = api;
|
||||
const bot = await api.getMe({ signal: controller.signal });
|
||||
if (String(bot?.id ?? '') !== this.#config.platformId || bot?.is_bot !== true) {
|
||||
throw new Error('Telegram token identity does not match the saved bot');
|
||||
|
|
@ -878,9 +889,11 @@ export class TelegramRuntime {
|
|||
async stop() {
|
||||
const pollTask = this.#pollTask;
|
||||
const bridge = this.#bridge;
|
||||
const httpTransport = this.#httpTransport;
|
||||
this.#abortController?.abort();
|
||||
this.#abortController = null;
|
||||
this.#pollTask = null;
|
||||
this.#httpTransport = null;
|
||||
this.#api = null;
|
||||
this.#bridge = null;
|
||||
await Promise.race([
|
||||
|
|
@ -891,6 +904,11 @@ export class TelegramRuntime {
|
|||
bridge?.waitForIdle() ?? Promise.resolve(),
|
||||
new Promise((resolve) => setTimeout(resolve, 2_000)),
|
||||
]);
|
||||
try {
|
||||
await httpTransport?.destroy();
|
||||
} catch {
|
||||
this.#logger.warn?.('[dsh-im:telegram] Telegram HTTP transport cleanup failed');
|
||||
}
|
||||
this.#status.ready = false;
|
||||
this.#status.connectionState = 'idle';
|
||||
return this.status;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue