From bd3f671d438cd4cb37dac9f26ba2a1bc19592900 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 15:20:50 +0000 Subject: [PATCH 01/24] Add implementation notes --- doc/implementation planning/session-pool.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/implementation planning/session-pool.md diff --git a/doc/implementation planning/session-pool.md b/doc/implementation planning/session-pool.md new file mode 100644 index 0000000000..034fb80d0a --- /dev/null +++ b/doc/implementation planning/session-pool.md @@ -0,0 +1,3 @@ +# Session pool + +WIP From 8e15c1394f7c455f5a1a1e97855b9a55296cb863 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 15:38:25 +0000 Subject: [PATCH 02/24] Introduce SessionPool --- src/domain/RootViewModel.js | 7 ++++--- src/pool/SessionPool.ts | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/pool/SessionPool.ts diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 2896fba612..7f052951b1 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -22,6 +22,7 @@ import {LogoutViewModel} from "./LogoutViewModel"; import {ForcedLogoutViewModel} from "./ForcedLogoutViewModel"; import {SessionPickerViewModel} from "./SessionPickerViewModel.js"; import {ViewModel} from "./ViewModel"; +import {SessionPool} from "../pool/SessionPool"; export class RootViewModel extends ViewModel { constructor(options) { @@ -158,11 +159,11 @@ export class RootViewModel extends ViewModel { } _showSessionLoader(sessionId) { - const client = new Client(this.platform, this.features); - client.startWithExistingSession(sessionId); + const sessionPool = new SessionPool(this.platform, this.features); + sessionPool.startWithExistingSession(sessionId); this._setSection(() => { this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({ - client, + client: sessionPool.client, ready: client => this._showSession(client) })); this._sessionLoadViewModel.start(); diff --git a/src/pool/SessionPool.ts b/src/pool/SessionPool.ts new file mode 100644 index 0000000000..5d9f671d68 --- /dev/null +++ b/src/pool/SessionPool.ts @@ -0,0 +1,20 @@ +import {Platform} from "../platform/web/Platform"; +import {FeatureSet} from "../features"; +import {Client} from "../matrix/Client"; + +export class SessionPool { + private readonly _client: Client; + + constructor(platform: Platform, features: FeatureSet) { + this._client = new Client(platform, features); + } + + // TODO REFACTOR: Remove this method since client should not be exposed. + get client(): Client { + return this._client; + } + + startWithExistingSession(sessionId: string) { + this._client.startWithExistingSession(sessionId); + } +} From 9f78416e107f275f28861382d122535858cd0225 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 15:41:54 +0000 Subject: [PATCH 03/24] Inject SessionPool into SessionViewModel --- src/domain/RootViewModel.js | 2 +- src/domain/SessionLoadViewModel.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 7f052951b1..2dc1d0c758 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -163,7 +163,7 @@ export class RootViewModel extends ViewModel { sessionPool.startWithExistingSession(sessionId); this._setSection(() => { this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({ - client: sessionPool.client, + sessionPool: sessionPool, ready: client => this._showSession(client) })); this._sessionLoadViewModel.start(); diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 6a63145f4a..a0a675b443 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -22,8 +22,9 @@ import {ViewModel} from "./ViewModel"; export class SessionLoadViewModel extends ViewModel { constructor(options) { super(options); - const {client, ready, homeserver, deleteSessionOnCancel} = options; - this._client = client; + const {sessionPool, ready, homeserver, deleteSessionOnCancel} = options; + // TODO REFACTOR: Remove this._client, all operations should be done through the session pool. + this._client = sessionPool.client; this._ready = ready; this._homeserver = homeserver; this._deleteSessionOnCancel = deleteSessionOnCancel; From 7beaee74f6c5628edc6f6067461650a065e72e64 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 15:54:22 +0000 Subject: [PATCH 04/24] Pass sessionId when retrieving client --- src/domain/RootViewModel.js | 1 + src/domain/SessionLoadViewModel.js | 4 ++-- src/pool/SessionPool.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 2dc1d0c758..5445cb2c26 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -163,6 +163,7 @@ export class RootViewModel extends ViewModel { sessionPool.startWithExistingSession(sessionId); this._setSection(() => { this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({ + sessionId, sessionPool: sessionPool, ready: client => this._showSession(client) })); diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index a0a675b443..d8368777fb 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -22,9 +22,9 @@ import {ViewModel} from "./ViewModel"; export class SessionLoadViewModel extends ViewModel { constructor(options) { super(options); - const {sessionPool, ready, homeserver, deleteSessionOnCancel} = options; + const {sessionId, sessionPool, ready, homeserver, deleteSessionOnCancel} = options; // TODO REFACTOR: Remove this._client, all operations should be done through the session pool. - this._client = sessionPool.client; + this._client = sessionPool.client(sessionId); this._ready = ready; this._homeserver = homeserver; this._deleteSessionOnCancel = deleteSessionOnCancel; diff --git a/src/pool/SessionPool.ts b/src/pool/SessionPool.ts index 5d9f671d68..a14cb06717 100644 --- a/src/pool/SessionPool.ts +++ b/src/pool/SessionPool.ts @@ -10,7 +10,7 @@ export class SessionPool { } // TODO REFACTOR: Remove this method since client should not be exposed. - get client(): Client { + client(sessionId: string): Client { return this._client; } From a578409c747253f656a52b7390b462b7d6ad7d2a Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 16:02:00 +0000 Subject: [PATCH 05/24] Make SessionPool capable of holding clients for multiple sessions --- src/pool/SessionPool.ts | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/pool/SessionPool.ts b/src/pool/SessionPool.ts index a14cb06717..0079527e38 100644 --- a/src/pool/SessionPool.ts +++ b/src/pool/SessionPool.ts @@ -2,19 +2,33 @@ import {Platform} from "../platform/web/Platform"; import {FeatureSet} from "../features"; import {Client} from "../matrix/Client"; +type SessionId = string; + export class SessionPool { - private readonly _client: Client; + private readonly _clients: Map = new Map; + private readonly _platform: Platform; + private readonly _features: FeatureSet; constructor(platform: Platform, features: FeatureSet) { - this._client = new Client(platform, features); + this._platform = platform; + this._features = features; } - // TODO REFACTOR: Remove this method since client should not be exposed. - client(sessionId: string): Client { - return this._client; + // TODO REFACTOR: Make this method private since client should not be exposed. + client(sessionId: SessionId): Client { + if (!this._clients.has(sessionId)) { + throw `Session with id ${sessionId} not found in pool`; + } + + return this._clients.get(sessionId); } - startWithExistingSession(sessionId: string) { - this._client.startWithExistingSession(sessionId); + startWithExistingSession(sessionId: SessionId) { + if (this._clients.has(sessionId)) { + throw `Session with id ${sessionId} is already in pool`; + } + const client = new Client(this._platform, this._features); + this._clients.set(sessionId, client); + client.startWithExistingSession(sessionId); } } From d7d63f8b617dfbc9e1930c963d1dcc11742fb763 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 16:32:02 +0000 Subject: [PATCH 06/24] Implement loadStatus in SessionPool --- src/domain/SessionLoadViewModel.js | 4 +++- src/pool/SessionPool.ts | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index d8368777fb..33fca3e41d 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -23,6 +23,8 @@ export class SessionLoadViewModel extends ViewModel { constructor(options) { super(options); const {sessionId, sessionPool, ready, homeserver, deleteSessionOnCancel} = options; + this._sessionId = sessionId; + this._sessionPool = sessionPool; // TODO REFACTOR: Remove this._client, all operations should be done through the session pool. this._client = sessionPool.client(sessionId); this._ready = ready; @@ -42,7 +44,7 @@ export class SessionLoadViewModel extends ViewModel { try { this._loading = true; this.emitChange("loading"); - this._waitHandle = this._client.loadStatus.waitFor(s => { + this._waitHandle = this._sessionPool.loadStatus(this._sessionId).waitFor(s => { if (s === LoadStatus.AccountSetup) { this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._client.accountSetup})); } else { diff --git a/src/pool/SessionPool.ts b/src/pool/SessionPool.ts index 0079527e38..227e3ec640 100644 --- a/src/pool/SessionPool.ts +++ b/src/pool/SessionPool.ts @@ -1,6 +1,7 @@ import {Platform} from "../platform/web/Platform"; import {FeatureSet} from "../features"; -import {Client} from "../matrix/Client"; +import {Client, LoadStatus} from "../matrix/Client"; +import {ObservableValue} from "../observable/value"; type SessionId = string; @@ -31,4 +32,8 @@ export class SessionPool { this._clients.set(sessionId, client); client.startWithExistingSession(sessionId); } + + loadStatus(sessionId: SessionId): ObservableValue { + return this.client(sessionId).loadStatus; + } } From 2ff595241dfac88a064d3bc3734130fb828ea0d5 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 16:43:41 +0000 Subject: [PATCH 07/24] Retrieve AccountSetup from SessionPool --- src/domain/SessionLoadViewModel.js | 3 +-- src/matrix/Client.js | 2 +- src/pool/SessionPool.ts | 6 +++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 33fca3e41d..74af7714d8 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -34,7 +34,6 @@ export class SessionLoadViewModel extends ViewModel { this._error = null; this.backUrl = this.urlRouter.urlForSegment("session", true); this._accountSetupViewModel = undefined; - } async start() { @@ -46,7 +45,7 @@ export class SessionLoadViewModel extends ViewModel { this.emitChange("loading"); this._waitHandle = this._sessionPool.loadStatus(this._sessionId).waitFor(s => { if (s === LoadStatus.AccountSetup) { - this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._client.accountSetup})); + this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._sessionPool.accountSetup(this._sessionId)})); } else { this._accountSetupViewModel = undefined; } diff --git a/src/matrix/Client.js b/src/matrix/Client.js index fabb489b67..b8aad22fc1 100644 --- a/src/matrix/Client.js +++ b/src/matrix/Client.js @@ -502,7 +502,7 @@ export class Client { } } -class AccountSetup { +export class AccountSetup { constructor(encryptedDehydratedDevice, finishStage) { this._encryptedDehydratedDevice = encryptedDehydratedDevice; this._dehydratedDevice = undefined; diff --git a/src/pool/SessionPool.ts b/src/pool/SessionPool.ts index 227e3ec640..c15707460a 100644 --- a/src/pool/SessionPool.ts +++ b/src/pool/SessionPool.ts @@ -1,6 +1,6 @@ import {Platform} from "../platform/web/Platform"; import {FeatureSet} from "../features"; -import {Client, LoadStatus} from "../matrix/Client"; +import {AccountSetup, Client, LoadStatus} from "../matrix/Client"; import {ObservableValue} from "../observable/value"; type SessionId = string; @@ -36,4 +36,8 @@ export class SessionPool { loadStatus(sessionId: SessionId): ObservableValue { return this.client(sessionId).loadStatus; } + + accountSetup(sessionId: SessionId): AccountSetup { + return this.client(sessionId).accountSetup; + } } From d423708b2dfffc4fe6cf4af468b80c52a10d734f Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 16:49:58 +0000 Subject: [PATCH 08/24] Retrieve sync status from SessionPool --- src/domain/SessionLoadViewModel.js | 2 +- src/pool/SessionPool.ts | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 74af7714d8..0cb23b216a 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -52,7 +52,7 @@ export class SessionLoadViewModel extends ViewModel { this.emitChange("loadLabel"); // wait for initial sync, but not catchup sync const isCatchupSync = s === LoadStatus.FirstSync && - this._client.sync.status.get() === SyncStatus.CatchupSync; + this._sessionPool.syncStatus(this._sessionId).get() === SyncStatus.CatchupSync; return isCatchupSync || s === LoadStatus.LoginFailed || s === LoadStatus.Error || diff --git a/src/pool/SessionPool.ts b/src/pool/SessionPool.ts index c15707460a..89cde316ff 100644 --- a/src/pool/SessionPool.ts +++ b/src/pool/SessionPool.ts @@ -2,6 +2,7 @@ import {Platform} from "../platform/web/Platform"; import {FeatureSet} from "../features"; import {AccountSetup, Client, LoadStatus} from "../matrix/Client"; import {ObservableValue} from "../observable/value"; +import {SyncStatus} from "../matrix/Sync"; type SessionId = string; @@ -40,4 +41,8 @@ export class SessionPool { accountSetup(sessionId: SessionId): AccountSetup { return this.client(sessionId).accountSetup; } + + syncStatus(sessionId: SessionId): ObservableValue { + return this.client(sessionId).sync.status; + } } From 37f69036920ad733ffbe1886032355a847676898 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 17:00:31 +0000 Subject: [PATCH 09/24] Retrieve loadStatus from session pool everywhere in SessionLoadViewModel --- src/domain/SessionLoadViewModel.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 0cb23b216a..6af5afa2a1 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -69,7 +69,7 @@ export class SessionLoadViewModel extends ViewModel { // much like we will once you are in the app. Probably a good idea // did it finish or get stuck at LoginFailed or Error? - const loadStatus = this._client.loadStatus.get(); + const loadStatus = this._sessionPool(this._sessionId).loadStatus.get(); const loadError = this._client.loadError; if (loadStatus === LoadStatus.FirstSync || loadStatus === LoadStatus.Ready) { const client = this._client; @@ -108,7 +108,7 @@ export class SessionLoadViewModel extends ViewModel { // to show a spinner or not get loading() { const client = this._client; - if (client && client.loadStatus.get() === LoadStatus.AccountSetup) { + if (client && this._sessionPool(this._sessionId).loadStatus.get() === LoadStatus.AccountSetup) { return false; } return this._loading; @@ -117,13 +117,13 @@ export class SessionLoadViewModel extends ViewModel { get loadLabel() { const client = this._client; const error = this._getError(); - if (error || (client && client.loadStatus.get() === LoadStatus.Error)) { + if (error || (client && this._sessionPool(this._sessionId).loadStatus.get() === LoadStatus.Error)) { return `Something went wrong: ${error && error.message}.`; } // Statuses related to login are handled by respective login view models if (client) { - switch (client.loadStatus.get()) { + switch (this._sessionPool(this._sessionId).loadStatus.get()) { case LoadStatus.QueryAccount: return `Querying account encryption setup…`; case LoadStatus.AccountSetup: @@ -135,7 +135,7 @@ export class SessionLoadViewModel extends ViewModel { case LoadStatus.FirstSync: return `Getting your conversations from the server…`; default: - return this._client.loadStatus.get(); + return this._sessionPool(this._sessionId).loadStatus.get(); } } From c2b3d0cc802ecaa9c6a512d1a5d4b8c1cfacd2f0 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 17:03:12 +0000 Subject: [PATCH 10/24] Retrieve loadError from session pool everywhere in SessionLoadViewModel --- src/domain/SessionLoadViewModel.js | 4 ++-- src/pool/SessionPool.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 6af5afa2a1..fa1f275693 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -70,7 +70,7 @@ export class SessionLoadViewModel extends ViewModel { // did it finish or get stuck at LoginFailed or Error? const loadStatus = this._sessionPool(this._sessionId).loadStatus.get(); - const loadError = this._client.loadError; + const loadError = this._sessionPool(this._sessionId).loadError; if (loadStatus === LoadStatus.FirstSync || loadStatus === LoadStatus.Ready) { const client = this._client; // session container is ready, @@ -143,7 +143,7 @@ export class SessionLoadViewModel extends ViewModel { } _getError() { - return this._error || this._client?.loadError; + return this._error || this._sessionPool(this._sessionId).loadError; } get hasError() { diff --git a/src/pool/SessionPool.ts b/src/pool/SessionPool.ts index 89cde316ff..415b5312f6 100644 --- a/src/pool/SessionPool.ts +++ b/src/pool/SessionPool.ts @@ -38,6 +38,10 @@ export class SessionPool { return this.client(sessionId).loadStatus; } + loadError(sessionId: SessionId): Error { + return this.client(sessionId).loadError; + } + accountSetup(sessionId: SessionId): AccountSetup { return this.client(sessionId).accountSetup; } From 01d15f43a2155649881aafa6cfff3d0825d19837 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 17:37:19 +0000 Subject: [PATCH 11/24] Rename SessionPool to ClientPool --- src/domain/RootViewModel.js | 8 +++---- src/domain/SessionLoadViewModel.js | 26 +++++++++++----------- src/pool/{SessionPool.ts => ClientPool.ts} | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) rename src/pool/{SessionPool.ts => ClientPool.ts} (98%) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 5445cb2c26..e3d4463276 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -22,7 +22,7 @@ import {LogoutViewModel} from "./LogoutViewModel"; import {ForcedLogoutViewModel} from "./ForcedLogoutViewModel"; import {SessionPickerViewModel} from "./SessionPickerViewModel.js"; import {ViewModel} from "./ViewModel"; -import {SessionPool} from "../pool/SessionPool"; +import {ClientPool} from "../pool/ClientPool"; export class RootViewModel extends ViewModel { constructor(options) { @@ -159,12 +159,12 @@ export class RootViewModel extends ViewModel { } _showSessionLoader(sessionId) { - const sessionPool = new SessionPool(this.platform, this.features); - sessionPool.startWithExistingSession(sessionId); + const clientPool = new ClientPool(this.platform, this.features); + clientPool.startWithExistingSession(sessionId); this._setSection(() => { this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({ sessionId, - sessionPool: sessionPool, + clientPool: clientPool, ready: client => this._showSession(client) })); this._sessionLoadViewModel.start(); diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index fa1f275693..32f780b5cb 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -22,11 +22,11 @@ import {ViewModel} from "./ViewModel"; export class SessionLoadViewModel extends ViewModel { constructor(options) { super(options); - const {sessionId, sessionPool, ready, homeserver, deleteSessionOnCancel} = options; + const {sessionId, clientPool, ready, homeserver, deleteSessionOnCancel} = options; this._sessionId = sessionId; - this._sessionPool = sessionPool; + this._clientPool = clientPool; // TODO REFACTOR: Remove this._client, all operations should be done through the session pool. - this._client = sessionPool.client(sessionId); + this._client = clientPool.client(sessionId); this._ready = ready; this._homeserver = homeserver; this._deleteSessionOnCancel = deleteSessionOnCancel; @@ -43,16 +43,16 @@ export class SessionLoadViewModel extends ViewModel { try { this._loading = true; this.emitChange("loading"); - this._waitHandle = this._sessionPool.loadStatus(this._sessionId).waitFor(s => { + this._waitHandle = this._clientPool.loadStatus(this._sessionId).waitFor(s => { if (s === LoadStatus.AccountSetup) { - this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._sessionPool.accountSetup(this._sessionId)})); + this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._clientPool.accountSetup(this._sessionId)})); } else { this._accountSetupViewModel = undefined; } this.emitChange("loadLabel"); // wait for initial sync, but not catchup sync const isCatchupSync = s === LoadStatus.FirstSync && - this._sessionPool.syncStatus(this._sessionId).get() === SyncStatus.CatchupSync; + this._clientPool.syncStatus(this._sessionId).get() === SyncStatus.CatchupSync; return isCatchupSync || s === LoadStatus.LoginFailed || s === LoadStatus.Error || @@ -69,8 +69,8 @@ export class SessionLoadViewModel extends ViewModel { // much like we will once you are in the app. Probably a good idea // did it finish or get stuck at LoginFailed or Error? - const loadStatus = this._sessionPool(this._sessionId).loadStatus.get(); - const loadError = this._sessionPool(this._sessionId).loadError; + const loadStatus = this._clientPool(this._sessionId).loadStatus.get(); + const loadError = this._clientPool(this._sessionId).loadError; if (loadStatus === LoadStatus.FirstSync || loadStatus === LoadStatus.Ready) { const client = this._client; // session container is ready, @@ -108,7 +108,7 @@ export class SessionLoadViewModel extends ViewModel { // to show a spinner or not get loading() { const client = this._client; - if (client && this._sessionPool(this._sessionId).loadStatus.get() === LoadStatus.AccountSetup) { + if (client && this._clientPool(this._sessionId).loadStatus.get() === LoadStatus.AccountSetup) { return false; } return this._loading; @@ -117,13 +117,13 @@ export class SessionLoadViewModel extends ViewModel { get loadLabel() { const client = this._client; const error = this._getError(); - if (error || (client && this._sessionPool(this._sessionId).loadStatus.get() === LoadStatus.Error)) { + if (error || (client && this._clientPool(this._sessionId).loadStatus.get() === LoadStatus.Error)) { return `Something went wrong: ${error && error.message}.`; } // Statuses related to login are handled by respective login view models if (client) { - switch (this._sessionPool(this._sessionId).loadStatus.get()) { + switch (this._clientPool(this._sessionId).loadStatus.get()) { case LoadStatus.QueryAccount: return `Querying account encryption setup…`; case LoadStatus.AccountSetup: @@ -135,7 +135,7 @@ export class SessionLoadViewModel extends ViewModel { case LoadStatus.FirstSync: return `Getting your conversations from the server…`; default: - return this._sessionPool(this._sessionId).loadStatus.get(); + return this._clientPool(this._sessionId).loadStatus.get(); } } @@ -143,7 +143,7 @@ export class SessionLoadViewModel extends ViewModel { } _getError() { - return this._error || this._sessionPool(this._sessionId).loadError; + return this._error || this._clientPool(this._sessionId).loadError; } get hasError() { diff --git a/src/pool/SessionPool.ts b/src/pool/ClientPool.ts similarity index 98% rename from src/pool/SessionPool.ts rename to src/pool/ClientPool.ts index 415b5312f6..98b507ad67 100644 --- a/src/pool/SessionPool.ts +++ b/src/pool/ClientPool.ts @@ -6,7 +6,7 @@ import {SyncStatus} from "../matrix/Sync"; type SessionId = string; -export class SessionPool { +export class ClientPool { private readonly _clients: Map = new Map; private readonly _platform: Platform; private readonly _features: FeatureSet; From 3454822e9e75553694c4b319fd861b39849380b9 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 17:38:58 +0000 Subject: [PATCH 12/24] Add ClientProxy --- src/pool/ClientPool.ts | 2 +- src/pool/ClientProxy.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/pool/ClientProxy.ts diff --git a/src/pool/ClientPool.ts b/src/pool/ClientPool.ts index 98b507ad67..538a3da8e6 100644 --- a/src/pool/ClientPool.ts +++ b/src/pool/ClientPool.ts @@ -4,7 +4,7 @@ import {AccountSetup, Client, LoadStatus} from "../matrix/Client"; import {ObservableValue} from "../observable/value"; import {SyncStatus} from "../matrix/Sync"; -type SessionId = string; +export type SessionId = string; export class ClientPool { private readonly _clients: Map = new Map; diff --git a/src/pool/ClientProxy.ts b/src/pool/ClientProxy.ts new file mode 100644 index 0000000000..1197dfda01 --- /dev/null +++ b/src/pool/ClientProxy.ts @@ -0,0 +1,17 @@ +import {ClientPool, SessionId} from "./ClientPool"; +import {Client} from "../matrix/Client"; + +export class ClientProxy { + private readonly _sessionId: SessionId; + private readonly _clientPool: ClientPool; + + constructor(sessionId: SessionId, clientPool: ClientPool) { + this._sessionId = sessionId; + this._clientPool = clientPool; + } + + // TODO REFACTOR: Make this method private since client should not be exposed. + public get client(): Client { + return this._clientPool.client(this._sessionId); + } +} From 559b9ca679199253a337f4e0bf39ae7c2fbfbe84 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 18:13:38 +0000 Subject: [PATCH 13/24] Use ClientProxy instead of ClientPool --- src/domain/RootViewModel.js | 7 +++--- src/domain/SessionLoadViewModel.js | 29 +++++++++++------------ src/pool/ClientPool.ts | 38 +++++++----------------------- src/pool/ClientProxy.ts | 20 +++++++++++++++- 4 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index e3d4463276..a009c321c0 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -159,12 +159,11 @@ export class RootViewModel extends ViewModel { } _showSessionLoader(sessionId) { - const clientPool = new ClientPool(this.platform, this.features); - clientPool.startWithExistingSession(sessionId); + this._clientPool = new ClientPool(this.platform, this.features); + const clientProxy = this._clientPool.loadSession(sessionId); this._setSection(() => { this._sessionLoadViewModel = new SessionLoadViewModel(this.childOptions({ - sessionId, - clientPool: clientPool, + clientProxy, ready: client => this._showSession(client) })); this._sessionLoadViewModel.start(); diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 32f780b5cb..9c8ce5d83e 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -22,11 +22,10 @@ import {ViewModel} from "./ViewModel"; export class SessionLoadViewModel extends ViewModel { constructor(options) { super(options); - const {sessionId, clientPool, ready, homeserver, deleteSessionOnCancel} = options; - this._sessionId = sessionId; - this._clientPool = clientPool; - // TODO REFACTOR: Remove this._client, all operations should be done through the session pool. - this._client = clientPool.client(sessionId); + const {clientProxy, ready, homeserver, deleteSessionOnCancel} = options; + this._clientProxy = clientProxy; + // TODO REFACTOR: Remove this._client, all operations should be done through the proxy. + this._client = clientProxy.client; this._ready = ready; this._homeserver = homeserver; this._deleteSessionOnCancel = deleteSessionOnCancel; @@ -43,16 +42,16 @@ export class SessionLoadViewModel extends ViewModel { try { this._loading = true; this.emitChange("loading"); - this._waitHandle = this._clientPool.loadStatus(this._sessionId).waitFor(s => { + this._waitHandle = this._clientProxy.loadStatus().waitFor(s => { if (s === LoadStatus.AccountSetup) { - this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._clientPool.accountSetup(this._sessionId)})); + this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._clientProxy.accountSetup()})); } else { this._accountSetupViewModel = undefined; } this.emitChange("loadLabel"); // wait for initial sync, but not catchup sync const isCatchupSync = s === LoadStatus.FirstSync && - this._clientPool.syncStatus(this._sessionId).get() === SyncStatus.CatchupSync; + this._clientProxy.syncStatus().get() === SyncStatus.CatchupSync; return isCatchupSync || s === LoadStatus.LoginFailed || s === LoadStatus.Error || @@ -69,8 +68,8 @@ export class SessionLoadViewModel extends ViewModel { // much like we will once you are in the app. Probably a good idea // did it finish or get stuck at LoginFailed or Error? - const loadStatus = this._clientPool(this._sessionId).loadStatus.get(); - const loadError = this._clientPool(this._sessionId).loadError; + const loadStatus = this._clientProxy.loadStatus().get(); + const loadError = this._clientProxy.loadError(); if (loadStatus === LoadStatus.FirstSync || loadStatus === LoadStatus.Ready) { const client = this._client; // session container is ready, @@ -108,7 +107,7 @@ export class SessionLoadViewModel extends ViewModel { // to show a spinner or not get loading() { const client = this._client; - if (client && this._clientPool(this._sessionId).loadStatus.get() === LoadStatus.AccountSetup) { + if (client && this._clientProxy.loadStatus().get() === LoadStatus.AccountSetup) { return false; } return this._loading; @@ -117,13 +116,13 @@ export class SessionLoadViewModel extends ViewModel { get loadLabel() { const client = this._client; const error = this._getError(); - if (error || (client && this._clientPool(this._sessionId).loadStatus.get() === LoadStatus.Error)) { + if (error || (client && this._clientProxy.loadStatus().get() === LoadStatus.Error)) { return `Something went wrong: ${error && error.message}.`; } // Statuses related to login are handled by respective login view models if (client) { - switch (this._clientPool(this._sessionId).loadStatus.get()) { + switch (this._clientProxy.loadStatus().get()) { case LoadStatus.QueryAccount: return `Querying account encryption setup…`; case LoadStatus.AccountSetup: @@ -135,7 +134,7 @@ export class SessionLoadViewModel extends ViewModel { case LoadStatus.FirstSync: return `Getting your conversations from the server…`; default: - return this._clientPool(this._sessionId).loadStatus.get(); + return this._clientProxy.loadStatus().get(); } } @@ -143,7 +142,7 @@ export class SessionLoadViewModel extends ViewModel { } _getError() { - return this._error || this._clientPool(this._sessionId).loadError; + return this._error || this._clientProxy.loadError(); } get hasError() { diff --git a/src/pool/ClientPool.ts b/src/pool/ClientPool.ts index 538a3da8e6..dfdf2de5c1 100644 --- a/src/pool/ClientPool.ts +++ b/src/pool/ClientPool.ts @@ -1,8 +1,7 @@ import {Platform} from "../platform/web/Platform"; import {FeatureSet} from "../features"; -import {AccountSetup, Client, LoadStatus} from "../matrix/Client"; -import {ObservableValue} from "../observable/value"; -import {SyncStatus} from "../matrix/Sync"; +import {Client} from "../matrix/Client"; +import {ClientProxy} from "./ClientProxy"; export type SessionId = string; @@ -16,37 +15,18 @@ export class ClientPool { this._features = features; } - // TODO REFACTOR: Make this method private since client should not be exposed. - client(sessionId: SessionId): Client { - if (!this._clients.has(sessionId)) { - throw `Session with id ${sessionId} not found in pool`; - } - - return this._clients.get(sessionId); - } - - startWithExistingSession(sessionId: SessionId) { - if (this._clients.has(sessionId)) { - throw `Session with id ${sessionId} is already in pool`; - } + loadSession(sessionId: SessionId): ClientProxy { const client = new Client(this._platform, this._features); this._clients.set(sessionId, client); - client.startWithExistingSession(sessionId); - } - loadStatus(sessionId: SessionId): ObservableValue { - return this.client(sessionId).loadStatus; - } - - loadError(sessionId: SessionId): Error { - return this.client(sessionId).loadError; - } + // TODO REFACTOR: Handle case where session doesn't yet exist. + client.startWithExistingSession(sessionId); - accountSetup(sessionId: SessionId): AccountSetup { - return this.client(sessionId).accountSetup; + return new ClientProxy(sessionId, this); } - syncStatus(sessionId: SessionId): ObservableValue { - return this.client(sessionId).sync.status; + // TODO REFACTOR: Make this method private since client should not be exposed. + client(sessionId: SessionId): Client | undefined { + return this._clients.get(sessionId); } } diff --git a/src/pool/ClientProxy.ts b/src/pool/ClientProxy.ts index 1197dfda01..59c8701880 100644 --- a/src/pool/ClientProxy.ts +++ b/src/pool/ClientProxy.ts @@ -1,5 +1,7 @@ import {ClientPool, SessionId} from "./ClientPool"; -import {Client} from "../matrix/Client"; +import {AccountSetup, Client, LoadStatus} from "../matrix/Client"; +import {SyncStatus} from "../matrix/Sync"; +import {ObservableValue} from "../observable/value"; export class ClientProxy { private readonly _sessionId: SessionId; @@ -14,4 +16,20 @@ export class ClientProxy { public get client(): Client { return this._clientPool.client(this._sessionId); } + + loadStatus(): ObservableValue { + return this.client.loadStatus; + } + + loadError(): Error { + return this.client.loadError; + } + + accountSetup(): AccountSetup { + return this.client.accountSetup; + } + + syncStatus(): ObservableValue { + return this.client.sync.status; + } } From 24f45b082be22e5b8ef494766f1120ca72d88d38 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 18:18:13 +0000 Subject: [PATCH 14/24] Use ClientProxy instead of ClientPool --- src/domain/SessionLoadViewModel.js | 20 ++++++++++---------- src/pool/ClientProxy.ts | 4 ++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 9c8ce5d83e..989b9840ad 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -93,9 +93,9 @@ export class SessionLoadViewModel extends ViewModel { dispose() { - if (this._client) { - this._client.dispose(); - this._client = null; + if (this._clientProxy) { + this._clientProxy.dispose(); + this._clientProxy = null; } if (this._waitHandle) { // rejects with AbortError @@ -106,23 +106,23 @@ export class SessionLoadViewModel extends ViewModel { // to show a spinner or not get loading() { - const client = this._client; - if (client && this._clientProxy.loadStatus().get() === LoadStatus.AccountSetup) { + const clientProxy = this._clientProxy; + if (clientProxy && clientProxy.loadStatus().get() === LoadStatus.AccountSetup) { return false; } return this._loading; } get loadLabel() { - const client = this._client; + const clientProxy = this._clientProxy; const error = this._getError(); - if (error || (client && this._clientProxy.loadStatus().get() === LoadStatus.Error)) { + if (error || (clientProxy && clientProxy.loadStatus().get() === LoadStatus.Error)) { return `Something went wrong: ${error && error.message}.`; } // Statuses related to login are handled by respective login view models - if (client) { - switch (this._clientProxy.loadStatus().get()) { + if (clientProxy) { + switch (clientProxy.loadStatus().get()) { case LoadStatus.QueryAccount: return `Querying account encryption setup…`; case LoadStatus.AccountSetup: @@ -134,7 +134,7 @@ export class SessionLoadViewModel extends ViewModel { case LoadStatus.FirstSync: return `Getting your conversations from the server…`; default: - return this._clientProxy.loadStatus().get(); + return clientProxy.loadStatus().get(); } } diff --git a/src/pool/ClientProxy.ts b/src/pool/ClientProxy.ts index 59c8701880..fdeaf63f78 100644 --- a/src/pool/ClientProxy.ts +++ b/src/pool/ClientProxy.ts @@ -32,4 +32,8 @@ export class ClientProxy { syncStatus(): ObservableValue { return this.client.sync.status; } + + dispose() { + this.client.dispose(); + } } From 9cd723210001ae5a834e6295195d76ef043b730b Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 18:20:27 +0000 Subject: [PATCH 15/24] Add startLogout() to ClientProxy --- src/domain/SessionLoadViewModel.js | 2 +- src/pool/ClientProxy.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 989b9840ad..a4fe3b03f6 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -155,7 +155,7 @@ export class SessionLoadViewModel extends ViewModel { } async logout() { - await this._client.startLogout(this.navigation.path.get("session").value); + await this._clientProxy.startLogout(this.navigation.path.get("session").value); this.navigation.push("session", true); } diff --git a/src/pool/ClientProxy.ts b/src/pool/ClientProxy.ts index fdeaf63f78..fd3813ccfd 100644 --- a/src/pool/ClientProxy.ts +++ b/src/pool/ClientProxy.ts @@ -33,6 +33,10 @@ export class ClientProxy { return this.client.sync.status; } + startLogout() { + return this.client.startLogout(this._sessionId); + } + dispose() { this.client.dispose(); } From 7bf6acf08c9e06d7611d77dc6898f1df88e2d1cc Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 18:23:51 +0000 Subject: [PATCH 16/24] No longer rely on Client in SessionLoadViewModel --- src/domain/SessionLoadViewModel.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index a4fe3b03f6..af1a9a8eb3 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -24,8 +24,6 @@ export class SessionLoadViewModel extends ViewModel { super(options); const {clientProxy, ready, homeserver, deleteSessionOnCancel} = options; this._clientProxy = clientProxy; - // TODO REFACTOR: Remove this._client, all operations should be done through the proxy. - this._client = clientProxy.client; this._ready = ready; this._homeserver = homeserver; this._deleteSessionOnCancel = deleteSessionOnCancel; @@ -71,11 +69,11 @@ export class SessionLoadViewModel extends ViewModel { const loadStatus = this._clientProxy.loadStatus().get(); const loadError = this._clientProxy.loadError(); if (loadStatus === LoadStatus.FirstSync || loadStatus === LoadStatus.Ready) { - const client = this._client; + const client = this._clientProxy.client; // session container is ready, // don't dispose it anymore when // we get disposed - this._client = null; + this._clientProxy = null; this._ready(client); } if (loadError) { From 6071901f00010bb45d45c97b88040c44122b8d21 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Tue, 14 Feb 2023 18:49:55 +0000 Subject: [PATCH 17/24] Rename doc --- doc/implementation planning/client-pool.md | 3 +++ doc/implementation planning/session-pool.md | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 doc/implementation planning/client-pool.md delete mode 100644 doc/implementation planning/session-pool.md diff --git a/doc/implementation planning/client-pool.md b/doc/implementation planning/client-pool.md new file mode 100644 index 0000000000..9dd91726f1 --- /dev/null +++ b/doc/implementation planning/client-pool.md @@ -0,0 +1,3 @@ +# Client pool + +WIP diff --git a/doc/implementation planning/session-pool.md b/doc/implementation planning/session-pool.md deleted file mode 100644 index 034fb80d0a..0000000000 --- a/doc/implementation planning/session-pool.md +++ /dev/null @@ -1,3 +0,0 @@ -# Session pool - -WIP From 2b1d56725bfc6577130eceece5f5980be4fa07cd Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 15 Feb 2023 11:57:18 +0000 Subject: [PATCH 18/24] Add SyncInWorker --- src/matrix/Client.js | 3 ++- src/matrix/SyncInWorker.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/matrix/SyncInWorker.ts diff --git a/src/matrix/Client.js b/src/matrix/Client.js index b8aad22fc1..d3354d6919 100644 --- a/src/matrix/Client.js +++ b/src/matrix/Client.js @@ -32,6 +32,7 @@ import {SSOLoginHelper} from "./login/SSOLoginHelper"; import {getDehydratedDevice} from "./e2ee/Dehydration.js"; import {Registration} from "./registration/Registration"; import {FeatureSet} from "../features"; +import {SyncInWorker} from "./SyncInWorker"; export const LoadStatus = createEnum( "NotLoading", @@ -291,7 +292,7 @@ export class Client { await log.wrap("createIdentity", log => this._session.createIdentity(log)); } - this._sync = new Sync({hsApi: this._requestScheduler.hsApi, storage: this._storage, session: this._session, logger: this._platform.logger}); + this._sync = new SyncInWorker({hsApi: this._requestScheduler.hsApi, storage: this._storage, session: this._session, logger: this._platform.logger}); // notify sync and session when back online this._reconnectSubscription = this._reconnector.connectionStatus.subscribe(state => { if (state === ConnectionStatus.Online) { diff --git a/src/matrix/SyncInWorker.ts b/src/matrix/SyncInWorker.ts new file mode 100644 index 0000000000..70dda00e15 --- /dev/null +++ b/src/matrix/SyncInWorker.ts @@ -0,0 +1,18 @@ +import {Sync} from "./Sync"; +import {HomeServerApi} from "./net/HomeServerApi"; +import {Session} from "./Session"; +import {Storage} from "./storage/idb/Storage"; +import {Logger} from "../logging/Logger"; + +interface SyncOptions { + hsApi: HomeServerApi, + session: Session, + storage: Storage, + logger: Logger +} + +export class SyncInWorker extends Sync { + constructor(options: SyncOptions) { + super(options); + } +} From 8a3a56ab34554a171a11c5c7582898d164800cff Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 15 Feb 2023 12:12:26 +0000 Subject: [PATCH 19/24] Override public methods --- src/matrix/SyncInWorker.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/matrix/SyncInWorker.ts b/src/matrix/SyncInWorker.ts index 70dda00e15..8c3648bda5 100644 --- a/src/matrix/SyncInWorker.ts +++ b/src/matrix/SyncInWorker.ts @@ -1,4 +1,4 @@ -import {Sync} from "./Sync"; +import {Sync, SyncStatus} from "./Sync"; import {HomeServerApi} from "./net/HomeServerApi"; import {Session} from "./Session"; import {Storage} from "./storage/idb/Storage"; @@ -15,4 +15,20 @@ export class SyncInWorker extends Sync { constructor(options: SyncOptions) { super(options); } + + get status(): SyncStatus { + return super.status; + } + + get error(): Error { + return super.error; + } + + start(): void { + super.start(); + } + + stop(): void { + super.stop(); + } } From c3886a6ddf41407b5e3c299e0643743eafd6039c Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 15 Feb 2023 14:52:07 +0000 Subject: [PATCH 20/24] Add sync worker --- src/platform/web/Platform.js | 5 +++++ src/platform/web/sdk/paths/vite.js | 2 ++ src/platform/web/worker-sync/SyncWorker.ts | 15 ++++++++++++++ .../web/worker-sync/SyncWorkerPool.ts | 20 +++++++++++++++++++ src/platform/web/worker-sync/sync-worker.js | 1 + 5 files changed, 43 insertions(+) create mode 100644 src/platform/web/worker-sync/SyncWorker.ts create mode 100644 src/platform/web/worker-sync/SyncWorkerPool.ts create mode 100644 src/platform/web/worker-sync/sync-worker.js diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index be8c997078..ad09fa984f 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -43,6 +43,7 @@ import {MediaDevicesWrapper} from "./dom/MediaDevices"; import {DOMWebRTC} from "./dom/WebRTC"; import {ThemeLoader} from "./theming/ThemeLoader"; import {TimeFormatter} from "./dom/TimeFormatter"; +import {SyncWorkerPool} from "./worker-sync/SyncWorkerPool"; function addScript(src) { return new Promise(function (resolve, reject) { @@ -149,6 +150,10 @@ export class Platform { this._serviceWorkerHandler = new ServiceWorkerHandler(); this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } + this._syncWorkerPool = null; + if (assetPaths.syncWorker && window.Worker) { + this._syncWorkerPool = new SyncWorkerPool(this._assetPaths.syncWorker); + } this.notificationService = undefined; // Only try to use crypto when olm is provided if(this._assetPaths.olm) { diff --git a/src/platform/web/sdk/paths/vite.js b/src/platform/web/sdk/paths/vite.js index 48a17da45a..44d3e26747 100644 --- a/src/platform/web/sdk/paths/vite.js +++ b/src/platform/web/sdk/paths/vite.js @@ -2,6 +2,7 @@ import _downloadSandboxPath from "../../assets/download-sandbox.html?url"; // @ts-ignore import _workerPath from "../../worker/main.js?url"; +import _syncWorkerPath from "../../worker-sync/sync-worker.js?url"; // @ts-ignore import olmWasmPath from "@matrix-org/olm/olm.wasm?url"; // @ts-ignore @@ -12,6 +13,7 @@ import olmLegacyJsPath from "@matrix-org/olm/olm_legacy.js?url"; export default { downloadSandbox: _downloadSandboxPath, worker: _workerPath, + syncWorker: _syncWorkerPath, olm: { wasm: olmWasmPath, legacyBundle: olmLegacyJsPath, diff --git a/src/platform/web/worker-sync/SyncWorker.ts b/src/platform/web/worker-sync/SyncWorker.ts new file mode 100644 index 0000000000..917fc5ec6e --- /dev/null +++ b/src/platform/web/worker-sync/SyncWorker.ts @@ -0,0 +1,15 @@ +class SyncWorker { + constructor() { + self.addEventListener("message", this.onMessage); + } + + private onMessage(event: MessageEvent) { + console.log(event) + const data = event.data; + console.log(data); + postMessage(data); + } +} + +// @ts-ignore +self.syncWorker = new SyncWorker(); diff --git a/src/platform/web/worker-sync/SyncWorkerPool.ts b/src/platform/web/worker-sync/SyncWorkerPool.ts new file mode 100644 index 0000000000..e7fc5d854d --- /dev/null +++ b/src/platform/web/worker-sync/SyncWorkerPool.ts @@ -0,0 +1,20 @@ +export class SyncWorkerPool { + private readonly _worker: Worker; + + constructor(path: string) { + this._worker = new Worker(path, {type: "module"}); + this._worker.postMessage({ + hello: { + foo: "foo", + bar: "bar", + }, + world: { + baz: "baz" + }, + }); + this._worker.onmessage = (e) => { + const reply = e.data; + console.log(reply); + } + } +} diff --git a/src/platform/web/worker-sync/sync-worker.js b/src/platform/web/worker-sync/sync-worker.js new file mode 100644 index 0000000000..21c3ccb3d3 --- /dev/null +++ b/src/platform/web/worker-sync/sync-worker.js @@ -0,0 +1 @@ +import "./SyncWorker"; From 0540468aa9eb088efa0c2b1572e17fb0f381cee9 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 15 Feb 2023 16:03:52 +0000 Subject: [PATCH 21/24] Define API of SyncWorkerPool --- src/platform/web/Platform.js | 1 + .../web/worker-sync/SyncWorkerPool.ts | 35 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index ad09fa984f..247e708b49 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -153,6 +153,7 @@ export class Platform { this._syncWorkerPool = null; if (assetPaths.syncWorker && window.Worker) { this._syncWorkerPool = new SyncWorkerPool(this._assetPaths.syncWorker); + this._syncWorkerPool.add("my-session-id"); } this.notificationService = undefined; // Only try to use crypto when olm is provided diff --git a/src/platform/web/worker-sync/SyncWorkerPool.ts b/src/platform/web/worker-sync/SyncWorkerPool.ts index e7fc5d854d..a56d03a7b3 100644 --- a/src/platform/web/worker-sync/SyncWorkerPool.ts +++ b/src/platform/web/worker-sync/SyncWorkerPool.ts @@ -1,9 +1,30 @@ +type SessionId = string; + export class SyncWorkerPool { - private readonly _worker: Worker; + private readonly _workers: Map = new Map; + private readonly _path: string; constructor(path: string) { - this._worker = new Worker(path, {type: "module"}); - this._worker.postMessage({ + this._path = path; + } + + add(sessionId: SessionId) { + if (this._workers.size > 0) { + throw "Currently there can only be one active sync worker"; + } + + if (this._workers.has(sessionId)) { + throw `Session with id ${sessionId} already has a sync worker`; + } + + const worker = new Worker(this._path, {type: "module"}); + this._workers.set(sessionId, worker); + worker.onmessage = event => { + const data = event.data; + console.log(data); + } + + worker.postMessage({ hello: { foo: "foo", bar: "bar", @@ -12,9 +33,9 @@ export class SyncWorkerPool { baz: "baz" }, }); - this._worker.onmessage = (e) => { - const reply = e.data; - console.log(reply); - } + } + + remove(sessionId: SessionId) { + // TODO } } From b084a9e70de385f88b06a85c15f97c12e0d6ad12 Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 15 Feb 2023 16:27:21 +0000 Subject: [PATCH 22/24] Iterate on worker --- src/platform/web/worker-sync/SyncWorker.ts | 45 ++++++++++++++----- .../web/worker-sync/SyncWorkerPool.ts | 15 +++---- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/platform/web/worker-sync/SyncWorker.ts b/src/platform/web/worker-sync/SyncWorker.ts index 917fc5ec6e..69926d6425 100644 --- a/src/platform/web/worker-sync/SyncWorker.ts +++ b/src/platform/web/worker-sync/SyncWorker.ts @@ -1,15 +1,40 @@ -class SyncWorker { - constructor() { - self.addEventListener("message", this.onMessage); - } +import {SessionId} from "./SyncWorkerPool"; + +type Payload = object; + +export enum SyncWorkerMessageType { + StartSync, +} + +interface Message { + type: SyncWorkerMessageType, + payload: Payload +} - private onMessage(event: MessageEvent) { - console.log(event) - const data = event.data; - console.log(data); - postMessage(data); +export interface StartSyncPayload extends Payload { + sessionId: SessionId, +} + +class SyncWorker { + start(payload: StartSyncPayload): Payload { + console.log(`Starting sync for session with id ${payload.sessionId}`); + return payload; } } +const worker = new SyncWorker(); // @ts-ignore -self.syncWorker = new SyncWorker(); +self.syncWorker = worker; + +self.addEventListener("message", event => { + const data: Message = event.data; + + let reply: Payload; + switch (data.type) { + case SyncWorkerMessageType.StartSync: + reply = worker.start(data.payload as StartSyncPayload); + break; + } + + postMessage(reply); +}); diff --git a/src/platform/web/worker-sync/SyncWorkerPool.ts b/src/platform/web/worker-sync/SyncWorkerPool.ts index a56d03a7b3..4152d6fdbd 100644 --- a/src/platform/web/worker-sync/SyncWorkerPool.ts +++ b/src/platform/web/worker-sync/SyncWorkerPool.ts @@ -1,4 +1,6 @@ -type SessionId = string; +import {SyncWorkerMessageType} from "./SyncWorker"; + +export type SessionId = string; export class SyncWorkerPool { private readonly _workers: Map = new Map; @@ -25,13 +27,10 @@ export class SyncWorkerPool { } worker.postMessage({ - hello: { - foo: "foo", - bar: "bar", - }, - world: { - baz: "baz" - }, + type: SyncWorkerMessageType.StartSync, + payload: { + sessionId: sessionId, + } }); } From b46a22be5e0eddd7c96d7e9f2d70b5addd35530e Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 15 Feb 2023 16:49:32 +0000 Subject: [PATCH 23/24] Pass session info to worker --- .../localstorage/SessionInfoStorage.ts | 2 +- src/platform/web/Platform.js | 2 +- src/platform/web/worker-sync/SyncWorker.ts | 20 ++++++++++--------- .../web/worker-sync/SyncWorkerPool.ts | 12 +++++++---- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts b/src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts index ebe575f65d..d8225c9c13 100644 --- a/src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts +++ b/src/matrix/sessioninfo/localstorage/SessionInfoStorage.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -interface ISessionInfo { +export interface ISessionInfo { id: string; deviceId: string; userId: string; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 247e708b49..93d37504ff 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -153,7 +153,7 @@ export class Platform { this._syncWorkerPool = null; if (assetPaths.syncWorker && window.Worker) { this._syncWorkerPool = new SyncWorkerPool(this._assetPaths.syncWorker); - this._syncWorkerPool.add("my-session-id"); + this._syncWorkerPool.add("1646528482480255"); } this.notificationService = undefined; // Only try to use crypto when olm is provided diff --git a/src/platform/web/worker-sync/SyncWorker.ts b/src/platform/web/worker-sync/SyncWorker.ts index 69926d6425..776ee55de6 100644 --- a/src/platform/web/worker-sync/SyncWorker.ts +++ b/src/platform/web/worker-sync/SyncWorker.ts @@ -1,4 +1,4 @@ -import {SessionId} from "./SyncWorkerPool"; +import {ISessionInfo} from "../../../matrix/sessioninfo/localstorage/SessionInfoStorage"; type Payload = object; @@ -12,12 +12,12 @@ interface Message { } export interface StartSyncPayload extends Payload { - sessionId: SessionId, + sessionInfo: ISessionInfo, } class SyncWorker { - start(payload: StartSyncPayload): Payload { - console.log(`Starting sync for session with id ${payload.sessionId}`); + async start(payload: StartSyncPayload): Promise { + console.log(`Starting sync for session with id ${payload.sessionInfo.id}`); return payload; } } @@ -26,15 +26,17 @@ const worker = new SyncWorker(); // @ts-ignore self.syncWorker = worker; -self.addEventListener("message", event => { +self.onmessage = (event: MessageEvent) => { const data: Message = event.data; - let reply: Payload; + let promise: Promise; switch (data.type) { case SyncWorkerMessageType.StartSync: - reply = worker.start(data.payload as StartSyncPayload); + promise = worker.start(data.payload as StartSyncPayload); break; } - postMessage(reply); -}); + promise.then((reply: Payload) => { + postMessage(reply); + }).catch(error => console.error(error)) +}; diff --git a/src/platform/web/worker-sync/SyncWorkerPool.ts b/src/platform/web/worker-sync/SyncWorkerPool.ts index 4152d6fdbd..778368e1d1 100644 --- a/src/platform/web/worker-sync/SyncWorkerPool.ts +++ b/src/platform/web/worker-sync/SyncWorkerPool.ts @@ -1,16 +1,19 @@ import {SyncWorkerMessageType} from "./SyncWorker"; +import {SessionInfoStorage} from "../../../matrix/sessioninfo/localstorage/SessionInfoStorage"; export type SessionId = string; export class SyncWorkerPool { private readonly _workers: Map = new Map; private readonly _path: string; + private readonly _sessionInfoStorage: SessionInfoStorage; - constructor(path: string) { + constructor(path: string, sessionInfoStorage: SessionInfoStorage) { this._path = path; + this._sessionInfoStorage = sessionInfoStorage; } - add(sessionId: SessionId) { + async add(sessionId: SessionId) { if (this._workers.size > 0) { throw "Currently there can only be one active sync worker"; } @@ -26,11 +29,12 @@ export class SyncWorkerPool { console.log(data); } + const sessionInfo = await this._sessionInfoStorage.get(sessionId); worker.postMessage({ type: SyncWorkerMessageType.StartSync, payload: { - sessionId: sessionId, - } + sessionInfo: sessionInfo + }, }); } From 578cfd8d996d4f61e1633dcdeab1a6201c5d66ea Mon Sep 17 00:00:00 2001 From: Paulo Pinto Date: Wed, 15 Feb 2023 17:12:43 +0000 Subject: [PATCH 24/24] Create instance of HomeserverApi --- src/platform/web/Platform.js | 9 ++++--- src/platform/web/worker-sync/SyncWorker.ts | 29 +++++++++++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 93d37504ff..6c4ac5e69f 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -151,10 +151,6 @@ export class Platform { this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } this._syncWorkerPool = null; - if (assetPaths.syncWorker && window.Worker) { - this._syncWorkerPool = new SyncWorkerPool(this._assetPaths.syncWorker); - this._syncWorkerPool.add("1646528482480255"); - } this.notificationService = undefined; // Only try to use crypto when olm is provided if(this._assetPaths.olm) { @@ -179,6 +175,11 @@ export class Platform { this.mediaDevices = new MediaDevicesWrapper(navigator.mediaDevices); this.webRTC = new DOMWebRTC(); this._themeLoader = import.meta.env.DEV? null: new ThemeLoader(this); + + if (assetPaths.syncWorker && window.Worker) { + this._syncWorkerPool = new SyncWorkerPool(this._assetPaths.syncWorker, this.sessionInfoStorage); + this._syncWorkerPool.add("1646528482480255"); + } } async init() { diff --git a/src/platform/web/worker-sync/SyncWorker.ts b/src/platform/web/worker-sync/SyncWorker.ts index 776ee55de6..d49fa3597f 100644 --- a/src/platform/web/worker-sync/SyncWorker.ts +++ b/src/platform/web/worker-sync/SyncWorker.ts @@ -1,4 +1,10 @@ import {ISessionInfo} from "../../../matrix/sessioninfo/localstorage/SessionInfoStorage"; +import {HomeServerApi} from "../../../matrix/net/HomeServerApi"; +import {createFetchRequest} from "../dom/request/fetch"; +import {Clock} from "../dom/Clock"; +import {Reconnector} from "../../../matrix/net/Reconnector"; +import {ExponentialRetryDelay} from "../../../matrix/net/ExponentialRetryDelay"; +import {OnlineStatus} from "../dom/OnlineStatus"; type Payload = object; @@ -16,8 +22,29 @@ export interface StartSyncPayload extends Payload { } class SyncWorker { + private _clock: Clock; + private _reconnector: Reconnector; + async start(payload: StartSyncPayload): Promise { - console.log(`Starting sync for session with id ${payload.sessionInfo.id}`); + const sessionInfo = payload.sessionInfo; + console.log(`Starting sync worker for session with id ${sessionInfo.id}`); + + this._clock = new Clock; + + this._reconnector = new Reconnector({ + onlineStatus: new OnlineStatus(), + retryDelay: new ExponentialRetryDelay(this._clock.createTimeout), + createMeasure: this._clock.createMeasure + }); + + const hsApi = new HomeServerApi({ + homeserver: sessionInfo.homeserver, + accessToken: sessionInfo.accessToken, + request: createFetchRequest(this._clock.createTimeout), + reconnector: this._reconnector, + }); + + return payload; } }