From 62201c5eebc52e9723dbbb2cc64823155ce1e0f9 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Tue, 11 Jun 2019 22:40:45 -0400 Subject: [PATCH] Migrated XMLHttpRequest to fetch API (#506). --- packages/errors/src.ts/index.ts | 7 +- packages/tests/src.ts/test-providers.ts | 4 +- packages/web/src.ts/browser-xmlhttprequest.ts | 8 -- packages/web/src.ts/index.ts | 136 +++++++++--------- 4 files changed, 72 insertions(+), 83 deletions(-) delete mode 100644 packages/web/src.ts/browser-xmlhttprequest.ts diff --git a/packages/errors/src.ts/index.ts b/packages/errors/src.ts/index.ts index bb3392e0f9..fdb00ca586 100644 --- a/packages/errors/src.ts/index.ts +++ b/packages/errors/src.ts/index.ts @@ -17,9 +17,14 @@ export const NOT_IMPLEMENTED = "NOT_IMPLEMENTED"; // - operation export const UNSUPPORTED_OPERATION = "UNSUPPORTED_OPERATION"; -// Network Error +// Network Error (i.e. Ethereum Network, such as an invalid chain ID) export const NETWORK_ERROR = "NETWORK_ERROR"; +// Some sort of bad response from the server +export const SERVER_ERROR = "SERVER_ERROR"; + +// Timeout +export const TIMEOUT = "TIMEOUT"; /////////////////// // Operational Errors diff --git a/packages/tests/src.ts/test-providers.ts b/packages/tests/src.ts/test-providers.ts index 2321414035..3d4fda4909 100644 --- a/packages/tests/src.ts/test-providers.ts +++ b/packages/tests/src.ts/test-providers.ts @@ -460,7 +460,7 @@ describe('Test Basic Authentication', function() { url: string; user: string; password: string; - allowInsecure?: boolean; + allowInsecureAuthentication?: boolean; }; function test(name: string, url: TestCase): void { @@ -488,7 +488,7 @@ describe('Test Basic Authentication', function() { url: 'http://httpbin.org/basic-auth/user/passwd', user: 'user', password: 'passwd', - allowInsecure: true + allowInsecureAuthentication: true }; test('secure url', secure); diff --git a/packages/web/src.ts/browser-xmlhttprequest.ts b/packages/web/src.ts/browser-xmlhttprequest.ts deleted file mode 100644 index 21cb7e1219..0000000000 --- a/packages/web/src.ts/browser-xmlhttprequest.ts +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; - -try { - module.exports.XMLHttpRequest = XMLHttpRequest; -} catch(error) { - console.log("Warning: XMLHttpRequest is not defined"); - module.exports.XMLHttpRequest = null; -} diff --git a/packages/web/src.ts/index.ts b/packages/web/src.ts/index.ts index 03799de7de..349b119b05 100644 --- a/packages/web/src.ts/index.ts +++ b/packages/web/src.ts/index.ts @@ -1,6 +1,6 @@ "use strict"; -import { XMLHttpRequest } from "xmlhttprequest"; +import { default as fetch } from "node-fetch"; import { encode as base64Encode } from "@ethersproject/base64"; import * as errors from "@ethersproject/errors"; @@ -13,7 +13,7 @@ export type ConnectionInfo = { url: string, user?: string, password?: string, - allowInsecure?: boolean, + allowInsecureAuthentication?: boolean, timeout?: number, headers?: { [key: string]: string | number } }; @@ -40,14 +40,24 @@ export function fetchJson(connection: string | ConnectionInfo, json?: string, pr let url: string = null; + // @TODO: Allow ConnectionInfo to override some of these values + let options: any = { + method: "GET", + mode: "cors", // no-cors, cors, *same-origin + cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached + credentials: "same-origin", // include, *same-origin, omit + redirect: "follow", // manual, *follow, error + referrer: "client", // no-referrer, *client + }; + let timeout = 2 * 60 * 1000; if (typeof(connection) === "string") { url = connection; } else if (typeof(connection) === "object") { - if (connection.url == null) { - errors.throwError("missing URL", errors.MISSING_ARGUMENT, { arg: "url" }); + if (connection == null || connection.url == null) { + errors.throwArgumentError("missing URL", "connection.url", connection); } url = connection.url; @@ -63,7 +73,7 @@ export function fetchJson(connection: string | ConnectionInfo, json?: string, pr } if (connection.user != null && connection.password != null) { - if (url.substring(0, 6) !== "https:" && connection.allowInsecure !== true) { + if (url.substring(0, 6) !== "https:" && connection.allowInsecureAuthentication !== true) { errors.throwError( "basic authentication requires a secure https url", errors.INVALID_ARGUMENT, @@ -80,18 +90,19 @@ export function fetchJson(connection: string | ConnectionInfo, json?: string, pr } return new Promise(function(resolve, reject) { - let request = new XMLHttpRequest(); let timer: any = null; - timer = setTimeout(() => { - if (timer == null) { return; } - timer = null; - - reject(new Error("timeout")); - setTimeout(() => { - request.abort(); - }, 0); - }, timeout); + if (timeout) { + timer = setTimeout(() => { + if (timer == null) { return; } + timer = null; + + reject(errors.makeError("timeout", errors.TIMEOUT, { })); + //setTimeout(() => { + // request.abort(); + //}, 0); + }, timeout); + } let cancelTimeout = () => { if (timer == null) { return; } @@ -100,85 +111,66 @@ export function fetchJson(connection: string | ConnectionInfo, json?: string, pr } if (json) { - request.open("POST", url, true); + options.method = "POST"; + options.body = json; headers["content-type"] = { key: "Content-Type", value: "application/json" }; - } else { - request.open("GET", url, true); } + let flatHeaders: { [ key: string ]: string } = { }; Object.keys(headers).forEach((key) => { let header = headers[key]; - request.setRequestHeader(header.key, header.value); + flatHeaders[header.key] = header.value; }); - - request.onreadystatechange = function() { - if (request.readyState !== 4) { return; } - - if (request.status != 200) { - cancelTimeout(); - // @TODO: not any! - let error: any = new Error("invalid response - " + request.status); - error.statusCode = request.status; - if (request.responseText) { - error.responseText = request.responseText; + options.headers = flatHeaders; + + return fetch(url, options).then((response) => { + return response.text().then((body) => { + if (!response.ok) { + errors.throwError("bad response", errors.SERVER_ERROR, { + status: response.status, + body: body, + type: response.type, + url: response.url + }); } - reject(error); - return; - } - let result: any = null; + return body; + }); + + }).then((text) => { + let json: any = null; try { - result = JSON.parse(request.responseText); + json = JSON.parse(text); } catch (error) { - cancelTimeout(); - // @TODO: not any! - let jsonError: any = new Error("invalid json response"); - jsonError.orginialError = error; - jsonError.responseText = request.responseText; - if (json != null) { - jsonError.requestBody = json; - } - jsonError.url = url; - reject(jsonError); - return; + errors.throwError("invalid JSON", errors.SERVER_ERROR, { + body: text, + error: error, + url: url + }); } if (processFunc) { try { - result = processFunc(result); + json = processFunc(json); } catch (error) { - cancelTimeout(); - error.url = url; - error.body = json; - error.responseText = request.responseText; - reject(error); - return; + errors.throwError("processing response error", errors.SERVER_ERROR, { + body: json, + error: error + }); } } + return json; + + }, (error) => { + throw error; + }).then((result) => { cancelTimeout(); resolve(result); - }; - - request.onerror = function(error) { + }, (error) => { cancelTimeout(); reject(error); - } - - try { - if (json != null) { - request.send(json); - } else { - request.send(); - } - - } catch (error) { - cancelTimeout(); - // @TODO: not any! - let connectionError: any = new Error("connection error"); - connectionError.error = error; - reject(connectionError); - } + }); }); }