diff --git a/.circleci/config.yml b/.circleci/config.yml index 2236fe7c..464f946e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,6 +44,9 @@ jobs: - run: name: Install extra deps for CI command: npm install --no-save replace-in-file@3.0.0 aws-sdk@2.597.0 @octokit/rest@16.36.0 @slack/client@5.0.2 + - add_ssh_keys: + fingerprints: + - "56:a2:ef:d1:41:dd:99:a2:95:cf:df:a0:15:0e:6b:f1" - run: node ./scripts/release-master workflows: diff --git a/CHANGELOG.md b/CHANGELOG.md index c6df7f38..05c388ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## release note +## [v4.3.0](https://github.com/skyway/skyway-js-sdk/releases/tag/v4.3.0) - 2021-01-19 + +### Fixed + +- Fixed a connecting process to signaling server so that `Peer` would reconnect when a request to the dispatcher server failed. ([#297](https://github.com/skyway/skyway-js-sdk/pull/297)) +- Remove unnecessary module settings in package.json. + ## [v4.2.0](https://github.com/skyway/skyway-js-sdk/releases/tag/v4.2.0) - 2020-12-22 ### Added diff --git a/README.md b/README.md index 1aa0e073..da8b7161 100644 --- a/README.md +++ b/README.md @@ -7,23 +7,13 @@ Add the following script tag to your html file. ```html - + ``` You can then use the `Peer` object to start connecting. For more details, check out the [Tutorial](https://webrtc.ecl.ntt.com/en/js-tutorial.html). ([日本語](https://webrtc.ecl.ntt.com/js-tutorial.html)) -#### How to specify version and minified -By changing the file name, you can use each version and minified version. - -Example -- Using version 4.2.0: `skyway-4.2.0.js`. -- Using minified version 4.2.0 : `skyway-4.2.0.min.js`. -- Using the latest minified version: `skyway-latest.min.js`. - -#### CAUTION -When using `skyway-latest.js` and `skyway-latest.min.js`, when a new version of the SDK is released, the latest version will automatically be applied to your application. This makes it easy to keep up with browser updates, but depending on the changes in the SDK, your application may be affected. - +To use the compressed version, add `.min` to the end of the file name, like `skyway-4.3.0.min.js`. ### Installing using npm to use with a bundler (e.g. webpack or browserify) diff --git a/examples/p2p-data/index.html b/examples/p2p-data/index.html index 0de28da4..21873fb7 100644 --- a/examples/p2p-data/index.html +++ b/examples/p2p-data/index.html @@ -27,7 +27,7 @@

P2P Data example

- + diff --git a/examples/p2p-media/index.html b/examples/p2p-media/index.html index 9f0b32e4..041e31c6 100644 --- a/examples/p2p-media/index.html +++ b/examples/p2p-media/index.html @@ -26,7 +26,7 @@

P2P Media example

- + diff --git a/examples/room/index.html b/examples/room/index.html index 086ad81f..513738d5 100644 --- a/examples/room/index.html +++ b/examples/room/index.html @@ -32,7 +32,7 @@

Room example

- + diff --git a/package.json b/package.json index 9abb7353..df5d3f25 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,8 @@ { "name": "skyway-js", - "version": "4.2.0", + "version": "4.3.0", "description": "The official JavaScript SDK for SkyWay", "main": "dist/skyway.js", - "module": "src/peer.js", "types": "skyway-js.d.ts", "scripts": { "test": "karma start ./karma.conf.js", diff --git a/scripts/release-master/index.js b/scripts/release-master/index.js index a192a0b9..a118fd90 100644 --- a/scripts/release-master/index.js +++ b/scripts/release-master/index.js @@ -8,6 +8,7 @@ const isReleaseReady = require('./is-release-ready'); const publishToNpm = require('./publish-to-npm'); const publishToGitHub = require('./publish-to-github'); const notifySlack = require('./notify-slack'); +const execSync = require('child_process').execSync; (async function() { const { @@ -59,6 +60,12 @@ const notifySlack = require('./notify-slack'); return process.exit(0); } + console.log('## Change sdk version on website'); + const stdout = execSync( + `bash ./scripts/release-master/update-website.sh ${version}` + ); + console.log(`stdout: ${stdout.toString()}`); + console.log('## Publish to npm'); await publishToNpm({ NPM_TOKEN }); console.log(''); diff --git a/scripts/release-master/is-release-ready.js b/scripts/release-master/is-release-ready.js index af544149..bb367729 100644 --- a/scripts/release-master/is-release-ready.js +++ b/scripts/release-master/is-release-ready.js @@ -12,6 +12,26 @@ module.exports = async function isReleaseReady(version) { console.log('=> Yes. continue release steps'); console.log(''); + console.log(`Script tag for v${version} exists in each example file?`); + const cond2 = isAllExampleSdkVersionURLCorrect(version); + if (!cond2) { + console.log('=> No. abort release steps'); + console.log(''); + return false; + } + console.log('=> Yes. continue release steps'); + console.log(''); + + console.log(`v${version} exists in README.md?`); + const cond3 = isReadmeVersionURLCorrect(version); + if (!cond3) { + console.log('=> No. abort release steps'); + console.log(''); + return false; + } + console.log('=> Yes. continue release steps'); + console.log(''); + return true; }; @@ -34,3 +54,30 @@ async function hasChangeLog(version) { rl.once('close', () => resolve(false)); }); } + +function isSdkVersionURLCorrect(version, filepath) { + const data = fs.readFileSync(filepath, 'utf8'); + const matches = data.match( + /cdn\.webrtc\.ecl\.ntt\.com\/skyway-([0-9]+\.[0-9]+\.[0-9]+)(\.min)?\.js/g + ); + for (const match of matches) { + if (!match.includes(version)) return false; + } + return true; +} + +function isAllExampleSdkVersionURLCorrect(version) { + const examplePaths = [ + './examples/p2p-data/index.html', + './examples/p2p-media/index.html', + './examples/room/index.html', + ]; + for (const path of examplePaths) { + if (!isSdkVersionURLCorrect(version, path)) return false; + } + return true; +} + +function isReadmeVersionURLCorrect(version) { + return isSdkVersionURLCorrect(version, './README.md'); +} diff --git a/scripts/release-master/update-website.sh b/scripts/release-master/update-website.sh new file mode 100644 index 00000000..3c03f0e3 --- /dev/null +++ b/scripts/release-master/update-website.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +version=$1 +BRANCH_WEBSITE='master' + +# setup git +git config --global user.email "webrtc.skyway@gmail.com" +git config --global user.name "skyway-ci-bot" + +# clone website repo +echo -e "StrictHostKeyChecking no\n" >> ~/.ssh/config +git clone -b $BRANCH_WEBSITE git@github.com:nttcom-webcore/skyway-official-web-site.git + +# update version +cat skyway-official-web-site/docs/documents/javascript-sdk.md | grep ${version} +if [ "$?" -eq 0 ]; then + echo "Website is up to date. Skip updating website." + exit 0 +fi +sed -i -e "s/skyway-[0-9]\{0,\}\.[0-9]\{0,\}\.[0-9]\{0,\}/${version}/g" skyway-official-web-site/docs/documents/javascript-sdk.md +sed -i -e "s/skyway-[0-9]\{0,\}\.[0-9]\{0,\}\.[0-9]\{0,\}/${version}/g" skyway-official-web-site/docs/en/documents/javascript-sdk.md + +# deploy +cd skyway-official-web-site +git add -A +git commit -m 'Update js-sdk version' +git push origin $BRANCH_WEBSITE +echo 'Successful deployment!!' +exit 0 diff --git a/src/peer/socket.js b/src/peer/socket.js index 877b7269..a6176d56 100644 --- a/src/peer/socket.js +++ b/src/peer/socket.js @@ -85,15 +85,12 @@ class Socket extends EventEmitter { } if (this._dispatcherUrl) { - let serverInfo; try { - serverInfo = await this._getSignalingServer(); + this.signalingServerUrl = await this._fetchSignalingServerUrlWithRetry(); } catch (err) { this.emit('error', err); return; } - const httpProtocol = serverInfo.secure ? 'https://' : 'http://'; - this.signalingServerUrl = `${httpProtocol}${serverInfo.host}:${serverInfo.port}`; } this._io = io(this.signalingServerUrl, { @@ -115,41 +112,48 @@ class Socket extends EventEmitter { } /** - * Connect to "new" signaling server. Attempts up to 10 times before giving up and emitting an error on the socket. - * @param {number} [numAttempts=0] - Current number of attempts. + * Connect to "new" signaling server. * @return {Promise} A promise that resolves with new connection has done. * @private */ - async _connectToNewServer(numAttempts = 0) { - // max number of attempts to get a new server from the dispatcher. - const maxNumberOfAttempts = 10; - if ( - numAttempts >= maxNumberOfAttempts || - this._reconnectAttempts >= config.numberServersToTry - ) { + async _connectToNewServer() { + if (this._reconnectAttempts >= config.numberServersToTry) { this.emit('error', 'Could not connect to server.'); return; } - // Keep trying until we connect to a new server because consul can take some time to remove from the active list. - let serverInfo; try { - serverInfo = await this._getSignalingServer(); + this.signalingServerUrl = await this._fetchSignalingServerUrlWithRetry(); } catch (err) { this.emit('error', err); return; } + this._io.io.uri = this.signalingServerUrl; + this._io.connect(); + this._reconnectAttempts++; + } - if (this.signalingServerUrl.indexOf(serverInfo.host) === -1) { - const httpProtocol = serverInfo.secure ? 'https://' : 'http://'; - this.signalingServerUrl = `${httpProtocol}${serverInfo.host}:${serverInfo.port}`; - - this._io.io.uri = this.signalingServerUrl; - this._io.connect(); - this._reconnectAttempts++; - } else { - this._connectToNewServer(++numAttempts); + /** + * Return signaling server url. This attempts trying up to maxNumberOfAttempts times before giving up then throw error. + * @return {String} A string of signaling server url. + */ + async _fetchSignalingServerUrlWithRetry() { + for (let attempts = 0; attempts < config.maxNumberOfAttempts; attempts++) { + const serverInfo = await this._fetchSignalingServer().catch(err => { + logger.warn(err); + }); + if ( + serverInfo && + serverInfo.port && + serverInfo.host && + (!this.signalingServerUrl || + this.signalingServerUrl.indexOf(serverInfo.host) === -1) + ) { + const httpProtocol = serverInfo.secure ? 'https://' : 'http://'; + return `${httpProtocol}${serverInfo.host}:${serverInfo.port}`; + } } + throw new Error('Could not get signaling server url.'); } /** @@ -157,7 +161,7 @@ class Socket extends EventEmitter { * @return {Promise} A promise that resolves with signaling server info and rejects if there's no response or status code isn't 200. */ - _getSignalingServer() { + _fetchSignalingServer() { return new Promise((resolve, reject) => { const http = new XMLHttpRequest(); diff --git a/src/shared/config.js b/src/shared/config.js index ffa20e50..bfced9e3 100644 --- a/src/shared/config.js +++ b/src/shared/config.js @@ -55,6 +55,9 @@ const maxDataSize = 20 * 1024 * 1024; // The minimum interval of using Room.send() is 100 ms const minBroadcastIntervalMs = 100; +// max number of attempts to get a new server from the dispatcher. +const maxNumberOfAttempts = 10; + // Number of reconnection attempts to the same server before giving up const reconnectionAttempts = 2; @@ -88,6 +91,7 @@ export default { maxChunkSize, maxDataSize, minBroadcastIntervalMs, + maxNumberOfAttempts, reconnectionAttempts, numberServersToTry, sendInterval, diff --git a/tests/peer/socket.js b/tests/peer/socket.js index 013e0ed5..d55f7a65 100644 --- a/tests/peer/socket.js +++ b/tests/peer/socket.js @@ -105,7 +105,7 @@ describe('Socket', () => { const signalingHost = 'signaling.io'; const signalingPort = 443; const signalingSecure = true; - let getSignalingServerStub; + let fetchSignalingServerStub; beforeEach(() => { socket = new Socket(apiKey, { @@ -114,8 +114,8 @@ describe('Socket', () => { dispatcherSecure: dispatcherSecure, }); - getSignalingServerStub = sinon.stub(socket, '_getSignalingServer'); - getSignalingServerStub.returns( + fetchSignalingServerStub = sinon.stub(socket, '_fetchSignalingServer'); + fetchSignalingServerStub.returns( Promise.resolve({ host: signalingHost, port: signalingPort, @@ -125,7 +125,7 @@ describe('Socket', () => { }); afterEach(() => { - getSignalingServerStub.restore(); + fetchSignalingServerStub.restore(); }); it('should set _dispatcherUrl', () => { @@ -135,7 +135,7 @@ describe('Socket', () => { ); }); - it('should get set the signalingServerUrl from _getSignalingServer', done => { + it('should get set the signalingServerUrl from _fetchSignalingServer', done => { socket.start(null, token).then(() => { const httpProtocol = signalingSecure ? 'https://' : 'http://'; const signalingServerUrl = `${httpProtocol}${signalingHost}:${signalingPort}`; @@ -419,7 +419,7 @@ describe('Socket', () => { }); }); - describe('_getSignalingServer', () => { + describe('_fetchSignalingServer', () => { let requests = []; let xhr; const fakeDomain = 'fake.domain'; @@ -443,7 +443,7 @@ describe('Socket', () => { const result = { domain: fakeDomain }; socket - ._getSignalingServer() + ._fetchSignalingServer() .then(() => { assert.equal(requests.length, 1); @@ -469,7 +469,7 @@ describe('Socket', () => { const result = { domain: fakeDomain }; socket - ._getSignalingServer() + ._fetchSignalingServer() .then(res => { assert.deepEqual(res, { host: fakeDomain, @@ -492,7 +492,7 @@ describe('Socket', () => { const result = {}; socket - ._getSignalingServer() + ._fetchSignalingServer() .then(() => { assert.fail('This should be rejected.'); done(); @@ -516,7 +516,7 @@ describe('Socket', () => { }; socket - ._getSignalingServer() + ._fetchSignalingServer() .then(() => { assert.fail('This should be rejected.'); done(); @@ -543,7 +543,7 @@ describe('Socket', () => { }, }; socket - ._getSignalingServer() + ._fetchSignalingServer() .then(() => { assert.fail('This should be rejected.'); done(); @@ -571,7 +571,7 @@ describe('Socket', () => { }; socket - ._getSignalingServer() + ._fetchSignalingServer() .then(() => { assert.fail('This should be rejected.'); done(); @@ -589,4 +589,90 @@ describe('Socket', () => { }); }); }); + + describe('_fetchSignalingServerUrlWithRetry', () => { + const signalingHost = 'signaling.io'; + const signalingPort = 443; + const signalingSecure = true; + const httpProtocol = 'https://'; + const signalingServerUrl = `${httpProtocol}${signalingHost}:${signalingPort}`; + let emitStub; + let fetchSignalingServerStub; + + beforeEach(() => { + emitStub = sinon.stub(socket, 'emit'); + fetchSignalingServerStub = sinon.stub(socket, '_fetchSignalingServer'); + fetchSignalingServerStub.returns( + Promise.resolve({ + host: signalingHost, + port: signalingPort, + secure: signalingSecure, + }) + ); + }); + + afterEach(() => { + emitStub.restore(); + fetchSignalingServerStub.restore(); + }); + + it('should return signalingServerUrl', async () => { + const url = await socket._fetchSignalingServerUrlWithRetry(); + + assert.equal(url, signalingServerUrl); + }); + + it('should attempt 10 times before giving up and throw error', async () => { + socket.signalingServerUrl = signalingServerUrl; + + await socket._fetchSignalingServerUrlWithRetry().catch(err => { + assert.equal(fetchSignalingServerStub.callCount, 10); + assert.equal(err.message, 'Could not get signaling server url.'); + }); + + // assert.throws(await socket._fetchSignalingServerUrlWithRetry(), 'Could not get signaling server url.'); + }); + }); + + describe('_connectToNewServer', () => { + let connectToNewServerSpy; + let emitStub; + let fetchSignalingServerUrlWithRetryStub; + + beforeEach(() => { + connectToNewServerSpy = sinon.spy(socket, '_connectToNewServer'); + emitStub = sinon.stub(socket, 'emit'); + fetchSignalingServerUrlWithRetryStub = sinon.stub( + socket, + '_fetchSignalingServerUrlWithRetry' + ); + }); + + afterEach(() => { + connectToNewServerSpy.restore(); + emitStub.restore(); + fetchSignalingServerUrlWithRetryStub.restore(); + }); + + it('should set _io.io.uri', () => { + const signalingServerUrl = 'https://signaling.io:443'; + fetchSignalingServerUrlWithRetryStub.returns(signalingServerUrl); + + socket.start(undefined, token).then(async () => { + await socket._connectToNewServer(); + + assert.equal(socket._io.io.uri, signalingServerUrl); + }); + }); + + describe('when response from dispatcher is empty', () => { + it('should emit an error on the socket', async () => { + fetchSignalingServerUrlWithRetryStub.throws(); + + await socket._connectToNewServer(); + + assert.deepEqual(emitStub.args[0], ['error', new Error()]); + }); + }); + }); });