Nenhuma descrição

XMLHttpRequest.js 10KB

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