WIP worker work

This commit is contained in:
Bruno Windels 2020-09-10 13:00:11 +02:00
parent 17412bbb2f
commit fdbc5f3c1d
9 changed files with 248 additions and 23 deletions

View file

@ -84,10 +84,11 @@ async function build() {
// also creates the directories where the theme css bundles are placed in, // also creates the directories where the theme css bundles are placed in,
// so do it first // so do it first
const themeAssets = await copyThemeAssets(themes, legacy); const themeAssets = await copyThemeAssets(themes, legacy);
const jsBundlePath = await buildJs(); const jsBundlePath = await buildJs("src/main.js", `${PROJECT_ID}.js`);
const jsLegacyBundlePath = await buildJsLegacy(); const jsLegacyBundlePath = await buildJsLegacy("src/main.js", `${PROJECT_ID}-legacy.js`);
const jsWorkerPath = await buildWorkerJsLegacy("src/worker.js", `worker.js`);
const cssBundlePaths = await buildCssBundles(legacy ? buildCssLegacy : buildCss, themes, themeAssets); const cssBundlePaths = await buildCssBundles(legacy ? buildCssLegacy : buildCss, themes, themeAssets);
const assetPaths = createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, themeAssets); const assetPaths = createAssetPaths(jsBundlePath, jsLegacyBundlePath, jsWorkerPath, cssBundlePaths, themeAssets);
let manifestPath; let manifestPath;
if (offline) { if (offline) {
@ -98,7 +99,7 @@ async function build() {
console.log(`built ${PROJECT_ID} ${version} successfully`); console.log(`built ${PROJECT_ID} ${version} successfully`);
} }
function createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, themeAssets) { function createAssetPaths(jsBundlePath, jsLegacyBundlePath, jsWorkerPath, cssBundlePaths, themeAssets) {
function trim(path) { function trim(path) {
if (!path.startsWith(targetDir)) { if (!path.startsWith(targetDir)) {
throw new Error("invalid target path: " + targetDir); throw new Error("invalid target path: " + targetDir);
@ -108,6 +109,7 @@ function createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, them
return { return {
jsBundle: () => trim(jsBundlePath), jsBundle: () => trim(jsBundlePath),
jsLegacyBundle: () => trim(jsLegacyBundlePath), jsLegacyBundle: () => trim(jsLegacyBundlePath),
jsWorker: () => trim(jsWorkerPath),
cssMainBundle: () => trim(cssBundlePaths.main), cssMainBundle: () => trim(cssBundlePaths.main),
cssThemeBundle: themeName => trim(cssBundlePaths.themes[themeName]), cssThemeBundle: themeName => trim(cssBundlePaths.themes[themeName]),
cssThemeBundles: () => Object.values(cssBundlePaths.themes).map(a => trim(a)), cssThemeBundles: () => Object.values(cssBundlePaths.themes).map(a => trim(a)),
@ -180,23 +182,24 @@ async function buildHtml(doc, version, assetPaths, manifestPath) {
await fs.writeFile(path.join(targetDir, "index.html"), doc.html(), "utf8"); await fs.writeFile(path.join(targetDir, "index.html"), doc.html(), "utf8");
} }
async function buildJs() { async function buildJs(inputFile, outputName) {
// create js bundle // create js bundle
const bundle = await rollup({ const bundle = await rollup({
input: 'src/main.js', input: inputFile,
plugins: [removeJsComments({comments: "none"})] plugins: [removeJsComments({comments: "none"})]
}); });
const {output} = await bundle.generate({ const {output} = await bundle.generate({
format: 'es', format: 'es',
// TODO: can remove this?
name: `${PROJECT_ID}Bundle` name: `${PROJECT_ID}Bundle`
}); });
const code = output[0].code; const code = output[0].code;
const bundlePath = resource(`${PROJECT_ID}.js`, code); const bundlePath = resource(outputName, code);
await fs.writeFile(bundlePath, code, "utf8"); await fs.writeFile(bundlePath, code, "utf8");
return bundlePath; return bundlePath;
} }
async function buildJsLegacy() { async function buildJsLegacy(inputFile, outputName) {
// compile down to whatever IE 11 needs // compile down to whatever IE 11 needs
const babelPlugin = babel.babel({ const babelPlugin = babel.babel({
babelHelpers: 'bundled', babelHelpers: 'bundled',
@ -214,7 +217,7 @@ async function buildJsLegacy() {
}); });
// create js bundle // create js bundle
const rollupConfig = { const rollupConfig = {
input: ['src/legacy-polyfill.js', 'src/main.js'], input: ['src/legacy-polyfill.js', inputFile],
plugins: [multi(), commonjs(), nodeResolve(), babelPlugin, removeJsComments({comments: "none"})] plugins: [multi(), commonjs(), nodeResolve(), babelPlugin, removeJsComments({comments: "none"})]
}; };
const bundle = await rollup(rollupConfig); const bundle = await rollup(rollupConfig);
@ -223,7 +226,39 @@ async function buildJsLegacy() {
name: `${PROJECT_ID}Bundle` name: `${PROJECT_ID}Bundle`
}); });
const code = output[0].code; const code = output[0].code;
const bundlePath = resource(`${PROJECT_ID}-legacy.js`, code); const bundlePath = resource(outputName, code);
await fs.writeFile(bundlePath, code, "utf8");
return bundlePath;
}
async function buildWorkerJsLegacy(inputFile, outputName) {
// compile down to whatever IE 11 needs
const babelPlugin = babel.babel({
babelHelpers: 'bundled',
exclude: 'node_modules/**',
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "entry",
corejs: "3",
targets: "IE 11"
}
]
]
});
// create js bundle
const rollupConfig = {
input: ['src/worker-polyfill.js', inputFile],
plugins: [multi(), commonjs(), nodeResolve(), babelPlugin, removeJsComments({comments: "none"})]
};
const bundle = await rollup(rollupConfig);
const {output} = await bundle.generate({
format: 'iife',
name: `${PROJECT_ID}Bundle`
});
const code = output[0].code;
const bundlePath = resource(outputName, code);
await fs.writeFile(bundlePath, code, "utf8"); await fs.writeFile(bundlePath, code, "utf8");
return bundlePath; return bundlePath;
} }

View file

@ -54,7 +54,7 @@ export class TimelineViewModel extends ViewModel {
if (firstTile.shape === "gap") { if (firstTile.shape === "gap") {
return firstTile.fill(); return firstTile.fill();
} else { } else {
await this._timeline.loadAtTop(50); await this._timeline.loadAtTop(10);
return false; return false;
} }
} }

View file

@ -21,6 +21,7 @@ import {SessionInfo} from "./decryption/SessionInfo.js";
import {DecryptionPreparation} from "./decryption/DecryptionPreparation.js"; import {DecryptionPreparation} from "./decryption/DecryptionPreparation.js";
import {SessionDecryption} from "./decryption/SessionDecryption.js"; import {SessionDecryption} from "./decryption/SessionDecryption.js";
import {SessionCache} from "./decryption/SessionCache.js"; import {SessionCache} from "./decryption/SessionCache.js";
import {DecryptionWorker} from "./decryption/DecryptionWorker.js";
function getSenderKey(event) { function getSenderKey(event) {
return event.content?.["sender_key"]; return event.content?.["sender_key"];
@ -38,7 +39,9 @@ export class Decryption {
constructor({pickleKey, olm}) { constructor({pickleKey, olm}) {
this._pickleKey = pickleKey; this._pickleKey = pickleKey;
this._olm = olm; this._olm = olm;
// this._worker = new MessageHandler(new Worker("worker-2580578233.js")); // this._decryptor = new DecryptionWorker(new Worker("./src/worker.js"));
this._decryptor = new DecryptionWorker(new Worker("worker-3074010154.js"));
this._initPromise = this._decryptor.init();
} }
createSessionCache(fallback) { createSessionCache(fallback) {
@ -55,6 +58,7 @@ export class Decryption {
* @return {DecryptionPreparation} * @return {DecryptionPreparation}
*/ */
async prepareDecryptAll(roomId, events, sessionCache, txn) { async prepareDecryptAll(roomId, events, sessionCache, txn) {
await this._initPromise;
const errors = new Map(); const errors = new Map();
const validEvents = []; const validEvents = [];
@ -85,7 +89,7 @@ export class Decryption {
errors.set(event.event_id, new DecryptionError("MEGOLM_NO_SESSION", event)); errors.set(event.event_id, new DecryptionError("MEGOLM_NO_SESSION", event));
} }
} else { } else {
sessionDecryptions.push(new SessionDecryption(sessionInfo, eventsForSession)); sessionDecryptions.push(new SessionDecryption(sessionInfo, eventsForSession, this._decryptor));
} }
})); }));

View file

@ -0,0 +1,64 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export class DecryptionWorker {
constructor(worker) {
this._worker = worker;
this._requests = new Map();
this._counter = 0;
this._worker.addEventListener("message", this);
}
handleEvent(e) {
if (e.type === "message") {
const message = e.data;
console.log("worker reply", message);
const request = this._requests.get(message.replyToId);
if (request) {
if (message.type === "success") {
request.resolve(message.payload);
} else if (message.type === "error") {
request.reject(new Error(message.stack));
}
this._requests.delete(message.ref_id);
}
}
}
_send(message) {
this._counter += 1;
message.id = this._counter;
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
this._requests.set(message.id, {reject, resolve});
this._worker.postMessage(message);
return promise;
}
decrypt(session, ciphertext) {
const sessionKey = session.export_session(session.first_known_index());
return this._send({type: "megolm_decrypt", ciphertext, sessionKey});
}
init() {
return this._send({type: "load_olm", path: "olm_legacy-3232457086.js"});
// return this._send({type: "load_olm", path: "../lib/olm/olm_legacy.js"});
}
}

View file

@ -22,10 +22,11 @@ import {ReplayDetectionEntry} from "./ReplayDetectionEntry.js";
* Does the actual decryption of all events for a given megolm session in a batch * Does the actual decryption of all events for a given megolm session in a batch
*/ */
export class SessionDecryption { export class SessionDecryption {
constructor(sessionInfo, events) { constructor(sessionInfo, events, decryptor) {
sessionInfo.retain(); sessionInfo.retain();
this._sessionInfo = sessionInfo; this._sessionInfo = sessionInfo;
this._events = events; this._events = events;
this._decryptor = decryptor;
} }
async decryptAll() { async decryptAll() {
@ -38,7 +39,7 @@ export class SessionDecryption {
try { try {
const {session} = this._sessionInfo; const {session} = this._sessionInfo;
const ciphertext = event.content.ciphertext; const ciphertext = event.content.ciphertext;
const {plaintext, message_index: messageIndex} = await this._decrypt(session, ciphertext); const {plaintext, message_index: messageIndex} = await this._decryptor.decrypt(session, ciphertext);
let payload; let payload;
try { try {
payload = JSON.parse(plaintext); payload = JSON.parse(plaintext);
@ -63,12 +64,6 @@ export class SessionDecryption {
return {results, errors, replayEntries}; return {results, errors, replayEntries};
} }
async _decrypt(session, ciphertext) {
// const sessionKey = session.export_session(session.first_known_index());
// return this._worker.decrypt(sessionKey, ciphertext);
return session.decrypt(ciphertext);
}
dispose() { dispose() {
this._sessionInfo.release(); this._sessionInfo.release();
} }

View file

@ -42,7 +42,7 @@ export class Timeline {
/** @package */ /** @package */
async load() { async load() {
const entries = await this._timelineReader.readFromEnd(50); const entries = await this._timelineReader.readFromEnd(25);
this._remoteEntries.setManySorted(entries); this._remoteEntries.setManySorted(entries);
} }

19
src/worker-polyfill.js Normal file
View file

@ -0,0 +1,19 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// polyfills needed for IE11
import "regenerator-runtime/runtime";
import "core-js/modules/es.promise";

108
src/worker.js Normal file
View file

@ -0,0 +1,108 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
function asErrorMessage(err) {
return {
type: "error",
message: err.message,
stack: err.stack
};
}
function asSuccessMessage(payload) {
return {
type: "success",
payload
};
}
class MessageHandler {
constructor() {
this._olm = null;
}
handleEvent(e) {
if (e.type === "message") {
this._handleMessage(e.data);
}
}
_sendReply(refMessage, reply) {
reply.replyToId = refMessage.id;
self.postMessage(reply);
}
_toMessage(fn) {
try {
let payload = fn();
if (payload instanceof Promise) {
return payload.then(
payload => asSuccessMessage(payload),
err => asErrorMessage(err)
);
} else {
return asSuccessMessage(payload);
}
} catch (err) {
return asErrorMessage(err);
}
}
_loadOlm(path) {
return this._toMessage(async () => {
// might have some problems here with window vs self as global object?
if (self.msCrypto && !self.crypto) {
self.crypto = self.msCrypto;
}
self.importScripts(path);
const olm = self.olm_exports;
// mangle the globals enough to make olm load believe it is running in a browser
self.window = self;
self.document = {};
await olm.init();
delete self.document;
delete self.window;
this._olm = olm;
});
}
_megolmDecrypt(sessionKey, ciphertext) {
return this._toMessage(() => {
let session;
try {
session = new this._olm.InboundGroupSession();
session.import_session(sessionKey);
// returns object with plaintext and message_index
return session.decrypt(ciphertext);
} finally {
session?.free();
}
});
}
async _handleMessage(message) {
switch (message.type) {
case "load_olm":
this._sendReply(message, await this._loadOlm(message.path));
break;
case "megolm_decrypt":
this._sendReply(message, this._megolmDecrypt(message.sessionKey, message.ciphertext));
break;
}
}
}
self.addEventListener("message", new MessageHandler());