From 12cbbecee22e5681b0de915301eae61d1fe0e0ea Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Sun, 1 Oct 2023 02:36:16 -0700 Subject: [PATCH 01/32] Outside of browser environment --- jest.config.js | 2 +- package-lock.json | 62 +++++++++++++++++++++---------------- package.json | 2 ++ src/SignalingClient.spec.ts | 26 ++++++++++++++++ src/SignalingClient.ts | 21 +++++++++---- webpack.dev.config.js | 3 +- 6 files changed, 82 insertions(+), 34 deletions(-) diff --git a/jest.config.js b/jest.config.js index 216adfd..31e2e42 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,6 +15,6 @@ module.exports = { transform: { '^.+\\.ts$': 'ts-jest', }, - testEnvironment: "jsdom", + testEnvironment: 'jsdom', clearMocks: true, }; diff --git a/package-lock.json b/package-lock.json index 0b598cd..7c1c1fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,12 +9,14 @@ "version": "2.1.0", "license": "Apache-2.0", "dependencies": { + "Buffer": "^0.0.0", "isomorphic-webcrypto": "^2.3.6", "jsdom": "^20.0.0", "json-schema": "^0.4.0", "json5": "^2.2.3", "tslib": "^1.10.0", "ua-parser-js": "^1.0.35", + "ws": "^8.14.2", "xml2js": "^0.5.0" }, "devDependencies": { @@ -7555,6 +7557,31 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true, + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/bl/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -7767,29 +7794,12 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "node_modules/Buffer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/Buffer/-/Buffer-0.0.0.tgz", + "integrity": "sha512-+zdncl8lI5TCkARStn9F1BwcuJYofYmD0oEHe5FNfCvGfeDJwf6+dSikCdQN6BMXXmHMhNNUagBN367WST1AIQ==", + "engines": { + "node": ">= 0.2.0" } }, "node_modules/buffer-alloc": { @@ -22239,9 +22249,9 @@ } }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index bf1ddff..ace7de5 100644 --- a/package.json +++ b/package.json @@ -55,12 +55,14 @@ "webpack-merge": "^4.2.2" }, "dependencies": { + "Buffer": "^0.0.0", "isomorphic-webcrypto": "^2.3.6", "jsdom": "^20.0.0", "json-schema": "^0.4.0", "json5": "^2.2.3", "tslib": "^1.10.0", "ua-parser-js": "^1.0.35", + "ws": "^8.14.2", "xml2js": "^0.5.0" }, "overrides": { diff --git a/src/SignalingClient.spec.ts b/src/SignalingClient.spec.ts index 20012a5..ef4b783 100644 --- a/src/SignalingClient.spec.ts +++ b/src/SignalingClient.spec.ts @@ -572,4 +572,30 @@ describe('SignalingClient', () => { }); }); }); + + describe('outsideBrowser', () => { + it('parseJSONObjectFromBase64String', done => { + global.atob = undefined; + const client = new SignalingClient(config as SignalingClientConfig); + client.once('sdpAnswer', (sdpAnswer, _) => { + expect(sdpAnswer).toEqual(SDP_ANSWER_OBJECT); + done(); + }); + client.once('open', () => { + MockWebSocket.instance.emit('message', { data: SDP_ANSWER_MASTER_MESSAGE }); + }); + client.open(); + }); + + it('serializeJSONObjectAsBase64String', done => { + global.btoa = undefined; + const client = new SignalingClient(config as SignalingClientConfig); + client.open(); + client.on('open', () => { + client.sendSdpOffer(SDP_OFFER); + expect(MockWebSocket.instance.send).toHaveBeenCalledWith(SDP_OFFER_VIEWER_STRING); + done(); + }); + }); + }); }); diff --git a/src/SignalingClient.ts b/src/SignalingClient.ts index 280af01..8ee0173 100644 --- a/src/SignalingClient.ts +++ b/src/SignalingClient.ts @@ -137,7 +137,8 @@ export class SignalingClient extends EventEmitter { return; } - this.websocket = new WebSocket(signedURL); + /* istanbul ignore next */ + this.websocket = new (global.WebSocket || require('ws'))(signedURL); this.websocket.addEventListener('open', this.onOpen); this.websocket.addEventListener('message', this.onMessage); @@ -166,7 +167,7 @@ export class SignalingClient extends EventEmitter { * @param {string} [recipientClientId] - ID of the client to send the message to. Required for 'MASTER' role. Should not be present for 'VIEWER' role. */ public sendSdpOffer(sdpOffer: RTCSessionDescription, recipientClientId?: string): void { - this.sendMessage(MessageType.SDP_OFFER, sdpOffer.toJSON(), recipientClientId); + this.sendMessage(MessageType.SDP_OFFER, sdpOffer, recipientClientId); } /** @@ -177,7 +178,7 @@ export class SignalingClient extends EventEmitter { * @param {string} [recipientClientId] - ID of the client to send the message to. Required for 'MASTER' role. Should not be present for 'VIEWER' role. */ public sendSdpAnswer(sdpAnswer: RTCSessionDescription, recipientClientId?: string): void { - this.sendMessage(MessageType.SDP_ANSWER, sdpAnswer.toJSON(), recipientClientId); + this.sendMessage(MessageType.SDP_ANSWER, sdpAnswer, recipientClientId); } /** @@ -188,7 +189,7 @@ export class SignalingClient extends EventEmitter { * @param {string} [recipientClientId] - ID of the client to send the message to. Required for 'MASTER' role. Should not be present for 'VIEWER' role. */ public sendIceCandidate(iceCandidate: RTCIceCandidate, recipientClientId?: string): void { - this.sendMessage(MessageType.ICE_CANDIDATE, iceCandidate.toJSON(), recipientClientId); + this.sendMessage(MessageType.ICE_CANDIDATE, iceCandidate, recipientClientId); } /** @@ -266,14 +267,22 @@ export class SignalingClient extends EventEmitter { * Takes the given base64 encoded string and decodes it into a JSON object. */ private static parseJSONObjectFromBase64String(base64EncodedString: string): object { - return JSON.parse(atob(base64EncodedString)); + try { + return JSON.parse(atob(base64EncodedString)); + } catch (e) { + return JSON.parse(Buffer.from(base64EncodedString, 'base64').toString()); + } } /** * Takes the given JSON object and encodes it into a base64 string. */ private static serializeJSONObjectAsBase64String(object: object): string { - return btoa(JSON.stringify(object)); + try { + return btoa(JSON.stringify(object)); + } catch (e) { + return Buffer.from(JSON.stringify(object)).toString('base64'); + } } /** diff --git a/webpack.dev.config.js b/webpack.dev.config.js index f4af1d8..423ec93 100644 --- a/webpack.dev.config.js +++ b/webpack.dev.config.js @@ -1,5 +1,6 @@ const path = require('path'); const merge = require('webpack-merge'); +const webpack = require('webpack'); module.exports = merge.smart(require('./webpack.config'), { mode: 'development', @@ -11,7 +12,7 @@ module.exports = merge.smart(require('./webpack.config'), { devServer: { static: { - directory: path.join(__dirname, "examples") + directory: path.join(__dirname, 'examples'), }, devMiddleware: { publicPath: '/', From 752eaf402020dbcdde21ba31387ea352e05e58e7 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Sun, 1 Oct 2023 03:02:36 -0700 Subject: [PATCH 02/32] Remove Buffer --- package-lock.json | 9 --------- package.json | 1 - 2 files changed, 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c1c1fd..ef61c80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "2.1.0", "license": "Apache-2.0", "dependencies": { - "Buffer": "^0.0.0", "isomorphic-webcrypto": "^2.3.6", "jsdom": "^20.0.0", "json-schema": "^0.4.0", @@ -7794,14 +7793,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/Buffer": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/Buffer/-/Buffer-0.0.0.tgz", - "integrity": "sha512-+zdncl8lI5TCkARStn9F1BwcuJYofYmD0oEHe5FNfCvGfeDJwf6+dSikCdQN6BMXXmHMhNNUagBN367WST1AIQ==", - "engines": { - "node": ">= 0.2.0" - } - }, "node_modules/buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", diff --git a/package.json b/package.json index ace7de5..9c7c604 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "webpack-merge": "^4.2.2" }, "dependencies": { - "Buffer": "^0.0.0", "isomorphic-webcrypto": "^2.3.6", "jsdom": "^20.0.0", "json-schema": "^0.4.0", From 6331efcd009c38170b1c4f22c4641cfe41b33cc8 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Sun, 1 Oct 2023 19:28:01 -0700 Subject: [PATCH 03/32] Remove unnecessary import --- webpack.dev.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/webpack.dev.config.js b/webpack.dev.config.js index 423ec93..28041d6 100644 --- a/webpack.dev.config.js +++ b/webpack.dev.config.js @@ -1,6 +1,5 @@ const path = require('path'); const merge = require('webpack-merge'); -const webpack = require('webpack'); module.exports = merge.smart(require('./webpack.config'), { mode: 'development', From d37bb0b0c949fa2ac11112f8ff5b031650e22e19 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Sun, 1 Oct 2023 19:40:10 -0700 Subject: [PATCH 04/32] Increase max package size --- webpack.dist.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.dist.config.js b/webpack.dist.config.js index 48ea7f5..2b74f43 100644 --- a/webpack.dist.config.js +++ b/webpack.dist.config.js @@ -3,7 +3,7 @@ const TerserPlugin = require('terser-webpack-plugin'); const merge = require('webpack-merge'); // Define maximum asset size before gzipping -const MAX_ASSET_SIZE_KB = 23; +const MAX_ASSET_SIZE_KB = 23.3; const MAX_ASSET_SIZE_BYTES = MAX_ASSET_SIZE_KB * 1024; module.exports = merge.smart(require('./webpack.config'), { From 373983a8541eb752c5119dcbf2e312830ff57ef2 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Mon, 2 Oct 2023 13:41:57 -0700 Subject: [PATCH 05/32] Remove _ --- src/SignalingClient.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalingClient.spec.ts b/src/SignalingClient.spec.ts index ef4b783..b682078 100644 --- a/src/SignalingClient.spec.ts +++ b/src/SignalingClient.spec.ts @@ -577,7 +577,7 @@ describe('SignalingClient', () => { it('parseJSONObjectFromBase64String', done => { global.atob = undefined; const client = new SignalingClient(config as SignalingClientConfig); - client.once('sdpAnswer', (sdpAnswer, _) => { + client.once('sdpAnswer', sdpAnswer => { expect(sdpAnswer).toEqual(SDP_ANSWER_OBJECT); done(); }); From a93d77189c43d65604f2e2bb796a6a4b8748af5c Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Thu, 28 Sep 2023 23:55:43 -0700 Subject: [PATCH 06/32] Add options allowing for fine-grained control of sending and receiving certain ICE candidates --- examples/app.js | 108 ++++++++++++++++++++++++++++++++++++++++++++ examples/index.html | 62 +++++++++++++++++++++++++ examples/master.js | 18 ++++++-- examples/viewer.js | 14 ++++-- 4 files changed, 194 insertions(+), 8 deletions(-) diff --git a/examples/app.js b/examples/app.js index 0fc9f4b..0f943e5 100644 --- a/examples/app.js +++ b/examples/app.js @@ -88,6 +88,18 @@ function getFormValues() { secretAccessKey: $('#secretAccessKey').val(), sessionToken: $('#sessionToken').val() || null, enableDQPmetrics: $('#enableDQPmetrics').is(':checked'), + sendHostCandidates: $('#send-host').is(':checked'), + acceptHostCandidates: $('#accept-host').is(':checked'), + sendRelayCandidates: $('#send-relay').is(':checked'), + acceptRelayCandidates: $('#accept-relay').is(':checked'), + sendSrflxCandidates: $('#send-srflx').is(':checked'), + acceptSrflxCandidates: $('#accept-srflx').is(':checked'), + sendPrflxCandidates: $('#send-prflx').is(':checked'), + acceptPrflxCandidates: $('#accept-prflx').is(':checked'), + sendTcpCandidates: $('#send-tcp').is(':checked'), + acceptTcpCandidates: $('#accept-tcp').is(':checked'), + sendUdpCandidates: $('#send-udp').is(':checked'), + acceptUdpCandidates: $('#accept-udp').is(':checked'), }; } @@ -410,7 +422,21 @@ const fields = [ { field: 'forceSTUN', type: 'radio', name: 'natTraversal' }, { field: 'forceTURN', type: 'radio', name: 'natTraversal' }, { field: 'natTraversalDisabled', type: 'radio', name: 'natTraversal' }, + { field: 'enableDQPmetrics', type: 'checkbox' }, + { field: 'send-host', type: 'checkbox' }, + { field: 'accept-host', type: 'checkbox' }, + { field: 'send-relay', type: 'checkbox' }, + { field: 'accept-relay', type: 'checkbox' }, + { field: 'send-srflx', type: 'checkbox' }, + { field: 'accept-srflx', type: 'checkbox' }, + { field: 'send-prflx', type: 'checkbox' }, + { field: 'accept-prflx', type: 'checkbox' }, + { field: 'send-tcp', type: 'checkbox' }, + { field: 'accept-tcp', type: 'checkbox' }, + { field: 'send-udp', type: 'checkbox' }, + { field: 'accept-udp', type: 'checkbox' }, ]; + fields.forEach(({ field, type, name }) => { const id = '#' + field; @@ -459,6 +485,88 @@ fields.forEach(({ field, type, name }) => { }); }); +/** + * Determines whether the ICE Candidate should be added. + * @param formValues Settings used. + * @param candidate {RTCIceCandidate} iceCandidate to check + * @returns true if the candidate should be added to the peerConnection. + */ +function shouldAcceptCandidate(formValues, candidate) { + const words = candidate.candidate.split(' '); + + if (words.length < 7) { + console.error('Invalid ice candidate!', candidate); + return false; + } + + // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 + const transport = words[2]; + const type = words[7]; + + if (!formValues.acceptUdpCandidates && transport === 'udp') { + return false; + } + + if (!formValues.acceptTcpCandidates && transport === 'tcp') { + return false; + } + + switch (type) { + case 'host': + return formValues.acceptHostCandidates; + case 'srflx': + return formValues.acceptSrflxCandidates; + case 'relay': + return formValues.acceptRelayCandidates; + case 'prflx': + return formValues.acceptPrflxCandidates; + default: + console.warn('ShouldAcceptICECandidate: Unknown candidate type:', candidate.type); + return false; + } +} + +/** + * Determines whether the ICE Candidate should be sent to the peer. + * @param formValues Settings used. + * @param candidate {RTCIceCandidate} iceCandidate to check + * @returns true if the candidate should be sent to the peer. + */ +function shouldSendIceCandidate(formValues, candidate) { + const words = candidate.candidate.split(' '); + + if (words.length < 7) { + console.error('Invalid ice candidate!', candidate); + return false; + } + + // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 + const transport = words[2]; + const type = words[7]; + + if (!formValues.sendUdpCandidates && transport === 'udp') { + return false; + } + + if (!formValues.sendTcpCandidates && transport === 'tcp') { + return false; + } + + switch (type) { + case 'host': + return formValues.sendHostCandidates; + case 'srflx': + return formValues.sendSrflxCandidates; + case 'relay': + return formValues.sendRelayCandidates; + case 'prflx': + return formValues.sendPrflxCandidates; + default: + console.warn('ShouldSendICECandidate: Unknown candidate type:', candidate.type); + return false; + } +} + $('#copy-logs').on('click', async function() { const logsResult = []; $('#logs') diff --git a/examples/index.html b/examples/index.html index d12c8a7..423e313 100644 --- a/examples/index.html +++ b/examples/index.html @@ -172,6 +172,66 @@

Amazon KVS WebRTC DQP

"> + +
Advanced +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
@@ -292,6 +352,8 @@

Live Stats (from Master)

+ +

Logs

diff --git a/examples/master.js b/examples/master.js index a4fd1bc..ad684d7 100644 --- a/examples/master.js +++ b/examples/master.js @@ -262,8 +262,12 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR // When trickle ICE is enabled, send the ICE candidates as they are generated. if (formValues.useTrickleICE) { - printSignalingLog('[MASTER] Sending ICE candidate to client', remoteClientId); - master.signalingClient.sendIceCandidate(candidate, remoteClientId); + if (shouldSendIceCandidate(formValues, candidate)) { + printSignalingLog('[MASTER] Sending ICE candidate to client', remoteClientId); + master.signalingClient.sendIceCandidate(candidate, remoteClientId); + } else { + console.log('[MASTER] Not sending ICE candidate to client', remoteClientId); + } } } else { printSignalingLog('[MASTER] All ICE candidates have been generated for client', remoteClientId); @@ -311,9 +315,13 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR printSignalingLog('[MASTER] Received ICE candidate from client', remoteClientId); console.debug('[MASTER] ICE candidate:', candidate); - // Add the ICE candidate received from the client to the peer connection - const peerConnection = master.peerConnectionByClientId[remoteClientId]; - peerConnection.addIceCandidate(candidate); + if (shouldAcceptCandidate(formValues, candidate)) { + // Add the ICE candidate received from the client to the peer connection + const peerConnection = master.peerConnectionByClientId[remoteClientId]; + peerConnection.addIceCandidate(candidate); + } else { + console.log('[MASTER] Not adding candidate from peer.'); + } }); master.signalingClient.on('close', () => { diff --git a/examples/viewer.js b/examples/viewer.js index f4c90f5..9248d12 100644 --- a/examples/viewer.js +++ b/examples/viewer.js @@ -290,7 +290,11 @@ async function startViewer(localView, remoteView, formValues, onStatsReport, onR // Add the ICE candidate received from the MASTER to the peer connection console.log('[VIEWER] Received ICE candidate'); console.debug('ICE candidate', candidate); - viewer.peerConnection.addIceCandidate(candidate); + if (shouldAddIceCandidate(formValues, candidate)) { + viewer.peerConnection.addIceCandidate(candidate); + } else { + console.log('[VIEWER] Not adding candidate from peer.'); + } }); viewer.signalingClient.on('close', () => { @@ -309,8 +313,12 @@ async function startViewer(localView, remoteView, formValues, onStatsReport, onR // When trickle ICE is enabled, send the ICE candidates as they are generated. if (formValues.useTrickleICE) { - console.log('[VIEWER] Sending ICE candidate'); - viewer.signalingClient.sendIceCandidate(candidate); + if (shouldSendIceCandidate(formValues, candidate)) { + console.log('[VIEWER] Sending ICE candidate'); + viewer.signalingClient.sendIceCandidate(candidate); + } else { + console.log('[VIEWER] Not sending ICE candidate'); + } } } else { console.log('[VIEWER] All ICE candidates have been generated'); From a17d06ac12a577f2992cf3e09f4537033118e313 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 29 Sep 2023 00:00:05 -0700 Subject: [PATCH 07/32] Add description --- examples/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/index.html b/examples/index.html index 423e313..32bdc23 100644 --- a/examples/index.html +++ b/examples/index.html @@ -174,6 +174,7 @@

Amazon KVS WebRTC DQP

Advanced +

Filter settings for which ICE candidates and sent to and received from the peer.

From f043e5772b50e3b62a8092c8849321b6d7a28445 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 29 Sep 2023 00:33:11 -0700 Subject: [PATCH 08/32] Add presets --- examples/app.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/examples/app.js b/examples/app.js index 0f943e5..95a900e 100644 --- a/examples/app.js +++ b/examples/app.js @@ -526,6 +526,69 @@ function shouldAcceptCandidate(formValues, candidate) { } } +$('#natTraversalEnabled').on('click', () => { + $('#accept-host').prop('checked', true); + $('#send-host').prop('checked', true); + $('#accept-relay').prop('checked', true); + $('#send-relay').prop('checked', true); + $('#accept-srflx').prop('checked', true); + $('#send-srflx').prop('checked', true); + $('#accept-prflx').prop('checked', true); + $('#send-prflx').prop('checked', true); + + saveAdvanced(); +}); + +$('#forceSTUN').on('click', () => { + $('#accept-host').prop('checked', false); + $('#send-host').prop('checked', false); + $('#accept-relay').prop('checked', false); + $('#send-relay').prop('checked', false); + $('#accept-srflx').prop('checked', true); + $('#send-srflx').prop('checked', true); + $('#accept-prflx').prop('checked', false); + $('#send-prflx').prop('checked', false); + + saveAdvanced(); +}); + +$('#forceTURN').on('click', () => { + $('#accept-host').prop('checked', false); + $('#send-host').prop('checked', false); + $('#accept-relay').prop('checked', true); + $('#send-relay').prop('checked', true); + $('#accept-srflx').prop('checked', false); + $('#send-srflx').prop('checked', false); + $('#accept-prflx').prop('checked', false); + $('#send-prflx').prop('checked', false); + + saveAdvanced(); +}); + +$('#natTraversalDisabled').on('click', () => { + $('#accept-host').prop('checked', true); + $('#send-host').prop('checked', true); + $('#accept-relay').prop('checked', true); + $('#send-relay').prop('checked', true); + $('#accept-srflx').prop('checked', true); + $('#send-srflx').prop('checked', true); + $('#accept-prflx').prop('checked', true); + $('#send-prflx').prop('checked', true); + + saveAdvanced(); +}); + +function saveAdvanced() { + $('#accept-host').trigger('change'); + $('#send-host').trigger('change'); + $('#accept-relay').trigger('change'); + $('#send-relay').trigger('change'); + $('#accept-srflx').trigger('change'); + $('#send-srflx').trigger('change'); + $('#accept-prflx').trigger('change'); + $('#send-prflx').trigger('change'); +} + /** * Determines whether the ICE Candidate should be sent to the peer. * @param formValues Settings used. From 20072a7efb8673b165c89db9c92ad6a8fa7a3998 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 29 Sep 2023 00:37:20 -0700 Subject: [PATCH 09/32] Fix wrong method name --- examples/viewer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/viewer.js b/examples/viewer.js index 9248d12..c2331d0 100644 --- a/examples/viewer.js +++ b/examples/viewer.js @@ -39,6 +39,7 @@ let timeArray = []; async function startViewer(localView, remoteView, formValues, onStatsReport, onRemoteDataMessage) { try { + const buttonPressedStart = Date.now(); console.log('[VIEWER] Client id is:', formValues.clientId); viewer.localView = localView; @@ -290,7 +291,7 @@ async function startViewer(localView, remoteView, formValues, onStatsReport, onR // Add the ICE candidate received from the MASTER to the peer connection console.log('[VIEWER] Received ICE candidate'); console.debug('ICE candidate', candidate); - if (shouldAddIceCandidate(formValues, candidate)) { + if (shouldAcceptCandidate(formValues, candidate)) { viewer.peerConnection.addIceCandidate(candidate); } else { console.log('[VIEWER] Not adding candidate from peer.'); @@ -334,6 +335,10 @@ async function startViewer(localView, remoteView, formValues, onStatsReport, onR viewer.peerConnection.addEventListener('connectionstatechange', async event => { printPeerConnectionStateInfo(event, '[VIEWER]'); + + if (event.target.connectionState === 'connected') { + console.error(Date.now() - buttonPressedStart, 'ms'); + } }); // As remote tracks are received, add them to the remote view From 08c093f7a1b11c707d635548875e9599b7dcb35a Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 29 Sep 2023 00:49:22 -0700 Subject: [PATCH 10/32] Remove debugging artifacts --- examples/viewer.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/viewer.js b/examples/viewer.js index c2331d0..4c05039 100644 --- a/examples/viewer.js +++ b/examples/viewer.js @@ -39,7 +39,6 @@ let timeArray = []; async function startViewer(localView, remoteView, formValues, onStatsReport, onRemoteDataMessage) { try { - const buttonPressedStart = Date.now(); console.log('[VIEWER] Client id is:', formValues.clientId); viewer.localView = localView; @@ -335,10 +334,6 @@ async function startViewer(localView, remoteView, formValues, onStatsReport, onR viewer.peerConnection.addEventListener('connectionstatechange', async event => { printPeerConnectionStateInfo(event, '[VIEWER]'); - - if (event.target.connectionState === 'connected') { - console.error(Date.now() - buttonPressedStart, 'ms'); - } }); // As remote tracks are received, add them to the remote view From 60d6c6d22f34a460435f94ece4491a0abb2f0fad Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 29 Sep 2023 00:57:13 -0700 Subject: [PATCH 11/32] Remove extra newlines --- examples/index.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/index.html b/examples/index.html index 32bdc23..17279a7 100644 --- a/examples/index.html +++ b/examples/index.html @@ -353,8 +353,6 @@

Live Stats (from Master)

- -

Logs

From d879653cf8db219c3192bfd939c9157138dc5fe8 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Mon, 2 Oct 2023 14:16:06 -0700 Subject: [PATCH 12/32] Extract to common method --- examples/app.js | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/examples/app.js b/examples/app.js index 95a900e..6bd5f69 100644 --- a/examples/app.js +++ b/examples/app.js @@ -492,16 +492,7 @@ fields.forEach(({ field, type, name }) => { * @returns true if the candidate should be added to the peerConnection. */ function shouldAcceptCandidate(formValues, candidate) { - const words = candidate.candidate.split(' '); - - if (words.length < 7) { - console.error('Invalid ice candidate!', candidate); - return false; - } - - // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 - const transport = words[2]; - const type = words[7]; + const { transport, type } = extractTransportAndType(candidate); if (!formValues.acceptUdpCandidates && transport === 'udp') { return false; @@ -596,16 +587,7 @@ function saveAdvanced() { * @returns true if the candidate should be sent to the peer. */ function shouldSendIceCandidate(formValues, candidate) { - const words = candidate.candidate.split(' '); - - if (words.length < 7) { - console.error('Invalid ice candidate!', candidate); - return false; - } - - // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 - const transport = words[2]; - const type = words[7]; + const { transport, type } = extractTransportAndType(candidate); if (!formValues.sendUdpCandidates && transport === 'udp') { return false; @@ -630,6 +612,18 @@ function shouldSendIceCandidate(formValues, candidate) { } } +function extractTransportAndType(candidate) { + const words = candidate.candidate.split(' '); + + if (words.length < 7) { + console.error('Invalid ice candidate!', candidate); + return false; + } + + // https://datatracker.ietf.org/doc/html/rfc5245#section-15.1 + return { transport: words[2], type: words[7] }; +} + $('#copy-logs').on('click', async function() { const logsResult = []; $('#logs') From b530868b3b371cf1e9b9977606dfaf67ff8d2375 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 1 Sep 2023 00:25:14 -0700 Subject: [PATCH 13/32] sample: Add manual JoinStorageSession button --- examples/app.js | 8 +++++ examples/index.html | 6 ++++ examples/joinStorageSession.js | 64 ++++++++++++++++++++++++++++++++++ examples/master.js | 26 ++++++++++++-- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 examples/joinStorageSession.js diff --git a/examples/app.js b/examples/app.js index 6bd5f69..57f8c89 100644 --- a/examples/app.js +++ b/examples/app.js @@ -76,6 +76,7 @@ function getFormValues() { sendAudio: $('#sendAudio').is(':checked'), streamName: $('#streamName').val(), ingestMedia: $('#ingest-media').is(':checked'), + showJSSButton: $('#show-join-storage-session-button').is(':checked'), openDataChannel: $('#openDataChannel').is(':checked'), widescreen: $('#widescreen').is(':checked'), fullscreen: $('#fullscreen').is(':checked'), @@ -135,6 +136,7 @@ function onStop() { } $('#form').removeClass('d-none'); + $('#join-storage-session-button').addClass('d-none'); ROLE = null; } @@ -414,6 +416,7 @@ const fields = [ { field: 'sendAudio', type: 'checkbox' }, { field: 'streamName', type: 'text' }, { field: 'ingest-media', type: 'checkbox' }, + { field: 'show-join-storage-session-button', type: 'checkbox' }, { field: 'widescreen', type: 'radio', name: 'resolution' }, { field: 'fullscreen', type: 'radio', name: 'resolution' }, { field: 'openDataChannel', type: 'checkbox' }, @@ -671,6 +674,11 @@ $('#create-stream-modal-create-stream-button').on('click', async function() { }); }); +$('#join-storage-session-button').on('click', async function() { + const formValues = getFormValues(); + joinStorageSessionManually(formValues); +}); + // Enable tooltips $(document).ready(function() { $('[data-toggle="tooltip"]').tooltip(); diff --git a/examples/index.html b/examples/index.html index 17279a7..23694c5 100644 --- a/examples/index.html +++ b/examples/index.html @@ -113,6 +113,10 @@

Tracks

Additional information ">
+
+ + +

Video Resolution

@@ -298,6 +302,7 @@
Viewer Return Channel
+
@@ -388,6 +393,7 @@

Logs

+ diff --git a/examples/joinStorageSession.js b/examples/joinStorageSession.js new file mode 100644 index 0000000..3319146 --- /dev/null +++ b/examples/joinStorageSession.js @@ -0,0 +1,64 @@ +/** + * This function calls joinStorageSession. + */ +async function joinStorageSessionManually(formValues) { + $('#logs-header')[0].scrollIntoView({ + block: 'start', + }); + + try { + console.log('[JOIN_STORAGE_SESSION] Calling JoinStorageSession for channel', formValues.channelName); + + // Create KVS client + const kinesisVideoClient = new AWS.KinesisVideo({ + region: formValues.region, + accessKeyId: formValues.accessKeyId, + secretAccessKey: formValues.secretAccessKey, + sessionToken: formValues.sessionToken, + endpoint: formValues.endpoint, + }); + + // Step 1: Obtain the ARN of the Signaling Channel + const describeSignalingChannelResponse = await kinesisVideoClient + .describeSignalingChannel({ + ChannelName: formValues.channelName, + }) + .promise(); + const channelARN = describeSignalingChannelResponse.ChannelInfo.ChannelARN; + + // Step 2: Obtain the WEBRTC endpoint + const getSignalingChannelEndpointResponse = await kinesisVideoClient + .getSignalingChannelEndpoint({ + ChannelARN: channelARN, + SingleMasterChannelEndpointConfiguration: { + Protocols: ['WEBRTC'], + Role: KVSWebRTC.Role.VIEWER, + }, + }) + .promise(); + const webrtcEndpoint = getSignalingChannelEndpointResponse.ResourceEndpointList[0].ResourceEndpoint; + + const kinesisVideoClientWebRTCStorageClient = new AWS.KinesisVideoWebRTCStorage({ + region: formValues.region, + accessKeyId: formValues.accessKeyId, + secretAccessKey: formValues.secretAccessKey, + sessionToken: formValues.sessionToken, + endpoint: webrtcEndpoint, + maxRetries: 0, + httpOptions: { + timeout: retryIntervalForJoinStorageSession, + }, + }); + + // Step 3. Call JoinStorageSession + await kinesisVideoClientWebRTCStorageClient + .joinStorageSession({ + channelArn: channelARN, + }) + .promise(); + + console.log('[JOIN_STORAGE_SESSION] Finished invoking JoinStorageSession for channel', formValues.channelName); + } catch (e) { + console.error('[JOIN_STORAGE_SESSION] Encountered error:', e); + } +} diff --git a/examples/master.js b/examples/master.js index ad684d7..c7cae47 100644 --- a/examples/master.js +++ b/examples/master.js @@ -211,6 +211,10 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR master.websocketOpened = true; console.log('[MASTER] Connected to signaling service'); if (master.streamARN) { + if (formValues.showJSSButton) { + $('#join-storage-session-button').removeClass('d-none'); + } + if (formValues.ingestMedia) { await connectToMediaServer(masterRunId); } else { @@ -234,6 +238,22 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR const peerConnection = new RTCPeerConnection(configuration); master.peerConnectionByClientId[remoteClientId] = peerConnection; + // If in WebRTC ingestion mode, retry if no connection was established within 5 seconds. + if (master.streamARN) { + setTimeout(function() { + // We check that it's not failed because if the state transitioned to failed, + // the state change callback would handle this already + if ( + peerConnection.connectionState !== 'connected' && + peerConnection.connectionState !== 'failed' && + peerConnection.connectionState !== 'closed' + ) { + console.error('[MASTER] Connection failed to establish within 5 seconds. Retrying...'); + onPeerConnectionFailed(false); + } + }, 5000); + } + if (formValues.openDataChannel) { peerConnection.ondatachannel = event => { master.dataChannelByClientId[remoteClientId] = event.channel; @@ -341,9 +361,11 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR } } -function onPeerConnectionFailed() { +function onPeerConnectionFailed(printLostConnectionLog = true) { if (master.streamARN) { - console.warn('[MASTER] Lost connection to the storage session.'); + if (printLostConnectionLog) { + console.warn('[MASTER] Lost connection to the storage session.'); + } master.connectionFailures.push(new Date().getTime()); if (shouldStopRetryingJoinStorageSession()) { console.error( From 0569350cc23a3bd3f6c290959fd52b4e45a4f1e6 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 1 Sep 2023 00:31:12 -0700 Subject: [PATCH 14/32] sample: Adjust labels --- examples/index.html | 6 +++--- examples/master.js | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/examples/index.html b/examples/index.html index 23694c5..523a35b 100644 --- a/examples/index.html +++ b/examples/index.html @@ -106,7 +106,7 @@

Tracks

List storage channels outputs the ARNs of all signaling channels configured for storage and their associated stream's ARN.

- +
- - + +
diff --git a/examples/master.js b/examples/master.js index c7cae47..477f081 100644 --- a/examples/master.js +++ b/examples/master.js @@ -210,11 +210,10 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR const masterRunId = ++master.runId; master.websocketOpened = true; console.log('[MASTER] Connected to signaling service'); + if (formValues.showJSSButton) { + $('#join-storage-session-button').removeClass('d-none'); + } if (master.streamARN) { - if (formValues.showJSSButton) { - $('#join-storage-session-button').removeClass('d-none'); - } - if (formValues.ingestMedia) { await connectToMediaServer(masterRunId); } else { From 8f0b763591cb80fac1baa910d23f2986db674f6f Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 1 Sep 2023 00:37:48 -0700 Subject: [PATCH 15/32] master role --- examples/joinStorageSession.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/joinStorageSession.js b/examples/joinStorageSession.js index 3319146..260aeb9 100644 --- a/examples/joinStorageSession.js +++ b/examples/joinStorageSession.js @@ -32,7 +32,7 @@ async function joinStorageSessionManually(formValues) { ChannelARN: channelARN, SingleMasterChannelEndpointConfiguration: { Protocols: ['WEBRTC'], - Role: KVSWebRTC.Role.VIEWER, + Role: KVSWebRTC.Role.MASTER, }, }) .promise(); From 80db54bd15ee2c2eeb5f72d71f7d6e69ed265027 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Fri, 1 Sep 2023 00:55:33 -0700 Subject: [PATCH 16/32] sample: move the connected check to after the SDP answer is sent --- examples/master.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/master.js b/examples/master.js index 477f081..ac0a5a8 100644 --- a/examples/master.js +++ b/examples/master.js @@ -237,22 +237,6 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR const peerConnection = new RTCPeerConnection(configuration); master.peerConnectionByClientId[remoteClientId] = peerConnection; - // If in WebRTC ingestion mode, retry if no connection was established within 5 seconds. - if (master.streamARN) { - setTimeout(function() { - // We check that it's not failed because if the state transitioned to failed, - // the state change callback would handle this already - if ( - peerConnection.connectionState !== 'connected' && - peerConnection.connectionState !== 'failed' && - peerConnection.connectionState !== 'closed' - ) { - console.error('[MASTER] Connection failed to establish within 5 seconds. Retrying...'); - onPeerConnectionFailed(false); - } - }, 5000); - } - if (formValues.openDataChannel) { peerConnection.ondatachannel = event => { master.dataChannelByClientId[remoteClientId] = event.channel; @@ -328,6 +312,22 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR master.signalingClient.sendSdpAnswer(peerConnection.localDescription, remoteClientId); } printSignalingLog('[MASTER] Generating ICE candidates for client', remoteClientId); + + // If in WebRTC ingestion mode, retry if no connection was established within 5 seconds. + if (master.streamARN) { + setTimeout(function() { + // We check that it's not failed because if the state transitioned to failed, + // the state change callback would handle this already + if ( + peerConnection.connectionState !== 'connected' && + peerConnection.connectionState !== 'failed' && + peerConnection.connectionState !== 'closed' + ) { + console.error('[MASTER] Connection failed to establish within 5 seconds. Retrying...'); + onPeerConnectionFailed(false); + } + }, 5000); + } }); master.signalingClient.on('iceCandidate', async (candidate, remoteClientId) => { From 2b7e848098ade464a3a4cb1f69256a3331765640 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 11 Oct 2023 14:04:32 -0700 Subject: [PATCH 17/32] Add correlationId to sdp answer for joinStorageSession --- examples/app.js | 4 +++ examples/master.js | 21 ++++++++++++---- src/SignalingClient.ts | 56 +++++++++++++++++++++++++++++++++++------- 3 files changed, 67 insertions(+), 14 deletions(-) diff --git a/examples/app.js b/examples/app.js index 57f8c89..d4cf1fc 100644 --- a/examples/app.js +++ b/examples/app.js @@ -615,6 +615,10 @@ function shouldSendIceCandidate(formValues, candidate) { } } +function randomString() { + return Date.now().toString(); +} + function extractTransportAndType(candidate) { const words = candidate.candidate.split(' '); diff --git a/examples/master.js b/examples/master.js index ac0a5a8..4f1fcba 100644 --- a/examples/master.js +++ b/examples/master.js @@ -87,7 +87,7 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR if (mediaServiceMode) { if (!formValues.sendAudio || !formValues.sendVideo) { console.error('[MASTER] Both Send Video and Send Audio checkboxes need to be checked to ingest and store media.'); - return; + // return; } protocols.push('WEBRTC'); master.streamARN = mediaStorageConfiguration.StreamARN; @@ -278,8 +278,9 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR // When trickle ICE is disabled, send the answer now that all the ICE candidates have ben generated. if (!formValues.useTrickleICE) { printSignalingLog('[MASTER] Sending SDP answer to client', remoteClientId); - console.debug('SDP answer:', peerConnection.localDescription); - master.signalingClient.sendSdpAnswer(peerConnection.localDescription, remoteClientId); + const correlationId = randomString(); + console.debug('SDP answer:', peerConnection.localDescription, 'correlationId:', correlationId); + master.signalingClient.sendSdpAnswer(peerConnection.localDescription, remoteClientId, correlationId); } } }); @@ -308,8 +309,9 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR // When trickle ICE is enabled, send the answer now and then send ICE candidates as they are generated. Otherwise wait on the ICE candidates. if (formValues.useTrickleICE) { printSignalingLog('[MASTER] Sending SDP answer to client', remoteClientId); - console.debug('SDP answer:', peerConnection.localDescription); - master.signalingClient.sendSdpAnswer(peerConnection.localDescription, remoteClientId); + const correlationId = randomString(); + console.debug('SDP answer:', peerConnection.localDescription, 'correlationId:', correlationId); + master.signalingClient.sendSdpAnswer(peerConnection.localDescription, remoteClientId, correlationId); } printSignalingLog('[MASTER] Generating ICE candidates for client', remoteClientId); @@ -343,6 +345,15 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR } }); + master.signalingClient.on('statusResponse', (statusResponse, senderClientId) => { + console.error('[MASTER] Received response from Signaling:', statusResponse, senderClientId || '(no senderClientId provided)'); + + if (master.streamARN) { + console.error('[MASTER] Encountered a fatal error. Stopping the application.'); + onStop(); + } + }); + master.signalingClient.on('close', () => { master.websocketOpened = false; master.runId++; diff --git a/src/SignalingClient.ts b/src/SignalingClient.ts index 8ee0173..16fb7aa 100644 --- a/src/SignalingClient.ts +++ b/src/SignalingClient.ts @@ -33,6 +33,7 @@ enum MessageType { SDP_ANSWER = 'SDP_ANSWER', SDP_OFFER = 'SDP_OFFER', ICE_CANDIDATE = 'ICE_CANDIDATE', + STATUS_RESPONSE = 'STATUS_RESPONSE', } enum ReadyState { @@ -46,6 +47,14 @@ interface WebSocketMessage { messageType: MessageType; messagePayload: string; senderClientId?: string; + statusResponse?: StatusResponse; +} + +interface StatusResponse { + correlationId: 'string'; + errorType: 'string'; + statusCode: 'string'; + description: 'string'; } /** @@ -165,9 +174,11 @@ export class SignalingClient extends EventEmitter { * Typically, only the 'VIEWER' role should send an SDP offer. * @param {RTCSessionDescription} sdpOffer - SDP offer to send. * @param {string} [recipientClientId] - ID of the client to send the message to. Required for 'MASTER' role. Should not be present for 'VIEWER' role. + * @param {string} [correlationId] - Unique ID for this message. If this is present and there is an error, + * Signaling will send a StatusResponse message describing the error. If this is not present, no error will be returned. */ - public sendSdpOffer(sdpOffer: RTCSessionDescription, recipientClientId?: string): void { - this.sendMessage(MessageType.SDP_OFFER, sdpOffer, recipientClientId); + public sendSdpOffer(sdpOffer: RTCSessionDescription, recipientClientId?: string, correlationId?: string): void { + this.sendMessage(MessageType.SDP_OFFER, sdpOffer, recipientClientId, correlationId); } /** @@ -176,9 +187,11 @@ export class SignalingClient extends EventEmitter { * Typically, only the 'MASTER' role should send an SDP answer. * @param {RTCSessionDescription} sdpAnswer - SDP answer to send. * @param {string} [recipientClientId] - ID of the client to send the message to. Required for 'MASTER' role. Should not be present for 'VIEWER' role. + * @param {string} [correlationId] - Unique ID for this message. If this is present and there is an error, + * Signaling will send a StatusResponse message describing the error. If this is not present, no error will be returned. */ - public sendSdpAnswer(sdpAnswer: RTCSessionDescription, recipientClientId?: string): void { - this.sendMessage(MessageType.SDP_ANSWER, sdpAnswer, recipientClientId); + public sendSdpAnswer(sdpAnswer: RTCSessionDescription, recipientClientId?: string, correlationId?: string): void { + this.sendMessage(MessageType.SDP_ANSWER, sdpAnswer, recipientClientId, correlationId); } /** @@ -187,26 +200,30 @@ export class SignalingClient extends EventEmitter { * Typically, both the 'VIEWER' role and 'MASTER' role should send ICE candidates. * @param {RTCIceCandidate} iceCandidate - ICE candidate to send. * @param {string} [recipientClientId] - ID of the client to send the message to. Required for 'MASTER' role. Should not be present for 'VIEWER' role. + * @param {string} [correlationId] - Unique ID for this message. If this is present and there is an error, + * Signaling will send a StatusResponse message describing the error. If this is not present, no error will be returned. */ - public sendIceCandidate(iceCandidate: RTCIceCandidate, recipientClientId?: string): void { - this.sendMessage(MessageType.ICE_CANDIDATE, iceCandidate, recipientClientId); + public sendIceCandidate(iceCandidate: RTCIceCandidate, recipientClientId?: string, correlationId?: string): void { + this.sendMessage(MessageType.ICE_CANDIDATE, iceCandidate, recipientClientId, correlationId); } /** * Validates the WebSocket connection is open and that the recipient client id is present if sending as the 'MASTER'. Encodes the given message payload * and sends the message to the signaling service. */ - private sendMessage(action: MessageType, messagePayload: object, recipientClientId?: string): void { + private sendMessage(action: MessageType, messagePayload: object, recipientClientId?: string, correlationId?: string): void { if (this.readyState !== ReadyState.OPEN) { throw new Error('Could not send message because the connection to the signaling service is not open.'); } this.validateRecipientClientId(recipientClientId); + this.validateCorrelationId(correlationId); this.websocket.send( JSON.stringify({ action, messagePayload: SignalingClient.serializeJSONObjectAsBase64String(messagePayload), recipientClientId: recipientClientId || undefined, + correlationId: correlationId || undefined, }), ); } @@ -241,13 +258,22 @@ export class SignalingClient extends EventEmitter { let parsedMessagePayload: object; try { parsedEventData = JSON.parse(event.data) as WebSocketMessage; - parsedMessagePayload = SignalingClient.parseJSONObjectFromBase64String(parsedEventData.messagePayload); } catch (e) { // For forwards compatibility we ignore messages that are not able to be parsed. // TODO: Consider how to make it easier for users to be aware of dropped messages. return; } - const { messageType, senderClientId } = parsedEventData; + try { + parsedMessagePayload = SignalingClient.parseJSONObjectFromBase64String(parsedEventData.messagePayload); + } catch (e) { + // TODO: Consider how to make it easier for users to be aware of dropped messages. + } + const { messageType, senderClientId, statusResponse } = parsedEventData; + if (!parsedMessagePayload && !statusResponse) { + // TODO: Consider how to make it easier for users to be aware of dropped messages. + return; + } + switch (messageType) { case MessageType.SDP_OFFER: this.emit('sdpOffer', parsedMessagePayload, senderClientId); @@ -260,6 +286,9 @@ export class SignalingClient extends EventEmitter { case MessageType.ICE_CANDIDATE: this.emitOrQueueIceCandidate(parsedMessagePayload, senderClientId); return; + case MessageType.STATUS_RESPONSE: + this.emit('statusResponse', statusResponse, senderClientId); + return; } } @@ -326,6 +355,15 @@ export class SignalingClient extends EventEmitter { } } + /** + * Throws an error if the recipient client id is null and the current role is 'MASTER' as all messages sent as 'MASTER' should have a recipient client id. + */ + private validateCorrelationId(correlationId?: string): void { + if (correlationId && !/^[a-zA-Z0-9_.-]{1,256}$/.test(correlationId)) { + throw new Error('Correlation id does not fit the constraint!'); + } + } + /** * 'error' event handler. Forwards the error onto listeners. */ From 5c872be57c751b6fd56b7e64924184f79e136f53 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 11 Oct 2023 14:49:50 -0700 Subject: [PATCH 18/32] 100% code coverage in the unit tests --- src/SignalingClient.spec.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/SignalingClient.spec.ts b/src/SignalingClient.spec.ts index b682078..31c298c 100644 --- a/src/SignalingClient.spec.ts +++ b/src/SignalingClient.spec.ts @@ -59,6 +59,14 @@ const ICE_CANDIDATE_VIEWER_MESSAGE = const ICE_CANDIDATE_MASTER_MESSAGE = '{"messageType":"ICE_CANDIDATE","messagePayload":"eyJjYW5kaWRhdGUiOiJ1cGQgMTAuMTExLjM0Ljg4Iiwic2RwTWlkIjoiMSIsInNkcE1MaW5lSW5kZXgiOjF9"}'; +const CORRELATION_ID = '1697058567743'; +const STATUS_RESPONSE_MESSAGE = + '{"messageType": "STATUS_RESPONSE","statusResponse":{"correlationId": "' + + CORRELATION_ID + + '","errorType": "InvalidArgumentException","statusCode": "400","success": false}}'; + +const UNKNOWN_MESSAGE = '{"message": "Endpoint request timed out", "connectionId":"Jzo5lcQFtjvCJcq=", "requestId":"Jzo5vMzgNjZFb9Q="}'; + class MockWebSocket extends EventEmitter { static instance: MockWebSocket; @@ -350,6 +358,15 @@ describe('SignalingClient', () => { }); }); + it('should throw an error if the correlationId is invalid', done => { + const client = new SignalingClient(config as SignalingClientConfig); + client.open(); + client.on('open', () => { + expect(() => client.sendSdpAnswer(SDP_ANSWER, null, '?????')).toThrowError(); + done(); + }); + }); + it('should send the message as the master', done => { config.role = Role.MASTER; delete config.clientId; @@ -429,6 +446,7 @@ describe('SignalingClient', () => { }); client.on('open', () => { MockWebSocket.instance.emit('message', { data: 'not valid JSON' }); + MockWebSocket.instance.emit('message', { data: UNKNOWN_MESSAGE }); MockWebSocket.instance.emit('message', { data: SDP_OFFER_MASTER_MESSAGE }); }); client.open(); @@ -571,6 +589,23 @@ describe('SignalingClient', () => { client.open(); }); }); + + describe('statusResponse', () => { + it('should parse statusResponse message from signaling', done => { + config.role = Role.MASTER; + delete config.clientId; + const client = new SignalingClient(config as SignalingClientConfig); + client.once('statusResponse', (statusResponse, senderClientId) => { + expect(senderClientId).toBeUndefined(); + expect(statusResponse.correlationId).toEqual(CORRELATION_ID); + done(); + }); + client.on('open', () => { + MockWebSocket.instance.emit('message', { data: STATUS_RESPONSE_MESSAGE }); + }); + client.open(); + }); + }); }); describe('outsideBrowser', () => { From 445596fff6b40e07adc8921e7ec2e8d33e17724a Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 11 Oct 2023 14:51:29 -0700 Subject: [PATCH 19/32] Increase package size --- webpack.dist.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.dist.config.js b/webpack.dist.config.js index 2b74f43..d7e9762 100644 --- a/webpack.dist.config.js +++ b/webpack.dist.config.js @@ -3,7 +3,7 @@ const TerserPlugin = require('terser-webpack-plugin'); const merge = require('webpack-merge'); // Define maximum asset size before gzipping -const MAX_ASSET_SIZE_KB = 23.3; +const MAX_ASSET_SIZE_KB = 23.6; const MAX_ASSET_SIZE_BYTES = MAX_ASSET_SIZE_KB * 1024; module.exports = merge.smart(require('./webpack.config'), { From 8f105b067123b3c8c3dfba02d532953c459a2974 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 11 Oct 2023 14:53:30 -0700 Subject: [PATCH 20/32] Remove testing artifact --- examples/master.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/master.js b/examples/master.js index 4f1fcba..8c49c57 100644 --- a/examples/master.js +++ b/examples/master.js @@ -87,7 +87,7 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR if (mediaServiceMode) { if (!formValues.sendAudio || !formValues.sendVideo) { console.error('[MASTER] Both Send Video and Send Audio checkboxes need to be checked to ingest and store media.'); - // return; + return; } protocols.push('WEBRTC'); master.streamARN = mediaStorageConfiguration.StreamARN; From 2534f56b0af86dc366154d50a473f55fa141b645 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Thu, 12 Oct 2023 11:23:21 -0700 Subject: [PATCH 21/32] Update README for correlationId --- README.md | 22 +++++++++++++++++++--- examples/master.js | 7 +++++-- src/SignalingClient.ts | 13 +++++++------ 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 857e220..2f6664a 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,19 @@ Emitted when a new SDP answer is received over the channel. Typically only a vie Emitted when a new ICE candidate is received over the channel. +#### Event: `'statusResponse'` +* `statusResponse` {statusResponse} The [status response](https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis-7.html) received from the signaling service. + +Emitted when a statusResponse is received over the channel. + +* `statusResponse` {object} + * `correlationId` {string} `correlationId` of the message for which the status is meant. + * `success` {boolean} Whether this `statusResponse` is a success or failure. (Currently, these responses are only sent on failures.) + * `errorType` {optional string} A name to uniquely identify the error. + * `statusCode` {optional string} HTTP status code corresponding to the nature of the response. + * `description` {optional string} A string description explaining the status. + + #### Event: `'close'` Emitted when the connection to the signaling service is closed. Even if there is an error, as long as the connection is closed, this event will be emitted. @@ -305,17 +318,20 @@ Opens a connection to the signaling service. An error will be thrown if there is #### Method: `close()` Closes the active connection to the signaling service. Nothing will happen if there is no open connection. -#### Method: `sendSdpOffer(sdpOffer, [recipientClientId])` +#### Method: `sendSdpOffer(sdpOffer, [recipientClientId, correlationId])` * `sdpOffer` {[RTCSessionDescription](https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription)} SDP offer to send to the recipient client. * `recipientClientId` {string} The id of the client to send the SDP offer to. If no id is provided, it will be sent to the master. +* `correlationId` {string} A unique identifier for this message. If there was an error with this message, Signaling will send a failure StatusResponse with the same correlationId. -#### Method: `sendSdpAnswer(sdpAnswer, [recipientClientId])` +#### Method: `sendSdpAnswer(sdpAnswer, [recipientClientId, correlationId])` * `sdpAnswer` {[RTCSessionDescription](https://developer.mozilla.org/en-US/docs/Web/API/RTCSessionDescription)} SDP answer to send to the recipient client. * `recipientClientId` {string} The id of the client to send the SDP answer to. If no id is provided, it will be sent to the master. +* `correlationId` {string} A unique identifier for this message. If there was an error with this message, Signaling will send a failure StatusResponse with the same correlationId. -#### Method: `sendIceCandidate(iceCandidate, [recipientClientId])` +#### Method: `sendIceCandidate(iceCandidate, [recipientClientId, correlationId])` * `iceCandidate` {[RTCIceCandidate](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidate)} ICE candidate to send to the recipient client. * `recipientClientId` {string} The id of the client to send the ICE candidate to. If no id is provided, it will be sent to the master. +* `correlationId` {string} A unique identifier for this message. If there was an error with this message, Signaling will send a failure StatusResponse with the same correlationId. ### Interface: `RequestSigner` Interface for signing HTTP and WebSocket requests. diff --git a/examples/master.js b/examples/master.js index 8c49c57..dff0313 100644 --- a/examples/master.js +++ b/examples/master.js @@ -345,8 +345,11 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR } }); - master.signalingClient.on('statusResponse', (statusResponse, senderClientId) => { - console.error('[MASTER] Received response from Signaling:', statusResponse, senderClientId || '(no senderClientId provided)'); + master.signalingClient.on('statusResponse', statusResponse => { + if (!statusResponse.success) { + return; + } + console.error('[MASTER] Received response from Signaling:', statusResponse); if (master.streamARN) { console.error('[MASTER] Encountered a fatal error. Stopping the application.'); diff --git a/src/SignalingClient.ts b/src/SignalingClient.ts index 16fb7aa..736ff9a 100644 --- a/src/SignalingClient.ts +++ b/src/SignalingClient.ts @@ -45,16 +45,17 @@ enum ReadyState { interface WebSocketMessage { messageType: MessageType; - messagePayload: string; + messagePayload?: string; senderClientId?: string; statusResponse?: StatusResponse; } -interface StatusResponse { +export interface StatusResponse { correlationId: 'string'; - errorType: 'string'; - statusCode: 'string'; - description: 'string'; + success: 'boolean'; + errorType?: 'string'; + statusCode?: 'string'; + description?: 'string'; } /** @@ -287,7 +288,7 @@ export class SignalingClient extends EventEmitter { this.emitOrQueueIceCandidate(parsedMessagePayload, senderClientId); return; case MessageType.STATUS_RESPONSE: - this.emit('statusResponse', statusResponse, senderClientId); + this.emit('statusResponse', statusResponse); return; } } From cf4f6aca1ee1965851a8037476c0d1e265f060be Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 18 Oct 2023 12:23:14 -0700 Subject: [PATCH 22/32] Fix boolean flag --- examples/master.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/master.js b/examples/master.js index dff0313..b3f0ac0 100644 --- a/examples/master.js +++ b/examples/master.js @@ -87,7 +87,7 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR if (mediaServiceMode) { if (!formValues.sendAudio || !formValues.sendVideo) { console.error('[MASTER] Both Send Video and Send Audio checkboxes need to be checked to ingest and store media.'); - return; + // return; } protocols.push('WEBRTC'); master.streamARN = mediaStorageConfiguration.StreamARN; @@ -346,7 +346,7 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR }); master.signalingClient.on('statusResponse', statusResponse => { - if (!statusResponse.success) { + if (statusResponse.success) { return; } console.error('[MASTER] Received response from Signaling:', statusResponse); @@ -371,6 +371,7 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR master.signalingClient.open(); } catch (e) { console.error('[MASTER] Encountered error starting:', e); + onStop(); } } From c9ef64ec0471efc267603e84950b92ff50740fca Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 18 Oct 2023 12:32:25 -0700 Subject: [PATCH 23/32] Adjust css --- examples/app.css | 4 ++-- examples/master.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/app.css b/examples/app.css index 056dc4e..4b38773 100644 --- a/examples/app.css +++ b/examples/app.css @@ -26,7 +26,7 @@ pre .warn { color: goldenrod; } -#debug pre { +#logs { height: 500px; overflow: auto; -} \ No newline at end of file +} diff --git a/examples/master.js b/examples/master.js index b3f0ac0..2b7e5d3 100644 --- a/examples/master.js +++ b/examples/master.js @@ -87,7 +87,7 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR if (mediaServiceMode) { if (!formValues.sendAudio || !formValues.sendVideo) { console.error('[MASTER] Both Send Video and Send Audio checkboxes need to be checked to ingest and store media.'); - // return; + return; } protocols.push('WEBRTC'); master.streamARN = mediaStorageConfiguration.StreamARN; From cc7dea95f72d4d24fe107796759f05cbf65a7988 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 18 Oct 2023 13:06:29 -0700 Subject: [PATCH 24/32] Disable datachannel for WebRTC ingestion and remove remote-view in ingestion mode --- examples/app.js | 2 ++ examples/index.html | 4 ++-- examples/master.js | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/examples/app.js b/examples/app.js index d4cf1fc..62a11df 100644 --- a/examples/app.js +++ b/examples/app.js @@ -125,6 +125,8 @@ function onStop() { if (ROLE === 'master') { stopMaster(); $('#master').addClass('d-none'); + $('#master .remote-view').removeClass('d-none'); + $('#master .remote').removeClass('d-none'); } else { stopViewer(); $('#viewer').addClass('d-none'); diff --git a/examples/index.html b/examples/index.html index 523a35b..9152335 100644 --- a/examples/index.html +++ b/examples/index.html @@ -280,7 +280,7 @@

Master

Master Section
-
+
Viewer Return Channel
@@ -291,7 +291,7 @@
Viewer Return Channel
-
+

                     
diff --git a/examples/master.js b/examples/master.js index 2b7e5d3..1827570 100644 --- a/examples/master.js +++ b/examples/master.js @@ -92,6 +92,13 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR protocols.push('WEBRTC'); master.streamARN = mediaStorageConfiguration.StreamARN; console.log(`[MASTER] Using media ingestion feature. Stream ARN: ${master.streamARN}`); + + $('#master .remote').addClass('d-none'); + if (formValues.openDataChannel) { + console.warn('[MASTER] DataChannel is not enabled for WebRTC ingestion. Overriding value to false.'); + formValues.openDataChannel = false; + $('.datachannel').addClass('d-none'); + } } else { console.log('[MASTER] Not using media ingestion feature.'); master.streamARN = null; From 8725bed2f33734b44df1b1be78adb5a9bd675f27 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 18 Oct 2023 13:11:57 -0700 Subject: [PATCH 25/32] Adjust stop button color --- examples/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/index.html b/examples/index.html index 9152335..9beceb1 100644 --- a/examples/index.html +++ b/examples/index.html @@ -301,7 +301,7 @@
Viewer Return Channel
- +
@@ -334,7 +334,7 @@
From Master
- + From c949b5722a98e39d7c67409901c6868600059261 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 18 Oct 2023 16:31:47 -0700 Subject: [PATCH 26/32] reset storage client on next run --- examples/master.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/master.js b/examples/master.js index 1827570..2733a78 100644 --- a/examples/master.js +++ b/examples/master.js @@ -150,6 +150,8 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR timeout: retryIntervalForJoinStorageSession, }, }); + } else { + master.streamARN = null; } // Get ICE server configuration From 57bc54e659e0db35e68cb76821dc785bc77a2e94 Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Wed, 18 Oct 2023 16:32:33 -0700 Subject: [PATCH 27/32] reset storage client on next run --- examples/master.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/master.js b/examples/master.js index 2733a78..3309db6 100644 --- a/examples/master.js +++ b/examples/master.js @@ -151,7 +151,7 @@ async function startMaster(localView, remoteView, formValues, onStatsReport, onR }, }); } else { - master.streamARN = null; + master.storageClient = null; } // Get ICE server configuration From 5201fcf5bcea2f1c5c9fc0e6e97f167d78b0476e Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Thu, 19 Oct 2023 10:37:20 -0700 Subject: [PATCH 28/32] Adjust comment for correlationId --- src/SignalingClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalingClient.ts b/src/SignalingClient.ts index 736ff9a..cd7a14c 100644 --- a/src/SignalingClient.ts +++ b/src/SignalingClient.ts @@ -357,7 +357,7 @@ export class SignalingClient extends EventEmitter { } /** - * Throws an error if the recipient client id is null and the current role is 'MASTER' as all messages sent as 'MASTER' should have a recipient client id. + * Throws an error if the correlationId does not fit the constraints mentioned in {@link https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis4.html the documentation}. */ private validateCorrelationId(correlationId?: string): void { if (correlationId && !/^[a-zA-Z0-9_.-]{1,256}$/.test(correlationId)) { From 4f8e5bd5596d78d78ba36a38de60bca41e1e18c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 01:22:37 +0000 Subject: [PATCH 29/32] Bump @babel/traverse from 7.21.4 to 7.23.2 Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.4 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 125 +++++++++++++++++++++++----------------------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef61c80..8c62e49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,12 +62,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "devOptional": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -113,12 +114,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "devOptional": true, "dependencies": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -232,9 +233,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "devOptional": true, "engines": { "node": ">=6.9.0" @@ -254,25 +255,25 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "devOptional": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "devOptional": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -407,30 +408,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "devOptional": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "devOptional": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "devOptional": true, "engines": { "node": ">=6.9.0" @@ -476,13 +477,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "devOptional": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -490,9 +491,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "devOptional": true, "bin": { "parser": "bin/babel-parser.js" @@ -2020,33 +2021,33 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "devOptional": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", - "devOptional": true, - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "devOptional": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2064,13 +2065,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "devOptional": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { From 80de306db2df0e42d43b96004d20d2385bb3a2e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 21 Oct 2023 02:28:28 +0000 Subject: [PATCH 30/32] Bump react-devtools-core from 4.27.6 to 4.28.4 Bumps [react-devtools-core](https://github.com/facebook/react/tree/HEAD/packages/react-devtools-core) from 4.27.6 to 4.28.4. - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/HEAD/packages/react-devtools-core) --- updated-dependencies: - dependency-name: react-devtools-core dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8c62e49..5aed5d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18344,9 +18344,9 @@ } }, "node_modules/react-devtools-core": { - "version": "4.27.6", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.27.6.tgz", - "integrity": "sha512-jeFNhEzcSwpiqmw+zix5IFibNEPmUodICN7ClrlRKGktzO/3FMteMb52l1NRUiz/ABSYt9hOZ9IPgVDrg5pyUw==", + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.28.4.tgz", + "integrity": "sha512-IUZKLv3CimeM07G3vX4H4loxVpByrzq3HvfTX7v9migalwvLs9ZY5D3S3pKR33U+GguYfBBdMMZyToFhsSE/iQ==", "optional": true, "peer": true, "dependencies": { From 93dc4e228907a994c6163b48f7222a51c8d3242f Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Tue, 7 Nov 2023 08:37:31 -0800 Subject: [PATCH 31/32] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c7c604..928848c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amazon-kinesis-video-streams-webrtc", - "version": "2.1.0", + "version": "2.2.0", "description": "Amazon Kinesis Video Streams WebRTC SDK for JavaScript.", "repository": { "type": "git", From 391f4bc655ac6d62217fc9fbcab875cce351455b Mon Sep 17 00:00:00 2001 From: Jeremy Gunawan Date: Tue, 7 Nov 2023 08:45:31 -0800 Subject: [PATCH 32/32] Bump package lock --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5aed5d8..2ea6edf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "amazon-kinesis-video-streams-webrtc", - "version": "2.1.0", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "amazon-kinesis-video-streams-webrtc", - "version": "2.1.0", + "version": "2.2.0", "license": "Apache-2.0", "dependencies": { "isomorphic-webcrypto": "^2.3.6",