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

Commit c7bbc1c

Browse files
authored
Merge commit from fork
and migrate existing account-level settings once-only for existing sessions.
1 parent 5dda51f commit c7bbc1c

File tree

5 files changed

+139
-13
lines changed

5 files changed

+139
-13
lines changed

src/Lifecycle.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ dis.register((payload) => {
102102
// If we unset the client and the component is updated, the render will fail and unmount everything.
103103
// (The module dialog closes and fires a `aria_unhide_main_app` that will trigger a re-render)
104104
stopMatrixClient(false);
105-
doSetLoggedIn(typed.credentials, true).catch((e) => {
105+
doSetLoggedIn(typed.credentials, true, true).catch((e) => {
106106
// XXX we might want to fire a new event here to let the app know that the login failed ?
107107
// The module api could use it to display a message to the user.
108108
logger.warn("Failed to overwrite login", e);
@@ -208,6 +208,7 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
208208
guest: true,
209209
},
210210
true,
211+
false,
211212
).then(() => true);
212213
}
213214
const success = await restoreFromLocalStorage({
@@ -465,6 +466,7 @@ function registerAsGuest(hsUrl: string, isUrl?: string, defaultDeviceDisplayName
465466
guest: true,
466467
},
467468
true,
469+
true,
468470
).then(() => true);
469471
},
470472
(err) => {
@@ -610,6 +612,7 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
610612
freshLogin: freshLogin,
611613
},
612614
false,
615+
false,
613616
);
614617
return true;
615618
} else {
@@ -663,7 +666,7 @@ export async function setLoggedIn(credentials: IMatrixClientCreds): Promise<Matr
663666
logger.log("Pickle key not created");
664667
}
665668

666-
return doSetLoggedIn(Object.assign({}, credentials, { pickleKey }), true);
669+
return doSetLoggedIn(Object.assign({}, credentials, { pickleKey }), true, true);
667670
}
668671

669672
/**
@@ -700,7 +703,7 @@ export async function hydrateSession(credentials: IMatrixClientCreds): Promise<M
700703
(await PlatformPeg.get()?.getPickleKey(credentials.userId, credentials.deviceId)) ?? undefined;
701704
}
702705

703-
return doSetLoggedIn(credentials, overwrite);
706+
return doSetLoggedIn(credentials, overwrite, false);
704707
}
705708

706709
/**
@@ -746,12 +749,17 @@ async function createOidcTokenRefresher(credentials: IMatrixClientCreds): Promis
746749
* optionally clears localstorage, persists new credentials
747750
* to localstorage, starts the new client.
748751
*
749-
* @param {IMatrixClientCreds} credentials
750-
* @param {Boolean} clearStorageEnabled
752+
* @param {IMatrixClientCreds} credentials The credentials to use
753+
* @param {Boolean} clearStorageEnabled True to clear storage before starting the new client
754+
* @param {Boolean} isFreshLogin True if this is a fresh login, false if it is previous session being restored
751755
*
752756
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
753757
*/
754-
async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnabled: boolean): Promise<MatrixClient> {
758+
async function doSetLoggedIn(
759+
credentials: IMatrixClientCreds,
760+
clearStorageEnabled: boolean,
761+
isFreshLogin: boolean,
762+
): Promise<MatrixClient> {
755763
checkSessionLock();
756764
credentials.guest = Boolean(credentials.guest);
757765

@@ -840,6 +848,9 @@ async function doSetLoggedIn(credentials: IMatrixClientCreds, clearStorageEnable
840848
clientPegOpts.rustCryptoStoreKey?.fill(0);
841849
}
842850

851+
// Run the migrations after the MatrixClientPeg has been assigned
852+
SettingsStore.runMigrations(isFreshLogin);
853+
843854
return client;
844855
}
845856

@@ -1020,9 +1031,6 @@ async function startMatrixClient(
10201031

10211032
checkSessionLock();
10221033

1023-
// Run the migrations after the MatrixClientPeg has been assigned
1024-
SettingsStore.runMigrations();
1025-
10261034
// This needs to be started after crypto is set up
10271035
DeviceListener.sharedInstance().start(client);
10281036
// Similarly, don't start sending presence updates until we've started
@@ -1165,5 +1173,6 @@ window.mxLoginWithAccessToken = async (hsUrl: string, accessToken: string): Prom
11651173
userId,
11661174
},
11671175
true,
1176+
false,
11681177
);
11691178
};

src/components/views/room_settings/UrlPreviewSettings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export default class UrlPreviewSettings extends React.Component<IProps> {
101101
(
102102
<SettingsFlag
103103
name={isEncrypted ? "urlPreviewsEnabled_e2ee" : "urlPreviewsEnabled"}
104-
level={SettingLevel.ROOM_ACCOUNT}
104+
level={SettingLevel.ROOM_DEVICE}
105105
roomId={roomId}
106106
/>
107107
);

src/settings/Settings.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -900,7 +900,7 @@ export const SETTINGS: { [setting: string]: ISetting } = {
900900
controller: new UIFeatureController(UIFeature.URLPreviews),
901901
},
902902
"urlPreviewsEnabled_e2ee": {
903-
supportedLevels: [SettingLevel.ROOM_DEVICE, SettingLevel.ROOM_ACCOUNT],
903+
supportedLevels: [SettingLevel.ROOM_DEVICE],
904904
displayName: {
905905
"room-account": _td("settings|inline_url_previews_room_account"),
906906
},

src/settings/SettingsStore.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717

1818
import { logger } from "matrix-js-sdk/src/logger";
1919
import { ReactNode } from "react";
20+
import { ClientEvent, SyncState } from "matrix-js-sdk/src/matrix";
2021

2122
import DeviceSettingsHandler from "./handlers/DeviceSettingsHandler";
2223
import RoomDeviceSettingsHandler from "./handlers/RoomDeviceSettingsHandler";
@@ -36,6 +37,7 @@ import { SettingUpdatedPayload } from "../dispatcher/payloads/SettingUpdatedPayl
3637
import { Action } from "../dispatcher/actions";
3738
import PlatformSettingsHandler from "./handlers/PlatformSettingsHandler";
3839
import ReloadOnChangeController from "./controllers/ReloadOnChangeController";
40+
import { MatrixClientPeg } from "../MatrixClientPeg";
3941

4042
// Convert the settings to easier to manage objects for the handlers
4143
const defaultSettings: Record<string, any> = {};
@@ -637,10 +639,61 @@ export default class SettingsStore {
637639
return null;
638640
}
639641

642+
/**
643+
* Migrate the setting for URL previews in e2e rooms from room account
644+
* data to the room device level.
645+
*
646+
* @param isFreshLogin True if the user has just logged in, false if a previous session is being restored.
647+
*/
648+
private static async migrateURLPreviewsE2EE(isFreshLogin: boolean): Promise<void> {
649+
const MIGRATION_DONE_FLAG = "url_previews_e2ee_migration_done";
650+
if (localStorage.getItem(MIGRATION_DONE_FLAG)) return;
651+
if (isFreshLogin) return;
652+
653+
const client = MatrixClientPeg.safeGet();
654+
655+
const doMigration = async (): Promise<void> => {
656+
logger.info("Performing one-time settings migration of URL previews in E2EE rooms");
657+
658+
const roomAccounthandler = LEVEL_HANDLERS[SettingLevel.ROOM_ACCOUNT];
659+
660+
for (const room of client.getRooms()) {
661+
// We need to use the handler directly because this setting is no longer supported
662+
// at this level at all
663+
const val = roomAccounthandler.getValue("urlPreviewsEnabled_e2ee", room.roomId);
664+
665+
if (val !== undefined) {
666+
await SettingsStore.setValue("urlPreviewsEnabled_e2ee", room.roomId, SettingLevel.ROOM_DEVICE, val);
667+
}
668+
}
669+
670+
localStorage.setItem(MIGRATION_DONE_FLAG, "true");
671+
};
672+
673+
const onSync = (state: SyncState): void => {
674+
if (state === SyncState.Prepared) {
675+
client.removeListener(ClientEvent.Sync, onSync);
676+
677+
doMigration().catch((e) => {
678+
logger.error("Failed to migrate URL previews in E2EE rooms:", e);
679+
});
680+
}
681+
};
682+
683+
client.on(ClientEvent.Sync, onSync);
684+
}
685+
640686
/**
641687
* Runs or queues any setting migrations needed.
642688
*/
643-
public static runMigrations(): void {
689+
public static runMigrations(isFreshLogin: boolean): void {
690+
// This can be removed once enough users have run a version of Element with
691+
// this migration. A couple of months after its release should be sufficient
692+
// (so around October 2024).
693+
// The consequences of missing the migration are only that URL previews will
694+
// be disabled in E2EE rooms.
695+
SettingsStore.migrateURLPreviewsE2EE(isFreshLogin);
696+
644697
// Dev notes: to add your migration, just add a new `migrateMyFeature` function, call it, and
645698
// add a comment to note when it can be removed.
646699
return;

test/settings/SettingsStore-test.ts

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

17+
import { ClientEvent, MatrixClient, Room, SyncState } from "matrix-js-sdk";
18+
import { localstorage } from "modernizr";
19+
1720
import BasePlatform from "../../src/BasePlatform";
1821
import SdkConfig from "../../src/SdkConfig";
1922
import { SettingLevel } from "../../src/settings/SettingLevel";
2023
import SettingsStore from "../../src/settings/SettingsStore";
21-
import { mockPlatformPeg } from "../test-utils";
24+
import { mkStubRoom, mockPlatformPeg, stubClient } from "../test-utils";
2225

2326
const TEST_DATA = [
2427
{
@@ -84,4 +87,65 @@ describe("SettingsStore", () => {
8487
expect(SettingsStore.getValueAt(SettingLevel.DEVICE, SETTING_NAME_WITH_CONFIG_OVERRIDE)).toBe(true);
8588
});
8689
});
90+
91+
describe("runMigrations", () => {
92+
let client: MatrixClient;
93+
let room: Room;
94+
let localStorageSetItemSpy: jest.SpyInstance;
95+
let localStorageSetPromise: Promise<void>;
96+
97+
beforeEach(() => {
98+
client = stubClient();
99+
room = mkStubRoom("!room:example.org", "Room", client);
100+
room.getAccountData = jest.fn().mockReturnValue({
101+
getContent: jest.fn().mockReturnValue({
102+
urlPreviewsEnabled_e2ee: true,
103+
}),
104+
});
105+
client.getRooms = jest.fn().mockReturnValue([room]);
106+
client.getRoom = jest.fn().mockReturnValue(room);
107+
108+
localStorageSetPromise = new Promise((resolve) => {
109+
localStorageSetItemSpy = jest
110+
.spyOn(localStorage.__proto__, "setItem")
111+
.mockImplementation(() => resolve());
112+
});
113+
});
114+
115+
afterEach(() => {
116+
jest.restoreAllMocks();
117+
});
118+
119+
it("migrates URL previews setting for e2ee rooms", async () => {
120+
SettingsStore.runMigrations(false);
121+
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
122+
123+
expect(room.getAccountData).toHaveBeenCalled();
124+
125+
await localStorageSetPromise;
126+
127+
expect(localStorageSetItemSpy!).toHaveBeenCalledWith(
128+
`mx_setting_urlPreviewsEnabled_e2ee_${room.roomId}`,
129+
JSON.stringify({ value: true }),
130+
);
131+
});
132+
133+
it("does not migrate e2ee URL previews on a fresh login", async () => {
134+
SettingsStore.runMigrations(true);
135+
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
136+
137+
expect(room.getAccountData).not.toHaveBeenCalled();
138+
});
139+
140+
it("does not migrate if the device is flagged as migrated", async () => {
141+
jest.spyOn(localStorage.__proto__, "getItem").mockImplementation((key: unknown): string | undefined => {
142+
if (key === "url_previews_e2ee_migration_done") return JSON.stringify({ value: true });
143+
return undefined;
144+
});
145+
SettingsStore.runMigrations(false);
146+
client.emit(ClientEvent.Sync, SyncState.Prepared, null);
147+
148+
expect(room.getAccountData).not.toHaveBeenCalled();
149+
});
150+
});
87151
});

0 commit comments

Comments
 (0)