123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- // Copyright 2016 wkh237@github. All rights reserved.
- // Use of this source code is governed by a MIT-style license that can be
- // found in the LICENSE file.
-
- import RNFetchBlob from '../index.js'
- import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget.js'
- import Log from '../utils/log.js'
- import Blob from './Blob.js'
- import ProgressEvent from './ProgressEvent.js'
- import URIUtil from '../utils/uri'
-
- const log = new Log('XMLHttpRequest')
-
- log.disable()
- // log.level(3)
-
- const UNSENT = 0
- const OPENED = 1
- const HEADERS_RECEIVED = 2
- const LOADING = 3
- const DONE = 4
-
- export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
-
- _onreadystatechange : () => void;
-
- upload : XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget();
- static binaryContentTypes : Array<string> = [
- 'image/', 'video/', 'audio/'
- ];
-
- // readonly
- _readyState : number = UNSENT;
- _uriType : 'net' | 'file' = 'net';
- _response : any = '';
- _responseText : any = '';
- _responseHeaders : any = {};
- _responseType : '' | 'arraybuffer' | 'blob' | 'json' | 'text' = '';
- // TODO : not suppoted ATM
- _responseURL : null = '';
- _responseXML : null = '';
- _status : number = 0;
- _statusText : string = '';
- _timeout : number = 60000;
- _sendFlag : boolean = false;
- _uploadStarted : boolean = false;
- _increment : boolean = false;
-
- // RNFetchBlob compatible data structure
- _config : RNFetchBlobConfig = {};
- _url : any;
- _method : string;
- _headers: any = {
- 'Content-Type' : 'text/plain'
- };
- _cleanUp : () => void = null;
- _body: any;
-
- // RNFetchBlob promise object, which has `progress`, `uploadProgress`, and
- // `cancel` methods.
- _task: any;
-
- // constants
- get UNSENT() { return UNSENT }
- get OPENED() { return OPENED }
- get HEADERS_RECEIVED() { return HEADERS_RECEIVED }
- get LOADING() { return LOADING }
- get DONE() { return DONE }
-
- static get UNSENT() {
- return UNSENT
- }
-
- static get OPENED() {
- return OPENED
- }
-
- static get HEADERS_RECEIVED() {
- return HEADERS_RECEIVED
- }
-
- static get LOADING() {
- return LOADING
- }
-
- static get DONE() {
- return DONE
- }
-
- static setLog(level:number) {
- if(level === -1)
- log.disable()
- else
- log.level(level)
- }
-
- static addBinaryContentType(substr:string) {
- for(let i in XMLHttpRequest.binaryContentTypes) {
- if(new RegExp(substr,'i').test(XMLHttpRequest.binaryContentTypes[i])) {
- return
- }
- }
- XMLHttpRequest.binaryContentTypes.push(substr)
-
- }
-
- static removeBinaryContentType(val) {
- for(let i in XMLHttpRequest.binaryContentTypes) {
- if(new RegExp(substr,'i').test(XMLHttpRequest.binaryContentTypes[i])) {
- XMLHttpRequest.binaryContentTypes.splice(i,1)
- return
- }
- }
- }
-
- constructor() {
- log.verbose('XMLHttpRequest constructor called')
- super()
- }
-
-
- /**
- * XMLHttpRequest.open, always async, user and password not supported. When
- * this method invoked, headers should becomes empty again.
- * @param {string} method Request method
- * @param {string} url Request URL
- * @param {true} async Always async
- * @param {any} user NOT SUPPORTED
- * @param {any} password NOT SUPPORTED
- */
- open(method:string, url:string, async:true, user:any, password:any) {
- log.verbose('XMLHttpRequest open ', method, url, async, user, password)
- this._method = method
- this._url = url
- this._headers = {}
- this._increment = URIUtil.isJSONStreamURI(this._url)
- this._url = this._url.replace(/^JSONStream\:\/\//, '')
- this._dispatchReadStateChange(XMLHttpRequest.OPENED)
- }
-
- /**
- * Invoke this function to send HTTP request, and set body.
- * @param {any} body Body in RNfetchblob flavor
- */
- send(body) {
-
- this._body = body
-
- if(this._readyState !== XMLHttpRequest.OPENED)
- throw 'InvalidStateError : XMLHttpRequest is not opened yet.'
- let promise = Promise.resolve()
- this._sendFlag = true
- log.verbose('XMLHttpRequest send ', body)
- let {_method, _url, _headers } = this
- log.verbose('sending request with args', _method, _url, _headers, body)
- log.verbose(typeof body, body instanceof FormData)
-
- if(body instanceof Blob) {
- log.debug('sending blob body', body._blobCreated)
- promise = new Promise((resolve, reject) => {
- body.onCreated((blob) => {
- // when the blob is derived (not created by RN developer), the blob
- // will be released after XMLHttpRequest sent
- if(blob.isDerived) {
- this._cleanUp = () => {
- blob.close()
- }
- }
- log.debug('body created send request')
- body = RNFetchBlob.wrap(blob.getRNFetchBlobRef())
- resolve()
- })
- })
- }
- else if(typeof body === 'object') {
- body = JSON.stringify(body)
- promise = Promise.resolve()
- }
- else {
- body = body ? body.toString() : body
- promise = Promise.resolve()
- }
-
- promise.then(() => {
- log.debug('send request invoke', body)
- for(let h in _headers) {
- _headers[h] = _headers[h].toString()
- }
-
- this._task = RNFetchBlob
- .config({
- auto: true,
- timeout : this._timeout,
- increment : this._increment,
- binaryContentTypes : XMLHttpRequest.binaryContentTypes
- })
- .fetch(_method, _url, _headers, body)
- this._task
- .stateChange(this._headerReceived)
- .uploadProgress(this._uploadProgressEvent)
- .progress(this._progressEvent)
- .catch(this._onError)
- .then(this._onDone)
-
- })
- }
-
- overrideMimeType(mime:string) {
- log.verbose('XMLHttpRequest overrideMimeType', mime)
- this._headers['Content-Type'] = mime
- }
-
- setRequestHeader(name, value) {
- log.verbose('XMLHttpRequest set header', name, value)
- if(this._readyState !== OPENED || this._sendFlag) {
- throw `InvalidStateError : Calling setRequestHeader in wrong state ${this._readyState}`
- }
- // UNICODE SHOULD NOT PASS
- if(typeof name !== 'string' || /[^\u0000-\u00ff]/.test(name)) {
- throw 'TypeError : header field name should be a string'
- }
- //
- let invalidPatterns = [
- /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
- /tt/
- ]
- for(let i in invalidPatterns) {
- if(invalidPatterns[i].test(name) || typeof name !== 'string') {
- throw `SyntaxError : Invalid header field name ${name}`
- }
- }
- this._headers[name] = value
- }
-
- abort() {
- log.verbose('XMLHttpRequest abort ')
- if(!this._task)
- return
- this._task.cancel((err) => {
- let e = {
- timeStamp : Date.now(),
- }
- if(this.onabort)
- this.onabort()
- if(err) {
- e.detail = err
- e.type = 'error'
- this.dispatchEvent('error', e)
- }
- else {
- e.type = 'abort'
- this.dispatchEvent('abort', e)
- }
- })
- }
-
- getResponseHeader(field:string):string | null {
- log.verbose('XMLHttpRequest get header', field, this._responseHeaders)
- if(!this._responseHeaders)
- return null
- return (this._responseHeaders[field] || this._responseHeaders[field.toLowerCase()]) || null
-
- }
-
- getAllResponseHeaders():string | null {
- log.verbose('XMLHttpRequest get all headers', this._responseHeaders)
- if(!this._responseHeaders)
- return ''
- let result = ''
- let respHeaders = this.responseHeaders
- for(let i in respHeaders) {
- result += `${i}: ${respHeaders[i]}${String.fromCharCode(0x0D,0x0A)}`
- }
- return result.substr(0, result.length-2)
- }
-
- _headerReceived = (e) => {
- log.debug('header received ', this._task.taskId, e)
- this.responseURL = this._url
- if(e.state === "2") {
- this._responseHeaders = e.headers
- this._statusText = e.status
- this._status = Math.floor(e.status)
- this._dispatchReadStateChange(XMLHttpRequest.HEADERS_RECEIVED)
- }
- }
-
- _uploadProgressEvent = (send:number, total:number) => {
- if(!this._uploadStarted) {
- this.upload.dispatchEvent('loadstart')
- this._uploadStarted = true
- }
- if(send >= total)
- this.upload.dispatchEvent('load')
- this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total))
- }
-
- _progressEvent = (send:number, total:number, chunk:string) => {
- log.verbose(this.readyState)
- if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED)
- this._dispatchReadStateChange(XMLHttpRequest.LOADING)
- let lengthComputable = false
- if(total && total >= 0)
- lengthComputable = true
- let e = new ProgressEvent(lengthComputable, send, total)
-
- if(this._increment) {
- this._responseText += chunk
- }
- this.dispatchEvent('progress', e)
- }
-
- _onError = (err) => {
- let statusCode = Math.floor(this.status)
- if(statusCode >= 100 && statusCode !== 408) {
- return
- }
- log.debug('XMLHttpRequest error', err)
- this._statusText = err
- this._status = String(err).match(/\d+/)
- this._status = this._status ? Math.floor(this.status) : 404
- this._dispatchReadStateChange(XMLHttpRequest.DONE)
- if(err && String(err.message).match(/(timed\sout|timedout)/) || this._status == 408) {
- this.dispatchEvent('timeout')
- }
- this.dispatchEvent('loadend')
- this.dispatchEvent('error', {
- type : 'error',
- detail : err
- })
- this.clearEventListeners()
- }
-
- _onDone = (resp) => {
- log.debug('XMLHttpRequest done', this._url, resp, this)
- this._statusText = this._status
- let responseDataReady = () => {
- log.debug('request done state = 4')
- this.dispatchEvent('load')
- this.dispatchEvent('loadend')
- this._dispatchReadStateChange(XMLHttpRequest.DONE)
- this.clearEventListeners()
- }
- if(resp) {
- let info = resp.respInfo || {}
- log.debug(this._url, info, info.respType)
- switch(this._responseType) {
- case 'blob' :
- resp.blob().then((b) => {
- this._responseText = resp.text()
- this._response = b
- responseDataReady()
- })
- break;
- case 'arraybuffer':
- // TODO : to array buffer
- break
- case 'json':
- this._response = resp.json()
- this._responseText = resp.text()
- break
- default :
- this._responseText = resp.text()
- this._response = this.responseText
- responseDataReady()
- break;
- }
- }
-
- }
-
- _dispatchReadStateChange(state) {
- this._readyState = state
- if(typeof this._onreadystatechange === 'function')
- this._onreadystatechange()
- }
-
- set onreadystatechange(fn:() => void) {
- log.verbose('XMLHttpRequest set onreadystatechange', fn)
- this._onreadystatechange = fn
- }
-
- get onreadystatechange() {
- return this._onreadystatechange
- }
-
- get readyState() {
- log.verbose('get readyState', this._readyState)
- return this._readyState
- }
-
- get status() {
- log.verbose('get status', this._status)
- return this._status
- }
-
- get statusText() {
- log.verbose('get statusText', this._statusText)
- return this._statusText
- }
-
- get response() {
- log.verbose('get response', this._response)
- return this._response
- }
-
- get responseText() {
- log.verbose('get responseText', this._responseText)
- return this._responseText
- }
-
- get responseURL() {
- log.verbose('get responseURL', this._responseURL)
- return this._responseURL
- }
-
- get responseHeaders() {
- log.verbose('get responseHeaders', this._responseHeaders)
- return this._responseHeaders
- }
-
- set timeout(val) {
- this._timeout = val*1000
- log.verbose('set timeout', this._timeout)
- }
-
- get timeout() {
- log.verbose('get timeout', this._timeout)
- return this._timeout
- }
-
- set responseType(val) {
- log.verbose('set response type', this._responseType)
- this._responseType = val
- }
-
- get responseType() {
- log.verbose('get response type', this._responseType)
- return this._responseType
- }
-
- static get isRNFBPolyfill() {
- return true
- }
-
- }
|