diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 05dd437f94..21641aec09 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -508,4 +508,19 @@ export default abstract class BasePlatform { * Begin update polling, if applicable */ public startUpdater(): void {} + + /** + * Checks if the current session is lock-free, i.e., no other instance is holding the session lock. + * Platforms that support session locking should override this method. + * @returns {boolean} True if the session is lock-free, false otherwise. + */ + public abstract checkSessionLockFree(): boolean; + /** + * Attempts to acquire a session lock for this instance. + * If another instance is detected, calls the provided callback. + * Platforms that support session locking should override this method. + * @param _onNewInstance Callback to invoke if a new instance is detected. + * @returns {Promise} True if the lock was acquired, false otherwise. + */ + public abstract getSessionLock(_onNewInstance: () => Promise): Promise; } diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index aef4fcdaf0..51eb23c787 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -130,7 +130,6 @@ import { NotificationLevel } from "../../stores/notifications/NotificationLevel" import { type UserTab } from "../views/dialogs/UserTab"; import { shouldSkipSetupEncryption } from "../../utils/crypto/shouldSkipSetupEncryption"; import { Filter } from "../views/dialogs/spotlight/Filter"; -import { checkSessionLockFree, getSessionLock } from "../../utils/SessionLock"; import { SessionLockStolenView } from "./auth/SessionLockStolenView"; import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"; import { LoginSplashView } from "./auth/LoginSplashView"; @@ -314,7 +313,8 @@ export default class MatrixChat extends React.PureComponent { private async initSession(): Promise { // The Rust Crypto SDK will break if two Element instances try to use the same datastore at once, so // make sure we are the only Element instance in town (on this browser/domain). - if (!(await getSessionLock(() => this.onSessionLockStolen()))) { + const platform = PlatformPeg.get(); + if (platform && !(await platform.getSessionLock(() => this.onSessionLockStolen()))) { // we failed to get the lock. onSessionLockStolen should already have been called, so nothing left to do. return; } @@ -479,7 +479,8 @@ export default class MatrixChat extends React.PureComponent { // mounted. if (!this.sessionLoadStarted) { this.sessionLoadStarted = true; - if (!checkSessionLockFree()) { + const platform = PlatformPeg.get(); + if (platform && !platform.checkSessionLockFree()) { // another instance holds the lock; confirm its theft before proceeding setTimeout(() => this.setState({ view: Views.CONFIRM_LOCK_THEFT }), 0); } else { diff --git a/src/vector/platform/ElectronPlatform.tsx b/src/vector/platform/ElectronPlatform.tsx index 4139f73d53..236703922c 100644 --- a/src/vector/platform/ElectronPlatform.tsx +++ b/src/vector/platform/ElectronPlatform.tsx @@ -558,4 +558,12 @@ export default class ElectronPlatform extends BasePlatform { } return url; } + + public checkSessionLockFree(): boolean { + return true; + } + + public async getSessionLock(_onNewInstance: () => Promise): Promise { + return true; + } } diff --git a/src/vector/platform/WebPlatform.ts b/src/vector/platform/WebPlatform.ts index 46b6a61f83..87ed2389dc 100644 --- a/src/vector/platform/WebPlatform.ts +++ b/src/vector/platform/WebPlatform.ts @@ -22,6 +22,7 @@ import ToastStore from "../../stores/ToastStore.ts"; import GenericToast from "../../components/views/toasts/GenericToast.tsx"; import SdkConfig from "../../SdkConfig.ts"; import type { ActionPayload } from "../../dispatcher/payloads.ts"; +import * as SessionLock from "../../utils/SessionLock.ts"; const POKE_RATE_MS = 10 * 60 * 1000; // 10 min @@ -268,4 +269,12 @@ export default class WebPlatform extends BasePlatform { public reload(): void { window.location.reload(); } + + public checkSessionLockFree(): boolean { + return SessionLock.checkSessionLockFree(); + } + + public async getSessionLock(onNewInstance: () => Promise): Promise { + return SessionLock.getSessionLock(onNewInstance); + } } diff --git a/test/test-utils/platform.ts b/test/test-utils/platform.ts index 3769826576..ec742a407e 100644 --- a/test/test-utils/platform.ts +++ b/test/test-utils/platform.ts @@ -10,6 +10,7 @@ import { type MethodLikeKeys, mocked, type MockedObject } from "jest-mock"; import BasePlatform from "../../src/BasePlatform"; import PlatformPeg from "../../src/PlatformPeg"; +import * as SessionLock from "../../src/utils/SessionLock"; // doesn't implement abstract // @ts-ignore @@ -18,6 +19,14 @@ class MockPlatform extends BasePlatform { super(); Object.assign(this, platformMocks); } + + public checkSessionLockFree(): boolean { + return SessionLock.checkSessionLockFree(); + } + + public async getSessionLock(onNewInstance: () => Promise): Promise { + return SessionLock.getSessionLock(onNewInstance); + } } /** * Mock Platform Peg diff --git a/test/unit-tests/components/structures/MatrixChat-test.tsx b/test/unit-tests/components/structures/MatrixChat-test.tsx index e16e42d0d4..eef9b999d2 100644 --- a/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -1632,6 +1632,10 @@ describe("", () => { }); describe("Multi-tab lockout", () => { + beforeEach(() => { + mockPlatformPeg(); + }); + afterEach(() => { Lifecycle.setSessionLockNotStolen(); }); @@ -1677,6 +1681,8 @@ describe("", () => { beforeEach(() => { // make sure we start from a clean DOM for each of these tests document.body.replaceChildren(); + // use the MockPlatform + mockPlatformPeg(); }); function simulateSessionLockClaim() {