From 1aa38db4e66ed1ea47417ba5e6254c4b3652c8a3 Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Thu, 14 Nov 2019 08:10:26 -0500
Subject: [PATCH 1/2] replace nets with Fetch API

---
 package.json                                 |  1 -
 src/extensions/scratch3_text2speech/index.js | 50 ++++++++++----------
 src/extensions/scratch3_translate/index.js   | 25 ++++------
 src/util/fetch-with-timeout.js               | 28 +++++++++++
 webpack.config.js                            |  1 -
 5 files changed, 62 insertions(+), 43 deletions(-)
 create mode 100644 src/util/fetch-with-timeout.js

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..f8cc408b4 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,46 @@ 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: 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);
+                return;
             });
-        });
     }
 }
 module.exports = Scratch3Text2SpeechBlocks;
diff --git a/src/extensions/scratch3_translate/index.js b/src/extensions/scratch3_translate/index.js
index 3c1d4cd19..693a87f66 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');
 
@@ -261,28 +261,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<Response>} 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

From 1035e9da28eb975af605b7518f77a0a4305950dd Mon Sep 17 00:00:00 2001
From: adroitwhiz <adroitwhiz@protonmail.com>
Date: Thu, 4 Mar 2021 00:29:50 -0500
Subject: [PATCH 2/2] Clean up TTS request code

---
 src/extensions/scratch3_text2speech/index.js | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/extensions/scratch3_text2speech/index.js b/src/extensions/scratch3_text2speech/index.js
index f8cc408b4..f45c7010b 100644
--- a/src/extensions/scratch3_text2speech/index.js
+++ b/src/extensions/scratch3_text2speech/index.js
@@ -734,7 +734,7 @@ class Scratch3Text2SpeechBlocks {
                 // Play the sound
                 const sound = {
                     data: {
-                        buffer: buffer
+                        buffer
                     }
                 };
                 return this.runtime.audioEngine.decodeSoundPlayer(sound);
@@ -760,7 +760,6 @@ class Scratch3Text2SpeechBlocks {
             })
             .catch(err => {
                 log.warn(err);
-                return;
             });
     }
 }