Skip to content

Commit 52b24b5

Browse files
Ivan KamkinIvan Kamkin
authored andcommitted
Rewrite httpClient to fetch api
1 parent 749366a commit 52b24b5

4 files changed

Lines changed: 161 additions & 73 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ This repository contains Aspose.BarCode Cloud SDK for Node.js source code.
2626

2727
To use these SDKs, you will need Client Id and Client Secret which can be looked up at [Aspose Cloud Dashboard](https://dashboard.aspose.cloud/applications) (free registration in Aspose Cloud is required for this).
2828

29+
## Requirements
30+
31+
+ Node.js 18 or later (native `fetch` required).
32+
2933
## How to use the SDK
3034

3135
The complete source code is available in this repository folder. You can either directly use it in your project via source code or get [nmpjs distribution](https://www.npmjs.com/package/aspose-barcode-cloud-node) (recommended).
@@ -126,4 +130,3 @@ RecognizeApi | [**recognizeMultipart**](docs/index.md#recognizemultipart) | **PO
126130
ScanApi | [**scan**](docs/index.md#scan) | **GET** /barcode/scan | Scan barcode from file on server in the Internet using GET requests with parameter in query string. For scaning files from your hard drive use `scan-body` or `scan-multipart` endpoints instead.
127131
ScanApi | [**scanBase64**](docs/index.md#scanbase64) | **POST** /barcode/scan-body | Scan barcode from file in request body using POST requests with parameter in body in json or xml format.
128132
ScanApi | [**scanMultipart**](docs/index.md#scanmultipart) | **POST** /barcode/scan-multipart | Scan barcode from file in request body using POST requests with parameter in multipart form.
129-

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
"dist"
130130
],
131131
"engines": {
132-
"node": ">=16"
132+
"node": ">=18"
133133
},
134134
"scripts": {
135135
"test": "npx jest",

src/httpClient.ts

Lines changed: 155 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import http from 'http';
2-
import https from 'https';
31
import { ApiErrorResponse } from './models';
42

53
export interface StringKeyWithStringValue {
@@ -35,6 +33,26 @@ export interface HttpRejectType {
3533
error: Error;
3634
}
3735

36+
interface FetchHeaders {
37+
forEach(callback: (value: string, key: string) => void): void;
38+
}
39+
40+
interface FetchResponse {
41+
status: number;
42+
statusText: string;
43+
headers: FetchHeaders;
44+
ok: boolean;
45+
arrayBuffer(): Promise<ArrayBuffer>;
46+
}
47+
48+
interface FetchRequestInit {
49+
method?: string;
50+
headers?: StringKeyWithStringValue;
51+
body?: any;
52+
}
53+
54+
type Fetcher = (input: string | URL, init?: FetchRequestInit) => Promise<FetchResponse>;
55+
3856
export class HttpClient {
3957
public requestAsync(options: HttpOptions): Promise<HttpResult> {
4058
const url: URL = options.qs
@@ -43,14 +61,18 @@ export class HttpClient {
4361

4462
const requestBody = this.buildRequestBody(options);
4563

46-
const requestOptions: http.RequestOptions = {
47-
method: options.method,
64+
const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';
65+
66+
const requestOptions: FetchRequestInit = {
67+
method: options.method || 'GET',
4868
headers: options.headers,
4969
};
5070

51-
const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8';
71+
if (requestBody) {
72+
requestOptions.body = requestBody;
73+
}
5274

53-
return this.doHttpRequest(url, requestBody, requestOptions, responseEncoding);
75+
return this.doFetchRequest(url, requestOptions, responseEncoding);
5476
}
5577

5678
private buildRequestBody(options: HttpOptions) {
@@ -78,78 +100,141 @@ export class HttpClient {
78100
return requestBody;
79101
}
80102

81-
private doHttpRequest(
103+
private async doFetchRequest(
82104
url: URL,
83-
requestBody: any,
84-
requestOptions: http.RequestOptions,
105+
requestOptions: FetchRequestInit,
85106
responseEncoding: BufferEncoding | null
86107
): Promise<HttpResult> {
87-
return new Promise((resolve, reject: (result: HttpRejectType) => void) => {
88-
function requestCallback(res: http.IncomingMessage) {
89-
if (responseEncoding) {
90-
// encoding = null for binary responses
91-
res.setEncoding(responseEncoding);
92-
}
93-
const chunks: any[] | Uint8Array[] = [];
94-
95-
res.on('data', (chunk) => {
96-
chunks.push(chunk);
97-
});
98-
99-
res.on('end', () => {
100-
const respBody = responseEncoding ? chunks.join('') : Buffer.concat(chunks);
101-
102-
const response: HttpResponse = {
103-
statusCode: res.statusCode!,
104-
statusMessage: res.statusMessage!,
105-
headers: res.headers,
106-
body: respBody,
107-
};
108-
109-
if (response.statusCode >= 200 && response.statusCode <= 299) {
110-
resolve({
111-
response: response,
112-
body: respBody,
113-
});
114-
} else {
115-
var rejectObject: HttpRejectType = {
116-
response: response,
117-
error: new Error(`Error on '${url}': ${res.statusCode} ${res.statusMessage}`),
118-
errorResponse: null,
119-
};
120-
var errorResponse = null;
121-
try {
122-
errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse;
123-
} catch (parseError) {}
124-
125-
if (errorResponse) {
126-
rejectObject.errorResponse = errorResponse;
127-
} else {
128-
rejectObject.error.message += `. ${respBody}`;
129-
}
130-
reject(rejectObject);
108+
const fetcher = this.getFetch();
109+
let response: FetchResponse;
110+
try {
111+
response = await fetcher(url.toString(), requestOptions);
112+
} catch (error) {
113+
return Promise.reject({
114+
response: null,
115+
error: this.normalizeFetchError(error),
116+
errorResponse: null,
117+
});
118+
}
119+
120+
const respBody = await this.readResponseBody(response, responseEncoding);
121+
const responseHeaders = this.toHeaderDict(response.headers);
122+
123+
const httpResponse: HttpResponse = {
124+
statusCode: response.status,
125+
statusMessage: response.statusText,
126+
headers: responseHeaders,
127+
body: respBody,
128+
};
129+
130+
if (response.ok) {
131+
return {
132+
response: httpResponse,
133+
body: respBody,
134+
};
135+
}
136+
137+
const rejectObject: HttpRejectType = {
138+
response: httpResponse,
139+
error: new Error(`Error on '${url}': ${response.status} ${response.statusText}`),
140+
errorResponse: null,
141+
};
142+
let errorResponse = null;
143+
try {
144+
errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse;
145+
} catch (parseError) {}
146+
147+
if (errorResponse) {
148+
rejectObject.errorResponse = errorResponse;
149+
} else {
150+
rejectObject.error.message += `. ${respBody}`;
151+
}
152+
153+
return Promise.reject(rejectObject);
154+
}
155+
156+
private async readResponseBody(
157+
response: FetchResponse,
158+
responseEncoding: BufferEncoding | null
159+
): Promise<string | Buffer> {
160+
const arrayBuffer = await response.arrayBuffer();
161+
const buffer = Buffer.from(arrayBuffer);
162+
163+
if (responseEncoding === null) {
164+
return buffer;
165+
}
166+
167+
return buffer.toString(responseEncoding);
168+
}
169+
170+
private toHeaderDict(headers: FetchHeaders): NodeJS.Dict<string | string[]> {
171+
const normalizedHeaders: NodeJS.Dict<string | string[]> = {};
172+
173+
headers.forEach((value, key) => {
174+
const existing = normalizedHeaders[key];
175+
if (existing === undefined) {
176+
normalizedHeaders[key] = value;
177+
return;
178+
}
179+
180+
if (Array.isArray(existing)) {
181+
existing.push(value);
182+
normalizedHeaders[key] = existing;
183+
return;
184+
}
185+
186+
normalizedHeaders[key] = [existing, value];
187+
});
188+
189+
return normalizedHeaders;
190+
}
191+
192+
private getFetch(): Fetcher {
193+
const fetcher = (globalThis as { fetch?: Fetcher }).fetch;
194+
if (!fetcher) {
195+
throw new Error('Global fetch API is not available. Please use Node.js 18+.');
196+
}
197+
198+
return fetcher;
199+
}
200+
201+
private normalizeFetchError(error: unknown): Error {
202+
if (error instanceof Error) {
203+
const mutableError = error as Error & { code?: string; cause?: unknown; name: string };
204+
let normalizedCode = mutableError.code;
205+
206+
if (!normalizedCode) {
207+
const cause = mutableError.cause;
208+
if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) {
209+
const code = (cause as { code?: string }).code;
210+
if (code) {
211+
normalizedCode = String(code);
131212
}
132-
});
213+
}
133214
}
134215

135-
const req =
136-
url.protocol === 'http:'
137-
? http.request(url, requestOptions, requestCallback)
138-
: https.request(url, requestOptions, requestCallback);
139-
140-
req.on('error', (error) => {
141-
reject({
142-
response: null,
143-
error: error,
144-
errorResponse: null,
145-
});
146-
});
216+
if (!normalizedCode) {
217+
normalizedCode = mutableError.name || 'FETCH_ERROR';
218+
}
219+
220+
try {
221+
if (!mutableError.code) {
222+
mutableError.code = normalizedCode;
223+
}
224+
} catch (assignError) {}
147225

148-
if (requestBody) {
149-
req.write(requestBody);
226+
if (mutableError.code) {
227+
return mutableError;
150228
}
151229

152-
req.end();
153-
});
230+
const wrapped = new Error(mutableError.message);
231+
wrapped.name = mutableError.name;
232+
(wrapped as { code?: string }).code = normalizedCode;
233+
return wrapped;
234+
}
235+
236+
const wrapped = new Error(String(error));
237+
(wrapped as { code?: string }).code = 'FETCH_ERROR';
238+
return wrapped;
154239
}
155240
}

src/multipart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import crypto from 'crypto';
2-
import { StringKeyWithStringValue } from 'httpClient';
2+
import { StringKeyWithStringValue } from './httpClient';
33

44
export interface FormParamsType extends Array<Array<string>> {}
55

0 commit comments

Comments
 (0)