1+ import {
2+ global ,
3+ XHR_STATES
4+ } from "./constants"
5+
6+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
7+ type Headers = Record < string , string >
8+ type RequestBody = string | null
9+ type AjaxCallback = ( response : any ) => void
10+ type TimeoutCallback = ( ) => void
11+
12+ interface XDomainRequest {
13+ timeout : number
14+ open ( method : string , url : string ) : void
15+ send ( body ?: string | null ) : void
16+ onload : ( ( ) => void ) | null
17+ ontimeout : ( ( ) => void ) | null
18+ onprogress : ( ( ) => void ) | null
19+ responseText : string
20+ }
21+
22+ interface XMLHttpRequestLike {
23+ open ( method : string , url : string , async ?: boolean ) : void
24+ send ( body ?: string | null ) : void
25+ setRequestHeader ( name : string , value : string ) : void
26+ timeout : number
27+ readyState : number
28+ responseText : string
29+ onreadystatechange : ( ( ) => void ) | null
30+ onerror : ( ( ) => void ) | null
31+ ontimeout : ( ( ) => void ) | null
32+ }
33+
34+ export default class Ajax {
35+ static request (
36+ method : HttpMethod ,
37+ endPoint : string ,
38+ headers : Headers ,
39+ body : RequestBody ,
40+ timeout : number ,
41+ ontimeout : TimeoutCallback | null ,
42+ callback : AjaxCallback | null
43+ ) : XMLHttpRequestLike | XDomainRequest | AbortController {
44+ if ( global . XDomainRequest ) {
45+ let req = new global . XDomainRequest ( ) // IE8, IE9
46+ return this . xdomainRequest ( req , method , endPoint , body , timeout , ontimeout , callback )
47+ } else if ( global . XMLHttpRequest ) {
48+ let req = new global . XMLHttpRequest ( ) // IE7+, Firefox, Chrome, Opera, Safari
49+ return this . xhrRequest ( req , method , endPoint , headers , body , timeout , ontimeout , callback )
50+ } else if ( global . fetch && global . AbortController ) {
51+ // Fetch with AbortController for modern browsers
52+ return this . fetchRequest ( method , endPoint , headers , body , timeout , ontimeout , callback )
53+ } else {
54+ throw new Error ( "No suitable XMLHttpRequest implementation found" )
55+ }
56+ }
57+
58+ static fetchRequest (
59+ method : HttpMethod ,
60+ endPoint : string ,
61+ headers : Headers ,
62+ body : RequestBody ,
63+ timeout : number ,
64+ ontimeout : TimeoutCallback | null ,
65+ callback : AjaxCallback | null
66+ ) : AbortController {
67+ let options : RequestInit = {
68+ method,
69+ headers,
70+ body,
71+ }
72+ let controller = new AbortController ( )
73+ if ( timeout ) {
74+ const _timeoutId = setTimeout ( ( ) => controller . abort ( ) , timeout )
75+ options . signal = controller . signal
76+ }
77+ global . fetch ( endPoint , options )
78+ . then ( response => response . text ( ) )
79+ . then ( data => this . parseJSON ( data ) )
80+ . then ( data => callback && callback ( data ) )
81+ . catch ( err => {
82+ if ( err . name === "AbortError" && ontimeout ) {
83+ ontimeout ( )
84+ } else {
85+ callback && callback ( null )
86+ }
87+ } )
88+ return controller
89+ }
90+
91+ static xdomainRequest (
92+ req : XDomainRequest ,
93+ method : HttpMethod ,
94+ endPoint : string ,
95+ body : RequestBody ,
96+ timeout : number ,
97+ ontimeout : TimeoutCallback | null ,
98+ callback : AjaxCallback | null
99+ ) : XDomainRequest {
100+ req . timeout = timeout
101+ req . open ( method , endPoint )
102+ req . onload = ( ) => {
103+ let response = this . parseJSON ( req . responseText )
104+ callback && callback ( response )
105+ }
106+ if ( ontimeout ) { req . ontimeout = ontimeout }
107+
108+ // Work around bug in IE9 that requires an attached onprogress handler
109+ req . onprogress = ( ) => { }
110+
111+ req . send ( body )
112+ return req
113+ }
114+
115+ static xhrRequest (
116+ req : XMLHttpRequestLike ,
117+ method : HttpMethod ,
118+ endPoint : string ,
119+ headers : Headers ,
120+ body : RequestBody ,
121+ timeout : number ,
122+ ontimeout : TimeoutCallback | null ,
123+ callback : AjaxCallback | null
124+ ) : XMLHttpRequestLike {
125+ req . open ( method , endPoint , true )
126+ req . timeout = timeout
127+ for ( let [ key , value ] of Object . entries ( headers ) ) {
128+ req . setRequestHeader ( key , value )
129+ }
130+ req . onerror = ( ) => callback && callback ( null )
131+ req . onreadystatechange = ( ) => {
132+ if ( req . readyState === XHR_STATES . complete && callback ) {
133+ let response = this . parseJSON ( req . responseText )
134+ callback ( response )
135+ }
136+ }
137+ if ( ontimeout ) { req . ontimeout = ontimeout }
138+
139+ req . send ( body )
140+ return req
141+ }
142+
143+ static parseJSON ( resp : string | null | undefined ) : any {
144+ if ( ! resp || resp === "" ) { return null }
145+
146+ try {
147+ return JSON . parse ( resp )
148+ } catch {
149+ console && console . log ( "failed to parse JSON response" , resp )
150+ return null
151+ }
152+ }
153+
154+ static serialize ( obj : Record < string , any > , parentKey ?: string ) : string {
155+ let queryStr : string [ ] = [ ]
156+ for ( var key in obj ) {
157+ if ( ! Object . prototype . hasOwnProperty . call ( obj , key ) ) { continue }
158+ let paramKey = parentKey ? `${ parentKey } [${ key } ]` : key
159+ let paramVal = obj [ key ]
160+ if ( typeof paramVal === "object" ) {
161+ queryStr . push ( this . serialize ( paramVal , paramKey ) )
162+ } else {
163+ queryStr . push ( encodeURIComponent ( paramKey ) + "=" + encodeURIComponent ( paramVal ) )
164+ }
165+ }
166+ return queryStr . join ( "&" )
167+ }
168+
169+ static appendParams ( url : string , params : Record < string , any > ) : string {
170+ if ( Object . keys ( params ) . length === 0 ) { return url }
171+
172+ let prefix = url . match ( / \? / ) ? "&" : "?"
173+ return `${ url } ${ prefix } ${ this . serialize ( params ) } `
174+ }
175+ }
0 commit comments