Compare commits

..

77 Commits

Author SHA1 Message Date
Luke Barnard 678ff23bcb v0.9.2 2017-12-04 11:57:05 +00:00
Luke Barnard f93d50dcd0 Prepare changelog for v0.9.2 2017-12-04 11:57:04 +00:00
Luke Barnard aa3201ebb0 Send correct m.visibility flag in js-sdk 2017-12-04 11:54:42 +00:00
David Baker 5b1a5b7dd0 v0.9.1 2017-11-17 15:55:21 +00:00
David Baker f25324fb1c Prepare changelog for v0.9.1 2017-11-17 15:55:20 +00:00
David Baker 2a7f35a633 Merge pull request #577 from matrix-org/dbkr/fix_force_turn
Fix the force TURN option
2017-11-17 15:51:01 +00:00
David Baker eb2d5484b8 jsdoc and remove pointless return 2017-11-17 15:31:50 +00:00
David Baker 40cbd5ec9d Honour old forceTURN option for now 2017-11-17 15:03:12 +00:00
David Baker 10680ace17 Fix the force TURN option
Option needed to be passed in when creating a webrtc call, but for
incoming calls the js-sdk creates the call itself, so the app never
gets a chance to set the option.
2017-11-17 14:51:37 +00:00
Luke Barnard 3091a76702 v0.9.0 2017-11-15 10:56:31 +00:00
Luke Barnard 117e2370d7 Prepare changelog for v0.9.0 2017-11-15 10:56:31 +00:00
Luke Barnard a60e4efe6d v0.9.0-rc.1 2017-11-10 13:30:24 +00:00
Luke Barnard 4c0ebeab58 Prepare changelog for v0.9.0-rc.1 2017-11-10 13:30:24 +00:00
David Baker 0143ac2a86 Ignore pushrules in our cached sync response (#571)
* Ignore pushrules in our cached sync response

As hopefully explained in the comment
2017-11-10 13:14:54 +00:00
Luke Barnard 497a2bd057 Update API wrapper for group room association configuration (#569)
This is now a generic API on the homeserver. This is wrapped in a non-generic js-sdk function for now. In future, other group server implementations may want specific configuration and the configuration keys are namespaced to prevent collisions there.
2017-11-09 17:34:26 +00:00
Travis Ralston bf462e2840 Install babel-runtime, as it is required for operation (#561)
Signed-off-by: Travis Ralston <travpc@gmail.com>
2017-11-08 14:34:08 +00:00
Matthew Hodgson fb75134179 unbreak tests 2017-11-06 15:17:06 +00:00
Matthew Hodgson 68d67b7fc2 Remove ugly english non-i18n invite descriptions
any sensible app should be synthesising a better name for invites which is i18n-aware.
2017-11-06 11:18:24 +00:00
David Baker bbf412f3ad Merge pull request #567 from matrix-org/luke/groups-update-group-room-assoc
Modify addRoomToGroup to allow setting isPublic, create alias updateGroupRoomAssociation
2017-11-02 14:55:01 +00:00
David Baker 52b575296c Merge pull request #565 from matrix-org/dbkr/get_rule_by_id
Expose more functionality of pushprocessor
2017-11-02 13:49:00 +00:00
David Baker c1652f4898 Merge pull request #566 from matrix-org/dbkr/may_trigger_notif
Function for working out notif trigger permission
2017-11-02 13:48:07 +00:00
Luke Barnard d40cc795f7 Modify addRoomToGroup to allow setting isPublic, create alias updateGroupRoomAssociation
The API on synapse was modified to be an upsert, which means the same API call for adding a room to a group can be used to update the visibility of that association.
2017-11-02 13:34:34 +00:00
David Baker 4035d933ad Use the default power level if there's no PL event 2017-11-02 13:28:58 +00:00
David Baker d255348762 Remove leave check
1. It's wrong because it doesn't consider the ban state
2. This should be caught by whether they have permission to send
   the event in the first place rather than worrying about whether
   it has notification permission or not
2017-11-02 13:26:39 +00:00
David Baker 978db89deb Function for working out notif trigger permission
And make pushprocessor use this function
2017-11-02 12:04:55 +00:00
David Baker b867afe772 lint 2017-11-01 19:48:12 +00:00
David Baker e34fd89bb6 Expose more functionality of pushprocessor
This exposes a function to get rules by their ID and
ruleMatchesEvent to test whether a rule matches a given event.
2017-11-01 19:40:50 +00:00
Richard van der Hoff 51883b8f11 Merge pull request #555 from uhoreg/e2e_replay
keep track of event ID and timestamp of decrypted messages
2017-10-25 14:14:33 +01:00
David Baker ff5f95227a Merge pull request #564 from matrix-org/dbkr/fix_notifevents
Fix notifEvent computation
2017-10-25 12:06:05 +01:00
David Baker dc929236a4 oops, events not event 2017-10-25 12:03:17 +01:00
David Baker b09e20747a Oops, missing this 2017-10-25 12:00:11 +01:00
David Baker e8fc857dbc Don't calculate notifEvents until ready
We were previously computing notifEvents at the point where we
processed them but before the room they belong to was stored.
This was problematic because some push conditions try to get the
room and therefore failed. Since the push actions are cached, this
spurious calculation also got cached.

This moves the calculation out to a separate function that gets
called only after the room has been stored.
2017-10-25 11:54:44 +01:00
David Baker b42a13cc3b Merge pull request #563 from matrix-org/dbkr/fix_sentinels_on_power_levels
Fix power level of sentinel members
2017-10-25 11:47:18 +01:00
David Baker 37ed9800c5 Fix sender_notification_permission cond
Don't assume all events have a sender, because they don't.
2017-10-25 11:10:55 +01:00
David Baker 99f4968888 Replace all the sentinel members on power_levels
Rather than trying to be clever and keep ones whose power level isn't
affected, since they'll still have an associated power_levels event
that isn't the one actually in effect, which could be confusing.
2017-10-25 10:57:28 +01:00
David Baker 34428a811c Fix setPowerLevelEvent() semantics
* Make setPowerLevelEvent() use getDirectionalContent() rather than
   getContent(), otherwise power level events from pagination set
   power levels the wrong way
 * Don't assume the presence of a `users` key since the first
   power_levels event in a room will have no prev_content
2017-10-25 10:40:15 +01:00
Richard van der Hoff da7104b00d Merge pull request #554 from uhoreg/e2e_redact
don't try to decrypt a redacted message (fixes vector-im/riot-web#3744)
2017-10-24 21:02:01 +01:00
Hubert Chathi b373601e13 make sure redacted e2e messages are returned as a promise
Signed-off-by: Hubert Chathi <hubert@uhoreg.ca>
2017-10-24 13:11:48 -04:00
David Baker fcc90e884b Fix power level of sentinel members
When a ppower_levels event arrived, the current member objects were
being updated but the sentinel members were not, meaning that the
power levels of RoomMember objects associated with events had stale
power levels. This was causing power level based push rules to
be wrong.
2017-10-23 19:07:04 +01:00
David Baker 7a90096077 Merge pull request #562 from matrix-org/dbkr/room_notifs
Support room notifs
2017-10-23 17:01:23 +01:00
David Baker e7886a55fe lint 2017-10-23 16:50:01 +01:00
David Baker e33b786f65 Support room notifs
Specifically, add support for the sender_notification_permission
push rule condition. Also delays calculating notification events
until after they're added to the timeline, as per comment.
2017-10-23 16:02:06 +01:00
Luke Barnard b0b50f4ef9 Implement API wrapper for GET /group/$groupId/invited_users (#560) 2017-10-16 18:23:40 +02:00
David Baker 37c8e6dc07 Merge branch 'master' into develop 2017-10-16 14:37:39 +01:00
David Baker 675441fe93 v0.8.5 2017-10-16 14:35:20 +01:00
David Baker 2495d97862 Prepare changelog for v0.8.5 2017-10-16 14:35:20 +01:00
David Baker 70c998dd55 Merge pull request #558 from matrix-org/dbkr/fix_glob_to_regex
Fix the glob-to-regex code
2017-10-16 14:04:14 +01:00
David Baker ec60e46611 Merge remote-tracking branch 'origin/develop' into release-v0.8.5 2017-10-16 14:03:04 +01:00
David Baker bb2dc618f7 Merge pull request #559 from matrix-org/dbkr/unknown_condition_dont_match
Make unknown pushrule conditions not match
2017-10-16 14:02:02 +01:00
David Baker d9addf84ef Make unknown pushrule conditions not match
Unknown conditions previously were assumed to match, but given rules
can be added on the server as base rules, it's probably better to not
match unknown conditions.
2017-10-16 13:55:07 +01:00
David Baker d89163110e Fix the glob-to-regex code
It was only making one replacement so would fail for anything with
more than ine glob special char (eg. *foo would be fine, but *foo*
was broken)
2017-10-16 13:40:49 +01:00
David Baker 93b722af3c v0.8.5-rc.1 2017-10-13 11:01:58 +01:00
David Baker ee0e036aa5 Prepare changelog for v0.8.5-rc.1 2017-10-13 11:01:57 +01:00
David Baker 24ef004adc remove debug logging 2017-10-12 16:03:08 +01:00
Hubert Chathi dcab4eb70b fix lint error, and incorporate suggestions from richvdh and krombel
Signed-off-by: Hubert Chathi <hubert@uhoreg.ca>
2017-10-12 09:04:12 -04:00
Hubert Chathi 8f252992e4 keep track of event ID and timestamp of decrypted messages
This is to avoid false positives when detecting replay attacks.

fixes: vector-im/riot-web#3712

Signed-off-by: Hubert Chathi <hubert@uhoreg.ca>
2017-10-11 23:27:36 -04:00
Hubert Chathi a3a3e32e21 don't try to decrypt a redacted message (fixes vector-im/riot-web#3744)
set the decrypted data to an empty message

Signed-off-by: Hubert Chathi <hubert@uhoreg.ca>
2017-10-04 12:26:08 -04:00
David Baker f5f8867326 Merge pull request #553 from matrix-org/luke/groups-remove-room
Implement wrapper API for removing a room from a group
2017-10-04 10:58:28 +01:00
Luke Barnard 5494a9dd3b Implement wrapper API for removing a room from a group 2017-09-29 17:57:53 +01:00
David Baker 89f7857c84 Merge pull request #552 from matrix-org/rav/stuck_key_download
Fix typo which resulted in stuck key download requests
2017-09-29 10:22:26 +01:00
Richard van der Hoff 3140c6526a Fix typo which resulted in stuck key download requests
(... and hence stuck outgoing e2e messages)

Fixes https://github.com/vector-im/riot-web/issues/4278
2017-09-29 10:02:16 +01:00
Luke Barnard 96d828d22b Implement API wrapper for adding a room to a group (#551) 2017-09-27 14:27:19 +01:00
David Baker a8a50384b8 Add wrapper for group update_publicity API (#550)
Add wrapper for group update_publicity API
2017-09-25 15:27:59 +01:00
David Baker 787af6448d Merge pull request #549 from matrix-org/dbkr/groups_store_when_created
Store group when it's created
2017-09-22 18:02:17 +01:00
David Baker 4633135322 Store group when it's created
Otherwise we emit events when we set properties on the group but
getGroup() for that group returns null which is very confusing.
2017-09-22 17:57:08 +01:00
David Baker 050d522735 Merge pull request #548 from matrix-org/luke/groups-remove-rooms-users-from-summary
Luke/groups remove rooms users from summary
2017-09-22 16:23:47 +01:00
Luke Barnard 809b202e70 Merge branch 'develop' into luke/groups-remove-rooms-users-from-summary 2017-09-22 16:19:17 +01:00
Luke Barnard f1555dbb5d Implement wrapper APIs for removing users and rooms from group summary 2017-09-22 16:18:18 +01:00
krombel d1d0266a10 reduce sendToDevice payload (#522)
instead of sending one huge request split them up.
2017-09-22 14:29:23 +01:00
David Baker ee37ed0697 Add a clean target (#547) 2017-09-21 21:45:55 +01:00
David Baker e3972dee2c Merge branch 'master' into develop 2017-09-21 21:41:48 +01:00
Richard van der Hoff 8d804013f3 Merge pull request #546 from matrix-org/dbkr/clean_on_prerelease
Clean on prepublish
2017-09-21 18:06:28 +01:00
David Baker d73a115436 Clean on prerelease
Otherwise you can make broken releases on case insensitive file
systems
2017-09-21 18:02:30 +01:00
Luke Barnard 0b4ff731e8 Implement wrapper APIs for adding rooms to group summary (#545) 2017-09-21 17:05:36 +01:00
Luke Barnard a58ec3f192 Implement wrapper APIs for adding rooms to group summary 2017-09-21 17:01:33 +01:00
Richard van der Hoff 868c20b161 Fix a race in decrypting megolm messages (#544)
* Fix a race in decrypting megolm messages

This fixes a race wherein it was possible for us to fail to decrypt a message,
if the keys arrived immediately after our attempt to decrypt it. In that case,
a retry *should* have been scheduled, but was not.

Fixes https://github.com/vector-im/riot-web/issues/5001.

* WORDS
2017-09-21 15:17:28 +01:00
Luke Barnard c2cd050419 Implement API to add users to group summaries (#543)
(and call it addUserToGroupSummary)
2017-09-20 16:31:28 +01:00
16 changed files with 730 additions and 163 deletions
+65
View File
@@ -1,3 +1,68 @@
Changes in [0.9.2](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.9.2) (2017-12-04)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.9.1...v0.9.2)
Changes in [0.9.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.9.1) (2017-11-17)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.9.0...v0.9.1)
* Fix the force TURN option
[\#577](https://github.com/matrix-org/matrix-js-sdk/pull/577)
Changes in [0.9.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.9.0) (2017-11-15)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.9.0-rc.1...v0.9.0)
Changes in [0.9.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.9.0-rc.1) (2017-11-10)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.8.5...v0.9.0-rc.1)
* Modify addRoomToGroup to allow setting isPublic, create alias
updateGroupRoomAssociation
[\#567](https://github.com/matrix-org/matrix-js-sdk/pull/567)
* Expose more functionality of pushprocessor
[\#565](https://github.com/matrix-org/matrix-js-sdk/pull/565)
* Function for working out notif trigger permission
[\#566](https://github.com/matrix-org/matrix-js-sdk/pull/566)
* keep track of event ID and timestamp of decrypted messages
[\#555](https://github.com/matrix-org/matrix-js-sdk/pull/555)
* Fix notifEvent computation
[\#564](https://github.com/matrix-org/matrix-js-sdk/pull/564)
* Fix power level of sentinel members
[\#563](https://github.com/matrix-org/matrix-js-sdk/pull/563)
* don't try to decrypt a redacted message (fixes vector-im/riot-web#3744)
[\#554](https://github.com/matrix-org/matrix-js-sdk/pull/554)
* Support room notifs
[\#562](https://github.com/matrix-org/matrix-js-sdk/pull/562)
* Fix the glob-to-regex code
[\#558](https://github.com/matrix-org/matrix-js-sdk/pull/558)
Changes in [0.8.5](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.8.5) (2017-10-16)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.8.5-rc.1...v0.8.5)
* Make unknown pushrule conditions not match
[\#559](https://github.com/matrix-org/matrix-js-sdk/pull/559)
Changes in [0.8.5-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.8.5-rc.1) (2017-10-13)
==========================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.8.4...v0.8.5-rc.1)
* Implement wrapper API for removing a room from a group
[\#553](https://github.com/matrix-org/matrix-js-sdk/pull/553)
* Fix typo which resulted in stuck key download requests
[\#552](https://github.com/matrix-org/matrix-js-sdk/pull/552)
* Store group when it's created
[\#549](https://github.com/matrix-org/matrix-js-sdk/pull/549)
* Luke/groups remove rooms users from summary
[\#548](https://github.com/matrix-org/matrix-js-sdk/pull/548)
* Clean on prepublish
[\#546](https://github.com/matrix-org/matrix-js-sdk/pull/546)
* Implement wrapper APIs for adding rooms to group summary
[\#545](https://github.com/matrix-org/matrix-js-sdk/pull/545)
Changes in [0.8.4](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.8.4) (2017-09-21)
================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.8.3...v0.8.4)
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "0.8.4",
"version": "0.9.2",
"description": "Matrix Client-Server SDK for Javascript",
"main": "index.js",
"scripts": {
@@ -11,11 +11,12 @@
"check": "npm run test:build && _mocha --recursive specbuild --colors",
"gendoc": "babel --no-babelrc -d .jsdocbuild src && jsdoc -r .jsdocbuild -P package.json -R README.md -d .jsdoc",
"start": "babel -s -w -d lib src",
"clean": "rimraf lib dist",
"build": "babel -s -d lib src && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && uglifyjs -c -m -o dist/browser-matrix.min.js --source-map dist/browser-matrix.min.js.map --in-source-map dist/browser-matrix.js.map dist/browser-matrix.js",
"dist": "npm run build",
"watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v",
"lint": "eslint --max-warnings 109 src spec",
"prepublish": "npm run build && git rev-parse HEAD > git-revision.txt"
"prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt"
},
"repository": {
"url": "https://github.com/matrix-org/matrix-js-sdk"
@@ -49,6 +50,7 @@
],
"dependencies": {
"another-json": "^0.2.0",
"babel-runtime": "^6.26.0",
"bluebird": "^3.5.0",
"browser-request": "^0.3.3",
"content-type": "^1.0.2",
@@ -181,5 +181,88 @@ describe("MegolmDecryption", function() {
expect(payload.content.session_key).toExist();
});
});
it("can detect replay attacks", function() {
// trying to decrypt two different messages (marked by different
// event IDs or timestamps) using the same (sender key, session id,
// message index) triple should result in an exception being thrown
// as it should be detected as a replay attack.
const sessionId = groupSession.session_id();
const cipherText = groupSession.encrypt(JSON.stringify({
room_id: ROOM_ID,
content: 'testytest',
}));
const event1 = new MatrixEvent({
type: 'm.room.encrypted',
room_id: ROOM_ID,
content: {
algorithm: 'm.megolm.v1.aes-sha2',
sender_key: "SENDER_CURVE25519",
session_id: sessionId,
ciphertext: cipherText,
},
event_id: "$event1",
origin_server_ts: 1507753886000,
});
const successHandler = expect.createSpy();
const failureHandler = expect.createSpy()
.andCall((err) => {
expect(err.toString()).toMatch(
/Duplicate message index, possible replay attack/,
);
});
return megolmDecryption.decryptEvent(event1).then((res) => {
const event2 = new MatrixEvent({
type: 'm.room.encrypted',
room_id: ROOM_ID,
content: {
algorithm: 'm.megolm.v1.aes-sha2',
sender_key: "SENDER_CURVE25519",
session_id: sessionId,
ciphertext: cipherText,
},
event_id: "$event2",
origin_server_ts: 1507754149000,
});
return megolmDecryption.decryptEvent(event2);
}).then(
successHandler,
failureHandler,
).then(() => {
expect(successHandler).toNotHaveBeenCalled();
expect(failureHandler).toHaveBeenCalled();
});
});
it("allows re-decryption of the same event", function() {
// in contrast with the previous test, if the event ID and
// timestamp are the same, then it should not be considered a
// replay attack
const sessionId = groupSession.session_id();
const cipherText = groupSession.encrypt(JSON.stringify({
room_id: ROOM_ID,
content: 'testytest',
}));
const event = new MatrixEvent({
type: 'm.room.encrypted',
room_id: ROOM_ID,
content: {
algorithm: 'm.megolm.v1.aes-sha2',
sender_key: "SENDER_CURVE25519",
session_id: sessionId,
ciphertext: cipherText,
},
event_id: "$event1",
origin_server_ts: 1507753886000,
});
return megolmDecryption.decryptEvent(event).then((res) => {
return megolmDecryption.decryptEvent(event);
// test is successful if no exception is thrown
});
});
});
});
+3 -3
View File
@@ -886,7 +886,7 @@ describe("Room", function() {
expect(name).toEqual("Empty room");
});
it("should return 'Invite from [inviter display name] if state event " +
it("should return '[inviter display name] if state event " +
"available",
function() {
setJoinRule("invite");
@@ -894,7 +894,7 @@ describe("Room", function() {
addMember(userB, "invite", {user: userA});
room.recalculate(userB);
const name = room.name;
expect(name).toEqual("Invite from Alice");
expect(name).toEqual("Alice");
});
it("should return inviter mxid if display name not available",
@@ -904,7 +904,7 @@ describe("Room", function() {
addMember(userB, "invite", {user: userA});
room.recalculate(userB);
const name = room.name;
expect(name).toEqual("Invite from " + userA);
expect(name).toEqual(userA);
});
});
});
+154
View File
@@ -448,6 +448,26 @@ MatrixBaseApis.prototype.getGroupUsers = function(groupId) {
return this._http.authedRequest(undefined, "GET", path);
};
/**
* @param {string} groupId
* @return {module:client.Promise} Resolves: Group users list object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.getGroupInvitedUsers = function(groupId) {
const path = utils.encodeUri("/groups/$groupId/invited_users", {$groupId: groupId});
return this._http.authedRequest(undefined, "GET", path);
};
/**
* @param {string} groupId
* @return {module:client.Promise} Resolves: Group rooms list object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.getGroupRooms = function(groupId) {
const path = utils.encodeUri("/groups/$groupId/rooms", {$groupId: groupId});
return this._http.authedRequest(undefined, "GET", path);
};
/**
* @param {string} groupId
* @param {string} userId
@@ -476,6 +496,124 @@ MatrixBaseApis.prototype.removeUserFromGroup = function(groupId, userId) {
return this._http.authedRequest(undefined, "PUT", path, undefined, {});
};
/**
* @param {string} groupId
* @param {string} userId
* @param {string} roleId Optional.
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.addUserToGroupSummary = function(groupId, userId, roleId) {
const path = utils.encodeUri(
roleId ?
"/groups/$groupId/summary/$roleId/users/$userId" :
"/groups/$groupId/summary/users/$userId",
{$groupId: groupId, $roleId: roleId, $userId: userId},
);
return this._http.authedRequest(undefined, "PUT", path, undefined, {});
};
/**
* @param {string} groupId
* @param {string} userId
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.removeUserFromGroupSummary = function(groupId, userId) {
const path = utils.encodeUri(
"/groups/$groupId/summary/users/$userId",
{$groupId: groupId, $userId: userId},
);
return this._http.authedRequest(undefined, "DELETE", path, undefined, {});
};
/**
* @param {string} groupId
* @param {string} roomId
* @param {string} categoryId Optional.
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.addRoomToGroupSummary = function(groupId, roomId, categoryId) {
const path = utils.encodeUri(
categoryId ?
"/groups/$groupId/summary/$categoryId/rooms/$roomId" :
"/groups/$groupId/summary/rooms/$roomId",
{$groupId: groupId, $categoryId: categoryId, $roomId: roomId},
);
return this._http.authedRequest(undefined, "PUT", path, undefined, {});
};
/**
* @param {string} groupId
* @param {string} roomId
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.removeRoomFromGroupSummary = function(groupId, roomId) {
const path = utils.encodeUri(
"/groups/$groupId/summary/rooms/$roomId",
{$groupId: groupId, $roomId: roomId},
);
return this._http.authedRequest(undefined, "DELETE", path, undefined, {});
};
/**
* @param {string} groupId
* @param {string} roomId
* @param {bool} isPublic Whether the room-group association is visible to non-members
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.addRoomToGroup = function(groupId, roomId, isPublic) {
if (isPublic === undefined) {
isPublic = true;
}
const path = utils.encodeUri(
"/groups/$groupId/admin/rooms/$roomId",
{$groupId: groupId, $roomId: roomId},
);
return this._http.authedRequest(undefined, "PUT", path, undefined,
{ "m.visibility": { type: isPublic ? "public" : "private" } },
);
};
/**
* Configure the visibility of a room-group association.
* @param {string} groupId
* @param {string} roomId
* @param {bool} isPublic Whether the room-group association is visible to non-members
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.updateGroupRoomVisibility = function(groupId, roomId, isPublic) {
// NB: The /config API is generic but there's not much point in exposing this yet as synapse
// is the only server to implement this. In future we should consider an API that allows
// arbitrary configuration, i.e. "config/$configKey".
const path = utils.encodeUri(
"/groups/$groupId/admin/rooms/$roomId/config/m.visibility",
{$groupId: groupId, $roomId: roomId},
);
return this._http.authedRequest(undefined, "PUT", path, undefined,
{ type: isPublic ? "public" : "private" },
);
};
/**
* @param {string} groupId
* @param {string} roomId
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.removeRoomFromGroup = function(groupId, roomId) {
const path = utils.encodeUri(
"/groups/$groupId/admin/rooms/$roomId",
{$groupId: groupId, $roomId: roomId},
);
return this._http.authedRequest(undefined, "DELETE", path, undefined, {});
};
/**
* @param {string} groupId
* @return {module:client.Promise} Resolves: Empty object
@@ -545,6 +683,22 @@ MatrixBaseApis.prototype.getPublicisedGroups = function(userIds) {
);
};
/**
* @param {string} groupId
* @param {bool} isPublic Whether the user's membership of this group is made public
* @return {module:client.Promise} Resolves: Empty object
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.setGroupPublicity = function(groupId, isPublic) {
const path = utils.encodeUri(
"/groups/$groupId/self/update_publicity",
{$groupId: groupId},
);
return this._http.authedRequest(undefined, "PUT", path, undefined, {
publicise: isPublic,
});
};
/**
* Retrieve a state event.
* @param {string} roomId
+15 -1
View File
@@ -175,6 +175,8 @@ function MatrixClient(opts) {
this._cryptoStore = opts.cryptoStore;
this._sessionStore = opts.sessionStore;
this._forceTURN = opts.forceTURN || false;
if (CRYPTO_ENABLED) {
this.olmVersion = Crypto.getOlmVersion();
}
@@ -252,6 +254,16 @@ MatrixClient.prototype.supportsVoip = function() {
return this._supportsVoip;
};
/**
* Set whether VoIP calls are forced to use only TURN
* candidates. This is the same as the forceTURN option
* when creating the client.
* @param {bool} forceTURN True to force use of TURN servers
*/
MatrixClient.prototype.setForceTURN = function(forceTURN) {
this._forceTURN = forceTURN;
};
/**
* Get the current sync state.
* @return {?string} the sync state, which may be null.
@@ -3153,7 +3165,9 @@ function setupCallEventHandler(client) {
);
}
call = webRtcCall.createNewMatrixCall(client, event.getRoomId());
call = webRtcCall.createNewMatrixCall(client, event.getRoomId(), {
forceTURN: client._forceTURN,
});
if (!call) {
console.log(
"Incoming call ID " + content.call_id + " but this client " +
+1 -1
View File
@@ -518,7 +518,7 @@ class DeviceListUpdateSerialiser {
}
}, (e) => {
console.warn('Error downloading keys for ' + downloadUsers + ':', e);
this._downloadInProgressInProgress = false;
this._downloadInProgress = false;
deferred.reject(e);
});
+27 -10
View File
@@ -96,12 +96,18 @@ function OlmDevice(sessionStore) {
// This partially mitigates a replay attack where a MITM resends a group
// message into the room.
//
// TODO: If we ever remove an event from memory we will also need to remove
// it from this map. Otherwise if we download the event from the server we
// will think that it is a duplicate.
// When we decrypt a message and the message index matches a previously
// decrypted message, one possible cause of that is that we are decrypting
// the same event, and may not indicate an actual replay attack. For
// example, this could happen if we receive events, forget about them, and
// then re-fetch them when we backfill. So we store the event ID and
// timestamp corresponding to each message index when we first decrypt it,
// and compare these against the event ID and timestamp every time we use
// that same index. If they match, then we're probably decrypting the same
// event and we don't consider it a replay attack.
//
// Keys are strings of form "<senderKey>|<session_id>|<message_index>"
// Values are true.
// Values are objects of the form "{id: <event id>, timestamp: <ts>}"
this._inboundGroupSessionMessageIndexes = {};
}
@@ -794,6 +800,8 @@ OlmDevice.prototype.importInboundGroupSession = async function(data) {
* @param {string} senderKey base64-encoded curve25519 key of the sender
* @param {string} sessionId session identifier
* @param {string} body base64-encoded body of the encrypted message
* @param {string} eventId ID of the event being decrypted
* @param {Number} timestamp timestamp of the event being decrypted
*
* @return {null} the sessionId is unknown
*
@@ -802,7 +810,7 @@ OlmDevice.prototype.importInboundGroupSession = async function(data) {
* keysClaimed: Object<string, string>}>}
*/
OlmDevice.prototype.decryptGroupMessage = async function(
roomId, senderKey, sessionId, body,
roomId, senderKey, sessionId, body, eventId, timestamp,
) {
const self = this;
@@ -815,14 +823,23 @@ OlmDevice.prototype.decryptGroupMessage = async function(
plaintext = res;
} else {
// Check if we have seen this message index before to detect replay attacks.
// If the event ID and timestamp are specified, and the match the event ID
// and timestamp from the last time we used this message index, then we
// don't consider it a replay attack.
const messageIndexKey = senderKey + "|" + sessionId + "|" + res.message_index;
if (messageIndexKey in self._inboundGroupSessionMessageIndexes) {
throw new Error(
"Duplicate message index, possible replay attack: " +
messageIndexKey,
);
const msgInfo = self._inboundGroupSessionMessageIndexes[messageIndexKey];
if (msgInfo.id !== eventId || msgInfo.timestamp !== timestamp) {
throw new Error(
"Duplicate message index, possible replay attack: " +
messageIndexKey,
);
}
}
self._inboundGroupSessionMessageIndexes[messageIndexKey] = true;
self._inboundGroupSessionMessageIndexes[messageIndexKey] = {
id: eventId,
timestamp: timestamp,
};
}
sessionData.session = session.pickle(self._pickleKey);
+210 -106
View File
@@ -74,6 +74,14 @@ OutboundSessionInfo.prototype.needsRotation = function(
return false;
};
OutboundSessionInfo.prototype.markSharedWithDevice = function(
userId, deviceId, chainIndex,
) {
if (!this.sharedWithDevices[userId]) {
this.sharedWithDevices[userId] = {};
}
this.sharedWithDevices[userId][deviceId] = chainIndex;
};
/**
* Determine if this session has been shared with devices which it shouldn't
@@ -262,15 +270,151 @@ MegolmEncryption.prototype._prepareNewSession = async function() {
*
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
*
* @param {number} chainIndex current chain index
*
* @param {object<userId, deviceId>} devicemap
* mapping from userId to deviceId to {@link module:crypto~OlmSessionResult}
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices
*
* @return {module:client.Promise} Promise which resolves once the key sharing
* message has been sent.
* @return {array<object<userid, deviceInfo>>}
*/
MegolmEncryption.prototype._shareKeyWithDevices = function(session, devicesByUser) {
const self = this;
MegolmEncryption.prototype._splitUserDeviceMap = function(
session, chainIndex, devicemap, devicesByUser,
) {
const maxToDeviceMessagesPerRequest = 20;
// use an array where the slices of a content map gets stored
const mapSlices = [];
let currentSliceId = 0; // start inserting in the first slice
let entriesInCurrentSlice = 0;
for (const userId of Object.keys(devicesByUser)) {
const devicesToShareWith = devicesByUser[userId];
const sessionResults = devicemap[userId];
for (let i = 0; i < devicesToShareWith.length; i++) {
const deviceInfo = devicesToShareWith[i];
const deviceId = deviceInfo.deviceId;
const sessionResult = sessionResults[deviceId];
if (!sessionResult.sessionId) {
// no session with this device, probably because there
// were no one-time keys.
//
// we could send them a to_device message anyway, as a
// signal that they have missed out on the key sharing
// message because of the lack of keys, but there's not
// much point in that really; it will mostly serve to clog
// up to_device inboxes.
// mark this device as "handled" because we don't want to try
// to claim a one-time-key for dead devices on every message.
session.markSharedWithDevice(userId, deviceId, chainIndex);
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
continue;
}
console.log(
"share keys with device " + userId + ":" + deviceId,
);
if (entriesInCurrentSlice > maxToDeviceMessagesPerRequest) {
// the current slice is filled up. Start inserting into the next slice
entriesInCurrentSlice = 0;
currentSliceId++;
}
if (!mapSlices[currentSliceId]) {
mapSlices[currentSliceId] = [];
}
mapSlices[currentSliceId].push({
userId: userId,
deviceInfo: deviceInfo,
});
entriesInCurrentSlice++;
}
}
return mapSlices;
};
/**
* @private
*
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
*
* @param {number} chainIndex current chain index
*
* @param {object<userId, deviceInfo>} userDeviceMap
* mapping from userId to deviceInfo
*
* @param {object} payload fields to include in the encrypted payload
*
* @return {module:client.Promise} Promise which resolves once the key sharing
* for the given userDeviceMap is generated and has been sent.
*/
MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
session, chainIndex, userDeviceMap, payload,
) {
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: this._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
const contentMap = {};
const promises = [];
for (let i = 0; i < userDeviceMap.length; i++) {
const val = userDeviceMap[i];
const userId = val.userId;
const deviceInfo = val.deviceInfo;
const deviceId = deviceInfo.deviceId;
if (!contentMap[userId]) {
contentMap[userId] = {};
}
contentMap[userId][deviceId] = encryptedContent;
promises.push(
olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
this._userId,
this._deviceId,
this._olmDevice,
userId,
deviceInfo,
payload,
),
);
}
return Promise.all(promises).then(() => {
return this._baseApis.sendToDevice("m.room.encrypted", contentMap).then(() => {
// store that we successfully uploaded the keys of the current slice
for (const userId of Object.keys(contentMap)) {
for (const deviceId of Object.keys(contentMap[userId])) {
session.markSharedWithDevice(
userId, deviceId, chainIndex,
);
}
}
});
});
};
/**
* @private
*
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
*
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
* map from userid to list of devices
*/
MegolmEncryption.prototype._shareKeyWithDevices = async function(session, devicesByUser) {
const key = this._olmDevice.getOutboundGroupSessionKey(session.sessionId);
const payload = {
type: "m.room_key",
@@ -283,104 +427,28 @@ MegolmEncryption.prototype._shareKeyWithDevices = function(session, devicesByUse
},
};
const contentMap = {};
return olmlib.ensureOlmSessionsForDevices(
const devicemap = await olmlib.ensureOlmSessionsForDevices(
this._olmDevice, this._baseApis, devicesByUser,
).then(function(devicemap) {
const promises = [];
);
for (const userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) {
continue;
}
const userDeviceMaps = this._splitUserDeviceMap(
session, key.chain_index, devicemap, devicesByUser,
);
const devicesToShareWith = devicesByUser[userId];
const sessionResults = devicemap[userId];
for (let i = 0; i < userDeviceMaps.length; i++) {
try {
await this._encryptAndSendKeysToDevices(
session, key.chain_index, userDeviceMaps[i], payload,
);
console.log(`Completed megolm keyshare in ${this._roomId} `
+ `(slice ${i + 1}/${userDeviceMaps.length})`);
} catch (e) {
console.log(`megolm keyshare in ${this._roomId} `
+ `(slice ${i + 1}/${userDeviceMaps.length}) failed`);
for (let i = 0; i < devicesToShareWith.length; i++) {
const deviceInfo = devicesToShareWith[i];
const deviceId = deviceInfo.deviceId;
const sessionResult = sessionResults[deviceId];
if (!sessionResult.sessionId) {
// no session with this device, probably because there
// were no one-time keys.
//
// we could send them a to_device message anyway, as a
// signal that they have missed out on the key sharing
// message because of the lack of keys, but there's not
// much point in that really; it will mostly serve to clog
// up to_device inboxes.
//
// ensureOlmSessionsForUsers has already done the logging,
// so just skip it.
continue;
}
console.log(
"sharing keys with device " + userId + ":" + deviceId,
);
const encryptedContent = {
algorithm: olmlib.OLM_ALGORITHM,
sender_key: self._olmDevice.deviceCurve25519Key,
ciphertext: {},
};
if (!contentMap[userId]) {
contentMap[userId] = {};
}
contentMap[userId][deviceId] = encryptedContent;
promises.push(
olmlib.encryptMessageForDevice(
encryptedContent.ciphertext,
self._userId,
self._deviceId,
self._olmDevice,
userId,
deviceInfo,
payload,
),
);
}
throw e;
}
if (promises.length === 0) {
// no devices to send to
return Promise.resolve();
}
return Promise.all(promises).then(() => {
// TODO: retries
return self._baseApis.sendToDevice("m.room.encrypted", contentMap);
});
}).then(function() {
console.log(`Completed megolm keyshare in ${self._roomId}`);
// Add the devices we have shared with to session.sharedWithDevices.
//
// we deliberately iterate over devicesByUser (ie, the devices we
// attempted to share with) rather than the contentMap (those we did
// share with), because we don't want to try to claim a one-time-key
// for dead devices on every message.
for (const userId in devicesByUser) {
if (!devicesByUser.hasOwnProperty(userId)) {
continue;
}
if (!session.sharedWithDevices[userId]) {
session.sharedWithDevices[userId] = {};
}
const devicesToShareWith = devicesByUser[userId];
for (let i = 0; i < devicesToShareWith.length; i++) {
const deviceInfo = devicesToShareWith[i];
session.sharedWithDevices[userId][deviceInfo.deviceId] =
key.chain_index;
}
}
});
}
};
/**
@@ -522,7 +590,7 @@ function MegolmDecryption(params) {
base.DecryptionAlgorithm.call(this, params);
// events which we couldn't decrypt due to unknown sessions / indexes: map from
// senderKey|sessionId to list of MatrixEvents
// senderKey|sessionId to Set of MatrixEvents
this._pendingEvents = {};
// this gets stubbed out by the unit tests.
@@ -549,14 +617,21 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
throw new base.DecryptionError("Missing fields in input");
}
// we add the event to the pending list *before* we start decryption.
//
// then, if the key turns up while decryption is in progress (and
// decryption fails), we will schedule a retry.
// (fixes https://github.com/vector-im/riot-web/issues/5001)
this._addEventToPendingList(event);
let res;
try {
res = await this._olmDevice.decryptGroupMessage(
event.getRoomId(), content.sender_key, content.session_id, content.ciphertext,
event.getId(), event.getTs(),
);
} catch (e) {
if (e.message === 'OLM.UNKNOWN_MESSAGE_INDEX') {
this._addEventToPendingList(event);
this._requestKeysForEvent(event);
}
throw new base.DecryptionError(
@@ -568,7 +643,12 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
if (res === null) {
// We've got a message for a session we don't have.
this._addEventToPendingList(event);
//
// (XXX: We might actually have received this key since we started
// decrypting, in which case we'll have scheduled a retry, and this
// request will be redundant. We could probably check to see if the
// event is still in the pending list; if not, a retry will have been
// scheduled, so we needn't send out the request here.)
this._requestKeysForEvent(event);
throw new base.DecryptionError(
"The sender's device has not sent us the keys for this message.",
@@ -578,6 +658,10 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
);
}
// success. We can remove the event from the pending list, if that hasn't
// already happened.
this._removeEventFromPendingList(event);
const payload = JSON.parse(res.result);
// belt-and-braces check that the room id matches that indicated by the HS
@@ -621,8 +705,7 @@ MegolmDecryption.prototype._requestKeysForEvent = function(event) {
};
/**
* Add an event to the list of those we couldn't decrypt the first time we
* saw them.
* Add an event to the list of those awaiting their session keys.
*
* @private
*
@@ -632,11 +715,32 @@ MegolmDecryption.prototype._addEventToPendingList = function(event) {
const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id;
if (!this._pendingEvents[k]) {
this._pendingEvents[k] = [];
this._pendingEvents[k] = new Set();
}
this._pendingEvents[k].push(event);
this._pendingEvents[k].add(event);
};
/**
* Remove an event from the list of those awaiting their session keys.
*
* @private
*
* @param {module:models/event.MatrixEvent} event
*/
MegolmDecryption.prototype._removeEventFromPendingList = function(event) {
const content = event.getWireContent();
const k = content.sender_key + "|" + content.session_id;
if (!this._pendingEvents[k]) {
return;
}
this._pendingEvents[k].delete(event);
if (this._pendingEvents[k].size === 0) {
delete this._pendingEvents[k];
}
};
/**
* @inheritdoc
*
@@ -841,8 +945,8 @@ MegolmDecryption.prototype._retryDecryption = function(senderKey, sessionId) {
delete this._pendingEvents[k];
for (let i = 0; i < pending.length; i++) {
pending[i].attemptDecryption(this._crypto);
for (const ev of pending) {
ev.attemptDecryption(this._crypto);
}
};
+9
View File
@@ -769,6 +769,15 @@ Crypto.prototype.encryptEvent = function(event, room) {
* is a problem decrypting the event.
*/
Crypto.prototype.decryptEvent = function(event) {
if (event.isRedacted()) {
return Promise.resolve({
clearEvent: {
room_id: event.getRoomId(),
type: "m.room.message",
content: {},
},
});
}
const content = event.getWireContent();
const alg = this._getRoomDecryptor(event.getRoomId(), content.algorithm);
return alg.decryptEvent(event);
+9 -6
View File
@@ -104,17 +104,20 @@ RoomMember.prototype.setPowerLevelEvent = function(powerLevelEvent) {
if (powerLevelEvent.getType() !== "m.room.power_levels") {
return;
}
let maxLevel = powerLevelEvent.getContent().users_default || 0;
utils.forEach(utils.values(powerLevelEvent.getContent().users), function(lvl) {
const evContent = powerLevelEvent.getDirectionalContent();
let maxLevel = evContent.users_default || 0;
utils.forEach(utils.values(evContent.users), function(lvl) {
maxLevel = Math.max(maxLevel, lvl);
});
const oldPowerLevel = this.powerLevel;
const oldPowerLevelNorm = this.powerLevelNorm;
if (powerLevelEvent.getContent().users[this.userId] !== undefined) {
this.powerLevel = powerLevelEvent.getContent().users[this.userId];
} else if (powerLevelEvent.getContent().users_default !== undefined) {
this.powerLevel = powerLevelEvent.getContent().users_default;
if (evContent.users && evContent.users[this.userId] !== undefined) {
this.powerLevel = evContent.users[this.userId];
} else if (evContent.users_default !== undefined) {
this.powerLevel = evContent.users_default;
} else {
this.powerLevel = 0;
}
+39
View File
@@ -197,6 +197,16 @@ RoomState.prototype.setStateEvents = function(stateEvents) {
member.setPowerLevelEvent(event);
self.emit("RoomState.members", event, self, member);
});
// Go through the sentinel members and see if any of them would be
// affected by the new power levels. If so, replace the sentinel.
for (const userId of Object.keys(self._sentinels)) {
const oldSentinel = self._sentinels[userId];
const newSentinel = new RoomMember(event.getRoomId(), userId);
newSentinel.setMembershipEvent(oldSentinel.events.member, self);
newSentinel.setPowerLevelEvent(event);
self._sentinels[userId] = newSentinel;
}
}
});
};
@@ -383,6 +393,35 @@ RoomState.prototype._maySendEventOfType = function(eventType, userId, state) {
return member.powerLevel >= required_level;
};
/**
* Returns true if the given user ID has permission to trigger notification
* of type `notifLevelKey`
* @param {string} notifLevelKey The level of notification to test (eg. 'room')
* @param {string} userId The user ID of the user to test permission for
* @return {boolean} true if the given user ID has permission to trigger a
* notification of this type.
*/
RoomState.prototype.mayTriggerNotifOfType = function(notifLevelKey, userId) {
const member = this.getMember(userId);
if (!member) {
return false;
}
const powerLevelsEvent = this.getStateEvents('m.room.power_levels', '');
let notifLevel = 50;
if (
powerLevelsEvent &&
powerLevelsEvent.getContent() &&
powerLevelsEvent.getContent().notifications &&
powerLevelsEvent.getContent().notifications[notifLevelKey]
) {
notifLevel = powerLevelsEvent.getContent().notifications[notifLevelKey];
}
return member.powerLevel >= notifLevel;
};
/**
* The RoomState class.
*/
+2 -2
View File
@@ -1205,13 +1205,13 @@ function calculateRoomName(room, userId, ignoreRoomNameEvent) {
if (myMemberEvent && myMemberEvent.content.membership == "invite") {
if (room.currentState.getMember(myMemberEvent.sender)) {
// extract who invited us to the room
return "Invite from " + room.currentState.getMember(
return room.currentState.getMember(
myMemberEvent.sender,
).name;
} else if (allMembers[0].events.member) {
// use the sender field from the invite event, although this only
// gets us the mxid
return "Invite from " + myMemberEvent.sender;
return myMemberEvent.sender;
} else {
return "Room Invite";
}
+64 -19
View File
@@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 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.
@@ -17,6 +18,8 @@ limitations under the License.
* @module pushprocessor
*/
const RULEKINDS_IN_ORDER = ['override', 'content', 'room', 'sender', 'underride'];
/**
* Construct a Push Processor.
* @constructor
@@ -27,12 +30,11 @@ function PushProcessor(client) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
const matchingRuleFromKindSet = function(ev, kindset, device) {
const rulekinds_in_order = ['override', 'content', 'room', 'sender', 'underride'];
const matchingRuleFromKindSet = (ev, kindset, device) => {
for (let ruleKindIndex = 0;
ruleKindIndex < rulekinds_in_order.length;
ruleKindIndex < RULEKINDS_IN_ORDER.length;
++ruleKindIndex) {
const kind = rulekinds_in_order[ruleKindIndex];
const kind = RULEKINDS_IN_ORDER[ruleKindIndex];
const ruleset = kindset[kind];
for (let ruleIndex = 0; ruleIndex < ruleset.length; ++ruleIndex) {
@@ -46,7 +48,7 @@ function PushProcessor(client) {
continue;
}
if (ruleMatchesEvent(rawrule, ev)) {
if (this.ruleMatchesEvent(rawrule, ev)) {
rule.kind = kind;
return rule;
}
@@ -106,27 +108,38 @@ function PushProcessor(client) {
return rawrule;
};
const ruleMatchesEvent = function(rule, ev) {
let ret = true;
for (let i = 0; i < rule.conditions.length; ++i) {
const cond = rule.conditions[i];
ret &= eventFulfillsCondition(cond, ev);
}
//console.log("Rule "+rule.rule_id+(ret ? " matches" : " doesn't match"));
return ret;
};
const eventFulfillsCondition = function(cond, ev) {
const condition_functions = {
"event_match": eventFulfillsEventMatchCondition,
"device": eventFulfillsDeviceCondition,
"contains_display_name": eventFulfillsDisplayNameCondition,
"room_member_count": eventFulfillsRoomMemberCountCondition,
"sender_notification_permission": eventFulfillsSenderNotifPermCondition,
};
if (condition_functions[cond.kind]) {
return condition_functions[cond.kind](cond, ev);
}
return true;
// unknown conditions: we previously matched all unknown conditions,
// but given that rules can be added to the base rules on a server,
// it's probably better to not match unknown conditions.
return false;
};
const eventFulfillsSenderNotifPermCondition = function(cond, ev) {
const notifLevelKey = cond['key'];
if (!notifLevelKey) {
return false;
}
const room = client.getRoom(ev.getRoomId());
if (!room || !room.currentState) {
return false;
}
// Note that this should not be the current state of the room but the state at
// the point the event is in the DAG. Unfortunately the js-sdk does not store
// this.
return room.currentState.mayTriggerNotifOfType(notifLevelKey, ev.getSender());
};
const eventFulfillsRoomMemberCountCondition = function(cond, ev) {
@@ -215,9 +228,9 @@ function PushProcessor(client) {
// Because micromatch is about 130KB with dependencies,
// and minimatch is not much better.
let pat = escapeRegExp(glob);
pat = pat.replace(/\\\*/, '.*');
pat = pat.replace(/\?/, '.');
pat = pat.replace(/\\\[(!|)(.*)\\]/, function(match, p1, p2, offset, string) {
pat = pat.replace(/\\\*/g, '.*');
pat = pat.replace(/\?/g, '.');
pat = pat.replace(/\\\[(!|)(.*)\\]/g, function(match, p1, p2, offset, string) {
const first = p1 && '^' || '';
const second = p2.replace(/\\\-/, '-');
return '[' + first + second + ']';
@@ -291,6 +304,17 @@ function PushProcessor(client) {
return actionObj;
};
this.ruleMatchesEvent = function(rule, ev) {
let ret = true;
for (let i = 0; i < rule.conditions.length; ++i) {
const cond = rule.conditions[i];
ret &= eventFulfillsCondition(cond, ev);
}
//console.log("Rule "+rule.rule_id+(ret ? " matches" : " doesn't match"));
return ret;
};
/**
* Get the user's push actions for the given event
*
@@ -301,6 +325,27 @@ function PushProcessor(client) {
this.actionsForEvent = function(ev) {
return pushActionsForEventAndRulesets(ev, client.pushRules);
};
/**
* Get one of the users push rules by its ID
*
* @param {string} ruleId The ID of the rule to search for
* @return {object} The push rule, or null if no such rule was found
*/
this.getPushRuleById = function(ruleId) {
for (const scope of ['device', 'global']) {
if (client.pushRules[scope] === undefined) continue;
for (const kind of RULEKINDS_IN_ORDER) {
if (client.pushRules[scope][kind] === undefined) continue;
for (const rule of client.pushRules[scope][kind]) {
if (rule.rule_id === ruleId) return rule;
}
}
}
return null;
};
}
/**
+39 -10
View File
@@ -131,6 +131,7 @@ SyncApi.prototype.createGroup = function(groupId) {
const client = this.client;
const group = new Group(groupId);
client.reEmitter.reEmit(group, ["Group.profile", "Group.myMembership"]);
client.store.storeGroup(group);
return group;
};
@@ -230,6 +231,8 @@ SyncApi.prototype.syncLeftRooms = function() {
room.recalculate(client.credentials.userId);
client.store.storeRoom(room);
client.emit("Room", room);
self._processEventsForNotifs(room, timelineEvents);
});
return rooms;
});
@@ -613,7 +616,7 @@ SyncApi.prototype._sync = async function(syncOptions) {
}
try {
await this._processSyncResponse(syncToken, data);
await this._processSyncResponse(syncToken, data, isCachedResponse);
} catch(e) {
// log the exception with stack if we have it, else fall back
// to the plain description
@@ -696,8 +699,11 @@ SyncApi.prototype._onSyncError = function(err, syncOptions) {
* @param {string} syncToken the old next_batch token sent to this
* sync request.
* @param {Object} data The response from /sync
* @param {bool} isCachedResponse True if this response is from our local cache
*/
SyncApi.prototype._processSyncResponse = async function(syncToken, data) {
SyncApi.prototype._processSyncResponse = async function(
syncToken, data, isCachedResponse,
) {
const client = this.client;
const self = this;
@@ -776,7 +782,13 @@ SyncApi.prototype._processSyncResponse = async function(syncToken, data) {
client.store.storeAccountDataEvents(events);
events.forEach(
function(accountDataEvent) {
if (accountDataEvent.getType() == 'm.push_rules') {
// XXX: This is awful: ignore push rules from our cached sync. We fetch the
// push rules before syncing so we actually have up-to-date ones. We do want
// to honour new push rules that come down the sync but synapse doesn't
// put new push rules in the sync stream when the base rules change, so
// if the base rules change, we do need to refresh. We therefore ignore
// the push rules in our cached sync response.
if (accountDataEvent.getType() == 'm.push_rules' && !isCachedResponse) {
client.pushRules = accountDataEvent.getContent();
}
client.emit("accountData", accountDataEvent);
@@ -960,6 +972,8 @@ SyncApi.prototype._processSyncResponse = async function(syncToken, data) {
client.emit("Room", room);
}
self._processEventsForNotifs(room, timelineEvents);
async function processRoomEvent(e) {
client.emit("event", e);
if (e.isState() && e.getType() == "m.room.encryption" && self.opts.crypto) {
@@ -996,6 +1010,8 @@ SyncApi.prototype._processSyncResponse = async function(syncToken, data) {
client.emit("Room", room);
}
self._processEventsForNotifs(room, timelineEvents);
stateEvents.forEach(function(e) {
client.emit("event", e);
});
@@ -1144,7 +1160,7 @@ SyncApi.prototype._processGroupSyncEntry = function(groupsSection, sectionName)
}
group.setMyMembership(sectionName);
if (isBrandNew) {
this.client.store.storeGroup(group);
// Now we've filled in all the fields, emit the Group event
this.client.emit("Group", group);
}
}
@@ -1276,20 +1292,33 @@ SyncApi.prototype._processRoomEvents = function(room, stateEventList,
// may make notifications appear which should have the right name.
room.recalculate(this.client.credentials.userId);
// execute the timeline events, this will begin to diverge the current state
// if the timeline has any state events in it.
// This also needs to be done before running push rules on the events as they need
// to be decorated with sender etc.
room.addLiveEvents(timelineEventList);
};
/**
* Takes a list of timelineEvents and adds and adds to _notifEvents
* as appropriate.
* This must be called after the room the events belong to has been stored.
*
* @param {Room} room
* @param {MatrixEvent[]} [timelineEventList] A list of timeline events. Lower index
* is earlier in time. Higher index is later.
*/
SyncApi.prototype._processEventsForNotifs = function(room, timelineEventList) {
// gather our notifications into this._notifEvents
if (client.getNotifTimelineSet()) {
if (this.client.getNotifTimelineSet()) {
for (let i = 0; i < timelineEventList.length; i++) {
const pushActions = client.getPushActionsForEvent(timelineEventList[i]);
const pushActions = this.client.getPushActionsForEvent(timelineEventList[i]);
if (pushActions && pushActions.notify &&
pushActions.tweaks && pushActions.tweaks.highlight) {
this._notifEvents.push(timelineEventList[i]);
}
}
}
// execute the timeline events, this will begin to diverge the current state
// if the timeline has any state events in it.
room.addLiveEvents(timelineEventList);
};
/**
+6 -3
View File
@@ -1296,8 +1296,8 @@ module.exports.setVideoInput = function(deviceId) { videoInput = deviceId; };
* Create a new Matrix call for the browser.
* @param {MatrixClient} client The client instance to use.
* @param {string} roomId The room the call is in.
* @param {Object?} options optional options map.
* @param {boolean} options.forceTURN whether relay through TURN should be forced.
* @param {Object?} options DEPRECATED optional options map.
* @param {boolean} options.forceTURN DEPRECATED whether relay through TURN should be forced. This option is deprecated - use opts.forceTURN when creating the matrix client since it's only possible to set this option on outbound calls.
* @return {MatrixCall} the call or null if the browser doesn't support calling.
*/
module.exports.createNewMatrixCall = function(client, roomId, options) {
@@ -1350,6 +1350,9 @@ module.exports.createNewMatrixCall = function(client, roomId, options) {
!webRtc.RtcPeerConnection || !webRtc.getUserMedia) {
return null; // WebRTC is not supported.
}
const optionsForceTURN = options ? options.forceTURN : false;
const opts = {
webRtc: webRtc,
client: client,
@@ -1357,7 +1360,7 @@ module.exports.createNewMatrixCall = function(client, roomId, options) {
roomId: roomId,
turnServers: client.getTurnServers(),
// call level options
forceTURN: options ? options.forceTURN : false,
forceTURN: client._forceTURN || optionsForceTURN,
};
return new MatrixCall(opts);
};