Merge branch 'develop' into feat/matrix-wysisyg-integration

pull/28217/head
Florian Duros 2022-10-11 10:47:01 +02:00 committed by GitHub
commit 203f75f755
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 43 deletions

View File

@ -42,7 +42,10 @@ import { Action } from "./dispatcher/actions";
import { isLoggedIn } from "./utils/login"; import { isLoggedIn } from "./utils/login";
import SdkConfig from "./SdkConfig"; import SdkConfig from "./SdkConfig";
import PlatformPeg from "./PlatformPeg"; import PlatformPeg from "./PlatformPeg";
import { recordClientInformation } from "./utils/device/clientInformation"; import {
recordClientInformation,
removeClientInformation,
} from "./utils/device/clientInformation";
import SettingsStore, { CallbackFn } from "./settings/SettingsStore"; import SettingsStore, { CallbackFn } from "./settings/SettingsStore";
const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000; const KEY_BACKUP_POLL_INTERVAL = 5 * 60 * 1000;
@ -90,7 +93,7 @@ export default class DeviceListener {
); );
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this.recheck(); this.recheck();
this.recordClientInformation(); this.updateClientInformation();
} }
public stop() { public stop() {
@ -216,7 +219,7 @@ export default class DeviceListener {
private onAction = ({ action }: ActionPayload) => { private onAction = ({ action }: ActionPayload) => {
if (action !== Action.OnLoggedIn) return; if (action !== Action.OnLoggedIn) return;
this.recheck(); this.recheck();
this.recordClientInformation(); this.updateClientInformation();
}; };
// The server doesn't tell us when key backup is set up, so we poll // The server doesn't tell us when key backup is set up, so we poll
@ -368,25 +371,26 @@ export default class DeviceListener {
this.shouldRecordClientInformation = !!newValue; this.shouldRecordClientInformation = !!newValue;
if (this.shouldRecordClientInformation && !prevValue) { if (this.shouldRecordClientInformation !== prevValue) {
this.recordClientInformation(); this.updateClientInformation();
} }
}; };
private recordClientInformation = async () => { private updateClientInformation = async () => {
if (!this.shouldRecordClientInformation) {
return;
}
try { try {
await recordClientInformation( if (this.shouldRecordClientInformation) {
MatrixClientPeg.get(), await recordClientInformation(
SdkConfig.get(), MatrixClientPeg.get(),
PlatformPeg.get(), SdkConfig.get(),
); PlatformPeg.get(),
);
} else {
await removeClientInformation(MatrixClientPeg.get());
}
} catch (error) { } catch (error) {
// this is a best effort operation // this is a best effort operation
// log the error without rethrowing // log the error without rethrowing
logger.error('Failed to record client information', error); logger.error('Failed to update client information', error);
} }
}; };
} }

View File

@ -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 => const sanitizeContentString = (value: unknown): string | undefined =>
value && typeof value === 'string' ? value : undefined; value && typeof value === 'string' ? value : undefined;

View File

@ -58,15 +58,16 @@ const getDeviceType = (
return DeviceType.Unknown; return DeviceType.Unknown;
}; };
interface CustomValues {
customDeviceModel?: string;
customDeviceOS?: string;
}
/** /**
* Some mobile model and OS strings are not recognised * Some mobile model and OS strings are not recognised
* by the UA parsing library * by the UA parsing library
* check they exist by hand * check they exist by hand
*/ */
const checkForCustomValues = (userAgent: string): { const checkForCustomValues = (userAgent: string): CustomValues => {
customDeviceModel?: string;
customDeviceOS?: string;
} => {
if (userAgent.includes(BROWSER_KEYWORD)) { if (userAgent.includes(BROWSER_KEYWORD)) {
return {}; return {};
} }
@ -97,13 +98,23 @@ export const parseUserAgent = (userAgent?: string): ExtendedDeviceInformation =>
const device = parser.getDevice(); const device = parser.getDevice();
const operatingSystem = parser.getOS(); const operatingSystem = parser.getOS();
const deviceOperatingSystem = concatenateNameAndVersion(operatingSystem.name, operatingSystem.version);
const deviceModel = concatenateNameAndVersion(device.vendor, device.model);
const client = concatenateNameAndVersion(browser.name, browser.major || browser.version);
const { customDeviceModel, customDeviceOS } = checkForCustomValues(userAgent);
const deviceType = getDeviceType(userAgent, device, browser, operatingSystem); const deviceType = getDeviceType(userAgent, device, browser, operatingSystem);
// OSX versions are frozen at 10.15.17 in UA strings https://chromestatus.com/feature/5452592194781184
// ignore OS version in browser based sessions
const shouldIgnoreOSVersion = deviceType === DeviceType.Web || deviceType === DeviceType.Desktop;
const deviceOperatingSystem = concatenateNameAndVersion(
operatingSystem.name,
shouldIgnoreOSVersion ? undefined : operatingSystem.version,
);
const deviceModel = concatenateNameAndVersion(device.vendor, device.model);
const client = concatenateNameAndVersion(browser.name, browser.version);
// only try to parse custom model and OS when device type is known
const { customDeviceModel, customDeviceOS } = deviceType !== DeviceType.Unknown
? checkForCustomValues(userAgent)
: {} as CustomValues;
return { return {
deviceType, deviceType,
deviceModel: deviceModel || customDeviceModel, deviceModel: deviceModel || customDeviceModel,

View File

@ -17,7 +17,7 @@ limitations under the License.
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { mocked } from "jest-mock"; 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 { logger } from "matrix-js-sdk/src/logger";
import DeviceListener from "../src/DeviceListener"; import DeviceListener from "../src/DeviceListener";
@ -66,6 +66,7 @@ class MockClient extends EventEmitter {
getClientWellKnown = jest.fn(); getClientWellKnown = jest.fn();
getDeviceId = jest.fn().mockReturnValue(deviceId); getDeviceId = jest.fn().mockReturnValue(deviceId);
setAccountData = jest.fn(); setAccountData = jest.fn();
getAccountData = jest.fn();
} }
const mockDispatcher = mocked(dis); const mockDispatcher = mocked(dis);
const flushPromises = async () => await new Promise(process.nextTick); const flushPromises = async () => await new Promise(process.nextTick);
@ -138,7 +139,7 @@ describe('DeviceListener', () => {
await createAndStart(); await createAndStart();
expect(errorLogSpy).toHaveBeenCalledWith( expect(errorLogSpy).toHaveBeenCalledWith(
'Failed to record client information', 'Failed to update client information',
error, error,
); );
}); });
@ -161,19 +162,39 @@ describe('DeviceListener', () => {
}); });
describe('when device client information feature is disabled', () => { 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(() => { beforeEach(() => {
jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false); jest.spyOn(SettingsStore, 'getValue').mockReturnValue(false);
mockClient.getAccountData.mockReturnValue(undefined);
}); });
it('does not save client information on start', async () => { it('does not save client information on start', async () => {
await createAndStart(); 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}`, `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 () => { it('does not save client information on logged in action', async () => {
const instance = await createAndStart(); const instance = await createAndStart();
@ -182,10 +203,7 @@ describe('DeviceListener', () => {
await flushPromises(); await flushPromises();
expect(mockClient.setAccountData).not.toHaveBeenCalledWith( expect(mockClient.setAccountData).not.toHaveBeenCalled();
`io.element.matrix_client_information.${deviceId}`,
{ name: 'Element', url: 'localhost', version: '1.2.3' },
);
}); });
it('saves client information after setting is enabled', async () => { it('saves client information after setting is enabled', async () => {

View File

@ -70,8 +70,8 @@ const DESKTOP_UA = [
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) ElementNightly/2022091301 Chrome/104.0.5112.102 Electron/20.1.1 Safari/537.36",
]; ];
const DESKTOP_EXPECTED_RESULT = [ const DESKTOP_EXPECTED_RESULT = [
makeDeviceExtendedInfo(DeviceType.Desktop, undefined, "Mac OS 10.15.7", "Electron", "20"), makeDeviceExtendedInfo(DeviceType.Desktop, undefined, "Mac OS", "Electron", "20.1.1"),
makeDeviceExtendedInfo(DeviceType.Desktop, undefined, "Windows 10", "Electron", "20"), makeDeviceExtendedInfo(DeviceType.Desktop, undefined, "Windows", "Electron", "20.1.1"),
]; ];
const WEB_UA = [ const WEB_UA = [
@ -88,16 +88,16 @@ const WEB_UA = [
]; ];
const WEB_EXPECTED_RESULT = [ const WEB_EXPECTED_RESULT = [
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS 10.15.7", "Chrome", "104"), makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS", "Chrome", "104.0.5112.102"),
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows 10", "Chrome", "104"), makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows", "Chrome", "104.0.5112.102"),
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS 10.10", "Firefox", "39"), makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS", "Firefox", "39.0"),
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS 10.10.2", "Safari", "8"), makeDeviceExtendedInfo(DeviceType.Web, undefined, "Mac OS", "Safari", "8.0.3"),
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows Vista", "Firefox", "40"), makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows", "Firefox", "40.0"),
makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows 10", "Edge", "12"), makeDeviceExtendedInfo(DeviceType.Web, undefined, "Windows", "Edge", "12.246"),
// using mobile browser // using mobile browser
makeDeviceExtendedInfo(DeviceType.Web, "Apple iPad", "iOS 8.4.1", "Mobile Safari", "8"), makeDeviceExtendedInfo(DeviceType.Web, "Apple iPad", "iOS", "Mobile Safari", "8.0"),
makeDeviceExtendedInfo(DeviceType.Web, "Apple iPhone", "iOS 8.4.1", "Mobile Safari", "8"), makeDeviceExtendedInfo(DeviceType.Web, "Apple iPhone", "iOS", "Mobile Safari", "8.0"),
makeDeviceExtendedInfo(DeviceType.Web, "Samsung SM-G973U", "Android 9", "Chrome", "69"), makeDeviceExtendedInfo(DeviceType.Web, "Samsung SM-G973U", "Android", "Chrome", "69.0.3497.100"),
]; ];
@ -106,6 +106,8 @@ const MISC_UA = [
"Curl Client/1.0", "Curl Client/1.0",
"banana", "banana",
"", "",
// fluffy chat ios
"Dart/2.18 (dart:io)",
]; ];
const MISC_EXPECTED_RESULT = [ const MISC_EXPECTED_RESULT = [
@ -113,6 +115,7 @@ const MISC_EXPECTED_RESULT = [
makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined), makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined),
makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined), makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined),
makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined), makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined),
makeDeviceExtendedInfo(DeviceType.Unknown, undefined, undefined, undefined, undefined),
]; ];
/* eslint-disable max-len */ /* eslint-disable max-len */