From dad42da803f3304618bc06c428820fbc26170eb2 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Thu, 20 Aug 2020 11:36:19 +0300 Subject: [PATCH] Moved MTProto from web worker to service worker --- package-lock.json | 94 +++++++++++++++++++ package.json | 2 + .../{mtproto.worker.js => mtproto.service.ts} | 89 ++++++++++++------ src/lib/mtproto/mtprotoworker.ts | 61 ++++++++++-- src/lib/storage.ts | 33 ++++++- src/pages/pageIm.ts | 2 +- tsconfig.json | 4 +- webpack.common.js | 6 ++ 8 files changed, 249 insertions(+), 42 deletions(-) rename src/lib/mtproto/{mtproto.worker.js => mtproto.service.ts} (61%) diff --git a/package-lock.json b/package-lock.json index ebede32c..e880da60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3026,6 +3026,12 @@ "integrity": "sha512-SDSGgXT3LRCH6qMWk8OHT1vLSVNuHNvCpKCx2/TYtQMbMGGgxJC9fspwSkQjqzRagrWnCrxuLL3jMNXLXHHvSw==", "dev": true }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "dev": true + }, "@types/asn1js": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/@types/asn1js/-/asn1js-0.0.1.tgz", @@ -3186,12 +3192,91 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, + "@types/serviceworker-webpack-plugin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/serviceworker-webpack-plugin/-/serviceworker-webpack-plugin-1.0.1.tgz", + "integrity": "sha512-NBLpxauF9s0dG+g3KFRo7iQwEuSa9E9Sn+SG/4SxECVOIG/0JBIK3Qc4b+81vH3/1dXJqMfm1JFh8vVsgp5/Dw==", + "dev": true, + "requires": { + "@types/webpack": "*" + } + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, "@types/stack-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", "dev": true }, + "@types/tapable": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", + "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", + "integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/webpack": { + "version": "4.41.21", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", + "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", + "dev": true, + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@types/webpack-sources": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.2.tgz", + "integrity": "sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, "@types/yargs": { "version": "13.0.5", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.5.tgz", @@ -16064,6 +16149,15 @@ "send": "0.17.1" } }, + "serviceworker-webpack-plugin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/serviceworker-webpack-plugin/-/serviceworker-webpack-plugin-1.0.1.tgz", + "integrity": "sha512-VgDEkZ3pA0HajsRaWtl5w6bLxAXx0Y+4dm7YeTcIxVmvC9YXvstex38HOBDuYETeDS5fUlBy/47gC0QYBrG0nw==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", diff --git a/package.json b/package.json index 1668279f..50dda03d 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/chrome": "0.0.91", "@types/jest": "^24.9.1", "@types/puppeteer": "^3.0.1", + "@types/serviceworker-webpack-plugin": "^1.0.1", "aes-js": "^3.1.2", "autoprefixer": "^9.8.0", "babel-jest": "^24.9.0", @@ -61,6 +62,7 @@ "qr-code-styling": "^1.0.1", "resolve-url-loader": "^3.1.1", "sass-loader": "^8.0.2", + "serviceworker-webpack-plugin": "^1.0.1", "streamsaver": "^2.0.4", "style-loader": "^1.2.1", "terser-webpack-plugin": "^3.0.2", diff --git a/src/lib/mtproto/mtproto.worker.js b/src/lib/mtproto/mtproto.service.ts similarity index 61% rename from src/lib/mtproto/mtproto.worker.js rename to src/lib/mtproto/mtproto.service.ts index 25e72093..b4c68edd 100644 --- a/src/lib/mtproto/mtproto.worker.js +++ b/src/lib/mtproto/mtproto.service.ts @@ -7,8 +7,7 @@ import AppStorage from '../storage'; import cryptoWorker from "../crypto/cryptoworker"; import networkerFactory from "./networkerFactory"; -//const ctx: Worker = self as any; -const ctx = self; +const ctx = self as any as ServiceWorkerGlobalScope; //console.error('INCLUDE !!!', new Error().stack); @@ -25,8 +24,8 @@ const ctx = self; * let the calling scope pass in the global scope object. * @returns {boolean} */ -var _isSafari = null; -function isSafari(scope) { +var _isSafari: boolean = null; +function isSafari(scope: any) { if(_isSafari == null) { var userAgent = scope.navigator ? scope.navigator.userAgent : null; _isSafari = !!scope.safari || @@ -35,11 +34,11 @@ function isSafari(scope) { return _isSafari; } -function isObject(object) { +function isObject(object: any) { return typeof(object) === 'object' && object !== null; } -function fillTransfer(transfer, obj) { +function fillTransfer(transfer: any, obj: any) { if(!obj) return; if(obj instanceof ArrayBuffer) { @@ -57,10 +56,14 @@ function fillTransfer(transfer, obj) { } } -function reply() { +/** + * Respond to request + */ +function respond(client: Client | ServiceWorker | MessagePort, ...args: any[]) { // отключил для всего потому что не успел пофиксить transfer detached //if(isSafari(self)/* || true */) { - ctx.postMessage(...arguments); + // @ts-ignore + client.postMessage(...args); /* } else { var transfer = new Set(); fillTransfer(transfer, arguments); @@ -72,12 +75,24 @@ function reply() { } networkerFactory.setUpdatesProcessor((obj, bool) => { + //console.log('updatesss'); //ctx.postMessage({update: {obj, bool}}); - reply({update: {obj, bool}}); + //respond({update: {obj, bool}}); + + ctx.clients.matchAll({ includeUncontrolled: false, type: 'window' }).then((listeners) => { + if(!listeners.length) { + //console.trace('no listeners?', self, listeners); + return; + } + + listeners[0].postMessage({update: {obj, bool}}); + }); }); -ctx.onmessage = function(e) { - var taskID = e.data.taskID; +ctx.addEventListener('message', async(e) => { + const taskID = e.data.taskID; + + console.log('[SW] Got message:', taskID, e, e.data); if(e.data.useLs) { AppStorage.finishTask(e.data.taskID, e.data.args); @@ -87,36 +102,52 @@ ctx.onmessage = function(e) { switch(e.data.task) { case 'computeSRP': case 'gzipUncompress': + // @ts-ignore return cryptoWorker[e.data.task].apply(cryptoWorker, e.data.args).then(result => { - //ctx.postMessage({taskID: taskID, result: result}); - reply({taskID: taskID, result: result}); + respond(e.source, {taskID: taskID, result: result}); }); default: { try { + // @ts-ignore let result = apiManager[e.data.task].apply(apiManager, e.data.args); + if(result instanceof Promise) { - result.then(result => { - //console.log(e.data.task + ' result:', result, taskID); - reply({taskID: taskID, result: result}); - //ctx.postMessage({taskID: taskID, result: result}); - }).catch(err => { - //console.error(e.data.task + ' err:', err, taskID); - //ctx.postMessage({taskID: taskID, error: err}); - reply({taskID: taskID, error: err}); - }); - } else { - //ctx.postMessage({taskID: taskID, result: result}); - reply({taskID: taskID, result: result}); + result = await result; } + + respond(e.source, {taskID: taskID, result: result}); } catch(err) { - reply({taskID: taskID, error: err}); - //ctx.postMessage({taskID: taskID, error: err}); + respond(e.source, {taskID: taskID, error: err}); } //throw new Error('Unknown task: ' + e.data.task); } } -} +}); -ctx.postMessage('ready'); +/** + * Service Worker Installation + */ +ctx.addEventListener('install', (event: ExtendableEvent) => { + //console.log('service worker is installing'); + + /* initCache(); + + event.waitUntil( + initNetwork(), + ); */ + event.waitUntil(ctx.skipWaiting()); // Activate worker immediately +}); + +/** + * Service Worker Activation + */ +ctx.addEventListener('activate', (event) => { + //console.log('service worker activating', ctx); + + /* if (!ctx.cache) initCache(); + if (!ctx.network) initNetwork(); */ + + event.waitUntil(ctx.clients.claim()); +}); diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 95fbd88c..eae92d7a 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -1,6 +1,7 @@ import {dT, isObject, $rootScope} from '../utils'; import AppStorage from '../storage'; import CryptoWorkerMethods from '../crypto/crypto_methods'; +import runtime from 'serviceworker-webpack-plugin/lib/runtime'; type Task = { taskID: number, @@ -8,8 +9,15 @@ type Task = { args: any[] }; +/* let pending: any[] = []; +function resendPending() { + if(navigator.serviceWorker.controller) { + for(let i = 0; i < pending.length; i++) navigator.serviceWorker.controller.postMessage(pending[i]); + pending = []; + } +} */ + class ApiManagerProxy extends CryptoWorkerMethods { - private webWorker: Worker | boolean = false; private taskID = 0; private awaiting: { [id: number]: { @@ -27,8 +35,49 @@ class ApiManagerProxy extends CryptoWorkerMethods { super(); console.log(dT(), 'ApiManagerProxy constructor'); - if(window.Worker) { - import('./mtproto.worker.js').then((worker: any) => { + /** + * Service worker + */ + runtime.register({ scope: '/' }); + + navigator.serviceWorker.ready.then((registration) => { + console.info(dT(), 'ApiManagerProxy set SW'); + this.releasePending(); + }); + + navigator.serviceWorker.oncontrollerchange = () => { + console.error('oncontrollerchange'); + this.releasePending(); + + navigator.serviceWorker.controller.addEventListener('error', (e) => { + console.error('controller error:', e); + }); + }; + + /** + * Message resolver + */ + navigator.serviceWorker.addEventListener('message', (e) => { + if(!isObject(e.data)) { + return; + } + + if(e.data.useLs) { + // @ts-ignore + AppStorage[e.data.task](...e.data.args).then(res => { + navigator.serviceWorker.controller.postMessage({useLs: true, taskID: e.data.taskID, args: res}); + }); + } else if(e.data.update) { + if(this.updatesProcessor) { + this.updatesProcessor(e.data.update.obj, e.data.update.bool); + } + } else { + this.finalizeTask(e.data.taskID, e.data.result, e.data.error); + } + }); + + /* if(window.Worker) { + import('./mtproto_service.js').then((worker: any) => { var tmpWorker = new worker.default(); tmpWorker.onmessage = (e: any) => { if(!this.webWorker) { @@ -60,7 +109,7 @@ class ApiManagerProxy extends CryptoWorkerMethods { this.webWorker = false; }; }); - } + } */ } private finalizeTask(taskID: number, result: any, error: any) { @@ -93,9 +142,9 @@ class ApiManagerProxy extends CryptoWorkerMethods { } private releasePending() { - if(this.webWorker) { + if(navigator.serviceWorker.controller) { this.pending.forEach(pending => { - (this.webWorker as Worker).postMessage(pending); + navigator.serviceWorker.controller.postMessage(pending); }); this.pending.length = 0; diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 9f128ce9..b7eca533 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -142,6 +142,8 @@ class AppStorage { private isWebWorker: boolean; private taskID = 0; private tasks: {[taskID: number]: (result: any) => void} = {}; + //private log = (...args: any[]) => console.log('[SW LS]', ...args); + private log = (...args: any[]) => {}; constructor() { if(Modes.test) { @@ -149,7 +151,8 @@ class AppStorage { } // @ts-ignore - this.isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; + //this.isWebWorker = typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope; + this.isWebWorker = typeof ServiceWorkerGlobalScope !== 'undefined' && self instanceof ServiceWorkerGlobalScope; } public setPrefix(newPrefix: string) { @@ -161,6 +164,13 @@ class AppStorage { } public finishTask(taskID: number, result: any) { + this.log('finishTask:', taskID, result, Object.keys(this.tasks)); + + if(!this.tasks.hasOwnProperty(taskID)) { + this.log('no such task:', taskID, result); + return; + } + this.tasks[taskID](result); delete this.tasks[taskID]; } @@ -168,10 +178,25 @@ class AppStorage { private proxy(methodName: string, ..._args: any[]) { return new Promise((resolve, reject) => { if(this.isWebWorker) { - this.tasks[this.taskID] = resolve; + const taskID = this.taskID++; + + this.tasks[taskID] = resolve; + + (self as any as ServiceWorkerGlobalScope) + .clients + .matchAll({ includeUncontrolled: false, type: 'window' }) + .then((listeners) => { + if(!listeners.length) { + //console.trace('no listeners?', self, listeners); + return; + } + + this.log('will proxy', {useLs: true, task: methodName, taskID, args: _args}); + listeners[0].postMessage({useLs: true, task: methodName, taskID, args: _args}); + }); + // @ts-ignore - self.postMessage({useLs: true, task: methodName, taskID: this.taskID, args: _args}); - this.taskID++; + //self.postMessage({useLs: true, task: methodName, taskID: this.taskID, args: _args}); } else { let args = Array.prototype.slice.call(_args); args.push((result: T) => { diff --git a/src/pages/pageIm.ts b/src/pages/pageIm.ts index e0cbe4d9..d565b41b 100644 --- a/src/pages/pageIm.ts +++ b/src/pages/pageIm.ts @@ -29,7 +29,7 @@ let onFirstMount = () => { //(Array.from(document.getElementsByClassName('rp')) as HTMLElement[]).forEach(el => ripple(el)); Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => { - el.addEventListener('click', (e) => { + (el as HTMLElement).addEventListener('click', (e) => { //console.log('click pageIm'); if(!el.classList.contains('btn-menu-toggle')) return false; diff --git a/tsconfig.json b/tsconfig.json index cc3289b4..95ca0dcb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ //"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "target": "es2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ - "lib": ["es2016", "dom", "ES2018.Promise"], /* Specify library files to be included in the compilation. */ + "lib": ["es2016", "dom", "ES2018.Promise", "webworker"], /* Specify library files to be included in the compilation. */ "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ @@ -40,7 +40,7 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ diff --git a/webpack.common.js b/webpack.common.js index a24154ef..3e99e671 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -3,6 +3,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const MediaQueryPlugin = require('media-query-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const postcssPresetEnv = require('postcss-preset-env'); +const ServiceWorkerWebpackPlugin = require('serviceworker-webpack-plugin'); const fs = require('fs'); const allowedIPs = ['195.66.140.39', '192.168.31.144', '127.0.0.1', '192.168.31.1', '192.168.31.192', '176.100.18.181']; @@ -165,6 +166,11 @@ module.exports = { sockHost: useLocal ? undefined : 'tweb.enko.club', }, plugins: [ + new ServiceWorkerWebpackPlugin({ + entry: path.join(__dirname, 'src/lib/mtproto/mtproto.service.ts'), + filename: 'sw.js', + excludes: ['**/*'], + }), new HtmlWebpackPlugin({ filename: `index.html`, //template: 'public/index_template.html',