Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 678ff23bcb | |||
| f93d50dcd0 | |||
| aa3201ebb0 | |||
| 5b1a5b7dd0 | |||
| f25324fb1c | |||
| 2a7f35a633 | |||
| eb2d5484b8 | |||
| 40cbd5ec9d | |||
| 10680ace17 | |||
| 3091a76702 | |||
| 117e2370d7 | |||
| a60e4efe6d | |||
| 4c0ebeab58 | |||
| 0143ac2a86 | |||
| 497a2bd057 | |||
| bf462e2840 | |||
| fb75134179 | |||
| 68d67b7fc2 | |||
| bbf412f3ad | |||
| 52b575296c | |||
| c1652f4898 | |||
| d40cc795f7 | |||
| 4035d933ad | |||
| d255348762 | |||
| 978db89deb | |||
| b867afe772 | |||
| e34fd89bb6 | |||
| 51883b8f11 | |||
| ff5f95227a | |||
| dc929236a4 | |||
| b09e20747a | |||
| e8fc857dbc | |||
| b42a13cc3b | |||
| 37ed9800c5 | |||
| 99f4968888 | |||
| 34428a811c | |||
| da7104b00d | |||
| b373601e13 | |||
| fcc90e884b | |||
| 7a90096077 | |||
| e7886a55fe | |||
| e33b786f65 | |||
| b0b50f4ef9 | |||
| 37c8e6dc07 | |||
| 675441fe93 | |||
| 2495d97862 | |||
| 70c998dd55 | |||
| ec60e46611 | |||
| bb2dc618f7 | |||
| d9addf84ef | |||
| d89163110e | |||
| dcab4eb70b | |||
| 8f252992e4 | |||
| a3a3e32e21 |
@@ -1,3 +1,51 @@
|
||||
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)
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "0.8.5-rc.1",
|
||||
"version": "0.9.2",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -50,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
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+41
-3
@@ -448,6 +448,16 @@ 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
|
||||
@@ -551,15 +561,43 @@ MatrixBaseApis.prototype.removeRoomFromGroupSummary = function(groupId, roomId)
|
||||
/**
|
||||
* @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) {
|
||||
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, {});
|
||||
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" },
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -647,7 +685,7 @@ MatrixBaseApis.prototype.getPublicisedGroups = function(userIds) {
|
||||
|
||||
/**
|
||||
* @param {string} groupId
|
||||
* @param {bool} isPublic Whether the user's mebership of this group is made public
|
||||
* @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.
|
||||
*/
|
||||
|
||||
+15
-1
@@ -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 " +
|
||||
|
||||
+27
-10
@@ -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);
|
||||
|
||||
@@ -628,6 +628,7 @@ MegolmDecryption.prototype.decryptEvent = async function(event) {
|
||||
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') {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+37
-9
@@ -231,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;
|
||||
});
|
||||
@@ -614,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
|
||||
@@ -697,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;
|
||||
|
||||
@@ -777,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);
|
||||
@@ -961,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) {
|
||||
@@ -997,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);
|
||||
});
|
||||
@@ -1277,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
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user