1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-11-26 17:03:12 +03:00

Merge branch 'develop' into loglevel-extend

This commit is contained in:
Janith Kasun
2019-05-19 09:40:38 +05:30
committed by GitHub
48 changed files with 1281 additions and 434 deletions

View File

@@ -1,6 +1,8 @@
{ {
"presets": ["es2015"], "presets": ["es2015"],
"plugins": [ "plugins": [
"transform-class-properties",
// this transforms async functions into generator functions, which // this transforms async functions into generator functions, which
// are then made to use the regenerator module by babel's // are then made to use the regenerator module by babel's
// transform-regnerator plugin (which is enabled by es2015). // transform-regnerator plugin (which is enabled by es2015).

View File

@@ -22,3 +22,13 @@ steps:
plugins: plugins:
- docker#v3.0.1: - docker#v3.0.1:
image: "node:10" image: "node:10"
- wait
- label: "🐴 Trigger matrix-react-sdk"
trigger: "matrix-react-sdk"
branches: "develop"
build:
branch: "develop"
message: "[js-sdk] ${BUILDKITE_MESSAGE}"
async: true

View File

@@ -14,6 +14,9 @@ module.exports = {
es6: true, es6: true,
}, },
extends: ["eslint:recommended", "google"], extends: ["eslint:recommended", "google"],
plugins: [
"babel",
],
rules: { rules: {
// rules we've always adhered to or now do // rules we've always adhered to or now do
"max-len": ["error", { "max-len": ["error", {
@@ -73,5 +76,10 @@ module.exports = {
"asyncArrow": "always", "asyncArrow": "always",
}], }],
"arrow-parens": "off", "arrow-parens": "off",
// eslint's built in no-invalid-this rule breaks with class properties
"no-invalid-this": "off",
// so we replace it with a version that is class property aware
"babel/no-invalid-this": "error",
} }
} }

View File

@@ -1,3 +1,65 @@
Changes in [1.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.1.0) (2019-05-07)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.1.0-rc.1...v1.1.0)
* No Changes since rc.1
Changes in [1.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.1.0-rc.1) (2019-04-30)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.4...v1.1.0-rc.1)
* use the release version of olm 3.1.0
[\#903](https://github.com/matrix-org/matrix-js-sdk/pull/903)
* Use new Olm repo link in README
[\#901](https://github.com/matrix-org/matrix-js-sdk/pull/901)
* Support being fed a .well-known config object for validation
[\#897](https://github.com/matrix-org/matrix-js-sdk/pull/897)
* emit self-membership event at end of handling sync update
[\#900](https://github.com/matrix-org/matrix-js-sdk/pull/900)
* Use packages.matrix.org for Olm
[\#898](https://github.com/matrix-org/matrix-js-sdk/pull/898)
* Fix tests on develop
[\#899](https://github.com/matrix-org/matrix-js-sdk/pull/899)
* Stop syncing when the token is invalid
[\#895](https://github.com/matrix-org/matrix-js-sdk/pull/895)
* change event redact, POST request to PUT request
[\#887](https://github.com/matrix-org/matrix-js-sdk/pull/887)
* Expose better autodiscovery error messages
[\#894](https://github.com/matrix-org/matrix-js-sdk/pull/894)
* Explicitly guard store usage during sync startup
[\#892](https://github.com/matrix-org/matrix-js-sdk/pull/892)
* Flag v3 rooms as safe
[\#893](https://github.com/matrix-org/matrix-js-sdk/pull/893)
* Cache failed capabilities lookups for shorter amounts of time
[\#890](https://github.com/matrix-org/matrix-js-sdk/pull/890)
* Fix highlight notifications for unencrypted rooms
[\#891](https://github.com/matrix-org/matrix-js-sdk/pull/891)
* Document checking crypto state before using `hasUnverifiedDevices`
[\#889](https://github.com/matrix-org/matrix-js-sdk/pull/889)
* Add logging to sync startup path
[\#888](https://github.com/matrix-org/matrix-js-sdk/pull/888)
* Track e2e highlights better, particularly in 'Mentions Only' rooms
[\#886](https://github.com/matrix-org/matrix-js-sdk/pull/886)
* support both the incorrect and correct MAC methods
[\#882](https://github.com/matrix-org/matrix-js-sdk/pull/882)
* Refuse to set forwards pagination token on live timeline
[\#885](https://github.com/matrix-org/matrix-js-sdk/pull/885)
* Degrade `IndexedDBStore` back to memory only on failure
[\#884](https://github.com/matrix-org/matrix-js-sdk/pull/884)
* Refuse to link live timelines into the forwards/backwards position when
either is invalid
[\#877](https://github.com/matrix-org/matrix-js-sdk/pull/877)
* Key backup logging improvements
[\#883](https://github.com/matrix-org/matrix-js-sdk/pull/883)
* Don't assume aborts are always from txn.abort()
[\#880](https://github.com/matrix-org/matrix-js-sdk/pull/880)
* Add a bunch of logging
[\#878](https://github.com/matrix-org/matrix-js-sdk/pull/878)
* Refuse splicing the live timeline into a broken position
[\#873](https://github.com/matrix-org/matrix-js-sdk/pull/873)
* Add existence check to local storage based crypto store
[\#872](https://github.com/matrix-org/matrix-js-sdk/pull/872)
Changes in [1.0.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.4) (2019-04-08) Changes in [1.0.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v1.0.4) (2019-04-08)
================================================================================================ ================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.3...v1.0.4) [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v1.0.3...v1.0.4)

View File

@@ -1,6 +1,6 @@
{ {
"name": "matrix-js-sdk", "name": "matrix-js-sdk",
"version": "1.0.4", "version": "1.1.0",
"description": "Matrix Client-Server SDK for Javascript", "description": "Matrix Client-Server SDK for Javascript",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -9,7 +9,7 @@
"test:watch": "mocha --watch --compilers js:babel-core/register --recursive spec --colors", "test:watch": "mocha --watch --compilers js:babel-core/register --recursive spec --colors",
"test": "yarn test:build && yarn test:run", "test": "yarn test:build && yarn test:run",
"check": "yarn test:build && _mocha --recursive specbuild --colors", "check": "yarn test:build && _mocha --recursive specbuild --colors",
"gendoc": "babel --no-babelrc -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc", "gendoc": "babel --no-babelrc --plugins transform-class-properties -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc",
"start": "yarn start:init && yarn start:watch", "start": "yarn start:init && yarn start:watch",
"start:watch": "babel -s -w --skip-initial-build -d lib src", "start:watch": "babel -s -w --skip-initial-build -d lib src",
"start:init": "babel -s -d lib src", "start:init": "babel -s -d lib src",
@@ -54,7 +54,6 @@
"dependencies": { "dependencies": {
"another-json": "^0.2.0", "another-json": "^0.2.0",
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"base-x": "3.0.4",
"bluebird": "^3.5.0", "bluebird": "^3.5.0",
"browser-request": "^0.3.3", "browser-request": "^0.3.3",
"bs58": "^4.0.1", "bs58": "^4.0.1",
@@ -68,12 +67,14 @@
"babel-cli": "^6.18.0", "babel-cli": "^6.18.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-plugin-transform-async-to-bluebird": "^1.1.1", "babel-plugin-transform-async-to-bluebird": "^1.1.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.18.0", "babel-preset-es2015": "^6.18.0",
"browserify": "^16.2.3", "browserify": "^16.2.3",
"browserify-shim": "^3.8.13", "browserify-shim": "^3.8.13",
"eslint": "^5.12.0", "eslint": "^5.12.0",
"eslint-config-google": "^0.7.1", "eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^5.3.0",
"exorcist": "^0.4.0", "exorcist": "^0.4.0",
"expect": "^1.20.2", "expect": "^1.20.2",
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
@@ -93,5 +94,8 @@
"transform": [ "transform": [
"sourceify" "sourceify"
] ]
},
"resolutions": {
"bs58/base-x": "3.0.4"
} }
} }

View File

@@ -88,7 +88,7 @@ describe("DeviceList management:", function() {
} }
beforeEach(async function() { beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
// we create our own sessionStoreBackend so that we can use it for // we create our own sessionStoreBackend so that we can use it for
// another TestClient. // another TestClient.

View File

@@ -406,7 +406,7 @@ describe("MatrixClient crypto", function() {
} }
beforeEach(async function() { beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
aliTestClient = new TestClient(aliUserId, aliDeviceId, aliAccessToken); aliTestClient = new TestClient(aliUserId, aliDeviceId, aliAccessToken);
await aliTestClient.client.initCrypto(); await aliTestClient.client.initCrypto();

View File

@@ -15,7 +15,7 @@ describe("MatrixClient events", function() {
const selfAccessToken = "aseukfgwef"; const selfAccessToken = "aseukfgwef";
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn); sdk.request(httpBackend.requestFn);
client = sdk.createClient({ client = sdk.createClient({

View File

@@ -103,7 +103,7 @@ describe("getEventTimeline support", function() {
let client; let client;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn); sdk.request(httpBackend.requestFn);
}); });
@@ -228,7 +228,7 @@ describe("MatrixClient event timelines", function() {
let httpBackend = null; let httpBackend = null;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn); sdk.request(httpBackend.requestFn);

View File

@@ -21,7 +21,7 @@ describe("MatrixClient", function() {
const accessToken = "aseukfgwef"; const accessToken = "aseukfgwef";
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
store = new MemoryStore(); store = new MemoryStore();

View File

@@ -58,7 +58,7 @@ describe("MatrixClient opts", function() {
}; };
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
}); });

View File

@@ -20,7 +20,7 @@ describe("MatrixClient retrying", function() {
let room; let room;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn); sdk.request(httpBackend.requestFn);
scheduler = new sdk.MatrixScheduler(); scheduler = new sdk.MatrixScheduler();

View File

@@ -104,7 +104,7 @@ describe("MatrixClient room timelines", function() {
} }
beforeEach(function(done) { beforeEach(function(done) {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn); sdk.request(httpBackend.requestFn);
client = sdk.createClient({ client = sdk.createClient({

View File

@@ -23,7 +23,7 @@ describe("MatrixClient syncing", function() {
const roomTwo = "!bar:localhost"; const roomTwo = "!bar:localhost";
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new HttpBackend(); httpBackend = new HttpBackend();
sdk.request(httpBackend.requestFn); sdk.request(httpBackend.requestFn);
client = sdk.createClient({ client = sdk.createClient({

View File

@@ -283,7 +283,7 @@ describe("megolm", function() {
} }
beforeEach(async function() { beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
aliceTestClient = new TestClient( aliceTestClient = new TestClient(
"@alice:localhost", "xzcvb", "akjgkrgjs", "@alice:localhost", "xzcvb", "akjgkrgjs",

View File

@@ -30,7 +30,7 @@ describe("AutoDiscovery", function() {
let httpBackend = null; let httpBackend = null;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
httpBackend = new MockHttpBackend(); httpBackend = new MockHttpBackend();
sdk.request(httpBackend.requestFn); sdk.request(httpBackend.requestFn);
}); });

View File

@@ -9,7 +9,7 @@ describe("ContentRepo", function() {
const baseUrl = "https://my.home.server"; const baseUrl = "https://my.home.server";
beforeEach(function() { beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
}); });
describe("getHttpUriForMxc", function() { describe("getHttpUriForMxc", function() {

View File

@@ -60,7 +60,7 @@ describe('DeviceList', function() {
let deviceLists = []; let deviceLists = [];
beforeEach(function() { beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
deviceLists = []; deviceLists = [];

View File

@@ -32,7 +32,7 @@ describe("MegolmDecryption", function() {
let mockBaseApis; let mockBaseApis;
beforeEach(async function() { beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
await Olm.init(); await Olm.init();

View File

@@ -54,7 +54,7 @@ describe("OlmDecryption", function() {
let bobOlmDevice; let bobOlmDevice;
beforeEach(async function() { beforeEach(async function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
await global.Olm.init(); await global.Olm.init();

View File

@@ -126,7 +126,7 @@ describe("MegolmBackup", function() {
let megolmDecryption; let megolmDecryption;
beforeEach(async function() { beforeEach(async function() {
await Olm.init(); await Olm.init();
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
mockCrypto = testUtils.mock(Crypto, 'Crypto'); mockCrypto = testUtils.mock(Crypto, 'Crypto');
mockCrypto.backupKey = new Olm.PkEncryption(); mockCrypto.backupKey = new Olm.PkEncryption();

View File

@@ -29,7 +29,7 @@ export async function makeTestClients(userInfos, options) {
for (const [deviceId, msg] of Object.entries(devMap)) { for (const [deviceId, msg] of Object.entries(devMap)) {
if (deviceId in clientMap[userId]) { if (deviceId in clientMap[userId]) {
const event = new MatrixEvent({ const event = new MatrixEvent({
sender: this.getUserId(), // eslint-disable-line no-invalid-this sender: this.getUserId(), // eslint-disable-line babel/no-invalid-this
type: type, type: type,
content: msg, content: msg,
}); });

View File

@@ -18,7 +18,7 @@ describe("EventTimeline", function() {
let timeline; let timeline;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
// XXX: this is a horrid hack; should use sinon or something instead to mock // XXX: this is a horrid hack; should use sinon or something instead to mock
const timelineSet = { room: { roomId: roomId }}; const timelineSet = { room: { roomId: roomId }};

View File

@@ -25,7 +25,7 @@ import logger from '../../src/logger';
describe("MatrixEvent", () => { describe("MatrixEvent", () => {
beforeEach(function() { beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
}); });
describe(".attemptDecryption", () => { describe(".attemptDecryption", () => {

View File

@@ -12,7 +12,7 @@ describe("Filter", function() {
let filter; let filter;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
filter = new Filter(userId); filter = new Filter(userId);
}); });

View File

@@ -36,7 +36,7 @@ class FakeClient {
describe("InteractiveAuth", function() { describe("InteractiveAuth", function() {
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
}); });
it("should start an auth stage and complete it", function(done) { it("should start an auth stage and complete it", function(done) {

View File

@@ -125,7 +125,7 @@ describe("MatrixClient", function() {
} }
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install(); clock = lolex.install();
scheduler = [ scheduler = [
"getQueueForEvent", "queueEvent", "removeEventFromQueue", "getQueueForEvent", "queueEvent", "removeEventFromQueue",

View File

@@ -15,7 +15,7 @@ describe("realtime-callbacks", function() {
} }
beforeEach(function() { beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install(); clock = lolex.install();
const fakeDate = clock.Date; const fakeDate = clock.Date;
callbacks.setNow(fakeDate.now.bind(fakeDate)); callbacks.setNow(fakeDate.now.bind(fakeDate));
@@ -56,8 +56,8 @@ describe("realtime-callbacks", function() {
it("should set 'this' to the global object", function() { it("should set 'this' to the global object", function() {
let passed = false; let passed = false;
const callback = function() { const callback = function() {
expect(this).toBe(global); // eslint-disable-line no-invalid-this expect(this).toBe(global); // eslint-disable-line babel/no-invalid-this
expect(this.console).toBeTruthy(); // eslint-disable-line no-invalid-this expect(this.console).toBeTruthy(); // eslint-disable-line babel/no-invalid-this
passed = true; passed = true;
}; };
callbacks.setTimeout(callback); callbacks.setTimeout(callback);

View File

@@ -14,7 +14,7 @@ describe("RoomMember", function() {
let member; let member;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
member = new RoomMember(roomId, userA); member = new RoomMember(roomId, userA);
}); });

View File

@@ -17,7 +17,7 @@ describe("RoomState", function() {
let state; let state;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
state = new RoomState(roomId); state = new RoomState(roomId);
state.setStateEvents([ state.setStateEvents([
utils.mkMembership({ // userA joined utils.mkMembership({ // userA joined

View File

@@ -19,7 +19,7 @@ describe("Room", function() {
let room; let room;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
room = new Room(roomId); room = new Room(roomId);
// mock RoomStates // mock RoomStates
room.oldState = room.getLiveTimeline()._startState = room.oldState = room.getLiveTimeline()._startState =

View File

@@ -26,7 +26,7 @@ describe("MatrixScheduler", function() {
}); });
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
clock = lolex.install(); clock = lolex.install();
scheduler = new MatrixScheduler(function(ev, attempts, err) { scheduler = new MatrixScheduler(function(ev, attempts, err) {
if (retryFn) { if (retryFn) {

View File

@@ -26,7 +26,7 @@ describe("SyncAccumulator", function() {
let sa; let sa;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
sa = new SyncAccumulator({ sa = new SyncAccumulator({
maxTimelineEntries: 10, maxTimelineEntries: 10,
}); });

View File

@@ -68,7 +68,7 @@ function createLinkedTimelines() {
describe("TimelineIndex", function() { describe("TimelineIndex", function() {
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
}); });
describe("minIndex", function() { describe("minIndex", function() {
@@ -164,7 +164,7 @@ describe("TimelineWindow", function() {
} }
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
}); });
describe("load", function() { describe("load", function() {

View File

@@ -11,7 +11,7 @@ describe("User", function() {
let user; let user;
beforeEach(function() { beforeEach(function() {
utils.beforeEach(this); // eslint-disable-line no-invalid-this utils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
user = new User(userId); user = new User(userId);
}); });

View File

@@ -7,7 +7,7 @@ import expect from 'expect';
describe("utils", function() { describe("utils", function() {
beforeEach(function() { beforeEach(function() {
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this testUtils.beforeEach(this); // eslint-disable-line babel/no-invalid-this
}); });
describe("encodeParams", function() { describe("encodeParams", function() {

View File

@@ -480,7 +480,7 @@ export class AutoDiscovery {
const request = require("./matrix").getRequest(); const request = require("./matrix").getRequest();
if (!request) throw new Error("No request library available"); if (!request) throw new Error("No request library available");
request( request(
{ method: "GET", uri: url }, { method: "GET", uri: url, timeout: 5000 },
(err, response, body) => { (err, response, body) => {
if (err || response.statusCode < 200 || response.statusCode >= 300) { if (err || response.statusCode < 200 || response.statusCode >= 300) {
let action = "FAIL_PROMPT"; let action = "FAIL_PROMPT";

View File

@@ -904,7 +904,7 @@ MatrixBaseApis.prototype.redactEvent = function(
callback = txnId; callback = txnId;
} }
const path = utils.encodeUri("/rooms/$roomId/redact/$eventId/$tnxId", { const path = utils.encodeUri("/rooms/$roomId/redact/$eventId/$txnId", {
$roomId: roomId, $roomId: roomId,
$eventId: eventId, $eventId: eventId,
$txnId: txnId ? txnId : this.makeTxnId(), $txnId: txnId ? txnId : this.makeTxnId(),

View File

@@ -149,6 +149,11 @@ function keyFromRecoverySession(session, decryptionKey) {
* maintain support for back-paginating the live timeline after a '/sync' * maintain support for back-paginating the live timeline after a '/sync'
* result with a gap. * result with a gap.
* *
* @param {boolean} [opts.unstableClientRelationAggregation = false]
* Optional. Set to true to enable client-side aggregation of event relations
* via `EventTimelineSet#getRelationsForEvent`.
* This feature is currently unstable and the API may change without notice.
*
* @param {Array} [opts.verificationMethods] Optional. The verification method * @param {Array} [opts.verificationMethods] Optional. The verification method
* that the application can handle. Each element should be an item from {@link * that the application can handle. Each element should be an item from {@link
* module:crypto~verificationMethods verificationMethods}, or a class that * module:crypto~verificationMethods verificationMethods}, or a class that
@@ -214,6 +219,7 @@ function MatrixClient(opts) {
this.timelineSupport = Boolean(opts.timelineSupport); this.timelineSupport = Boolean(opts.timelineSupport);
this.urlPreviewCache = {}; this.urlPreviewCache = {};
this._notifTimelineSet = null; this._notifTimelineSet = null;
this.unstableClientRelationAggregation = !!opts.unstableClientRelationAggregation;
this._crypto = null; this._crypto = null;
this._cryptoStore = opts.cryptoStore; this._cryptoStore = opts.cryptoStore;
@@ -1712,7 +1718,7 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
content: content, content: content,
}); });
localEvent._txnId = txnId; localEvent._txnId = txnId;
localEvent.status = EventStatus.SENDING; localEvent.setStatus(EventStatus.SENDING);
// add this event immediately to the local store as 'sending'. // add this event immediately to the local store as 'sending'.
if (room) { if (room) {
@@ -1842,7 +1848,7 @@ function _updatePendingEventStatus(room, event, newStatus) {
if (room) { if (room) {
room.updatePendingEvent(event, newStatus); room.updatePendingEvent(event, newStatus);
} else { } else {
event.status = newStatus; event.setStatus(newStatus);
} }
} }
@@ -4157,6 +4163,10 @@ function _PojoToMatrixEventMapper(client) {
]); ]);
event.attemptDecryption(client._crypto); event.attemptDecryption(client._crypto);
} }
const room = client.getRoom(event.getRoomId());
if (room) {
room.reEmitter.reEmit(event, ["Event.replaced"]);
}
return event; return event;
} }
return mapper; return mapper;

View File

@@ -378,7 +378,11 @@ Crypto.prototype.isKeyBackupTrusted = async function(backupInfo) {
); );
sigInfo.valid = true; sigInfo.valid = true;
} catch (e) { } catch (e) {
logger.info("Bad signature from key ID " + keyId, e); logger.info(
"Bad signature from key ID " + keyId + " userID " + this._userId +
" device ID " + device.deviceId + " fingerprint: " +
device.getFingerprint(), backupInfo.auth_data, e,
);
sigInfo.valid = false; sigInfo.valid = false;
} }
} else { } else {

View File

@@ -21,6 +21,7 @@ const EventEmitter = require("events").EventEmitter;
const utils = require("../utils"); const utils = require("../utils");
const EventTimeline = require("./event-timeline"); const EventTimeline = require("./event-timeline");
import logger from '../../src/logger'; import logger from '../../src/logger';
import Relations from './relations';
// var DEBUG = false; // var DEBUG = false;
const DEBUG = true; const DEBUG = true;
@@ -55,22 +56,38 @@ if (DEBUG) {
* map from event_id to timeline and index. * map from event_id to timeline and index.
* *
* @constructor * @constructor
* @param {?Room} room the optional room for this timelineSet * @param {?Room} room
* @param {Object} opts hash of options inherited from Room. * Room for this timelineSet. May be null for non-room cases, such as the
* opts.timelineSupport gives whether timeline support is enabled * notification timeline.
* opts.filter is the filter object, if any, for this timelineSet. * @param {Object} opts Options inherited from Room.
*
* @param {boolean} [opts.timelineSupport = false]
* Set to true to enable improved timeline support.
* @param {Object} [opts.filter = null]
* The filter object, if any, for this timelineSet.
* @param {boolean} [opts.unstableClientRelationAggregation = false]
* Optional. Set to true to enable client-side aggregation of event relations
* via `getRelationsForEvent`.
* This feature is currently unstable and the API may change without notice.
*/ */
function EventTimelineSet(room, opts) { function EventTimelineSet(room, opts) {
this.room = room; this.room = room;
this._timelineSupport = Boolean(opts.timelineSupport); this._timelineSupport = Boolean(opts.timelineSupport);
this._liveTimeline = new EventTimeline(this); this._liveTimeline = new EventTimeline(this);
this._unstableClientRelationAggregation = !!opts.unstableClientRelationAggregation;
// just a list - *not* ordered. // just a list - *not* ordered.
this._timelines = [this._liveTimeline]; this._timelines = [this._liveTimeline];
this._eventIdToTimeline = {}; this._eventIdToTimeline = {};
this._filter = opts.filter || null; this._filter = opts.filter || null;
if (this._unstableClientRelationAggregation) {
// A tree of objects to access a set of relations for an event, as in:
// this._relations[relatesToEventId][relationType][relationEventType]
this._relations = {};
}
} }
utils.inherits(EventTimelineSet, EventEmitter); utils.inherits(EventTimelineSet, EventEmitter);
@@ -524,6 +541,9 @@ EventTimelineSet.prototype.addEventToTimeline = function(event, timeline,
timeline.addEvent(event, toStartOfTimeline); timeline.addEvent(event, toStartOfTimeline);
this._eventIdToTimeline[eventId] = timeline; this._eventIdToTimeline[eventId] = timeline;
this.setRelationsTarget(event);
this.aggregateRelations(event);
const data = { const data = {
timeline: timeline, timeline: timeline,
liveEvent: !toStartOfTimeline && timeline == this._liveTimeline, liveEvent: !toStartOfTimeline && timeline == this._liveTimeline,
@@ -658,6 +678,122 @@ EventTimelineSet.prototype.compareEventOrdering = function(eventId1, eventId2) {
return null; return null;
}; };
/**
* Get a collection of relations to a given event in this timeline set.
*
* @param {String} eventId
* The ID of the event that you'd like to access relation events for.
* For example, with annotations, this would be the ID of the event being annotated.
* @param {String} relationType
* The type of relation involved, such as "m.annotation", "m.reference", "m.replace", etc.
* @param {String} eventType
* The relation event's type, such as "m.reaction", etc.
*
* @returns {Relations}
* A container for relation events.
*/
EventTimelineSet.prototype.getRelationsForEvent = function(
eventId, relationType, eventType,
) {
if (!this._unstableClientRelationAggregation) {
throw new Error("Client-side relation aggregation is disabled");
}
if (!eventId || !relationType || !eventType) {
throw new Error("Invalid arguments for `getRelationsForEvent`");
}
// debuglog("Getting relations for: ", eventId, relationType, eventType);
const relationsForEvent = this._relations[eventId] || {};
const relationsWithRelType = relationsForEvent[relationType] || {};
return relationsWithRelType[eventType];
};
/**
* Set an event as the target event if any Relations exist for it already
*
* @param {MatrixEvent} event
* The event to check as relation target.
*/
EventTimelineSet.prototype.setRelationsTarget = function(event) {
if (!this._unstableClientRelationAggregation) {
return;
}
const relationsForEvent = this._relations[event.getId()];
if (!relationsForEvent) {
return;
}
// don't need it for non m.replace relations for now
const relationsWithRelType = relationsForEvent["m.replace"];
if (!relationsWithRelType) {
return;
}
// only doing replacements for messages for now (e.g. edits)
const relationsWithEventType = relationsWithRelType["m.room.message"];
if (relationsWithEventType) {
relationsWithEventType.setTargetEvent(event);
}
};
/**
* Add relation events to the relevant relation collection.
*
* @param {MatrixEvent} event
* The new relation event to be aggregated.
*/
EventTimelineSet.prototype.aggregateRelations = function(event) {
if (!this._unstableClientRelationAggregation) {
return;
}
// If the event is currently encrypted, wait until it has been decrypted.
if (event.isBeingDecrypted()) {
event.once("Event.decrypted", () => {
this.aggregateRelations(event);
});
return;
}
const relation = event.getRelation();
if (!relation) {
return;
}
const relatesToEventId = relation.event_id;
const relationType = relation.rel_type;
const eventType = event.getType();
// debuglog("Aggregating relation: ", event.getId(), eventType, relation);
let relationsForEvent = this._relations[relatesToEventId];
if (!relationsForEvent) {
relationsForEvent = this._relations[relatesToEventId] = {};
}
let relationsWithRelType = relationsForEvent[relationType];
if (!relationsWithRelType) {
relationsWithRelType = relationsForEvent[relationType] = {};
}
let relationsWithEventType = relationsWithRelType[eventType];
if (!relationsWithEventType) {
relationsWithEventType = relationsWithRelType[eventType] = new Relations(
relationType,
eventType,
this.room,
);
const relatesToEvent = this.findEventById(relatesToEventId);
if (relatesToEvent) {
relationsWithEventType.setTargetEvent(relatesToEvent);
relatesToEvent.emit("Event.relationsCreated", relationType, eventType);
}
}
relationsWithEventType.addEvent(event);
};
/** /**
* The EventTimelineSet class. * The EventTimelineSet class.
*/ */

View File

@@ -51,6 +51,12 @@ module.exports.EventStatus = {
}; };
const interns = {}; const interns = {};
function intern(str) {
if (!interns[str]) {
interns[str] = str;
}
return interns[str];
}
/** /**
* Construct a Matrix Event object * Construct a Matrix Event object
@@ -88,20 +94,25 @@ module.exports.MatrixEvent = function MatrixEvent(
if (!event[prop]) { if (!event[prop]) {
return; return;
} }
if (!interns[event[prop]]) { event[prop] = intern(event[prop]);
interns[event[prop]] = event[prop];
}
event[prop] = interns[event[prop]];
}); });
["membership", "avatar_url", "displayname"].forEach((prop) => { ["membership", "avatar_url", "displayname"].forEach((prop) => {
if (!event.content || !event.content[prop]) { if (!event.content || !event.content[prop]) {
return; return;
} }
if (!interns[event.content[prop]]) { event.content[prop] = intern(event.content[prop]);
interns[event.content[prop]] = event.content[prop]; });
["rel_type"].forEach((prop) => {
if (
!event.content ||
!event.content["m.relates_to"] ||
!event.content["m.relates_to"][prop]
) {
return;
} }
event.content[prop] = interns[event.content[prop]]; event.content["m.relates_to"][prop] = intern(event.content["m.relates_to"][prop]);
}); });
this.event = event || {}; this.event = event || {};
@@ -112,6 +123,7 @@ module.exports.MatrixEvent = function MatrixEvent(
this.error = null; this.error = null;
this.forwardLooking = true; this.forwardLooking = true;
this._pushActions = null; this._pushActions = null;
this._replacingEvent = null;
this._clearEvent = {}; this._clearEvent = {};
@@ -210,12 +222,28 @@ utils.extend(module.exports.MatrixEvent.prototype, {
}, },
/** /**
* Get the (decrypted, if necessary) event content JSON. * Get the (decrypted, if necessary) event content JSON, even if the event
* was replaced by another event.
*
* @return {Object} The event content JSON, or an empty object.
*/
getOriginalContent: function() {
return this._clearEvent.content || this.event.content || {};
},
/**
* Get the (decrypted, if necessary) event content JSON,
* or the content from the replacing event, if any.
* See `makeReplaced`.
* *
* @return {Object} The event content JSON, or an empty object. * @return {Object} The event content JSON, or an empty object.
*/ */
getContent: function() { getContent: function() {
return this._clearEvent.content || this.event.content || {}; if (this._replacingEvent) {
return this._replacingEvent.getContent()["m.new_content"] || {};
} else {
return this.getOriginalContent();
}
}, },
/** /**
@@ -651,6 +679,9 @@ utils.extend(module.exports.MatrixEvent.prototype, {
throw new Error("invalid redaction_event in makeRedacted"); throw new Error("invalid redaction_event in makeRedacted");
} }
this.emit("Event.beforeRedaction", this, redaction_event);
this._replacingEvent = null;
// we attempt to replicate what we would see from the server if // we attempt to replicate what we would see from the server if
// the event had been redacted before we saw it. // the event had been redacted before we saw it.
// //
@@ -718,7 +749,131 @@ utils.extend(module.exports.MatrixEvent.prototype, {
handleRemoteEcho: function(event) { handleRemoteEcho: function(event) {
this.event = event; this.event = event;
// successfully sent. // successfully sent.
this.status = null; this.setStatus(null);
},
/**
* Whether the event is in any phase of sending, send failure, waiting for
* remote echo, etc.
*
* @return {boolean}
*/
isSending() {
return !!this.status;
},
/**
* Update the event's sending status and emit an event as well.
*
* @param {String} status The new status
*/
setStatus(status) {
this.status = status;
this.emit("Event.status", this, status);
},
/**
* Get whether the event is a relation event, and of a given type if
* `relType` is passed in.
*
* @param {string?} relType if given, checks that the relation is of the
* given type
* @return {boolean}
*/
isRelation(relType = undefined) {
// Relation info is lifted out of the encrypted content when sent to
// encrypted rooms, so we have to check `getWireContent` for this.
const content = this.getWireContent();
const relation = content && content["m.relates_to"];
return relation && relation.rel_type && relation.event_id &&
((relType && relation.rel_type === relType) || !relType);
},
/**
* Get relation info for the event, if any.
*
* @return {Object}
*/
getRelation() {
if (!this.isRelation()) {
return null;
}
return this.getWireContent()["m.relates_to"];
},
/**
* Set an event that replaces the content of this event, through an m.replace relation.
*
* @param {MatrixEvent?} newEvent the event with the replacing content, if any.
*/
makeReplaced(newEvent) {
if (this.isRedacted()) {
return;
}
if (this._replacingEvent !== newEvent) {
this._replacingEvent = newEvent;
this.emit("Event.replaced", this);
}
},
/**
* Returns the status of the event, or the replacing event in case `makeReplace` has been called.
*
* @return {EventStatus}
*/
replacementOrOwnStatus() {
if (this._replacingEvent) {
return this._replacingEvent.status;
} else {
return this.status;
}
},
/**
* Returns the event ID of the event replacing the content of this event, if any.
*
* @return {string?}
*/
replacingEventId() {
return this._replacingEvent && this._replacingEvent.getId();
},
/**
* Returns the event replacing the content of this event, if any.
*
* @return {MatrixEvent?}
*/
replacingEvent() {
return this._replacingEvent;
},
/**
* Summarise the event as JSON for debugging. If encrypted, include both the
* decrypted and encrypted view of the event. This is named `toJSON` for use
* with `JSON.stringify` which checks objects for functions named `toJSON`
* and will call them to customise the output if they are defined.
*
* @return {Object}
*/
toJSON() {
const event = {
type: this.getType(),
sender: this.getSender(),
content: this.getContent(),
event_id: this.getId(),
origin_server_ts: this.getTs(),
unsigned: this.getUnsigned(),
room_id: this.getRoomId(),
};
if (!this.isEncrypted()) {
return event;
}
return {
decrypted: event,
encrypted: this.event,
};
}, },
}); });

337
src/models/relations.js Normal file
View File

@@ -0,0 +1,337 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import EventEmitter from 'events';
import { EventStatus } from '../../lib/models/event';
/**
* A container for relation events that supports easy access to common ways of
* aggregating such events. Each instance holds events that of a single relation
* type and event type. All of the events also relate to the same original event.
*
* The typical way to get one of these containers is via
* EventTimelineSet#getRelationsForEvent.
*/
export default class Relations extends EventEmitter {
/**
* @param {String} relationType
* The type of relation involved, such as "m.annotation", "m.reference",
* "m.replace", etc.
* @param {String} eventType
* The relation event's type, such as "m.reaction", etc.
* @param {?Room} room
* Room for this container. May be null for non-room cases, such as the
* notification timeline.
*/
constructor(relationType, eventType, room) {
super();
this.relationType = relationType;
this.eventType = eventType;
this._relations = new Set();
this._annotationsByKey = {};
this._annotationsBySender = {};
this._sortedAnnotationsByKey = [];
this._targetEvent = null;
}
/**
* Add relation events to this collection.
*
* @param {MatrixEvent} event
* The new relation event to be added.
*/
addEvent(event) {
if (this._relations.has(event)) {
return;
}
const relation = event.getRelation();
if (!relation) {
console.error("Event must have relation info");
return;
}
const relationType = relation.rel_type;
const eventType = event.getType();
if (this.relationType !== relationType || this.eventType !== eventType) {
console.error("Event relation info doesn't match this container");
return;
}
// If the event is in the process of being sent, listen for cancellation
// so we can remove the event from the collection.
if (event.isSending()) {
event.on("Event.status", this._onEventStatus);
}
this._relations.add(event);
if (this.relationType === "m.annotation") {
this._addAnnotationToAggregation(event);
} else if (this.relationType === "m.replace" && this._targetEvent) {
this._targetEvent.makeReplaced(this.getLastReplacement());
}
event.on("Event.beforeRedaction", this._onBeforeRedaction);
this.emit("Relations.add", event);
}
/**
* Remove relation event from this collection.
*
* @param {MatrixEvent} event
* The relation event to remove.
*/
_removeEvent(event) {
if (!this._relations.has(event)) {
return;
}
const relation = event.getRelation();
if (!relation) {
console.error("Event must have relation info");
return;
}
const relationType = relation.rel_type;
const eventType = event.getType();
if (this.relationType !== relationType || this.eventType !== eventType) {
console.error("Event relation info doesn't match this container");
return;
}
this._relations.delete(event);
if (this.relationType === "m.annotation") {
this._removeAnnotationFromAggregation(event);
} else if (this.relationType === "m.replace" && this._targetEvent) {
this._targetEvent.makeReplaced(this.getLastReplacement());
}
this.emit("Relations.remove", event);
}
/**
* Listens for event status changes to remove cancelled events.
*
* @param {MatrixEvent} event The event whose status has changed
* @param {EventStatus} status The new status
*/
_onEventStatus = (event, status) => {
if (!event.isSending()) {
// Sending is done, so we don't need to listen anymore
event.removeListener("Event.status", this._onEventStatus);
return;
}
if (status !== EventStatus.CANCELLED) {
return;
}
// Event was cancelled, remove from the collection
event.removeListener("Event.status", this._onEventStatus);
this._removeEvent(event);
}
/**
* Get all relation events in this collection.
*
* These are currently in the order of insertion to this collection, which
* won't match timeline order in the case of scrollback.
* TODO: Tweak `addEvent` to insert correctly for scrollback.
*
* @return {Array}
* Relation events in insertion order.
*/
getRelations() {
return [...this._relations];
}
_addAnnotationToAggregation(event) {
const { key } = event.getRelation();
if (!key) {
return;
}
let eventsForKey = this._annotationsByKey[key];
if (!eventsForKey) {
eventsForKey = this._annotationsByKey[key] = new Set();
this._sortedAnnotationsByKey.push([key, eventsForKey]);
}
// Add the new event to the set for this key
eventsForKey.add(event);
// Re-sort the [key, events] pairs in descending order of event count
this._sortedAnnotationsByKey.sort((a, b) => {
const aEvents = a[1];
const bEvents = b[1];
return bEvents.size - aEvents.size;
});
const sender = event.getSender();
let eventsFromSender = this._annotationsBySender[sender];
if (!eventsFromSender) {
eventsFromSender = this._annotationsBySender[sender] = new Set();
}
// Add the new event to the set for this sender
eventsFromSender.add(event);
}
_removeAnnotationFromAggregation(event) {
const { key } = event.getRelation();
if (!key) {
return;
}
const eventsForKey = this._annotationsByKey[key];
if (eventsForKey) {
eventsForKey.delete(event);
// Re-sort the [key, events] pairs in descending order of event count
this._sortedAnnotationsByKey.sort((a, b) => {
const aEvents = a[1];
const bEvents = b[1];
return bEvents.size - aEvents.size;
});
}
const sender = event.getSender();
const eventsFromSender = this._annotationsBySender[sender];
if (eventsFromSender) {
eventsFromSender.delete(event);
}
}
/**
* For relations that have been redacted, we want to remove them from
* aggregation data sets and emit an update event.
*
* To do so, we listen for `Event.beforeRedaction`, which happens:
* - after the server accepted the redaction and remote echoed back to us
* - before the original event has been marked redacted in the client
*
* @param {MatrixEvent} redactedEvent
* The original relation event that is about to be redacted.
*/
_onBeforeRedaction = (redactedEvent) => {
if (!this._relations.has(redactedEvent)) {
return;
}
this._relations.delete(redactedEvent);
if (this.relationType === "m.annotation") {
// Remove the redacted annotation from aggregation by key
this._removeAnnotationFromAggregation(redactedEvent);
} else if (this.relationType === "m.replace" && this._targetEvent) {
this._targetEvent.makeReplaced(this.getLastReplacement());
}
redactedEvent.removeListener("Event.beforeRedaction", this._onBeforeRedaction);
// Dispatch a redaction event on this collection. `setTimeout` is used
// to wait until the next event loop iteration by which time the event
// has actually been marked as redacted.
setTimeout(() => {
this.emit("Relations.redaction");
}, 0);
}
/**
* Get all events in this collection grouped by key and sorted by descending
* event count in each group.
*
* This is currently only supported for the annotation relation type.
*
* @return {Array}
* An array of [key, events] pairs sorted by descending event count.
* The events are stored in a Set (which preserves insertion order).
*/
getSortedAnnotationsByKey() {
if (this.relationType !== "m.annotation") {
// Other relation types are not grouped currently.
return null;
}
return this._sortedAnnotationsByKey;
}
/**
* Get all events in this collection grouped by sender.
*
* This is currently only supported for the annotation relation type.
*
* @return {Object}
* An object with each relation sender as a key and the matching Set of
* events for that sender as a value.
*/
getAnnotationsBySender() {
if (this.relationType !== "m.annotation") {
// Other relation types are not grouped currently.
return null;
}
return this._annotationsBySender;
}
/**
* Returns the most recent (and allowed) m.replace relation, if any.
*
* This is currently only supported for the m.replace relation type,
* once the target event is known, see `addEvent`.
*
* @return {MatrixEvent?}
*/
getLastReplacement() {
if (this.relationType !== "m.replace") {
// Aggregating on last only makes sense for this relation type
return null;
}
if (!this._targetEvent) {
// Don't know which replacements to accept yet.
// This method shouldn't be called before the original
// event is known anyway.
return null;
}
return this.getRelations().reduce((last, event) => {
if (event.getSender() !== this._targetEvent.getSender()) {
return last;
}
if (last && last.getTs() > event.getTs()) {
return last;
}
return event;
}, null);
}
/*
* @param {MatrixEvent} targetEvent the event the relations are related to.
*/
setTargetEvent(event) {
if (this._targetEvent) {
return;
}
this._targetEvent = event;
if (this.relationType === "m.replace") {
const replacement = this.getLastReplacement();
// this is the initial update, so only call it if we already have something
// to not emit Event.replaced needlessly
if (replacement) {
this._targetEvent.makeReplaced(replacement);
}
}
}
}

View File

@@ -93,9 +93,12 @@ function synthesizeReceipt(userId, event, receiptType) {
* "<b>detached</b>", pending messages will appear in a separate list, * "<b>detached</b>", pending messages will appear in a separate list,
* accessbile via {@link module:models/room#getPendingEvents}. Default: * accessbile via {@link module:models/room#getPendingEvents}. Default:
* "chronological". * "chronological".
*
* @param {boolean} [opts.timelineSupport = false] Set to true to enable improved * @param {boolean} [opts.timelineSupport = false] Set to true to enable improved
* timeline support. * timeline support.
* @param {boolean} [opts.unstableClientRelationAggregation = false]
* Optional. Set to true to enable client-side aggregation of event relations
* via `EventTimelineSet#getRelationsForEvent`.
* This feature is currently unstable and the API may change without notice.
* *
* @prop {string} roomId The ID of this room. * @prop {string} roomId The ID of this room.
* @prop {string} name The human-readable display name for this room. * @prop {string} name The human-readable display name for this room.
@@ -1002,7 +1005,6 @@ Room.prototype.removeFilteredTimelineSet = function(filter) {
* @private * @private
*/ */
Room.prototype._addLiveEvent = function(event, duplicateStrategy) { Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
let i;
if (event.getType() === "m.room.redaction") { if (event.getType() === "m.room.redaction") {
const redactId = event.event.redacts; const redactId = event.event.redacts;
@@ -1036,7 +1038,7 @@ Room.prototype._addLiveEvent = function(event, duplicateStrategy) {
} }
// add to our timeline sets // add to our timeline sets
for (i = 0; i < this._timelineSets.length; i++) { for (let i = 0; i < this._timelineSets.length; i++) {
this._timelineSets[i].addLiveEvent(event, duplicateStrategy); this._timelineSets[i].addLiveEvent(event, duplicateStrategy);
} }
@@ -1101,9 +1103,27 @@ Room.prototype.addPendingEvent = function(event, txnId) {
if (this._opts.pendingEventOrdering == "detached") { if (this._opts.pendingEventOrdering == "detached") {
if (this._pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) { if (this._pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
logger.warn("Setting event as NOT_SENT due to messages in the same state"); logger.warn("Setting event as NOT_SENT due to messages in the same state");
event.status = EventStatus.NOT_SENT; event.setStatus(EventStatus.NOT_SENT);
} }
this._pendingEventList.push(event); this._pendingEventList.push(event);
if (event.isRelation()) {
// For pending events, add them to the relations collection immediately.
// (The alternate case below already covers this as part of adding to
// the timeline set.)
// TODO: We should consider whether this means it would be a better
// design to lift the relations handling up to the room instead.
for (let i = 0; i < this._timelineSets.length; i++) {
const timelineSet = this._timelineSets[i];
if (timelineSet.getFilter()) {
if (this._filter.filterRoomTimeline([event]).length) {
timelineSet.aggregateRelations(event);
}
} else {
timelineSet.aggregateRelations(event);
}
}
}
} else { } else {
for (let i = 0; i < this._timelineSets.length; i++) { for (let i = 0; i < this._timelineSets.length; i++) {
const timelineSet = this._timelineSets[i]; const timelineSet = this._timelineSets[i];
@@ -1242,7 +1262,7 @@ Room.prototype.updatePendingEvent = function(event, newStatus, newEventId) {
newStatus); newStatus);
} }
event.status = newStatus; event.setStatus(newStatus);
if (newStatus == EventStatus.SENT) { if (newStatus == EventStatus.SENT) {
// update the event id // update the event id

View File

@@ -23,6 +23,37 @@ import {escapeRegExp, globToRegexp} from "./utils";
const RULEKINDS_IN_ORDER = ['override', 'content', 'room', 'sender', 'underride']; const RULEKINDS_IN_ORDER = ['override', 'content', 'room', 'sender', 'underride'];
// The default override rules to apply when calculating actions for an event. These
// defaults apply under no other circumstances to avoid confusing the client with server
// state. We do this for two reasons:
// 1. Synapse is unlikely to send us the push rule in an incremental sync - see
// https://github.com/matrix-org/synapse/pull/4867#issuecomment-481446072 for
// more details.
// 2. We often want to start using push rules ahead of the server supporting them,
// and so we can put them here.
const DEFAULT_OVERRIDE_RULES = [
{
// For homeservers which don't support MSC1930 yet
rule_id: ".m.rule.tombstone",
default: true,
enabled: true,
conditions: [
{
kind: "event_match",
key: "type",
pattern: "m.room.tombstone",
},
],
actions: [
"notify",
{
set_tweak: "highlight",
value: true,
},
],
},
];
/** /**
* Construct a Push Processor. * Construct a Push Processor.
* @constructor * @constructor
@@ -312,6 +343,33 @@ function PushProcessor(client) {
return actionObj; return actionObj;
}; };
const applyRuleDefaults = function(clientRuleset) {
// Deep clone the object before we mutate it
const ruleset = JSON.parse(JSON.stringify(clientRuleset));
if (!clientRuleset['global']) {
clientRuleset['global'] = {};
}
if (!clientRuleset['global']['override']) {
clientRuleset['global']['override'] = [];
}
// Apply default overrides
const globalOverrides = clientRuleset['global']['override'];
for (const override of DEFAULT_OVERRIDE_RULES) {
const existingRule = globalOverrides
.find((r) => r.rule_id === override.rule_id);
if (!existingRule) {
const ruleId = override.rule_id;
console.warn(`Adding default global override for ${ruleId}`);
globalOverrides.push(override);
}
}
return ruleset;
};
this.ruleMatchesEvent = function(rule, ev) { this.ruleMatchesEvent = function(rule, ev) {
let ret = true; let ret = true;
for (let i = 0; i < rule.conditions.length; ++i) { for (let i = 0; i < rule.conditions.length; ++i) {
@@ -331,7 +389,8 @@ function PushProcessor(client) {
* @return {PushAction} * @return {PushAction}
*/ */
this.actionsForEvent = function(ev) { this.actionsForEvent = function(ev) {
return pushActionsForEventAndRulesets(ev, client.pushRules); const rules = applyRuleDefaults(client.pushRules);
return pushActionsForEventAndRulesets(ev, rules);
}; };
/** /**

View File

@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/* eslint-disable no-invalid-this */ /* eslint-disable babel/no-invalid-this */
import Promise from 'bluebird'; import Promise from 'bluebird';
import {MemoryStore} from "./memory"; import {MemoryStore} from "./memory";

View File

@@ -117,10 +117,15 @@ function SyncApi(client, opts) {
*/ */
SyncApi.prototype.createRoom = function(roomId) { SyncApi.prototype.createRoom = function(roomId) {
const client = this.client; const client = this.client;
const {
timelineSupport,
unstableClientRelationAggregation,
} = client;
const room = new Room(roomId, client, client.getUserId(), { const room = new Room(roomId, client, client.getUserId(), {
lazyLoadMembers: this.opts.lazyLoadMembers, lazyLoadMembers: this.opts.lazyLoadMembers,
pendingEventOrdering: this.opts.pendingEventOrdering, pendingEventOrdering: this.opts.pendingEventOrdering,
timelineSupport: client.timelineSupport, timelineSupport,
unstableClientRelationAggregation,
}); });
client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", "Room.redaction", client.reEmitter.reEmit(room, ["Room.name", "Room.timeline", "Room.redaction",
"Room.receipt", "Room.tags", "Room.receipt", "Room.tags",
@@ -128,6 +133,7 @@ SyncApi.prototype.createRoom = function(roomId) {
"Room.localEchoUpdated", "Room.localEchoUpdated",
"Room.accountData", "Room.accountData",
"Room.myMembership", "Room.myMembership",
"Room.replaceEvent",
]); ]);
this._registerStateListeners(room); this._registerStateListeners(room);
return room; return room;
@@ -712,7 +718,6 @@ SyncApi.prototype._syncFromCache = async function(savedSync) {
* @param {boolean} syncOptions.hasSyncedBefore * @param {boolean} syncOptions.hasSyncedBefore
*/ */
SyncApi.prototype._sync = async function(syncOptions) { SyncApi.prototype._sync = async function(syncOptions) {
debuglog("Starting sync request processing...");
const client = this.client; const client = this.client;
if (!this._running) { if (!this._running) {
@@ -751,9 +756,7 @@ SyncApi.prototype._sync = async function(syncOptions) {
// Reset after a successful sync // Reset after a successful sync
this._failedSyncCount = 0; this._failedSyncCount = 0;
debuglog("Storing sync data...");
await client.store.setSyncData(data); await client.store.setSyncData(data);
debuglog("Sync data stored");
const syncEventData = { const syncEventData = {
oldSyncToken: syncToken, oldSyncToken: syncToken,
@@ -768,7 +771,6 @@ SyncApi.prototype._sync = async function(syncOptions) {
} }
try { try {
debuglog("Processing sync response...");
await this._processSyncResponse(syncEventData, data); await this._processSyncResponse(syncEventData, data);
} catch(e) { } catch(e) {
// log the exception with stack if we have it, else fall back // log the exception with stack if we have it, else fall back

746
yarn.lock

File diff suppressed because it is too large Load Diff