diff --git a/Makefile b/Makefile index 5d02441..9f06519 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,11 @@ npm-test: npm test .PHONY: test -test: npm-test example snippets +test: typecheck npm-test example snippets + +.PHONY: typecheck +typecheck: + npm run typecheck .PHONY: cover cover: diff --git a/README.md b/README.md index 3b78df0..feea673 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ This repository contains Aspose.BarCode Cloud SDK for Node.js source code. 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). +## Requirements + +- Node.js 18 or later (native `fetch` required). + ## How to use the SDK 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). @@ -55,7 +59,7 @@ const config = new Barcode.Configuration( async function generateBarcode(api) { const request = new Barcode.GenerateRequestWrapper( - Barcode.EncodeBarcodeType.Qr, + Barcode.EncodeBarcodeType.Qr, 'Aspose.BarCode for Cloud Sample'); const oneBarcode = await api.generate(request); diff --git a/example.js b/example.js index 7f60991..8983627 100644 --- a/example.js +++ b/example.js @@ -10,7 +10,7 @@ const config = new Barcode.Configuration( async function generateBarcode(api) { const request = new Barcode.GenerateRequestWrapper( - Barcode.EncodeBarcodeType.Qr, + Barcode.EncodeBarcodeType.Qr, 'Aspose.BarCode for Cloud Sample'); const oneBarcode = await api.generate(request); diff --git a/package-lock.json b/package-lock.json index 8cdad2d..925412b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "uuid": "^13.0.0" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@babel/code-frame": { diff --git a/package.json b/package.json index 9228dd8..12a128b 100644 --- a/package.json +++ b/package.json @@ -129,10 +129,11 @@ "dist" ], "engines": { - "node": ">=16" + "node": ">=18" }, "scripts": { "test": "npx jest", + "typecheck": "npx tsc --noEmit", "cover": "npx jest --coverage", "lint": "npx eslint src test snippets", "format": "npx eslint src test snippets eslint.config.mjs --fix", diff --git a/scripts/run_example.sh b/scripts/run_example.sh index f28d1b2..e6825a4 100755 --- a/scripts/run_example.sh +++ b/scripts/run_example.sh @@ -2,6 +2,11 @@ set -euo pipefail +if [[ "${ASPOSE_E2E:-}" != "1" ]]; then + echo "Skipping example: set ASPOSE_E2E=1 to run against live API." + exit 0 +fi + TEST_DIR="demo" rm -rf "${TEST_DIR}" || true diff --git a/scripts/run_snippet.sh b/scripts/run_snippet.sh old mode 100644 new mode 100755 index 868ee56..f8dc539 --- a/scripts/run_snippet.sh +++ b/scripts/run_snippet.sh @@ -8,6 +8,6 @@ SCRIPT_DIR=$3 CONFIG_FILE_PATH=$4 echo "Run snippet file: $FILE_PATH" -node ${SCRIPT_DIR}/insert-credentials.js $FILE_PATH $CONFIG_FILE_PATH $RUN_DIR +node "${SCRIPT_DIR}/insert-credentials.js" "$FILE_PATH" "$CONFIG_FILE_PATH" "$RUN_DIR" -node ./$RUN_DIR/"${FILE_PATH##*/}" || exit 1 \ No newline at end of file +node "./$RUN_DIR/${FILE_PATH##*/}" || exit 1 diff --git a/scripts/run_snippets.sh b/scripts/run_snippets.sh old mode 100644 new mode 100755 index 7df076c..f19208f --- a/scripts/run_snippets.sh +++ b/scripts/run_snippets.sh @@ -29,4 +29,4 @@ popd ${SCRIPT_DIR}/run_snippet.sh "${SNIPPETS_DIR}/manualFetchToken.js" $RUN_DIR $SCRIPT_DIR $CONFIG_FILE_PATH || exit 1; -rm -rf "${RUN_DIR}" || true \ No newline at end of file +rm -rf "${RUN_DIR}" || true diff --git a/snippets/generate/appearance/generateBody.js b/snippets/generate/appearance/generateBody.js index 0bb22fb..2467044 100644 --- a/snippets/generate/appearance/generateBody.js +++ b/snippets/generate/appearance/generateBody.js @@ -41,7 +41,7 @@ async function generateBarcode(api, fileName) { } const genApi = new Barcode.GenerateApi(config); -const fileName = path.resolve('testdata', 'Code39.png'); +const fileName = path.resolve('testdata', 'Code39-red.png'); generateBarcode(genApi, fileName) .then(() => { diff --git a/snippets/generate/set-colorscheme/generateMultipart.js b/snippets/generate/set-colorscheme/generateMultipart.js index 9a671d5..8995b3b 100644 --- a/snippets/generate/set-colorscheme/generateMultipart.js +++ b/snippets/generate/set-colorscheme/generateMultipart.js @@ -36,7 +36,7 @@ function generateBarcode(api, fileName) { } const genApi = new Barcode.GenerateApi(config); -const fileName = path.resolve('testdata', 'Code39.png'); +const fileName = path.resolve('testdata', 'Code39-green.png'); generateBarcode(genApi, fileName) .then(() => { diff --git a/src/Authentication.ts b/src/Authentication.ts index 050d5ee..a95e389 100644 --- a/src/Authentication.ts +++ b/src/Authentication.ts @@ -1,8 +1,9 @@ -import { HttpOptions } from './httpClient'; - export interface Authentication { /** * Apply authentication settings to header and query params. */ - applyToRequestAsync(requestOptions: HttpOptions): Promise; + applyToRequestAsync(requestOptions: { + headers?: Record; + qs?: Record; + }): Promise; } diff --git a/src/JWTAuth.ts b/src/JWTAuth.ts index 2dda176..e8fe7b8 100644 --- a/src/JWTAuth.ts +++ b/src/JWTAuth.ts @@ -1,26 +1,47 @@ import { Configuration } from './Configuration'; import { Authentication } from './Authentication'; -import { HttpClient, HttpOptions } from './httpClient'; +import { ApiErrorResponse } from './models'; + +type AuthResponse = { + statusCode: number; + statusMessage: string; + headers: NodeJS.Dict; + body: any; +}; + +type AuthRejectType = { + response: AuthResponse | null; + errorResponse: ApiErrorResponse | null; + error: Error; +}; export class JWTAuth implements Authentication { private _accessToken?: string; private readonly _configuration: Configuration; - private _client: HttpClient; + private readonly _fetcher: typeof fetch; constructor(configuration: Configuration) { this._configuration = configuration; + const resolvedFetch = (globalThis as { fetch?: typeof fetch }).fetch; + if (!resolvedFetch) { + throw new Error('Global fetch API is not available. Please use Node.js 18+.'); + } + + this._fetcher = resolvedFetch; if (configuration.accessToken) { // Use saved token this._accessToken = configuration.accessToken; } - this._client = new HttpClient(); } /** * Apply authentication settings to header and query params. */ - public async applyToRequestAsync(requestOptions: HttpOptions): Promise { + public async applyToRequestAsync(requestOptions: { + headers?: Record; + qs?: Record; + }): Promise { if (this._accessToken == null) { this._accessToken = await this.requestToken(); } @@ -36,18 +57,120 @@ export class JWTAuth implements Authentication { if (!this._configuration.clientId || !this._configuration.clientSecret) { throw new Error("Required 'clientId' or 'clientSecret' not specified in configuration."); } - const requestOptions: HttpOptions = { - method: 'POST', - uri: this._configuration.tokenUrl, - form: { - grant_type: 'client_credentials', - client_id: this._configuration.clientId, - client_secret: this._configuration.clientSecret, - }, + const requestBody = new URLSearchParams({ + grant_type: 'client_credentials', + client_id: this._configuration.clientId, + client_secret: this._configuration.clientSecret, + }).toString(); + let response: Response; + try { + response = await this._fetcher(this._configuration.tokenUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: requestBody, + }); + } catch (error) { + throw { + response: null, + error: this.normalizeFetchError(error), + errorResponse: null, + } as AuthRejectType; + } + + const responseBody = await response.text(); + const responseInfo: AuthResponse = { + statusCode: response.status, + statusMessage: response.statusText, + headers: this.toHeaderDict(response.headers), + body: responseBody, }; - const result = await this._client.requestAsync(requestOptions); - const parsed = JSON.parse(result.body); + if (!response.ok) { + const rejectObject: AuthRejectType = { + response: responseInfo, + error: new Error( + `Error on '${this._configuration.tokenUrl}': ${response.status} ${response.statusText}` + ), + errorResponse: null, + }; + let errorResponse = null; + try { + errorResponse = JSON.parse(responseBody) as ApiErrorResponse; + } catch (parseError) {} + + if (errorResponse) { + rejectObject.errorResponse = errorResponse; + } else { + rejectObject.error.message += `. ${responseBody}`; + } + throw rejectObject; + } + + const parsed = JSON.parse(responseBody); return parsed.access_token; } + + private toHeaderDict(headers: Headers): NodeJS.Dict { + const normalizedHeaders: NodeJS.Dict = {}; + + headers.forEach((value, key) => { + const existing = normalizedHeaders[key]; + if (existing === undefined) { + normalizedHeaders[key] = value; + return; + } + + if (Array.isArray(existing)) { + existing.push(value); + normalizedHeaders[key] = existing; + return; + } + + normalizedHeaders[key] = [existing, value]; + }); + + return normalizedHeaders; + } + + private normalizeFetchError(error: unknown): Error { + if (error instanceof Error) { + const mutableError = error as Error & { code?: string; cause?: unknown; name: string }; + let normalizedCode = mutableError.code; + + if (!normalizedCode) { + const cause = mutableError.cause; + if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) { + const code = (cause as { code?: string }).code; + if (code) { + normalizedCode = String(code); + } + } + } + + if (!normalizedCode) { + normalizedCode = mutableError.name || 'FETCH_ERROR'; + } + + try { + if (!mutableError.code) { + mutableError.code = normalizedCode; + } + } catch (assignError) {} + + if (mutableError.code) { + return mutableError; + } + + const wrapped = new Error(mutableError.message); + wrapped.name = mutableError.name; + (wrapped as { code?: string }).code = normalizedCode; + return wrapped; + } + + const wrapped = new Error(String(error)); + (wrapped as { code?: string }).code = 'FETCH_ERROR'; + return wrapped; + } } diff --git a/src/api.ts b/src/api.ts index c78e932..346bad7 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,6 +1,5 @@ import { Configuration } from './Configuration'; -import { HttpClient, HttpOptions, HttpResponse, HttpResult } from './httpClient'; -import { Multipart, RequestFile, FormParamsType } from './multipart'; +import { Multipart, RequestFile, FormParamPairs } from './multipart'; export * from './models'; @@ -37,6 +36,224 @@ import { ScanMultipartRequestWrapper, } from './models'; +type StringMap = Record; + +type ApiRequestOptions = { + uri: string; + body?: any; + encoding?: BufferEncoding | null; + form?: StringMap; + headers?: StringMap; + json?: boolean; + method?: string; + qs?: StringMap; +}; + +type ApiResponse = { + statusCode: number; + statusMessage: string; + headers: NodeJS.Dict; + body: any; +}; + +type ApiResult = { + response: ApiResponse; + body: T; +}; + +type ApiRejectType = { + response: ApiResponse | null; + errorResponse: ApiErrorResponse | null; + error: Error; +}; + +export class ApiClient { + private readonly _fetcher: typeof fetch; + + constructor() { + const resolvedFetch = (globalThis as { fetch?: typeof fetch }).fetch; + if (!resolvedFetch) { + throw new Error('Global fetch API is not available. Please use Node.js 18+.'); + } + + this._fetcher = resolvedFetch; + } + + public requestAsync(options: ApiRequestOptions): Promise { + const url: URL = options.qs + ? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri) + : new URL(options.uri); + + const requestBody = this.buildRequestBody(options); + + const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8'; + + const requestOptions: RequestInit = { + method: options.method || 'GET', + headers: options.headers, + }; + + if (requestBody) { + requestOptions.body = requestBody; + } + + return this.doFetchRequest(url, requestOptions, responseEncoding); + } + + private buildRequestBody(options: ApiRequestOptions) { + let requestBody = options.body; + if (options.form) { + // Override requestBody for form with form content + requestBody = new URLSearchParams(options.form).toString(); + options.headers = Object.assign( + { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + options.headers + ); + } + if (options.json) { + // Override requestBody with JSON value + requestBody = JSON.stringify(options.body); + options.headers = Object.assign( + { + 'Content-Type': 'application/json', + }, + options.headers + ); + } + return requestBody; + } + + private async doFetchRequest( + url: URL, + requestOptions: RequestInit, + responseEncoding: BufferEncoding | null + ): Promise { + let response: Response; + try { + response = await this._fetcher(url.toString(), requestOptions); + } catch (error) { + return Promise.reject({ + response: null, + error: this.normalizeFetchError(error), + errorResponse: null, + }); + } + + const respBody = await this.readResponseBody(response, responseEncoding); + const responseHeaders = this.toHeaderDict(response.headers); + + const httpResponse: ApiResponse = { + statusCode: response.status, + statusMessage: response.statusText, + headers: responseHeaders, + body: respBody, + }; + + if (response.ok) { + return { + response: httpResponse, + body: respBody, + }; + } + + const rejectObject: ApiRejectType = { + response: httpResponse, + error: new Error(`Error on '${url}': ${response.status} ${response.statusText}`), + errorResponse: null, + }; + let errorResponse = null; + try { + errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse; + } catch (parseError) {} + + if (errorResponse) { + rejectObject.errorResponse = errorResponse; + } else { + rejectObject.error.message += `. ${respBody}`; + } + + return Promise.reject(rejectObject); + } + + private async readResponseBody( + response: Response, + responseEncoding: BufferEncoding | null + ): Promise { + const arrayBuffer = await response.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + if (responseEncoding === null) { + return buffer; + } + + return buffer.toString(responseEncoding); + } + + private toHeaderDict(headers: Headers): NodeJS.Dict { + const normalizedHeaders: NodeJS.Dict = {}; + + headers.forEach((value, key) => { + const existing = normalizedHeaders[key]; + if (existing === undefined) { + normalizedHeaders[key] = value; + return; + } + + if (Array.isArray(existing)) { + existing.push(value); + normalizedHeaders[key] = existing; + return; + } + + normalizedHeaders[key] = [existing, value]; + }); + + return normalizedHeaders; + } + + private normalizeFetchError(error: unknown): Error { + if (error instanceof Error) { + const mutableError = error as Error & { code?: string; cause?: unknown; name: string }; + let normalizedCode = mutableError.code; + + if (!normalizedCode) { + const cause = mutableError.cause; + if (cause && typeof cause === 'object' && 'code' in (cause as { code?: string })) { + const code = (cause as { code?: string }).code; + if (code) { + normalizedCode = String(code); + } + } + } + + if (!normalizedCode) { + normalizedCode = mutableError.name || 'FETCH_ERROR'; + } + + try { + if (!mutableError.code) { + mutableError.code = normalizedCode; + } + } catch (assignError) {} + + if (mutableError.code) { + return mutableError; + } + + const wrapped = new Error(mutableError.message); + wrapped.name = mutableError.name; + (wrapped as { code?: string }).code = normalizedCode; + return wrapped; + } + + const wrapped = new Error(String(error)); + (wrapped as { code?: string }).code = 'FETCH_ERROR'; + return wrapped; + } +} + let primitives = ['string', 'boolean', 'double', 'integer', 'long', 'float', 'number', 'any']; class ObjectSerializer { @@ -206,11 +423,11 @@ export class GenerateApi { 'x-aspose-client-version': '26.1.0', }; protected _configuration: Configuration; - private _client: HttpClient; + private _client: ApiClient; constructor(configuration: Configuration) { this._configuration = configuration; - this._client = new HttpClient(); + this._client = new ApiClient(); } /** @@ -218,7 +435,7 @@ export class GenerateApi { * @summary Generate barcode using GET request with parameters in route and query string. * @param request GenerateRequestWrapper */ - public async generate(request: GenerateRequestWrapper): Promise<{ response: HttpResponse; body: Buffer }> { + public async generate(request: GenerateRequestWrapper): Promise<{ response: ApiResponse; body: Buffer }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/generate/{barcodeType}'.replace('{' + 'barcodeType' + '}', String(request.barcodeType)); @@ -279,7 +496,7 @@ export class GenerateApi { queryParameters['rotationAngle'] = ObjectSerializer.serialize(request.rotationAngle, 'number'); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'GET', qs: queryParameters, headers: headerParams, @@ -289,7 +506,7 @@ export class GenerateApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -302,7 +519,7 @@ export class GenerateApi { * @summary Generate barcode using POST request with parameters in body in json or xml format. * @param request GenerateBodyRequestWrapper */ - public async generateBody(request: GenerateBodyRequestWrapper): Promise<{ response: HttpResponse; body: Buffer }> { + public async generateBody(request: GenerateBodyRequestWrapper): Promise<{ response: ApiResponse; body: Buffer }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/generate-body'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); @@ -314,7 +531,7 @@ export class GenerateApi { ); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'POST', qs: queryParameters, headers: headerParams, @@ -326,7 +543,7 @@ export class GenerateApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -341,11 +558,11 @@ export class GenerateApi { */ public async generateMultipart( request: GenerateMultipartRequestWrapper - ): Promise<{ response: HttpResponse; body: Buffer }> { + ): Promise<{ response: ApiResponse; body: Buffer }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/generate-multipart'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); - const formParams: FormParamsType = []; + const formParams: FormParamPairs = []; // verify required parameter 'request.barcodeType' is not null or undefined if (request.barcodeType == null) { @@ -395,7 +612,7 @@ export class GenerateApi { if (request.rotationAngle != null) { formParams.push(['rotationAngle', ObjectSerializer.serialize(request.rotationAngle, 'number')]); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'POST', qs: queryParameters, headers: headerParams, @@ -411,7 +628,7 @@ export class GenerateApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -426,11 +643,11 @@ export class RecognizeApi { 'x-aspose-client-version': '26.1.0', }; protected _configuration: Configuration; - private _client: HttpClient; + private _client: ApiClient; constructor(configuration: Configuration) { this._configuration = configuration; - this._client = new HttpClient(); + this._client = new ApiClient(); } /** @@ -440,7 +657,7 @@ export class RecognizeApi { */ public async recognize( request: RecognizeRequestWrapper - ): Promise<{ response: HttpResponse; body: BarcodeResponseList }> { + ): Promise<{ response: ApiResponse; body: BarcodeResponseList }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/recognize'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); @@ -474,7 +691,7 @@ export class RecognizeApi { ); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'GET', qs: queryParameters, headers: headerParams, @@ -483,7 +700,7 @@ export class RecognizeApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -498,7 +715,7 @@ export class RecognizeApi { */ public async recognizeBase64( request: RecognizeBase64RequestWrapper - ): Promise<{ response: HttpResponse; body: BarcodeResponseList }> { + ): Promise<{ response: ApiResponse; body: BarcodeResponseList }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/recognize-body'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); @@ -510,7 +727,7 @@ export class RecognizeApi { ); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'POST', qs: queryParameters, headers: headerParams, @@ -521,7 +738,7 @@ export class RecognizeApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -536,11 +753,11 @@ export class RecognizeApi { */ public async recognizeMultipart( request: RecognizeMultipartRequestWrapper - ): Promise<{ response: HttpResponse; body: BarcodeResponseList }> { + ): Promise<{ response: ApiResponse; body: BarcodeResponseList }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/recognize-multipart'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); - const formParams: FormParamsType = []; + const formParams: FormParamPairs = []; // verify required parameter 'request.barcodeType' is not null or undefined if (request.barcodeType == null) { @@ -571,7 +788,7 @@ export class RecognizeApi { ObjectSerializer.serialize(request.recognitionImageKind, 'RecognitionImageKind'), ]); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'POST', qs: queryParameters, headers: headerParams, @@ -586,7 +803,7 @@ export class RecognizeApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -601,11 +818,11 @@ export class ScanApi { 'x-aspose-client-version': '26.1.0', }; protected _configuration: Configuration; - private _client: HttpClient; + private _client: ApiClient; constructor(configuration: Configuration) { this._configuration = configuration; - this._client = new HttpClient(); + this._client = new ApiClient(); } /** @@ -613,7 +830,7 @@ export class ScanApi { * @summary 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. * @param request ScanRequestWrapper */ - public async scan(request: ScanRequestWrapper): Promise<{ response: HttpResponse; body: BarcodeResponseList }> { + public async scan(request: ScanRequestWrapper): Promise<{ response: ApiResponse; body: BarcodeResponseList }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/scan'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); @@ -627,7 +844,7 @@ export class ScanApi { queryParameters['fileUrl'] = ObjectSerializer.serialize(request.fileUrl, 'string'); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'GET', qs: queryParameters, headers: headerParams, @@ -636,7 +853,7 @@ export class ScanApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -651,7 +868,7 @@ export class ScanApi { */ public async scanBase64( request: ScanBase64RequestWrapper - ): Promise<{ response: HttpResponse; body: BarcodeResponseList }> { + ): Promise<{ response: ApiResponse; body: BarcodeResponseList }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/scan-body'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); @@ -663,7 +880,7 @@ export class ScanApi { ); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'POST', qs: queryParameters, headers: headerParams, @@ -674,7 +891,7 @@ export class ScanApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, @@ -689,18 +906,18 @@ export class ScanApi { */ public async scanMultipart( request: ScanMultipartRequestWrapper - ): Promise<{ response: HttpResponse; body: BarcodeResponseList }> { + ): Promise<{ response: ApiResponse; body: BarcodeResponseList }> { const requestPath = this._configuration.getApiBaseUrl() + '/barcode/scan-multipart'; let queryParameters: any = {}; let headerParams: any = (Object as any).assign({}, this.defaultHeaders); - const formParams: FormParamsType = []; + const formParams: FormParamPairs = []; // verify required parameter 'request.fileBytes' is not null or undefined if (request.fileBytes == null) { throw new Error('Required parameter request.fileBytes was null or undefined when calling scanMultipart.'); } - const requestOptions: HttpOptions = { + const requestOptions: ApiRequestOptions = { method: 'POST', qs: queryParameters, headers: headerParams, @@ -715,7 +932,7 @@ export class ScanApi { await this._configuration.authentication.applyToRequestAsync(requestOptions); - const result: HttpResult = await this._client.requestAsync(requestOptions); + const result: ApiResult = await this._client.requestAsync(requestOptions); return { response: result.response, diff --git a/src/httpClient.ts b/src/httpClient.ts deleted file mode 100644 index 6f15255..0000000 --- a/src/httpClient.ts +++ /dev/null @@ -1,155 +0,0 @@ -import http from 'http'; -import https from 'https'; -import { ApiErrorResponse } from './models'; - -export interface StringKeyWithStringValue { - [key: string]: string; -} - -export interface HttpOptions { - uri: string; - body?: any; - encoding?: BufferEncoding | null; - form?: StringKeyWithStringValue; - headers?: StringKeyWithStringValue; - json?: boolean; - method?: string; - qs?: StringKeyWithStringValue; -} - -export interface HttpResponse { - statusCode: number; - statusMessage: string; - headers: NodeJS.Dict; - body: any; -} - -export interface HttpResult { - response: HttpResponse; - body: any; -} - -export interface HttpRejectType { - response: HttpResponse | null; - errorResponse: ApiErrorResponse | null; - error: Error; -} - -export class HttpClient { - public requestAsync(options: HttpOptions): Promise { - const url: URL = options.qs - ? new URL(`?${new URLSearchParams(options.qs).toString()}`, options.uri) - : new URL(options.uri); - - const requestBody = this.buildRequestBody(options); - - const requestOptions: http.RequestOptions = { - method: options.method, - headers: options.headers, - }; - - const responseEncoding: BufferEncoding | null = options.encoding === null ? null : options.encoding || 'utf-8'; - - return this.doHttpRequest(url, requestBody, requestOptions, responseEncoding); - } - - private buildRequestBody(options: HttpOptions) { - let requestBody = options.body; - if (options.form) { - // Override requestBody for form with form content - requestBody = new URLSearchParams(options.form).toString(); - options.headers = Object.assign( - { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - options.headers - ); - } - if (options.json) { - // Override requestBody with JSON value - requestBody = JSON.stringify(options.body); - options.headers = Object.assign( - { - 'Content-Type': 'application/json', - }, - options.headers - ); - } - return requestBody; - } - - private doHttpRequest( - url: URL, - requestBody: any, - requestOptions: http.RequestOptions, - responseEncoding: BufferEncoding | null - ): Promise { - return new Promise((resolve, reject: (result: HttpRejectType) => void) => { - function requestCallback(res: http.IncomingMessage) { - if (responseEncoding) { - // encoding = null for binary responses - res.setEncoding(responseEncoding); - } - const chunks: any[] | Uint8Array[] = []; - - res.on('data', (chunk) => { - chunks.push(chunk); - }); - - res.on('end', () => { - const respBody = responseEncoding ? chunks.join('') : Buffer.concat(chunks); - - const response: HttpResponse = { - statusCode: res.statusCode!, - statusMessage: res.statusMessage!, - headers: res.headers, - body: respBody, - }; - - if (response.statusCode >= 200 && response.statusCode <= 299) { - resolve({ - response: response, - body: respBody, - }); - } else { - var rejectObject: HttpRejectType = { - response: response, - error: new Error(`Error on '${url}': ${res.statusCode} ${res.statusMessage}`), - errorResponse: null, - }; - var errorResponse = null; - try { - errorResponse = JSON.parse(respBody.toString()) as ApiErrorResponse; - } catch (parseError) {} - - if (errorResponse) { - rejectObject.errorResponse = errorResponse; - } else { - rejectObject.error.message += `. ${respBody}`; - } - reject(rejectObject); - } - }); - } - - const req = - url.protocol === 'http:' - ? http.request(url, requestOptions, requestCallback) - : https.request(url, requestOptions, requestCallback); - - req.on('error', (error) => { - reject({ - response: null, - error: error, - errorResponse: null, - }); - }); - - if (requestBody) { - req.write(requestBody); - } - - req.end(); - }); - } -} diff --git a/src/multipart.ts b/src/multipart.ts index f06a174..5a4aaa3 100644 --- a/src/multipart.ts +++ b/src/multipart.ts @@ -1,7 +1,8 @@ import crypto from 'crypto'; -import { StringKeyWithStringValue } from 'httpClient'; -export interface FormParamsType extends Array> {} +type StringMap = Record; + +export interface FormParamPairs extends Array> {} interface IRequestFile { name: string; @@ -22,9 +23,9 @@ export class RequestFile implements IRequestFile { export class Multipart { readonly boundary: string; readonly body: Buffer; - readonly headers: StringKeyWithStringValue; + readonly headers: StringMap; - constructor(textFields: FormParamsType, files?: IRequestFile[]) { + constructor(textFields: FormParamPairs, files?: IRequestFile[]) { const random = crypto.randomUUID(); this.boundary = '------------------------' + random.replace(/-/g, ''); diff --git a/test/httpClient.test.ts b/test/apiClient.test.ts similarity index 91% rename from test/httpClient.test.ts rename to test/apiClient.test.ts index bade9fc..3ab5c6d 100644 --- a/test/httpClient.test.ts +++ b/test/apiClient.test.ts @@ -1,11 +1,11 @@ import assert from 'assert'; -import { HttpClient } from '../src/httpClient'; +import { ApiClient } from '../src/api'; -describe('httpClient tests', () => { +describe('api client tests', () => { jest.setTimeout(60000); - const client = new HttpClient(); + const client = new ApiClient(); it('should return response', async () => { const response = await client.requestAsync({ diff --git a/test/errorResponse.test.ts b/test/errorResponse.test.ts index 72d5096..61e5ddc 100644 --- a/test/errorResponse.test.ts +++ b/test/errorResponse.test.ts @@ -1,7 +1,6 @@ import assert from 'assert'; import * as Barcode from '../src/api'; -import { HttpRejectType } from '../src/httpClient'; import { LoadTestConfiguration } from './helpers'; describe('Error response tests', () => { @@ -14,10 +13,10 @@ describe('Error response tests', () => { try { await api.generate(request); - assert.fail('Expected HttpRejectType was not thrown'); + assert.fail('Expected API error was not thrown'); } catch (err) { assert.ok(err); - var typedError = err as HttpRejectType; + const typedError = err as { errorResponse?: { error?: { message?: string } } }; assert.ok(typedError); assert.ok(typedError.errorResponse?.error); assert.strictEqual( diff --git a/testdata/Code39.png b/testdata/Code39-green.png similarity index 100% rename from testdata/Code39.png rename to testdata/Code39-green.png diff --git a/testdata/Code39-red.png b/testdata/Code39-red.png new file mode 100644 index 0000000..9c556c4 Binary files /dev/null and b/testdata/Code39-red.png differ