mirror of
https://github.com/by-sonic/tglock.git
synced 2026-10-08 18:26:01 +03:00
fix: address routing, media, worker and platform issues
This commit is contained in:
+71
-15
@@ -28,9 +28,14 @@ const ALLOWED_DESTINATIONS = new Set([
|
||||
]);
|
||||
|
||||
const TELEGRAM_PORT = 443;
|
||||
const CONNECT_TIMEOUT_MS = 3000;
|
||||
// WebSocket events cannot be paused. Bound the outstanding writes instead of
|
||||
// retaining an unlimited chain of promises when Telegram stops reading.
|
||||
const MAX_PENDING_BYTES = 1024 * 1024;
|
||||
const MAX_PENDING_MESSAGES = 256;
|
||||
|
||||
export default {
|
||||
async fetch(request, env) {
|
||||
async fetch(request, env = {}) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname !== "/apiws") {
|
||||
@@ -50,19 +55,48 @@ export default {
|
||||
return new Response("destination not allowed", { status: 403 });
|
||||
}
|
||||
|
||||
if (!request.headers.get("Sec-WebSocket-Protocol")?.split(",").some((p) => p.trim() === "binary")) {
|
||||
return new Response("binary websocket subprotocol required", { status: 400 });
|
||||
}
|
||||
|
||||
let upstream;
|
||||
let timer;
|
||||
let timedOut = false;
|
||||
try {
|
||||
upstream = connect({ hostname: destination, port: TELEGRAM_PORT });
|
||||
// Both promises can reject on connect failure; observe closed immediately.
|
||||
upstream.closed.catch(() => {});
|
||||
await Promise.race([
|
||||
upstream.opened,
|
||||
new Promise((_, reject) => {
|
||||
timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
reject(new Error("connect timeout"));
|
||||
}, CONNECT_TIMEOUT_MS);
|
||||
}),
|
||||
]);
|
||||
} catch {
|
||||
if (upstream) await upstream.close().catch(() => {});
|
||||
return new Response(timedOut ? "Telegram TCP connect timeout" : "Telegram TCP connect failed", {
|
||||
status: timedOut ? 504 : 502,
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
const [client, server] = Object.values(new WebSocketPair());
|
||||
server.accept();
|
||||
|
||||
const upstream = connect({ hostname: destination, port: TELEGRAM_PORT });
|
||||
const writer = upstream.writable.getWriter();
|
||||
let closed = false;
|
||||
|
||||
const shutdown = () => {
|
||||
const shutdown = (code = 1000, reason = "") => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
writer.close().catch(() => {});
|
||||
// close() cancels both directions, including a blocked write/read.
|
||||
upstream.close().catch(() => {});
|
||||
try {
|
||||
server.close();
|
||||
server.close(code, reason);
|
||||
} catch {
|
||||
// соединение уже закрыто
|
||||
}
|
||||
@@ -77,19 +111,39 @@ export default {
|
||||
// проявлялось. Как только голодание починили, в воркер пошёл настоящий
|
||||
// поток (by-sonic/tglock#42).
|
||||
let pending = Promise.resolve();
|
||||
let pendingBytes = 0;
|
||||
let pendingMessages = 0;
|
||||
|
||||
server.addEventListener("message", (event) => {
|
||||
const chunk =
|
||||
event.data instanceof ArrayBuffer
|
||||
? new Uint8Array(event.data)
|
||||
: event.data;
|
||||
if (closed) return;
|
||||
if (!(event.data instanceof ArrayBuffer) && !ArrayBuffer.isView(event.data)) {
|
||||
shutdown(1003, "binary messages required");
|
||||
return;
|
||||
}
|
||||
const chunk = event.data instanceof ArrayBuffer
|
||||
? new Uint8Array(event.data)
|
||||
: new Uint8Array(event.data.buffer, event.data.byteOffset, event.data.byteLength);
|
||||
if (pendingBytes + chunk.byteLength > MAX_PENDING_BYTES || pendingMessages >= MAX_PENDING_MESSAGES) {
|
||||
shutdown(1009, "Telegram write queue full");
|
||||
return;
|
||||
}
|
||||
pendingBytes += chunk.byteLength;
|
||||
pendingMessages += 1;
|
||||
pending = pending
|
||||
.then(() => writer.ready)
|
||||
.then(() => writer.write(chunk))
|
||||
.catch(shutdown);
|
||||
.then(async () => {
|
||||
if (closed) return;
|
||||
await writer.ready;
|
||||
if (!closed) await writer.write(chunk);
|
||||
})
|
||||
.catch(() => shutdown(1011, "Telegram write failed"))
|
||||
.finally(() => {
|
||||
pendingBytes -= chunk.byteLength;
|
||||
pendingMessages -= 1;
|
||||
});
|
||||
});
|
||||
server.addEventListener("close", shutdown);
|
||||
server.addEventListener("error", shutdown);
|
||||
server.addEventListener("close", () => shutdown());
|
||||
server.addEventListener("error", () => shutdown(1011, "WebSocket failed"));
|
||||
upstream.closed.catch(() => shutdown(1011, "Telegram socket failed"));
|
||||
|
||||
// Обратное направление: всё, что приходит от Telegram, уходит клиенту.
|
||||
(async () => {
|
||||
@@ -101,7 +155,9 @@ export default {
|
||||
server.send(value);
|
||||
}
|
||||
} catch {
|
||||
// разрыв соединения — обычная ситуация, не ошибка
|
||||
shutdown(1011, "Telegram read failed");
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
shutdown();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user