Repository: onflow/fcl-js
Pull request #1182 (2022-05-13 20:10:38)

PKG -- [util-node-http-modules] Fix browser entry point

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 88aec8ef066ff21847aa2a8a2e4ac77001e4e919

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                           | Type  |
| ------------------------------ | ----- |
| @onflow/util-node-http-modules | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/onflow/fcl-js/new/jribbink/fix-http-browser?filename=.changeset/twenty-pugs-accept.md&value=---%0A%22%40onflow%2Futil-node-http-modules%22%3A%20patch%0A---%0A%0APKG%20--%20%5Butil-node-http-modules%5D%20Fix%20browser%20entry%20point%0A)



Patch:
diff --git a/.changeset/happy-cherries-talk.md b/.changeset/happy-cherries-talk.md
new file mode 100644
index 00000000..14cfdd0b
--- /dev/null
+++ b/.changeset/happy-cherries-talk.md
@@ -0,0 +1,5 @@
+---
+"@onflow/util-node-http-modules": patch
+---
+
+Try/catch was not catching missing import error and separate entry points for browser/node were required
diff --git a/.changeset/tidy-keys-heal.md b/.changeset/tidy-keys-heal.md
new file mode 100644
index 00000000..c91bf15d
--- /dev/null
+++ b/.changeset/tidy-keys-heal.md
@@ -0,0 +1,5 @@
+---
+"@onflow/transport-http": minor
+---
+
+Added automatic retries for failed requests with 500,502,503,504 status code
diff --git a/package-lock.json b/package-lock.json
index db2d03c6..395451a7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6995,11 +6995,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/http": {
-      "version": "0.0.1-security",
-      "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
-      "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
-    },
     "node_modules/http-proxy-agent": {
       "version": "4.0.1",
       "dev": true,
@@ -7027,11 +7022,6 @@
         "npm": ">=1.3.7"
       }
     },
-    "node_modules/https": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
-      "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q="
-    },
     "node_modules/https-browserify": {
       "version": "1.0.0",
       "dev": true,
@@ -16072,10 +16062,10 @@
     },
     "packages/config": {
       "name": "@onflow/config",
-      "version": "1.0.0-alpha.1",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
-        "@onflow/util-actor": "^1.0.0-alpha.1"
+        "@onflow/util-actor": "^1.0.0"
       },
       "devDependencies": {
         "jest": "25.3.0",
@@ -17498,19 +17488,19 @@
     },
     "packages/fcl": {
       "name": "@onflow/fcl",
-      "version": "1.0.0-alpha.2",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
-        "@onflow/config": "^1.0.0-alpha.1",
+        "@onflow/config": "^1.0.0",
         "@onflow/interaction": "0.0.11",
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/sdk": "^1.0.0-alpha.1",
-        "@onflow/types": "^1.0.0-alpha.0",
-        "@onflow/util-actor": "^1.0.0-alpha.1",
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0",
-        "@onflow/util-uid": "^1.0.0-alpha.0"
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/sdk": "^1.0.0",
+        "@onflow/types": "^1.0.0",
+        "@onflow/util-actor": "^1.0.0",
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-template": "^1.0.0",
+        "@onflow/util-uid": "^1.0.0"
       },
       "devDependencies": {
         "jest": "^27.0.6",
@@ -19631,7 +19621,7 @@
     },
     "packages/protobuf": {
       "name": "@onflow/protobuf",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
         "@improbable-eng/grpc-web": "^0.12.0",
@@ -20806,7 +20796,7 @@
     },
     "packages/rlp": {
       "name": "@onflow/rlp",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "MPL-2.0",
       "dependencies": {
         "buffer": "^6.0.3"
@@ -20819,17 +20809,17 @@
     },
     "packages/sdk": {
       "name": "@onflow/sdk",
-      "version": "1.0.0-alpha.1",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
-        "@onflow/config": "^1.0.0-alpha.1",
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/transport-http": "^1.0.0-alpha.1",
-        "@onflow/util-actor": "^1.0.0-alpha.1",
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-logger": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0",
+        "@onflow/config": "^1.0.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/transport-http": "^1.0.0",
+        "@onflow/util-actor": "^1.0.0",
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-logger": "^1.0.0",
+        "@onflow/util-template": "^1.0.0",
         "deepmerge": "^4.2.2",
         "sha3": "^2.1.4"
       },
@@ -22952,16 +22942,16 @@
     },
     "packages/transport-grpc": {
       "name": "@onflow/transport-grpc",
-      "version": "1.0.0-alpha.2",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
         "@improbable-eng/grpc-web": "^0.14.0",
         "@improbable-eng/grpc-web-node-http-transport": "^0.14.0",
-        "@onflow/protobuf": "^1.0.0-alpha.0",
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0"
+        "@onflow/protobuf": "^1.0.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-template": "^1.0.0"
       },
       "devDependencies": {
         "jest": "^27.0.6",
@@ -25103,17 +25093,17 @@
     },
     "packages/transport-http": {
       "name": "@onflow/transport-http",
-      "version": "1.0.0-alpha.1",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-node-http-modules": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0"
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-node-http-modules": "^1.0.0",
+        "@onflow/util-template": "^1.0.0"
       },
       "devDependencies": {
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/types": "^1.0.0-alpha.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/types": "^1.0.0",
         "jest": "^27.0.6",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "^0.13.3"
@@ -27236,7 +27226,7 @@
     },
     "packages/types": {
       "name": "@onflow/types",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "devDependencies": {
         "jest": "25.3.0",
@@ -28569,7 +28559,7 @@
     },
     "packages/util-actor": {
       "name": "@onflow/util-actor",
-      "version": "1.0.0-alpha.1",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
         "queue-microtask": "1.1.2"
@@ -28722,10 +28712,10 @@
     },
     "packages/util-address": {
       "name": "@onflow/util-address",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "devDependencies": {
-        "@onflow/types": "^1.0.0-alpha.0",
+        "@onflow/types": "^1.0.0",
         "jest": "26.4.2",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "0.12.4"
@@ -28733,14 +28723,14 @@
     },
     "packages/util-encode-key": {
       "name": "@onflow/util-encode-key",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0"
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0"
       },
       "devDependencies": {
-        "@onflow/types": "^1.0.0-alpha.0",
+        "@onflow/types": "^1.0.0",
         "jest": "^26.6.3",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "^0.13.0"
@@ -29803,10 +29793,10 @@
     },
     "packages/util-invariant": {
       "name": "@onflow/util-invariant",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "devDependencies": {
-        "@onflow/types": "^1.0.0-alpha.0",
+        "@onflow/types": "^1.0.0",
         "jest": "26.4.2",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "0.12.4"
@@ -29814,10 +29804,10 @@
     },
     "packages/util-logger": {
       "name": "@onflow/util-logger",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "dependencies": {
-        "@onflow/sdk": "^1.0.0-alpha.0"
+        "@onflow/sdk": "^1.0.0"
       },
       "devDependencies": {
         "jest": "25.3.0",
@@ -31150,12 +31140,8 @@
     },
     "packages/util-node-http-modules": {
       "name": "@onflow/util-node-http-modules",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
-      "dependencies": {
-        "http": "^0.0.1-security",
-        "https": "^1.0.0"
-      },
       "devDependencies": {
         "jest": "^27.0.6",
         "jest-esm-transformer": "1.0.0",
@@ -33279,7 +33265,7 @@
     },
     "packages/util-template": {
       "name": "@onflow/util-template",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "devDependencies": {
         "jest": "25.3.0",
@@ -34612,7 +34598,7 @@
     },
     "packages/util-uid": {
       "name": "@onflow/util-uid",
-      "version": "1.0.0-alpha.0",
+      "version": "1.0.0",
       "license": "Apache-2.0",
       "devDependencies": {
         "jest": "25.3.0",
@@ -37807,7 +37793,7 @@
     "@onflow/config": {
       "version": "file:packages/config",
       "requires": {
-        "@onflow/util-actor": "^1.0.0-alpha.1",
+        "@onflow/util-actor": "^1.0.0",
         "jest": "25.3.0",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "0.12.0-next.8"
@@ -38984,16 +38970,16 @@
     "@onflow/fcl": {
       "version": "file:packages/fcl",
       "requires": {
-        "@onflow/config": "^1.0.0-alpha.1",
+        "@onflow/config": "^1.0.0",
         "@onflow/interaction": "0.0.11",
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/sdk": "^1.0.0-alpha.1",
-        "@onflow/types": "^1.0.0-alpha.0",
-        "@onflow/util-actor": "^1.0.0-alpha.1",
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0",
-        "@onflow/util-uid": "^1.0.0-alpha.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/sdk": "^1.0.0",
+        "@onflow/types": "^1.0.0",
+        "@onflow/util-actor": "^1.0.0",
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-template": "^1.0.0",
+        "@onflow/util-uid": "^1.0.0",
         "jest": "^27.0.6",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "^0.13.3"
@@ -41248,14 +41234,14 @@
     "@onflow/sdk": {
       "version": "file:packages/sdk",
       "requires": {
-        "@onflow/config": "^1.0.0-alpha.1",
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/transport-http": "^1.0.0-alpha.1",
-        "@onflow/util-actor": "^1.0.0-alpha.1",
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-logger": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0",
+        "@onflow/config": "^1.0.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/transport-http": "^1.0.0",
+        "@onflow/util-actor": "^1.0.0",
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-logger": "^1.0.0",
+        "@onflow/util-template": "^1.0.0",
         "deepmerge": "^4.2.2",
         "jest": "^27.0.6",
         "jest-esm-transformer": "1.0.0",
@@ -42630,11 +42616,11 @@
       "requires": {
         "@improbable-eng/grpc-web": "^0.14.0",
         "@improbable-eng/grpc-web-node-http-transport": "^0.14.0",
-        "@onflow/protobuf": "^1.0.0-alpha.0",
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0",
+        "@onflow/protobuf": "^1.0.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-template": "^1.0.0",
         "jest": "^27.0.6",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "^0.13.3"
@@ -44012,12 +43998,12 @@
     "@onflow/transport-http": {
       "version": "file:packages/transport-http",
       "requires": {
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/types": "^1.0.0-alpha.0",
-        "@onflow/util-address": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
-        "@onflow/util-node-http-modules": "^1.0.0-alpha.0",
-        "@onflow/util-template": "^1.0.0-alpha.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/types": "^1.0.0",
+        "@onflow/util-address": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
+        "@onflow/util-node-http-modules": "^1.0.0",
+        "@onflow/util-template": "^1.0.0",
         "jest": "^27.0.6",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "^0.13.3"
@@ -46487,7 +46473,7 @@
     "@onflow/util-address": {
       "version": "file:packages/util-address",
       "requires": {
-        "@onflow/types": "^1.0.0-alpha.0",
+        "@onflow/types": "^1.0.0",
         "jest": "26.4.2",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "0.12.4"
@@ -46496,9 +46482,9 @@
     "@onflow/util-encode-key": {
       "version": "file:packages/util-encode-key",
       "requires": {
-        "@onflow/rlp": "^1.0.0-alpha.0",
-        "@onflow/types": "^1.0.0-alpha.0",
-        "@onflow/util-invariant": "^1.0.0-alpha.0",
+        "@onflow/rlp": "^1.0.0",
+        "@onflow/types": "^1.0.0",
+        "@onflow/util-invariant": "^1.0.0",
         "jest": "^26.6.3",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "^0.13.0"
@@ -47122,7 +47108,7 @@
     "@onflow/util-invariant": {
       "version": "file:packages/util-invariant",
       "requires": {
-        "@onflow/types": "^1.0.0-alpha.0",
+        "@onflow/types": "^1.0.0",
         "jest": "26.4.2",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "0.12.4"
@@ -47131,7 +47117,7 @@
     "@onflow/util-logger": {
       "version": "file:packages/util-logger",
       "requires": {
-        "@onflow/sdk": "^1.0.0-alpha.0",
+        "@onflow/sdk": "^1.0.0",
         "jest": "25.3.0",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "0.12.0-next.8"
@@ -48128,8 +48114,6 @@
     "@onflow/util-node-http-modules": {
       "version": "file:packages/util-node-http-modules",
       "requires": {
-        "http": "^0.0.1-security",
-        "https": "*",
         "jest": "^27.0.6",
         "jest-esm-transformer": "1.0.0",
         "microbundle": "^0.13.3"
@@ -54458,11 +54442,6 @@
       "version": "2.0.2",
       "dev": true
     },
-    "http": {
-      "version": "0.0.1-security",
-      "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
-      "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
-    },
     "http-proxy-agent": {
       "version": "4.0.1",
       "dev": true,
@@ -54481,11 +54460,6 @@
         "sshpk": "^1.7.0"
       }
     },
-    "https": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz",
-      "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q="
-    },
     "https-browserify": {
       "version": "1.0.0",
       "dev": true
diff --git a/packages/transport-http/src/http-request.js b/packages/transport-http/src/http-request.js
index 1e448bf3..574f13cd 100644
--- a/packages/transport-http/src/http-request.js
+++ b/packages/transport-http/src/http-request.js
@@ -2,7 +2,19 @@ import {invariant} from "@onflow/util-invariant"
 import {getNodeHttpModules} from "@onflow/util-node-http-modules"
 
 class HTTPRequestError extends Error {
-  constructor({transport, error, hostname, path, port, method, requestBody, responseBody, responseStatusText, reqOn}) {
+  constructor({
+    transport,
+    error,
+    hostname,
+    path,
+    port,
+    method,
+    requestBody,
+    responseBody,
+    responseStatusText,
+    reqOn,
+    statusCode,
+  }) {
     const msg = `
       HTTP Request Error: An error occurred when interacting with the Access API.
       ${transport ? `transport=${transport}` : ""}
@@ -14,30 +26,34 @@ class HTTPRequestError extends Error {
       ${responseBody ? `responseBody=${responseBody}` : ""}
       ${responseStatusText ? `responseStatusText=${responseStatusText}` : ""}
       ${reqOn ? `reqOn=${reqOn}` : ""}
+      ${statusCode ? `statusCode=${statusCode}` : ""}
     `
     super(msg)
     this.name = "HTTP Request Error"
+    this.statusCode = statusCode
   }
 }
 
 /**
  * Creates an HTTP Request to be sent to a REST Access API.
- * 
+ *
  * Supports the Fetch API on Web Browsers and Deno.
  * Uses the Node HTTP(S) standard libraries for Node.
- * 
+ *
  * @param {String} hostname - Access API Hostname
  * @param {String} path - Path to the resource on the Access API
  * @param {String} method - HTTP Method
  * @param {Object} body - HTTP Request Body
- * 
- * @returns JSON object response from Access API. 
+ *
+ * @returns JSON object response from Access API.
  */
 export async function httpRequest({
   hostname,
   path,
   method,
   body,
+  retryLimit = 5,
+  retryIntervalMs = 1000,
 }) {
   const isHTTPs = hostname.substring(0, 5) === "https"
 
@@ -46,133 +62,170 @@ export async function httpRequest({
     fetchTransport = fetch || window?.fetch
   } catch (e) {}
 
-  const {
-    nodeHttpsTransport,
-    nodeHttpTransport
-  } = await getNodeHttpModules()
-
-  invariant((fetchTransport || nodeHttpsTransport || nodeHttpTransport), "HTTP Request error: Could not find a supported HTTP module.")
+  const {nodeHttpsTransport, nodeHttpTransport} = await getNodeHttpModules()
+
+  invariant(
+    fetchTransport || nodeHttpsTransport || nodeHttpTransport,
+    "HTTP Request error: Could not find a supported HTTP module."
+  )
+
+  async function requestLoop(retryAttempt = 0) {
+    try {
+      const resp = await makeRequest()
+      return resp
+    } catch (error) {
+      const retryStatusCodes = [500, 502, 503, 504]
+
+      if (retryStatusCodes.includes(error.statusCode)) {
+        return await new Promise((resolve, reject) => {
+          if (retryAttempt < retryLimit) {
+            console.log(
+              `Access node unavailable, retrying in ${retryIntervalMs} ms...`
+            )
+            setTimeout(() => {
+              resolve(requestLoop(retryAttempt + 1))
+            }, retryIntervalMs)
+          } else {
+            throw error
+          }
+        })
+      } else {
+        throw error
+      }
+    }
+  }
 
-  if (fetchTransport) {
-    return fetchTransport(
-      `${hostname}${path}`,
-      {
+  function makeRequest() {
+    if (fetchTransport) {
+      return fetchTransport(`${hostname}${path}`, {
         method: method,
         body: body ? JSON.stringify(body) : undefined,
-      }
-    ).then(async res => {
-      if (res.ok) {
-        return res.json()
-      }
-      const responseJSON = JSON.stringify(await res.json())
-      throw new HTTPRequestError({
-        transport: "FetchTransport",
-        error: responseJSON?.message,
-        hostname,
-        path,
-        method,
-        requestBody: body,
-        responseBody: responseJSON,
-        responseStatusText: res.statusText
-      })
-    }).catch(e => {
-      if (e instanceof HTTPRequestError) {
-        throw e
-      }
-      throw new HTTPRequestError({
-        transport: "FetchTransport",
-        error: e?.message,
-        hostname,
-        path,
-        method,
-        requestBody: body
       })
-    })
-
-  } else if (nodeHttpsTransport && nodeHttpTransport) {
-
-    return new Promise((resolve, reject) => {
-
-      const hostnameParts = hostname.split(":")
-      const port = hostnameParts.length == 3 ? hostnameParts[2] : undefined
-
-      let parsedHostname = hostnameParts.length > 1 ? hostnameParts[1].substring(2) : hostnameParts[0]
-
-      const transport = isHTTPs ? nodeHttpsTransport : nodeHttpTransport
-      const bodyString = body ? JSON.stringify(body) : null
+        .then(async res => {
+          if (res.ok) {
+            return res.json()
+          }
+          const responseJSON = JSON.stringify(await res.json())
+          throw new HTTPRequestError({
+            transport: "FetchTransport",
+            error: responseJSON?.message,
+            hostname,
+            path,
+            method,
+            requestBody: body,
+            responseBody: responseJSON,
+            responseStatusText: res.statusText,
+            statusCode: res.status,
+          })
+        })
+        .catch(e => {
+          if (e instanceof HTTPRequestError) {
+            throw e
+          }
+          throw new HTTPRequestError({
+            transport: "FetchTransport",
+            error: e?.message,
+            hostname,
+            path,
+            method,
+            requestBody: body,
+          })
+        })
+    } else if (nodeHttpsTransport && nodeHttpTransport) {
+      return new Promise((resolve, reject) => {
+        const hostnameParts = hostname.split(":")
+        const port = hostnameParts.length == 3 ? hostnameParts[2] : undefined
 
-      const options = {
-        hostname: parsedHostname,
-        path,
-        port,
-        method,
-        headers: body ? {
-          "Content-Type": "application/json",
-          "Content-Length": bodyString.length
-        } : undefined
-      }
+        let parsedHostname =
+          hostnameParts.length > 1
+            ? hostnameParts[1].substring(2)
+            : hostnameParts[0]
 
-      var responseBody = []
-      const req = transport.request(options, (res) => {
-        res.setEncoding('utf8');
-        
-        res.on("data", dataChunk => {
-          responseBody.push(dataChunk)
-        })
+        const transport = isHTTPs ? nodeHttpsTransport : nodeHttpTransport
+        const bodyString = body ? JSON.stringify(body) : null
 
-        res.on("end", () => {
-          try {
-            responseBody = JSON.parse(responseBody.join(""))
-            if (res?.statusCode
-              && (Number(res?.statusCode) < 200 || Number(res?.statusCode) >= 300)
-            ) {
+        const options = {
+          hostname: parsedHostname,
+          path,
+          port,
+          method,
+          headers: body
+            ? {
+                "Content-Type": "application/json",
+                "Content-Length": bodyString.length,
+              }
+            : undefined,
+        }
+
+        var responseBody = []
+        const req = transport.request(options, res => {
+          res.setEncoding("utf8")
+
+          res.on("data", dataChunk => {
+            responseBody.push(dataChunk)
+          })
+
+          res.on("end", () => {
+            try {
+              responseBody = JSON.parse(responseBody.join(""))
+              if (
+                res?.statusCode &&
+                (Number(res?.statusCode) < 200 ||
+                  Number(res?.statusCode) >= 300)
+              ) {
+                throw new HTTPRequestError({
+                  transport: isHTTPs
+                    ? "NodeHTTPsTransport"
+                    : "NodeHTTPTransport",
+                  error: JSON.stringify(responseBody),
+                  hostname: parsedHostname,
+                  path,
+                  port,
+                  method,
+                  requestBody: body ? JSON.stringify(body) : null,
+                  responseBody: JSON.stringify(responseBody),
+                  reqOn: "end",
+                  statusCode: res?.statusCode,
+                })
+              }
+            } catch (e) {
+              if (e instanceof HTTPRequestError) {
+                throw e
+              }
               throw new HTTPRequestError({
                 transport: isHTTPs ? "NodeHTTPsTransport" : "NodeHTTPTransport",
-                error: JSON.stringify(responseBody),
+                error: e,
                 hostname: parsedHostname,
                 path,
                 port,
                 method,
-                requestBody: body ? JSON.stringify(body) : null,
-                responseBody: JSON.stringify(responseBody),
                 reqOn: "end",
               })
             }
-          } catch(e) {
-            if (e instanceof HTTPRequestError) {
-              throw e
-            }
-            throw new HTTPRequestError({
-              transport: isHTTPs ? "NodeHTTPsTransport" : "NodeHTTPTransport",
-              error: e,
-              hostname: parsedHostname,
-              path,
-              port,
-              method,
-              reqOn: "end",
-            })
-          }
-          resolve(responseBody)
+            resolve(responseBody)
+          })
         })
-      })
 
-      req.on("error", e => {
-        throw new HTTPRequestError({
-          transport: isHTTPs ? "NodeHTTPsTransport" : "NodeHTTPTransport",
-          error: e,
-          hostname: parsedHostname,
-          path,
-          port,
-          method,
-          requestBody: body,
-          responseBody,
-          reqOn: "error",
+        req.on("error", e => {
+          throw new HTTPRequestError({
+            transport: isHTTPs ? "NodeHTTPsTransport" : "NodeHTTPTransport",
+            error: e,
+            hostname: parsedHostname,
+            path,
+            port,
+            method,
+            requestBody: body,
+            responseBody,
+            reqOn: "error",
+          })
         })
-      })
-      
-      if (body) req.write(bodyString)
-      req.end()
 
-    })
+        if (body) req.write(bodyString)
+        req.end()
+      })
+    }
   }
+
+  // Keep retrying request until server available or max attempts exceeded
+  return await requestLoop()
 }
diff --git a/packages/util-node-http-modules/package.json b/packages/util-node-http-modules/package.json
index 54b19414..486d1eaa 100644
--- a/packages/util-node-http-modules/package.json
+++ b/packages/util-node-http-modules/package.json
@@ -1,37 +1,42 @@
 {
-    "name": "@onflow/util-node-http-modules",
-    "version": "1.0.0",
-    "description": "Flow SDK Util Get Node Http Modules",
-    "license": "Apache-2.0",
-    "author": "Dapper Labs <dev@dapperlabs.com>",
-    "homepage": "https://onflow.org",
-    "repository": {
-        "type": "git",
-        "url": "git+ssh://git@github.com/onflow/flow-js-sdk.git"
-    },
-    "bugs": {
-        "url": "https://github.com/onflow/flow-js-sdk/issues"
-    },
-    "jest": {
-        "transform": {
-            ".js": "jest-esm-transformer"
-        }
-    },
-    "devDependencies": {
-        "jest": "^27.0.6",
-        "jest-esm-transformer": "1.0.0",
-        "microbundle": "^0.13.3"
-    },
-    "source": "src/util-node-http-modules.js",
-    "main": "dist/util-node-http-modules.js",
-    "module": "dist/util-node-http-modules.module.js",
-    "unpkg": "dist/util-node-http-modules.umd.js",
-    "scripts": {
-        "alpha": "npm publish --tag alpha",
-        "prepublishOnly": "npm test && npm run build",
-        "test": "jest",
-        "build": "microbundle --no-compress --external http,https",
-        "test:watch": "jest --watch",
-        "start": "microbundle watch --no-compress --external http,https"
+  "name": "@onflow/util-node-http-modules",
+  "version": "1.0.0",
+  "description": "Flow SDK Util Get Node Http Modules",
+  "license": "Apache-2.0",
+  "author": "Dapper Labs <dev@dapperlabs.com>",
+  "homepage": "https://onflow.org",
+  "repository": {
+    "type": "git",
+    "url": "git+ssh://git@github.com/onflow/flow-js-sdk.git"
+  },
+  "bugs": {
+    "url": "https://github.com/onflow/flow-js-sdk/issues"
+  },
+  "jest": {
+    "transform": {
+      ".js": "jest-esm-transformer"
     }
+  },
+  "devDependencies": {
+    "jest": "^27.0.6",
+    "jest-esm-transformer": "1.0.0",
+    "microbundle": "^0.13.3"
+  },
+  "source": [
+    "./src/util-node-http-modules.module.js",
+    "./src/util-node-http-modules.noop.js"
+  ],
+  "main": "dist/util-node-http-modules.noop.js",
+  "exports": {
+    "node": "./dist/util-node-http-modules.module.js",
+    "import": "./dist/util-node-http-modules.noop.js"
+  },
+  "scripts": {
+    "alpha": "npm publish --tag alpha",
+    "prepublishOnly": "npm test && npm run build",
+    "test": "jest",
+    "build": "microbundle --no-compress --external http,https",
+    "test:watch": "jest --watch",
+    "start": "microbundle watch --no-compress --external http,https"
+  }
 }
diff --git a/packages/util-node-http-modules/src/util-node-http-modules.js b/packages/util-node-http-modules/src/util-node-http-modules.module.js
similarity index 100%
rename from packages/util-node-http-modules/src/util-node-http-modules.js
rename to packages/util-node-http-modules/src/util-node-http-modules.module.js
diff --git a/packages/util-node-http-modules/src/util-node-http-modules.noop.js b/packages/util-node-http-modules/src/util-node-http-modules.noop.js
new file mode 100644
index 00000000..fbb6e4e7
--- /dev/null
+++ b/packages/util-node-http-modules/src/util-node-http-modules.noop.js
@@ -0,0 +1,6 @@
+export async function getNodeHttpModules() {
+  return {
+    nodeHttpsTransport: undefined,
+    nodeHttpTransport: undefined,
+  }
+}


Test patch:
diff --git a/packages/util-node-http-modules/src/util-node-http-modules.test.js b/packages/util-node-http-modules/src/util-node-http-modules.test.js
index b54fbd2d..b358ddc3 100644
--- a/packages/util-node-http-modules/src/util-node-http-modules.test.js
+++ b/packages/util-node-http-modules/src/util-node-http-modules.test.js
@@ -1,6 +1,6 @@
-import { getNodeHttpModules } from './util-node-http-modules'
+import {getNodeHttpModules} from "./util-node-http-modules.module"
 
 test("HTTP Transports exist", async () => {
-    const { nodeHttpsTransport, nodeHttpTransport } = await getNodeHttpModules()
-    expect(nodeHttpTransport | nodeHttpsTransport).toBeDefined()
-})
\ No newline at end of file
+  const {nodeHttpsTransport, nodeHttpTransport} = await getNodeHttpModules()
+  expect(nodeHttpTransport | nodeHttpsTransport).toBeDefined()
+})