Compare commits

..

12 Commits

Author SHA1 Message Date
RiotRobot c0e3ad4b83 v17.0.0-rc.3 2022-04-11 11:32:30 +01:00
RiotRobot 5305e373a0 Prepare changelog for v17.0.0-rc.3 2022-04-11 11:32:30 +01:00
Michael Telatynski 877e3df71b [Release] Port multiple threads fixes (#2292)
Co-authored-by: Germain Souquet <germains@element.io>
2022-04-11 11:29:22 +01:00
RiotRobot d705a0ed9e v17.0.0-rc.2 2022-04-08 12:08:04 +01:00
RiotRobot 7023fb1c99 Prepare changelog for v17.0.0-rc.2 2022-04-08 12:08:03 +01:00
RiotRobot fe36dafcc7 v3.42.2-rc.3 2022-04-08 10:40:56 +01:00
RiotRobot 04a6dbbedf Prepare changelog for v3.42.2-rc.3 2022-04-08 10:40:56 +01:00
RiotRobot 29b427aab7 v17.0.0-rc.1 2022-04-08 10:36:14 +01:00
RiotRobot 643b783dec Prepare changelog for v17.0.0-rc.1 2022-04-08 10:36:13 +01:00
Germain 872033a552 Port #2283 to release (#2284) 2022-04-08 10:34:09 +01:00
RiotRobot d457fd6db0 v16.0.2-rc.1 2022-04-05 18:07:38 +01:00
RiotRobot 03f4700bd7 Prepare changelog for v16.0.2-rc.1 2022-04-05 18:07:37 +01:00
28 changed files with 206 additions and 704 deletions
-41
View File
@@ -1,41 +0,0 @@
# Minor white-space adjustments
1d1d59c75744e1f6a2be1cb3e0d1bd9ded5f8025
# Import ordering and spacing: eslint-plugin-import
80aaa6c32b50601f82e0c991c24e5a4590f39463
# Minor white-space adjustment
8fb036ba2d01fab66dc4373802ccf19b5cac8541
# Minor white-space adjustment
b63de6a902a9e1f8ffd7697dea33820fc04f028e
3ca84cfc491b0987eec1f13f13cae58d2032bf54
# Conform to new typescript eslint rules
a87858840b57514603f63e2abbbda4f107f05a77
5cf6684129a921295f5593173f16f192336fe0a2
# Comply with new member-delimiter-style rule
b2ad957d298720d3e026b6bd91be0c403338361a
# Fix semicolons in TS files
e2ec8952e38b8fea3f0ccaa09ecb42feeba0d923
# Migrate to `eslint-plugin-matrix-org`
# and `babel/...` to `@babel/...` migration
09fac77ce0d9bcf6637088c29afab84084f0e739
102704e91a70643bcc09721e14b0d909f0ef55c6
# Eslint formatting
cec00cd303787fa9008b6c48826e75ed438036fa
# Minor eslint changes
68bb8182e4e62d8f450f80c408c4b231b8725f1b
c979ff6696e30ab8983ac416a3590996d84d3560
f4a7395e3a3751a1a8e92dd302c49175a3296ad2
# eslint --fix for dangley commas on function calls
423175f5397910b0afe3112d6fb18283fc7d27d4
# eslint ---fix for prefer-const
7bca05af644e8b997dae81e568a3913d8f18d7ca
# Fix linting on tests
cee7f7a280a8c20bafc21c0a2911f60851f7a7ca
# eslint --fix
0fa9f7c6098822db1ae214f352fd1fe5c248b02c
# eslint --fix for lots of white-space
5abf6b9f208801c5022a47023150b5846cb0b309
# eslint --fix
7ed65407e6cdf292ce3cf659310c68d19dcd52b2
# Switch to ESLint from JSHint (Google eslint rules as a base)
e057956ede9ad1a931ff8050c411aca7907e0394
-1
View File
@@ -29,4 +29,3 @@ jobs:
with:
fail_ci_if_error: false
verbose: true
override_commit: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || '' }}
+6 -26
View File
@@ -1,31 +1,11 @@
Changes in [17.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v17.1.0) (2022-04-26)
==================================================================================================
Changes in [17.0.0-rc.3](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v17.0.0-rc.3) (2022-04-11)
============================================================================================================
## ✨ Features
* Add MatrixClient.doesServerSupportLogoutDevices() for MSC2457 ([\#2297](https://github.com/matrix-org/matrix-js-sdk/pull/2297)).
* Live location sharing - expose room liveBeaconIds ([\#2296](https://github.com/matrix-org/matrix-js-sdk/pull/2296)).
* Support for MSC2457 logout_devices param for setPassword() ([\#2285](https://github.com/matrix-org/matrix-js-sdk/pull/2285)).
* Stabilise token authenticated registration support ([\#2181](https://github.com/matrix-org/matrix-js-sdk/pull/2181)). Contributed by @govynnus.
* Live location sharing - Aggregate beacon locations on beacons ([\#2268](https://github.com/matrix-org/matrix-js-sdk/pull/2268)).
Changes in [17.0.0-rc.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v17.0.0-rc.2) (2022-04-08)
============================================================================================================
## 🐛 Bug Fixes
* Prevent duplicated re-emitter setups in event-mapper ([\#2293](https://github.com/matrix-org/matrix-js-sdk/pull/2293)).
* Make self membership less prone to races ([\#2277](https://github.com/matrix-org/matrix-js-sdk/pull/2277)). Fixes vector-im/element-web#21661.
Changes in [17.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v17.0.0) (2022-04-11)
==================================================================================================
## 🚨 BREAKING CHANGES
* Remove groups and groups-related APIs ([\#2234](https://github.com/matrix-org/matrix-js-sdk/pull/2234)).
## ✨ Features
* Add Element video room type ([\#2273](https://github.com/matrix-org/matrix-js-sdk/pull/2273)).
* Live location sharing - handle redacted beacons ([\#2269](https://github.com/matrix-org/matrix-js-sdk/pull/2269)).
## 🐛 Bug Fixes
* Fix getSessionsNeedingBackup() limit support ([\#2270](https://github.com/matrix-org/matrix-js-sdk/pull/2270)). Contributed by @adamvy.
* Fix issues with /search and /context API handling for threads ([\#2261](https://github.com/matrix-org/matrix-js-sdk/pull/2261)). Fixes vector-im/element-web#21543.
* Prevent exception 'Unable to set up secret storage' ([\#2260](https://github.com/matrix-org/matrix-js-sdk/pull/2260)).
Changes in [17.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v17.0.0-rc.1) (2022-04-08)
============================================================================================================
Changes in [16.0.2-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v16.0.2-rc.1) (2022-04-05)
============================================================================================================
+2 -2
View File
@@ -341,7 +341,7 @@ function printLine(event) {
var maxNameWidth = 15;
if (name.length > maxNameWidth) {
name = name.slice(0, maxNameWidth-1) + "\u2026";
name = name.substr(0, maxNameWidth-1) + "\u2026";
}
if (event.getType() === "m.room.message") {
@@ -398,7 +398,7 @@ function print(str, formatter) {
function fixWidth(str, len) {
if (str.length > len) {
return str.substring(0, len-2) + "\u2026";
return str.substr(0, len-2) + "\u2026";
}
else if (str.length < len) {
return str + new Array(len - str.length).join(" ");
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "17.1.0",
"version": "17.0.0-rc.3",
"description": "Matrix Client-Server SDK for Javascript",
"scripts": {
"prepublishOnly": "yarn build",
@@ -496,7 +496,6 @@ describe("MatrixClient event timelines", function() {
});
it("should handle thread replies with server support by fetching a contiguous thread timeline", async () => {
client.clientOpts.experimentalThreadSupport = true;
Thread.setServerSideSupport(true);
client.stopClient(); // we don't need the client to be syncing at this time
const room = client.getRoom(roomId);
+2 -3
View File
@@ -42,6 +42,7 @@ export const makeBeaconInfoEvent = (
roomId: string,
contentProps: Partial<InfoContentProps> = {},
eventId?: string,
eventTypeSuffix?: string,
): MatrixEvent => {
const {
timeout, isLive, description, assetType,
@@ -50,14 +51,12 @@ export const makeBeaconInfoEvent = (
...contentProps,
};
const event = new MatrixEvent({
type: M_BEACON_INFO.name,
type: `${M_BEACON_INFO.name}.${sender}.${eventTypeSuffix || Date.now()}`,
room_id: roomId,
state_key: sender,
content: makeBeaconInfoContent(timeout, isLive, description, assetType),
});
event.event.origin_server_ts = Date.now();
// live beacons use the beacon_info event id
// set or default this
event.replaceLocalEventId(eventId || `$${Math.random()}-${Math.random()}`);
+6 -3
View File
@@ -16,6 +16,7 @@ limitations under the License.
import { REFERENCE_RELATION } from "matrix-events-sdk";
import { M_BEACON_INFO } from "../../src/@types/beacon";
import { LocationAssetType, M_ASSET, M_LOCATION, M_TIMESTAMP } from "../../src/@types/location";
import { makeBeaconContent, makeBeaconInfoContent } from "../../src/content-helpers";
@@ -35,9 +36,11 @@ describe('Beacon content helpers', () => {
'nice beacon_info',
LocationAssetType.Pin,
)).toEqual({
description: 'nice beacon_info',
timeout: 1234,
live: true,
[M_BEACON_INFO.name]: {
description: 'nice beacon_info',
timeout: 1234,
live: true,
},
[M_TIMESTAMP.name]: mockDateNow,
[M_ASSET.name]: {
type: LocationAssetType.Pin,
+6 -90
View File
@@ -33,7 +33,6 @@ import * as testUtils from "../test-utils/test-utils";
import { makeBeaconInfoContent } from "../../src/content-helpers";
import { M_BEACON_INFO } from "../../src/@types/beacon";
import { Room } from "../../src";
import { makeBeaconEvent } from "../test-utils/beacon";
jest.useFakeTimers();
@@ -816,9 +815,6 @@ describe("MatrixClient", function() {
},
addPendingEvent: jest.fn(),
updatePendingEvent: jest.fn(),
reEmitter: {
reEmit: jest.fn(),
},
};
beforeEach(() => {
@@ -1003,10 +999,10 @@ describe("MatrixClient", function() {
});
it("creates new beacon info", async () => {
await client.unstable_createLiveBeacon(roomId, content);
await client.unstable_createLiveBeacon(roomId, content, '123');
// event type combined
const expectedEventType = M_BEACON_INFO.name;
const expectedEventType = `${M_BEACON_INFO.name}.${userId}.123`;
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
expect(callback).toBeFalsy();
expect(method).toBe('PUT');
@@ -1019,97 +1015,17 @@ describe("MatrixClient", function() {
});
it("updates beacon info with specific event type", async () => {
await client.unstable_setLiveBeacon(roomId, content);
const eventType = `${M_BEACON_INFO.name}.${userId}.456`;
await client.unstable_setLiveBeacon(roomId, eventType, content);
// event type combined
const [, , path, , requestContent] = client.http.authedRequest.mock.calls[0];
expect(path).toEqual(
`/rooms/${encodeURIComponent(roomId)}/state/` +
`${encodeURIComponent(M_BEACON_INFO.name)}/${encodeURIComponent(userId)}`,
`${encodeURIComponent(eventType)}/${encodeURIComponent(userId)}`,
);
expect(requestContent).toEqual(content);
});
describe('processBeaconEvents()', () => {
it('does nothing when events is falsy', () => {
const room = new Room(roomId, client, userId);
const roomStateProcessSpy = jest.spyOn(room.currentState, 'processBeaconEvents');
client.processBeaconEvents(room, undefined);
expect(roomStateProcessSpy).not.toHaveBeenCalled();
});
it('does nothing when events is of length 0', () => {
const room = new Room(roomId, client, userId);
const roomStateProcessSpy = jest.spyOn(room.currentState, 'processBeaconEvents');
client.processBeaconEvents(room, []);
expect(roomStateProcessSpy).not.toHaveBeenCalled();
});
it('calls room states processBeaconEvents with m.beacon events', () => {
const room = new Room(roomId, client, userId);
const roomStateProcessSpy = jest.spyOn(room.currentState, 'processBeaconEvents');
const messageEvent = testUtils.mkMessage({ room: roomId, user: userId, event: true });
const beaconEvent = makeBeaconEvent(userId);
client.processBeaconEvents(room, [messageEvent, beaconEvent]);
expect(roomStateProcessSpy).toHaveBeenCalledWith([beaconEvent]);
});
});
});
describe("setPassword", () => {
const auth = { session: 'abcdef', type: 'foo' };
const newPassword = 'newpassword';
const callback = () => {};
const passwordTest = (expectedRequestContent: any, expectedCallback?: Function) => {
const [callback, method, path, queryParams, requestContent] = client.http.authedRequest.mock.calls[0];
if (expectedCallback) {
expect(callback).toBe(expectedCallback);
} else {
expect(callback).toBeFalsy();
}
expect(method).toBe('POST');
expect(path).toEqual('/account/password');
expect(queryParams).toBeFalsy();
expect(requestContent).toEqual(expectedRequestContent);
};
beforeEach(() => {
client.http.authedRequest.mockClear().mockResolvedValue({});
});
it("no logout_devices specified", async () => {
await client.setPassword(auth, newPassword);
passwordTest({ auth, new_password: newPassword });
});
it("no logout_devices specified + callback", async () => {
await client.setPassword(auth, newPassword, callback);
passwordTest({ auth, new_password: newPassword }, callback);
});
it("overload logoutDevices=true", async () => {
await client.setPassword(auth, newPassword, true);
passwordTest({ auth, new_password: newPassword, logout_devices: true });
});
it("overload logoutDevices=true + callback", async () => {
await client.setPassword(auth, newPassword, true, callback);
passwordTest({ auth, new_password: newPassword, logout_devices: true }, callback);
});
it("overload logoutDevices=false", async () => {
await client.setPassword(auth, newPassword, false);
passwordTest({ auth, new_password: newPassword, logout_devices: false });
});
it("overload logoutDevices=false + callback", async () => {
await client.setPassword(auth, newPassword, false, callback);
passwordTest({ auth, new_password: newPassword, logout_devices: false }, callback);
});
});
});
+30 -123
View File
@@ -14,12 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventType } from "../../../src";
import { M_BEACON_INFO } from "../../../src/@types/beacon";
import {
isTimestampInDuration,
isBeaconInfoEventType,
Beacon,
BeaconEvent,
} from "../../../src/models/beacon";
import { makeBeaconEvent, makeBeaconInfoEvent } from "../../test-utils/beacon";
import { makeBeaconInfoEvent } from "../../test-utils/beacon";
jest.useFakeTimers();
@@ -54,9 +57,27 @@ describe('Beacon', () => {
});
});
describe('isBeaconInfoEventType', () => {
it.each([
EventType.CallAnswer,
`prefix.${M_BEACON_INFO.name}`,
`prefix.${M_BEACON_INFO.altName}`,
])('returns false for %s', (type) => {
expect(isBeaconInfoEventType(type)).toBe(false);
});
it.each([
M_BEACON_INFO.name,
M_BEACON_INFO.altName,
`${M_BEACON_INFO.name}.@test:server.org.12345`,
`${M_BEACON_INFO.altName}.@test:server.org.12345`,
])('returns true for %s', (type) => {
expect(isBeaconInfoEventType(type)).toBe(true);
});
});
describe('Beacon', () => {
const userId = '@user:server.org';
const userId2 = '@user2:server.org';
const roomId = '$room:server.org';
// 14.03.2022 16:15
const now = 1647270879403;
@@ -67,7 +88,6 @@ describe('Beacon', () => {
// without timeout of 3 hours
let liveBeaconEvent;
let notLiveBeaconEvent;
let user2BeaconEvent;
const advanceDateAndTime = (ms: number) => {
// bc liveness check uses Date.now we have to advance this mock
@@ -87,21 +107,14 @@ describe('Beacon', () => {
isLive: true,
},
'$live123',
'$live123',
);
notLiveBeaconEvent = makeBeaconInfoEvent(
userId,
roomId,
{ timeout: HOUR_MS * 3, isLive: false },
'$dead123',
);
user2BeaconEvent = makeBeaconInfoEvent(
userId2,
roomId,
{
timeout: HOUR_MS * 3,
isLive: true,
},
'$user2live123',
'$dead123',
);
// back to now
@@ -120,7 +133,7 @@ describe('Beacon', () => {
expect(beacon.isLive).toEqual(true);
expect(beacon.beaconInfoOwner).toEqual(userId);
expect(beacon.beaconInfoEventType).toEqual(liveBeaconEvent.getType());
expect(beacon.identifier).toEqual(`${roomId}_${userId}`);
expect(beacon.identifier).toEqual(liveBeaconEvent.getType());
expect(beacon.beaconInfo).toBeTruthy();
});
@@ -158,27 +171,8 @@ describe('Beacon', () => {
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
expect(() => beacon.update(user2BeaconEvent)).toThrow();
// didnt update
expect(beacon.identifier).toEqual(`${roomId}_${userId}`);
});
it('does not update with an older event', () => {
const beacon = new Beacon(liveBeaconEvent);
const emitSpy = jest.spyOn(beacon, 'emit').mockClear();
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
const oldUpdateEvent = makeBeaconInfoEvent(
userId,
roomId,
);
// less than the original event
oldUpdateEvent.event.origin_server_ts = liveBeaconEvent.event.origin_server_ts - 1000;
beacon.update(oldUpdateEvent);
// didnt update
expect(emitSpy).not.toHaveBeenCalled();
expect(beacon.beaconInfoId).toEqual(liveBeaconEvent.getId());
expect(() => beacon.update(notLiveBeaconEvent)).toThrow();
expect(beacon.isLive).toEqual(true);
});
it('updates event', () => {
@@ -188,7 +182,7 @@ describe('Beacon', () => {
expect(beacon.isLive).toEqual(true);
const updatedBeaconEvent = makeBeaconInfoEvent(
userId, roomId, { timeout: HOUR_MS * 3, isLive: false }, '$live123');
userId, roomId, { timeout: HOUR_MS * 3, isLive: false }, '$live123', '$live123');
beacon.update(updatedBeaconEvent);
expect(beacon.isLive).toEqual(false);
@@ -206,6 +200,7 @@ describe('Beacon', () => {
roomId,
{ timeout: HOUR_MS * 3, isLive: false },
beacon.beaconInfoId,
'$live123',
);
beacon.update(updatedBeaconEvent);
@@ -282,93 +277,5 @@ describe('Beacon', () => {
expect(emitSpy).toHaveBeenCalledTimes(1);
});
});
describe('addLocations', () => {
it('ignores locations when beacon is not live', () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: false }));
const emitSpy = jest.spyOn(beacon, 'emit');
beacon.addLocations([
makeBeaconEvent(userId, { beaconInfoId: beacon.beaconInfoId, timestamp: now + 1 }),
]);
expect(beacon.latestLocationState).toBeFalsy();
expect(emitSpy).not.toHaveBeenCalled();
});
it('ignores locations outside the beacon live duration', () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
const emitSpy = jest.spyOn(beacon, 'emit');
beacon.addLocations([
// beacon has now + 60000 live period
makeBeaconEvent(userId, { beaconInfoId: beacon.beaconInfoId, timestamp: now + 100000 }),
]);
expect(beacon.latestLocationState).toBeFalsy();
expect(emitSpy).not.toHaveBeenCalled();
});
it('sets latest location state to most recent location', () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
const emitSpy = jest.spyOn(beacon, 'emit');
const locations = [
// older
makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:foo', timestamp: now + 1 },
),
// newer
makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:bar', timestamp: now + 10000 },
),
// not valid
makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:baz', timestamp: now - 5 },
),
];
beacon.addLocations(locations);
const expectedLatestLocation = {
description: undefined,
timestamp: now + 10000,
uri: 'geo:bar',
};
// the newest valid location
expect(beacon.latestLocationState).toEqual(expectedLatestLocation);
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.LocationUpdate, expectedLatestLocation);
});
it('ignores locations that are less recent that the current latest location', () => {
const beacon = new Beacon(makeBeaconInfoEvent(userId, roomId, { isLive: true, timeout: 60000 }));
const olderLocation = makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:foo', timestamp: now + 1 },
);
const newerLocation = makeBeaconEvent(
userId, { beaconInfoId: beacon.beaconInfoId, uri: 'geo:bar', timestamp: now + 10000 },
);
beacon.addLocations([newerLocation]);
// latest location set to newerLocation
expect(beacon.latestLocationState).toEqual(expect.objectContaining({
uri: 'geo:bar',
}));
const emitSpy = jest.spyOn(beacon, 'emit').mockClear();
// add older location
beacon.addLocations([olderLocation]);
// no change
expect(beacon.latestLocationState).toEqual(expect.objectContaining({
uri: 'geo:bar',
}));
// no emit
expect(emitSpy).not.toHaveBeenCalled();
});
});
});
});
+17 -70
View File
@@ -1,8 +1,8 @@
import * as utils from "../test-utils/test-utils";
import { makeBeaconEvent, makeBeaconInfoEvent } from "../test-utils/beacon";
import { makeBeaconInfoEvent } from "../test-utils/beacon";
import { filterEmitCallsByEventType } from "../test-utils/emitter";
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
import { BeaconEvent, getBeaconInfoIdentifier } from "../../src/models/beacon";
import { BeaconEvent } from "../../src/models/beacon";
describe("RoomState", function() {
const roomId = "!foo:bar";
@@ -260,7 +260,7 @@ describe("RoomState", function() {
state.setStateEvents([beaconEvent]);
expect(state.beacons.size).toEqual(1);
const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
const beaconInstance = state.beacons.get(beaconEvent.getType());
expect(beaconInstance).toBeTruthy();
expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
});
@@ -275,49 +275,49 @@ describe("RoomState", function() {
// no beacon added
expect(state.beacons.size).toEqual(0);
expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
expect(state.beacons.get(redactedBeaconEvent.getType)).toBeFalsy();
// no new beacon emit
expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
});
it('updates existing beacon info events in state', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId, beaconId);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
const beaconInstance = state.beacons.get(beaconEvent.getType());
expect(beaconInstance.isLive).toEqual(true);
state.setStateEvents([updatedBeaconEvent]);
// same Beacon
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
expect(state.beacons.get(beaconEvent.getType())).toBe(beaconInstance);
// updated liveness
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent)).isLive).toEqual(false);
expect(state.beacons.get(beaconEvent.getType()).isLive).toEqual(false);
});
it('destroys and removes redacted beacon events', () => {
const beaconId = '$beacon1';
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
const redactionEvent = { event: { type: 'm.room.redaction', redacts: beaconEvent.getId() } };
const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId, beaconId);
const redactionEvent = { event: { type: 'm.room.redaction' } };
redactedBeaconEvent.makeRedacted(redactionEvent);
state.setStateEvents([beaconEvent]);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
const beaconInstance = state.beacons.get(beaconEvent.getType());
const destroySpy = jest.spyOn(beaconInstance, 'destroy');
expect(beaconInstance.isLive).toEqual(true);
state.setStateEvents([redactedBeaconEvent]);
expect(destroySpy).toHaveBeenCalled();
expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
expect(state.beacons.get(beaconEvent.getType())).toBe(undefined);
});
it('updates live beacon ids once after setting state events', () => {
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2');
const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1', '$beacon1');
const deadBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, '$beacon2', '$beacon2');
const emitSpy = jest.spyOn(state, 'emit');
@@ -334,7 +334,7 @@ describe("RoomState", function() {
state.setStateEvents([updatedLiveBeaconEvent]);
expect(state.hasLiveBeacons).toBe(false);
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3);
expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(2);
expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
});
});
@@ -712,57 +712,4 @@ describe("RoomState", function() {
expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false);
});
});
describe('processBeaconEvents', () => {
const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1', '$beacon1');
const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2', '$beacon2');
it('does nothing when state has no beacons', () => {
const emitSpy = jest.spyOn(state, 'emit');
state.processBeaconEvents([makeBeaconEvent(userA, { beaconInfoId: '$beacon1' })]);
expect(emitSpy).not.toHaveBeenCalled();
});
it('does nothing when there are no events', () => {
state.setStateEvents([beacon1, beacon2]);
const emitSpy = jest.spyOn(state, 'emit').mockClear();
state.processBeaconEvents([]);
expect(emitSpy).not.toHaveBeenCalled();
});
it('discards events for beacons that are not in state', () => {
const location = makeBeaconEvent(userA, {
beaconInfoId: 'some-other-beacon',
});
state.setStateEvents([beacon1, beacon2]);
const emitSpy = jest.spyOn(state, 'emit').mockClear();
state.processBeaconEvents([location]);
expect(emitSpy).not.toHaveBeenCalled();
});
it('adds locations to beacons', () => {
const location1 = makeBeaconEvent(userA, {
beaconInfoId: '$beacon1', timestamp: Date.now() + 1,
});
const location2 = makeBeaconEvent(userA, {
beaconInfoId: '$beacon1', timestamp: Date.now() + 2,
});
const location3 = makeBeaconEvent(userB, {
beaconInfoId: 'some-other-beacon',
});
state.setStateEvents([beacon1, beacon2]);
expect(state.beacons.size).toEqual(2);
const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beacon1));
const addLocationsSpy = jest.spyOn(beaconInstance, 'addLocations');
state.processBeaconEvents([location1, location2, location3]);
expect(addLocationsSpy).toHaveBeenCalledTimes(1);
// only called with locations for beacon1
expect(addLocationsSpy).toHaveBeenCalledWith([location1, location2]);
});
});
});
-29
View File
@@ -10,11 +10,8 @@ import {
prevString,
simpleRetryOperation,
stringToBase,
sortEventsByLatestContentTimestamp,
} from "../../src/utils";
import { logger } from "../../src/logger";
import { mkMessage } from "../test-utils/test-utils";
import { makeBeaconEvent } from "../test-utils/beacon";
// TODO: Fix types throughout
@@ -509,30 +506,4 @@ describe("utils", function() {
});
});
});
describe('sortEventsByLatestContentTimestamp', () => {
const roomId = '!room:server';
const userId = '@user:server';
const eventWithoutContentTimestamp = mkMessage({ room: roomId, user: userId, event: true });
// m.beacon events have timestamp in content
const beaconEvent1 = makeBeaconEvent(userId, { timestamp: 1648804528557 });
const beaconEvent2 = makeBeaconEvent(userId, { timestamp: 1648804528558 });
const beaconEvent3 = makeBeaconEvent(userId, { timestamp: 1648804528000 });
const beaconEvent4 = makeBeaconEvent(userId, { timestamp: 0 });
it('sorts events with timestamps as later than events without', () => {
expect(
[beaconEvent4, eventWithoutContentTimestamp, beaconEvent1]
.sort(utils.sortEventsByLatestContentTimestamp),
).toEqual([
beaconEvent1, beaconEvent4, eventWithoutContentTimestamp,
]);
});
it('sorts by content timestamps correctly', () => {
expect(
[beaconEvent1, beaconEvent2, beaconEvent3].sort(sortEventsByLatestContentTimestamp),
).toEqual([beaconEvent2, beaconEvent1, beaconEvent3]);
});
});
});
+19 -9
View File
@@ -14,14 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { RELATES_TO_RELATIONSHIP, REFERENCE_RELATION } from "matrix-events-sdk";
import { EitherAnd, RELATES_TO_RELATIONSHIP, REFERENCE_RELATION } from "matrix-events-sdk";
import { UnstableValue } from "../NamespacedValue";
import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
/**
* Beacon info and beacon event types as described in MSC3672
* https://github.com/matrix-org/matrix-spec-proposals/pull/3672
* Beacon info and beacon event types as described in MSC3489
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
*/
/**
@@ -60,11 +60,16 @@ import { MAssetEvent, MLocationEvent, MTimestampEvent } from "./location";
* }
*/
/**
* Variable event type for m.beacon_info
*/
export const M_BEACON_INFO_VARIABLE = new UnstableValue("m.beacon_info.*", "org.matrix.msc3489.beacon_info.*");
/**
* Non-variable type for m.beacon_info event content
*/
export const M_BEACON_INFO = new UnstableValue("m.beacon_info", "org.matrix.msc3672.beacon_info");
export const M_BEACON = new UnstableValue("m.beacon", "org.matrix.msc3672.beacon");
export const M_BEACON_INFO = new UnstableValue("m.beacon_info", "org.matrix.msc3489.beacon_info");
export const M_BEACON = new UnstableValue("m.beacon", "org.matrix.msc3489.beacon");
export type MBeaconInfoContent = {
description?: string;
@@ -75,11 +80,16 @@ export type MBeaconInfoContent = {
live?: boolean;
};
export type MBeaconInfoEvent = EitherAnd<
{ [M_BEACON_INFO.name]: MBeaconInfoContent },
{ [M_BEACON_INFO.altName]: MBeaconInfoContent }
>;
/**
* m.beacon_info Event example from the spec
* https://github.com/matrix-org/matrix-spec-proposals/pull/3672
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
* {
"type": "m.beacon_info",
"type": "m.beacon_info.@matthew:matrix.org.1",
"state_key": "@matthew:matrix.org",
"content": {
"m.beacon_info": {
@@ -98,7 +108,7 @@ export type MBeaconInfoContent = {
* m.beacon_info.* event content
*/
export type MBeaconInfoEventContent = &
MBeaconInfoContent &
MBeaconInfoEvent &
// creation timestamp of the beacon on the client
MTimestampEvent &
// the type of asset being tracked as per MSC3488
@@ -106,7 +116,7 @@ export type MBeaconInfoEventContent = &
/**
* m.beacon event example
* https://github.com/matrix-org/matrix-spec-proposals/pull/3672
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
*
* {
"type": "m.beacon",
+21 -79
View File
@@ -180,7 +180,7 @@ import { MediaHandler } from "./webrtc/mediaHandler";
import { IRefreshTokenResponse } from "./@types/auth";
import { TypedEventEmitter } from "./models/typed-event-emitter";
import { Thread, THREAD_RELATION_TYPE } from "./models/thread";
import { MBeaconInfoEventContent, M_BEACON, M_BEACON_INFO } from "./@types/beacon";
import { MBeaconInfoEventContent, M_BEACON_INFO_VARIABLE } from "./@types/beacon";
export type Store = IStore;
export type SessionStore = WebStorageSessionStore;
@@ -3649,30 +3649,39 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Create an m.beacon_info event
* @param {string} roomId
* @param {MBeaconInfoEventContent} beaconInfoContent
* @param {string} eventTypeSuffix - string to suffix event type
* to make event type unique.
* See MSC3489 for more context
* https://github.com/matrix-org/matrix-spec-proposals/pull/3489
* @returns {ISendEventResponse}
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public async unstable_createLiveBeacon(
roomId: Room["roomId"],
beaconInfoContent: MBeaconInfoEventContent,
eventTypeSuffix: string,
) {
return this.unstable_setLiveBeacon(roomId, beaconInfoContent);
const userId = this.getUserId();
const eventType = M_BEACON_INFO_VARIABLE.name.replace('*', `${userId}.${eventTypeSuffix}`);
return this.unstable_setLiveBeacon(roomId, eventType, beaconInfoContent);
}
/**
* Upsert a live beacon event
* using a specific m.beacon_info.* event variable type
* @param {string} roomId string
* @param {string} beaconInfoEventType event type including variable suffix
* @param {MBeaconInfoEventContent} beaconInfoContent
* @returns {ISendEventResponse}
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public async unstable_setLiveBeacon(
roomId: string,
beaconInfoEventType: string,
beaconInfoContent: MBeaconInfoEventContent,
) {
const userId = this.getUserId();
return this.sendStateEvent(roomId, M_BEACON_INFO.name, beaconInfoContent, userId);
return this.sendStateEvent(roomId, beaconInfoEventType, beaconInfoContent, userId);
}
/**
@@ -3776,17 +3785,9 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const thread = room?.threads.get(threadId);
if (thread) {
localEvent.setThread(thread);
localEvent.setThreadId(thread.id);
}
// set up re-emitter for this new event - this is normally the job of EventMapper but we don't use it here
this.reEmitter.reEmit(localEvent, [
MatrixEventEvent.Replaced,
MatrixEventEvent.VisibilityChange,
]);
room?.reEmitter.reEmit(localEvent, [
MatrixEventEvent.BeforeRedaction,
]);
// if this is a relation or redaction of an event
// that hasn't been sent yet (e.g. with a local id starting with a ~)
// then listen for the remote echo of that event so that by the time
@@ -5177,7 +5178,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const [timelineEvents, threadedEvents] = room.partitionThreadedEvents(matrixEvents);
this.processBeaconEvents(room, matrixEvents);
room.addEventsToTimeline(timelineEvents, true, room.getLiveTimeline());
await this.processThreadEvents(room, threadedEvents, true);
@@ -5277,10 +5277,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// Where the event is a thread reply (not a root) and running in MSC-enabled mode the Thread timeline only
// functions contiguously, so we have to jump through some hoops to get our target event in it.
// XXX: workaround for https://github.com/vector-im/element-meta/issues/150
if (Thread.hasServerSideSupport &&
this.supportsExperimentalThreads() &&
event.isRelation(THREAD_RELATION_TYPE.name)
) {
if (Thread.hasServerSideSupport && event.isRelation(THREAD_RELATION_TYPE.name)) {
const [, threadedEvents] = timelineSet.room.partitionThreadedEvents(events);
const thread = await timelineSet.room.createThreadFetchRoot(event.threadRootId, threadedEvents, true);
@@ -5320,7 +5317,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
timelineSet.addEventsToTimeline(timelineEvents, true, timeline, res.start);
// The target event is not in a thread but process the contextual events, so we can show any threads around it.
await this.processThreadEvents(timelineSet.room, threadedEvents, true);
this.processBeaconEvents(timelineSet.room, events);
// There is no guarantee that the event ended up in "timeline" (we might have switched to a neighbouring
// timeline) - so check the room's index again. On the other hand, there's no guarantee the event ended up
@@ -5451,7 +5447,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
// in the notification timeline set
const timelineSet = eventTimeline.getTimelineSet();
timelineSet.addEventsToTimeline(matrixEvents, backwards, eventTimeline, token);
this.processBeaconEvents(timelineSet.room, matrixEvents);
// if we've hit the end of the timeline, we need to stop trying to
// paginate. We need to keep the 'forwards' token though, to make sure
@@ -5488,7 +5483,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
const timelineSet = eventTimeline.getTimelineSet();
const [timelineEvents, threadedEvents] = timelineSet.room.partitionThreadedEvents(matrixEvents);
timelineSet.addEventsToTimeline(timelineEvents, backwards, eventTimeline, token);
this.processBeaconEvents(timelineSet.room, matrixEvents);
await this.processThreadEvents(room, threadedEvents, backwards);
// if we've hit the end of the timeline, we need to stop trying to
@@ -6412,18 +6406,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public async _unstable_getSharedRooms(userId: string): Promise<string[]> { // eslint-disable-line
const sharedRoomsSupport = await this.doesServerSupportUnstableFeature("uk.half-shot.msc2666");
const mutualRoomsSupport = await this.doesServerSupportUnstableFeature("uk.half-shot.msc2666.mutual_rooms");
if (!sharedRoomsSupport && !mutualRoomsSupport) {
throw Error('Server does not support mutual_rooms API');
if (!(await this.doesServerSupportUnstableFeature("uk.half-shot.msc2666"))) {
throw Error('Server does not support shared_rooms API');
}
const path = utils.encodeUri(
`/uk.half-shot.msc2666/user/${mutualRoomsSupport ? 'mutual_rooms' : 'shared_rooms'}/$userId`,
{ $userId: userId },
);
const path = utils.encodeUri("/uk.half-shot.msc2666/user/shared_rooms/$userId", {
$userId: userId,
});
const res = await this.http.authedRequest<{ joined: string[] }>(
undefined, Method.Get, path, undefined, undefined,
{ prefix: PREFIX_UNSTABLE },
@@ -6593,14 +6581,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
}
}
/**
* Query the server to see if it supports the MSC2457 `logout_devices` parameter when setting password
* @return {Promise<boolean>} true if server supports the `logout_devices` parameter
*/
public doesServerSupportLogoutDevices(): Promise<boolean> {
return this.isVersionSupported("r0.6.1");
}
/**
* Get if lazy loading members is being used.
* @return {boolean} Whether or not members are lazy loaded by this client
@@ -7844,45 +7824,18 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* Make a request to change your password.
* @param {Object} authDict
* @param {string} newPassword The new desired password.
* @param {boolean} logoutDevices Should all sessions be logged out after the password change. Defaults to true.
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: TODO
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
public setPassword(
authDict: any,
newPassword: string,
callback?: Callback,
): Promise<{}>;
public setPassword(
authDict: any,
newPassword: string,
logoutDevices: boolean,
callback?: Callback,
): Promise<{}>;
public setPassword(
authDict: any,
newPassword: string,
logoutDevices?: Callback | boolean,
callback?: Callback,
): Promise<{}> {
if (typeof logoutDevices === 'function') {
callback = logoutDevices;
}
if (typeof logoutDevices !== 'boolean') {
// Use backwards compatible behaviour of not specifying logout_devices
// This way it is left up to the server:
logoutDevices = undefined;
}
public setPassword(authDict: any, newPassword: string, callback?: Callback): Promise<any> { // TODO: Types
const path = "/account/password";
const data = {
'auth': authDict,
'new_password': newPassword,
'logout_devices': logoutDevices,
};
return this.http.authedRequest<{}>(
return this.http.authedRequest(
callback, Method.Post, path, null, data,
);
}
@@ -8907,17 +8860,6 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
await room.processThreadedEvents(threadedEvents, toStartOfTimeline);
}
public processBeaconEvents(
room: Room,
events?: MatrixEvent[],
): void {
if (!events?.length) {
return;
}
const beaconEvents = events.filter(event => M_BEACON.matches(event.getType()));
room.currentState.processBeaconEvents(beaconEvents);
}
/**
* Fetches the user_id of the configured access token.
*/
+10 -23
View File
@@ -18,7 +18,7 @@ limitations under the License.
import { REFERENCE_RELATION } from "matrix-events-sdk";
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent } from "./@types/beacon";
import { MBeaconEventContent, MBeaconInfoContent, MBeaconInfoEventContent, M_BEACON_INFO } from "./@types/beacon";
import { MsgType } from "./@types/event";
import { TEXT_NODE_TYPE } from "./@types/extensible_events";
import {
@@ -208,9 +208,11 @@ export const makeBeaconInfoContent: MakeBeaconInfoContent = (
assetType,
timestamp,
) => ({
description,
timeout,
live: isLive,
[M_BEACON_INFO.name]: {
description,
timeout,
live: isLive,
},
[M_TIMESTAMP.name]: timestamp || Date.now(),
[M_ASSET.name]: {
type: assetType ?? LocationAssetType.Self,
@@ -225,7 +227,7 @@ export type BeaconInfoState = MBeaconInfoContent & {
* Flatten beacon info event content
*/
export const parseBeaconInfoContent = (content: MBeaconInfoEventContent): BeaconInfoState => {
const { description, timeout, live } = content;
const { description, timeout, live } = M_BEACON_INFO.findIn<MBeaconInfoContent>(content);
const { type: assetType } = M_ASSET.findIn<MAssetContent>(content);
const timestamp = M_TIMESTAMP.findIn<number>(content);
@@ -241,14 +243,14 @@ export const parseBeaconInfoContent = (content: MBeaconInfoEventContent): Beacon
export type MakeBeaconContent = (
uri: string,
timestamp: number,
beaconInfoEventId: string,
beaconInfoId: string,
description?: string,
) => MBeaconEventContent;
export const makeBeaconContent: MakeBeaconContent = (
uri,
timestamp,
beaconInfoEventId,
beaconInfoId,
description,
) => ({
[M_LOCATION.name]: {
@@ -258,21 +260,6 @@ export const makeBeaconContent: MakeBeaconContent = (
[M_TIMESTAMP.name]: timestamp,
"m.relates_to": {
rel_type: REFERENCE_RELATION.name,
event_id: beaconInfoEventId,
event_id: beaconInfoId,
},
});
export type BeaconLocationState = MLocationContent & {
timestamp: number;
};
export const parseBeaconContent = (content: MBeaconEventContent): BeaconLocationState => {
const { description, uri } = M_LOCATION.findIn<MLocationContent>(content);
const timestamp = M_TIMESTAMP.findIn<number>(content);
return {
description,
uri,
timestamp,
};
};
+2 -2
View File
@@ -73,8 +73,8 @@ export function getHttpUriForMxc(
const fragmentOffset = serverAndMediaId.indexOf("#");
let fragment = "";
if (fragmentOffset >= 0) {
fragment = serverAndMediaId.slice(fragmentOffset);
serverAndMediaId = serverAndMediaId.slice(0, fragmentOffset);
fragment = serverAndMediaId.substr(fragmentOffset);
serverAndMediaId = serverAndMediaId.substr(0, fragmentOffset);
}
const urlParams = (Object.keys(params).length === 0 ? "" : ("?" + utils.encodeParams(params)));
@@ -228,8 +228,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
// (hence 43 characters long).
func({
senderKey: key.slice(KEY_INBOUND_SESSION_PREFIX.length, KEY_INBOUND_SESSION_PREFIX.length + 43),
sessionId: key.slice(KEY_INBOUND_SESSION_PREFIX.length + 44),
senderKey: key.substr(KEY_INBOUND_SESSION_PREFIX.length, 43),
sessionId: key.substr(KEY_INBOUND_SESSION_PREFIX.length + 44),
sessionData: getJsonItem(this.store, key),
});
}
@@ -299,7 +299,7 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
if (key.startsWith(prefix)) {
const roomId = key.slice(prefix.length);
const roomId = key.substr(prefix.length);
result[roomId] = getJsonItem(this.store, key);
}
}
@@ -313,8 +313,8 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
for (const session in sessionsNeedingBackup) {
if (Object.prototype.hasOwnProperty.call(sessionsNeedingBackup, session)) {
// see getAllEndToEndInboundGroupSessions for the magic number explanations
const senderKey = session.slice(0, 43);
const sessionId = session.slice(44);
const senderKey = session.substr(0, 43);
const sessionId = session.substr(44);
this.getEndToEndInboundGroupSession(
senderKey, sessionId, null,
(sessionData) => {
+4 -4
View File
@@ -418,8 +418,8 @@ export class MemoryCryptoStore implements CryptoStore {
// (hence 43 characters long).
func({
senderKey: key.slice(0, 43),
sessionId: key.slice(44),
senderKey: key.substr(0, 43),
sessionId: key.substr(44),
sessionData: this.inboundGroupSessions[key],
});
}
@@ -482,8 +482,8 @@ export class MemoryCryptoStore implements CryptoStore {
for (const session in this.sessionsNeedingBackup) {
if (this.inboundGroupSessions[session]) {
sessions.push({
senderKey: session.slice(0, 43),
sessionId: session.slice(44),
senderKey: session.substr(0, 43),
sessionId: session.substr(44),
sessionData: this.inboundGroupSessions[session],
});
if (limit && session.length >= limit) {
+1 -3
View File
@@ -25,7 +25,7 @@ export interface MapperOpts {
}
export function eventMapperFor(client: MatrixClient, options: MapperOpts): EventMapper {
let preventReEmit = Boolean(options.preventReEmit);
const preventReEmit = Boolean(options.preventReEmit);
const decrypt = options.decrypt !== false;
function mapper(plainOldJsObject: Partial<IEvent>) {
@@ -43,8 +43,6 @@ export function eventMapperFor(client: MatrixClient, options: MapperOpts): Event
} else {
// merge the latest unsigned data from the server
event.setUnsigned({ ...event.getUnsigned(), ...plainOldJsObject.unsigned });
// prevent doubling up re-emitters
preventReEmit = true;
}
const thread = room?.findThreadForEvent(event);
+1 -1
View File
@@ -36,7 +36,7 @@ import {
function matchesWildcard(actualValue: string, filterValue: string): boolean {
if (filterValue.endsWith("*")) {
const typePrefix = filterValue.slice(0, -1);
return actualValue.slice(0, typePrefix.length) === typePrefix;
return actualValue.substr(0, typePrefix.length) === typePrefix;
} else {
return actualValue === filterValue;
}
+2 -7
View File
@@ -60,11 +60,7 @@ export enum AuthType {
Sso = "m.login.sso",
SsoUnstable = "org.matrix.login.sso",
Dummy = "m.login.dummy",
RegistrationToken = "m.login.registration_token",
// For backwards compatability with servers that have not yet updated to
// use the stable "m.login.registration_token" type.
// The authentication flow is the same in both cases.
UnstableRegistrationToken = "org.matrix.msc3231.login.registration_token",
RegistrationToken = "org.matrix.msc3231.login.registration_token",
}
export interface IAuthDict {
@@ -83,8 +79,7 @@ export interface IAuthDict {
// eslint-disable-next-line camelcase
threepid_creds?: any;
threepidCreds?: any;
// For m.login.registration_token type
token?: string;
registrationToken?: string;
}
class NoAuthFlowFoundError extends Error {
+12 -66
View File
@@ -14,27 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MBeaconEventContent } from "../@types/beacon";
import { M_TIMESTAMP } from "../@types/location";
import { BeaconInfoState, BeaconLocationState, parseBeaconContent, parseBeaconInfoContent } from "../content-helpers";
import { M_BEACON_INFO } from "../@types/beacon";
import { BeaconInfoState, parseBeaconInfoContent } from "../content-helpers";
import { MatrixEvent } from "../matrix";
import { sortEventsByLatestContentTimestamp } from "../utils";
import { TypedEventEmitter } from "./typed-event-emitter";
export enum BeaconEvent {
New = "Beacon.new",
Update = "Beacon.update",
LivenessChange = "Beacon.LivenessChange",
Destroy = "Beacon.Destroy",
LocationUpdate = "Beacon.LocationUpdate",
Destroy = "Destroy",
}
export type BeaconEventHandlerMap = {
[BeaconEvent.Update]: (event: MatrixEvent, beacon: Beacon) => void;
[BeaconEvent.LivenessChange]: (isLive: boolean, beacon: Beacon) => void;
[BeaconEvent.Destroy]: (beaconIdentifier: string) => void;
[BeaconEvent.LocationUpdate]: (locationState: BeaconLocationState) => void;
[BeaconEvent.Destroy]: (beaconIdentifier: string) => void;
};
export const isTimestampInDuration = (
@@ -43,19 +38,16 @@ export const isTimestampInDuration = (
timestamp: number,
): boolean => timestamp >= startTimestamp && startTimestamp + durationMs >= timestamp;
// beacon info events are uniquely identified by
// `<roomId>_<state_key>`
export type BeaconIdentifier = string;
export const getBeaconInfoIdentifier = (event: MatrixEvent): BeaconIdentifier =>
`${event.getRoomId()}_${event.getStateKey()}`;
export const isBeaconInfoEventType = (type: string) =>
type.startsWith(M_BEACON_INFO.name) ||
type.startsWith(M_BEACON_INFO.altName);
// https://github.com/matrix-org/matrix-spec-proposals/pull/3672
// https://github.com/matrix-org/matrix-spec-proposals/pull/3489
export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.New>, BeaconEventHandlerMap> {
public readonly roomId: string;
private _beaconInfo: BeaconInfoState;
private _isLive: boolean;
private livenessWatchInterval: number;
private _latestLocationState: BeaconLocationState | undefined;
constructor(
private rootEvent: MatrixEvent,
@@ -69,8 +61,8 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
return this._isLive;
}
public get identifier(): BeaconIdentifier {
return getBeaconInfoIdentifier(this.rootEvent);
public get identifier(): string {
return this.beaconInfoEventType;
}
public get beaconInfoId(): string {
@@ -89,23 +81,14 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
return this._beaconInfo;
}
public get latestLocationState(): BeaconLocationState | undefined {
return this._latestLocationState;
}
public update(beaconInfoEvent: MatrixEvent): void {
if (getBeaconInfoIdentifier(beaconInfoEvent) !== this.identifier) {
if (beaconInfoEvent.getType() !== this.beaconInfoEventType) {
throw new Error('Invalid updating event');
}
// don't update beacon with an older event
if (beaconInfoEvent.event.origin_server_ts < this.rootEvent.event.origin_server_ts) {
return;
}
this.rootEvent = beaconInfoEvent;
this.setBeaconInfo(this.rootEvent);
this.emit(BeaconEvent.Update, beaconInfoEvent, this);
this.clearLatestLocation();
}
public destroy(): void {
@@ -126,51 +109,14 @@ export class Beacon extends TypedEventEmitter<Exclude<BeaconEvent, BeaconEvent.N
clearInterval(this.livenessWatchInterval);
}
this.checkLiveness();
if (this.isLive) {
const expiryInMs = (this._beaconInfo?.timestamp + this._beaconInfo?.timeout) - Date.now();
const expiryInMs = (this._beaconInfo?.timestamp + this._beaconInfo?.timeout + 1) - Date.now();
if (expiryInMs > 1) {
this.livenessWatchInterval = setInterval(
() => { this.monitorLiveness(); },
expiryInMs,
);
this.livenessWatchInterval = setInterval(this.checkLiveness.bind(this), expiryInMs);
}
}
}
/**
* Process Beacon locations
* Emits BeaconEvent.LocationUpdate
*/
public addLocations(beaconLocationEvents: MatrixEvent[]): void {
// discard locations for beacons that are not live
if (!this.isLive) {
return;
}
const validLocationEvents = beaconLocationEvents.filter(event => {
const content = event.getContent<MBeaconEventContent>();
const timestamp = M_TIMESTAMP.findIn<number>(content);
return (
// only include positions that were taken inside the beacon's live period
isTimestampInDuration(this._beaconInfo.timestamp, this._beaconInfo.timeout, timestamp) &&
// ignore positions older than our current latest location
(!this.latestLocationState || timestamp > this.latestLocationState.timestamp)
);
});
const latestLocationEvent = validLocationEvents.sort(sortEventsByLatestContentTimestamp)?.[0];
if (latestLocationEvent) {
this._latestLocationState = parseBeaconContent(latestLocationEvent.getContent());
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState);
}
}
private clearLatestLocation = () => {
this._latestLocationState = undefined;
this.emit(BeaconEvent.LocationUpdate, this.latestLocationState);
};
private setBeaconInfo(event: MatrixEvent): void {
this._beaconInfo = parseBeaconInfoContent(event.getContent());
this.checkLiveness();
+18 -57
View File
@@ -26,11 +26,8 @@ import { MatrixEvent } from "./event";
import { MatrixClient } from "../client";
import { GuestAccess, HistoryVisibility, IJoinRuleEventContent, JoinRule } from "../@types/partials";
import { TypedEventEmitter } from "./typed-event-emitter";
import { Beacon, BeaconEvent, BeaconEventHandlerMap } from "./beacon";
import { Beacon, BeaconEvent, isBeaconInfoEventType, BeaconEventHandlerMap } from "./beacon";
import { TypedReEmitter } from "../ReEmitter";
import { M_BEACON_INFO } from "../@types/beacon";
import { getBeaconInfoIdentifier } from "./beacon";
import { BeaconIdentifier } from "..";
// possible statuses for out-of-band member loading
enum OobStatus {
@@ -83,8 +80,8 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
public events = new Map<string, Map<string, MatrixEvent>>(); // Map<eventType, Map<stateKey, MatrixEvent>>
public paginationToken: string = null;
public readonly beacons = new Map<BeaconIdentifier, Beacon>();
private _liveBeaconIds: BeaconIdentifier[] = [];
public readonly beacons = new Map<string, Beacon>();
private liveBeaconIds: string[] = [];
/**
* Construct room state.
@@ -251,10 +248,6 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
return !!this.liveBeaconIds?.length;
}
public get liveBeaconIds(): BeaconIdentifier[] {
return this._liveBeaconIds;
}
/**
* Creates a copy of this room state so that mutations to either won't affect the other.
* @return {RoomState} the copy of the room state
@@ -337,7 +330,7 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
return;
}
if (M_BEACON_INFO.matches(event.getType())) {
if (isBeaconInfoEventType(event.getType())) {
this.setBeacon(event);
}
@@ -411,37 +404,6 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
this.emit(RoomStateEvent.Update, this);
}
public processBeaconEvents(events: MatrixEvent[]): void {
if (
!events.length ||
// discard locations if we have no beacons
!this.beacons.size
) {
return;
}
// names are confusing here
// a Beacon is the parent event, but event type is 'm.beacon_info'
// a location is the 'child' related to the Beacon, but the event type is 'm.beacon'
// group locations by beaconInfo event id
const locationEventsByBeaconEventId = events.reduce<Record<string, MatrixEvent[]>>((acc, event) => {
const beaconInfoEventId = event.getRelation()?.event_id;
if (!acc[beaconInfoEventId]) {
acc[beaconInfoEventId] = [];
}
acc[beaconInfoEventId].push(event);
return acc;
}, {});
Object.entries(locationEventsByBeaconEventId).forEach(([beaconInfoEventId, events]) => {
const beacon = [...this.beacons.values()].find(beacon => beacon.beaconInfoId === beaconInfoEventId);
if (beacon) {
beacon.addLocations(events);
}
});
}
/**
* Looks up a member by the given userId, and if it doesn't exist,
* create it and emit the `RoomState.newMember` event.
@@ -475,16 +437,12 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
* @experimental
*/
private setBeacon(event: MatrixEvent): void {
const beaconIdentifier = getBeaconInfoIdentifier(event);
if (this.beacons.has(beaconIdentifier)) {
const beacon = this.beacons.get(beaconIdentifier);
if (this.beacons.has(event.getType())) {
const beacon = this.beacons.get(event.getType());
if (event.isRedacted()) {
if (beacon.beaconInfoId === event.getRedactionEvent()?.['redacts']) {
beacon.destroy();
this.beacons.delete(beaconIdentifier);
}
beacon.destroy();
this.beacons.delete(event.getType());
return;
}
@@ -506,22 +464,25 @@ export class RoomState extends TypedEventEmitter<EmittedEvents, EventHandlerMap>
this.emit(BeaconEvent.New, event, beacon);
beacon.on(BeaconEvent.LivenessChange, this.onBeaconLivenessChange.bind(this));
beacon.on(BeaconEvent.Destroy, this.onBeaconLivenessChange.bind(this));
this.beacons.set(beacon.identifier, beacon);
this.beacons.set(beacon.beaconInfoEventType, beacon);
}
/**
* @experimental
* Check liveness of room beacons
* emit RoomStateEvent.BeaconLiveness event
* emit RoomStateEvent.BeaconLiveness when
* roomstate.hasLiveBeacons has changed
*/
private onBeaconLivenessChange(): void {
this._liveBeaconIds = Array.from(this.beacons.values())
const prevHasLiveBeacons = !!this.liveBeaconIds?.length;
this.liveBeaconIds = Array.from(this.beacons.values())
.filter(beacon => beacon.isLive)
.map(beacon => beacon.identifier);
.map(beacon => beacon.beaconInfoId);
this.emit(RoomStateEvent.BeaconLiveness, this, this.hasLiveBeacons);
const hasLiveBeacons = !!this.liveBeaconIds.length;
if (prevHasLiveBeacons !== hasLiveBeacons) {
this.emit(RoomStateEvent.BeaconLiveness, this, hasLiveBeacons);
}
}
private getStateEventMatching(event: MatrixEvent): MatrixEvent | null {
+18 -23
View File
@@ -1474,7 +1474,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
public threadsReady = false;
public async fetchRoomThreads(): Promise<void> {
if (this.threadsReady || !this.client.supportsExperimentalThreads()) {
if (this.threadsReady) {
return;
}
@@ -1662,7 +1662,7 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
// benefit from all the APIs a homeserver can provide to enhance the thread experience
thread = this.createThread(rootEvent, events, toStartOfTimeline);
if (thread) {
rootEvent?.setThread(thread);
rootEvent.setThread(thread);
}
deferred.resolve(thread);
}
@@ -2339,27 +2339,22 @@ export class Room extends TypedEventEmitter<EmittedEvents, RoomEventHandlerMap>
// set fake stripped state events if this is an invite room so logic remains
// consistent elsewhere.
const membershipEvent = this.currentState.getStateEvents(EventType.RoomMember, this.myUserId);
if (membershipEvent) {
const membership = membershipEvent.getContent().membership;
this.updateMyMembership(membership);
if (membership === "invite") {
const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || [];
strippedStateEvents.forEach((strippedEvent) => {
const existingEvent = this.currentState.getStateEvents(strippedEvent.type, strippedEvent.state_key);
if (!existingEvent) {
// set the fake stripped event instead
this.currentState.setStateEvents([new MatrixEvent({
type: strippedEvent.type,
state_key: strippedEvent.state_key,
content: strippedEvent.content,
event_id: "$fake" + Date.now(),
room_id: this.roomId,
user_id: this.myUserId, // technically a lie
})]);
}
});
}
if (membershipEvent && membershipEvent.getContent().membership === "invite") {
const strippedStateEvents = membershipEvent.getUnsigned().invite_room_state || [];
strippedStateEvents.forEach((strippedEvent) => {
const existingEvent = this.currentState.getStateEvents(strippedEvent.type, strippedEvent.state_key);
if (!existingEvent) {
// set the fake stripped event instead
this.currentState.setStateEvents([new MatrixEvent({
type: strippedEvent.type,
state_key: strippedEvent.state_key,
content: strippedEvent.content,
event_id: "$fake" + Date.now(),
room_id: this.roomId,
user_id: this.myUserId, // technically a lie
})]);
}
});
}
const oldName = this.name;
+12 -14
View File
@@ -95,7 +95,6 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
]);
this.room.on(MatrixEventEvent.BeforeRedaction, this.onBeforeRedaction);
this.room.on(RoomEvent.Redaction, this.onRedaction);
this.room.on(RoomEvent.LocalEchoUpdated, this.onEcho);
this.timelineSet.on(RoomEvent.Timeline, this.onEcho);
@@ -116,24 +115,23 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
}
}
private onBeforeRedaction = (event: MatrixEvent, redaction: MatrixEvent) => {
private onBeforeRedaction = (event: MatrixEvent) => {
if (event?.isRelation(THREAD_RELATION_TYPE.name) &&
this.room.eventShouldLiveIn(event).threadId === this.id &&
!redaction.status // only respect it when it succeeds
this.room.eventShouldLiveIn(event).threadId === this.id
) {
this.replyCount--;
this.emit(ThreadEvent.Update, this);
}
};
private onRedaction = (event: MatrixEvent) => {
if (event.threadRootId !== this.id) return; // ignore redactions for other timelines
const events = [...this.timelineSet.getLiveTimeline().getEvents()].reverse();
this.lastEvent = events.find(e => (
!e.isRedacted() &&
e.isRelation(THREAD_RELATION_TYPE.name)
)) ?? this.rootEvent;
this.emit(ThreadEvent.Update, this);
if (this.lastEvent?.getId() === event.getId()) {
const events = [...this.timelineSet.getLiveTimeline().getEvents()].reverse();
this.lastEvent = events.find(e => (
!e.isRedacted() &&
e.getId() !== event.getId() &&
e.isRelation(THREAD_RELATION_TYPE.name)
)) ?? this.rootEvent;
this.emit(ThreadEvent.NewReply, this, this.lastEvent);
}
};
private onEcho = (event: MatrixEvent) => {
@@ -144,7 +142,7 @@ export class Thread extends TypedEventEmitter<EmittedEvents, EventHandlerMap> {
// when threads are used over federation. That could result in the reply
// count value drifting away from the value returned by the server
const isThreadReply = event.isRelation(THREAD_RELATION_TYPE.name);
if (!this.lastEvent || this.lastEvent.isRedacted() || (isThreadReply
if (!this.lastEvent || (isThreadReply
&& (event.getId() !== this.lastEvent.getId())
&& (event.localTimestamp > this.lastEvent.localTimestamp))
) {
+5 -5
View File
@@ -78,7 +78,7 @@ WebStorageSessionStore.prototype = {
const devices = {};
for (let i = 0; i < this.store.length; ++i) {
const key = this.store.key(i);
const userId = key.slice(prefix.length);
const userId = key.substr(prefix.length);
if (key.startsWith(prefix)) devices[userId] = getJsonItem(this.store, key);
}
return devices;
@@ -125,7 +125,7 @@ WebStorageSessionStore.prototype = {
const deviceKeys = getKeysWithPrefix(this.store, keyEndToEndSessions(''));
const results = {};
for (const k of deviceKeys) {
const unprefixedKey = k.slice(keyEndToEndSessions('').length);
const unprefixedKey = k.substr(keyEndToEndSessions('').length);
results[unprefixedKey] = getJsonItem(this.store, k);
}
return results;
@@ -158,8 +158,8 @@ WebStorageSessionStore.prototype = {
// (hence 43 characters long).
result.push({
senderKey: key.slice(prefix.length, prefix.length + 43),
sessionId: key.slice(prefix.length + 44),
senderKey: key.substr(prefix.length, 43),
sessionId: key.substr(prefix.length + 44),
});
}
return result;
@@ -182,7 +182,7 @@ WebStorageSessionStore.prototype = {
const roomKeys = getKeysWithPrefix(this.store, keyEndToEndRoom(''));
const results = {};
for (const k of roomKeys) {
const unprefixedKey = k.slice(keyEndToEndRoom('').length);
const unprefixedKey = k.substr(keyEndToEndRoom('').length);
results[unprefixedKey] = getJsonItem(this.store, k);
}
return results;
+5 -1
View File
@@ -1170,6 +1170,7 @@ export class SyncApi {
stateEvents.forEach(function(e) {
client.emit(ClientEvent.Event, e);
});
room.updateMyMembership("invite");
});
// Handle joins
@@ -1316,6 +1317,8 @@ export class SyncApi {
client.emit(ClientEvent.Event, e);
});
room.updateMyMembership("join");
// Decrypt only the last message in all rooms to make sure we can generate a preview
// And decrypt all events after the recorded read receipt to ensure an accurate
// notification count
@@ -1349,6 +1352,8 @@ export class SyncApi {
accountDataEvents.forEach(function(e) {
client.emit(ClientEvent.Event, e);
});
room.updateMyMembership("leave");
});
// update the notification timeline, if appropriate.
@@ -1636,7 +1641,6 @@ export class SyncApi {
// This also needs to be done before running push rules on the events as they need
// to be decorated with sender etc.
room.addLiveEvents(timelineEventList || [], null, fromCache);
this.client.processBeaconEvents(room, timelineEventList);
}
/**
+1 -15
View File
@@ -24,8 +24,6 @@ import unhomoglyph from "unhomoglyph";
import promiseRetry from "p-retry";
import type NodeCrypto from "crypto";
import { MatrixEvent } from ".";
import { M_TIMESTAMP } from "./@types/location";
/**
* Encode a dictionary of query parameters.
@@ -427,7 +425,7 @@ export function globToRegexp(glob: string, extended?: any): string {
export function ensureNoTrailingSlash(url: string): string {
if (url && url.endsWith("/")) {
return url.slice(0, -1);
return url.substr(0, url.length - 1);
} else {
return url;
}
@@ -710,15 +708,3 @@ export function recursivelyAssign(target: Object, source: Object, ignoreNullish
}
return target;
}
function getContentTimestampWithFallback(event: MatrixEvent): number {
return M_TIMESTAMP.findIn<number>(event.getContent()) ?? -1;
}
/**
* Sort events by their content m.ts property
* Latest timestamp first
*/
export function sortEventsByLatestContentTimestamp(left: MatrixEvent, right: MatrixEvent): number {
return getContentTimestampWithFallback(right) - getContentTimestampWithFallback(left);
}