Compare commits

..

25 Commits

Author SHA1 Message Date
RiotRobot ae6e2cca27 v9.10.0-rc.1 2021-03-25 12:06:34 +00:00
RiotRobot bd920eef1f Prepare changelog for v9.10.0-rc.1 2021-03-25 12:06:34 +00:00
Travis Ralston ed6d4e5f6c Merge pull request #1647 from SimonBrandner/dont-send-hangup
Don't send m.call.hangup if m.call.invite wasn't sent either
2021-03-22 00:42:04 -06:00
Travis Ralston accfa325b5 Merge pull request #1641 from NicolaiSoeborg/fix-registerGuest
docs: registerGuest()
2021-03-21 20:37:49 -06:00
Šimon Brandner b6ef8d95cd Don't send hangup if invite wasn't sent
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-21 21:29:00 +01:00
Travis Ralston c1144e3810 Merge pull request #1639 from Johennes/feature/chunk-device-keys
Download device keys in chunks of 250
2021-03-18 13:17:15 -06:00
Johannes Marbach 8663fd402b Download device keys in chunks of 250
Depending on the number of users in the request, the server might
overload. To prevent this, the download is broken into chunks of
250 users each. Additionally, no more than 3 requests are kicked off
at the same time to avoid running into rate limiting. Responses are
processed once all chunks have been downloaded.

Fixes: #1619

Signed-off-by: Johannes Marbach <n0-0ne+github@mailbox.org>
2021-03-17 20:56:25 +01:00
David Baker f34052fd31 Merge pull request #1646 from matrix-org/dbkr/more_call_fixes
More VoIP connectivity fixes
2021-03-17 15:00:09 +00:00
David Baker 27d75a269f unintentional comment 2021-03-16 19:18:45 +00:00
David Baker d208a7fc5f Remove unintentionally committed stuff 2021-03-16 19:17:04 +00:00
David Baker 702e16e3df More VoIP connectivity fixes
* Don't ignore other candidates when we see a null one (continue
   rather than return)
 * await on addICECandidate()
 * Don't add ice candidates until we've set a remote description
 * More & better logging
2021-03-16 19:13:03 +00:00
Travis Ralston 12050b14f0 Merge pull request #1644 from SimonBrandner/fix-optional
Make selectDesktopCapturerSource param optional
2021-03-15 21:12:03 -06:00
Nicolai Søborg 2d4a4f1736 docs: registerGuest 2021-03-15 22:46:43 +00:00
RiotRobot cd38fb9b4c Resetting package fields for development 2021-03-15 14:34:56 +00:00
RiotRobot 7941b16ec4 Merge branch 'master' into develop 2021-03-15 14:34:56 +00:00
Šimon Brandner 56ea4b8741 Make selectDesktopCapturerSource param optional
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
2021-03-13 15:03:36 +01:00
Nicolai Søborg 6dabfcda6f setGuest(true) when registerGuest()
Signed-off-by: Nicolai Søborg <git@xn--sb-lka.org>
2021-03-11 22:39:51 +01:00
J. Ryan Stinnett 0b7b35f800 Merge pull request #1632 from matrix-org/matthew/rework-cross-signing-login
Expose APIs needed for reworked cross-signing login flow
2021-03-11 12:54:18 +00:00
Matthew Hodgson 9fb2fbaeec factor out getDehydratedDevice 2021-03-09 00:09:22 +00:00
Matthew Hodgson 977682d37f fix lint 2021-03-08 09:24:25 +00:00
Matthew Hodgson 0bafe263d7 fix lint 2021-03-08 05:05:14 +00:00
Matthew Hodgson 1a8fced80e Merge branch 'develop' into matthew/rework-cross-signing-login 2021-03-08 04:59:40 +00:00
Matthew Hodgson 1c4d0b5e99 expose getDevice API 2021-03-08 04:59:29 +00:00
Matthew Hodgson 844a2b457c expose getDehydratedDevice API 2021-03-08 04:59:19 +00:00
Matthew Hodgson ccf06f2216 don't cancel ourselves when selecting a self-verification partner 2021-03-08 04:58:55 +00:00
11 changed files with 206 additions and 38 deletions
+17
View File
@@ -1,3 +1,20 @@
Changes in [9.10.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.10.0-rc.1) (2021-03-25)
============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.9.0...v9.10.0-rc.1)
* Don't send m.call.hangup if m.call.invite wasn't sent either
[\#1647](https://github.com/matrix-org/matrix-js-sdk/pull/1647)
* docs: registerGuest()
[\#1641](https://github.com/matrix-org/matrix-js-sdk/pull/1641)
* Download device keys in chunks of 250
[\#1639](https://github.com/matrix-org/matrix-js-sdk/pull/1639)
* More VoIP connectivity fixes
[\#1646](https://github.com/matrix-org/matrix-js-sdk/pull/1646)
* Make selectDesktopCapturerSource param optional
[\#1644](https://github.com/matrix-org/matrix-js-sdk/pull/1644)
* Expose APIs needed for reworked cross-signing login flow
[\#1632](https://github.com/matrix-org/matrix-js-sdk/pull/1632)
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)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "matrix-js-sdk",
"version": "9.9.0",
"version": "9.10.0-rc.1",
"description": "Matrix Client-Server SDK for Javascript",
"scripts": {
"prepublishOnly": "yarn build",
+58 -2
View File
@@ -51,6 +51,36 @@ const signedDeviceList = {
},
};
const signedDeviceList2 = {
"failures": {},
"device_keys": {
"@test2:sw1v.org": {
"QJVRHWAKGH": {
"signatures": {
"@test2:sw1v.org": {
"ed25519:QJVRHWAKGH":
"w1xxdLe1iIqzEFHLRVYQeuiM6t2N2ZRiI8s5nDKxf054BP8" +
"1CPEX/AQXh5BhkKAVMlKnwg4T9zU1/wBALeajk3",
},
},
"user_id": "@test2:sw1v.org",
"keys": {
"ed25519:QJVRHWAKGH":
"Ig0/C6T+bBII1l2By2Wnnvtjp1nm/iXBlLU5/QESFXL",
"curve25519:QJVRHWAKGH":
"YR3eQnUvTQzGlWih4rsmJkKxpDxzgkgIgsBd1DEZIbm",
},
"algorithms": [
"m.olm.v1.curve25519-aes-sha2",
"m.megolm.v1.aes-sha2",
],
"device_id": "QJVRHWAKGH",
"unsigned": {},
},
},
},
};
describe('DeviceList', function() {
let downloadSpy;
let cryptoStore;
@@ -69,7 +99,7 @@ describe('DeviceList', function() {
}
});
function createTestDeviceList() {
function createTestDeviceList(keyDownloadChunkSize = 250) {
const baseApis = {
downloadKeysForUsers: downloadSpy,
getUserId: () => '@test1:sw1v.org',
@@ -78,7 +108,7 @@ describe('DeviceList', function() {
const mockOlm = {
verifySignature: function(key, message, signature) {},
};
const dl = new DeviceList(baseApis, cryptoStore, mockOlm);
const dl = new DeviceList(baseApis, cryptoStore, mockOlm, keyDownloadChunkSize);
deviceLists.push(dl);
return dl;
}
@@ -150,4 +180,30 @@ describe('DeviceList', function() {
expect(Object.keys(storedKeys)).toEqual(['HGKAWHRVJQ']);
});
});
it("should download device keys in batches", function() {
const dl = createTestDeviceList(1);
dl.startTrackingDeviceList('@test1:sw1v.org');
dl.startTrackingDeviceList('@test2:sw1v.org');
const queryDefer1 = utils.defer();
downloadSpy.mockReturnValueOnce(queryDefer1.promise);
const queryDefer2 = utils.defer();
downloadSpy.mockReturnValueOnce(queryDefer2.promise);
const prom1 = dl.refreshOutdatedDeviceLists();
expect(downloadSpy).toBeCalledTimes(2);
expect(downloadSpy).toHaveBeenNthCalledWith(1, ['@test1:sw1v.org'], {});
expect(downloadSpy).toHaveBeenNthCalledWith(2, ['@test2:sw1v.org'], {});
queryDefer1.resolve(utils.deepCopy(signedDeviceList));
queryDefer2.resolve(utils.deepCopy(signedDeviceList2));
return prom1.then(() => {
const storedKeys1 = dl.getRawStoredDevicesForUser('@test1:sw1v.org');
expect(Object.keys(storedKeys1)).toEqual(['HGKAWHRVJQ']);
const storedKeys2 = dl.getRawStoredDevicesForUser('@test2:sw1v.org');
expect(Object.keys(storedKeys2)).toEqual(['QJVRHWAKGH']);
});
});
});
+26
View File
@@ -282,4 +282,30 @@ describe("utils", function() {
expect(target.nonenumerableProp).toBe(undefined);
});
});
describe("chunkPromises", function() {
it("should execute promises in chunks", async function() {
let promiseCount = 0;
function fn1() {
return new Promise(async function(resolve, reject) {
await utils.sleep(1);
expect(promiseCount).toEqual(0);
++promiseCount;
resolve();
});
}
function fn2() {
return new Promise(function(resolve, reject) {
expect(promiseCount).toEqual(1);
++promiseCount;
resolve();
});
}
await utils.chunkPromises([fn1, fn2], 1);
expect(promiseCount).toEqual(2);
});
});
});
+31 -1
View File
@@ -246,10 +246,25 @@ MatrixBaseApis.prototype.register = function(
/**
* Register a guest account.
* This method returns the auth info needed to create a new authenticated client,
* Remember to call `setGuest(true)` on the (guest-)authenticated client, e.g:
* ```javascript
* const tmpClient = await sdk.createClient(MATRIX_INSTANCE);
* const { user_id, device_id, access_token } = tmpClient.registerGuest();
* const client = createClient({
* baseUrl: MATRIX_INSTANCE,
* accessToken: access_token,
* userId: user_id,
* deviceId: device_id,
* })
* client.setGuest(true);
* ```
*
* @param {Object=} opts Registration options
* @param {Object} opts.body JSON HTTP body to provide.
* @param {module:client.callback} callback Optional.
* @return {Promise} Resolves: TODO
* @return {Promise} Resolves: JSON object that contains:
* { user_id, device_id, access_token, home_server }
* @return {module:http-api.MatrixError} Rejects: with an error response.
*/
MatrixBaseApis.prototype.registerGuest = function(opts, callback) {
@@ -1518,6 +1533,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
*
+23 -13
View File
@@ -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.
+16 -9
View File
@@ -28,7 +28,7 @@ import {DeviceInfo} from './deviceinfo';
import {CrossSigningInfo} from './CrossSigning';
import * as olmlib from './olmlib';
import {IndexedDBCryptoStore} from './store/indexeddb-crypto-store';
import {defer, sleep} from '../utils';
import {chunkPromises, defer, sleep} from '../utils';
/* State transition diagram for DeviceList._deviceTrackingStatus
@@ -62,7 +62,7 @@ const TRACKING_STATUS_UP_TO_DATE = 3;
* @alias module:crypto/DeviceList
*/
export class DeviceList extends EventEmitter {
constructor(baseApis, cryptoStore, olmDevice) {
constructor(baseApis, cryptoStore, olmDevice, keyDownloadChunkSize = 250) {
super();
this._cryptoStore = cryptoStore;
@@ -98,6 +98,9 @@ export class DeviceList extends EventEmitter {
// userId -> promise
this._keyDownloadsInProgressByUser = {};
// Maximum number of user IDs per request to prevent server overload (#1619)
this._keyDownloadChunkSize = keyDownloadChunkSize;
// Set whenever changes are made other than setting the sync token
this._dirty = false;
@@ -780,13 +783,17 @@ class DeviceListUpdateSerialiser {
opts.token = this._syncToken;
}
this._baseApis.downloadKeysForUsers(
downloadUsers, opts,
).then(async (res) => {
const dk = res.device_keys || {};
const masterKeys = res.master_keys || {};
const ssks = res.self_signing_keys || {};
const usks = res.user_signing_keys || {};
const factories = [];
for (let i = 0; i < downloadUsers.length; i += this._deviceList._keyDownloadChunkSize) {
const userSlice = downloadUsers.slice(i, i + this._deviceList._keyDownloadChunkSize);
factories.push(() => this._baseApis.downloadKeysForUsers(userSlice, opts));
}
chunkPromises(factories, 3).then(async (responses) => {
const dk = Object.assign({}, ...(responses.map(res => res.device_keys || {})));
const masterKeys = Object.assign({}, ...(responses.map(res => res.master_keys || {})));
const ssks = Object.assign({}, ...(responses.map(res => res.self_signing_keys || {})));
const usks = Object.assign({}, ...(responses.map(res => res.user_signing_keys || {})));
// yield to other things that want to execute in between users, to
// avoid wedging the CPU
@@ -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",
+1 -1
View File
@@ -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();
+9
View File
@@ -745,6 +745,15 @@ export function promiseTry<T>(fn: () => T): Promise<T> {
return new Promise((resolve) => resolve(fn()));
}
// Creates and awaits all promises, running no more than `chunkSize` at the same time
export async function chunkPromises<T>(fns: (() => Promise<T>)[], chunkSize: number): Promise<T[]> {
const results: T[] = [];
for (let i = 0; i < fns.length; i += chunkSize) {
results.push(...(await Promise.all(fns.slice(i, i + chunkSize).map(fn => fn()))));
}
return results;
}
// We need to be able to access the Node.js crypto library from within the
// Matrix SDK without needing to `require("crypto")`, which will fail in
// browsers. So `index.ts` will call `setCrypto` to store it, and when we need
+21 -10
View File
@@ -372,7 +372,7 @@ export class MatrixCall extends EventEmitter {
async placeScreenSharingCall(
remoteVideoElement: HTMLVideoElement,
localVideoElement: HTMLVideoElement,
selectDesktopCapturerSource: () => Promise<DesktopCapturerSource>,
selectDesktopCapturerSource?: () => Promise<DesktopCapturerSource>,
) {
logger.debug("placeScreenSharingCall");
this.checkForErrorListener();
@@ -541,6 +541,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);
@@ -659,6 +660,8 @@ export class MatrixCall extends EventEmitter {
logger.debug("Ending call " + this.callId);
this.terminate(CallParty.Local, reason, !suppressEvent);
// We don't want to send hangup here if we didn't even get to sending an invite
if (this.state === CallState.WaitLocalMedia) return;
const content = {};
// Continue to send no reason for user hangups temporarily, until
// clients understand the user_hangup reason (voip v1)
@@ -985,7 +988,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,
);
@@ -1019,7 +1022,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;
@@ -1051,7 +1054,7 @@ export class MatrixCall extends EventEmitter {
return;
}
this.addIceCandidates(cands);
await this.addIceCandidates(cands);
}
/**
@@ -1059,7 +1062,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;
}
@@ -1072,6 +1078,7 @@ export class MatrixCall extends EventEmitter {
}
this.chooseOpponent(event);
await this.addBufferedIceCandidates();
this.setState(CallState.Connecting);
@@ -1722,6 +1729,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
@@ -1735,30 +1744,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);
}
}
}