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,
// so do it first
const themeAssets = await copyThemeAssets(themes, legacy);
const jsBundlePath = await buildJs();
const jsLegacyBundlePath = await buildJsLegacy();
const jsBundlePath = await buildJs("src/main.js", `${PROJECT_ID}.js`);
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 assetPaths = createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, themeAssets);
const assetPaths = createAssetPaths(jsBundlePath, jsLegacyBundlePath, jsWorkerPath, cssBundlePaths, themeAssets);
let manifestPath;
if (offline) {
@ -98,7 +99,7 @@ async function build() {
console.log(`built ${PROJECT_ID} ${version} successfully`);
}
function createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, themeAssets) {
function createAssetPaths(jsBundlePath, jsLegacyBundlePath, jsWorkerPath, cssBundlePaths, themeAssets) {
function trim(path) {
if (!path.startsWith(targetDir)) {
throw new Error("invalid target path: " + targetDir);
@ -108,6 +109,7 @@ function createAssetPaths(jsBundlePath, jsLegacyBundlePath, cssBundlePaths, them
return {
jsBundle: () => trim(jsBundlePath),
jsLegacyBundle: () => trim(jsLegacyBundlePath),
jsWorker: () => trim(jsWorkerPath),
cssMainBundle: () => trim(cssBundlePaths.main),
cssThemeBundle: themeName => trim(cssBundlePaths.themes[themeName]),
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");
}
async function buildJs() {
async function buildJs(inputFile, outputName) {
// create js bundle
const bundle = await rollup({
input: 'src/main.js',
input: inputFile,
plugins: [removeJsComments({comments: "none"})]
});
const {output} = await bundle.generate({
format: 'es',
// TODO: can remove this?
name: `${PROJECT_ID}Bundle`
});
const code = output[0].code;
const bundlePath = resource(`${PROJECT_ID}.js`, code);
const bundlePath = resource(outputName, code);
await fs.writeFile(bundlePath, code, "utf8");
return bundlePath;
}
async function buildJsLegacy() {
async function buildJsLegacy(inputFile, outputName) {
// compile down to whatever IE 11 needs
const babelPlugin = babel.babel({
babelHelpers: 'bundled',
@ -214,7 +217,7 @@ async function buildJsLegacy() {
});
// create js bundle
const rollupConfig = {
input: ['src/legacy-polyfill.js', 'src/main.js'],
input: ['src/legacy-polyfill.js', inputFile],
plugins: [multi(), commonjs(), nodeResolve(), babelPlugin, removeJsComments({comments: "none"})]
};
const bundle = await rollup(rollupConfig);
@ -223,7 +226,39 @@ async function buildJsLegacy() {
name: `${PROJECT_ID}Bundle`
});
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");
return bundlePath;
}

View File

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

View File

@ -21,6 +21,7 @@ import {SessionInfo} from "./decryption/SessionInfo.js";
import {DecryptionPreparation} from "./decryption/DecryptionPreparation.js";
import {SessionDecryption} from "./decryption/SessionDecryption.js";
import {SessionCache} from "./decryption/SessionCache.js";
import {DecryptionWorker} from "./decryption/DecryptionWorker.js";
function getSenderKey(event) {
return event.content?.["sender_key"];
@ -38,7 +39,9 @@ export class Decryption {
constructor({pickleKey, olm}) {
this._pickleKey = pickleKey;
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) {
@ -55,6 +58,7 @@ export class Decryption {
* @return {DecryptionPreparation}
*/
async prepareDecryptAll(roomId, events, sessionCache, txn) {
await this._initPromise;
const errors = new Map();
const validEvents = [];
@ -85,7 +89,7 @@ export class Decryption {
errors.set(event.event_id, new DecryptionError("MEGOLM_NO_SESSION", event));
}
} 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
*/
export class SessionDecryption {
constructor(sessionInfo, events) {
constructor(sessionInfo, events, decryptor) {
sessionInfo.retain();
this._sessionInfo = sessionInfo;
this._events = events;
this._decryptor = decryptor;
}
async decryptAll() {
@ -38,7 +39,7 @@ export class SessionDecryption {
try {
const {session} = this._sessionInfo;
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;
try {
payload = JSON.parse(plaintext);
@ -63,12 +64,6 @@ export class SessionDecryption {
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() {
this._sessionInfo.release();
}

View File

@ -42,7 +42,7 @@ export class Timeline {
/** @package */
async load() {
const entries = await this._timelineReader.readFromEnd(50);
const entries = await this._timelineReader.readFromEnd(25);
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());