diff --git a/CHANGELOG.md b/CHANGELOG.md index b88da3c3d..bdfc2363a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,58 @@ +Changes in [9.9.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.9.0) (2021-03-15) +================================================================================================ +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.9.0-rc.1...v9.9.0) + + * No changes since rc.1 + +Changes in [9.9.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.9.0-rc.1) (2021-03-10) +========================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.8.0...v9.9.0-rc.1) + + * Remove detailed Olm session logging + [\#1638](https://github.com/matrix-org/matrix-js-sdk/pull/1638) + * Add space summary suggested only param + [\#1637](https://github.com/matrix-org/matrix-js-sdk/pull/1637) + * Check TURN servers periodically, and at start of calls + [\#1634](https://github.com/matrix-org/matrix-js-sdk/pull/1634) + * Support sending invite reasons + [\#1624](https://github.com/matrix-org/matrix-js-sdk/pull/1624) + * Bump elliptic from 6.5.3 to 6.5.4 + [\#1636](https://github.com/matrix-org/matrix-js-sdk/pull/1636) + * Add a function to get a room's MXC URI + [\#1635](https://github.com/matrix-org/matrix-js-sdk/pull/1635) + * Stop streams if the call has ended + [\#1633](https://github.com/matrix-org/matrix-js-sdk/pull/1633) + * Remove export keyword from global.d.ts + [\#1631](https://github.com/matrix-org/matrix-js-sdk/pull/1631) + * Fix IndexedDB store creation example + [\#1445](https://github.com/matrix-org/matrix-js-sdk/pull/1445) + * An attempt to cleanup how constraints are handled in calls + [\#1613](https://github.com/matrix-org/matrix-js-sdk/pull/1613) + * Extract display name patterns to constants + [\#1628](https://github.com/matrix-org/matrix-js-sdk/pull/1628) + * Bump pug-code-gen from 2.0.2 to 2.0.3 + [\#1630](https://github.com/matrix-org/matrix-js-sdk/pull/1630) + * Avoid deadlocks when ensuring Olm sessions for devices + [\#1627](https://github.com/matrix-org/matrix-js-sdk/pull/1627) + * Filter out edits from other senders in history + [\#1626](https://github.com/matrix-org/matrix-js-sdk/pull/1626) + * Fix ContentHelpers export + [\#1618](https://github.com/matrix-org/matrix-js-sdk/pull/1618) + * Add logging to in progress Olm sessions + [\#1621](https://github.com/matrix-org/matrix-js-sdk/pull/1621) + * Don't ignore ICE candidates received before offer/answer + [\#1623](https://github.com/matrix-org/matrix-js-sdk/pull/1623) + * Better handling of send failures on VoIP events + [\#1622](https://github.com/matrix-org/matrix-js-sdk/pull/1622) + * Log when turn creds expire + [\#1620](https://github.com/matrix-org/matrix-js-sdk/pull/1620) + * Initial Spaces [MSC1772] support + [\#1563](https://github.com/matrix-org/matrix-js-sdk/pull/1563) + * Add logging to crypto store transactions + [\#1617](https://github.com/matrix-org/matrix-js-sdk/pull/1617) + * Room helper for getting type and checking if it is a space room + [\#1610](https://github.com/matrix-org/matrix-js-sdk/pull/1610) + Changes in [9.8.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.8.0) (2021-03-01) ================================================================================================ [Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.8.0-rc.1...v9.8.0) diff --git a/package.json b/package.json index f9fafb132..c4b9e4047 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "9.8.0", + "version": "9.9.0", "description": "Matrix Client-Server SDK for Javascript", "scripts": { "prepublishOnly": "yarn build", diff --git a/src/base-apis.js b/src/base-apis.js index 37c3d76d6..a86d24465 100644 --- a/src/base-apis.js +++ b/src/base-apis.js @@ -1518,6 +1518,21 @@ MatrixBaseApis.prototype.getDevices = function() { ); }; +/** + * Gets specific device details for the logged-in user + * @param {string} device_id device to query + * @return {Promise} Resolves: result object + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ +MatrixBaseApis.prototype.getDevice = function(device_id) { + const path = utils.encodeUri("/devices/$device_id", { + $device_id: device_id, + }); + return this._http.authedRequest( + undefined, 'GET', path, undefined, undefined, + ); +}; + /** * Update the given device * @@ -2378,18 +2393,27 @@ MatrixBaseApis.prototype.reportEvent = function(roomId, eventId, score, reason) * Fetches or paginates a summary of a space as defined by MSC2946 * @param {string} roomId The ID of the space-room to use as the root of the summary. * @param {number?} maxRoomsPerSpace The maximum number of rooms to return per subspace. + * @param {boolean?} suggestedOnly Whether to only return rooms with suggested=true. * @param {boolean?} autoJoinOnly Whether to only return rooms with auto_join=true. * @param {number?} limit The maximum number of rooms to return in total. * @param {string?} batch The opaque token to paginate a previous summary request. * @returns {Promise} the response, with next_batch, rooms, events fields. */ -MatrixBaseApis.prototype.getSpaceSummary = function(roomId, maxRoomsPerSpace, autoJoinOnly, limit, batch) { +MatrixBaseApis.prototype.getSpaceSummary = function( + roomId, + maxRoomsPerSpace, + suggestedOnly, + autoJoinOnly, + limit, + batch, +) { const path = utils.encodeUri("/rooms/$roomId/spaces", { $roomId: roomId, }); return this._http.authedRequest(undefined, "POST", path, null, { max_rooms_per_space: maxRoomsPerSpace, + suggested_only: suggestedOnly, auto_join_only: autoJoinOnly, limit, batch, diff --git a/src/client.js b/src/client.js index 8832c0965..53fca78ac 100644 --- a/src/client.js +++ b/src/client.js @@ -500,19 +500,8 @@ MatrixClient.prototype.rehydrateDevice = async function() { return; } - let getDeviceResult; - try { - getDeviceResult = await this._http.authedRequest( - undefined, - "GET", - "/dehydrated_device", - undefined, undefined, - { - prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2", - }, - ); - } catch (e) { - logger.info("could not get dehydrated device", e.toString()); + const getDeviceResult = this.getDehydratedDevice(); + if (!getDeviceResult) { return; } @@ -574,6 +563,27 @@ MatrixClient.prototype.rehydrateDevice = async function() { } }; +/** + * Get the current dehydrated device, if any + * @return {Promise} A promise of an object containing the dehydrated device + */ +MatrixClient.prototype.getDehydratedDevice = async function() { + try { + return await this._http.authedRequest( + undefined, + "GET", + "/dehydrated_device", + undefined, undefined, + { + prefix: "/_matrix/client/unstable/org.matrix.msc2697.v2", + }, + ); + } catch (e) { + logger.info("could not get dehydrated device", e.toString()); + return; + } +}; + /** * Set the dehydration key. This will also periodically dehydrate devices to * the server. diff --git a/src/crypto/olmlib.js b/src/crypto/olmlib.js index eb0d8a4a9..f1e487e97 100644 --- a/src/crypto/olmlib.js +++ b/src/crypto/olmlib.js @@ -213,9 +213,8 @@ export async function ensureOlmSessionsForDevices( // synchronous operation, as otherwise it is possible to have deadlocks // where multiple tasks wait indefinitely on another task to update some set // of common devices. - for (const [userId, devices] of Object.entries(devicesByUser)) { + for (const [, devices] of Object.entries(devicesByUser)) { for (const deviceInfo of devices) { - const deviceId = deviceInfo.deviceId; const key = deviceInfo.getIdentityKey(); if (key === olmDevice.deviceCurve25519Key) { @@ -224,15 +223,12 @@ export async function ensureOlmSessionsForDevices( continue; } - const forWhom = `for ${key} (${userId}:${deviceId})`; if (!olmDevice._sessionsInProgress[key]) { // pre-emptively mark the session as in-progress to avoid race // conditions. If we find that we already have a session, then // we'll resolve - log.debug(`Marking Olm session in progress ${forWhom}`); olmDevice._sessionsInProgress[key] = new Promise(resolve => { resolveSession[key] = (...args) => { - log.debug(`Resolved Olm session in progress ${forWhom}`); delete olmDevice._sessionsInProgress[key]; resolve(...args); }; @@ -266,11 +262,9 @@ export async function ensureOlmSessionsForDevices( } const forWhom = `for ${key} (${userId}:${deviceId})`; - log.debug(`Ensuring Olm session ${forWhom}`); const sessionId = await olmDevice.getSessionIdForDevice( key, resolveSession[key], log, ); - log.debug(`Got Olm session ${sessionId} ${forWhom}`); if (sessionId !== null && resolveSession[key]) { // we found a session, but we had marked the session as // in-progress, so resolve it now, which will unmark it and @@ -299,18 +293,6 @@ export async function ensureOlmSessionsForDevices( const oneTimeKeyAlgorithm = "signed_curve25519"; let res; let taskDetail = `one-time keys for ${devicesWithoutSession.length} devices`; - // If your homeserver takes a nap here and never replies, this process - // would hang indefinitely. While that's easily fixed by setting a - // timeout on this request, let's first log whether that's the root - // cause we're seeing in practice. - // See also https://github.com/vector-im/element-web/issues/16194 - let otkTimeoutLogger; - // XXX: Perhaps there should be a default timeout? - if (otkTimeout) { - otkTimeoutLogger = setTimeout(() => { - log.error(`Homeserver never replied while claiming ${taskDetail}`); - }, otkTimeout); - } try { log.debug(`Claiming ${taskDetail}`); res = await baseApis.claimOneTimeKeys( @@ -323,8 +305,6 @@ export async function ensureOlmSessionsForDevices( } log.log(`Failed to claim ${taskDetail}`, e, devicesWithoutSession); throw e; - } finally { - clearTimeout(otkTimeoutLogger); } if (failedServers && "failures" in res) { diff --git a/src/crypto/store/indexeddb-crypto-store-backend.js b/src/crypto/store/indexeddb-crypto-store-backend.js index 6ecffe7be..c3203240f 100644 --- a/src/crypto/store/indexeddb-crypto-store-backend.js +++ b/src/crypto/store/indexeddb-crypto-store-backend.js @@ -20,6 +20,7 @@ import {logger} from '../../logger'; import * as utils from "../../utils"; export const VERSION = 9; +const PROFILE_TRANSACTIONS = false; /** * Implementation of a CryptoStore which is backed by an existing @@ -759,20 +760,26 @@ export class Backend { } doTxn(mode, stores, func, log = logger) { - const txnId = this._nextTxnId++; - const startTime = Date.now(); - const description = `${mode} crypto store transaction ${txnId} in ${stores}`; - log.debug(`Starting ${description}`); + let startTime; + let description; + if (PROFILE_TRANSACTIONS) { + const txnId = this._nextTxnId++; + startTime = Date.now(); + description = `${mode} crypto store transaction ${txnId} in ${stores}`; + log.debug(`Starting ${description}`); + } const txn = this._db.transaction(stores, mode); const promise = promiseifyTxn(txn); const result = func(txn); - promise.then(() => { - const elapsedTime = Date.now() - startTime; - log.debug(`Finished ${description}, took ${elapsedTime} ms`); - }, () => { - const elapsedTime = Date.now() - startTime; - log.error(`Failed ${description}, took ${elapsedTime} ms`); - }); + if (PROFILE_TRANSACTIONS) { + promise.then(() => { + const elapsedTime = Date.now() - startTime; + log.debug(`Finished ${description}, took ${elapsedTime} ms`); + }, () => { + const elapsedTime = Date.now() - startTime; + log.error(`Failed ${description}, took ${elapsedTime} ms`); + }); + } return promise.then(() => { return result; }); diff --git a/src/crypto/verification/request/ToDeviceChannel.js b/src/crypto/verification/request/ToDeviceChannel.js index d04a75853..9f3659389 100644 --- a/src/crypto/verification/request/ToDeviceChannel.js +++ b/src/crypto/verification/request/ToDeviceChannel.js @@ -179,7 +179,9 @@ export class ToDeviceChannel { const isAcceptingEvent = type === START_TYPE || type === READY_TYPE; // the request has picked a ready or start event, tell the other devices about it if (isAcceptingEvent && !wasStarted && isStarted && this._deviceId) { - const nonChosenDevices = this._devices.filter(d => d !== this._deviceId); + const nonChosenDevices = this._devices.filter( + d => d !== this._deviceId && d !== this._client.getDeviceId(), + ); if (nonChosenDevices.length) { const message = this.completeContent({ code: "m.accepted", diff --git a/src/http-api.js b/src/http-api.js index cc86506ad..26b3d5f5b 100644 --- a/src/http-api.js +++ b/src/http-api.js @@ -271,10 +271,10 @@ MatrixHttpApi.prototype = { xhr.timeout_timer = callbacks.setTimeout(timeout_fn, 30000); xhr.onreadystatechange = function() { + let resp; switch (xhr.readyState) { case global.XMLHttpRequest.DONE: callbacks.clearTimeout(xhr.timeout_timer); - var resp; try { if (xhr.status === 0) { throw new AbortError(); diff --git a/src/store/indexeddb-local-backend.js b/src/store/indexeddb-local-backend.js index d538e88f0..3a0af28b1 100644 --- a/src/store/indexeddb-local-backend.js +++ b/src/store/indexeddb-local-backend.js @@ -435,7 +435,7 @@ LocalIndexedDBStoreBackend.prototype = { * @return {Promise} Resolves if the data was persisted. */ _persistSyncData: function(nextBatch, roomsData, groupsData) { - logger.log("Persisting sync data up to ", nextBatch); + logger.log("Persisting sync data up to", nextBatch); return utils.promiseTry(() => { const txn = this.db.transaction(["sync"], "readwrite"); const store = txn.objectStore("sync"); diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 1950088b4..6d12021f0 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -479,6 +479,7 @@ export class MatrixCall extends EventEmitter { this.chooseOpponent(event); try { await this.peerConn.setRemoteDescription(invite.offer); + await this.addBufferedIceCandidates(); } catch (e) { logger.debug("Failed to set remote description", e); this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false); @@ -883,7 +884,7 @@ export class MatrixCall extends EventEmitter { private gotLocalIceCandidate = (event: RTCPeerConnectionIceEvent) => { if (event.candidate) { logger.debug( - "Got local ICE " + event.candidate.sdpMid + " candidate: " + + "Call " + this.callId + " got local ICE " + event.candidate.sdpMid + " candidate: " + event.candidate.candidate, ); @@ -917,7 +918,7 @@ export class MatrixCall extends EventEmitter { } }; - onRemoteIceCandidatesReceived(ev: MatrixEvent) { + async onRemoteIceCandidatesReceived(ev: MatrixEvent) { if (this.callHasEnded()) { //debuglog("Ignoring remote ICE candidate because call has ended"); return; @@ -949,7 +950,7 @@ export class MatrixCall extends EventEmitter { return; } - this.addIceCandidates(cands); + await this.addIceCandidates(cands); } /** @@ -957,7 +958,10 @@ export class MatrixCall extends EventEmitter { * @param {Object} msg */ async onAnswerReceived(event: MatrixEvent) { + logger.debug(`Got answer for call ID ${this.callId} from party ID ${event.getContent().party_id}`); + if (this.callHasEnded()) { + logger.debug(`Ignoring answer because call ID ${this.callId} has ended`); return; } @@ -970,6 +974,7 @@ export class MatrixCall extends EventEmitter { } this.chooseOpponent(event); + await this.addBufferedIceCandidates(); this.setState(CallState.Connecting); @@ -1549,6 +1554,8 @@ export class MatrixCall extends EventEmitter { // I choo-choo-choose you const msg = ev.getContent(); + logger.debug(`Choosing party ID ${msg.party_id} for call ID ${this.callId}`); + this.opponentVersion = msg.version; if (this.opponentVersion === 0) { // set to null to indicate that we've chosen an opponent, but because @@ -1562,30 +1569,32 @@ export class MatrixCall extends EventEmitter { } this.opponentCaps = msg.capabilities || {}; this.opponentMember = ev.sender; + } + private async addBufferedIceCandidates() { const bufferedCands = this.remoteCandidateBuffer.get(this.opponentPartyId); if (bufferedCands) { logger.info(`Adding ${bufferedCands.length} buffered candidates for opponent ${this.opponentPartyId}`); - this.addIceCandidates(bufferedCands); + await this.addIceCandidates(bufferedCands); } this.remoteCandidateBuffer = null; } - private addIceCandidates(cands: RTCIceCandidate[]) { + private async addIceCandidates(cands: RTCIceCandidate[]) { for (const cand of cands) { if ( (cand.sdpMid === null || cand.sdpMid === undefined) && (cand.sdpMLineIndex === null || cand.sdpMLineIndex === undefined) ) { logger.debug("Ignoring remote ICE candidate with no sdpMid or sdpMLineIndex"); - return; + continue; } - logger.debug("Got remote ICE " + cand.sdpMid + " candidate: " + cand.candidate); + logger.debug("Call " + this.callId + " got remote ICE " + cand.sdpMid + " candidate: " + cand.candidate); try { - this.peerConn.addIceCandidate(cand); + await this.peerConn.addIceCandidate(cand); } catch (err) { if (!this.ignoreOffer) { - logger.info("Failed to add remore ICE candidate", err); + logger.info("Failed to add remote ICE candidate", err); } } }