|
|
|
/*
|
|
|
|
* 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 dcConfigurator, { TransportType } 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<TransportType, number>;
|
|
|
|
private transports: {[k in TransportType]?: MTTransport};
|
|
|
|
private pinging: boolean;
|
|
|
|
|
|
|
|
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 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<boolean>();
|
|
|
|
((this.transports.https as HTTP)._send(new Uint8Array(), 'no-cors') as any as Promise<any>)
|
|
|
|
.then(() => httpPromise.resolve(true), () => httpPromise.resolve(false));
|
|
|
|
setTimeout(() => httpPromise.resolve(false), timeout);
|
|
|
|
|
|
|
|
const websocketPromise = deferredPromise<boolean>();
|
|
|
|
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;
|