mirror of
https://github.com/by-sonic/tglock.git
synced 2026-08-02 16:45:50 +03:00
f03e9106ee
Резервный маршрут через Worker был в коде с 2.0, но воспользоваться им никто не мог: в ARCHITECTURE_V2.md описан только контракт эндпоинта — это спецификация для того, кто будет писать воркер, а не руководство. Ни скрипта, ни шагов в репозитории не было. Поэтому люди, у которых легли все обычные маршруты, писали «не работает» вместо того, чтобы включить запасной выход. Добавлено: - worker/tglock-worker.js — готовый скрипт. Проверяет путь и upgrade, подтверждает подпротокол binary (без этого клиент рвёт рукопожатие), соединяется только с семью адресами Telegram, которые запрашивает TGLock, и поддерживает необязательный TGLOCK_TOKEN. Без списка адресов воркер стал бы открытым TCP-прокси для любого, кто узнает его адрес. - docs/CLOUDFLARE_WORKER.md — когда это нужно и когда нет (таблица «что видно в приложении → нужен ли Worker»), установка через веб-интерфейс, проверка живости, подключение в GUI и через --worker, ограничение доступа, контракт для своих реализаций. - Ссылки из README: в FAQ про блокировку web.telegram.org и в блок docs. Контракт закреплён тестами, чтобы документация не разошлась с кодом: - worker_path вынесен в функцию, из неё же строятся боевые маршруты; - documented_worker_contract_matches_the_requested_path сверяет формат пути; - worker_allowlist_covers_every_address_a_route_can_ask_for падает, если в маршрутах появится адрес, которого нет в скрипте воркера; - connects_through_the_documented_worker_contract поднимает сервер, ведущий себя ровно по документации, и проверяет что туннель работает в обе стороны и что запрошен именно документированный URI. Чего тесты не проверяют: развёрнутый воркер в самом Cloudflare. Это указано и в самой инструкции. Отдельно: снят флаг prerelease в release.yml. До правки /releases/latest отдавал v2.0.0-beta.1, то есть кнопка «Скачать» в README вела на сборку без CLI и без фикса рендера. Существующий релиз v2.0.0-beta.2 помечен как latest вручную. Co-authored-by: by-sonic <171230345+by-sonic@users.noreply.github.com>
105 lines
3.9 KiB
JavaScript
105 lines
3.9 KiB
JavaScript
// Резервный маршрут TGLock через Cloudflare Worker.
|
|
//
|
|
// Нужен в одном случае: провайдер заблокировал саму веб-инфраструктуру
|
|
// Telegram, и все обычные маршруты TGLock перестали отвечать. Тогда соединение
|
|
// идёт на твой домен *.workers.dev, а воркер доводит его до Telegram.
|
|
//
|
|
// Инструкция по установке: docs/CLOUDFLARE_WORKER.md
|
|
//
|
|
// Контракт, который ожидает клиент (src/transport.rs):
|
|
// wss://<домен>/apiws?dst=<telegram-ip>&dc=<номер-dc>
|
|
// заголовок Sec-WebSocket-Protocol: binary — его обязательно нужно
|
|
// подтвердить в ответе, иначе клиент разорвёт рукопожатие;
|
|
// бинарные frames в обе стороны, без обёрток.
|
|
|
|
import { connect } from "cloudflare:sockets";
|
|
|
|
// Только те адреса, которые запрашивает сам TGLock. Без этого списка любой,
|
|
// кто узнает адрес воркера, получит через твой аккаунт произвольный
|
|
// TCP-прокси.
|
|
const ALLOWED_DESTINATIONS = new Set([
|
|
"91.105.192.100",
|
|
"149.154.167.51",
|
|
"149.154.167.91",
|
|
"149.154.167.220",
|
|
"149.154.171.5",
|
|
"149.154.175.50",
|
|
"149.154.175.100",
|
|
]);
|
|
|
|
const TELEGRAM_PORT = 443;
|
|
|
|
export default {
|
|
async fetch(request, env) {
|
|
const url = new URL(request.url);
|
|
|
|
if (url.pathname !== "/apiws") {
|
|
return new Response("not found", { status: 404 });
|
|
}
|
|
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") {
|
|
return new Response("expected a websocket upgrade", { status: 426 });
|
|
}
|
|
// Необязательный общий секрет: задай переменную TGLOCK_TOKEN в настройках
|
|
// воркера, и посторонние подключиться не смогут.
|
|
if (env.TGLOCK_TOKEN && url.searchParams.get("token") !== env.TGLOCK_TOKEN) {
|
|
return new Response("forbidden", { status: 403 });
|
|
}
|
|
|
|
const destination = url.searchParams.get("dst");
|
|
if (!destination || !ALLOWED_DESTINATIONS.has(destination)) {
|
|
return new Response("destination not allowed", { status: 403 });
|
|
}
|
|
|
|
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 = () => {
|
|
if (closed) return;
|
|
closed = true;
|
|
writer.close().catch(() => {});
|
|
try {
|
|
server.close();
|
|
} catch {
|
|
// соединение уже закрыто
|
|
}
|
|
};
|
|
|
|
server.addEventListener("message", (event) => {
|
|
const chunk =
|
|
event.data instanceof ArrayBuffer
|
|
? new Uint8Array(event.data)
|
|
: event.data;
|
|
writer.write(chunk).catch(shutdown);
|
|
});
|
|
server.addEventListener("close", shutdown);
|
|
server.addEventListener("error", shutdown);
|
|
|
|
// Обратное направление: всё, что приходит от Telegram, уходит клиенту.
|
|
(async () => {
|
|
const reader = upstream.readable.getReader();
|
|
try {
|
|
for (;;) {
|
|
const { value, done } = await reader.read();
|
|
if (done) break;
|
|
server.send(value);
|
|
}
|
|
} catch {
|
|
// разрыв соединения — обычная ситуация, не ошибка
|
|
}
|
|
shutdown();
|
|
})();
|
|
|
|
return new Response(null, {
|
|
status: 101,
|
|
webSocket: client,
|
|
// Обязательно: клиент запрашивает подпротокол binary и без
|
|
// подтверждения рвёт рукопожатие.
|
|
headers: { "Sec-WebSocket-Protocol": "binary" },
|
|
});
|
|
},
|
|
};
|