Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 8f65fbf

Browse files
authored
Merge pull request #12287 from matrix-org/backport-12280-to-staging
[Backport staging] Fix spurious session corruption error
2 parents 15e5130 + 76da40c commit 8f65fbf

File tree

4 files changed

+145
-44
lines changed

4 files changed

+145
-44
lines changed

src/Lifecycle.ts

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import { InvalidStoreError } from "matrix-js-sdk/src/errors";
2323
import { IEncryptedPayload } from "matrix-js-sdk/src/crypto/aes";
2424
import { QueryDict } from "matrix-js-sdk/src/utils";
2525
import { logger } from "matrix-js-sdk/src/logger";
26-
import { MINIMUM_MATRIX_VERSION, SUPPORTED_MATRIX_VERSIONS } from "matrix-js-sdk/src/version-support";
2726

2827
import { IMatrixClientCreds, MatrixClientPeg } from "./MatrixClientPeg";
2928
import SecurityCustomisations from "./customisations/Security";
@@ -74,7 +73,6 @@ import {
7473
getStoredOidcTokenIssuer,
7574
persistOidcAuthenticatedSettings,
7675
} from "./utils/oidc/persistOidcSettings";
77-
import GenericToast from "./components/views/toasts/GenericToast";
7876
import {
7977
ACCESS_TOKEN_IV,
8078
ACCESS_TOKEN_STORAGE_KEY,
@@ -635,45 +633,13 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
635633
},
636634
false,
637635
);
638-
await checkServerVersions();
639636
return true;
640637
} else {
641638
logger.log("No previous session found.");
642639
return false;
643640
}
644641
}
645642

646-
async function checkServerVersions(): Promise<void> {
647-
const client = MatrixClientPeg.get();
648-
if (!client) return;
649-
for (const version of SUPPORTED_MATRIX_VERSIONS) {
650-
// Check if the server supports this spec version. (`isVersionSupported` caches the response, so this loop will
651-
// only make a single HTTP request).
652-
if (await client.isVersionSupported(version)) {
653-
// we found a compatible spec version
654-
return;
655-
}
656-
}
657-
658-
const toastKey = "LEGACY_SERVER";
659-
ToastStore.sharedInstance().addOrReplaceToast({
660-
key: toastKey,
661-
title: _t("unsupported_server_title"),
662-
props: {
663-
description: _t("unsupported_server_description", {
664-
version: MINIMUM_MATRIX_VERSION,
665-
brand: SdkConfig.get().brand,
666-
}),
667-
acceptLabel: _t("action|ok"),
668-
onAccept: () => {
669-
ToastStore.sharedInstance().dismissToast(toastKey);
670-
},
671-
},
672-
component: GenericToast,
673-
priority: 98,
674-
});
675-
}
676-
677643
async function handleLoadSessionFailure(e: unknown): Promise<boolean> {
678644
logger.error("Unable to load session", e);
679645

src/stores/LifecycleStore.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,20 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { SyncState } from "matrix-js-sdk/src/matrix";
18+
import { MINIMUM_MATRIX_VERSION, SUPPORTED_MATRIX_VERSIONS } from "matrix-js-sdk/src/version-support";
19+
import { logger } from "matrix-js-sdk/src/logger";
20+
1721
import { Action } from "../dispatcher/actions";
1822
import dis from "../dispatcher/dispatcher";
1923
import { ActionPayload } from "../dispatcher/payloads";
2024
import { DoAfterSyncPreparedPayload } from "../dispatcher/payloads/DoAfterSyncPreparedPayload";
2125
import { AsyncStore } from "./AsyncStore";
26+
import { MatrixClientPeg } from "../MatrixClientPeg";
27+
import ToastStore from "./ToastStore";
28+
import { _t } from "../languageHandler";
29+
import SdkConfig from "../SdkConfig";
30+
import GenericToast from "../components/views/toasts/GenericToast";
2231

2332
interface IState {
2433
deferredAction: ActionPayload | null;
@@ -51,6 +60,12 @@ class LifecycleStore extends AsyncStore<IState> {
5160
});
5261
break;
5362
case "MatrixActions.sync": {
63+
if (payload.state === SyncState.Syncing && payload.prevState !== SyncState.Syncing) {
64+
// We've reconnected to the server: update server version support
65+
// This is async but we don't care about the result, so just fire & forget.
66+
checkServerVersions();
67+
}
68+
5469
if (payload.state !== "PREPARED") {
5570
break;
5671
}
@@ -70,6 +85,48 @@ class LifecycleStore extends AsyncStore<IState> {
7085
}
7186
}
7287

88+
async function checkServerVersions(): Promise<void> {
89+
try {
90+
const client = MatrixClientPeg.get();
91+
if (!client) return;
92+
for (const version of SUPPORTED_MATRIX_VERSIONS) {
93+
// Check if the server supports this spec version. (`isVersionSupported` caches the response, so this loop will
94+
// only make a single HTTP request).
95+
// Note that although we do this on a reconnect, we cache the server's versions in memory
96+
// indefinitely, so it will only ever trigger the toast on the first connection after a fresh
97+
// restart of the client.
98+
if (await client.isVersionSupported(version)) {
99+
// we found a compatible spec version
100+
return;
101+
}
102+
}
103+
104+
// This is retrospective doc having debated about the exactly what this toast is for, but
105+
// our guess is that it's a nudge to update, or ask your HS admin to update your Homeserver
106+
// after a new version of Element has come out, in a way that doesn't lock you out of all
107+
// your messages.
108+
const toastKey = "LEGACY_SERVER";
109+
ToastStore.sharedInstance().addOrReplaceToast({
110+
key: toastKey,
111+
title: _t("unsupported_server_title"),
112+
props: {
113+
description: _t("unsupported_server_description", {
114+
version: MINIMUM_MATRIX_VERSION,
115+
brand: SdkConfig.get().brand,
116+
}),
117+
acceptLabel: _t("action|ok"),
118+
onAccept: () => {
119+
ToastStore.sharedInstance().dismissToast(toastKey);
120+
},
121+
},
122+
component: GenericToast,
123+
priority: 98,
124+
});
125+
} catch (e) {
126+
logger.warn("Failed to check server versions", e);
127+
}
128+
}
129+
73130
let singletonLifecycleStore: LifecycleStore | null = null;
74131
if (!singletonLifecycleStore) {
75132
singletonLifecycleStore = new LifecycleStore();

test/Lifecycle-test.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { MatrixClientPeg } from "../src/MatrixClientPeg";
2828
import Modal from "../src/Modal";
2929
import * as StorageManager from "../src/utils/StorageManager";
3030
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser, mockPlatformPeg } from "./test-utils";
31-
import ToastStore from "../src/stores/ToastStore";
3231
import { OidcClientStore } from "../src/stores/oidc/OidcClientStore";
3332
import { makeDelegatedAuthConfig } from "./test-utils/oidc";
3433
import { persistOidcAuthenticatedSettings } from "../src/utils/oidc/persistOidcSettings";
@@ -451,17 +450,10 @@ describe("Lifecycle", () => {
451450
});
452451
});
453452

454-
it("should show a toast if the matrix server version is unsupported", async () => {
455-
const toastSpy = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
456-
mockClient.isVersionSupported.mockImplementation(async (version) => version == "r0.6.0");
457-
initLocalStorageMock({ ...localStorageSession });
453+
it("should proceed if server is not accessible", async () => {
454+
mockClient.isVersionSupported.mockRejectedValue(new Error("Oh, noes, the server is down!"));
458455

459456
expect(await restoreFromLocalStorage()).toEqual(true);
460-
expect(toastSpy).toHaveBeenCalledWith(
461-
expect.objectContaining({
462-
title: "Your server is unsupported",
463-
}),
464-
);
465457
});
466458
});
467459
});

test/stores/LifecycleStore-test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
Copyright 2024 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { mocked } from "jest-mock";
18+
import { SyncState } from "matrix-js-sdk/src/matrix";
19+
20+
import { MatrixClientPeg } from "../../src/MatrixClientPeg";
21+
import ToastStore from "../../src/stores/ToastStore";
22+
import { stubClient } from "../test-utils";
23+
import LifecycleStore from "../../src/stores/LifecycleStore";
24+
25+
describe("LifecycleStore", () => {
26+
stubClient();
27+
const client = MatrixClientPeg.safeGet();
28+
let addOrReplaceToast: jest.SpyInstance;
29+
30+
beforeEach(() => {
31+
addOrReplaceToast = jest.spyOn(ToastStore.sharedInstance(), "addOrReplaceToast");
32+
});
33+
34+
it("should do nothing if the matrix server version is supported", async () => {
35+
mocked(client).isVersionSupported.mockResolvedValue(true);
36+
37+
(LifecycleStore as any).onDispatch({
38+
action: "MatrixActions.sync",
39+
state: SyncState.Syncing,
40+
prevState: SyncState.Prepared,
41+
});
42+
43+
await new Promise(setImmediate);
44+
45+
expect(addOrReplaceToast).not.toHaveBeenCalledWith(
46+
expect.objectContaining({
47+
title: "Your server is unsupported",
48+
}),
49+
);
50+
});
51+
52+
it("should show a toast if the matrix server version is unsupported", async () => {
53+
mocked(client).isVersionSupported.mockResolvedValue(false);
54+
55+
(LifecycleStore as any).onDispatch({
56+
action: "MatrixActions.sync",
57+
state: SyncState.Syncing,
58+
prevState: SyncState.Prepared,
59+
});
60+
61+
await new Promise(setImmediate);
62+
63+
expect(addOrReplaceToast).toHaveBeenCalledWith(
64+
expect.objectContaining({
65+
title: "Your server is unsupported",
66+
}),
67+
);
68+
});
69+
70+
it("dismisses toast on accept button", async () => {
71+
const dismissToast = jest.spyOn(ToastStore.sharedInstance(), "dismissToast");
72+
mocked(client).isVersionSupported.mockResolvedValue(false);
73+
74+
(LifecycleStore as any).onDispatch({
75+
action: "MatrixActions.sync",
76+
state: SyncState.Syncing,
77+
prevState: SyncState.Prepared,
78+
});
79+
80+
await new Promise(setImmediate);
81+
82+
addOrReplaceToast.mock.calls[0][0].props.onAccept();
83+
84+
expect(dismissToast).toHaveBeenCalledWith(addOrReplaceToast.mock.calls[0][0].key);
85+
});
86+
});

0 commit comments

Comments
 (0)