forked from mystiq/hydrogen-web
Worker WIP
This commit is contained in:
parent
fdbc5f3c1d
commit
0bf1723d99
12 changed files with 339 additions and 92 deletions
|
@ -36,8 +36,7 @@ export class ViewModel extends EventEmitter {
|
||||||
if (!this.disposables) {
|
if (!this.disposables) {
|
||||||
this.disposables = new Disposables();
|
this.disposables = new Disposables();
|
||||||
}
|
}
|
||||||
this.disposables.track(disposable);
|
return this.disposables.track(disposable);
|
||||||
return disposable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|
|
@ -38,7 +38,8 @@ export class RoomViewModel extends ViewModel {
|
||||||
async load() {
|
async load() {
|
||||||
this._room.on("change", this._onRoomChange);
|
this._room.on("change", this._onRoomChange);
|
||||||
try {
|
try {
|
||||||
this._timeline = await this._room.openTimeline();
|
this._timeline = this._room.openTimeline();
|
||||||
|
await this._timeline.load();
|
||||||
this._timelineVM = new TimelineViewModel(this.childOptions({
|
this._timelineVM = new TimelineViewModel(this.childOptions({
|
||||||
room: this._room,
|
room: this._room,
|
||||||
timeline: this._timeline,
|
timeline: this._timeline,
|
||||||
|
|
|
@ -268,7 +268,7 @@ class DecryptionPreparation {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this._megolmDecryptionChanges.dispose();
|
this._megolmDecryptionPreparation.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +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";
|
import {DecryptionWorker, WorkerPool} from "./decryption/DecryptionWorker.js";
|
||||||
|
|
||||||
function getSenderKey(event) {
|
function getSenderKey(event) {
|
||||||
return event.content?.["sender_key"];
|
return event.content?.["sender_key"];
|
||||||
|
@ -40,7 +40,7 @@ export class Decryption {
|
||||||
this._pickleKey = pickleKey;
|
this._pickleKey = pickleKey;
|
||||||
this._olm = olm;
|
this._olm = olm;
|
||||||
// this._decryptor = new DecryptionWorker(new Worker("./src/worker.js"));
|
// this._decryptor = new DecryptionWorker(new Worker("./src/worker.js"));
|
||||||
this._decryptor = new DecryptionWorker(new Worker("worker-3074010154.js"));
|
this._decryptor = new DecryptionWorker(new WorkerPool("worker-1039452087.js", 4));
|
||||||
this._initPromise = this._decryptor.init();
|
this._initPromise = this._decryptor.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,51 +14,200 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export class DecryptionWorker {
|
import {AbortError} from "../../../../utils/error.js";
|
||||||
|
|
||||||
|
class WorkerState {
|
||||||
constructor(worker) {
|
constructor(worker) {
|
||||||
this._worker = worker;
|
this.worker = worker;
|
||||||
|
this.busy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(pool) {
|
||||||
|
this.worker.addEventListener("message", pool);
|
||||||
|
this.worker.addEventListener("error", pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
detach(pool) {
|
||||||
|
this.worker.removeEventListener("message", pool);
|
||||||
|
this.worker.removeEventListener("error", pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Request {
|
||||||
|
constructor(message, pool) {
|
||||||
|
this._promise = new Promise((_resolve, _reject) => {
|
||||||
|
this._resolve = _resolve;
|
||||||
|
this._reject = _reject;
|
||||||
|
});
|
||||||
|
this._message = message;
|
||||||
|
this._pool = pool;
|
||||||
|
this._worker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
if (this._isNotDisposed) {
|
||||||
|
this._pool._abortRequest(this);
|
||||||
|
this._dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response() {
|
||||||
|
return this._promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dispose() {
|
||||||
|
this._reject = null;
|
||||||
|
this._resolve = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _isNotDisposed() {
|
||||||
|
return this._resolve && this._reject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkerPool {
|
||||||
|
constructor(path, amount) {
|
||||||
|
this._workers = [];
|
||||||
|
for (let i = 0; i < amount ; ++i) {
|
||||||
|
const worker = new WorkerState(new Worker(path));
|
||||||
|
worker.attach(this);
|
||||||
|
this._workers[i] = worker;
|
||||||
|
}
|
||||||
this._requests = new Map();
|
this._requests = new Map();
|
||||||
this._counter = 0;
|
this._counter = 0;
|
||||||
this._worker.addEventListener("message", this);
|
this._pendingFlag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEvent(e) {
|
handleEvent(e) {
|
||||||
if (e.type === "message") {
|
if (e.type === "message") {
|
||||||
const message = e.data;
|
const message = e.data;
|
||||||
console.log("worker reply", message);
|
|
||||||
const request = this._requests.get(message.replyToId);
|
const request = this._requests.get(message.replyToId);
|
||||||
if (request) {
|
if (request) {
|
||||||
if (message.type === "success") {
|
request._worker.busy = false;
|
||||||
request.resolve(message.payload);
|
if (request._isNotDisposed) {
|
||||||
} else if (message.type === "error") {
|
if (message.type === "success") {
|
||||||
request.reject(new Error(message.stack));
|
request._resolve(message.payload);
|
||||||
|
} else if (message.type === "error") {
|
||||||
|
request._reject(new Error(message.stack));
|
||||||
|
}
|
||||||
|
request._dispose();
|
||||||
}
|
}
|
||||||
this._requests.delete(message.ref_id);
|
this._requests.delete(message.replyToId);
|
||||||
|
}
|
||||||
|
console.log("got worker reply", message, this._requests.size);
|
||||||
|
this._sendPending();
|
||||||
|
} else if (e.type === "error") {
|
||||||
|
console.error("worker error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPendingRequest() {
|
||||||
|
for (const r of this._requests.values()) {
|
||||||
|
if (!r._worker) {
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_send(message) {
|
_getFreeWorker() {
|
||||||
|
for (const w of this._workers) {
|
||||||
|
if (!w.busy) {
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendPending() {
|
||||||
|
this._pendingFlag = false;
|
||||||
|
console.log("seeing if there is anything to send", this._requests.size);
|
||||||
|
let success;
|
||||||
|
do {
|
||||||
|
success = false;
|
||||||
|
const request = this._getPendingRequest();
|
||||||
|
if (request) {
|
||||||
|
console.log("sending pending request", request);
|
||||||
|
const worker = this._getFreeWorker();
|
||||||
|
if (worker) {
|
||||||
|
this._sendWith(request, worker);
|
||||||
|
success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (success);
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendWith(request, worker) {
|
||||||
|
request._worker = worker;
|
||||||
|
worker.busy = true;
|
||||||
|
console.log("sending message to worker", request._message);
|
||||||
|
worker.worker.postMessage(request._message);
|
||||||
|
}
|
||||||
|
|
||||||
|
_enqueueRequest(message) {
|
||||||
this._counter += 1;
|
this._counter += 1;
|
||||||
message.id = this._counter;
|
message.id = this._counter;
|
||||||
let resolve;
|
const request = new Request(message, this);
|
||||||
let reject;
|
this._requests.set(message.id, request);
|
||||||
const promise = new Promise((_resolve, _reject) => {
|
return request;
|
||||||
resolve = _resolve;
|
}
|
||||||
reject = _reject;
|
|
||||||
|
send(message) {
|
||||||
|
const request = this._enqueueRequest(message);
|
||||||
|
const worker = this._getFreeWorker();
|
||||||
|
if (worker) {
|
||||||
|
this._sendWith(request, worker);
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes all workers are free atm
|
||||||
|
sendAll(message) {
|
||||||
|
const promises = this._workers.map(worker => {
|
||||||
|
const request = this._enqueueRequest(message);
|
||||||
|
this._sendWith(request, worker);
|
||||||
|
return request.response();
|
||||||
});
|
});
|
||||||
this._requests.set(message.id, {reject, resolve});
|
return Promise.all(promises);
|
||||||
this._worker.postMessage(message);
|
}
|
||||||
return promise;
|
|
||||||
|
dispose() {
|
||||||
|
for (const w of this._workers) {
|
||||||
|
w.worker.terminate();
|
||||||
|
w.detach(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_trySendPendingInNextTick() {
|
||||||
|
if (!this._pendingFlag) {
|
||||||
|
this._pendingFlag = true;
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
this._sendPending();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_abortRequest(request) {
|
||||||
|
request._reject(new AbortError());
|
||||||
|
if (request._worker) {
|
||||||
|
request._worker.busy = false;
|
||||||
|
}
|
||||||
|
this._requests.delete(request._message.id);
|
||||||
|
// allow more requests to be aborted before trying to send other pending
|
||||||
|
this._trySendPendingInNextTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DecryptionWorker {
|
||||||
|
constructor(workerPool) {
|
||||||
|
this._workerPool = workerPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypt(session, ciphertext) {
|
decrypt(session, ciphertext) {
|
||||||
const sessionKey = session.export_session(session.first_known_index());
|
const sessionKey = session.export_session(session.first_known_index());
|
||||||
return this._send({type: "megolm_decrypt", ciphertext, sessionKey});
|
return this._workerPool.send({type: "megolm_decrypt", ciphertext, sessionKey});
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
async init() {
|
||||||
return this._send({type: "load_olm", path: "olm_legacy-3232457086.js"});
|
await this._workerPool.sendAll({type: "load_olm", path: "olm_legacy-3232457086.js"});
|
||||||
// return this._send({type: "load_olm", path: "../lib/olm/olm_legacy.js"});
|
// return this._send({type: "load_olm", path: "../lib/olm/olm_legacy.js"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ export class SessionDecryption {
|
||||||
this._sessionInfo = sessionInfo;
|
this._sessionInfo = sessionInfo;
|
||||||
this._events = events;
|
this._events = events;
|
||||||
this._decryptor = decryptor;
|
this._decryptor = decryptor;
|
||||||
|
this._decryptionRequests = decryptor ? [] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async decryptAll() {
|
async decryptAll() {
|
||||||
|
@ -39,7 +40,16 @@ 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._decryptor.decrypt(session, ciphertext);
|
let decryptionResult;
|
||||||
|
if (this._decryptor) {
|
||||||
|
const request = this._decryptor.decrypt(session, ciphertext);
|
||||||
|
this._decryptionRequests.push(request);
|
||||||
|
decryptionResult = await request.response();
|
||||||
|
} else {
|
||||||
|
decryptionResult = session.decrypt(ciphertext);
|
||||||
|
}
|
||||||
|
const plaintext = decryptionResult.plaintext;
|
||||||
|
const messageIndex = decryptionResult.message_index;
|
||||||
let payload;
|
let payload;
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(plaintext);
|
payload = JSON.parse(plaintext);
|
||||||
|
@ -54,6 +64,10 @@ export class SessionDecryption {
|
||||||
const result = new DecryptionResult(payload, this._sessionInfo.senderKey, this._sessionInfo.claimedKeys);
|
const result = new DecryptionResult(payload, this._sessionInfo.senderKey, this._sessionInfo.claimedKeys);
|
||||||
results.set(event.event_id, result);
|
results.set(event.event_id, result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// ignore AbortError from cancelling decryption requests in dispose method
|
||||||
|
if (err.name === "AbortError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
errors = new Map();
|
errors = new Map();
|
||||||
}
|
}
|
||||||
|
@ -65,6 +79,12 @@ export class SessionDecryption {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
if (this._decryptionRequests) {
|
||||||
|
for (const r of this._decryptionRequests) {
|
||||||
|
r.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: cancel decryptions here
|
||||||
this._sessionInfo.release();
|
this._sessionInfo.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,8 @@ export class Room extends EventEmitter {
|
||||||
retryEntries.push(new EventEntry(storageEntry, this._fragmentIdComparer));
|
retryEntries.push(new EventEntry(storageEntry, this._fragmentIdComparer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this._decryptEntries(DecryptionSource.Retry, retryEntries, txn);
|
const decryptRequest = this._decryptEntries(DecryptionSource.Retry, retryEntries, txn);
|
||||||
|
await decryptRequest.complete();
|
||||||
if (this._timeline) {
|
if (this._timeline) {
|
||||||
// only adds if already present
|
// only adds if already present
|
||||||
this._timeline.replaceEntries(retryEntries);
|
this._timeline.replaceEntries(retryEntries);
|
||||||
|
@ -89,31 +90,39 @@ export class Room extends EventEmitter {
|
||||||
* Used for decrypting when loading/filling the timeline, and retrying decryption,
|
* Used for decrypting when loading/filling the timeline, and retrying decryption,
|
||||||
* not during sync, where it is split up during the multiple phases.
|
* not during sync, where it is split up during the multiple phases.
|
||||||
*/
|
*/
|
||||||
async _decryptEntries(source, entries, inboundSessionTxn = null) {
|
_decryptEntries(source, entries, inboundSessionTxn = null) {
|
||||||
if (!inboundSessionTxn) {
|
const request = new DecryptionRequest(async r => {
|
||||||
inboundSessionTxn = await this._storage.readTxn([this._storage.storeNames.inboundGroupSessions]);
|
if (!inboundSessionTxn) {
|
||||||
}
|
inboundSessionTxn = await this._storage.readTxn([this._storage.storeNames.inboundGroupSessions]);
|
||||||
const events = entries.filter(entry => {
|
}
|
||||||
return entry.eventType === EVENT_ENCRYPTED_TYPE;
|
if (r.cancelled) return;
|
||||||
}).map(entry => entry.event);
|
const events = entries.filter(entry => {
|
||||||
const isTimelineOpen = this._isTimelineOpen;
|
return entry.eventType === EVENT_ENCRYPTED_TYPE;
|
||||||
const preparation = await this._roomEncryption.prepareDecryptAll(events, source, isTimelineOpen, inboundSessionTxn);
|
}).map(entry => entry.event);
|
||||||
const changes = await preparation.decrypt();
|
const isTimelineOpen = this._isTimelineOpen;
|
||||||
const stores = [this._storage.storeNames.groupSessionDecryptions];
|
r.preparation = await this._roomEncryption.prepareDecryptAll(events, source, isTimelineOpen, inboundSessionTxn);
|
||||||
if (isTimelineOpen) {
|
if (r.cancelled) return;
|
||||||
// read to fetch devices if timeline is open
|
// TODO: should this throw an AbortError?
|
||||||
stores.push(this._storage.storeNames.deviceIdentities);
|
const changes = await r.preparation.decrypt();
|
||||||
}
|
r.preparation = null;
|
||||||
const writeTxn = await this._storage.readWriteTxn(stores);
|
if (r.cancelled) return;
|
||||||
let decryption;
|
const stores = [this._storage.storeNames.groupSessionDecryptions];
|
||||||
try {
|
if (isTimelineOpen) {
|
||||||
decryption = await changes.write(writeTxn);
|
// read to fetch devices if timeline is open
|
||||||
} catch (err) {
|
stores.push(this._storage.storeNames.deviceIdentities);
|
||||||
writeTxn.abort();
|
}
|
||||||
throw err;
|
const writeTxn = await this._storage.readWriteTxn(stores);
|
||||||
}
|
let decryption;
|
||||||
await writeTxn.complete();
|
try {
|
||||||
decryption.applyToEntries(entries);
|
decryption = await changes.write(writeTxn);
|
||||||
|
} catch (err) {
|
||||||
|
writeTxn.abort();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
await writeTxn.complete();
|
||||||
|
decryption.applyToEntries(entries);
|
||||||
|
});
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
get needsPrepareSync() {
|
get needsPrepareSync() {
|
||||||
|
@ -349,7 +358,8 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
await txn.complete();
|
await txn.complete();
|
||||||
if (this._roomEncryption) {
|
if (this._roomEncryption) {
|
||||||
await this._decryptEntries(DecryptionSource.Timeline, gapResult.entries);
|
const decryptRequest = this._decryptEntries(DecryptionSource.Timeline, gapResult.entries);
|
||||||
|
await decryptRequest.complete();
|
||||||
}
|
}
|
||||||
// once txn is committed, update in-memory state & emit events
|
// once txn is committed, update in-memory state & emit events
|
||||||
for (const fragment of gapResult.fragments) {
|
for (const fragment of gapResult.fragments) {
|
||||||
|
@ -461,7 +471,7 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
async openTimeline() {
|
openTimeline() {
|
||||||
if (this._timeline) {
|
if (this._timeline) {
|
||||||
throw new Error("not dealing with load race here for now");
|
throw new Error("not dealing with load race here for now");
|
||||||
}
|
}
|
||||||
|
@ -483,7 +493,6 @@ export class Room extends EventEmitter {
|
||||||
if (this._roomEncryption) {
|
if (this._roomEncryption) {
|
||||||
this._timeline.enableEncryption(this._decryptEntries.bind(this, DecryptionSource.Timeline));
|
this._timeline.enableEncryption(this._decryptEntries.bind(this, DecryptionSource.Timeline));
|
||||||
}
|
}
|
||||||
await this._timeline.load();
|
|
||||||
return this._timeline;
|
return this._timeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -502,3 +511,25 @@ export class Room extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DecryptionRequest {
|
||||||
|
constructor(decryptFn) {
|
||||||
|
this._cancelled = false;
|
||||||
|
this.preparation = null;
|
||||||
|
this._promise = decryptFn(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
complete() {
|
||||||
|
return this._promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
get cancelled() {
|
||||||
|
return this._cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._cancelled = true;
|
||||||
|
if (this.preparation) {
|
||||||
|
this.preparation.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {SortedArray, MappedList, ConcatList} from "../../../observable/index.js";
|
import {SortedArray, MappedList, ConcatList} from "../../../observable/index.js";
|
||||||
|
import {Disposables} from "../../../utils/Disposables.js";
|
||||||
import {Direction} from "./Direction.js";
|
import {Direction} from "./Direction.js";
|
||||||
import {TimelineReader} from "./persistence/TimelineReader.js";
|
import {TimelineReader} from "./persistence/TimelineReader.js";
|
||||||
import {PendingEventEntry} from "./entries/PendingEventEntry.js";
|
import {PendingEventEntry} from "./entries/PendingEventEntry.js";
|
||||||
|
@ -26,12 +27,14 @@ export class Timeline {
|
||||||
this._storage = storage;
|
this._storage = storage;
|
||||||
this._closeCallback = closeCallback;
|
this._closeCallback = closeCallback;
|
||||||
this._fragmentIdComparer = fragmentIdComparer;
|
this._fragmentIdComparer = fragmentIdComparer;
|
||||||
|
this._disposables = new Disposables();
|
||||||
this._remoteEntries = new SortedArray((a, b) => a.compare(b));
|
this._remoteEntries = new SortedArray((a, b) => a.compare(b));
|
||||||
this._timelineReader = new TimelineReader({
|
this._timelineReader = new TimelineReader({
|
||||||
roomId: this._roomId,
|
roomId: this._roomId,
|
||||||
storage: this._storage,
|
storage: this._storage,
|
||||||
fragmentIdComparer: this._fragmentIdComparer
|
fragmentIdComparer: this._fragmentIdComparer
|
||||||
});
|
});
|
||||||
|
this._readerRequest = null;
|
||||||
const localEntries = new MappedList(pendingEvents, pe => {
|
const localEntries = new MappedList(pendingEvents, pe => {
|
||||||
return new PendingEventEntry({pendingEvent: pe, user});
|
return new PendingEventEntry({pendingEvent: pe, user});
|
||||||
}, (pee, params) => {
|
}, (pee, params) => {
|
||||||
|
@ -42,8 +45,14 @@ export class Timeline {
|
||||||
|
|
||||||
/** @package */
|
/** @package */
|
||||||
async load() {
|
async load() {
|
||||||
const entries = await this._timelineReader.readFromEnd(25);
|
// 30 seems to be a good amount to fill the entire screen
|
||||||
this._remoteEntries.setManySorted(entries);
|
const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(30));
|
||||||
|
try {
|
||||||
|
const entries = await readerRequest.complete();
|
||||||
|
this._remoteEntries.setManySorted(entries);
|
||||||
|
} finally {
|
||||||
|
this._disposables.disposeTracked(readerRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceEntries(entries) {
|
replaceEntries(entries) {
|
||||||
|
@ -71,12 +80,17 @@ export class Timeline {
|
||||||
if (!firstEventEntry) {
|
if (!firstEventEntry) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const entries = await this._timelineReader.readFrom(
|
const readerRequest = this._disposables.track(this._timelineReader.readFrom(
|
||||||
firstEventEntry.asEventKey(),
|
firstEventEntry.asEventKey(),
|
||||||
Direction.Backward,
|
Direction.Backward,
|
||||||
amount
|
amount
|
||||||
);
|
));
|
||||||
this._remoteEntries.setManySorted(entries);
|
try {
|
||||||
|
const entries = await readerRequest.complete();
|
||||||
|
this._remoteEntries.setManySorted(entries);
|
||||||
|
} finally {
|
||||||
|
this._disposables.disposeTracked(readerRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
/** @public */
|
||||||
|
@ -87,6 +101,8 @@ export class Timeline {
|
||||||
/** @public */
|
/** @public */
|
||||||
close() {
|
close() {
|
||||||
if (this._closeCallback) {
|
if (this._closeCallback) {
|
||||||
|
this._readerRequest?.dispose();
|
||||||
|
this._readerRequest = null;
|
||||||
this._closeCallback();
|
this._closeCallback();
|
||||||
this._closeCallback = null;
|
this._closeCallback = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,24 @@ import {Direction} from "../Direction.js";
|
||||||
import {EventEntry} from "../entries/EventEntry.js";
|
import {EventEntry} from "../entries/EventEntry.js";
|
||||||
import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js";
|
import {FragmentBoundaryEntry} from "../entries/FragmentBoundaryEntry.js";
|
||||||
|
|
||||||
|
class ReaderRequest {
|
||||||
|
constructor(fn) {
|
||||||
|
this.decryptRequest = null;
|
||||||
|
this._promise = fn(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
complete() {
|
||||||
|
return this._promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
if (this.decryptRequest) {
|
||||||
|
this.decryptRequest.dispose();
|
||||||
|
this.decryptRequest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class TimelineReader {
|
export class TimelineReader {
|
||||||
constructor({roomId, storage, fragmentIdComparer}) {
|
constructor({roomId, storage, fragmentIdComparer}) {
|
||||||
this._roomId = roomId;
|
this._roomId = roomId;
|
||||||
|
@ -42,12 +60,33 @@ export class TimelineReader {
|
||||||
return this._storage.readTxn(stores);
|
return this._storage.readTxn(stores);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readFrom(eventKey, direction, amount) {
|
readFrom(eventKey, direction, amount) {
|
||||||
const txn = await this._openTxn();
|
return new ReaderRequest(async r => {
|
||||||
return await this._readFrom(eventKey, direction, amount, txn);
|
const txn = await this._openTxn();
|
||||||
|
return await this._readFrom(eventKey, direction, amount, r, txn);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _readFrom(eventKey, direction, amount, txn) {
|
readFromEnd(amount) {
|
||||||
|
return new ReaderRequest(async r => {
|
||||||
|
const txn = await this._openTxn();
|
||||||
|
const liveFragment = await txn.timelineFragments.liveFragment(this._roomId);
|
||||||
|
let entries;
|
||||||
|
// room hasn't been synced yet
|
||||||
|
if (!liveFragment) {
|
||||||
|
entries = [];
|
||||||
|
} else {
|
||||||
|
this._fragmentIdComparer.add(liveFragment);
|
||||||
|
const liveFragmentEntry = FragmentBoundaryEntry.end(liveFragment, this._fragmentIdComparer);
|
||||||
|
const eventKey = liveFragmentEntry.asEventKey();
|
||||||
|
entries = await this._readFrom(eventKey, Direction.Backward, amount, r, txn);
|
||||||
|
entries.unshift(liveFragmentEntry);
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _readFrom(eventKey, direction, amount, r, txn) {
|
||||||
let entries = [];
|
let entries = [];
|
||||||
const timelineStore = txn.timelineEvents;
|
const timelineStore = txn.timelineEvents;
|
||||||
const fragmentStore = txn.timelineFragments;
|
const fragmentStore = txn.timelineFragments;
|
||||||
|
@ -83,25 +122,12 @@ export class TimelineReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._decryptEntries) {
|
if (this._decryptEntries) {
|
||||||
await this._decryptEntries(entries, txn);
|
r.decryptRequest = this._decryptEntries(entries, txn);
|
||||||
}
|
try {
|
||||||
|
await r.decryptRequest.complete();
|
||||||
return entries;
|
} finally {
|
||||||
}
|
r.decryptRequest = null;
|
||||||
|
}
|
||||||
async readFromEnd(amount) {
|
|
||||||
const txn = await this._openTxn();
|
|
||||||
const liveFragment = await txn.timelineFragments.liveFragment(this._roomId);
|
|
||||||
let entries;
|
|
||||||
// room hasn't been synced yet
|
|
||||||
if (!liveFragment) {
|
|
||||||
entries = [];
|
|
||||||
} else {
|
|
||||||
this._fragmentIdComparer.add(liveFragment);
|
|
||||||
const liveFragmentEntry = FragmentBoundaryEntry.end(liveFragment, this._fragmentIdComparer);
|
|
||||||
const eventKey = liveFragmentEntry.asEventKey();
|
|
||||||
entries = await this._readFrom(eventKey, Direction.Backward, amount, txn);
|
|
||||||
entries.unshift(liveFragmentEntry);
|
|
||||||
}
|
}
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ export class Disposables {
|
||||||
|
|
||||||
track(disposable) {
|
track(disposable) {
|
||||||
this._disposables.push(disposable);
|
this._disposables.push(disposable);
|
||||||
|
return disposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
|
|
@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// polyfills needed for IE11
|
// polyfills needed for IE11
|
||||||
|
// just enough to run olm, have promises and async/await
|
||||||
import "regenerator-runtime/runtime";
|
import "regenerator-runtime/runtime";
|
||||||
import "core-js/modules/es.promise";
|
import "core-js/modules/es.promise";
|
||||||
|
import "core-js/modules/es.math.imul";
|
||||||
|
import "core-js/modules/es.math.clz32";
|
||||||
|
|
|
@ -94,13 +94,13 @@ class MessageHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handleMessage(message) {
|
async _handleMessage(message) {
|
||||||
switch (message.type) {
|
const {type} = message;
|
||||||
case "load_olm":
|
if (type === "ping") {
|
||||||
this._sendReply(message, await this._loadOlm(message.path));
|
this._sendReply(message, {type: "pong"});
|
||||||
break;
|
} else if (type === "load_olm") {
|
||||||
case "megolm_decrypt":
|
this._sendReply(message, await this._loadOlm(message.path));
|
||||||
this._sendReply(message, this._megolmDecrypt(message.sessionKey, message.ciphertext));
|
} else if (type === "megolm_decrypt") {
|
||||||
break;
|
this._sendReply(message, this._megolmDecrypt(message.sessionKey, message.ciphertext));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue