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

Device manager - remove client information events when disabling setting #9384

Merged
merged 3 commits into from
Oct 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions src/DeviceListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ import { Action } from "./dispatcher/actions";
import { isLoggedIn } from "./utils/login";
import SdkConfig from "./SdkConfig";
import PlatformPeg from "./PlatformPeg";
import { recordClientInformation } from "./utils/device/clientInformation";
import {
recordClientInformation,
removeClientInformation,
} from "./utils/device/clientInformation";
import SettingsStore, { CallbackFn } from "./settings/SettingsStore";

const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
Expand Down Expand Up @@ -90,7 +93,7 @@ export default class DeviceListener {
);
this.dispatcherRef = dis.register(this.onAction);
this.recheck();
this.recordClientInformation();
this.updateClientInformation();
}

public stop() {
Expand Down Expand Up @@ -216,7 +219,7 @@ export default class DeviceListener {
private onAction = ({ action }: ActionPayload) => {
if (action !== Action.OnLoggedIn) return;
this.recheck();
this.recordClientInformation();
this.updateClientInformation();
};

// The server doesn't tell us when key backup is set up, so we poll
Expand Down Expand Up @@ -368,25 +371,26 @@ export default class DeviceListener {

this.shouldRecordClientInformation = !!newValue;

if (this.shouldRecordClientInformation && !prevValue) {
this.recordClientInformation();
if (this.shouldRecordClientInformation !== prevValue) {
this.updateClientInformation();
}
};

private recordClientInformation = async () => {
if (!this.shouldRecordClientInformation) {
return;
}
private updateClientInformation = async () => {
try {
await recordClientInformation(
MatrixClientPeg.get(),
SdkConfig.get(),
PlatformPeg.get(),
);
if (this.shouldRecordClientInformation) {
await recordClientInformation(
MatrixClientPeg.get(),
SdkConfig.get(),
PlatformPeg.get(),
);
} else {
await removeClientInformation(MatrixClientPeg.get());
}
} catch (error) {
// this is a best effort operation
// log the error without rethrowing
logger.error('Failed to record client information', error);
logger.error('Failed to update client information', error);
}
};
}
18 changes: 18 additions & 0 deletions src/utils/device/clientInformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@ export const recordClientInformation = async (
});
};

/**
* Remove extra client information
* @todo(kerrya) revisit after MSC3391: account data deletion is done
* (PSBE-12)
*/
export const removeClientInformation = async (
matrixClient: MatrixClient,
): Promise<void> => {
const deviceId = matrixClient.getDeviceId();
const type = getClientInformationEventType(deviceId);
const clientInformation = getDeviceClientInformation(matrixClient, deviceId);

// if a non-empty client info event exists, overwrite to remove the content
if (clientInformation.name || clientInformation.version || clientInformation.url) {
await matrixClient.setAccountData(type, {});
}
};

const sanitizeContentString = (value: unknown): string | undefined =>
value && typeof value === 'string' ? value : undefined;

Expand Down
34 changes: 26 additions & 8 deletions test/DeviceListener-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ limitations under the License.

import { EventEmitter } from "events";
import { mocked } from "jest-mock";
import { Room } from "matrix-js-sdk/src/matrix";
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";

import DeviceListener from "../src/DeviceListener";
Expand Down Expand Up @@ -66,6 +66,7 @@ class MockClient extends EventEmitter {
getClientWellKnown = jest.fn();
getDeviceId = jest.fn().mockReturnValue(deviceId);
setAccountData = jest.fn();
getAccountData = jest.fn();
}
const mockDispatcher = mocked(dis);
const flushPromises = async () => await new Promise(process.nextTick);
Expand Down Expand Up @@ -138,7 +139,7 @@ describe('DeviceListener', () => {
await createAndStart();

expect(errorLogSpy).toHaveBeenCalledWith(
'Failed to record client information',
'Failed to update client information',
error,
);
});
Expand All @@ -161,19 +162,39 @@ describe('DeviceListener', () => {
});

describe('when device client information feature is disabled', () => {
const clientInfoEvent = new MatrixEvent({ type: `io.element.matrix_client_information.${deviceId}`,
content: { name: 'hello' },
});
const emptyClientInfoEvent = new MatrixEvent({ type: `io.element.matrix_client_information.${deviceId}` });
beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);

mockClient.getAccountData.mockReturnValue(undefined);
});

it('does not save client information on start', async () => {
await createAndStart();

expect(mockClient.setAccountData).not.toHaveBeenCalledWith(
expect(mockClient.setAccountData).not.toHaveBeenCalled();
});

it('removes client information on start if it exists', async () => {
mockClient.getAccountData.mockReturnValue(clientInfoEvent);
await createAndStart();

expect(mockClient.setAccountData).toHaveBeenCalledWith(
`io.element.matrix_client_information.${deviceId}`,
{ name: 'Element', url: 'localhost', version: '1.2.3' },
{},
);
});

it('does not try to remove client info event that are already empty', async () => {
mockClient.getAccountData.mockReturnValue(emptyClientInfoEvent);
await createAndStart();

expect(mockClient.setAccountData).not.toHaveBeenCalled();
});

it('does not save client information on logged in action', async () => {
const instance = await createAndStart();

Expand All @@ -182,10 +203,7 @@ describe('DeviceListener', () => {

await flushPromises();

expect(mockClient.setAccountData).not.toHaveBeenCalledWith(
`io.element.matrix_client_information.${deviceId}`,
{ name: 'Element', url: 'localhost', version: '1.2.3' },
);
expect(mockClient.setAccountData).not.toHaveBeenCalled();
});

it('saves client information after setting is enabled', async () => {
Expand Down