Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 662fcb426b | |||
| fd0fe9e225 | |||
| 295c591e95 | |||
| 0cbe35e41f | |||
| d4e107b3cd | |||
| 881b60c85f | |||
| e3a8631faa | |||
| 3eab51ce79 | |||
| 9db0fe0795 | |||
| 228af037e3 | |||
| 654f250fd8 | |||
| a8693d9d68 | |||
| f9f345e428 | |||
| e678706414 | |||
| 8018259480 | |||
| 9f713781cd | |||
| 48d56dc5c0 | |||
| 701dfa09b4 | |||
| d965648fd7 | |||
| 08c15e7203 | |||
| 9b9e52d0a2 | |||
| 38cc8fe7dc | |||
| 590fac0fa9 | |||
| 9590c8aaf0 | |||
| e2b79e4e7e | |||
| 2df588f95a | |||
| 7c3af91b42 | |||
| 4cc4b28c47 | |||
| fad9b4c67f | |||
| ade3b3a021 | |||
| d8c4101fdd | |||
| a12c250f2b | |||
| 57eef2d832 | |||
| b67a179a54 | |||
| 06044b39c3 | |||
| d16cf26c5f | |||
| b060c5af38 | |||
| b28bad651e | |||
| 5c4b7a3213 | |||
| 7f21c591ae | |||
| 65b24f595c | |||
| 9d9c2720c2 | |||
| f845100062 | |||
| e6155f9e37 | |||
| e9590e9093 | |||
| 49f2d1501c | |||
| c6819e0450 | |||
| f518ea95f4 | |||
| 92c6332143 | |||
| 0df1a7da21 | |||
| 487a9c0967 | |||
| fb89761671 | |||
| d1d3ae074d | |||
| 7dedaf90c3 | |||
| 687b98a09d | |||
| c6b2e9873c | |||
| 1e80491675 | |||
| a727da9193 | |||
| 0b7754581a | |||
| 452e8ea385 | |||
| a189de9a2e | |||
| 8632ca6e37 | |||
| 7529d2b638 | |||
| c8a18d51e6 | |||
| d77af1e67a | |||
| 81c95224d1 |
@@ -12,7 +12,6 @@ lib-cov
|
||||
out
|
||||
/dist
|
||||
/lib
|
||||
/specbuild
|
||||
|
||||
# version file and tarball created by `npm pack` / `yarn pack`
|
||||
/git-revision.txt
|
||||
|
||||
@@ -1,3 +1,66 @@
|
||||
Changes in [9.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.0) (2020-10-26)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v9.0.0-rc.1...v9.0.0)
|
||||
|
||||
* Fix logger typo
|
||||
[\#1528](https://github.com/matrix-org/matrix-js-sdk/pull/1528)
|
||||
|
||||
Changes in [9.0.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v9.0.0-rc.1) (2020-10-21)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.5.0...v9.0.0-rc.1)
|
||||
|
||||
BREAKING CHANGES
|
||||
---
|
||||
|
||||
* `hasPendingEvent` now returns false instead of throwing when pending ordering mode is not `detached`
|
||||
|
||||
All changes
|
||||
---
|
||||
|
||||
* Don't cache failures when fetching /versions
|
||||
[\#1521](https://github.com/matrix-org/matrix-js-sdk/pull/1521)
|
||||
* Install deps first as part of release
|
||||
[\#1518](https://github.com/matrix-org/matrix-js-sdk/pull/1518)
|
||||
* [Breaking] Change hasPendingEvent to return false if pending ordering
|
||||
!detached
|
||||
[\#1517](https://github.com/matrix-org/matrix-js-sdk/pull/1517)
|
||||
* Skip editor prompts for merges
|
||||
[\#1519](https://github.com/matrix-org/matrix-js-sdk/pull/1519)
|
||||
* Convert call test to TypeScript
|
||||
[\#1516](https://github.com/matrix-org/matrix-js-sdk/pull/1516)
|
||||
* Support party_id
|
||||
[\#1512](https://github.com/matrix-org/matrix-js-sdk/pull/1512)
|
||||
* Support m.call.reject
|
||||
[\#1510](https://github.com/matrix-org/matrix-js-sdk/pull/1510)
|
||||
* Remove specbuild from .gitignore
|
||||
[\#1515](https://github.com/matrix-org/matrix-js-sdk/pull/1515)
|
||||
* Log the error when we failed to send candidates
|
||||
[\#1514](https://github.com/matrix-org/matrix-js-sdk/pull/1514)
|
||||
* Fixes for call state machine
|
||||
[\#1503](https://github.com/matrix-org/matrix-js-sdk/pull/1503)
|
||||
* Fix call event handler listener removing
|
||||
[\#1506](https://github.com/matrix-org/matrix-js-sdk/pull/1506)
|
||||
* Set the type of the call based on the tracks
|
||||
[\#1501](https://github.com/matrix-org/matrix-js-sdk/pull/1501)
|
||||
* Use new local timestamp for calls
|
||||
[\#1499](https://github.com/matrix-org/matrix-js-sdk/pull/1499)
|
||||
* Adjust types and APIs to match React SDK
|
||||
[\#1502](https://github.com/matrix-org/matrix-js-sdk/pull/1502)
|
||||
* Make an accurate version of 'age' for events
|
||||
[\#1495](https://github.com/matrix-org/matrix-js-sdk/pull/1495)
|
||||
* Make 'options' parameter optional
|
||||
[\#1498](https://github.com/matrix-org/matrix-js-sdk/pull/1498)
|
||||
* Create a giant event type enum
|
||||
[\#1497](https://github.com/matrix-org/matrix-js-sdk/pull/1497)
|
||||
* Convert call.js to Typescript & update WebRTC APIs (re-apply)
|
||||
[\#1494](https://github.com/matrix-org/matrix-js-sdk/pull/1494)
|
||||
|
||||
Changes in [8.5.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v8.5.0) (2020-10-12)
|
||||
================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.5.0-rc.1...v8.5.0)
|
||||
|
||||
* No changes since rc.1
|
||||
|
||||
Changes in [8.5.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v8.5.0-rc.1) (2020-10-07)
|
||||
==========================================================================================================
|
||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v8.4.1...v8.5.0-rc.1)
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-js-sdk",
|
||||
"version": "8.5.0-rc.1",
|
||||
"version": "9.0.0",
|
||||
"description": "Matrix Client-Server SDK for Javascript",
|
||||
"scripts": {
|
||||
"prepare": "yarn build",
|
||||
@@ -14,7 +14,7 @@
|
||||
"build:minify-browser": "terser dist/browser-matrix.js --compress --mangle --source-map --output dist/browser-matrix.min.js",
|
||||
"gendoc": "jsdoc -c jsdoc.json -P package.json",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
"lint:js": "eslint --max-warnings 76 src spec",
|
||||
"lint:js": "eslint --max-warnings 73 src spec",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"test": "jest spec/ --coverage --testEnvironment node",
|
||||
"test:watch": "jest spec/ --coverage --testEnvironment node --watch"
|
||||
@@ -67,6 +67,7 @@
|
||||
"@babel/preset-env": "^7.11.5",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@babel/register": "^7.11.5",
|
||||
"@types/jest": "^26.0.14",
|
||||
"@types/node": "12",
|
||||
"@types/request": "^2.48.5",
|
||||
"babel-eslint": "^10.1.0",
|
||||
|
||||
+10
-7
@@ -94,6 +94,14 @@ if [ $# -ne 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We use Git branch / commit dependencies for some packages, and Yarn seems
|
||||
# to have a hard time getting that right. See also
|
||||
# https://github.com/yarnpkg/yarn/issues/4734. As a workaround, we clean the
|
||||
# global cache here to ensure we get the right thing.
|
||||
yarn cache clean
|
||||
# Ensure all dependencies are updated
|
||||
yarn install --ignore-scripts
|
||||
|
||||
if [ -z "$skip_changelog" ]; then
|
||||
# update_changelog doesn't have a --version flag
|
||||
update_changelog -h > /dev/null || (echo "github-changelog-generator is required: please install it"; exit)
|
||||
@@ -204,11 +212,6 @@ if [ $dodist -eq 0 ]; then
|
||||
pushd "$builddir"
|
||||
git clone "$projdir" .
|
||||
git checkout "$rel_branch"
|
||||
# We use Git branch / commit dependencies for some packages, and Yarn seems
|
||||
# to have a hard time getting that right. See also
|
||||
# https://github.com/yarnpkg/yarn/issues/4734. As a workaround, we clean the
|
||||
# global cache here to ensure we get the right thing.
|
||||
yarn cache clean
|
||||
yarn install
|
||||
# We haven't tagged yet, so tell the dist script what version
|
||||
# it's building
|
||||
@@ -340,7 +343,7 @@ fi
|
||||
echo "updating master branch"
|
||||
git checkout master
|
||||
git pull
|
||||
git merge "$rel_branch"
|
||||
git merge "$rel_branch" --no-edit
|
||||
|
||||
# push master to github
|
||||
git push origin master
|
||||
@@ -349,6 +352,6 @@ git push origin master
|
||||
if [ $(git branch -lr | grep origin/develop -c) -ge 1 ]; then
|
||||
git checkout develop
|
||||
git pull
|
||||
git merge master
|
||||
git merge master --no-edit
|
||||
git push origin develop
|
||||
fi
|
||||
|
||||
@@ -69,6 +69,9 @@ export function TestClient(
|
||||
|
||||
this.deviceKeys = null;
|
||||
this.oneTimeKeys = {};
|
||||
this._callEventHandler = {
|
||||
calls: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
TestClient.prototype.toString = function() {
|
||||
@@ -230,3 +233,7 @@ TestClient.prototype.flushSync = function() {
|
||||
logger.log(`${this}: flushSync completed`);
|
||||
});
|
||||
};
|
||||
|
||||
TestClient.prototype.isFallbackICEServerAllowed = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -194,7 +194,7 @@ describe("Secrets", function() {
|
||||
};
|
||||
resetCrossSigningKeys(alice);
|
||||
|
||||
const newKeyId = await alice.addSecretStorageKey(
|
||||
const { keyId: newKeyId } = await alice.addSecretStorageKey(
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||
);
|
||||
// we don't await on this because it waits for the event to come down the sync
|
||||
@@ -226,8 +226,8 @@ describe("Secrets", function() {
|
||||
],
|
||||
{
|
||||
cryptoCallbacks: {
|
||||
onSecretRequested: e => {
|
||||
expect(e.name).toBe("foo");
|
||||
onSecretRequested: (userId, deviceId, requestId, secretName, deviceTrust) => {
|
||||
expect(secretName).toBe("foo");
|
||||
return "bar";
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,6 +17,39 @@ limitations under the License.
|
||||
|
||||
import {SyncAccumulator} from "../../src/sync-accumulator";
|
||||
|
||||
// The event body & unsigned object get frozen to assert that they don't get altered
|
||||
// by the impl
|
||||
const RES_WITH_AGE = {
|
||||
next_batch: "abc",
|
||||
rooms: {
|
||||
invite: {},
|
||||
leave: {},
|
||||
join: {
|
||||
"!foo:bar": {
|
||||
account_data: { events: [] },
|
||||
ephemeral: { events: [] },
|
||||
unread_notifications: {},
|
||||
timeline: {
|
||||
events: [
|
||||
Object.freeze({
|
||||
content: {
|
||||
body: "This thing is happening right now!",
|
||||
},
|
||||
origin_server_ts: 123456789,
|
||||
sender: "@alice:localhost",
|
||||
type: "m.room.message",
|
||||
unsigned: Object.freeze({
|
||||
age: 50,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
prev_batch: "something",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("SyncAccumulator", function() {
|
||||
let sa;
|
||||
|
||||
@@ -368,6 +401,39 @@ describe("SyncAccumulator", function() {
|
||||
expect(summary["m.joined_member_count"]).toEqual(5);
|
||||
expect(summary["m.heroes"]).toEqual(["@bob:bar"]);
|
||||
});
|
||||
|
||||
it("should return correctly adjusted age attributes", () => {
|
||||
const delta = 1000;
|
||||
const startingTs = 1000;
|
||||
|
||||
const oldDateNow = Date.now;
|
||||
try {
|
||||
Date.now = jest.fn();
|
||||
Date.now.mockReturnValue(startingTs);
|
||||
|
||||
sa.accumulate(RES_WITH_AGE);
|
||||
|
||||
Date.now.mockReturnValue(startingTs + delta);
|
||||
|
||||
const output = sa.getJSON();
|
||||
expect(output.roomsData.join["!foo:bar"].timeline.events[0].unsigned.age).toEqual(
|
||||
RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0].unsigned.age + delta,
|
||||
);
|
||||
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
|
||||
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
|
||||
);
|
||||
} finally {
|
||||
Date.now = oldDateNow;
|
||||
}
|
||||
});
|
||||
|
||||
it("should mangle age without adding extra keys", () => {
|
||||
sa.accumulate(RES_WITH_AGE);
|
||||
const output = sa.getJSON();
|
||||
expect(Object.keys(output.roomsData.join["!foo:bar"].timeline.events[0])).toEqual(
|
||||
Object.keys(RES_WITH_AGE.rooms.join["!foo:bar"].timeline.events[0]),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
Copyright 2020 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 {TestClient} from '../../TestClient';
|
||||
import {MatrixCall} from '../../../src/webrtc/call';
|
||||
|
||||
const DUMMY_SDP = (
|
||||
"v=0\r\n" +
|
||||
"o=- 5022425983810148698 2 IN IP4 127.0.0.1\r\n" +
|
||||
"s=-\r\nt=0 0\r\na=group:BUNDLE 0\r\n" +
|
||||
"a=msid-semantic: WMS h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA\r\n" +
|
||||
"m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 110 112 113 126\r\n" +
|
||||
"c=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=ice-ufrag:hLDR\r\n" +
|
||||
"a=ice-pwd:bMGD9aOldHWiI+6nAq/IIlRw\r\n" +
|
||||
"a=ice-options:trickle\r\n" +
|
||||
"a=fingerprint:sha-256 E4:94:84:F9:4A:98:8A:56:F5:5F:FD:AF:72:B9:32:89:49:5C:4B:9A:" +
|
||||
"4A:15:8E:41:8A:F3:69:E4:39:52:DC:D6\r\n" +
|
||||
"a=setup:active\r\n" +
|
||||
"a=mid:0\r\n" +
|
||||
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\n" +
|
||||
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\n" +
|
||||
"a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\n" +
|
||||
"a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\n" +
|
||||
"a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n" +
|
||||
"a=extmap:6 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n" +
|
||||
"a=sendrecv\r\n" +
|
||||
"a=msid:h3wAi7s8QpiQMH14WG3BnDbmlOqo9I5ezGZA 4357098f-3795-4131-bff4-9ba9c0348c49\r\n" +
|
||||
"a=rtcp-mux\r\n" +
|
||||
"a=rtpmap:111 opus/48000/2\r\n" +
|
||||
"a=rtcp-fb:111 transport-cc\r\n" +
|
||||
"a=fmtp:111 minptime=10;useinbandfec=1\r\n" +
|
||||
"a=rtpmap:103 ISAC/16000\r\n" +
|
||||
"a=rtpmap:104 ISAC/32000\r\n" +
|
||||
"a=rtpmap:9 G722/8000\r\n" +
|
||||
"a=rtpmap:0 PCMU/8000\r\n" +
|
||||
"a=rtpmap:8 PCMA/8000\r\n" +
|
||||
"a=rtpmap:106 CN/32000\r\n" +
|
||||
"a=rtpmap:105 CN/16000\r\n" +
|
||||
"a=rtpmap:13 CN/8000\r\n" +
|
||||
"a=rtpmap:110 telephone-event/48000\r\n" +
|
||||
"a=rtpmap:112 telephone-event/32000\r\n" +
|
||||
"a=rtpmap:113 telephone-event/16000\r\n" +
|
||||
"a=rtpmap:126 telephone-event/8000\r\n" +
|
||||
"a=ssrc:3619738545 cname:2RWtmqhXLdoF4sOi\r\n"
|
||||
);
|
||||
|
||||
class MockRTCPeerConnection {
|
||||
localDescription: RTCSessionDescription;
|
||||
|
||||
constructor() {
|
||||
this.localDescription = {
|
||||
sdp: DUMMY_SDP,
|
||||
type: 'offer',
|
||||
toJSON: function() {},
|
||||
};
|
||||
}
|
||||
|
||||
addEventListener() {}
|
||||
createOffer() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
setRemoteDescription() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
setLocalDescription() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
describe('Call', function() {
|
||||
let client;
|
||||
let call;
|
||||
let prevNavigator;
|
||||
let prevDocument;
|
||||
let prevWindow;
|
||||
|
||||
beforeEach(function() {
|
||||
prevNavigator = global.navigator;
|
||||
prevDocument = global.document;
|
||||
prevWindow = global.window;
|
||||
|
||||
global.navigator = {
|
||||
mediaDevices: {
|
||||
// @ts-ignore Mock
|
||||
getUserMedia: () => {
|
||||
return {
|
||||
getTracks: () => [],
|
||||
getAudioTracks: () => [],
|
||||
getVideoTracks: () => [],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
global.window = {
|
||||
// @ts-ignore Mock
|
||||
RTCPeerConnection: MockRTCPeerConnection,
|
||||
// @ts-ignore Mock
|
||||
RTCSessionDescription: {},
|
||||
// @ts-ignore Mock
|
||||
RTCIceCandidate: {},
|
||||
getUserMedia: {},
|
||||
};
|
||||
// @ts-ignore Mock
|
||||
global.document = {};
|
||||
|
||||
client = new TestClient("@alice:foo", "somedevice", "token", undefined, {});
|
||||
call = new MatrixCall({
|
||||
client: client.client,
|
||||
roomId: '!foo:bar',
|
||||
});
|
||||
// call checks one of these is wired up
|
||||
call.on('error', () => {});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
client.stop();
|
||||
global.navigator = prevNavigator;
|
||||
global.window = prevWindow;
|
||||
global.document = prevDocument;
|
||||
});
|
||||
|
||||
it('should ignore candidate events from non-matching party ID', async function() {
|
||||
await call.placeVoiceCall();
|
||||
await call.receivedAnswer({
|
||||
version: 0,
|
||||
call_id: call.callId,
|
||||
party_id: 'the_correct_party_id',
|
||||
answer: {
|
||||
sdp: DUMMY_SDP,
|
||||
},
|
||||
});
|
||||
|
||||
call.peerConn.addIceCandidate = jest.fn();
|
||||
call.onRemoteIceCandidatesReceived({
|
||||
getContent: () => {
|
||||
return {
|
||||
version: 0,
|
||||
call_id: call.callId,
|
||||
party_id: 'the_correct_party_id',
|
||||
candidates: [
|
||||
{
|
||||
candidate: '',
|
||||
sdpMid: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
|
||||
|
||||
call.onRemoteIceCandidatesReceived({
|
||||
getContent: () => {
|
||||
return {
|
||||
version: 0,
|
||||
call_id: call.callId,
|
||||
party_id: 'some_other_party_id',
|
||||
candidates: [
|
||||
{
|
||||
candidate: '',
|
||||
sdpMid: '',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
export enum EventType {
|
||||
// Room state events
|
||||
RoomCanonicalAlias = "m.room.canonical_alias",
|
||||
RoomCreate = "m.room.create",
|
||||
RoomJoinRules = "m.room.join_rules",
|
||||
RoomMember = "m.room.member",
|
||||
RoomThirdPartyInvite = "m.room.third_party_invite",
|
||||
RoomPowerLevels = "m.room.power_levels",
|
||||
RoomName = "m.room.name",
|
||||
RoomTopic = "m.room.topic",
|
||||
RoomAvatar = "m.room.avatar",
|
||||
RoomPinnedEvents = "m.room.pinned_events",
|
||||
RoomEncryption = "m.room.encryption",
|
||||
RoomHistoryVisibility = "m.room.history_visibility",
|
||||
RoomGuestAccess = "m.room.guest_access",
|
||||
RoomServerAcl = "m.room_server_acl",
|
||||
RoomTombstone = "m.room.tombstone",
|
||||
/**
|
||||
* @deprecated Should not be used.
|
||||
*/
|
||||
RoomAliases = "m.room.aliases", // deprecated https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
|
||||
|
||||
// Room timeline events
|
||||
RoomRedaction = "m.room.redaction",
|
||||
RoomMessage = "m.room.message",
|
||||
RoomMessageEncrypted = "m.room.encrypted",
|
||||
Sticker = "m.sticker",
|
||||
CallInvite = "m.call.invite",
|
||||
CallCandidates = "m.call.candidates",
|
||||
CallAnswer = "m.call.answer",
|
||||
CallHangup = "m.call.hangup",
|
||||
CallReject = "m.call.reject",
|
||||
KeyVerificationRequest = "m.key.verification.request",
|
||||
KeyVerificationStart = "m.key.verification.start",
|
||||
KeyVerificationCancel = "m.key.verification.cancel",
|
||||
KeyVerificationMac = "m.key.verification.mac",
|
||||
// use of this is discouraged https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-feedback
|
||||
RoomMessageFeedback = "m.room.message.feedback",
|
||||
|
||||
// Room ephemeral events
|
||||
Typing = "m.typing",
|
||||
Receipt = "m.receipt",
|
||||
Presence = "m.presence",
|
||||
|
||||
// Room account_data events
|
||||
FullyRead = "m.fully_read",
|
||||
Tag = "m.tag",
|
||||
|
||||
// User account_data events
|
||||
PushRules = "m.push_rules",
|
||||
Direct = "m.direct",
|
||||
IgnoredUserList = "m.ignored_user_list",
|
||||
|
||||
// to_device events
|
||||
RoomKey = "m.room_key",
|
||||
RoomKeyRequest = "m.room_key_request",
|
||||
ForwardedRoomKey = "m.forwarded_room_key",
|
||||
Dummy = "m.dummy",
|
||||
}
|
||||
|
||||
export enum MsgType {
|
||||
Text = "m.text",
|
||||
Emote = "m.emote",
|
||||
Notice = "m.notice",
|
||||
Image = "m.image",
|
||||
File = "m.file",
|
||||
Audio = "m.audio",
|
||||
Location = "m.location",
|
||||
Video = "m.video",
|
||||
}
|
||||
Vendored
+20
@@ -25,4 +25,24 @@ declare global {
|
||||
localStorage: Storage;
|
||||
}
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
interface HTMLAudioElement {
|
||||
// sinkId & setSinkId are experimental and typescript doesn't know about them
|
||||
sinkId: string;
|
||||
setSinkId(outputId: string);
|
||||
}
|
||||
|
||||
interface DummyInterfaceWeShouldntBeUsingThis {}
|
||||
|
||||
interface Navigator {
|
||||
// We check for the webkit-prefixed getUserMedia to detect if we're
|
||||
// on webkit: we should check if we still need to do this
|
||||
webkitGetUserMedia: DummyInterfaceWeShouldntBeUsingThis;
|
||||
}
|
||||
}
|
||||
|
||||
+31
-233
@@ -33,6 +33,7 @@ import {EventTimeline} from "./models/event-timeline";
|
||||
import {SearchResult} from "./models/search-result";
|
||||
import {StubStore} from "./store/stub";
|
||||
import {createNewMatrixCall} from "./webrtc/call";
|
||||
import {CallEventHandler} from './webrtc/callEventHandler';
|
||||
import * as utils from './utils';
|
||||
import {sleep} from './utils';
|
||||
import {
|
||||
@@ -235,7 +236,14 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* {
|
||||
* keys: {
|
||||
* <key name>: {
|
||||
* pubkey: {UInt8Array}
|
||||
* "algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||
* "passphrase": {
|
||||
* "algorithm": "m.pbkdf2",
|
||||
* "iterations": 500000,
|
||||
* "salt": "..."
|
||||
* },
|
||||
* "iv": "...",
|
||||
* "mac": "..."
|
||||
* }, ...
|
||||
* }
|
||||
* }
|
||||
@@ -247,6 +255,7 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* desired to avoid user prompts.
|
||||
* Args:
|
||||
* {string} keyId the ID of the new key
|
||||
* {object} keyInfo Infomation about the key as above for `getSecretStorageKey`
|
||||
* {Uint8Array} key the new private key
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.onSecretRequested]
|
||||
@@ -328,17 +337,15 @@ export function MatrixClient(opts) {
|
||||
}
|
||||
this.clientRunning = false;
|
||||
|
||||
this.callList = {
|
||||
// callId: MatrixCall
|
||||
};
|
||||
|
||||
// try constructing a MatrixCall to see if we are running in an environment
|
||||
// which has WebRTC. If we are, listen for and handle m.call.* events.
|
||||
const call = createNewMatrixCall(this);
|
||||
this._supportsVoip = false;
|
||||
if (call) {
|
||||
setupCallEventHandler(this);
|
||||
this._callEventHandler = new CallEventHandler(this);
|
||||
this._supportsVoip = true;
|
||||
} else {
|
||||
this._callEventHandler = null;
|
||||
}
|
||||
this._syncingRetry = null;
|
||||
this._syncApi = null;
|
||||
@@ -490,7 +497,7 @@ MatrixClient.prototype.rehydrateDevice = async function() {
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
logger.info("could not get dehydrated device", e);
|
||||
logger.info("could not get dehydrated device", e.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1434,7 +1441,7 @@ wrapCryptoFuncs(MatrixClient, [
|
||||
* containing the key, or rejects if the key cannot be obtained.
|
||||
* Returns:
|
||||
* {Promise} A promise which resolves to key creation data for
|
||||
* SecretStorage#addKey: an object with `passphrase` and/or `pubkey` fields.
|
||||
* SecretStorage#addKey: an object with `passphrase` etc fields.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -1449,7 +1456,9 @@ wrapCryptoFuncs(MatrixClient, [
|
||||
* @param {string} [keyName] the name of the key. If not given, a random
|
||||
* name will be generated.
|
||||
*
|
||||
* @return {string} the name of the key
|
||||
* @return {object} An object with:
|
||||
* keyId: {string} the ID of the key
|
||||
* keyInfo: {object} details about the key (iv, mac, passphrase)
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -2009,7 +2018,7 @@ MatrixClient.prototype.isValidRecoveryKey = function(recoveryKey) {
|
||||
*
|
||||
* @param {string} password Passphrase
|
||||
* @param {object} backupInfo Backup metadata from `checkKeyBackup`
|
||||
* @return {Promise<Buffer>} key backup key
|
||||
* @return {Promise<Uint8Array>} key backup key
|
||||
*/
|
||||
MatrixClient.prototype.keyBackupKeyFromPassword = function(
|
||||
password, backupInfo,
|
||||
@@ -2024,7 +2033,7 @@ MatrixClient.prototype.keyBackupKeyFromPassword = function(
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @param {string} recoveryKey The recovery key
|
||||
* @return {Buffer} key backup key
|
||||
* @return {Uint8Array} key backup key
|
||||
*/
|
||||
MatrixClient.prototype.keyBackupKeyFromRecoveryKey = function(recoveryKey) {
|
||||
return decodeRecoveryKey(recoveryKey);
|
||||
@@ -5105,6 +5114,11 @@ MatrixClient.prototype.stopClient = function() {
|
||||
if (this._peekSync) {
|
||||
this._peekSync.stopPeeking();
|
||||
}
|
||||
if (this._callEventHandler) {
|
||||
this._callEventHandler.stop();
|
||||
this._callEventHandler = null;
|
||||
}
|
||||
|
||||
global.clearTimeout(this._checkTurnServersTimeoutID);
|
||||
if (this._clientWellKnownIntervalID !== undefined) {
|
||||
global.clearInterval(this._clientWellKnownIntervalID);
|
||||
@@ -5129,7 +5143,12 @@ MatrixClient.prototype.getVersions = function() {
|
||||
{
|
||||
prefix: '',
|
||||
},
|
||||
);
|
||||
).catch((e) => {
|
||||
// Need to unset this if it fails, otherwise we'll never retry
|
||||
this._serverVersionsPromise = null;
|
||||
// but rethrow the exception to anything that was waiting
|
||||
throw e;
|
||||
});
|
||||
|
||||
return this._serverVersionsPromise;
|
||||
};
|
||||
@@ -5314,227 +5333,6 @@ async function(roomId, eventId, relationType, eventType, opts = {}) {
|
||||
};
|
||||
};
|
||||
|
||||
function setupCallEventHandler(client) {
|
||||
const candidatesByCall = {
|
||||
// callId: [Candidate]
|
||||
};
|
||||
|
||||
// The sync code always emits one event at a time, so it will patiently
|
||||
// wait for us to finish processing a call invite before delivering the
|
||||
// next event, even if that next event is a hangup. We therefore accumulate
|
||||
// all our call events and then process them on the 'sync' event, ie.
|
||||
// each time a sync has completed. This way, we can avoid emitting incoming
|
||||
// call events if we get both the invite and answer/hangup in the same sync.
|
||||
// This happens quite often, eg. replaying sync from storage, catchup sync
|
||||
// after loading and after we've been offline for a bit.
|
||||
let callEventBuffer = [];
|
||||
function evaluateEventBuffer() {
|
||||
if (client.getSyncState() === "SYNCING") {
|
||||
// don't process any events until they are all decrypted
|
||||
if (callEventBuffer.some((e) => e.isBeingDecrypted())) return;
|
||||
|
||||
const ignoreCallIds = {}; // Set<String>
|
||||
// inspect the buffer and mark all calls which have been answered
|
||||
// or hung up before passing them to the call event handler.
|
||||
for (let i = callEventBuffer.length - 1; i >= 0; i--) {
|
||||
const ev = callEventBuffer[i];
|
||||
if (ev.getType() === "m.call.answer" ||
|
||||
ev.getType() === "m.call.hangup") {
|
||||
ignoreCallIds[ev.getContent().call_id] = "yep";
|
||||
}
|
||||
}
|
||||
// now loop through the buffer chronologically and inject them
|
||||
callEventBuffer.forEach(function(e) {
|
||||
if (
|
||||
e.getType() === "m.call.invite" &&
|
||||
ignoreCallIds[e.getContent().call_id]
|
||||
) {
|
||||
// This call has previously been answered or hung up: ignore it
|
||||
return;
|
||||
}
|
||||
try {
|
||||
callEventHandler(e);
|
||||
} catch (e) {
|
||||
logger.error("Caught exception handling call event", e);
|
||||
}
|
||||
});
|
||||
callEventBuffer = [];
|
||||
}
|
||||
}
|
||||
client.on("sync", evaluateEventBuffer);
|
||||
|
||||
function onEvent(event) {
|
||||
// any call events or ones that might be once they're decrypted
|
||||
if (event.getType().indexOf("m.call.") === 0 || event.isBeingDecrypted()) {
|
||||
// queue up for processing once all events from this sync have been
|
||||
// processed (see above).
|
||||
callEventBuffer.push(event);
|
||||
}
|
||||
|
||||
if (event.isBeingDecrypted() || event.isDecryptionFailure()) {
|
||||
// add an event listener for once the event is decrypted.
|
||||
event.once("Event.decrypted", () => {
|
||||
if (event.getType().indexOf("m.call.") === -1) return;
|
||||
|
||||
if (callEventBuffer.includes(event)) {
|
||||
// we were waiting for that event to decrypt, so recheck the buffer
|
||||
evaluateEventBuffer();
|
||||
} else {
|
||||
// This one wasn't buffered so just run the event handler for it
|
||||
// straight away
|
||||
try {
|
||||
callEventHandler(event);
|
||||
} catch (e) {
|
||||
logger.error("Caught exception handling call event", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
client.on("event", onEvent);
|
||||
|
||||
function callEventHandler(event) {
|
||||
const content = event.getContent();
|
||||
let call = content.call_id ? client.callList[content.call_id] : undefined;
|
||||
let i;
|
||||
//console.info("RECV %s content=%s", event.getType(), JSON.stringify(content));
|
||||
|
||||
if (event.getType() === "m.call.invite") {
|
||||
if (event.getSender() === client.credentials.userId) {
|
||||
return; // ignore invites you send
|
||||
}
|
||||
|
||||
if (event.getAge() > content.lifetime) {
|
||||
return; // expired call
|
||||
}
|
||||
|
||||
if (call && call.state === "ended") {
|
||||
return; // stale/old invite event
|
||||
}
|
||||
if (call) {
|
||||
logger.log(
|
||||
"WARN: Already have a MatrixCall with id %s but got an " +
|
||||
"invite. Clobbering.",
|
||||
content.call_id,
|
||||
);
|
||||
}
|
||||
|
||||
call = createNewMatrixCall(client, event.getRoomId(), {
|
||||
forceTURN: client._forceTURN,
|
||||
});
|
||||
if (!call) {
|
||||
logger.log(
|
||||
"Incoming call ID " + content.call_id + " but this client " +
|
||||
"doesn't support WebRTC",
|
||||
);
|
||||
// don't hang up the call: there could be other clients
|
||||
// connected that do support WebRTC and declining the
|
||||
// the call on their behalf would be really annoying.
|
||||
return;
|
||||
}
|
||||
|
||||
call.callId = content.call_id;
|
||||
call._initWithInvite(event);
|
||||
client.callList[call.callId] = call;
|
||||
|
||||
// if we stashed candidate events for that call ID, play them back now
|
||||
if (candidatesByCall[call.callId]) {
|
||||
for (i = 0; i < candidatesByCall[call.callId].length; i++) {
|
||||
call._gotRemoteIceCandidate(
|
||||
candidatesByCall[call.callId][i],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Were we trying to call that user (room)?
|
||||
let existingCall;
|
||||
const existingCalls = utils.values(client.callList);
|
||||
for (i = 0; i < existingCalls.length; ++i) {
|
||||
const thisCall = existingCalls[i];
|
||||
if (call.roomId === thisCall.roomId &&
|
||||
thisCall.direction === 'outbound' &&
|
||||
(["wait_local_media", "create_offer", "invite_sent"].indexOf(
|
||||
thisCall.state) !== -1)) {
|
||||
existingCall = thisCall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingCall) {
|
||||
// If we've only got to wait_local_media or create_offer and
|
||||
// we've got an invite, pick the incoming call because we know
|
||||
// we haven't sent our invite yet otherwise, pick whichever
|
||||
// call has the lowest call ID (by string comparison)
|
||||
if (existingCall.state === 'wait_local_media' ||
|
||||
existingCall.state === 'create_offer' ||
|
||||
existingCall.callId > call.callId) {
|
||||
logger.log(
|
||||
"Glare detected: answering incoming call " + call.callId +
|
||||
" and canceling outgoing call " + existingCall.callId,
|
||||
);
|
||||
existingCall._replacedBy(call);
|
||||
call.answer();
|
||||
} else {
|
||||
logger.log(
|
||||
"Glare detected: rejecting incoming call " + call.callId +
|
||||
" and keeping outgoing call " + existingCall.callId,
|
||||
);
|
||||
call.hangup();
|
||||
}
|
||||
} else {
|
||||
client.emit("Call.incoming", call);
|
||||
}
|
||||
} else if (event.getType() === 'm.call.answer') {
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
if (event.getSender() === client.credentials.userId) {
|
||||
if (call.state === 'ringing') {
|
||||
call._onAnsweredElsewhere(content);
|
||||
}
|
||||
} else {
|
||||
call._receivedAnswer(content);
|
||||
}
|
||||
} else if (event.getType() === 'm.call.candidates') {
|
||||
if (event.getSender() === client.credentials.userId) {
|
||||
return;
|
||||
}
|
||||
if (!call) {
|
||||
// store the candidates; we may get a call eventually.
|
||||
if (!candidatesByCall[content.call_id]) {
|
||||
candidatesByCall[content.call_id] = [];
|
||||
}
|
||||
candidatesByCall[content.call_id] = candidatesByCall[
|
||||
content.call_id
|
||||
].concat(content.candidates);
|
||||
} else {
|
||||
for (i = 0; i < content.candidates.length; i++) {
|
||||
call._gotRemoteIceCandidate(content.candidates[i]);
|
||||
}
|
||||
}
|
||||
} else if (event.getType() === 'm.call.hangup') {
|
||||
// Note that we also observe our own hangups here so we can see
|
||||
// if we've already rejected a call that would otherwise be valid
|
||||
if (!call) {
|
||||
// if not live, store the fact that the call has ended because
|
||||
// we're probably getting events backwards so
|
||||
// the hangup will come before the invite
|
||||
call = createNewMatrixCall(client, event.getRoomId());
|
||||
if (call) {
|
||||
call.callId = content.call_id;
|
||||
call._initWithHangup(event);
|
||||
client.callList[content.call_id] = call;
|
||||
}
|
||||
} else {
|
||||
if (call.state !== 'ended') {
|
||||
call._onHangupReceived(content);
|
||||
delete client.callList[content.call_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkTurnServers(client) {
|
||||
if (!client._supportsVoip) {
|
||||
return;
|
||||
|
||||
@@ -344,14 +344,14 @@ class SSSSCryptoCallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
addPrivateKey(keyId, privKey) {
|
||||
addPrivateKey(keyId, keyInfo, privKey) {
|
||||
this._privateKeys.set(keyId, privKey);
|
||||
// Also pass along to application to cache if it wishes
|
||||
if (
|
||||
this._delegateCryptoCallbacks &&
|
||||
this._delegateCryptoCallbacks.cacheSecretStorageKey
|
||||
) {
|
||||
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, privKey);
|
||||
this._delegateCryptoCallbacks.cacheSecretStorageKey(keyId, keyInfo, privKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-15
@@ -81,25 +81,27 @@ export class SecretStorage extends EventEmitter {
|
||||
* @param {string} [keyId] the ID of the key. If not given, a random
|
||||
* ID will be generated.
|
||||
*
|
||||
* @return {string} the ID of the key
|
||||
* @return {object} An object with:
|
||||
* keyId: {string} the ID of the key
|
||||
* keyInfo: {object} details about the key (iv, mac, passphrase)
|
||||
*/
|
||||
async addKey(algorithm, opts, keyId) {
|
||||
const keyData = {algorithm};
|
||||
const keyInfo = {algorithm};
|
||||
|
||||
if (!opts) opts = {};
|
||||
|
||||
if (opts.name) {
|
||||
keyData.name = opts.name;
|
||||
keyInfo.name = opts.name;
|
||||
}
|
||||
|
||||
if (algorithm === SECRET_STORAGE_ALGORITHM_V1_AES) {
|
||||
if (opts.passphrase) {
|
||||
keyData.passphrase = opts.passphrase;
|
||||
keyInfo.passphrase = opts.passphrase;
|
||||
}
|
||||
if (opts.key) {
|
||||
const {iv, mac} = await SecretStorage._calculateKeyCheck(opts.key);
|
||||
keyData.iv = iv;
|
||||
keyData.mac = mac;
|
||||
keyInfo.iv = iv;
|
||||
keyInfo.mac = mac;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown key algorithm ${opts.algorithm}`);
|
||||
@@ -116,10 +118,13 @@ export class SecretStorage extends EventEmitter {
|
||||
}
|
||||
|
||||
await this._baseApis.setAccountData(
|
||||
`m.secret_storage.key.${keyId}`, keyData,
|
||||
`m.secret_storage.key.${keyId}`, keyInfo,
|
||||
);
|
||||
|
||||
return keyId;
|
||||
return {
|
||||
keyId,
|
||||
keyInfo,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -457,13 +462,13 @@ export class SecretStorage extends EventEmitter {
|
||||
if (!this._cryptoCallbacks.onSecretRequested) {
|
||||
return;
|
||||
}
|
||||
const secret = await this._cryptoCallbacks.onSecretRequested({
|
||||
user_id: sender,
|
||||
device_id: deviceId,
|
||||
request_id: content.request_id,
|
||||
name: content.name,
|
||||
device_trust: this._baseApis.checkDeviceTrust(sender, deviceId),
|
||||
});
|
||||
const secret = await this._cryptoCallbacks.onSecretRequested(
|
||||
sender,
|
||||
deviceId,
|
||||
content.request_id,
|
||||
content.name,
|
||||
this._baseApis.checkDeviceTrust(sender, deviceId),
|
||||
);
|
||||
if (secret) {
|
||||
logger.info(`Preparing ${content.name} secret for ${deviceId}`);
|
||||
const payload = {
|
||||
|
||||
+6
-6
@@ -633,7 +633,7 @@ Crypto.prototype.bootstrapCrossSigning = async function({
|
||||
* containing the key, or rejects if the key cannot be obtained.
|
||||
* Returns:
|
||||
* {Promise} A promise which resolves to key creation data for
|
||||
* SecretStorage#addKey: an object with `passphrase` and/or `pubkey` fields.
|
||||
* SecretStorage#addKey: an object with `passphrase` etc fields.
|
||||
*/
|
||||
Crypto.prototype.bootstrapSecretStorage = async function({
|
||||
createSecretStorageKey = async () => ({ }),
|
||||
@@ -663,13 +663,13 @@ Crypto.prototype.bootstrapSecretStorage = async function({
|
||||
opts.key = privateKey;
|
||||
}
|
||||
|
||||
const keyId = await secretStorage.addKey(
|
||||
const { keyId, keyInfo } = await secretStorage.addKey(
|
||||
SECRET_STORAGE_ALGORITHM_V1_AES, opts,
|
||||
);
|
||||
|
||||
if (privateKey) {
|
||||
// make the private key available to encrypt 4S secrets
|
||||
builder.ssssCryptoCallbacks.addPrivateKey(keyId, privateKey);
|
||||
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey);
|
||||
}
|
||||
|
||||
await secretStorage.setDefaultKeyId(keyId);
|
||||
@@ -682,9 +682,9 @@ Crypto.prototype.bootstrapSecretStorage = async function({
|
||||
{keys: {[keyId]: keyInfo}}, "",
|
||||
);
|
||||
if (key) {
|
||||
const keyData = key[1];
|
||||
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyData);
|
||||
const {iv, mac} = await SecretStorage._calculateKeyCheck(keyData);
|
||||
const privateKey = key[1];
|
||||
builder.ssssCryptoCallbacks.addPrivateKey(keyId, keyInfo, privateKey);
|
||||
const {iv, mac} = await SecretStorage._calculateKeyCheck(privateKey);
|
||||
keyInfo.iv = iv;
|
||||
keyInfo.mac = mac;
|
||||
|
||||
|
||||
+45
-16
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
Copyright 2017 Vector Creations Ltd
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2019, 2020 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.
|
||||
@@ -113,7 +113,7 @@ export function setCryptoStoreFactory(fac) {
|
||||
cryptoStoreFactory = fac;
|
||||
}
|
||||
|
||||
interface ICreateClientOpts {
|
||||
export interface ICreateClientOpts {
|
||||
baseUrl: string;
|
||||
idBaseUrl?: string;
|
||||
store?: Store;
|
||||
@@ -126,6 +126,7 @@ interface ICreateClientOpts {
|
||||
identityServer?: any;
|
||||
localTimeoutMs?: number;
|
||||
useAuthorizationHeader?: boolean;
|
||||
timelineSupport?: boolean;
|
||||
queryParams?: Record<string, unknown>;
|
||||
deviceToImport?: {
|
||||
olmDevice: {
|
||||
@@ -136,26 +137,54 @@ interface ICreateClientOpts {
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
};
|
||||
pickleKey?: string;
|
||||
sessionStore?: any;
|
||||
unstableClientRelationAggregation?: boolean;
|
||||
verificationMethods?: Array<any>;
|
||||
forceTURN?: boolean;
|
||||
fallbackICEServerAllowed?: boolean;
|
||||
cryptoCallbacks?: {
|
||||
getCrossSigningKey?: (keyType: string, pubKey: Uint8Array) => Promise<Uint8Array>;
|
||||
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => unknown;
|
||||
shouldUpgradeDeviceVerifications?: (
|
||||
users: Record<string, any>
|
||||
) => Promise<Array<string>>;
|
||||
getSecretStorageKey?: (
|
||||
keys: {keys: Record<string, {pubkey: Uint8Array}>}, name: string
|
||||
) => Promise<[string, Uint8Array] | null>;
|
||||
cacheSecretStorageKey?: (keyId: string, key: Uint8Array) => unknown;
|
||||
onSecretRequested?: (
|
||||
name: string, userId: string, deviceId: string,
|
||||
requestId: string, deviceTrust: any
|
||||
) => Promise<string>;
|
||||
cryptoCallbacks?: ICryptoCallbacks;
|
||||
}
|
||||
|
||||
export interface ICryptoCallbacks {
|
||||
getCrossSigningKey?: (keyType: string, pubKey: Uint8Array) => Promise<Uint8Array>;
|
||||
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
|
||||
shouldUpgradeDeviceVerifications?: (
|
||||
users: Record<string, any>
|
||||
) => Promise<string[]>;
|
||||
getSecretStorageKey?: (
|
||||
keys: {keys: Record<string, ISecretStorageKeyInfo>}, name: string
|
||||
) => Promise<[string, Uint8Array] | null>;
|
||||
cacheSecretStorageKey?: (
|
||||
keyId: string, keyInfo: ISecretStorageKeyInfo, key: Uint8Array
|
||||
) => void;
|
||||
onSecretRequested?: (
|
||||
userId: string, deviceId: string,
|
||||
requestId: string, secretName: string, deviceTrust: IDeviceTrustLevel
|
||||
) => Promise<string>;
|
||||
getDehydrationKey?: (
|
||||
keyInfo: ISecretStorageKeyInfo,
|
||||
checkFunc: (Uint8Array) => void,
|
||||
) => Promise<Uint8Array>;
|
||||
}
|
||||
|
||||
// TODO: Move this to `SecretStorage` once converted
|
||||
export interface ISecretStorageKeyInfo {
|
||||
passphrase?: {
|
||||
algorithm: "m.pbkdf2";
|
||||
iterations: number;
|
||||
salt: string;
|
||||
};
|
||||
iv?: string;
|
||||
mac?: string;
|
||||
}
|
||||
|
||||
// TODO: Move this to `CrossSigning` once converted
|
||||
export interface IDeviceTrustLevel {
|
||||
isVerified(): boolean;
|
||||
isCrossSigningVerified(): boolean;
|
||||
isLocallyVerified(): boolean;
|
||||
isTofu(): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
-2
@@ -169,6 +169,14 @@ export const MatrixEvent = function(
|
||||
allows for a unique ID which does not change when the event comes back down sync.
|
||||
*/
|
||||
this._txnId = null;
|
||||
|
||||
/* Set an approximate timestamp for the event relative the local clock.
|
||||
* This will inherently be approximate because it doesn't take into account
|
||||
* the time between the server putting the 'age' field on the event as it sent
|
||||
* it to us and the time we're now constructing this event, but that's better
|
||||
* than assuming the local clock is in sync with the origin HS's clock.
|
||||
*/
|
||||
this._localTimestamp = Date.now() - this.getAge();
|
||||
};
|
||||
utils.inherits(MatrixEvent, EventEmitter);
|
||||
|
||||
@@ -311,11 +319,12 @@ utils.extend(MatrixEvent.prototype, {
|
||||
|
||||
/**
|
||||
* Get the age of the event when this function was called.
|
||||
* Relies on the local clock being in sync with the clock of the original homeserver.
|
||||
* This is the 'age' field adjusted according to how long this client has
|
||||
* had the event.
|
||||
* @return {Number} The age of this event in milliseconds.
|
||||
*/
|
||||
getLocalAge: function() {
|
||||
return Date.now() - this.getTs();
|
||||
return Date.now() - this._localTimestamp;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
+2
-4
@@ -358,16 +358,14 @@ Room.prototype.getPendingEvents = function() {
|
||||
|
||||
/**
|
||||
* Check whether the pending event list contains a given event by ID.
|
||||
* If pending event ordering is not "detached" then this returns false.
|
||||
*
|
||||
* @param {string} eventId The event ID to check for.
|
||||
* @return {boolean}
|
||||
* @throws If <code>opts.pendingEventOrdering</code> was not 'detached'
|
||||
*/
|
||||
Room.prototype.hasPendingEvent = function(eventId) {
|
||||
if (this._opts.pendingEventOrdering !== "detached") {
|
||||
throw new Error(
|
||||
"Cannot call hasPendingEvent with pendingEventOrdering == " +
|
||||
this._opts.pendingEventOrdering);
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._pendingEventList.some(event => event.getId() === eventId);
|
||||
|
||||
@@ -226,7 +226,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
||||
account_data: {
|
||||
events: accountData,
|
||||
},
|
||||
});
|
||||
}, true);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -416,7 +416,7 @@ LocalIndexedDBStoreBackend.prototype = {
|
||||
},
|
||||
|
||||
syncToDatabase: function(userTuples) {
|
||||
const syncData = this._syncAccumulator.getJSON();
|
||||
const syncData = this._syncAccumulator.getJSON(true);
|
||||
|
||||
return Promise.all([
|
||||
this._persistUserPresenceEvents(userTuples),
|
||||
|
||||
+47
-12
@@ -87,8 +87,8 @@ export class SyncAccumulator {
|
||||
};
|
||||
}
|
||||
|
||||
accumulate(syncResponse) {
|
||||
this._accumulateRooms(syncResponse);
|
||||
accumulate(syncResponse, fromDatabase) {
|
||||
this._accumulateRooms(syncResponse, fromDatabase);
|
||||
this._accumulateGroups(syncResponse);
|
||||
this._accumulateAccountData(syncResponse);
|
||||
this.nextBatch = syncResponse.next_batch;
|
||||
@@ -107,35 +107,36 @@ export class SyncAccumulator {
|
||||
/**
|
||||
* Accumulate incremental /sync room data.
|
||||
* @param {Object} syncResponse the complete /sync JSON
|
||||
* @param {boolean} fromDatabase True if the sync response is one saved to the database
|
||||
*/
|
||||
_accumulateRooms(syncResponse) {
|
||||
_accumulateRooms(syncResponse, fromDatabase) {
|
||||
if (!syncResponse.rooms) {
|
||||
return;
|
||||
}
|
||||
if (syncResponse.rooms.invite) {
|
||||
Object.keys(syncResponse.rooms.invite).forEach((roomId) => {
|
||||
this._accumulateRoom(
|
||||
roomId, "invite", syncResponse.rooms.invite[roomId],
|
||||
roomId, "invite", syncResponse.rooms.invite[roomId], fromDatabase,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (syncResponse.rooms.join) {
|
||||
Object.keys(syncResponse.rooms.join).forEach((roomId) => {
|
||||
this._accumulateRoom(
|
||||
roomId, "join", syncResponse.rooms.join[roomId],
|
||||
roomId, "join", syncResponse.rooms.join[roomId], fromDatabase,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (syncResponse.rooms.leave) {
|
||||
Object.keys(syncResponse.rooms.leave).forEach((roomId) => {
|
||||
this._accumulateRoom(
|
||||
roomId, "leave", syncResponse.rooms.leave[roomId],
|
||||
roomId, "leave", syncResponse.rooms.leave[roomId], fromDatabase,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_accumulateRoom(roomId, category, data) {
|
||||
_accumulateRoom(roomId, category, data, fromDatabase) {
|
||||
// Valid /sync state transitions
|
||||
// +--------+ <======+ 1: Accept an invite
|
||||
// +== | INVITE | | (5) 2: Leave a room
|
||||
@@ -159,7 +160,7 @@ export class SyncAccumulator {
|
||||
delete this.inviteRooms[roomId];
|
||||
}
|
||||
// (3)
|
||||
this._accumulateJoinState(roomId, data);
|
||||
this._accumulateJoinState(roomId, data, fromDatabase);
|
||||
break;
|
||||
case "leave":
|
||||
if (this.inviteRooms[roomId]) { // (4)
|
||||
@@ -203,7 +204,7 @@ export class SyncAccumulator {
|
||||
}
|
||||
|
||||
// Accumulate timeline and state events in a room.
|
||||
_accumulateJoinState(roomId, data) {
|
||||
_accumulateJoinState(roomId, data, fromDatabase) {
|
||||
// We expect this function to be called a lot (every /sync) so we want
|
||||
// this to be fast. /sync stores events in an array but we often want
|
||||
// to clobber based on type/state_key. Rather than convert arrays to
|
||||
@@ -337,8 +338,20 @@ export class SyncAccumulator {
|
||||
setState(currentData._currentState, e);
|
||||
// append the event to the timeline. The back-pagination token
|
||||
// corresponds to the first event in the timeline
|
||||
let transformedEvent;
|
||||
if (!fromDatabase) {
|
||||
transformedEvent = Object.assign({}, e);
|
||||
if (transformedEvent.unsigned !== undefined) {
|
||||
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
|
||||
}
|
||||
const age = e.unsigned ? e.unsigned.age : e.age;
|
||||
if (age !== undefined) transformedEvent._localTs = Date.now() - age;
|
||||
} else {
|
||||
transformedEvent = e;
|
||||
}
|
||||
|
||||
currentData._timeline.push({
|
||||
event: e,
|
||||
event: transformedEvent,
|
||||
token: index === 0 ? data.timeline.prev_batch : null,
|
||||
});
|
||||
});
|
||||
@@ -405,6 +418,7 @@ export class SyncAccumulator {
|
||||
* represents all room data that should be stored. This should be paired
|
||||
* with the sync token which represents the most recent /sync response
|
||||
* provided to accumulate().
|
||||
* @param {boolean} forDatabase True to generate a sync to be saved to storage
|
||||
* @return {Object} An object with a "nextBatch", "roomsData" and "accountData"
|
||||
* keys.
|
||||
* The "nextBatch" key is a string which represents at what point in the
|
||||
@@ -414,7 +428,7 @@ export class SyncAccumulator {
|
||||
* /sync response from the 'rooms' key onwards. The "accountData" key is
|
||||
* a list of raw events which represent global account data.
|
||||
*/
|
||||
getJSON() {
|
||||
getJSON(forDatabase) {
|
||||
const data = {
|
||||
join: {},
|
||||
invite: {},
|
||||
@@ -486,7 +500,28 @@ export class SyncAccumulator {
|
||||
}
|
||||
roomJson.timeline.prev_batch = msgData.token;
|
||||
}
|
||||
roomJson.timeline.events.push(msgData.event);
|
||||
|
||||
let transformedEvent;
|
||||
if (!forDatabase && msgData.event._localTs) {
|
||||
// This means we have to copy each event so we can fix it up to
|
||||
// set a correct 'age' parameter whilst keeping the local timestamp
|
||||
// on our stored event. If this turns out to be a bottleneck, it could
|
||||
// be optimised either by doing this in the main process after the data
|
||||
// has been structured-cloned to go between the worker & main process,
|
||||
// or special-casing data from saved syncs to read the local timstamp
|
||||
// directly rather than turning it into age to then immediately be
|
||||
// transformed back again into a local timestamp.
|
||||
transformedEvent = Object.assign({}, msgData.event);
|
||||
if (transformedEvent.unsigned !== undefined) {
|
||||
transformedEvent.unsigned = Object.assign({}, transformedEvent.unsigned);
|
||||
}
|
||||
delete transformedEvent._localTs;
|
||||
transformedEvent.unsigned = transformedEvent.unsigned || {};
|
||||
transformedEvent.unsigned.age = Date.now() - msgData.event._localTs;
|
||||
} else {
|
||||
transformedEvent = msgData.event;
|
||||
}
|
||||
roomJson.timeline.events.push(transformedEvent);
|
||||
});
|
||||
|
||||
// Add state data: roll back current state to the start of timeline,
|
||||
|
||||
-1434
File diff suppressed because it is too large
Load Diff
+1393
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
Copyright 2020 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 MatrixEvent from '../models/event';
|
||||
import {logger} from '../logger';
|
||||
import { createNewMatrixCall, MatrixCall, CallErrorCode, CallState, CallDirection } from './call';
|
||||
import { EventType } from '../@types/event';
|
||||
import { MatrixClient } from '../client';
|
||||
|
||||
// Don't ring unless we'd be ringing for at least 3 seconds: the user needs some
|
||||
// time to press the 'accept' button
|
||||
const RING_GRACE_PERIOD = 3000;
|
||||
|
||||
export class CallEventHandler {
|
||||
client: MatrixClient;
|
||||
calls: Map<string, MatrixCall>;
|
||||
callEventBuffer: MatrixEvent[];
|
||||
candidateEventsByCall: Map<string, Array<MatrixEvent>>;
|
||||
|
||||
constructor(client: MatrixClient) {
|
||||
this.client = client;
|
||||
this.calls = new Map<string, MatrixCall>();
|
||||
// The sync code always emits one event at a time, so it will patiently
|
||||
// wait for us to finish processing a call invite before delivering the
|
||||
// next event, even if that next event is a hangup. We therefore accumulate
|
||||
// all our call events and then process them on the 'sync' event, ie.
|
||||
// each time a sync has completed. This way, we can avoid emitting incoming
|
||||
// call events if we get both the invite and answer/hangup in the same sync.
|
||||
// This happens quite often, eg. replaying sync from storage, catchup sync
|
||||
// after loading and after we've been offline for a bit.
|
||||
this.callEventBuffer = [];
|
||||
this.candidateEventsByCall = new Map<string, Array<MatrixEvent>>();
|
||||
this.client.on("sync", this.evaluateEventBuffer);
|
||||
this.client.on("event", this.onEvent);
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.client.removeListener("sync", this.evaluateEventBuffer);
|
||||
this.client.removeListener("event", this.onEvent);
|
||||
}
|
||||
|
||||
private evaluateEventBuffer = () => {
|
||||
if (this.client.getSyncState() === "SYNCING") {
|
||||
// don't process any events until they are all decrypted
|
||||
if (this.callEventBuffer.some((e) => e.isBeingDecrypted())) return;
|
||||
|
||||
const ignoreCallIds = new Set<String>();
|
||||
// inspect the buffer and mark all calls which have been answered
|
||||
// or hung up before passing them to the call event handler.
|
||||
for (const ev of this.callEventBuffer) {
|
||||
if (ev.getType() === EventType.CallAnswer ||
|
||||
ev.getType() === EventType.CallHangup) {
|
||||
ignoreCallIds.add(ev.getContent().call_id);
|
||||
}
|
||||
}
|
||||
// now loop through the buffer chronologically and inject them
|
||||
for (const e of this.callEventBuffer) {
|
||||
if (
|
||||
e.getType() === EventType.CallInvite &&
|
||||
ignoreCallIds.has(e.getContent().call_id)
|
||||
) {
|
||||
// This call has previously been answered or hung up: ignore it
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
this.handleCallEvent(e);
|
||||
} catch (e) {
|
||||
logger.error("Caught exception handling call event", e);
|
||||
}
|
||||
}
|
||||
this.callEventBuffer = [];
|
||||
}
|
||||
}
|
||||
|
||||
private onEvent = (event: MatrixEvent) => {
|
||||
// any call events or ones that might be once they're decrypted
|
||||
if (event.getType().indexOf("m.call.") === 0 || event.isBeingDecrypted()) {
|
||||
// queue up for processing once all events from this sync have been
|
||||
// processed (see above).
|
||||
this.callEventBuffer.push(event);
|
||||
}
|
||||
|
||||
if (event.isBeingDecrypted() || event.isDecryptionFailure()) {
|
||||
// add an event listener for once the event is decrypted.
|
||||
event.once("Event.decrypted", () => {
|
||||
if (event.getType().indexOf("m.call.") === -1) return;
|
||||
|
||||
if (this.callEventBuffer.includes(event)) {
|
||||
// we were waiting for that event to decrypt, so recheck the buffer
|
||||
this.evaluateEventBuffer();
|
||||
} else {
|
||||
// This one wasn't buffered so just run the event handler for it
|
||||
// straight away
|
||||
try {
|
||||
this.handleCallEvent(event);
|
||||
} catch (e) {
|
||||
logger.error("Caught exception handling call event", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private handleCallEvent(event: MatrixEvent) {
|
||||
const content = event.getContent();
|
||||
let call = content.call_id ? this.calls.get(content.call_id) : undefined;
|
||||
//console.info("RECV %s content=%s", event.getType(), JSON.stringify(content));
|
||||
|
||||
if (event.getType() === EventType.CallInvite) {
|
||||
if (event.getSender() === this.client.credentials.userId) {
|
||||
return; // ignore invites you send
|
||||
}
|
||||
|
||||
if (event.getLocalAge() > content.lifetime - RING_GRACE_PERIOD) {
|
||||
return; // expired call
|
||||
}
|
||||
|
||||
if (call && call.state === CallState.Ended) {
|
||||
return; // stale/old invite event
|
||||
}
|
||||
if (call) {
|
||||
logger.log(
|
||||
`WARN: Already have a MatrixCall with id ${content.call_id} but got an ` +
|
||||
`invite. Clobbering.`,
|
||||
);
|
||||
}
|
||||
|
||||
call = createNewMatrixCall(this.client, event.getRoomId(), {
|
||||
forceTURN: this.client._forceTURN,
|
||||
});
|
||||
if (!call) {
|
||||
logger.log(
|
||||
"Incoming call ID " + content.call_id + " but this client " +
|
||||
"doesn't support WebRTC",
|
||||
);
|
||||
// don't hang up the call: there could be other clients
|
||||
// connected that do support WebRTC and declining the
|
||||
// the call on their behalf would be really annoying.
|
||||
return;
|
||||
}
|
||||
|
||||
call.callId = content.call_id;
|
||||
call.initWithInvite(event);
|
||||
this.calls.set(call.callId, call);
|
||||
|
||||
// if we stashed candidate events for that call ID, play them back now
|
||||
if (this.candidateEventsByCall.get(call.callId)) {
|
||||
for (const ev of this.candidateEventsByCall.get(call.callId)) {
|
||||
call.onRemoteIceCandidatesReceived(ev);
|
||||
}
|
||||
}
|
||||
|
||||
// Were we trying to call that user (room)?
|
||||
let existingCall;
|
||||
for (const thisCall of this.calls.values()) {
|
||||
const isCalling = [CallState.WaitLocalMedia, CallState.CreateOffer, CallState.InviteSent].includes(
|
||||
thisCall.state,
|
||||
);
|
||||
|
||||
if (
|
||||
call.roomId === thisCall.roomId &&
|
||||
thisCall.direction === CallDirection.Outbound &&
|
||||
isCalling
|
||||
) {
|
||||
existingCall = thisCall;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingCall) {
|
||||
// If we've only got to wait_local_media or create_offer and
|
||||
// we've got an invite, pick the incoming call because we know
|
||||
// we haven't sent our invite yet otherwise, pick whichever
|
||||
// call has the lowest call ID (by string comparison)
|
||||
if (existingCall.state === CallState.WaitLocalMedia ||
|
||||
existingCall.state === CallState.CreateOffer ||
|
||||
existingCall.callId > call.callId) {
|
||||
logger.log(
|
||||
"Glare detected: answering incoming call " + call.callId +
|
||||
" and canceling outgoing call " + existingCall.callId,
|
||||
);
|
||||
existingCall.replacedBy(call);
|
||||
call.answer();
|
||||
} else {
|
||||
logger.log(
|
||||
"Glare detected: rejecting incoming call " + call.callId +
|
||||
" and keeping outgoing call " + existingCall.callId,
|
||||
);
|
||||
call.hangup(CallErrorCode.Replaced, true);
|
||||
}
|
||||
} else {
|
||||
this.client.emit("Call.incoming", call);
|
||||
}
|
||||
} else if (event.getType() === EventType.CallAnswer) {
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
if (event.getSender() === this.client.credentials.userId) {
|
||||
if (call.state === CallState.Ringing) {
|
||||
call.onAnsweredElsewhere(content);
|
||||
}
|
||||
} else {
|
||||
call.receivedAnswer(content);
|
||||
}
|
||||
} else if (event.getType() === EventType.CallCandidates) {
|
||||
if (event.getSender() === this.client.credentials.userId) {
|
||||
return;
|
||||
}
|
||||
if (!call) {
|
||||
// store the candidates; we may get a call eventually.
|
||||
if (!this.candidateEventsByCall.has(content.call_id)) {
|
||||
this.candidateEventsByCall.set(content.call_id, []);
|
||||
}
|
||||
this.candidateEventsByCall.get(content.call_id).push(event);
|
||||
} else {
|
||||
call.onRemoteIceCandidatesReceived(event);
|
||||
}
|
||||
} else if ([EventType.CallHangup, EventType.CallReject].includes(event.getType())) {
|
||||
// Note that we also observe our own hangups here so we can see
|
||||
// if we've already rejected a call that would otherwise be valid
|
||||
if (!call) {
|
||||
// if not live, store the fact that the call has ended because
|
||||
// we're probably getting events backwards so
|
||||
// the hangup will come before the invite
|
||||
call = createNewMatrixCall(this.client, event.getRoomId());
|
||||
if (call) {
|
||||
call.callId = content.call_id;
|
||||
call.initWithHangup(event);
|
||||
this.calls.set(content.call_id, call);
|
||||
}
|
||||
} else {
|
||||
if (call.state !== CallState.Ended) {
|
||||
if (event.getType() === EventType.CallHangup) {
|
||||
call.onHangupReceived(content);
|
||||
} else {
|
||||
call.onRejectReceived(content);
|
||||
}
|
||||
this.calls.delete(content.call_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1295,6 +1295,16 @@
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^13.0.0"
|
||||
|
||||
"@jest/types@^25.5.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||
integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^3.0.0"
|
||||
|
||||
"@types/babel-types@*", "@types/babel-types@^7.0.0":
|
||||
version "7.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.9.tgz#01d7b86949f455402a94c788883fe4ba574cad41"
|
||||
@@ -1375,6 +1385,14 @@
|
||||
"@types/istanbul-lib-coverage" "*"
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest@^26.0.14":
|
||||
version "26.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3"
|
||||
integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg==
|
||||
dependencies:
|
||||
jest-diff "^25.2.1"
|
||||
pretty-format "^25.2.1"
|
||||
|
||||
"@types/json-schema@^7.0.3":
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
|
||||
@@ -1427,6 +1445,13 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yargs@^15.0.0":
|
||||
version "15.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.8.tgz#7644904cad7427eb704331ea9bf1ee5499b82e23"
|
||||
integrity sha512-b0BYzFUzBpOhPjpl1wtAHU994jBeKF4TKVlT7ssFv44T617XNcPdRoG4AzHLVshLzlrF7i3lTelH7UbuNYV58Q==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^3.4.0":
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.7.0.tgz#0f91aa3c83d019591719e597fbdb73a59595a263"
|
||||
@@ -1657,6 +1682,13 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
ansi-styles@^4.0.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
|
||||
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||
dependencies:
|
||||
color-convert "^2.0.1"
|
||||
|
||||
ansi-styles@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
|
||||
@@ -2298,6 +2330,14 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
|
||||
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
|
||||
@@ -2767,6 +2807,11 @@ diff-sequences@^24.9.0:
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
|
||||
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
|
||||
|
||||
diff-sequences@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
|
||||
integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
|
||||
|
||||
diffie-hellman@^5.0.0:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
|
||||
@@ -4340,6 +4385,16 @@ jest-diff@^24.9.0:
|
||||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-diff@^25.2.1:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
|
||||
integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
|
||||
dependencies:
|
||||
chalk "^3.0.0"
|
||||
diff-sequences "^25.2.6"
|
||||
jest-get-type "^25.2.6"
|
||||
pretty-format "^25.5.0"
|
||||
|
||||
jest-docblock@^24.3.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.9.0.tgz#7970201802ba560e1c4092cc25cbedf5af5a8ce2"
|
||||
@@ -4386,6 +4441,11 @@ jest-get-type@^24.9.0:
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
|
||||
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
|
||||
|
||||
jest-get-type@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
|
||||
integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
|
||||
|
||||
jest-haste-map@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.9.0.tgz#b38a5d64274934e21fa417ae9a9fbeb77ceaac7d"
|
||||
@@ -5717,6 +5777,16 @@ pretty-format@^24.9.0:
|
||||
ansi-styles "^3.2.0"
|
||||
react-is "^16.8.4"
|
||||
|
||||
pretty-format@^25.2.1, pretty-format@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
|
||||
integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
|
||||
dependencies:
|
||||
"@jest/types" "^25.5.0"
|
||||
ansi-regex "^5.0.0"
|
||||
ansi-styles "^4.0.0"
|
||||
react-is "^16.12.0"
|
||||
|
||||
private@^0.1.8:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
|
||||
@@ -5977,7 +6047,7 @@ react-frame-component@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/react-frame-component/-/react-frame-component-4.1.3.tgz#64c09dd29574720879c5f43ee36c17d8ae74d4ec"
|
||||
integrity sha512-4PurhctiqnmC1F5prPZ+LdsalH7pZ3SFA5xoc0HBe8mSHctdLLt4Cr2WXfXOoajHBYq/yiipp9zOgx+vy8GiEA==
|
||||
|
||||
react-is@^16.8.1, react-is@^16.8.4:
|
||||
react-is@^16.12.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
Reference in New Issue
Block a user