diff --git a/CHANGELOG.md b/CHANGELOG.md index 01b3978cc..10568af89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +Changes in [9.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0) (2021-01-18) +================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.5.0-rc.1...v9.5.0) + + * No changes since rc.1 + +Changes in [9.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.5.0-rc.1) (2021-01-13) +========================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.1...v9.5.0-rc.1) + + * Don't log if no WebRTC + [\#1574](https://github.com/matrix-org/matrix-js-sdk/pull/1574) + * Add _unstable_getSharedRooms + [\#1417](https://github.com/matrix-org/matrix-js-sdk/pull/1417) + * Bump node-notifier from 8.0.0 to 8.0.1 + [\#1568](https://github.com/matrix-org/matrix-js-sdk/pull/1568) + * Ignore party ID if opponent is v0 + [\#1567](https://github.com/matrix-org/matrix-js-sdk/pull/1567) + * Basic call transfer initiation support + [\#1566](https://github.com/matrix-org/matrix-js-sdk/pull/1566) + * Room version 6 is now a thing + [\#1572](https://github.com/matrix-org/matrix-js-sdk/pull/1572) + * Store keys with same index but better trust level + [\#1571](https://github.com/matrix-org/matrix-js-sdk/pull/1571) + * Use TypeScript source for development, swap to build during release + [\#1561](https://github.com/matrix-org/matrix-js-sdk/pull/1561) + * Revert "Ignore party ID if opponent is v0" + [\#1565](https://github.com/matrix-org/matrix-js-sdk/pull/1565) + * Basic call transfer initiation support + [\#1558](https://github.com/matrix-org/matrix-js-sdk/pull/1558) + * Ignore party ID if opponent is v0 + [\#1559](https://github.com/matrix-org/matrix-js-sdk/pull/1559) + * Honour a call reject event from another of our own devices + [\#1562](https://github.com/matrix-org/matrix-js-sdk/pull/1562) + Changes in [9.4.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.4.1) (2020-12-21) ================================================================================================ [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.4.0...v9.4.1) diff --git a/package.json b/package.json index 62f2bba62..96871616b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "9.4.1", + "version": "9.5.0", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepublishOnly": "yarn build", diff --git a/spec/unit/matrix-client.spec.js b/spec/unit/matrix-client.spec.js index fc4a4dc56..3811c5b56 100644 --- a/spec/unit/matrix-client.spec.js +++ b/spec/unit/matrix-client.spec.js @@ -521,4 +521,19 @@ describe("MatrixClient", function() { xit("should be able to peek into a room using peekInRoom", function(done) { }); }); + + describe("getPresence", function() { + it("should send a presence HTTP GET", function() { + httpLookups = [{ + method: "GET", + path: `/presence/${encodeURIComponent(userId)}/status`, + data: { + "presence": "unavailable", + "last_active_ago": 420845, + }, + }]; + client.getPresence(userId); + expect(httpLookups.length).toEqual(0); + }); + }); }); diff --git a/src/client.js b/src/client.js index acf33e64c..343b3f2b3 100644 --- a/src/client.js +++ b/src/client.js @@ -3841,6 +3841,19 @@ MatrixClient.prototype.setPresence = function(opts, callback) { ); }; +/** + * @param {string} userId The user to get presence for + * @param {module:client.callback} callback Optional. + * @return {Promise} Resolves: The presence state for this user. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixClient.prototype.getPresence = function(userId, callback) { + const path = utils.encodeUri("/presence/$userId/status", { + $userId: userId, + }); + + return this._http.authedRequest(callback, "GET", path, undefined, undefined); +}; /** * Retrieve older messages from the given room and put them in the timeline. @@ -5425,6 +5438,11 @@ function checkTurnServers(client) { } }, function(err) { logger.error("Failed to get TURN URIs"); + // If we get a 403, there's no point in looping forever. + if (err.httpStatus === 403) { + logger.info("TURN access unavailable for this account"); + return; + } client._checkTurnServersTimeoutID = setTimeout(function() { checkTurnServers(client); }, 60000); diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index 006a11010..d2e4e86fb 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -264,11 +264,14 @@ MegolmEncryption.prototype._ensureOutboundSession = async function( await Promise.all([ (async () => { // share keys with devices that we already have a session for + logger.debug(`Sharing keys with existing Olm sessions in ${this._roomId}`); await this._shareKeyWithOlmSessions( session, key, payload, olmSessions, ); + logger.debug(`Shared keys with existing Olm sessions in ${this._roomId}`); })(), (async () => { + logger.debug(`Sharing keys with new Olm sessions in ${this._roomId}`); const errorDevices = []; // meanwhile, establish olm sessions for devices that we don't @@ -319,8 +322,10 @@ MegolmEncryption.prototype._ensureOutboundSession = async function( } else { await this._notifyFailedOlmDevices(session, key, errorDevices); } + logger.debug(`Shared keys with new Olm sessions in ${this._roomId}`); })(), (async () => { + logger.debug(`Notifying blocked devices in ${this._roomId}`); // also, notify blocked devices that they're blocked const blockedMap = {}; for (const [userId, userBlockedDevices] of Object.entries(blocked)) { @@ -336,6 +341,7 @@ MegolmEncryption.prototype._ensureOutboundSession = async function( } await this._notifyBlockedDevices(session, blockedMap); + logger.debug(`Notified blocked devices in ${this._roomId}`); })(), ]); }; @@ -348,6 +354,11 @@ MegolmEncryption.prototype._ensureOutboundSession = async function( // first wait for the previous share to complete const prom = this._setupPromise.then(prepareSession); + // Ensure any failures are logged for debugging + prom.catch(e => { + logger.error(`Failed to ensure outbound session in ${this._roomId}`, e); + }); + // _setupPromise resolves to `session` whether or not the share succeeds this._setupPromise = prom.then(returnSession, returnSession); @@ -369,17 +380,11 @@ MegolmEncryption.prototype._prepareNewSession = async function() { key.key, {ed25519: this._olmDevice.deviceEd25519Key}, ); - if (this._crypto.backupInfo) { - // don't wait for it to complete - this._crypto.backupGroupSession( - this._roomId, this._olmDevice.deviceCurve25519Key, [], - sessionId, key.key, - ).catch((e) => { - // This throws if the upload failed, but this is fine - // since it will have written it to the db and will retry. - logger.log("Failed to back up megolm session", e); - }); - } + // don't wait for it to complete + this._crypto.backupGroupSession( + this._roomId, this._olmDevice.deviceCurve25519Key, [], + sessionId, key.key, + ); return new OutboundSessionInfo(sessionId); }; @@ -846,24 +851,41 @@ MegolmEncryption.prototype.prepareToEncrypt = function(room) { // We're already preparing something, so don't do anything else. // FIXME: check if we need to restart // (https://github.com/matrix-org/matrix-js-sdk/issues/1255) + const elapsedTime = Date.now() - this.encryptionPreparationMetadata.startTime; + logger.debug( + `Already started preparing to encrypt for ${this._roomId} ` + + `${elapsedTime} ms ago, skipping`, + ); return; } logger.debug(`Preparing to encrypt events for ${this._roomId}`); + this.encryptionPreparationMetadata = { + startTime: Date.now(), + }; this.encryptionPreparation = (async () => { - const [devicesInRoom, blocked] = await this._getDevicesInRoom(room); + try { + logger.debug(`Getting devices in ${this._roomId}`); + const [devicesInRoom, blocked] = await this._getDevicesInRoom(room); - if (this._crypto.getGlobalErrorOnUnknownDevices()) { - // Drop unknown devices for now. When the message gets sent, we'll - // throw an error, but we'll still be prepared to send to the known - // devices. - this._removeUnknownDevices(devicesInRoom); + if (this._crypto.getGlobalErrorOnUnknownDevices()) { + // Drop unknown devices for now. When the message gets sent, we'll + // throw an error, but we'll still be prepared to send to the known + // devices. + this._removeUnknownDevices(devicesInRoom); + } + + logger.debug(`Ensuring outbound session in ${this._roomId}`); + await this._ensureOutboundSession(devicesInRoom, blocked, true); + + logger.debug(`Ready to encrypt events for ${this._roomId}`); + } catch (e) { + logger.error(`Failed to prepare to encrypt events for ${this._roomId}`, e); + } finally { + delete this.encryptionPreparationMetadata; + delete this.encryptionPreparation; } - - await this._ensureOutboundSession(devicesInRoom, blocked, true); - - delete this.encryptionPreparation; })(); }; @@ -1347,18 +1369,12 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { } }); }).then(() => { - if (this._crypto.backupInfo) { - // don't wait for the keys to be backed up for the server - this._crypto.backupGroupSession( - content.room_id, senderKey, forwardingKeyChain, - content.session_id, content.session_key, keysClaimed, - exportFormat, - ).catch((e) => { - // This throws if the upload failed, but this is fine - // since it will have written it to the db and will retry. - logger.log("Failed to back up megolm session", e); - }); - } + // don't wait for the keys to be backed up for the server + this._crypto.backupGroupSession( + content.room_id, senderKey, forwardingKeyChain, + content.session_id, content.session_key, keysClaimed, + exportFormat, + ); }).catch((e) => { logger.error(`Error handling m.room_key_event: ${e}`); }); @@ -1564,7 +1580,7 @@ MegolmDecryption.prototype.importRoomKey = function(session, opts = {}) { true, opts.untrusted ? { untrusted: opts.untrusted } : {}, ).then(() => { - if (this._crypto.backupInfo && opts.source !== "backup") { + if (opts.source !== "backup") { // don't wait for it to complete this._crypto.backupGroupSession( session.room_id, diff --git a/src/crypto/index.js b/src/crypto/index.js index 332ff7019..9b4a5bd6e 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -2884,18 +2884,18 @@ Crypto.prototype.backupGroupSession = async function( sessionId, sessionKey, keysClaimed, exportFormat, ) { - if (!this.backupInfo) { - throw new Error("Key backups are not enabled"); - } - await this._cryptoStore.markSessionsNeedingBackup([{ senderKey: senderKey, sessionId: sessionId, }]); - // don't wait for this to complete: it will delay so - // happens in the background - this.scheduleKeyBackupSend(); + if (this.backupInfo) { + // don't wait for this to complete: it will delay so + // happens in the background + this.scheduleKeyBackupSend(); + } + // if this.backupInfo is not set, then the keys will be backed up when + // client.enableKeyBackup is called }; /** diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 040562c9a..c9180bc2d 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -1136,8 +1136,21 @@ export class MatrixCall extends EventEmitter { await this.peerConn.setRemoteDescription(description); if (description.type === 'offer') { + // First we sent the direction of the tranciever to what we'd like it to be, + // irresepective of whether the other side has us on hold - so just whether we + // want the call to be on hold or not. This is necessary because in a few lines, + // we'll adjust the direction and unless we do this too, we'll never come off hold. + for (const tranceiver of this.peerConn.getTransceivers()) { + tranceiver.direction = this.isRemoteOnHold() ? 'inactive' : 'sendrecv'; + } const localDescription = await this.peerConn.createAnswer(); await this.peerConn.setLocalDescription(localDescription); + // Now we've got our answer, set the direction to the outcome of the negotiation. + // We need to do this otherwise Firefox will notice that the direction is not the + // currentDirection and try to negotiate itself off hold again. + for (const tranceiver of this.peerConn.getTransceivers()) { + tranceiver.direction = tranceiver.currentDirection; + } this.sendVoipEvent(EventType.CallNegotiate, { description: this.peerConn.localDescription, @@ -1281,7 +1294,7 @@ export class MatrixCall extends EventEmitter { return; // because ICE can still complete as we're ending the call } logger.debug( - "ICE connection state changed to: " + this.peerConn.iceConnectionState, + "Call ID " + this.callId + ": ICE connection state changed to: " + this.peerConn.iceConnectionState, ); // ideally we'd consider the call to be connected when we get media but // chrome doesn't implement any of the 'onstarted' events yet @@ -1399,7 +1412,7 @@ export class MatrixCall extends EventEmitter { } onHangupReceived = (msg) => { - logger.debug("Hangup received"); + logger.debug("Hangup received for call ID " + + this.callId); // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen // a partner yet but we're treating the hangup as a reject as per VoIP v0) @@ -1412,7 +1425,7 @@ export class MatrixCall extends EventEmitter { }; onRejectReceived = (msg) => { - logger.debug("Reject received"); + logger.debug("Reject received for call ID " + this.callId); // No need to check party_id for reject because if we'd received either // an answer or reject, we wouldn't be in state InviteSent @@ -1434,7 +1447,7 @@ export class MatrixCall extends EventEmitter { }; onAnsweredElsewhere = (msg) => { - logger.debug("Answered elsewhere"); + logger.debug("Call ID " + this.callId + " answered elsewhere"); this.terminate(CallParty.Remote, CallErrorCode.AnsweredElsewhere, true); };