Compare commits
38 Commits
v9.6.0
...
v9.7.0-rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
| b026e1c2f7 | |||
| f8194d9418 | |||
| 66bf0ec7af | |||
| 1b22df2b7b | |||
| 9f993f1f67 | |||
| 975518bd88 | |||
| 66a863456c | |||
| 91290c0d25 | |||
| 8a23e89c87 | |||
| 9e9cf85ba1 | |||
| 3dd365bbea | |||
| 33a824b980 | |||
| 8571884304 | |||
| 4f1067e66c | |||
| 7b5b851db0 | |||
| ed0be0cf84 | |||
| d3775e5cb1 | |||
| 2c8f658810 | |||
| 5c52f5f579 | |||
| 0a81bb3fdc | |||
| f33196bc51 | |||
| 6beb90a835 | |||
| 9d45e6acd6 | |||
| 516c464458 | |||
| 6ad3fb16b3 | |||
| 277fdd9b8c | |||
| 14cd37ec56 | |||
| 6776df8e80 | |||
| fbec079c9b | |||
| 4a3b68de8f | |||
| 349bb2730a | |||
| c13813348d | |||
| 26e70d6b30 | |||
| c701bf279f | |||
| c4e7c149a4 | |||
| f91edfabbb | |||
| f410004d45 | |||
| 49e238d580 |
@@ -1,3 +1,32 @@
|
||||
Changes in [9.7.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.7.0-rc.1) (2021-02-10)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0...v9.7.0-rc.1)
|
||||
|
||||
* Handle undefined peerconn
|
||||
[\#1600](https://github.com/matrix-org/matrix-js-sdk/pull/1600)
|
||||
* ReEmitter: Don't throw if no error handler is attached
|
||||
[\#1599](https://github.com/matrix-org/matrix-js-sdk/pull/1599)
|
||||
* Convert ReEmitter to TS
|
||||
[\#1598](https://github.com/matrix-org/matrix-js-sdk/pull/1598)
|
||||
* Fix typo in main readme
|
||||
[\#1597](https://github.com/matrix-org/matrix-js-sdk/pull/1597)
|
||||
* Remove rogue plus character
|
||||
[\#1596](https://github.com/matrix-org/matrix-js-sdk/pull/1596)
|
||||
* Fix call ID NaN
|
||||
[\#1595](https://github.com/matrix-org/matrix-js-sdk/pull/1595)
|
||||
* Fix Electron type merging
|
||||
[\#1594](https://github.com/matrix-org/matrix-js-sdk/pull/1594)
|
||||
* Fix browser screen share
|
||||
[\#1593](https://github.com/matrix-org/matrix-js-sdk/pull/1593)
|
||||
* Fix desktop Matrix screen sharing
|
||||
[\#1570](https://github.com/matrix-org/matrix-js-sdk/pull/1570)
|
||||
* Guard against confused server retry times
|
||||
[\#1591](https://github.com/matrix-org/matrix-js-sdk/pull/1591)
|
||||
* Decrypt redaction events
|
||||
[\#1589](https://github.com/matrix-org/matrix-js-sdk/pull/1589)
|
||||
* Fix edge cases with peeking where a room is re-peeked
|
||||
[\#1587](https://github.com/matrix-org/matrix-js-sdk/pull/1587)
|
||||
|
||||
Changes in [9.6.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.6.0) (2021-02-03)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.6.0-rc.1...v9.6.0)
|
||||
|
||||
@@ -307,7 +307,7 @@ The SDK supports end-to-end encryption via the Olm and Megolm protocols, using
|
||||
[libolm](https://gitlab.matrix.org/matrix-org/olm). It is left up to the
|
||||
application to make libolm available, via the ``Olm`` global.
|
||||
|
||||
It is also necessry to call ``matrixClient.initCrypto()`` after creating a new
|
||||
It is also necessary to call ``matrixClient.initCrypto()`` after creating a new
|
||||
``MatrixClient`` (but **before** calling ``matrixClient.startClient()``) to
|
||||
initialise the crypto layer.
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "9.6.0",
|
||||
"version": "9.7.0-rc.1",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"scripts": {
|
||||
"prepublishOnly": "yarn build",
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
import { ReEmitter } from "../../src/ReEmitter";
|
||||
|
||||
const EVENTNAME = "UnknownEntry";
|
||||
|
||||
class EventSource extends EventEmitter {
|
||||
doTheThing() {
|
||||
this.emit(EVENTNAME, "foo", "bar");
|
||||
}
|
||||
|
||||
doAnError() {
|
||||
this.emit('error');
|
||||
}
|
||||
}
|
||||
|
||||
class EventTarget extends EventEmitter {
|
||||
|
||||
}
|
||||
|
||||
describe("ReEmitter", function() {
|
||||
it("Re-Emits events with the same args", function() {
|
||||
const src = new EventSource();
|
||||
const tgt = new EventTarget();
|
||||
|
||||
const handler = jest.fn();
|
||||
tgt.on(EVENTNAME, handler);
|
||||
|
||||
const reEmitter = new ReEmitter(tgt);
|
||||
reEmitter.reEmit(src, [EVENTNAME]);
|
||||
|
||||
src.doTheThing();
|
||||
|
||||
// Args should be the args passed to 'emit' after the event name, and
|
||||
// also the source object of the event which re-emitter adds
|
||||
expect(handler).toHaveBeenCalledWith("foo", "bar", src);
|
||||
});
|
||||
|
||||
it("Doesn't throw if no handler for 'error' event", function() {
|
||||
const src = new EventSource();
|
||||
const tgt = new EventTarget();
|
||||
|
||||
const reEmitter = new ReEmitter(tgt);
|
||||
reEmitter.reEmit(src, ['error']);
|
||||
|
||||
// without the workaround in ReEmitter, this would throw
|
||||
src.doAnError();
|
||||
|
||||
const handler = jest.fn();
|
||||
tgt.on('error', handler);
|
||||
|
||||
src.doAnError();
|
||||
|
||||
// Now we've attached an error handler, it should be called
|
||||
expect(handler).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Vendored
+40
-1
@@ -26,10 +26,49 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
interface Window {
|
||||
electron?: Electron;
|
||||
}
|
||||
|
||||
interface Electron {
|
||||
getDesktopCapturerSources(options: GetSourcesOptions): Promise<Array<DesktopCapturerSource>>;
|
||||
}
|
||||
|
||||
interface MediaDevices {
|
||||
// This is experimental and types don't know about it yet
|
||||
// https://github.com/microsoft/TypeScript/issues/33232
|
||||
getDisplayMedia(constraints: MediaStreamConstraints): Promise<MediaStream>;
|
||||
getDisplayMedia(constraints: MediaStreamConstraints | DesktopCapturerConstraints): Promise<MediaStream>;
|
||||
getUserMedia(constraints: MediaStreamConstraints | DesktopCapturerConstraints): Promise<MediaStream>;
|
||||
}
|
||||
|
||||
export interface DesktopCapturerConstraints {
|
||||
audio: boolean | {
|
||||
mandatory: {
|
||||
chromeMediaSource: string;
|
||||
chromeMediaSourceId: string;
|
||||
};
|
||||
};
|
||||
video: boolean | {
|
||||
mandatory: {
|
||||
chromeMediaSource: string;
|
||||
chromeMediaSourceId: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface DesktopCapturerSource {
|
||||
id: string;
|
||||
name: string;
|
||||
thumbnailURL: string;
|
||||
}
|
||||
|
||||
interface GetSourcesOptions {
|
||||
types: Array<string>;
|
||||
thumbnailSize?: {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
fetchWindowIcons?: boolean;
|
||||
}
|
||||
|
||||
interface HTMLAudioElement {
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module
|
||||
*/
|
||||
|
||||
export class ReEmitter {
|
||||
constructor(target) {
|
||||
this.target = target;
|
||||
|
||||
// We keep one bound event handler for each event name so we know
|
||||
// what event is arriving
|
||||
this.boundHandlers = {};
|
||||
}
|
||||
|
||||
_handleEvent(eventName, ...args) {
|
||||
this.target.emit(eventName, ...args);
|
||||
}
|
||||
|
||||
reEmit(source, eventNames) {
|
||||
// We include the source as the last argument for event handlers which may need it,
|
||||
// such as read receipt listeners on the client class which won't have the context
|
||||
// of the room.
|
||||
const forSource = (handler, ...args) => {
|
||||
handler(...args, source);
|
||||
};
|
||||
for (const eventName of eventNames) {
|
||||
if (this.boundHandlers[eventName] === undefined) {
|
||||
this.boundHandlers[eventName] = this._handleEvent.bind(this, eventName);
|
||||
}
|
||||
|
||||
const boundHandler = forSource.bind(this, this.boundHandlers[eventName]);
|
||||
source.on(eventName, boundHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations 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.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
export class ReEmitter {
|
||||
private target: EventEmitter;
|
||||
|
||||
constructor(target: EventEmitter) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
reEmit(source: EventEmitter, eventNames: string[]) {
|
||||
for (const eventName of eventNames) {
|
||||
// We include the source as the last argument for event handlers which may need it,
|
||||
// such as read receipt listeners on the client class which won't have the context
|
||||
// of the room.
|
||||
const forSource = (...args) => {
|
||||
// EventEmitter special cases 'error' to make the emit function throw if no
|
||||
// handler is attached, which sort of makes sense for making sure that something
|
||||
// handles an error, but for re-emitting, there could be a listener on the original
|
||||
// source object so the test doesn't really work. We *could* try to replicate the
|
||||
// same logic and throw if there is no listener on either the source or the target,
|
||||
// but this behaviour is fairly undesireable for us anyway: the main place we throw
|
||||
// 'error' events is for calls, where error events are usually emitted some time
|
||||
// later by a different part of the code where 'emit' throwing because the app hasn't
|
||||
// added an error handler isn't terribly helpful. (A better fix in retrospect may
|
||||
// have been to just avoid using the event name 'error', but backwards compat...)
|
||||
if (eventName === 'error' && this.target.listenerCount('error') === 0) return;
|
||||
this.target.emit(eventName, ...args, source);
|
||||
};
|
||||
source.on(eventName, forSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-6
@@ -57,6 +57,7 @@ import {IllegalMethod} from "./verification/IllegalMethod";
|
||||
import {KeySignatureUploadError} from "../errors";
|
||||
import {decryptAES, encryptAES} from './aes';
|
||||
import {DehydrationManager} from './dehydration';
|
||||
import { MatrixEvent } from "../models/event";
|
||||
|
||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||
|
||||
@@ -3028,19 +3029,26 @@ Crypto.prototype.encryptEvent = async function(event, room) {
|
||||
* finished decrypting. Rejects with an `algorithms.DecryptionError` if there
|
||||
* is a problem decrypting the event.
|
||||
*/
|
||||
Crypto.prototype.decryptEvent = function(event) {
|
||||
Crypto.prototype.decryptEvent = async function(event) {
|
||||
if (event.isRedacted()) {
|
||||
return Promise.resolve({
|
||||
const redactionEvent = new MatrixEvent(event.getUnsigned().redacted_because);
|
||||
const decryptedEvent = await this.decryptEvent(redactionEvent);
|
||||
|
||||
return {
|
||||
clearEvent: {
|
||||
room_id: event.getRoomId(),
|
||||
type: "m.room.message",
|
||||
content: {},
|
||||
unsigned: {
|
||||
redacted_because: decryptedEvent.clearEvent,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
} else {
|
||||
const content = event.getWireContent();
|
||||
const alg = this._getRoomDecryptor(event.getRoomId(), content.algorithm);
|
||||
return await alg.decryptEvent(event);
|
||||
}
|
||||
const content = event.getWireContent();
|
||||
const alg = this._getRoomDecryptor(event.getRoomId(), content.algorithm);
|
||||
return alg.decryptEvent(event);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -811,6 +811,24 @@ utils.extend(MatrixEvent.prototype, {
|
||||
return this.getType() === "m.room.redaction";
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the (decrypted, if necessary) redaction event JSON
|
||||
* if event was redacted
|
||||
*
|
||||
* @returns {object} The redaction event JSON, or an empty object
|
||||
*/
|
||||
getRedactionEvent: function() {
|
||||
if (!this.isRedacted()) return null;
|
||||
|
||||
if (this._clearEvent.unsigned) {
|
||||
return this._clearEvent.unsigned.redacted_because;
|
||||
} else if (this.event.unsigned.redacted_because) {
|
||||
return this.event.unsigned.redacted_because;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the push actions, if known, for this event
|
||||
*
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ MatrixScheduler.RETRY_BACKOFF_RATELIMIT = function(event, attempts, err) {
|
||||
|
||||
if (err.name === "M_LIMIT_EXCEEDED") {
|
||||
const waitTime = err.data.retry_after_ms;
|
||||
if (waitTime) {
|
||||
if (waitTime > 0) {
|
||||
return waitTime;
|
||||
}
|
||||
}
|
||||
|
||||
+72
-16
@@ -185,6 +185,21 @@ const FALLBACK_ICE_SERVER = 'stun:turn.matrix.org';
|
||||
/** The length of time a call can be ringing for. */
|
||||
const CALL_TIMEOUT_MS = 60000;
|
||||
|
||||
/** Retrieves sources from desktopCapturer */
|
||||
export function getDesktopCapturerSources(): Promise<Array<DesktopCapturerSource>> {
|
||||
const options: GetSourcesOptions = {
|
||||
thumbnailSize: {
|
||||
height: 176,
|
||||
width: 312,
|
||||
},
|
||||
types: [
|
||||
"screen",
|
||||
"window",
|
||||
],
|
||||
};
|
||||
return window.electron.getDesktopCapturerSources(options);
|
||||
}
|
||||
|
||||
export class CallError extends Error {
|
||||
code : string;
|
||||
|
||||
@@ -196,8 +211,8 @@ export class CallError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function genCallID() {
|
||||
return Date.now() + randomString(16);
|
||||
function genCallID(): string {
|
||||
return Date.now().toString() + randomString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,23 +362,60 @@ export class MatrixCall extends EventEmitter {
|
||||
* to render the local camera preview.
|
||||
* @throws If you have not specified a listener for 'error' events.
|
||||
*/
|
||||
async placeScreenSharingCall(remoteVideoElement: HTMLVideoElement, localVideoElement: HTMLVideoElement) {
|
||||
async placeScreenSharingCall(
|
||||
remoteVideoElement: HTMLVideoElement,
|
||||
localVideoElement: HTMLVideoElement,
|
||||
selectDesktopCapturerSource: () => Promise<DesktopCapturerSource>,
|
||||
) {
|
||||
logger.debug("placeScreenSharingCall");
|
||||
this.checkForErrorListener();
|
||||
this.localVideoElement = localVideoElement;
|
||||
this.remoteVideoElement = remoteVideoElement;
|
||||
try {
|
||||
this.screenSharingStream = await navigator.mediaDevices.getDisplayMedia({'audio': false});
|
||||
logger.debug("Got screen stream, requesting audio stream...");
|
||||
const audioConstraints = getUserMediaVideoContraints(CallType.Voice);
|
||||
this.placeCallWithConstraints(audioConstraints);
|
||||
} catch (err) {
|
||||
this.emit(CallEvent.Error,
|
||||
new CallError(
|
||||
CallErrorCode.NoUserMedia,
|
||||
"Failed to get screen-sharing stream: ", err,
|
||||
),
|
||||
);
|
||||
|
||||
if (window.electron?.getDesktopCapturerSources) {
|
||||
// We have access to getDesktopCapturerSources()
|
||||
logger.debug("Electron getDesktopCapturerSources() is available...");
|
||||
try {
|
||||
const selectedSource = await selectDesktopCapturerSource();
|
||||
const getUserMediaOptions: MediaStreamConstraints | DesktopCapturerConstraints = {
|
||||
audio: false,
|
||||
video: {
|
||||
mandatory: {
|
||||
chromeMediaSource: "desktop",
|
||||
chromeMediaSourceId: selectedSource.id,
|
||||
},
|
||||
},
|
||||
}
|
||||
this.screenSharingStream = await window.navigator.mediaDevices.getUserMedia(getUserMediaOptions);
|
||||
|
||||
logger.debug("Got screen stream, requesting audio stream...");
|
||||
const audioConstraints = getUserMediaVideoContraints(CallType.Voice);
|
||||
this.placeCallWithConstraints(audioConstraints);
|
||||
} catch (err) {
|
||||
this.emit(CallEvent.Error,
|
||||
new CallError(
|
||||
CallErrorCode.NoUserMedia,
|
||||
"Failed to get screen-sharing stream: ", err,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
/* We do not have access to the Electron desktop capturer,
|
||||
* therefore we can assume we are on the web */
|
||||
logger.debug("Electron desktopCapturer is not available...");
|
||||
try {
|
||||
this.screenSharingStream = await navigator.mediaDevices.getDisplayMedia({'audio': false});
|
||||
logger.debug("Got screen stream, requesting audio stream...");
|
||||
const audioConstraints = getUserMediaVideoContraints(CallType.Voice);
|
||||
this.placeCallWithConstraints(audioConstraints);
|
||||
} catch (err) {
|
||||
this.emit(CallEvent.Error,
|
||||
new CallError(
|
||||
CallErrorCode.NoUserMedia,
|
||||
"Failed to get screen-sharing stream: ", err,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.type = CallType.Video;
|
||||
@@ -469,6 +521,10 @@ export class MatrixCall extends EventEmitter {
|
||||
}
|
||||
|
||||
private async collectCallStats(): Promise<any[]> {
|
||||
// This happens when the call fails before it starts.
|
||||
// For example when we fail to get capture sources
|
||||
if (!this.peerConn) return;
|
||||
|
||||
const statsReport = await this.peerConn.getStats();
|
||||
const stats = [];
|
||||
for (const item of statsReport) {
|
||||
@@ -1387,7 +1443,7 @@ export class MatrixCall extends EventEmitter {
|
||||
}
|
||||
|
||||
onHangupReceived = (msg) => {
|
||||
logger.debug("Hangup received for call ID " + + this.callId);
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user