暂无描述

XMLHttpRequest.js 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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. import RNFetchBlob from '../index.js'
  5. import XMLHttpRequestEventTarget from './XMLHttpRequestEventTarget.js'
  6. import Log from '../utils/log.js'
  7. import Blob from './Blob.js'
  8. import ProgressEvent from './ProgressEvent.js'
  9. const log = new Log('XMLHttpRequest')
  10. log.level(3)
  11. const UNSENT = 0
  12. const OPENED = 1
  13. const HEADERS_RECEIVED = 2
  14. const LOADING = 3
  15. const DONE = 4
  16. export default class XMLHttpRequest extends XMLHttpRequestEventTarget{
  17. _onreadystatechange : () => void;
  18. upload : XMLHttpRequestEventTarget = new XMLHttpRequestEventTarget();
  19. // readonly
  20. _readyState : number = UNSENT;
  21. _response : any = '';
  22. _responseText : any = null;
  23. _responseHeaders : any = {};
  24. _responseType : '' | 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' = '';
  25. // TODO : not suppoted ATM
  26. _responseURL : null = '';
  27. _responseXML : null = '';
  28. _status : number = 0;
  29. _statusText : string = '';
  30. _timeout : number = 0;
  31. _sendFlag : boolean = false;
  32. _uploadStarted : boolean = false;
  33. // RNFetchBlob compatible data structure
  34. _config : RNFetchBlobConfig = {};
  35. _url : any;
  36. _method : string;
  37. _headers: any = {
  38. 'Content-Type' : 'text/plain'
  39. };
  40. _body: any;
  41. // RNFetchBlob promise object, which has `progress`, `uploadProgress`, and
  42. // `cancel` methods.
  43. _task: any;
  44. // constants
  45. get UNSENT() { return UNSENT }
  46. get OPENED() { return OPENED }
  47. get HEADERS_RECEIVED() { return HEADERS_RECEIVED }
  48. get LOADING() { return LOADING }
  49. get DONE() { return DONE }
  50. static get UNSENT() {
  51. return UNSENT
  52. }
  53. static get OPENED() {
  54. return OPENED
  55. }
  56. static get HEADERS_RECEIVED() {
  57. return HEADERS_RECEIVED
  58. }
  59. static get LOADING() {
  60. return LOADING
  61. }
  62. static get DONE() {
  63. return DONE
  64. }
  65. constructor() {
  66. super()
  67. log.verbose('XMLHttpRequest constructor called')
  68. }
  69. /**
  70. * XMLHttpRequest.open, always async, user and password not supported. When
  71. * this method invoked, headers should becomes empty again.
  72. * @param {string} method Request method
  73. * @param {string} url Request URL
  74. * @param {true} async Always async
  75. * @param {any} user NOT SUPPORTED
  76. * @param {any} password NOT SUPPORTED
  77. */
  78. open(method:string, url:string, async:true, user:any, password:any) {
  79. log.verbose('XMLHttpRequest open ', method, url, async, user, password)
  80. this._method = method
  81. this._url = url
  82. this._headers = {}
  83. this._dispatchReadStateChange(XMLHttpRequest.OPENED)
  84. }
  85. /**
  86. * Invoke this function to send HTTP request, and set body.
  87. * @param {any} body Body in RNfetchblob flavor
  88. */
  89. send(body) {
  90. if(this._readyState !== XMLHttpRequest.OPENED)
  91. throw 'InvalidStateError : XMLHttpRequest is not opened yet.'
  92. this._sendFlag = true
  93. log.verbose('XMLHttpRequest send ', body)
  94. let {_method, _url, _headers } = this
  95. log.verbose('sending request with args', _method, _url, _headers, body)
  96. log.verbose(typeof body, body instanceof FormData)
  97. if(body instanceof Blob) {
  98. body = RNFetchBlob.wrap(body.getRNFetchBlobRef())
  99. }
  100. else if(typeof body === 'object') {
  101. body = JSON.stringify(body)
  102. }
  103. this._task = RNFetchBlob
  104. .config({ auto: true, timeout : this._timeout })
  105. .fetch(_method, _url, _headers, body)
  106. this.dispatchEvent('load')
  107. this._task
  108. .stateChange(this._headerReceived.bind(this))
  109. .uploadProgress(this._uploadProgressEvent.bind(this))
  110. .progress(this._progressEvent.bind(this))
  111. .catch(this._onError.bind(this))
  112. .then(this._onDone.bind(this))
  113. }
  114. overrideMimeType(mime:string) {
  115. log.verbose('XMLHttpRequest overrideMimeType', mime)
  116. this._headers['Content-Type'] = mime
  117. }
  118. setRequestHeader(name, value) {
  119. log.verbose('XMLHttpRequest set header', name, value)
  120. if(this._readyState !== OPENED || this._sendFlag) {
  121. throw `InvalidStateError : Calling setRequestHeader in wrong state ${this._readyState}`
  122. }
  123. // UNICODE SHOULD NOT PASS
  124. if(typeof name !== 'string' || /[^\u0000-\u00ff]/.test(name)) {
  125. throw 'TypeError : header field name should be a string'
  126. }
  127. //
  128. let invalidPatterns = [
  129. /[\(\)\>\<\@\,\:\\\/\[\]\?\=\}\{\s\ \u007f\;\t\0\v\r]/,
  130. /tt/
  131. ]
  132. for(let i in invalidPatterns) {
  133. if(invalidPatterns[i].test(name) || typeof name !== 'string') {
  134. throw `SyntaxError : Invalid header field name ${name}`
  135. }
  136. }
  137. this._headers[name] = value
  138. }
  139. abort() {
  140. log.verbose('XMLHttpRequest abort ')
  141. if(!this._task)
  142. return
  143. this._task.cancel((err) => {
  144. let e = {
  145. timeStamp : Date.now(),
  146. }
  147. if(this.onabort)
  148. this.onabort()
  149. if(err) {
  150. e.detail = err
  151. e.type = 'error'
  152. this.dispatchEvent('error', e)
  153. }
  154. else {
  155. e.type = 'abort'
  156. this.dispatchEvent('abort', e)
  157. }
  158. })
  159. }
  160. getResponseHeader(field:string):string | null {
  161. log.verbose('XMLHttpRequest get header', field)
  162. if(!this._responseHeaders)
  163. return null
  164. return this.responseHeaders[field] || null
  165. }
  166. getAllResponseHeaders():string | null {
  167. log.verbose('XMLHttpRequest get all headers', this._responseHeaders)
  168. if(!this._responseHeaders)
  169. return ''
  170. let result = ''
  171. let respHeaders = this.responseHeaders
  172. for(let i in respHeaders) {
  173. result += `${i}:${respHeaders[i]}\r\n`
  174. }
  175. return result
  176. }
  177. _headerReceived(e) {
  178. log.verbose('header received ', this._task.taskId, e)
  179. this.responseURL = this._url
  180. this.upload.dispatchEvent('loadend')
  181. this.dispatchEvent('load')
  182. if(e.state === "2") {
  183. this._responseHeaders = e.headers
  184. this._statusText = e.status
  185. this._responseType = e.respType || ''
  186. this._status = Math.floor(e.status)
  187. this._dispatchReadStateChange(XMLHttpRequest.HEADERS_RECEIVED)
  188. }
  189. }
  190. _uploadProgressEvent(send:number, total:number) {
  191. console.log('_upload', this.upload)
  192. if(!this._uploadStarted) {
  193. this.upload.dispatchEvent('loadstart')
  194. this._uploadStarted = true
  195. }
  196. if(send >= total)
  197. this.upload.dispatchEvent('load')
  198. this.upload.dispatchEvent('progress', new ProgressEvent(true, send, total))
  199. }
  200. _progressEvent(send:number, total:number) {
  201. log.verbose(this.readyState)
  202. if(this._readyState === XMLHttpRequest.HEADERS_RECEIVED)
  203. this._dispatchReadStateChange(XMLHttpRequest.LOADING)
  204. let lengthComputable = false
  205. if(total && total >= 0)
  206. lengthComputable = true
  207. let e = new ProgressEvent(lengthComputable, send, total)
  208. this.dispatchEvent('progress', e)
  209. }
  210. _onError(err) {
  211. log.verbose('XMLHttpRequest error', err)
  212. this._statusText = err
  213. this._status = String(err).match(/\d+/)
  214. this._status = this._status ? Math.floor(this.status) : 404
  215. this._dispatchReadStateChange(XMLHttpRequest.DONE)
  216. if(err && String(err.message).match(/(timed\sout|timedout)/)) {
  217. this.dispatchEvent('timeout')
  218. }
  219. this.dispatchEvent('loadend')
  220. this.dispatchEvent('error', {
  221. type : 'error',
  222. detail : err
  223. })
  224. this.clearEventListeners()
  225. }
  226. _onDone(resp) {
  227. log.verbose('XMLHttpRequest done', this._url, resp)
  228. this._statusText = this._status
  229. if(resp) {
  230. switch(resp.type) {
  231. case 'base64' :
  232. if(this._responseType === 'json') {
  233. this._responseText = resp.text()
  234. this._response = resp.json()
  235. }
  236. else {
  237. this._responseText = resp.text()
  238. this._response = this.responseText
  239. }
  240. break;
  241. case 'path' :
  242. this.response = resp.blob()
  243. break;
  244. default :
  245. this._responseText = resp.text()
  246. this._response = this.responseText
  247. break;
  248. }
  249. this.dispatchEvent('loadend')
  250. this._dispatchReadStateChange(XMLHttpRequest.DONE)
  251. }
  252. this.clearEventListeners()
  253. }
  254. _dispatchReadStateChange(state) {
  255. this._readyState = state
  256. if(typeof this._onreadystatechange === 'function')
  257. this._onreadystatechange()
  258. }
  259. set onreadystatechange(fn:() => void) {
  260. log.verbose('XMLHttpRequest set onreadystatechange', fn.toString())
  261. this._onreadystatechange = fn
  262. }
  263. get onreadystatechange(fn:() => void) {
  264. return this._onreadystatechange
  265. }
  266. get readyState() {
  267. log.verbose('get readyState', this._readyState)
  268. return this._readyState
  269. }
  270. get status() {
  271. log.verbose('get status', this._status)
  272. return this._status
  273. }
  274. get statusText() {
  275. log.verbose('get statusText', this._statusText)
  276. return this._statusText
  277. }
  278. get response() {
  279. log.verbose('get response', this._response)
  280. return this._response
  281. }
  282. get responseText() {
  283. log.verbose('get responseText', this._responseText)
  284. return this._responseText
  285. }
  286. get responseURL() {
  287. log.verbose('get responseURL', this._responseURL)
  288. return this._responseURL
  289. }
  290. get responseHeaders() {
  291. log.verbose('get responseHeaders', this._responseHeaders)
  292. return this._responseHeaders
  293. }
  294. set timeout(val) {
  295. log.verbose('set timeout', this._timeout)
  296. this._timeout = val
  297. }
  298. get timeout() {
  299. log.verbose('get timeout', this._timeout)
  300. return this._timeout
  301. }
  302. get responseType() {
  303. log.verbose('get response type', this._responseType)
  304. return this._responseType
  305. }
  306. }