diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index c40f94f2..727ed443 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -183,7 +183,7 @@ export class AppMessagesManager { public loadSavedState() { if(this.loaded) return this.loaded; - this.loaded = new Promise((resolve, reject) => { + return this.loaded = new Promise((resolve, reject) => { AppStorage.get<{ dialogs: Dialog[], allDialogsLoaded: AppMessagesManager['allDialogsLoaded'], diff --git a/src/lib/mtproto/apiFileManager.ts b/src/lib/mtproto/apiFileManager.ts index 5309ec91..aba3d67d 100644 --- a/src/lib/mtproto/apiFileManager.ts +++ b/src/lib/mtproto/apiFileManager.ts @@ -29,22 +29,22 @@ export class ApiFileManager { resolve: (...args: any[]) => void, reject: (...args: any[]) => void }, - activeDelta?: number + activeDelta: number }> } = {}; public downloadActives: {[dcID: string]: number} = {}; private log: ReturnType = logger('AFM'); - public downloadRequest(dcID: string | number, cb: () => Promise, activeDelta?: number) { + public downloadRequest(dcID: string | number, cb: () => Promise, activeDelta: number) { if(this.downloadPulls[dcID] === undefined) { this.downloadPulls[dcID] = []; this.downloadActives[dcID] = 0; } - var downloadPull = this.downloadPulls[dcID]; + const downloadPull = this.downloadPulls[dcID]; - let promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { downloadPull.push({cb: cb, deferred: {resolve, reject}, activeDelta: activeDelta}); })/* .catch(() => {}) */; @@ -56,15 +56,16 @@ export class ApiFileManager { } public downloadCheck(dcID: string | number) { - var downloadPull = this.downloadPulls[dcID]; - var downloadLimit = dcID == 'upload' ? 11 : 5; + const downloadPull = this.downloadPulls[dcID]; + //const downloadLimit = dcID == 'upload' ? 11 : 5; + const downloadLimit = 24; if(this.downloadActives[dcID] >= downloadLimit || !downloadPull || !downloadPull.length) { return false; } - var data = downloadPull.shift(); - var activeDelta = data.activeDelta || 1; + const data = downloadPull.shift(); + const activeDelta = data.activeDelta || 1; this.downloadActives[dcID] += activeDelta; @@ -122,8 +123,8 @@ export class ApiFileManager { } public getTempFileName(file: any) { - var size = file.size || -1; - var random = nextRandomInt(0xFFFFFFFF); + const size = file.size || -1; + const random = nextRandomInt(0xFFFFFFFF); return '_temp' + random + '_' + size; } @@ -131,12 +132,12 @@ export class ApiFileManager { if(!location) { return false; } - var fileName = this.getFileName(location); + const fileName = this.getFileName(location); return this.cachedDownloads[fileName] || false; } - public getFileStorage(): typeof cacheStorage { + public getFileStorage() { return cacheStorage; } @@ -419,7 +420,7 @@ export class ApiFileManager { fileDownload: true/* , singleInRequest: 'safari' in window */ }); - }, dcID).then((result: any) => { + }, 2).then((result: any) => { writeFilePromise.then(() => { if(canceled) { return Promise.resolve(); diff --git a/src/lib/mtproto/apiManager.ts b/src/lib/mtproto/apiManager.ts index 2db4525b..69f39aea 100644 --- a/src/lib/mtproto/apiManager.ts +++ b/src/lib/mtproto/apiManager.ts @@ -13,6 +13,7 @@ import passwordManager from './passwordManager'; /// #if !MTPROTO_WORKER import { $rootScope } from '../utils'; +import { InvokeApiOptions } from '../../types'; /// #endif //console.error('apiManager included!'); @@ -133,10 +134,10 @@ export class ApiManager { } // mtpGetNetworker - public async getNetworker(dcID: number, options: any = {}): Promise { - let upload = (options.fileUpload || options.fileDownload) + public async getNetworker(dcID: number, options: InvokeApiOptions): Promise { + const upload = (options.fileUpload || options.fileDownload) && (dcConfigurator.chooseServer(dcID, true) instanceof HTTP || Modes.multipleConnections); - let cache = upload ? this.cachedUploadNetworkers : this.cachedNetworkers; + const cache = upload ? this.cachedUploadNetworkers : this.cachedNetworkers; if(!dcID) { throw new Error('get Networker without dcID'); @@ -146,72 +147,61 @@ export class ApiManager { return cache[dcID]; } - let getKey = dcID + '-' + +upload; + const getKey = dcID + '-' + +upload; if(this.gettingNetworkers[getKey]) { return this.gettingNetworkers[getKey]; } + + const ak = 'dc' + dcID + '_auth_key'; + const akID = 'dc' + dcID + '_auth_keyID'; + const ss = 'dc' + dcID + '_server_salt'; - return this.gettingNetworkers[getKey] = new Promise(async(resolve, reject) => { - var ak = 'dc' + dcID + '_auth_key'; - var akID = 'dc' + dcID + '_auth_keyID'; - var ss = 'dc' + dcID + '_server_salt'; - - let result = await AppStorage.get([ak, akID, ss]); - - let [authKeyHex, authKeyIDHex, serverSaltHex] = result; - if(authKeyHex && !authKeyIDHex && serverSaltHex) { + return this.gettingNetworkers[getKey] = AppStorage.get([ak, akID, ss]) + .then(async([authKeyHex, authKeyIDHex, serverSaltHex]) => { + /* if(authKeyHex && !authKeyIDHex && serverSaltHex) { this.log.warn('Updating to new version (+akID)'); await AppStorage.remove(ak, akID, ss); authKeyHex = serverSaltHex = ''; - } + } */ + let networker: MTPNetworker; if(authKeyHex && authKeyHex.length == 512) { if(!serverSaltHex || serverSaltHex.length != 16) { serverSaltHex = 'AAAAAAAAAAAAAAAA'; } - var authKey = bytesFromHex(authKeyHex); - var authKeyID = new Uint8Array(bytesFromHex(authKeyIDHex)); - var serverSalt = bytesFromHex(serverSaltHex); - - resolve(cache[dcID] = networkerFactory.getNetworker(dcID, authKey, authKeyID, serverSalt, options)); - } else try { - let auth = await authorizer.auth(dcID); - - let storeObj = { - [ak]: bytesToHex(auth.authKey), - [akID]: auth.authKeyID.hex, - [ss]: bytesToHex(auth.serverSalt) - }; - - AppStorage.set(storeObj); + const authKey = bytesFromHex(authKeyHex); + const authKeyID = new Uint8Array(bytesFromHex(authKeyIDHex)); + const serverSalt = bytesFromHex(serverSaltHex); - resolve(cache[dcID] = networkerFactory.getNetworker(dcID, auth.authKey, auth.authKeyID, auth.serverSalt, options)); - } catch(error) { - this.log('Get networker error', error, error.stack); - reject(error); + networker = networkerFactory.getNetworker(dcID, authKey, authKeyID, serverSalt, options); + } else { + try { // if no saved state + const auth = await authorizer.auth(dcID); + + const storeObj = { + [ak]: bytesToHex(auth.authKey), + [akID]: auth.authKeyID.hex, + [ss]: bytesToHex(auth.serverSalt) + }; + + AppStorage.set(storeObj); + + networker = networkerFactory.getNetworker(dcID, auth.authKey, auth.authKeyID, auth.serverSalt, options); + } catch(error) { + this.log('Get networker error', error, error.stack); + delete this.gettingNetworkers[getKey]; + throw error; + } } delete this.gettingNetworkers[getKey]; + return cache[dcID] = networker; }); } // mtpInvokeApi - public invokeApi(method: string, params: any = {}, options: Partial<{ - dcID: number, - timeout: number, - noErrorBox: boolean, - fileUpload: boolean, - ignoreErrors: boolean, - fileDownload: boolean, - createNetworker: boolean, - singleInRequest: boolean, - startMaxLength: number, - - waitTime: number, - stopTime: number, - rawError: any - }> = {}) { + public invokeApi(method: string, params: any = {}, options: InvokeApiOptions = {}) { ///////this.log('Invoke api', method, params, options); return new Promise((resolve, reject) => { @@ -343,7 +333,6 @@ export class ApiManager { public checkPassword(value: string): Promise { return passwordManager.getState() .then(state => { - console.log(state); return passwordManager.check(state, value); }); } diff --git a/src/lib/mtproto/dcConfigurator.ts b/src/lib/mtproto/dcConfigurator.ts index 919792a4..9ff8077a 100644 --- a/src/lib/mtproto/dcConfigurator.ts +++ b/src/lib/mtproto/dcConfigurator.ts @@ -3,19 +3,11 @@ import MTTransport from './transports/transport'; import HTTP from './transports/http'; import { Modes } from './mtproto_config'; +type TransportTypes = 'websocket' | 'https' | 'http'; type Servers = { - [transport: string]: { - [dcID: number]: MTTransport + [transportType in TransportTypes]: { + [dcID: number]: MTTransport[] } - /* websocket: { - [dcID: number]: Socket - }, - https: { - [dcID: number]: HTTPTransport - }, - http: { - [dcID: number]: HTTPTransport - } */ }; export class DcConfigurator { @@ -47,41 +39,50 @@ export class DcConfigurator { http: {} }; - public chooseServer(dcID: number, upload?: boolean, transport = 'websocket') { - let servers = upload && (transport != 'websocket' || Modes.multipleConnections) - ? this.chosenUploadServers[transport] - : this.chosenServers[transport]; + public chooseServer(dcID: number, upload?: boolean, transportType: TransportTypes = 'websocket') { + const servers = upload && (transportType != 'websocket' || Modes.multipleConnections) + ? this.chosenUploadServers[transportType] + : this.chosenServers[transportType]; if(!(dcID in servers)) { - let chosenServer = ''; + servers[dcID] = []; + } - if(transport == 'websocket') { - let subdomain = this.sslSubdomains[dcID - 1]; - let path = Modes.test ? 'apiws_test' : 'apiws'; - chosenServer = 'wss://' + subdomain + '.web.telegram.org/' + path; - return servers[dcID] = new Socket(dcID, chosenServer); - } - - if(Modes.ssl || !Modes.http || transport == 'https') { - let subdomain = this.sslSubdomains[dcID - 1] + (upload ? '-1' : ''); - let path = Modes.test ? 'apiw_test1' : 'apiw1'; - chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path; - return servers[dcID] = new HTTP(dcID, chosenServer); + const transports = servers[dcID]; + + if(!transports.length || (upload && transports.length < 1)) { + let transport: MTTransport; + + if(transportType == 'websocket') { + const subdomain = this.sslSubdomains[dcID - 1]; + const path = Modes.test ? 'apiws_test' : 'apiws'; + const chosenServer = 'wss://' + subdomain + '.web.telegram.org/' + path; + transport = new Socket(dcID, chosenServer); + } else if(Modes.ssl || !Modes.http || transportType == 'https') { + const subdomain = this.sslSubdomains[dcID - 1] + (upload ? '-1' : ''); + const path = Modes.test ? 'apiw_test1' : 'apiw1'; + const chosenServer = 'https://' + subdomain + '.web.telegram.org/' + path; + transport = new HTTP(dcID, chosenServer); + } else { + for(let dcOption of this.dcOptions) { + if(dcOption.id == dcID) { + const chosenServer = 'http://' + dcOption.host + (dcOption.port != 80 ? ':' + dcOption.port : '') + '/apiw1'; + transport = new HTTP(dcID, chosenServer); + break; + } + } } - for(let dcOption of this.dcOptions) { - if(dcOption.id == dcID) { - chosenServer = 'http://' + dcOption.host + (dcOption.port != 80 ? ':' + dcOption.port : '') + '/apiw1'; - return servers[dcID] = new HTTP(dcID, chosenServer); - } + if(!transport) { + console.error('No chosenServer!', dcID); + return null; } - - console.error('No chosenServer!', dcID); - - return null; + + transports.push(transport); + return transport; } - return servers[dcID]; + return transports[0]; } } diff --git a/src/lib/mtproto/networker.ts b/src/lib/mtproto/networker.ts index 65362be6..39afbf69 100644 --- a/src/lib/mtproto/networker.ts +++ b/src/lib/mtproto/networker.ts @@ -14,6 +14,7 @@ import Socket from './transports/websocket'; import HTTP from './transports/http'; import { logger } from '../polyfill'; import { Modes, App } from './mtproto_config'; +import { InvokeApiOptions } from '../../types'; //console.error('networker included!', new Error().stack); @@ -90,14 +91,12 @@ class MTPNetworker { private log: ReturnType; constructor(private dcID: number, private authKey: number[], private authKeyID: Uint8Array, - private serverSalt: number[], private options: any = {}) { + private serverSalt: number[], private options: InvokeApiOptions = {}) { this.authKeyUint8 = convertToUint8Array(this.authKey); //this.authKeyID = sha1BytesSync(this.authKey).slice(-8); this.upload = this.options.fileUpload || this.options.fileDownload || false; - this.log = logger('NET-' + dcID + (this.upload ? '-U' : '')); - this.log('constructor'/* , this.authKey, this.authKeyID, this.serverSalt */); this.updateSession(); @@ -111,7 +110,7 @@ class MTPNetworker { this.transport = dcConfigurator.chooseServer(this.dcID, this.upload); if(this.transport instanceof HTTP) { - /* this.longPollInt = */window.setInterval(this.checkLongPoll.bind(this), 10000); + /* this.longPollInt = */setInterval(this.checkLongPoll.bind(this), 10000); this.checkLongPoll(); } else { (this.transport as Socket).networker = this; @@ -204,24 +203,7 @@ class MTPNetworker { return this.pushMessage(message, options); } - public wrapApiCall(method: string, params: any = {}, options: { - dcID?: number, - timeout?: number, - noErrorBox?: boolean, - fileUpload?: boolean, - ignoreErrors?: boolean, - fileDownload?: boolean, - createNetworker?: boolean, - singleInRequest?: boolean, - startMaxLength?: number, - - afterMessageID?: string, - resultType?: boolean, - - waitTime?: number, - stopTime?: number, - rawError?: any - } = {}) { + public wrapApiCall(method: string, params: any = {}, options: InvokeApiOptions = {}) { let serializer = new TLSerialization(options); if(!this.connectionInited) { // this will call once for each new session @@ -293,7 +275,7 @@ class MTPNetworker { } public checkLongPoll() { - var isClean = this.cleanupSent(); + const isClean = this.cleanupSent(); //this.log('Check lp', this.longPollPending, tsNow(), this.dcID, isClean, this); if((this.longPollPending && Date.now() < this.longPollPending) || this.offline) { @@ -301,18 +283,17 @@ class MTPNetworker { return false; } - var self = this; AppStorage.get('dc').then((baseDcID: number) => { if(isClean && ( - baseDcID != self.dcID || - self.upload || - (self.sleepAfter && Date.now() > self.sleepAfter) + baseDcID != this.dcID || + this.upload || + (this.sleepAfter && Date.now() > this.sleepAfter) )) { - //console.warn(dT(), 'Send long-poll for DC is delayed', self.dcID, self.sleepAfter); + //console.warn(dT(), 'Send long-poll for DC is delayed', this.dcID, this.sleepAfter); return; } - self.sendLongPoll(); + this.sendLongPoll(); }); } @@ -963,7 +944,7 @@ class MTPNetworker { clearTimeout(this.nextReqTimeout); this.nextReqTimeout = 0; if(delay > 0) { - this.nextReqTimeout = window.setTimeout(this.performScheduledRequest.bind(this), delay || 0); + this.nextReqTimeout = setTimeout(this.performScheduledRequest.bind(this), delay || 0); } else { setTimeout(this.performScheduledRequest.bind(this), 0); } @@ -984,26 +965,25 @@ class MTPNetworker { } public cleanupSent() { - var self = this; - var notEmpty = false; + let notEmpty = false; // this.log('clean start', this.dcID/*, this.sentMessages*/) Object.keys(this.sentMessages).forEach((msgID) => { - let message = this.sentMessages[msgID]; + const message = this.sentMessages[msgID]; // this.log('clean iter', msgID, message) - if(message.notContentRelated && self.pendingMessages[msgID] === undefined) { + if(message.notContentRelated && this.pendingMessages[msgID] === undefined) { // this.log('clean notContentRelated', msgID) - delete self.sentMessages[msgID]; + delete this.sentMessages[msgID]; } else if(message.container) { - for(var i = 0; i < message.inner.length; i++) { - if(self.sentMessages[message.inner[i]] !== undefined) { - // this.log('clean failed, found', msgID, message.inner[i], self.sentMessages[message.inner[i]].seq_no) + for(let i = 0; i < message.inner.length; i++) { + if(this.sentMessages[message.inner[i]] !== undefined) { + // this.log('clean failed, found', msgID, message.inner[i], this.sentMessages[message.inner[i]].seq_no) notEmpty = true; return; } } // this.log('clean container', msgID) - delete self.sentMessages[msgID]; + delete this.sentMessages[msgID]; } else { notEmpty = true; } diff --git a/src/lib/mtproto/networkerFactory.ts b/src/lib/mtproto/networkerFactory.ts index df16d8ab..6515c44e 100644 --- a/src/lib/mtproto/networkerFactory.ts +++ b/src/lib/mtproto/networkerFactory.ts @@ -1,4 +1,5 @@ import { MTPNetworker } from "./networker"; +import { InvokeApiOptions } from "../../types"; export class NetworkerFactory { public updatesProcessor: (obj: any, bool: boolean) => void = null; @@ -7,8 +8,8 @@ export class NetworkerFactory { this.updatesProcessor = callback; } - public getNetworker(dcID: number, authKey: number[], authKeyID: Uint8Array, serverSalt: number[], options: any) { - //console.log(dT(), 'NetworkerFactory: creating new instance of MTPNetworker:', dcID, options); + public getNetworker(dcID: number, authKey: number[], authKeyID: Uint8Array, serverSalt: number[], options: InvokeApiOptions) { + //console.log('NetworkerFactory: creating new instance of MTPNetworker:', dcID, options); return new MTPNetworker(dcID, authKey, authKeyID, serverSalt, options); } } diff --git a/src/lib/mtproto/passwordManager.ts b/src/lib/mtproto/passwordManager.ts index 444f7607..31d7f1f1 100644 --- a/src/lib/mtproto/passwordManager.ts +++ b/src/lib/mtproto/passwordManager.ts @@ -6,6 +6,8 @@ import {str2bigInt, greater, isZero, bigInt2str, powMod, int2bigInt, mult, mod, sub, bitSize, negative, mult, add} from 'leemon/es/index/'; export class PasswordManager { + private log = (...args: any[]) => {}; + public getState(options: any = {}) { return apiManager.invokeApi('account.getPassword', {}, options).then((result) => { return result @@ -89,19 +91,19 @@ export class PasswordManager { let buffer = bufferConcats(client_salt, passwordBuffer, client_salt); return CryptoWorker.sha256Hash(buffer).then((buffer: any) => { - console.log('encoded 1', bytesToHex(new Uint8Array(buffer))); + this.log('encoded 1', bytesToHex(new Uint8Array(buffer))); buffer = bufferConcats(server_salt, buffer, server_salt); return CryptoWorker.sha256Hash(buffer).then((buffer: any) => { - console.log('encoded 2', buffer, bytesToHex(new Uint8Array(buffer))); + this.log('encoded 2', buffer, bytesToHex(new Uint8Array(buffer))); return CryptoWorker.pbkdf2(new Uint8Array(buffer), client_salt, 100000).then((hash: any) => { - console.log('encoded 3', hash, bytesToHex(new Uint8Array(hash))); + this.log('encoded 3', hash, bytesToHex(new Uint8Array(hash))); hash = bufferConcats(server_salt, hash, server_salt); return CryptoWorker.sha256Hash(hash).then((buffer: any) => { - console.log('got password hash:', buffer, bytesToHex(new Uint8Array(buffer))); + this.log('got password hash:', buffer, bytesToHex(new Uint8Array(buffer))); return buffer; }); @@ -117,8 +119,8 @@ export class PasswordManager { let B = str2bigInt(bytesToHex(state.srp_B), 16); let g = int2bigInt(algo.g, 32, 256); - console.log('p', bigInt2str(p, 16)); - console.log('B', bigInt2str(B, 16)); + this.log('p', bigInt2str(p, 16)); + this.log('B', bigInt2str(B, 16)); /* if(B.compareTo(BigInteger.ZERO) < 0) { console.error('srp_B < 0') @@ -144,7 +146,7 @@ export class PasswordManager { new Uint8Array(algo.salt2)) as ArrayBuffer; let x = str2bigInt(bytesToHex(new Uint8Array(pw_hash)), 16); - console.warn('computed pw_hash:', pw_hash, x, bytesToHex(new Uint8Array(pw_hash))); + this.log('computed pw_hash:', pw_hash, x, bytesToHex(new Uint8Array(pw_hash))); var padArray = function(arr: any[], len: number, fill = 0) { @@ -155,25 +157,25 @@ export class PasswordManager { let gForHash = padArray(bytesFromHex(bigInt2str(g, 16)), 256); // like uint8array let b_for_hash = padArray(bytesFromHex(bigInt2str(B, 16)), 256); - console.log(bytesToHex(pForHash)); - console.log(bytesToHex(gForHash)); - console.log(bytesToHex(b_for_hash)); + this.log(bytesToHex(pForHash)); + this.log(bytesToHex(gForHash)); + this.log(bytesToHex(b_for_hash)); let g_x = powMod(g, x, p); - console.log('g_x', bigInt2str(g_x, 16)); + this.log('g_x', bigInt2str(g_x, 16)); let k: any = await CryptoWorker.sha256Hash(bufferConcat(pForHash, gForHash)); k = str2bigInt(bytesToHex(new Uint8Array(k)), 16); - console.log('k', bigInt2str(k, 16)); + this.log('k', bigInt2str(k, 16)); // kg_x = (k * g_x) % p let kg_x = mod(mult(k, g_x), p); // good - console.log('kg_x', bigInt2str(kg_x, 16)); + this.log('kg_x', bigInt2str(kg_x, 16)); let is_good_mod_exp_first = (modexp: any, prime: any) => { let diff = sub(prime, modexp); @@ -217,12 +219,12 @@ export class PasswordManager { let {a, a_for_hash, u} = await generate_and_check_random(); - console.log('a', bigInt2str(a, 16)); - console.log('a_for_hash', bytesToHex(a_for_hash)); - console.log('u', bigInt2str(u, 16)); + this.log('a', bigInt2str(a, 16)); + this.log('a_for_hash', bytesToHex(a_for_hash)); + this.log('u', bigInt2str(u, 16)); // g_b = (B - kg_x) % p - console.log('B - kg_x', bigInt2str(sub(B, kg_x), 16)); + this.log('B - kg_x', bigInt2str(sub(B, kg_x), 16)); //let g_b = mod(sub(B, kg_x), p); /* let g_b = sub(B, kg_x); if(negative(g_b)) g_b = add(g_b, p); @@ -231,15 +233,15 @@ export class PasswordManager { if(!negative(sub(B, kg_x))) g_b = sub(mod(B, p), kg_x); else g_b = mod(sub(B, kg_x), p); */ /* let lol = trim(sub(B, kg_x), 10); - console.log('llalala', bigInt2str(lol, 16)); */ + this.log('llalala', bigInt2str(lol, 16)); */ let g_b; if(!greater(B, kg_x)) { - console.log('negative'); + this.log('negative'); g_b = add(B, p); } else g_b = B; g_b = mod(sub(g_b, kg_x), p); //g_b = mod(g_b, p); - //console.log('g_b', bigInt2str(g_b, 16)); + //this.log('g_b', bigInt2str(g_b, 16)); /* if(!is_good_mod_exp_first(g_b, p)) throw new Error('bad g_b'); */ @@ -277,7 +279,7 @@ export class PasswordManager { }; - console.log('out', bytesToHex(out.A), bytesToHex(out.M1)); + this.log('out', bytesToHex(out.A), bytesToHex(out.M1)); return out; /* console.log(gForHash, pForHash, bForHash); */ diff --git a/src/types.d.ts b/src/types.d.ts index 39fd45e5..2c7880af 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -42,4 +42,23 @@ export type MTPhotoSize = { type?: string, // i, m, x, y, w by asc location?: any, bytes?: Uint8Array // if type == 'i' -}; \ No newline at end of file +}; + +export type InvokeApiOptions = Partial<{ + dcID: number, + timeout: number, + noErrorBox: boolean, + fileUpload: boolean, + ignoreErrors: boolean, + fileDownload: boolean, + createNetworker: boolean, + singleInRequest: boolean, + startMaxLength: number, + + afterMessageID: string, + resultType: boolean, + + waitTime: number, + stopTime: number, + rawError: any +}>; \ No newline at end of file