1- import * as http from 'http' ;
2- import * as https from 'https' ;
31import { version } from '../package.json' ;
42import Transaction from './request/transaction' ;
53import TransactionReport from './request/transaction-report' ;
@@ -13,6 +11,11 @@ interface ResponseError {
1311
1412type servicePath = 'factors' | 'insights' | 'score' | 'transactions/report' ;
1513
14+ const invalidResponseBody = {
15+ code : 'INVALID_RESPONSE_BODY' ,
16+ error : 'Received an invalid or unparseable response body' ,
17+ } ;
18+
1619export default class WebServiceClient {
1720 private accountID : string ;
1821 private host : string ;
@@ -65,7 +68,7 @@ export default class WebServiceClient {
6568 // eslint-disable-next-line @typescript-eslint/no-explicit-any
6669 modelClass ?: any
6770 ) : Promise < T > ;
68- private responseFor (
71+ private async responseFor (
6972 path : servicePath ,
7073 postData : string ,
7174 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -74,97 +77,94 @@ export default class WebServiceClient {
7477 const parsedPath = `/minfraud/v2.0/${ path } ` ;
7578 const url = `https://${ this . host } ${ parsedPath } ` ;
7679
77- const options = {
78- auth : `${ this . accountID } :${ this . licenseKey } ` ,
80+ const controller = new AbortController ( ) ;
81+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , this . timeout ) ;
82+
83+ const options : RequestInit = {
84+ body : postData ,
7985 headers : {
8086 Accept : 'application/json' ,
81- 'Content-Length' : Buffer . byteLength ( postData ) ,
87+ Authorization :
88+ 'Basic ' +
89+ Buffer . from ( `${ this . accountID } :${ this . licenseKey } ` ) . toString (
90+ 'base64'
91+ ) ,
92+ 'Content-Length' : Buffer . byteLength ( postData ) . toString ( ) ,
8293 'Content-Type' : 'application/json' ,
8394 'User-Agent' : `minfraud-api-node/${ version } ` ,
8495 } ,
85- host : this . host ,
8696 method : 'POST' ,
87- path : parsedPath ,
88- timeout : this . timeout ,
97+ signal : controller . signal ,
8998 } ;
9099
91- return new Promise ( ( resolve , reject ) => {
92- const req = https . request ( options , ( response ) => {
93- let data = '' ;
94-
95- response . on ( 'data' , ( chunk ) => {
96- data += chunk ;
97- } ) ;
98-
99- response . on ( 'end' , ( ) => {
100- if ( response . statusCode && response . statusCode === 204 ) {
101- return resolve ( ) ;
102- }
103-
104- try {
105- data = JSON . parse ( data ) ;
106- } catch {
107- return reject ( this . handleError ( { } , response , url ) ) ;
108- }
109-
110- if ( response . statusCode && response . statusCode !== 200 ) {
111- return reject (
112- this . handleError ( data as ResponseError , response , url )
113- ) ;
114- }
115-
116- return resolve ( new modelClass ( data ) ) ;
117- } ) ;
118- } ) ;
119-
120- req . on ( 'error' , ( err : NodeJS . ErrnoException ) => {
121- return reject ( {
122- code : err . code ,
123- error : err . message ,
124- url,
125- } as WebServiceClientError ) ;
126- } ) ;
127-
128- req . write ( postData ) ;
129-
130- req . end ( ) ;
131- } ) ;
100+ let data ;
101+ try {
102+ const response = await fetch ( url , options ) ;
103+
104+ if ( ! response . ok ) {
105+ return Promise . reject ( await this . handleError ( response , url ) ) ;
106+ }
107+
108+ if ( response . status === 204 ) {
109+ return Promise . resolve ( ) ;
110+ }
111+ data = await response . json ( ) ;
112+ } catch ( err ) {
113+ const error = err as TypeError ;
114+ switch ( error . name ) {
115+ case 'AbortError' :
116+ return Promise . reject ( {
117+ code : 'NETWORK_TIMEOUT' ,
118+ error : 'The request timed out' ,
119+ url,
120+ } ) ;
121+ case 'SyntaxError' :
122+ return Promise . reject ( {
123+ ...invalidResponseBody ,
124+ url,
125+ } ) ;
126+ default :
127+ return Promise . reject ( {
128+ code : 'FETCH_ERROR' ,
129+ error : `${ error . name } - ${ error . message } ` ,
130+ url,
131+ } ) ;
132+ }
133+ } finally {
134+ clearTimeout ( timeoutId ) ;
135+ }
136+ return new modelClass ( data ) ;
132137 }
133138
134- private handleError (
135- data : ResponseError ,
136- response : http . IncomingMessage ,
139+ private async handleError (
140+ response : Response ,
137141 url : string
138- ) : WebServiceClientError {
139- if (
140- response . statusCode &&
141- response . statusCode >= 500 &&
142- response . statusCode < 600
143- ) {
142+ ) : Promise < WebServiceClientError > {
143+ if ( response . status && response . status >= 500 && response . status < 600 ) {
144144 return {
145145 code : 'SERVER_ERROR' ,
146- error : `Received a server error with HTTP status code: ${ response . statusCode } ` ,
146+ error : `Received a server error with HTTP status code: ${ response . status } ` ,
147147 url,
148148 } ;
149149 }
150150
151- if (
152- response . statusCode &&
153- ( response . statusCode < 400 || response . statusCode >= 600 )
154- ) {
151+ if ( response . status && ( response . status < 400 || response . status >= 600 ) ) {
155152 return {
156153 code : 'HTTP_STATUS_CODE_ERROR' ,
157- error : `Received an unexpected HTTP status code: ${ response . statusCode } ` ,
154+ error : `Received an unexpected HTTP status code: ${ response . status } ` ,
158155 url,
159156 } ;
160157 }
161158
162- if ( ! data . code || ! data . error ) {
163- return {
164- code : 'INVALID_RESPONSE_BODY' ,
165- error : 'Received an invalid or unparseable response body' ,
166- url,
167- } ;
159+ let data ;
160+ try {
161+ data = ( await response . json ( ) ) as ResponseError ;
162+
163+ if ( ! data . code || ! data . error ) {
164+ return { ...invalidResponseBody , url } ;
165+ }
166+ } catch {
167+ return { ...invalidResponseBody , url } ;
168168 }
169169
170170 return { ...data , url } as WebServiceClientError ;
0 commit comments