mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' into feat/matrix-wysisyg-integration
commit
203f75f755
|
@ -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 {
|
||||||
|
if (this.shouldRecordClientInformation) {
|
||||||
await recordClientInformation(
|
await recordClientInformation(
|
||||||
MatrixClientPeg.get(),
|
MatrixClientPeg.get(),
|
||||||
SdkConfig.get(),
|
SdkConfig.get(),
|
||||||
PlatformPeg.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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue