Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate HTTPClient and Kmd to TypeScript #318

Merged
merged 15 commits into from
Mar 31, 2021
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
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"devDependencies": {
"@types/json-bigint": "^1.0.0",
"@types/mocha": "^8.2.1",
"@types/superagent": "^4.1.10",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.17.0",
"assert": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/client/algod.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const client = require('./client');
const { default: HTTPClient } = require('./client');
const { setSendTransactionHeaders } = require('./v2/algod/sendRawTransaction');

function Algod(
Expand All @@ -14,7 +14,7 @@ function Algod(
}

// Get client
const c = new client.HTTPClient(tokenHeader, baseServer, port, headers);
const c = new HTTPClient(tokenHeader, baseServer, port, headers);

/**
* Takes an object and convert its note field to Buffer, if exist.
Expand Down
144 changes: 103 additions & 41 deletions src/client/client.js → src/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
const request = require('superagent');
const utils = require('../utils/utils');
import request from 'superagent';
import * as utils from '../utils/utils';
import IntDecoding from '../types/intDecoding';

function createJSONParser(options) {
// eslint-disable-next-line consistent-return
return (res, fn) => {
if (typeof fn === 'string') {
interface ErrorWithAdditionalInfo extends Error {
rawResponse: string | null;
statusCode: number;
}

function createJSONParser(options: utils.JSONOptions) {
return (
res: request.Response,
// eslint-disable-next-line no-unused-vars
fnOrStr: string | ((err: Error, obj: any) => void)
// eslint-disable-next-line consistent-return
) => {
if (typeof fnOrStr === 'string') {
// in browser
return fn && utils.parseJSON(fn, options);
return fnOrStr && utils.parseJSON(fnOrStr, options);
}

// in node
Expand All @@ -17,29 +27,28 @@ function createJSONParser(options) {
res.text += chunk;
});
res.on('end', () => {
let body;
let err;
let body: any;
let err: ErrorWithAdditionalInfo | null;
try {
body = res.text && utils.parseJSON(res.text, options);
} catch (err_) {
err = err_;
// issue #675: return the raw response if the response parsing fails
err.rawResponse = res.text || null;
// issue #876: return the http status code if the response parsing fails
err.statusCode = res.statusCode;
err.statusCode = res.status;
} finally {
fn(err, body);
fnOrStr(err, body);
}
});
};
}

/**
* removeEmpty gets a dictionary and removes empty values
* Remove falsy values or values with a length of 0 from an object.
* @param obj
* @returns {*}
*/
function removeEmpty(obj) {
function removeFalsyOrEmpty(obj: Record<string, any>) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// eslint-disable-next-line no-param-reassign
Expand All @@ -49,13 +58,21 @@ function removeEmpty(obj) {
return obj;
}

type Query<F> = {
format?: F;
[key: string]: any;
};

/**
* getAccceptFormat returns the correct Accept header depending on the
* getAcceptFormat returns the correct Accept header depending on the
* requested format.
* @param query
* @returns {string}
*/
function getAccceptFormat(query) {
/* eslint-disable no-redeclare,no-unused-vars */
function getAcceptFormat(
query?: Query<'msgpack' | 'json'>
): 'application/msgpack' | 'application/json' {
/* eslint-enable no-redeclare,no-unused-vars */
if (
query !== undefined &&
Object.prototype.hasOwnProperty.call(query, 'format')
Expand All @@ -64,22 +81,54 @@ function getAccceptFormat(query) {
case 'msgpack':
return 'application/msgpack';
case 'json':
return 'application/json';
default:
return 'application/json';
}
} else return 'application/json';
}

function HTTPClient(token, baseServer, port, headers = {}) {
// Do not need colon if port is empty
let baseServerWithPort = baseServer;
if (port !== '') {
baseServerWithPort += `:${port.toString()}`;
export interface AlgodTokenHeader {
'X-Algo-API-Token': string;
}

export interface IndexerTokenHeader {
'X-Indexer-API-Token': string;
}

export interface KMDTokenHeader {
'X-KMD-API-Token': string;
}

export interface CustomTokenHeader {
[headerName: string]: string;
}

export type TokenHeader =
| AlgodTokenHeader
| IndexerTokenHeader
| KMDTokenHeader
| CustomTokenHeader;

export default class HTTPClient {
private address: string;
private tokenHeader: TokenHeader;
public intDecoding: IntDecoding = IntDecoding.DEFAULT;

constructor(
tokenHeader: TokenHeader,
baseServer: string,
port?: number,
private defaultHeaders: Record<string, any> = {}
) {
// Do not need colon if port is empty
let baseServerWithPort = baseServer;
if (typeof port !== 'undefined') {
baseServerWithPort += `:${port.toString()}`;
}
this.address = baseServerWithPort;
this.defaultHeaders = defaultHeaders;
this.tokenHeader = tokenHeader;
}
this.address = baseServerWithPort;
this.token = token;
this.defaultHeaders = headers;

/**
* Send a GET request.
Expand All @@ -88,25 +137,30 @@ function HTTPClient(token, baseServer, port, headers = {}) {
* @param {object} requestHeaders An object containing additional request headers to use.
* @param {object} jsonOptions Options object to use to decode JSON responses. See
* utils.parseJSON for the options available.
* @returns {Promise<object>} Response object.
* @returns Response object.
*/
this.get = async (path, query, requestHeaders = {}, jsonOptions = {}) => {
const format = getAccceptFormat(query);
async get(
path: string,
query?: Query<any>,
requestHeaders: Record<string, any> = {},
jsonOptions: utils.JSONOptions = {}
) {
const format = getAcceptFormat(query);
let r = request
.get(this.address + path)
.set(this.token)
.set(this.tokenHeader)
.set(this.defaultHeaders)
.set(requestHeaders)
.set('Accept', format)
.query(removeEmpty(query));
.query(removeFalsyOrEmpty(query));

if (format === 'application/msgpack') {
r = r.responseType('arraybuffer');
} else if (
format === 'application/json' &&
Object.keys(jsonOptions).length !== 0
) {
if (r.buffer !== r.ca) {
if (utils.isNode()) {
// in node, need to set buffer
r = r.buffer(true);
}
Expand All @@ -123,23 +177,31 @@ function HTTPClient(token, baseServer, port, headers = {}) {
res.body = underlyingArrayBuffer.slice(start, end);
}
return res;
};
}

this.post = async (path, data, requestHeaders = {}) =>
request
async post(
path: string,
data: string | object,
requestHeaders: Record<string, any> = {}
) {
return request
.post(this.address + path)
.set(this.token)
.set(this.tokenHeader)
.set(this.defaultHeaders)
.set(requestHeaders)
.send(data);
}

this.delete = async (path, data, requestHeaders = {}) =>
request
async delete(
path: string,
data: string | object,
requestHeaders: Record<string, any> = {}
) {
return request
.delete(this.address + path)
.set(this.token)
.set(this.tokenHeader)
.set(this.defaultHeaders)
.set(requestHeaders)
.send(data);
}
}

module.exports = { HTTPClient };
Loading