/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import App from "../../../config/app"; import deferredPromise from "../../../helpers/cancellablePromise"; import EventListenerBase from "../../../helpers/eventListenerBase"; import pause from "../../../helpers/schedulers/pause"; import { TransportType, DcConfigurator } from "../dcConfigurator"; import type HTTP from "./http"; import type TcpObfuscated from "./tcpObfuscated"; import MTTransport from "./transport"; export class MTTransportController extends EventListenerBase<{ change: (opened: MTTransportController['opened']) => void, transport: (type: TransportType) => void }> { private opened: Map; private transports: {[k in TransportType]?: MTTransport}; private pinging: boolean; private dcConfigurator: DcConfigurator; constructor() { super(true); this.opened = new Map(); /* this.addEventListener('change', (opened) => { this.dispatchEvent('transport', opened.get('websocket') || !opened.get('https') ? 'websocket' : 'https'); }); */ this.addEventListener('change', (opened) => { if(!opened.get('websocket')) { this.waitForWebSocket(); } }); // setTimeout(() => { // this.waitForWebSocket(); // }, 200); // wait for first transport so won't have delay for first WS } public async pingTransports() { const dcConfigurator = this.dcConfigurator ??= new DcConfigurator(); const timeout = 2000; const transports: {[k in TransportType]?: MTTransport} = this.transports = { https: dcConfigurator.chooseServer(App.baseDcId, 'client', 'https', false), websocket: dcConfigurator.chooseServer(App.baseDcId, 'client', 'websocket', false) }; const httpPromise = deferredPromise(); ((this.transports.https as HTTP)._send(new Uint8Array(), 'no-cors') as any as Promise) .then(() => httpPromise.resolve(true), () => httpPromise.resolve(false)); setTimeout(() => httpPromise.resolve(false), timeout); const websocketPromise = deferredPromise(); const socket = transports.websocket as TcpObfuscated; socket.setAutoReconnect(false); socket.connection.addEventListener('close', () => websocketPromise.resolve(false), {once: true}); socket.connection.addEventListener('open', () => websocketPromise.resolve(true), {once: true}); setTimeout(() => { if(websocketPromise.isFulfilled || websocketPromise.isRejected) { return; } if(socket.connection) { socket.connection.close(); } websocketPromise.resolve(false); }, timeout); const [isHttpAvailable, isWebSocketAvailable] = await Promise.all([httpPromise, websocketPromise]); for(const transportType in transports) { const transport = transports[transportType as TransportType]; transport.destroy(); } const result = { https: isHttpAvailable || this.opened.get('https') > 0, websocket: isWebSocketAvailable || this.opened.get('websocket') > 0 }; // result.websocket = false; return result; } public async waitForWebSocket() { if(this.pinging) return; this.pinging = true; while(true) { const {https, websocket} = await this.pingTransports(); if(https || websocket) { this.dispatchEvent('transport', websocket || !https ? 'websocket' : 'https'); } if(websocket) { break; } await pause(10000); } this.pinging = false; } public setTransportValue(type: TransportType, value: boolean) { let length = this.opened.get(type) || 0; length += value ? 1 : -1; this.opened.set(type, length); this.dispatchEvent('change', this.opened); } public setTransportOpened(type: TransportType) { return this.setTransportValue(type, true); } public setTransportClosed(type: TransportType) { return this.setTransportValue(type, false); } } const transportController = new MTTransportController(); export default transportController;