You've already forked matrix-js-sdk
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:
2
.babelrc
2
.babelrc
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
CHANGELOG.md
62
CHANGELOG.md
@@ -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)
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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 = [];
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 }};
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -353,7 +381,7 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
this._clearEvent && this._clearEvent.content &&
|
this._clearEvent && this._clearEvent.content &&
|
||||||
this._clearEvent.content.msgtype !== "m.bad.encrypted"
|
this._clearEvent.content.msgtype !== "m.bad.encrypted"
|
||||||
) {
|
) {
|
||||||
// we may want to just ignore this? let's start with rejecting it.
|
// we may want to just ignore this? let's start with rejecting it.
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -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.
|
||||||
//
|
//
|
||||||
@@ -698,28 +729,152 @@ utils.extend(module.exports.MatrixEvent.prototype, {
|
|||||||
*
|
*
|
||||||
* @return {?Object} push actions
|
* @return {?Object} push actions
|
||||||
*/
|
*/
|
||||||
getPushActions: function() {
|
getPushActions: function() {
|
||||||
return this._pushActions;
|
return this._pushActions;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the push actions for this event.
|
* Set the push actions for this event.
|
||||||
*
|
*
|
||||||
* @param {Object} pushActions push actions
|
* @param {Object} pushActions push actions
|
||||||
*/
|
*/
|
||||||
setPushActions: function(pushActions) {
|
setPushActions: function(pushActions) {
|
||||||
this._pushActions = pushActions;
|
this._pushActions = pushActions;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace the `event` property and recalculate any properties based on it.
|
* Replace the `event` property and recalculate any properties based on it.
|
||||||
* @param {Object} event the object to assign to the `event` property
|
* @param {Object} event the object to assign to the `event` property
|
||||||
*/
|
*/
|
||||||
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
337
src/models/relations.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
12
src/sync.js
12
src/sync.js
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user