Преглед изворни кода

Fix XMLHttpRequest module bugs #44

Add Event and ProgressEvent class #44

Add timeout support #44
Ben Hsieh пре 7 година
родитељ
комит
2ea0cdaea1

+ 4
- 0
src/android/src/main/java/com/RNFetchBlob/RNFetchBlobConfig.java Прегледај датотеку

17
     public String key;
17
     public String key;
18
     public String mime;
18
     public String mime;
19
     public Boolean auto;
19
     public Boolean auto;
20
+    public long timeout = -1;
20
 
21
 
21
     RNFetchBlobConfig(ReadableMap options) {
22
     RNFetchBlobConfig(ReadableMap options) {
22
         if(options == null)
23
         if(options == null)
31
         this.key = options.hasKey("key") ? options.getString("key") : null;
32
         this.key = options.hasKey("key") ? options.getString("key") : null;
32
         this.mime = options.hasKey("contentType") ? options.getString("contentType") : null;
33
         this.mime = options.hasKey("contentType") ? options.getString("contentType") : null;
33
         this.auto = options.hasKey("auto") ? options.getBoolean("auto") : false;
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 Прегледај датотеку

28
 import java.io.IOException;
28
 import java.io.IOException;
29
 import java.io.InputStream;
29
 import java.io.InputStream;
30
 import java.net.MalformedURLException;
30
 import java.net.MalformedURLException;
31
+import java.net.SocketTimeoutException;
31
 import java.net.URL;
32
 import java.net.URL;
32
 import java.util.HashMap;
33
 import java.util.HashMap;
34
+import java.util.concurrent.TimeUnit;
33
 
35
 
34
 import okhttp3.Call;
36
 import okhttp3.Call;
35
 import okhttp3.Headers;
37
 import okhttp3.Headers;
79
     long downloadManagerId;
81
     long downloadManagerId;
80
     RequestType requestType;
82
     RequestType requestType;
81
     ResponseType responseType;
83
     ResponseType responseType;
84
+    boolean timeout = false;
82
 
85
 
83
     public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) {
86
     public RNFetchBlobReq(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, ReadableArray arrayBody, final Callback callback) {
84
         this.method = method;
87
         this.method = method;
257
             clientBuilder.addInterceptor(new Interceptor() {
260
             clientBuilder.addInterceptor(new Interceptor() {
258
                 @Override
261
                 @Override
259
                 public Response intercept(Chain chain) throws IOException {
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
             OkHttpClient client = clientBuilder.build();
299
             OkHttpClient client = clientBuilder.build();
288
             Call call = client.newCall(req);
300
             Call call = client.newCall(req);
386
         info.putInt("status", resp.code());
398
         info.putInt("status", resp.code());
387
         info.putString("state", "2");
399
         info.putString("state", "2");
388
         info.putString("taskId", this.taskId);
400
         info.putString("taskId", this.taskId);
401
+        info.putBoolean("timeout", timeout);
389
         WritableMap headers = Arguments.createMap();
402
         WritableMap headers = Arguments.createMap();
390
         for(int i =0;i< resp.headers().size();i++) {
403
         for(int i =0;i< resp.headers().size();i++) {
391
             headers.putString(resp.headers().name(i), resp.headers().value(i));
404
             headers.putString(resp.headers().name(i), resp.headers().value(i));
392
         }
405
         }
393
         info.putMap("headers", headers);
406
         info.putMap("headers", headers);
394
         Headers h = resp.headers();
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
             info.putString("respType", "text");
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
             info.putString("respType", "json");
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
             info.putString("respType", "blob");
415
             info.putString("respType", "blob");
406
         }
416
         }
407
-        else
408
-        {
417
+        else {
409
             info.putString("respType", "text");
418
             info.putString("respType", "text");
410
         }
419
         }
411
         return info;
420
         return info;
413
 
422
 
414
     private boolean isBlobResponse(Response resp) {
423
     private boolean isBlobResponse(Response resp) {
415
         Headers h = resp.headers();
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
         boolean isJSON = !getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("application/json");
426
         boolean isJSON = !getHeaderIgnoreCases(h, "content-type").equalsIgnoreCase("application/json");
418
         return  !(isJSON || isText);
427
         return  !(isJSON || isText);
419
     }
428
     }

+ 19
- 10
src/index.js Прегледај датотеку

19
 import fs from './fs'
19
 import fs from './fs'
20
 import getUUID from './utils/uuid'
20
 import getUUID from './utils/uuid'
21
 import base64 from 'base-64'
21
 import base64 from 'base-64'
22
+import polyfill from './polyfill'
22
 const {
23
 const {
23
   RNFetchBlobSession,
24
   RNFetchBlobSession,
24
   readStream,
25
   readStream,
34
   mv,
35
   mv,
35
   cp
36
   cp
36
 } = fs
37
 } = fs
37
-import polyfill from './polyfill'
38
 
38
 
39
 const Blob = polyfill.Blob
39
 const Blob = polyfill.Blob
40
 const emitter = DeviceEventEmitter
40
 const emitter = DeviceEventEmitter
151
       subscriptionUpload.remove()
151
       subscriptionUpload.remove()
152
       stateEvent.remove()
152
       stateEvent.remove()
153
       if(err)
153
       if(err)
154
-        reject(new Error(err, data))
154
+        reject(new Error(err, info))
155
       else {
155
       else {
156
         let rnfbEncode = 'base64'
156
         let rnfbEncode = 'base64'
157
         // response data is saved to storage
157
         // response data is saved to storage
163
         }
163
         }
164
         info = info || {}
164
         info = info || {}
165
         info.rnfbEncode = rnfbEncode
165
         info.rnfbEncode = rnfbEncode
166
-
167
         resolve(new FetchBlobResponse(taskId, info, data))
166
         resolve(new FetchBlobResponse(taskId, info, data))
168
       }
167
       }
168
+
169
     })
169
     })
170
 
170
 
171
   })
171
   })
228
       return this.respInfo
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
      * Convert result to text.
250
      * Convert result to text.

+ 17
- 8
src/ios/RNFetchBlobNetwork.m Прегледај датотеку

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

+ 2
- 0
src/ios/RNFetchBlobReqBuilder.h Прегледај датотеку

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

+ 11
- 0
src/polyfill/Event.js Прегледај датотеку

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 Прегледај датотеку

1
 // Copyright 2016 wkh237@github. All rights reserved.
1
 // Copyright 2016 wkh237@github. All rights reserved.
2
 // Use of this source code is governed by a MIT-style license that can be
2
 // Use of this source code is governed by a MIT-style license that can be
3
 // found in the LICENSE file.
3
 // found in the LICENSE file.
4
+
4
 import Log from '../utils/log.js'
5
 import Log from '../utils/log.js'
5
 
6
 
6
 const log = new Log('EventTarget')
7
 const log = new Log('EventTarget')
14
     this.listeners = {}
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
   addEventListener(type:string, cb : () => void) {
23
   addEventListener(type:string, cb : () => void) {
18
     log.info('add event listener', type, cb)
24
     log.info('add event listener', type, cb)
19
     if(!(type in this.listeners)) {
25
     if(!(type in this.listeners)) {
22
     this.listeners[type].push(cb)
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
     log.info('remove event listener', type, cb)
38
     log.info('remove event listener', type, cb)
27
     if(!(type in this.listeners))
39
     if(!(type in this.listeners))
28
       return
40
       return
35
     }
47
     }
36
   }
48
   }
37
 
49
 
50
+  /**
51
+   * Dispatch an event
52
+   * @param {Evnet} event Event data payload.
53
+   */
38
   dispatchEvent(event:Event) {
54
   dispatchEvent(event:Event) {
39
     log.info('dispatch event', event)
55
     log.info('dispatch event', event)
40
     if(!(event.type in this.listeners))
56
     if(!(event.type in this.listeners))
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 Прегледај датотеку

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 Прегледај датотеку

6
 import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget.js'
6
 import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget.js'
7
 import Log from '../utils/log.js'
7
 import Log from '../utils/log.js'
8
 import Blob from './Blob.js'
8
 import Blob from './Blob.js'
9
+import ProgressEvent from './ProgressEvent.js'
9
 
10
 
10
 const log = new Log('XMLHttpRequest')
11
 const log = new Log('XMLHttpRequest')
11
 
12
 
21
 
22
 
22
   _onreadystatechange : () => void;
23
   _onreadystatechange : () => void;
23
 
24
 
25
+  upload : XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget();
26
+
27
+  // readonly
24
   _readyState : number = UNSENT;
28
   _readyState : number = UNSENT;
25
   _response : any = '';
29
   _response : any = '';
26
-  _responseText : any = '';
30
+  _responseText : any = null;
27
   _responseHeaders : any = {};
31
   _responseHeaders : any = {};
28
   _responseType : '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' = '';
32
   _responseType : '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' = '';
29
-  // TODO : not suppoted for now
33
+  // TODO : not suppoted ATM
30
   _responseURL : null = '';
34
   _responseURL : null = '';
31
   _responseXML : null = '';
35
   _responseXML : null = '';
32
   _status : number = 0;
36
   _status : number = 0;
33
   _statusText : string = '';
37
   _statusText : string = '';
34
   _timeout : number = 0;
38
   _timeout : number = 0;
35
-  _upload : XMLHttpRequestEventTarget;
36
   _sendFlag : boolean = false;
39
   _sendFlag : boolean = false;
40
+  _uploadStarted : boolean = false;
37
 
41
 
38
   // RNFetchBlob compatible data structure
42
   // RNFetchBlob compatible data structure
39
-  _config : RNFetchBlobConfig;
43
+  _config : RNFetchBlobConfig = {};
40
   _url : any;
44
   _url : any;
41
   _method : string;
45
   _method : string;
42
-  _headers: any;
46
+  _headers: any = {
47
+    'Content-Type' : 'text/plain'
48
+  };
43
   _body: any;
49
   _body: any;
44
 
50
 
45
   // RNFetchBlob promise object, which has `progress`, `uploadProgress`, and
51
   // RNFetchBlob promise object, which has `progress`, `uploadProgress`, and
46
   // `cancel` methods.
52
   // `cancel` methods.
47
   _task: any;
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
   static get UNSENT() {
62
   static get UNSENT() {
50
     return UNSENT
63
     return UNSENT
51
   }
64
   }
66
     return DONE
79
     return DONE
67
   }
80
   }
68
 
81
 
69
-  constructor(...args) {
82
+  constructor() {
70
     super()
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
     this._method = method
99
     this._method = method
90
     this._url = url
100
     this._url = url
91
     this._headers = {}
101
     this._headers = {}
92
-    this.readyState = XMLHttpRequest.OPENED
102
+    this._dispatchReadStateChange(XMLHttpRequest.OPENED)
93
   }
103
   }
94
 
104
 
95
   /**
105
   /**
105
     log.verbose('XMLHttpRequest send ', body)
115
     log.verbose('XMLHttpRequest send ', body)
106
     let {_method, _url, _headers } = this
116
     let {_method, _url, _headers } = this
107
     log.verbose('sending request with args', _method, _url, _headers, body)
117
     log.verbose('sending request with args', _method, _url, _headers, body)
108
-
109
-    this._upload = new XMLHttpRequestEventTarget()
110
     log.verbose(typeof body, body instanceof FormData)
118
     log.verbose(typeof body, body instanceof FormData)
111
 
119
 
112
     if(body instanceof Blob) {
120
     if(body instanceof Blob) {
113
       body = RNFetchBlob.wrap(body.getRNFetchBlobRef())
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
     this._task
131
     this._task
123
         .stateChange(this._headerReceived.bind(this))
132
         .stateChange(this._headerReceived.bind(this))
124
-        .uploadProgress(this._progressEvent.bind(this))
133
+        .uploadProgress(this._uploadProgressEvent.bind(this))
125
         .progress(this._progressEvent.bind(this))
134
         .progress(this._progressEvent.bind(this))
126
         .catch(this._onError.bind(this))
135
         .catch(this._onError.bind(this))
127
         .then(this._onDone.bind(this))
136
         .then(this._onDone.bind(this))
129
 
138
 
130
   overrideMimeType(mime:string) {
139
   overrideMimeType(mime:string) {
131
     log.verbose('XMLHttpRequest overrideMimeType', mime)
140
     log.verbose('XMLHttpRequest overrideMimeType', mime)
132
-    this._headers['content-type'] = mime
141
+    this._headers['Content-Type'] = mime
133
   }
142
   }
134
 
143
 
135
   setRequestHeader(name, value) {
144
   setRequestHeader(name, value) {
136
     log.verbose('XMLHttpRequest set header', name, value)
145
     log.verbose('XMLHttpRequest set header', name, value)
137
-    if(this._readyState !== OPENED && this._sendFlag) {
146
+    if(this._readyState !== OPENED || this._sendFlag) {
138
       throw `InvalidStateError : Calling setRequestHeader in wrong state  ${this._readyState}`
147
       throw `InvalidStateError : Calling setRequestHeader in wrong state  ${this._readyState}`
139
     }
148
     }
140
     // UNICODE SHOULD NOT PASS
149
     // UNICODE SHOULD NOT PASS
199
   _headerReceived(e) {
208
   _headerReceived(e) {
200
     log.verbose('header received ', this._task.taskId, e)
209
     log.verbose('header received ', this._task.taskId, e)
201
     this.responseURL = this._url
210
     this.responseURL = this._url
211
+    this.upload.dispatchEvent('loadend')
212
+    this.dispatchEvent('load')
202
     if(e.state === "2") {
213
     if(e.state === "2") {
203
-      this._readyState = XMLHttpRequest.HEADERS_RECEIVED
204
       this._responseHeaders = e.headers
214
       this._responseHeaders = e.headers
205
-      this._responseText = e.status
215
+      this._statusText = e.status
206
       this._responseType = e.respType || ''
216
       this._responseType = e.respType || ''
207
       this._status = Math.floor(e.status)
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
   _progressEvent(send:number, total:number) {
233
   _progressEvent(send:number, total:number) {
212
     log.verbose(this.readyState)
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
     let lengthComputable = false
237
     let lengthComputable = false
216
-    let e = { lengthComputable }
217
     if(total && total >= 0)
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
     this.dispatchEvent('progress', e)
241
     this.dispatchEvent('progress', e)
226
   }
242
   }
227
 
243
 
230
     this._statusText = err
246
     this._statusText = err
231
     this._status = String(err).match(/\d+/)
247
     this._status = String(err).match(/\d+/)
232
     this._status = this._status ? Math.floor(this.status) : 404
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
       this.dispatchEvent('timeout')
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
   _onDone(resp) {
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
             this._responseText = resp.text()
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
           this._responseText = resp.text()
280
           this._responseText = resp.text()
259
           this._response = this.responseText
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
   set onreadystatechange(fn:() => void) {
296
   set onreadystatechange(fn:() => void) {
277
     this._onreadystatechange = fn
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
   get readyState() {
305
   get readyState() {
300
     return this._status
312
     return this._status
301
   }
313
   }
302
 
314
 
303
-  set statusText(val) {
304
-    this._statusText = val
305
-  }
306
-
307
   get statusText() {
315
   get statusText() {
308
     log.verbose('get statusText', this._statusText)
316
     log.verbose('get statusText', this._statusText)
309
     return this._statusText
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
   get response() {
320
   get response() {
318
     log.verbose('get response', this._response)
321
     log.verbose('get response', this._response)
319
     return this._response
322
     return this._response
344
     return this._timeout
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
   get responseType() {
350
   get responseType() {
353
     log.verbose('get response type', this._responseType)
351
     log.verbose('get response type', this._responseType)
354
     return this._responseType
352
     return this._responseType

+ 35
- 7
src/polyfill/XMLHttpRequestEventTarget.js Прегледај датотеку

1
 // Copyright 2016 wkh237@github. All rights reserved.
1
 // Copyright 2016 wkh237@github. All rights reserved.
2
 // Use of this source code is governed by a MIT-style license that can be
2
 // Use of this source code is governed by a MIT-style license that can be
3
 // found in the LICENSE file.
3
 // found in the LICENSE file.
4
+
4
 import EventTarget from './EventTarget.js'
5
 import EventTarget from './EventTarget.js'
5
 import Log from '../utils/log.js'
6
 import Log from '../utils/log.js'
6
 
7
 
10
 
11
 
11
 export default class XMLHttpRequestEventTarget extends EventTarget {
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
   constructor() {
22
   constructor() {
22
     super()
23
     super()
23
     log.info('constructor called')
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
   set onabort(fn:(e:Event) => void) {
54
   set onabort(fn:(e:Event) => void) {
27
     log.info('set onabort')
55
     log.info('set onabort')
28
     this._onabort = fn
56
     this._onabort = fn

+ 2
- 1
src/polyfill/index.js Прегледај датотеку

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