Skip to content

Commit

Permalink
Merge branch 'main' into 1049--bug---rpc-eth_getlogs-does-return-only…
Browse files Browse the repository at this point in the history
…-first-256-logs
  • Loading branch information
fabiorigam authored Jul 23, 2024
2 parents be0be20 + 03308e0 commit 219fde7
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 423 deletions.
3 changes: 1 addition & 2 deletions apps/sdk-node-integration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
},
"dependencies": {
"@vechain/sdk-network": "1.0.0-beta.22",
"axios": "^1.6.8",
"typescript": "^5.3.3"
},
"devDependencies": {
Expand All @@ -22,4 +21,4 @@
"ts-node": "^10.9.2",
"tsup": "^8.0.2"
}
}
}
14 changes: 12 additions & 2 deletions apps/sdk-node-integration/src/vechain-transaction-logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ThorClient, Poll, EventPoll, FilterTransferLogsOptions, TransferLogs } from '@vechain/sdk-network';
import axios from 'axios';

/**
* The `VeChainTransactionLogger` class provides methods to monitor the transactions of an account
Expand Down Expand Up @@ -96,7 +95,18 @@ class VechainTransactionLogger {
private async notifyWebhook(newLogs: TransferLogs[]): Promise<void> {
try {
// Make an HTTP POST request to the webhook URL with the new transaction data
await axios.post(this.webhookUrl as string, newLogs);
const response = await fetch(this.webhookUrl as string, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newLogs),
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

console.log('Webhook notification sent successfully');
} catch (error) {
console.error('Error sending webhook notification:', error);
Expand Down
257 changes: 129 additions & 128 deletions apps/sdk-node-integration/yarn.lock

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions packages/network/jest.browser-setup.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
require('whatwg-fetch');

const fetchMock = require('jest-fetch-mock');

// Don't auto-enable mocks
fetchMock.dontMock();

// Jest configuration for WebSocket mocking based on environment
if (typeof window === 'undefined') {
// Running in Node.js environment
Expand All @@ -6,3 +13,6 @@ if (typeof window === 'undefined') {
// Running in browser environment
global.WebSocket = window.WebSocket;
}

// Make fetch global
global.fetch = fetch;
2 changes: 0 additions & 2 deletions packages/network/jest.config.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ module.exports = {
setupFiles: ['./jest.browser-setup.js'],
testPathIgnorePatterns: [
'tests/utils/poll/event/event-poll.unit.test.ts',
'tests/utils/http/http-client.solo.test.ts',
'http-client.testnet.test.ts'
],
coverageReporters: ['html', 'lcov', 'json'],
runner: 'groups',
Expand Down
7 changes: 5 additions & 2 deletions packages/network/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@
"@vechain/sdk-errors": "1.0.0-beta.22",
"@vechain/sdk-logging": "1.0.0-beta.22",
"abitype": "^1.0.5",
"axios": "^1.7.2",
"isomorphic-ws": "^5.0.0",
"ws": "^8.18.0"
},
"devDependencies": {
"jest-fetch-mock": "^3.0.3",
"whatwg-fetch": "^3.6.20"
}
}
}
50 changes: 0 additions & 50 deletions packages/network/src/utils/helpers/errors.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/network/src/utils/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './errors';
export * from './request';
120 changes: 74 additions & 46 deletions packages/network/src/utils/http/http-client.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,114 @@
import Axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios';
import { Agent as HttpAgent } from 'http';
import { Agent as HttpsAgent } from 'https';
import { type HttpClientOptions, type HttpParams } from './types';
import { convertError, DEFAULT_HTTP_TIMEOUT } from '../index';
import { DEFAULT_HTTP_TIMEOUT } from '../index';
import { buildError, HTTP_CLIENT } from '@vechain/sdk-errors';

/**
* Represents a concrete implementation of the `IHttpClient` interface, providing methods for making HTTP requests.
*
* This class leverages Axios for handling HTTP requests and allows for interaction with HTTP services.
* This class leverages the Fetch API for handling HTTP requests and allows for interaction with HTTP services.
* It is configured with a base URL and request timeout upon instantiation.
*/
class HttpClient {
/**
* Axios instance to make http requests
*/
protected readonly axios: AxiosInstance;
private readonly timeout: number;

/**
* Instantiates an `HttpClient` object with a specified base URL and HTTP request timeout.
* Instantiates an HttpClient object with a specified base URL and HTTP request timeout.
*
* @param baseURL - The base URL for all network requests.
* @param options - (Optional) An object containing additional configuration options for the HTTP client, such as a custom Axios instance and a request timeout.
* @param options - (Optional) An object containing additional configuration options for the HTTP client, such as a request timeout.
*/
constructor(
readonly baseURL: string,
options?: HttpClientOptions
) {
this.axios =
options?.axiosInstance ??
Axios.create({
httpAgent: new HttpAgent({ keepAlive: false }),
httpsAgent: new HttpsAgent({ keepAlive: false }),
baseURL,
timeout: options?.timeout ?? DEFAULT_HTTP_TIMEOUT
});
this.timeout = options?.timeout ?? DEFAULT_HTTP_TIMEOUT;
}

/**
* Sends an HTTP request using the Axios library.
* Sends an HTTP request using the Fetch API.
*
* @param method - The HTTP method to be used ('GET' or 'POST').
* @param path - The path to access on the server relative to the base URL.
* @param params - (Optional) Additional request parameters such as query parameters, request body, and custom headers.
* @returns A promise that resolves to the response data from the HTTP request.
* @throws {HTTPClientError} Will throw an error if the request fails, with more detailed information if the error is Axios-specific.
* @throws {HTTPClientError} Will throw an error if the request fails.
*/
public async http(
method: 'GET' | 'POST',
path: string,
params?: HttpParams
): Promise<unknown> {
const config: AxiosRequestConfig = {
let url: URL;
try {
url = new URL(path, this.baseURL);
} catch (error) {
throw buildError(
'INVALID_HTTP_REQUEST',
HTTP_CLIENT.INVALID_HTTP_REQUEST,
`Invalid URL: ${this.baseURL}${path}`,
{
method,
url: `${this.baseURL}${path}`,
message: 'Request failed'
}
);
}

if (params?.query != null) {
Object.entries(params.query).forEach(([key, value]) => {
url.searchParams.append(key, String(value));
});
}

const config: RequestInit = {
method,
url: path,
data: params?.body,
headers: params?.headers,
params: params?.query
headers: params?.headers as HeadersInit,
body: method !== 'GET' ? JSON.stringify(params?.body) : undefined
};

const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, this.timeout);

try {
const resp = await this.axios(config);
this.validateResponseHeader(params, resp.headers);
return resp.data as unknown;
} catch (err) {
if (Axios.isAxiosError(err)) {
throw convertError(err);
const response = await fetch(url.toString(), {
...config,
signal: controller.signal
});

clearTimeout(timeoutId);

if (!response.ok) {
throw buildError(
'INVALID_HTTP_REQUEST',
HTTP_CLIENT.INVALID_HTTP_REQUEST,
`HTTP error! status: ${response.status}`,
{
method,
url: url.toString(),
status: response.status
}
);
}
// If it's not an Axios error, re-throw the original error

this.validateResponseHeader(
params,
Object.fromEntries(response.headers.entries())
);

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return await response.json();
} catch (err) {
throw buildError(
'http',
'INVALID_HTTP_REQUEST',
HTTP_CLIENT.INVALID_HTTP_REQUEST,
'HTTP request failed: Check method, path, and parameters for validity.',
{ method, path, params },
err
`Invalid URL: ${this.baseURL}${path}`,
{
method,
url: url.toString(),
message: 'Request failed'
}
);
}
}
Expand All @@ -86,17 +121,10 @@ class HttpClient {
*/
private validateResponseHeader(
params?: HttpParams,
headers?: Record<string, unknown>
headers?: Record<string, string>
): void {
if (params?.validateResponseHeader != null && headers != null) {
const responseHeaders: Record<string, string> = {};
for (const key in headers) {
const value = headers[key];
if (typeof value === 'string') {
responseHeaders[key] = value;
}
}
params.validateResponseHeader(responseHeaders);
params.validateResponseHeader(headers);
}
}
}
Expand Down
8 changes: 0 additions & 8 deletions packages/network/src/utils/http/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { type AxiosInstance } from 'axios';

/**
* Represents the parameters for making an HTTP request.
*
Expand Down Expand Up @@ -37,12 +35,6 @@ interface HttpClientOptions {
* The timeout for an HTTP request, in milliseconds.
*/
timeout?: number;

/**
* An Axios instance to use for sending HTTP requests.
* This is useful for customizing the HTTP client, such as adding a custom user agent.
*/
axiosInstance?: AxiosInstance;
}

export type { HttpParams, HttpClientOptions };
4 changes: 2 additions & 2 deletions packages/network/tests/thor-client/logs/logs.testnet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('ThorClient - Logs Module', () => {
test('filterEventLogs', async () => {
const eventLogs =
await thorClient.logs.filterRawEventLogs(argFilterEventLogs);
expect(eventLogs).toStrictEqual(expectedFilterEventLogs);
expect(eventLogs).toEqual(expectedFilterEventLogs);
}, 3000);

/**
Expand All @@ -37,6 +37,6 @@ describe('ThorClient - Logs Module', () => {
argFilterTransferLogs
);
//
expect(transferLogs).toStrictEqual(expectedFilterTransferLogs);
expect(transferLogs).toEqual(expectedFilterTransferLogs);
}, 3000);
});
12 changes: 12 additions & 0 deletions packages/network/tests/thor-client/nodes/nodes.testnet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ describe('ThorClient - Nodes Module', () => {
);
}, 5000);

test('null or empty URL or blank URL', async () => {
let thorClient = ThorClient.fromUrl('');
await expect(thorClient.nodes.isHealthy()).rejects.toThrow(
HTTPClientError
);

thorClient = ThorClient.fromUrl(' ');
await expect(thorClient.nodes.isHealthy()).rejects.toThrow(
HTTPClientError
);
});

test('invalid URL', async () => {
/**
* client required to access a node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,7 @@ describe('ThorClient - Transactions Module', () => {

expect(txReceipt).toBeDefined();
expect(txReceipt?.reverted).toBe(expectedReceipt.reverted);
expect(txReceipt?.outputs).toStrictEqual(
expectedReceipt.outputs
);
expect(txReceipt?.outputs).toEqual(expectedReceipt.outputs);
expect(txReceipt?.gasUsed).toBe(expectedReceipt.gasUsed);
expect(txReceipt?.gasPayer).toBe(expectedReceipt.gasPayer);
expect(sendTransactionResult.id).toBe(txReceipt?.meta.txID);
Expand Down
Loading

0 comments on commit 219fde7

Please sign in to comment.