Ei kuvausta

XMLHttpRequest.js 10KB

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