Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/twenty-dogs-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@onflow/transport-http": minor
---

Add http headers as option to in httpRequest
21 changes: 11 additions & 10 deletions packages/transport-http/src/http-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@ class HTTPRequestError extends Error {
}

/**
* Creates an HTTP Request to be sent to a REST Access API.
* Creates an HTTP Request to be sent to a REST Access API via Fetch 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
* @param {Object} options - Options for the HTTP Request
* @param {String} options.hostname - Access API Hostname
* @param {String} options.path - Path to the resource on the Access API
* @param {String} options.method - HTTP Method
* @param {any} options.body - HTTP Request Body
* @param {Object | Headers} [options.headers] - HTTP Request Headers
*
* @returns JSON object response from Access API.
*/
Expand All @@ -48,6 +47,7 @@ export async function httpRequest({
path,
method,
body,
headers,
retryLimit = 5,
retryIntervalMs = 1000,
}) {
Expand Down Expand Up @@ -81,6 +81,7 @@ export async function httpRequest({
return fetchTransport(`${hostname}${path}`, {
method: method,
body: body ? JSON.stringify(body) : undefined,
headers,
})
.then(async res => {
if (res.ok) {
Expand All @@ -100,13 +101,13 @@ export async function httpRequest({
statusCode: res.status,
})
})
.catch(e => {
.catch(async e => {
if (e instanceof HTTPRequestError) {
throw e
}

// Show AN error for all network errors
logger.log({
await logger.log({
title: "Access Node Error",
message: `The provided access node ${hostname} does not appear to be a valid REST/HTTP access node.
Please verify that you are not unintentionally using a GRPC access node.
Expand Down
102 changes: 102 additions & 0 deletions packages/transport-http/src/http-request.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as fetchTransport from "node-fetch"
import * as logger from "@onflow/util-logger"
import {httpRequest} from "./http-request"

describe("httpRequest", () => {
test("makes valid fetch request", async () => {
const spy = jest.spyOn(fetchTransport, "default")
spy.mockImplementation(async () => ({
ok: true,
status: 200,
body: JSON.stringify({
foo: "bar",
}),
async json() {
return JSON.parse(this.body)
},
}))

const opts = {
hostname: "https://example.com",
path: "/foo/bar",
body: "abc123",
method: "POST",
headers: {
Authorization: "Bearer 1RyXjsFJfU",
},
}

await httpRequest(opts)

await expect(spy).toHaveBeenCalledWith(`${opts.hostname}${opts.path}`, {
method: opts.method,
body: JSON.stringify(opts.body),
headers: opts.headers,
})
})

test("handles http error properly, throws HTTP error", async () => {
const spy = jest.spyOn(fetchTransport, "default")
spy.mockImplementation(async () => ({
ok: false,
body: JSON.stringify({
foo: "bar",
}),
async json() {
return JSON.parse(this.body)
},
status: 400,
statusText: "foo bar",
}))

const opts = {
hostname: "https://example.com",
path: "/foo/bar",
body: "abc123",
method: "POST",
headers: {
Authorization: "Bearer 1RyXjsFJfU",
},
}

await expect(httpRequest(opts)).rejects.toThrow("HTTP Request Error:")
})

test("handles fetch error properly, displays AN error", async () => {
const fetchSpy = jest.spyOn(fetchTransport, "default")

fetchSpy.mockImplementation(() =>
Promise.reject({
ok: false,
body: JSON.stringify({
foo: "bar",
}),
async json() {
return JSON.parse(this.body)
},
status: 400,
statusText: "foo bar",
})
)

const loggerSpy = jest.spyOn(logger, "log")
loggerSpy.mockImplementation(() => {})

const opts = {
hostname: "https://example.com",
path: "/foo/bar",
body: "abc123",
method: "POST",
headers: {
Authorization: "Bearer 1RyXjsFJfU",
},
}

await expect(httpRequest(opts)).rejects.toThrow("HTTP Request Error:")
expect(loggerSpy.mock.calls[0][0].title).toBe("Access Node Error")
})

afterEach(() => {
jest.restoreAllMocks()
})
})