Browse Source

Fix XMLHttpRequest module bugs #44

Add Event and ProgressEvent class #44

Add timeout support #44
Ben Hsieh 7 years ago
parent
commit
2ea0cdaea1

+ 4
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java View File

@@ -17,6 +17,7 @@ public class RNFetchBlobConfig {
17 17
     public String key;
18 18
     public String mime;
19 19
     public Boolean auto;
20
+    public long timeout = -1;
20 21
 
21 22
     RNFetchBlobConfig(ReadableMap options) {
22 23
         if(options == null)
@@ -31,6 +32,9 @@ public class RNFetchBlobConfig {
31 32
         this.key = options.hasKey("key") ? options.getString("key") : null;
32 33
         this.mime = options.hasKey("contentType") ? options.getString("contentType") : null;
33 34
         this.auto = options.hasKey("auto") ? options.getBoolean("auto") : false;
35
+        if(options.hasKey("timeout")) {
36
+            this.timeout = options.getInt("timeout");
37
+        }
34 38
     }
35 39
 
36 40
 }

+ 42
- 33
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobReq.java View File

@@ -28,8 +28,10 @@ import java.io.FileOutputStream;
28 28
 import java.io.IOException;
29 29
 import java.io.InputStream;
30 30
 import java.net.MalformedURLException;
31
+import java.net.SocketTimeoutException;
31 32
 import java.net.URL;
32 33
 import java.util.HashMap;
34
+import java.util.concurrent.TimeUnit;
33 35
 
34 36
 import okhttp3.Call;
35 37
 import okhttp3.Headers;
@@ -79,6 +81,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
79 81
     long downloadManagerId;
80 82
     RequestType requestType;
81 83
     ResponseType responseType;
84
+    boolean timeout = false;
82 85
 
83 86
     public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) {
84 87
         this.method = method;
@@ -257,32 +260,41 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
257 260
             clientBuilder.addInterceptor(new Interceptor() {
258 261
                 @Override
259 262
                 public Response intercept(Chain chain) throws IOException {
260
-                Response originalResponse = chain.proceed(req);
261
-                    ResponseBody extended;
262
-                switch (responseType) {
263
-                    case KeepInMemory:
264
-                        extended = new RNFetchBlobDefaultResp(
265
-                                RNFetchBlob.RCTContext,
266
-                                taskId,
267
-                                originalResponse.body());
268
-                        break;
269
-                    case FileStorage:
270
-                        extended = new RNFetchBlobFileResp(
271
-                                RNFetchBlob.RCTContext,
272
-                                taskId,
273
-                                originalResponse.body(),
274
-                                destPath);
275
-                        break;
276
-                    default:
277
-                        extended = new RNFetchBlobDefaultResp(
278
-                                RNFetchBlob.RCTContext,
279
-                                taskId,
280
-                                originalResponse.body());
281
-                        break;
282
-                }
283
-                return originalResponse.newBuilder().body(extended).build();
263
+                    try {
264
+                        Response originalResponse = chain.proceed(req);
265
+                        ResponseBody extended;
266
+                        switch (responseType) {
267
+                            case KeepInMemory:
268
+                                extended = new RNFetchBlobDefaultResp(
269
+                                        RNFetchBlob.RCTContext,
270
+                                        taskId,
271
+                                        originalResponse.body());
272
+                                break;
273
+                            case FileStorage:
274
+                                extended = new RNFetchBlobFileResp(
275
+                                        RNFetchBlob.RCTContext,
276
+                                        taskId,
277
+                                        originalResponse.body(),
278
+                                        destPath);
279
+                                break;
280
+                            default:
281
+                                extended = new RNFetchBlobDefaultResp(
282
+                                        RNFetchBlob.RCTContext,
283
+                                        taskId,
284
+                                        originalResponse.body());
285
+                                break;
286
+                        }
287
+                        return originalResponse.newBuilder().body(extended).build();
288
+                    } catch(SocketTimeoutException ex) {
289
+                        timeout = true;
290
+                    }
291
+                    return chain.proceed(chain.request());
284 292
                 }
285 293
             });
294
+            
295
+            if(options.timeout != -1) {
296
+                clientBuilder.connectTimeout(options.timeout, TimeUnit.SECONDS);
297
+            }
286 298
 
287 299
             OkHttpClient client = clientBuilder.build();
288 300
             Call call = client.newCall(req);
@@ -386,26 +398,23 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
386 398
         info.putInt("status", resp.code());
387 399
         info.putString("state", "2");
388 400
         info.putString("taskId", this.taskId);
401
+        info.putBoolean("timeout", timeout);
389 402
         WritableMap headers = Arguments.createMap();
390 403
         for(int i =0;i< resp.headers().size();i++) {
391 404
             headers.putString(resp.headers().name(i), resp.headers().value(i));
392 405
         }
393 406
         info.putMap("headers", headers);
394 407
         Headers h = resp.headers();
395
-        if(getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/plain"))
396
-        {
408
+        if(getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/plain")) {
397 409
             info.putString("respType", "text");
398 410
         }
399
-        else if(getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("application/json"))
400
-        {
411
+        else if(getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("application/json")) {
401 412
             info.putString("respType", "json");
402 413
         }
403
-        else if(getHeaderIgnoreCases(h, "content-type").length() < 1)
404
-        {
414
+        else if(getHeaderIgnoreCases(h, "content-type").length() < 1) {
405 415
             info.putString("respType", "blob");
406 416
         }
407
-        else
408
-        {
417
+        else {
409 418
             info.putString("respType", "text");
410 419
         }
411 420
         return info;
@@ -413,7 +422,7 @@ public class RNFetchBlobReq extends BroadcastReceiver implements Runnable {
413 422
 
414 423
     private boolean isBlobResponse(Response resp) {
415 424
         Headers h = resp.headers();
416
-        boolean isText = !getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/plain");
425
+        boolean isText = !getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("text/");
417 426
         boolean isJSON = !getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("application/json");
418 427
         return  !(isJSON || isText);
419 428
     }

+ 19
- 10
src/index.js View File

@@ -19,6 +19,7 @@ import type {
19 19
 import fs from './fs'
20 20
 import getUUID from './utils/uuid'
21 21
 import base64 from 'base-64'
22
+import polyfill from './polyfill'
22 23
 const {
23 24
   RNFetchBlobSession,
24 25
   readStream,
@@ -34,7 +35,6 @@ const {
34 35
   mv,
35 36
   cp
36 37
 } = fs
37
-import polyfill from './polyfill'
38 38
 
39 39
 const Blob = polyfill.Blob
40 40
 const emitter = DeviceEventEmitter
@@ -151,7 +151,7 @@ function fetch(...args:any):Promise {
151 151
       subscriptionUpload.remove()
152 152
       stateEvent.remove()
153 153
       if(err)
154
-        reject(new Error(err, data))
154
+        reject(new Error(err, info))
155 155
       else {
156 156
         let rnfbEncode = 'base64'
157 157
         // response data is saved to storage
@@ -163,9 +163,9 @@ function fetch(...args:any):Promise {
163 163
         }
164 164
         info = info || {}
165 165
         info.rnfbEncode = rnfbEncode
166
-
167 166
         resolve(new FetchBlobResponse(taskId, info, data))
168 167
       }
168
+
169 169
     })
170 170
 
171 171
   })
@@ -228,14 +228,23 @@ class FetchBlobResponse {
228 228
       return this.respInfo
229 229
     }
230 230
     /**
231
-     * Convert result to javascript Blob object.
232
-     * @param  {string} contentType MIME type of the blob object.
233
-     * @param  {number} sliceSize   Slice size.
234
-     * @return {blob}             Return Blob object.
231
+     * Convert result to javascript RNFetchBlob object.
232
+     * @return {Promise<Blob>} Return a promise resolves Blob object.
235 233
      */
236
-    this.blob = (contentType:string, sliceSize:number) => {
237
-      console.warn('FetchBlobResponse.blob() is deprecated and has no funtionality.')
238
-      return this
234
+    this.blob = ():Promise<Blob> => {
235
+      return new Promise((resolve, reject) => {
236
+        if(this.type === 'base64') {
237
+          try {
238
+            let b = new polyfill.Blob(this.data, 'application/octet-stream;BASE64')
239
+            b.onCreated(() => {
240
+              console.log('####', b)
241
+              resolve(b)
242
+            })
243
+          } catch(err) {
244
+            reject(err)
245
+          }
246
+        }
247
+      })
239 248
     }
240 249
     /**
241 250
      * Convert result to text.

+ 17
- 8
src/ios/RNFetchBlobNetwork.m View File

@@ -14,6 +14,7 @@
14 14
 #import "RNFetchBlobFS.h"
15 15
 #import "RNFetchBlobNetwork.h"
16 16
 #import "RNFetchBlobConst.h"
17
+#import "RNFetchBlobReqBuilder.h"
17 18
 #import <CommonCrypto/CommonDigest.h>
18 19
 
19 20
 ////////////////////////////////////////
@@ -113,8 +114,12 @@ NSOperationQueue *taskQueue;
113 114
     // the session trust any SSL certification
114 115
 
115 116
     NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
117
+    if([options valueForKey:@"timeout"] != nil)
118
+    {
119
+        defaultConfigObject.timeoutIntervalForRequest = [[options valueForKey:@"timeout"] floatValue];
120
+    }
116 121
     session = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue:taskQueue];
117
-
122
+    
118 123
     if(path != nil || [self.options valueForKey:CONFIG_USE_TEMP]!= nil)
119 124
     {
120 125
         respFile = YES;
@@ -171,10 +176,12 @@ NSOperationQueue *taskQueue;
171 176
     if ([response respondsToSelector:@selector(allHeaderFields)])
172 177
     {
173 178
         NSDictionary *headers = [httpResponse allHeaderFields];
174
-        NSString * respType = [[headers valueForKey:@"Content-Type"] lowercaseString];
179
+        NSString * respType = [[RNFetchBlobReqBuilder getHeaderIgnoreCases:@"content-type"
180
+                                                               fromHeaders:headers]
181
+                               lowercaseString];
175 182
         if([headers valueForKey:@"Content-Type"] != nil)
176 183
         {
177
-            if([respType containsString:@"text/plain"])
184
+            if([respType containsString:@"text/"])
178 185
             {
179 186
                 respType = @"text";
180 187
             }
@@ -199,6 +206,7 @@ NSOperationQueue *taskQueue;
199 206
                      @"state": @"2",
200 207
                      @"headers": headers,
201 208
                      @"respType" : respType,
209
+                     @"timeout" : @NO,
202 210
                      @"status": [NSString stringWithFormat:@"%d", statusCode ]
203 211
                      };
204 212
         [self.bridge.eventDispatcher
@@ -253,22 +261,22 @@ NSOperationQueue *taskQueue;
253 261
 }
254 262
 
255 263
 - (void) URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
256
-    NSLog([error localizedDescription]);
264
+    
257 265
     self.error = error;
258 266
     [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
259 267
     
260 268
     NSString * respType = [respInfo valueForKey:@"respType"];
269
+    if(error != nil) {
270
+        NSLog([error localizedDescription]);
271
+    }
261 272
     
262 273
     if(respFile == YES)
263 274
     {
264
-        if(error != nil) {
265
-            NSLog([error localizedDescription]);
266
-        }
267 275
         [writeStream close];
268 276
         callback(@[error == nil ? [NSNull null] : [error localizedDescription],
269 277
                    respInfo == nil ? [NSNull null] : respInfo,
270 278
                    destPath
271
-                   ]);
279
+                ]);
272 280
     }
273 281
     // base64 response
274 282
     else {
@@ -347,4 +355,5 @@ NSOperationQueue *taskQueue;
347 355
     }
348 356
 }
349 357
 
358
+
350 359
 @end

+ 2
- 0
src/ios/RNFetchBlobReqBuilder.h View File

@@ -37,6 +37,8 @@
37 37
                         form:(NSString *)body
38 38
                   onComplete:(void(^)(NSURLRequest * req, long bodyLength))onComplete;
39 39
 
40
++(NSString *) getHeaderIgnoreCases:(NSString *)field fromHeaders:(NSMutableArray *) headers;
41
+
40 42
 
41 43
 @end
42 44
 

+ 11
- 0
src/polyfill/Event.js View File

@@ -0,0 +1,11 @@
1
+// Copyright 2016 wkh237@github. All rights reserved.
2
+// Use of this source code is governed by a MIT-style license that can be
3
+// found in the LICENSE file.
4
+
5
+export default class Event {
6
+
7
+  constructor() {
8
+
9
+  }
10
+
11
+}

+ 28
- 1
src/polyfill/EventTarget.js View File

@@ -1,6 +1,7 @@
1 1
 // Copyright 2016 wkh237@github. All rights reserved.
2 2
 // Use of this source code is governed by a MIT-style license that can be
3 3
 // found in the LICENSE file.
4
+
4 5
 import Log from '../utils/log.js'
5 6
 
6 7
 const log = new Log('EventTarget')
@@ -14,6 +15,11 @@ export default class EventTarget {
14 15
     this.listeners = {}
15 16
   }
16 17
 
18
+  /**
19
+   * Add an event listener to given event type
20
+   * @param {string} type Event type string
21
+   * @param {(Event) => void} cb   Event handler function
22
+   */
17 23
   addEventListener(type:string, cb : () => void) {
18 24
     log.info('add event listener', type, cb)
19 25
     if(!(type in this.listeners)) {
@@ -22,7 +28,13 @@ export default class EventTarget {
22 28
     this.listeners[type].push(cb)
23 29
   }
24 30
 
25
-  removeEventListener(type:string, cb:() => any) {
31
+  /**
32
+   * Remove an event listener
33
+   * @param  {string} type Type of the event listener
34
+   * @param  {()=>void} cb Event listener function.
35
+   * @return {[type]}             [description]
36
+   */
37
+  removeEventListener(type:string, cb:() => void) {
26 38
     log.info('remove event listener', type, cb)
27 39
     if(!(type in this.listeners))
28 40
       return
@@ -35,6 +47,10 @@ export default class EventTarget {
35 47
     }
36 48
   }
37 49
 
50
+  /**
51
+   * Dispatch an event
52
+   * @param {Evnet} event Event data payload.
53
+   */
38 54
   dispatchEvent(event:Event) {
39 55
     log.info('dispatch event', event)
40 56
     if(!(event.type in this.listeners))
@@ -46,4 +62,15 @@ export default class EventTarget {
46 62
 
47 63
   }
48 64
 
65
+  /**
66
+   * Remove all registered listeners from this object.
67
+   * @nonstandard
68
+   * @return {[type]} [description]
69
+   */
70
+  clearEventListeners() {
71
+    for(let i in this.listeners) {
72
+      delete listeners[i]
73
+    }
74
+  }
75
+
49 76
 }

+ 32
- 0
src/polyfill/ProgressEvent.js View File

@@ -0,0 +1,32 @@
1
+// Copyright 2016 wkh237@github. All rights reserved.
2
+// Use of this source code is governed by a MIT-style license that can be
3
+// found in the LICENSE file.
4
+
5
+import Event from './Event'
6
+
7
+export default class ProgressEvent extends Event {
8
+
9
+  _lengthComputable : boolean = false;
10
+  _loaded : number = -1;
11
+  _total : numver = -1;
12
+
13
+  constructor(lengthComputable, loaded, total) {
14
+    super()
15
+    this._lengthComputable = lengthComputable;
16
+    this._loaded = loaded
17
+    this._total = total
18
+  }
19
+
20
+  get lengthComputable() {
21
+    return this._lengthComputable
22
+  }
23
+
24
+  get loaded() {
25
+    return this._loaded
26
+  }
27
+
28
+  get total() {
29
+    return this._total
30
+  }
31
+
32
+}

+ 88
- 90
src/polyfill/XMLHttpRequest.js View File

@@ -6,6 +6,7 @@ import RNFetchBlob from '../index.js'
6 6
 import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget.js'
7 7
 import Log from '../utils/log.js'
8 8
 import Blob from './Blob.js'
9
+import ProgressEvent from './ProgressEvent.js'
9 10
 
10 11
 const log = new Log('XMLHttpRequest')
11 12
 
@@ -21,31 +22,43 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
21 22
 
22 23
   _onreadystatechange : () => void;
23 24
 
25
+  upload : XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget();
26
+
27
+  // readonly
24 28
   _readyState : number = UNSENT;
25 29
   _response : any = '';
26
-  _responseText : any = '';
30
+  _responseText : any = null;
27 31
   _responseHeaders : any = {};
28 32
   _responseType : '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' = '';
29
-  // TODO : not suppoted for now
33
+  // TODO : not suppoted ATM
30 34
   _responseURL : null = '';
31 35
   _responseXML : null = '';
32 36
   _status : number = 0;
33 37
   _statusText : string = '';
34 38
   _timeout : number = 0;
35
-  _upload : XMLHttpRequestEventTarget;
36 39
   _sendFlag : boolean = false;
40
+  _uploadStarted : boolean = false;
37 41
 
38 42
   // RNFetchBlob compatible data structure
39
-  _config : RNFetchBlobConfig;
43
+  _config : RNFetchBlobConfig = {};
40 44
   _url : any;
41 45
   _method : string;
42
-  _headers: any;
46
+  _headers: any = {
47
+    'Content-Type' : 'text/plain'
48
+  };
43 49
   _body: any;
44 50
 
45 51
   // RNFetchBlob promise object, which has `progress`, `uploadProgress`, and
46 52
   // `cancel` methods.
47 53
   _task: any;
48 54
 
55
+  // constants
56
+  get UNSENT() { return UNSENT }
57
+  get OPENED() { return OPENED }
58
+  get HEADERS_RECEIVED() { return HEADERS_RECEIVED }
59
+  get LOADING() { return LOADING }
60
+  get DONE() { return DONE }
61
+
49 62
   static get UNSENT() {
50 63
     return UNSENT
51 64
   }
@@ -66,12 +79,9 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
66 79
     return DONE
67 80
   }
68 81
 
69
-  constructor(...args) {
82
+  constructor() {
70 83
     super()
71
-    log.verbose('XMLHttpRequest constructor called', args)
72
-    this._config = {}
73
-    this._args = {}
74
-    this._headers = {}
84
+    log.verbose('XMLHttpRequest constructor called')
75 85
   }
76 86
 
77 87
 
@@ -89,7 +99,7 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
89 99
     this._method = method
90 100
     this._url = url
91 101
     this._headers = {}
92
-    this.readyState = XMLHttpRequest.OPENED
102
+    this._dispatchReadStateChange(XMLHttpRequest.OPENED)
93 103
   }
94 104
 
95 105
   /**
@@ -105,23 +115,22 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
105 115
     log.verbose('XMLHttpRequest send ', body)
106 116
     let {_method, _url, _headers } = this
107 117
     log.verbose('sending request with args', _method, _url, _headers, body)
108
-
109
-    this._upload = new XMLHttpRequestEventTarget()
110 118
     log.verbose(typeof body, body instanceof FormData)
111 119
 
112 120
     if(body instanceof Blob) {
113 121
       body = RNFetchBlob.wrap(body.getRNFetchBlobRef())
114 122
     }
123
+    else if(typeof body === 'object') {
124
+      body = JSON.stringify(body)
125
+    }
115 126
 
116
-    this.dispatchEvent('loadstart')
117
-    if(this.onloadstart)
118
-      this.onloadstart()
119
-
120
-    this._task = RNFetchBlob.config({ auto: true })
121
-                            .fetch(_method, _url, _headers, body)
127
+    this._task = RNFetchBlob
128
+                  .config({ auto: true, timeout : this._timeout })
129
+                  .fetch(_method, _url, _headers, body)
130
+    this.dispatchEvent('load')
122 131
     this._task
123 132
         .stateChange(this._headerReceived.bind(this))
124
-        .uploadProgress(this._progressEvent.bind(this))
133
+        .uploadProgress(this._uploadProgressEvent.bind(this))
125 134
         .progress(this._progressEvent.bind(this))
126 135
         .catch(this._onError.bind(this))
127 136
         .then(this._onDone.bind(this))
@@ -129,12 +138,12 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
129 138
 
130 139
   overrideMimeType(mime:string) {
131 140
     log.verbose('XMLHttpRequest overrideMimeType', mime)
132
-    this._headers['content-type'] = mime
141
+    this._headers['Content-Type'] = mime
133 142
   }
134 143
 
135 144
   setRequestHeader(name, value) {
136 145
     log.verbose('XMLHttpRequest set header', name, value)
137
-    if(this._readyState !== OPENED && this._sendFlag) {
146
+    if(this._readyState !== OPENED || this._sendFlag) {
138 147
       throw `InvalidStateError : Calling setRequestHeader in wrong state  ${this._readyState}`
139 148
     }
140 149
     // UNICODE SHOULD NOT PASS
@@ -199,29 +208,36 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
199 208
   _headerReceived(e) {
200 209
     log.verbose('header received ', this._task.taskId, e)
201 210
     this.responseURL = this._url
211
+    this.upload.dispatchEvent('loadend')
212
+    this.dispatchEvent('load')
202 213
     if(e.state === "2") {
203
-      this._readyState = XMLHttpRequest.HEADERS_RECEIVED
204 214
       this._responseHeaders = e.headers
205
-      this._responseText = e.status
215
+      this._statusText = e.status
206 216
       this._responseType = e.respType || ''
207 217
       this._status = Math.floor(e.status)
218
+      this._dispatchReadStateChange(XMLHttpRequest.HEADERS_RECEIVED)
208 219
     }
209 220
   }
210 221
 
222
+  _uploadProgressEvent(send:number, total:number) {
223
+    console.log('_upload', this.upload)
224
+    if(!this._uploadStarted) {
225
+      this.upload.dispatchEvent('loadstart')
226
+      this._uploadStarted = true
227
+    }
228
+    if(send >= total)
229
+      this.upload.dispatchEvent('load')
230
+    this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total))
231
+  }
232
+
211 233
   _progressEvent(send:number, total:number) {
212 234
     log.verbose(this.readyState)
213
-    if(this.readyState === XMLHttpRequest.HEADERS_RECEIVED)
214
-      this.readyState = XMLHttpRequest.LOADING
235
+    if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED)
236
+      this._dispatchReadStateChange(XMLHttpRequest.LOADING)
215 237
     let lengthComputable = false
216
-    let e = { lengthComputable }
217 238
     if(total && total >= 0)
218
-        e.lengthComputable = true
219
-    else {
220
-      Object.assign(e, { loaded : send, total })
221
-    }
222
-
223
-    if(this.onprogress)
224
-      this.onprogress(e)
239
+        lengthComputable = true
240
+    let e = new ProgressEvent(lengthComputable, send, total)
225 241
     this.dispatchEvent('progress', e)
226 242
   }
227 243
 
@@ -230,46 +246,51 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
230 246
     this._statusText = err
231 247
     this._status = String(err).match(/\d+/)
232 248
     this._status = this._status ? Math.floor(this.status) : 404
233
-    this._readyState = XMLHttpRequest.DONE
234
-    if(String(err).match('timeout') !== null) {
249
+    this._dispatchReadStateChange(XMLHttpRequest.DONE)
250
+    if(err && String(err.message).match(/(timed\sout|timedout)/)) {
235 251
       this.dispatchEvent('timeout')
236
-      if(this.ontimeout)
237
-        this.ontimeout()
238
-    }
239
-    else if(this.onerror) {
240
-      this.dispatchEvent('error')
241
-      this.onerror({
242
-        type : 'error',
243
-        detail : err
244
-      })
245 252
     }
253
+    this.dispatchEvent('loadend')
254
+    this.dispatchEvent('error', {
255
+      type : 'error',
256
+      detail : err
257
+    })
258
+    this.clearEventListeners()
246 259
   }
247 260
 
248 261
   _onDone(resp) {
249
-    log.verbose('XMLHttpRequest done', this._task.taskId, this)
250
-    this.statusText = '200 OK'
251
-    switch(resp.type) {
252
-      case 'base64' :
253
-        if(this.responseType === 'json') {
262
+    log.verbose('XMLHttpRequest done', this._url, resp)
263
+    this._statusText = this._status
264
+    if(resp) {
265
+      switch(resp.type) {
266
+        case 'base64' :
267
+          if(this._responseType === 'json') {
268
+              this._responseText = resp.text()
269
+              this._response = resp.json()
270
+          }
271
+          else {
254 272
             this._responseText = resp.text()
255
-            this._response = resp.json()
256
-        }
257
-        else {
273
+            this._response = this.responseText
274
+          }
275
+        break;
276
+        case 'path' :
277
+          this.response = resp.blob()
278
+        break;
279
+        default :
258 280
           this._responseText = resp.text()
259 281
           this._response = this.responseText
260
-        }
261
-      break;
262
-      case 'path' :
263
-        this.response = resp.blob()
264
-      break;
282
+        break;
283
+      }
284
+      this.dispatchEvent('loadend')
285
+      this._dispatchReadStateChange(XMLHttpRequest.DONE)
265 286
     }
266
-    this.dispatchEvent('loadend')
267
-    if(this.onloadend)
268
-      this.onloadend()
269
-    this.dispatchEvent('load')
270
-    if(this._onload)
271
-      this._onload()
272
-    this.readyState = XMLHttpRequest.DONE
287
+    this.clearEventListeners()
288
+  }
289
+
290
+  _dispatchReadStateChange(state) {
291
+    this._readyState = state
292
+    if(typeof this._onreadystatechange === 'function')
293
+      this._onreadystatechange()
273 294
   }
274 295
 
275 296
   set onreadystatechange(fn:() => void) {
@@ -277,17 +298,8 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
277 298
     this._onreadystatechange = fn
278 299
   }
279 300
 
280
-  set readyState(val:number) {
281
-
282
-    log.verbose('XMLHttpRequest ready state changed to ', val)
283
-    this._readyState = val
284
-    if(this._onreadystatechange) {
285
-      log.verbose('trigger onreadystatechange event', this._readyState)
286
-      log.verbose(this._onreadystatechange)
287
-      this.dispatchEvent('readystatechange', )
288
-      if(this._onreadystatechange)
289
-        this._onreadystatechange()
290
-    }
301
+  get onreadystatechange(fn:() => void) {
302
+    return this._onreadystatechange
291 303
   }
292 304
 
293 305
   get readyState() {
@@ -300,20 +312,11 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
300 312
     return this._status
301 313
   }
302 314
 
303
-  set statusText(val) {
304
-    this._statusText = val
305
-  }
306
-
307 315
   get statusText() {
308 316
     log.verbose('get statusText', this._statusText)
309 317
     return this._statusText
310 318
   }
311 319
 
312
-  set response(val) {
313
-    log.verbose('set response', val)
314
-    this._response = val
315
-  }
316
-
317 320
   get response() {
318 321
     log.verbose('get response', this._response)
319 322
     return this._response
@@ -344,11 +347,6 @@ export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
344 347
     return this._timeout
345 348
   }
346 349
 
347
-  get upload() {
348
-    log.verbose('get upload', this._upload)
349
-    return this._upload
350
-  }
351
-
352 350
   get responseType() {
353 351
     log.verbose('get response type', this._responseType)
354 352
     return this._responseType

+ 35
- 7
src/polyfill/XMLHttpRequestEventTarget.js View File

@@ -1,6 +1,7 @@
1 1
 // Copyright 2016 wkh237@github. All rights reserved.
2 2
 // Use of this source code is governed by a MIT-style license that can be
3 3
 // found in the LICENSE file.
4
+
4 5
 import EventTarget from './EventTarget.js'
5 6
 import Log from '../utils/log.js'
6 7
 
@@ -10,19 +11,46 @@ log.disable()
10 11
 
11 12
 export default class XMLHttpRequestEventTarget extends EventTarget {
12 13
 
13
-  _onabort : (e:Event) => void;
14
-  _onerror : (e:Event) => void;
15
-  _onload : (e:Event) => void;
16
-  _onloadstart : (e:Event) => void;
17
-  _onprogress : (e:Event) => void;
18
-  _ontimeout : (e:Event) => void;
19
-  _onloadend : (e:Event) => void;
14
+  _onabort : (e:Event) => void = () => {};
15
+  _onerror : (e:Event) => void = () => {};
16
+  _onload : (e:Event) => void = () => {};
17
+  _onloadstart : (e:Event) => void = () => {};
18
+  _onprogress : (e:Event) => void = () => {};
19
+  _ontimeout : (e:Event) => void = () => {};
20
+  _onloadend : (e:Event) => void = () => {};
20 21
 
21 22
   constructor() {
22 23
     super()
23 24
     log.info('constructor called')
24 25
   }
25 26
 
27
+  dispatchEvent(event:string, e:Event) {
28
+    super.dispatchEvent(event, e)
29
+    switch(event) {
30
+      case 'abort' :
31
+        this._onabort(e)
32
+      break;
33
+      case 'error' :
34
+        this._onerror(e)
35
+      break;
36
+      case 'load' :
37
+        this._onload(e)
38
+      break;
39
+      case 'loadstart' :
40
+        this._onloadstart(e)
41
+      break;
42
+      case 'loadend' :
43
+        this._onloadend(e)
44
+      break;
45
+      case 'progress' :
46
+        this._onprogress(e)
47
+      break;
48
+      case 'timeout' :
49
+        this._ontimeout(e)
50
+      break;
51
+    }
52
+  }
53
+
26 54
   set onabort(fn:(e:Event) => void) {
27 55
     log.info('set onabort')
28 56
     this._onabort = fn

+ 2
- 1
src/polyfill/index.js View File

@@ -2,7 +2,8 @@ import Blob from './Blob.js'
2 2
 import File from './File.js'
3 3
 import XMLHttpRequest from './XMLHttpRequest.js'
4 4
 import FormData from './FormData.js'
5
+import ProgressEvent from './ProgressEvent'
5 6
 
6 7
 export default {
7
-  Blob, File, XMLHttpRequest, FormData
8
+  Blob, File, XMLHttpRequest, FormData, ProgressEvent
8 9
 }