diff --git a/package.json b/package.json index b6df89595..338ff7dc8 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "immutable": "3.8.1", "jszip": "^3.1.5", "minilog": "3.1.0", - "nets": "3.2.0", "scratch-parser": "5.0.0", "scratch-sb1-converter": "0.2.7", "scratch-translate-extension-languages": "0.0.20191118205314", diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js index 4b26b6b02..f45c7010b 100644 --- a/src/extensions/scratch3_text2speech/index.js +++ b/src/extensions/scratch3_text2speech/index.js @@ -1,5 +1,4 @@ const formatMessage = require('format-message'); -const nets = require('nets'); const languageNames = require('scratch-translate-extension-languages'); const ArgumentType = require('../../extension-support/argument-type'); @@ -8,6 +7,7 @@ const Cast = require('../../util/cast'); const MathUtil = require('../../util/math-util'); const Clone = require('../../util/clone'); const log = require('../../util/log'); +const fetchWithTimeout = require('../../util/fetch-with-timeout'); /** * Icon svg to be displayed in the blocks category menu, encoded as a data URI. @@ -722,46 +722,45 @@ class Scratch3Text2SpeechBlocks { path += `&text=${encodeURIComponent(words.substring(0, 128))}`; // Perform HTTP request to get audio file - return new Promise(resolve => { - nets({ - url: path, - timeout: SERVER_TIMEOUT - }, (err, res, body) => { - if (err) { - log.warn(err); - return resolve(); - } - - if (res.statusCode !== 200) { - log.warn(res.statusCode); - return resolve(); + return fetchWithTimeout(path, {}, SERVER_TIMEOUT) + .then(res => { + if (res.status !== 200) { + throw new Error(`HTTP ${res.status} error reaching translation service`); } + return res.arrayBuffer(); + }) + .then(buffer => { // Play the sound const sound = { data: { - buffer: body.buffer + buffer } }; - this.runtime.audioEngine.decodeSoundPlayer(sound).then(soundPlayer => { - this._soundPlayers.set(soundPlayer.id, soundPlayer); + return this.runtime.audioEngine.decodeSoundPlayer(sound); + }) + .then(soundPlayer => { + this._soundPlayers.set(soundPlayer.id, soundPlayer); - soundPlayer.setPlaybackRate(playbackRate); + soundPlayer.setPlaybackRate(playbackRate); - // Increase the volume - const engine = this.runtime.audioEngine; - const chain = engine.createEffectChain(); - chain.set('volume', SPEECH_VOLUME); - soundPlayer.connect(chain); + // Increase the volume + const engine = this.runtime.audioEngine; + const chain = engine.createEffectChain(); + chain.set('volume', SPEECH_VOLUME); + soundPlayer.connect(chain); - soundPlayer.play(); + soundPlayer.play(); + return new Promise(resolve => { soundPlayer.on('stop', () => { this._soundPlayers.delete(soundPlayer.id); resolve(); }); }); + }) + .catch(err => { + log.warn(err); }); - }); } } module.exports = Scratch3Text2SpeechBlocks; diff --git a/src/extensions/scratch3_translate/index.js b/src/extensions/scratch3_translate/index.js index 560eeaeb2..871ca326a 100644 --- a/src/extensions/scratch3_translate/index.js +++ b/src/extensions/scratch3_translate/index.js @@ -2,7 +2,7 @@ const ArgumentType = require('../../extension-support/argument-type'); const BlockType = require('../../extension-support/block-type'); const Cast = require('../../util/cast'); const log = require('../../util/log'); -const nets = require('nets'); +const fetchWithTimeout = require('../../util/fetch-with-timeout'); const languageNames = require('scratch-translate-extension-languages'); const formatMessage = require('format-message'); @@ -265,28 +265,21 @@ class Scratch3TranslateBlocks { urlBase += encodeURIComponent(args.WORDS); const tempThis = this; - const translatePromise = new Promise(resolve => { - nets({ - url: urlBase, - timeout: serverTimeoutMs - }, (err, res, body) => { - if (err) { - log.warn(`error fetching translate result! ${res}`); - resolve(''); - return ''; - } - const translated = JSON.parse(body).result; + const translatePromise = fetchWithTimeout(urlBase, {}, serverTimeoutMs) + .then(response => response.text()) + .then(responseText => { + const translated = JSON.parse(responseText).result; tempThis._translateResult = translated; // Cache what we just translated so we don't keep making the // same call over and over. tempThis._lastTextTranslated = args.WORDS; tempThis._lastLangTranslated = args.LANGUAGE; - resolve(translated); return translated; + }) + .catch(err => { + log.warn(`error fetching translate result! ${err}`); + return ''; }); - - }); - translatePromise.then(translatedText => translatedText); return translatePromise; } } diff --git a/src/util/fetch-with-timeout.js b/src/util/fetch-with-timeout.js new file mode 100644 index 000000000..983c888b3 --- /dev/null +++ b/src/util/fetch-with-timeout.js @@ -0,0 +1,28 @@ +/** + * Fetch a remote resource like `fetch` does, but with a time limit. + * @param {Request|string} resource Remote resource to fetch. + * @param {?object} init An options object containing any custom settings that you want to apply to the request. + * @param {number} timeout The amount of time before the request is canceled, in milliseconds + * @returns {Promise} The response from the server. + */ +const fetchWithTimeout = (resource, init, timeout) => { + let timeoutID = null; + // Not supported in Safari <11 + const controller = window.AbortController ? new window.AbortController() : null; + const signal = controller ? controller.signal : null; + // The fetch call races a timer. + return Promise.race([ + fetch(resource, Object.assign({signal}, init)).then(response => { + clearTimeout(timeoutID); + return response; + }), + new Promise((resolve, reject) => { + timeoutID = setTimeout(() => { + if (controller) controller.abort(); + reject(new Error(`Fetch timed out after ${timeout} ms`)); + }, timeout); + }) + ]); +}; + +module.exports = fetchWithTimeout; diff --git a/webpack.config.js b/webpack.config.js index 9b38815bd..6a8a3b32b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -77,7 +77,6 @@ module.exports = [ 'immutable': true, 'jszip': true, 'minilog': true, - 'nets': true, 'scratch-parser': true, 'socket.io-client': true, 'text-encoding': true