Socket proxy class for Safari

This commit is contained in:
Eduard Kuzmenko 2021-02-13 20:37:46 +04:00
parent 641c50ae82
commit debac6f0e5
8 changed files with 230 additions and 169 deletions

View File

@ -105,7 +105,8 @@ export class Authorizer {
this.log('mtpSendPlainRequest: creating requestPromise'); this.log('mtpSendPlainRequest: creating requestPromise');
} }
return transport.send(resultArray).then(result => { const promise = transport.send(resultArray) as any as Promise<Uint8Array>;
return promise.then(result => {
if(DEBUG) { if(DEBUG) {
this.log('mtpSendPlainRequest: in good sector', result); this.log('mtpSendPlainRequest: in good sector', result);
} }

View File

@ -1,21 +1,16 @@
import MTTransport from './transports/transport'; import MTTransport, { MTConnection, MTConnectionConstructable } from './transports/transport';
import Modes from '../../config/modes'; import Modes from '../../config/modes';
/// #if MTPROTO_HTTP_UPLOAD /// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
// @ts-ignore
import TcpObfuscated from './transports/tcpObfuscated';
// @ts-ignore
import HTTP from './transports/http'; import HTTP from './transports/http';
/// #elif !MTPROTO_HTTP /// #endif
// @ts-ignore
/// #if !MTPROTO_HTTP
import Socket from './transports/websocket';
import TcpObfuscated from './transports/tcpObfuscated'; import TcpObfuscated from './transports/tcpObfuscated';
import EventListenerBase from '../../helpers/eventListenerBase';
import { isSafari } from '../../helpers/userAgent'; import { isSafari } from '../../helpers/userAgent';
import type MTPNetworker from './networker';
import { notifyAll, isWebWorker } from '../../helpers/context'; import { notifyAll, isWebWorker } from '../../helpers/context';
import { CancellablePromise, deferredPromise } from '../../helpers/cancellablePromise';
/// #else
// @ts-ignore
import HTTP from './transports/http';
/// #endif /// #endif
export type TransportType = 'websocket' | 'https' | 'http'; export type TransportType = 'websocket' | 'https' | 'http';
@ -31,6 +26,48 @@ type Servers = {
let socketId = 0; let socketId = 0;
const TEST_SUFFIX = Modes.test ? '_test' : ''; const TEST_SUFFIX = Modes.test ? '_test' : '';
class SocketProxied extends EventListenerBase<{
open: () => void,
message: (buffer: ArrayBuffer) => any,
close: () => void,
}> implements MTConnection {
private id: number;
constructor(protected dcId: number, protected url: string, logSuffix: string) {
super();
this.id = ++socketId;
socketsProxied.set(this.id, this);
notifyAll({
type: 'socketProxy',
payload: {
type: 'setup',
payload: {
dcId,
url,
logSuffix
},
id: this.id
}
});
}
send = (payload: Uint8Array) => {
const task: any = {
type: 'socketProxy',
payload: {
type: 'send',
payload,
id: this.id
}
};
notifyAll(task);
};
}
export const socketsProxied: Map<number, SocketProxied> = new Map();
export class DcConfigurator { export class DcConfigurator {
private sslSubdomains = ['pluto', 'venus', 'aurora', 'vesta', 'flora']; private sslSubdomains = ['pluto', 'venus', 'aurora', 'vesta', 'flora'];
@ -58,58 +95,10 @@ export class DcConfigurator {
const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : ''; const logSuffix = connectionType === 'upload' ? '-U' : connectionType === 'download' ? '-D' : '';
const retryTimeout = connectionType === 'client' ? 30000 : 10000; const retryTimeout = connectionType === 'client' ? 30000 : 10000;
if(isSafari && isWebWorker && false) {
class P implements MTTransport {
private id: number;
private taskId = 0;
public networker: MTPNetworker;
public promises: Map<number, CancellablePromise<Uint8Array>> = new Map();
constructor(dcId: number, url: string) { const oooohLetMeLive: MTConnectionConstructable = (isSafari && isWebWorker) || true ? SocketProxied : Socket;
this.id = ++socketId;
notifyAll({ return new TcpObfuscated(oooohLetMeLive, dcId, chosenServer, logSuffix, retryTimeout);
type: 'socketProxy',
payload: {
type: 'setup',
payload: {
dcId,
url,
logSuffix,
retryTimeout
},
id: this.id
}
});
}
send = (payload: Uint8Array) => {
const task: any = {
type: 'socketProxy',
payload: {
type: 'send',
payload,
id: this.id
}
};
if(this.networker) {
notifyAll(task);
return null;
}
task.payload.taskId = ++this.taskId;
const deferred = deferredPromise<Uint8Array>();
this.promises.set(task.id, deferred);
notifyAll(task);
return deferred;
};
}
return new P(dcId, chosenServer);
} else {
return new TcpObfuscated(dcId, chosenServer, logSuffix, retryTimeout);
}
}; };
/// #endif /// #endif

View File

@ -8,6 +8,7 @@ import apiFileManager from './apiFileManager';
//import { logger, LogLevels } from '../logger'; //import { logger, LogLevels } from '../logger';
import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service'; import type { ServiceWorkerTask, ServiceWorkerTaskResponse } from './mtproto.service';
import { ctx } from '../../helpers/userAgent'; import { ctx } from '../../helpers/userAgent';
import { socketsProxied } from './dcConfigurator';
//const log = logger('DW', LogLevels.error); //const log = logger('DW', LogLevels.error);
@ -109,6 +110,19 @@ const onMessage = async(e: any) => {
} else if(task.type === 'webpSupport') { } else if(task.type === 'webpSupport') {
webpSupported = task.payload; webpSupported = task.payload;
return; return;
} else if(task.type === 'socketProxy') {
const socketTask = task.payload;
const id = socketTask.id;
const socketProxied = socketsProxied.get(id);
if(socketTask.type === 'message') {
socketProxied.setListenerResult('message', socketTask.payload);
} else if(socketTask.type === 'open') {
socketProxied.setListenerResult('open');
} else if(socketTask.type === 'close') {
socketProxied.setListenerResult('close');
socketsProxied.delete(id);
}
} }
if(!task.task) { if(!task.task) {

View File

@ -1,5 +1,4 @@
import MTProtoWorker from 'worker-loader!./mtproto.worker'; import MTProtoWorker from 'worker-loader!./mtproto.worker';
import SocketWorker from 'worker-loader!./transports/websocket';
//import './mtproto.worker'; //import './mtproto.worker';
import { isObject } from '../../helpers/object'; import { isObject } from '../../helpers/object';
import type { MethodDeclMap } from '../../layer'; import type { MethodDeclMap } from '../../layer';
@ -16,6 +15,7 @@ import type { MTMessage } from './networker';
import referenceDatabase from './referenceDatabase'; import referenceDatabase from './referenceDatabase';
import appDocsManager from '../appManagers/appDocsManager'; import appDocsManager from '../appManagers/appDocsManager';
import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug'; import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
import Socket from './transports/websocket';
type Task = { type Task = {
taskId: number, taskId: number,
@ -59,7 +59,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
private debug = DEBUG; private debug = DEBUG;
private socketsWorkers: Map<number, SocketWorker> = new Map(); private sockets: Map<number, Socket> = new Map();
constructor() { constructor() {
super(); super();
@ -193,20 +193,53 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
} else if(task.type === 'socketProxy') { } else if(task.type === 'socketProxy') {
const socketTask = task.payload; const socketTask = task.payload;
const id = socketTask.id; const id = socketTask.id;
console.log('socketProxy', socketTask, id); //console.log('socketProxy', socketTask, id);
if(socketTask.type === 'send') { if(socketTask.type === 'send') {
const socketWorker = this.socketsWorkers.get(id); const socket = this.sockets.get(id);
socketWorker.postMessage(socketTask); socket.send(socketTask.payload);
} else if(socketTask.type === 'setup') { } else if(socketTask.type === 'setup') {
const socketWorker = new SocketWorker(); const socket = new Socket(socketTask.payload.dcId, socketTask.payload.url, socketTask.payload.logSuffix);
socketWorker.postMessage(socketTask);
socketWorker.addEventListener('message', (e) => {
const task = e.data;
const onOpen = () => {
//console.log('socketProxy onOpen');
this.postMessage({
type: 'socketProxy',
payload: {
type: 'open',
id
}
});
};
const onClose = () => {
this.postMessage({
type: 'socketProxy',
payload: {
type: 'close',
id
}
}); });
this.socketsWorkers.set(id, socketWorker); socket.removeListener('open', onOpen);
socket.removeListener('close', onClose);
socket.removeListener('message', onMessage);
this.sockets.delete(id);
};
const onMessage = (buffer: ArrayBuffer) => {
this.postMessage({
type: 'socketProxy',
payload: {
type: 'message',
id,
payload: buffer
}
});
};
socket.addListener('open', onOpen);
socket.addListener('close', onClose);
socket.addListener('message', onMessage);
this.sockets.set(id, socket);
} }
} else if(task.hasOwnProperty('result') || task.hasOwnProperty('error')) { } else if(task.hasOwnProperty('result') || task.hasOwnProperty('error')) {
this.finalizeTask(task.taskId, task.result, task.error); this.finalizeTask(task.taskId, task.result, task.error);

View File

@ -1013,7 +1013,7 @@ export default class MTPNetworker {
public sendEncryptedRequest(message: MTMessage) { public sendEncryptedRequest(message: MTMessage) {
return this.getEncryptedOutput(message).then(requestData => { return this.getEncryptedOutput(message).then(requestData => {
const promise = this.transport.send(requestData); const promise: Promise<Uint8Array> = this.transport.send(requestData) as any;
/// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD /// #if !MTPROTO_HTTP && !MTPROTO_HTTP_UPLOAD
return promise; return promise;
/// #else /// #else

View File

@ -2,8 +2,7 @@ import Modes from "../../../config/modes";
import { logger, LogLevels } from "../../logger"; import { logger, LogLevels } from "../../logger";
import MTPNetworker from "../networker"; import MTPNetworker from "../networker";
import Obfuscation from "./obfuscation"; import Obfuscation from "./obfuscation";
import MTTransport from "./transport"; import MTTransport, { MTConnection, MTConnectionConstructable } from "./transport";
import Socket from "./websocket";
import intermediatePacketCodec from './intermediate'; import intermediatePacketCodec from './intermediate';
export default class TcpObfuscated implements MTTransport { export default class TcpObfuscated implements MTTransport {
@ -27,11 +26,11 @@ export default class TcpObfuscated implements MTTransport {
private lastCloseTime: number; private lastCloseTime: number;
private socket: Socket; private connection: MTConnection;
//private debugPayloads: MTPNetworker['debugRequests'] = []; //private debugPayloads: MTPNetworker['debugRequests'] = [];
constructor(private dcId: number, private url: string, private logSuffix: string, public retryTimeout: number) { constructor(private Connection: MTConnectionConstructable, private dcId: number, private url: string, private logSuffix: string, public retryTimeout: number) {
let logLevel = LogLevels.error | LogLevels.log; let logLevel = LogLevels.error | LogLevels.log;
if(this.debug) logLevel |= LogLevels.debug; if(this.debug) logLevel |= LogLevels.debug;
this.log = logger(`WS-${dcId}` + logSuffix, logLevel); this.log = logger(`WS-${dcId}` + logSuffix, logLevel);
@ -40,10 +39,7 @@ export default class TcpObfuscated implements MTTransport {
this.connect(); this.connect();
} }
private connect() { private onOpen = () => {
this.socket = new Socket(this.dcId, this.url, this.logSuffix);
this.socket.addListener('open', () => {
this.connected = true; this.connected = true;
const initPayload = this.obfuscation.init(this.codec); const initPayload = this.obfuscation.init(this.codec);
@ -61,10 +57,10 @@ export default class TcpObfuscated implements MTTransport {
this.releasePending(); this.releasePending();
}, 0); }, 0);
this.socket.send(initPayload); this.connection.send(initPayload);
}); };
this.socket.addListener('message', (buffer) => { private onMessage = (buffer: ArrayBuffer) => {
let data = this.obfuscation.decode(new Uint8Array(buffer)); let data = this.obfuscation.decode(new Uint8Array(buffer));
data = this.codec.readPacket(data); data = this.codec.readPacket(data);
@ -98,11 +94,10 @@ export default class TcpObfuscated implements MTTransport {
} }
pending.resolve(data); pending.resolve(data);
}); };
this.socket.addListener('close', () => { private onClose = () => {
this.connected = false; this.connected = false;
this.socket = undefined;
const time = Date.now(); const time = Date.now();
const diff = time - this.lastCloseTime; const diff = time - this.lastCloseTime;
@ -125,7 +120,19 @@ export default class TcpObfuscated implements MTTransport {
this.connect(); this.connect();
}, needTimeout); }, needTimeout);
});
this.connection.removeListener('open', this.onOpen);
this.connection.removeListener('close', this.onClose);
this.connection.removeListener('message', this.onMessage);
this.connection = undefined;
};
private connect() {
this.connection = new this.Connection(this.dcId, this.url, this.logSuffix);
this.connection.addListener('open', this.onOpen);
this.connection.addListener('close', this.onClose);
this.connection.addListener('message', this.onMessage);
} }
private encodeBody = (body: Uint8Array) => { private encodeBody = (body: Uint8Array) => {
@ -202,7 +209,7 @@ export default class TcpObfuscated implements MTTransport {
//this.lol.push(body); //this.lol.push(body);
//setTimeout(() => { //setTimeout(() => {
this.socket.send(encoded); this.connection.send(encoded);
//}, 100); //}, 100);
//this.dd(); //this.dd();

View File

@ -1,3 +1,17 @@
export default abstract class MTTransport { import type EventListenerBase from "../../../helpers/eventListenerBase";
abstract send: (data: Uint8Array) => Promise<Uint8Array>;
export default interface MTTransport {
send: (data: Uint8Array) => void;
}
export interface MTConnection extends EventListenerBase<{
open: () => void,
message: (buffer: ArrayBuffer) => any,
close: () => void,
}> {
send: (data: Uint8Array) => void;
}
export interface MTConnectionConstructable {
new(dcId: number, url: string, logSuffix: string): MTConnection;
} }

View File

@ -1,13 +1,14 @@
import { logger, LogLevels } from '../../logger'; import { logger, LogLevels } from '../../logger';
import Modes from '../../../config/modes'; import Modes from '../../../config/modes';
import EventListenerBase from '../../../helpers/eventListenerBase'; import EventListenerBase from '../../../helpers/eventListenerBase';
import { MTConnection } from './transport';
export default class Socket extends EventListenerBase<{ export default class Socket extends EventListenerBase<{
open: () => void, open: () => void,
message: (buffer: ArrayBuffer) => any, message: (buffer: ArrayBuffer) => any,
close: () => void, close: () => void,
}> { }> implements MTConnection {
public ws: WebSocket; private ws: WebSocket;
private log: ReturnType<typeof logger>; private log: ReturnType<typeof logger>;
private debug = Modes.debug && false; private debug = Modes.debug && false;
@ -19,6 +20,8 @@ export default class Socket extends EventListenerBase<{
this.log = logger(`WS-${dcId}` + logSuffix, logLevel); this.log = logger(`WS-${dcId}` + logSuffix, logLevel);
this.log('constructor'); this.log('constructor');
this.connect(); this.connect();
return this;
} }
private removeListeners() { private removeListeners() {