説明なし

index.js 6.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /**
  2. * @author wkh237
  3. * @version 0.5.0
  4. * @flow
  5. */
  6. import {
  7. NativeModules,
  8. DeviceEventEmitter,
  9. NativeAppEventEmitter,
  10. Platform,
  11. } from 'react-native'
  12. import type {
  13. RNFetchBlobNative,
  14. RNFetchBlobConfig,
  15. RNFetchBlobStream
  16. }from './types'
  17. import base64 from 'base-64'
  18. // const emitter = (Platform.OS === 'android' ? DeviceEventEmitter : NativeAppEventEmitter)
  19. const emitter = DeviceEventEmitter
  20. const RNFetchBlob:RNFetchBlobNative = NativeModules.RNFetchBlob
  21. emitter.addListener("RNFetchBlobMessage", (e) => {
  22. if(e.event === 'warn') {
  23. console.warn(e.detail)
  24. }
  25. else if (e.event === 'error') {
  26. throw e.detail
  27. }
  28. else {
  29. console.log("RNFetchBlob native message", e.detail)
  30. }
  31. })
  32. // Show warning if native module not detected
  33. if(!RNFetchBlob || !RNFetchBlob.fetchBlobForm || !RNFetchBlob.fetchBlob) {
  34. console.warn(
  35. 'react-native-fetch-blob could not find valid native module.',
  36. 'please make sure you have linked native modules using `rnpm link`,',
  37. 'and restart RN packager or manually compile IOS/Android project.'
  38. )
  39. }
  40. function getSystemDirs() {
  41. return new Promise((resolve, reject) => {
  42. try {
  43. RNFetchBlob.getEnvironmentDirs((...dirs) => {
  44. let [PictureDir, MovieDir, DocumentDir, CacheDir] = [...dirs]
  45. resolve({PictureDir, MovieDir, DocumentDir, CacheDir})
  46. })
  47. } catch(err) {
  48. reject(err)
  49. }
  50. })
  51. }
  52. function config (options:RNFetchBlobConfig) {
  53. return { fetch : fetch.bind(options) }
  54. }
  55. // Promise wrapper function
  56. function fetch(...args:any) {
  57. // create task ID for receiving progress event
  58. let taskId = getUUID()
  59. let options = this || {}
  60. let promise = new Promise((resolve, reject) => {
  61. let [method, url, headers, body] = [...args]
  62. let nativeMethodName = Array.isArray(body) ? 'fetchBlobForm' : 'fetchBlob'
  63. // on progress event listener
  64. let subscription = emitter.addListener('RNFetchBlobProgress', (e) => {
  65. if(e.taskId === taskId && promise.onProgress) {
  66. promise.onProgress(e.written, e.total)
  67. }
  68. })
  69. let req = RNFetchBlob[nativeMethodName]
  70. req(options, taskId, method, url, headers || {}, body, (err, ...data) => {
  71. // task done, remove event listener
  72. subscription.remove()
  73. if(err)
  74. reject(new Error(err, ...data))
  75. else {
  76. let respType = 'base64'
  77. if(options.path || options.fileCache)
  78. respType = 'path'
  79. resolve(new FetchBlobResponse(taskId, respType, ...data))
  80. }
  81. })
  82. })
  83. promise.progress = (fn) => {
  84. promise.onProgress = fn
  85. return promise
  86. }
  87. return promise
  88. }
  89. function openReadStream(path:string, encoding:'utf8' | 'ascii' | 'base64'):RNFetchBlobStream {
  90. if(!path)
  91. throw Error('RNFetchBlob could not open file stream with empty `path`')
  92. let stream:RNFetchBlobStream = {
  93. onData : function(fn) {
  94. this._onData = fn
  95. },
  96. onError : function(fn) {
  97. this._onError = fn
  98. },
  99. onEnd : function(fn) {
  100. this._onEnd = fn
  101. },
  102. }
  103. // register for file stream event
  104. let subscription = emitter.addListener(`RNFetchBlobStream+${path}`, (e) => {
  105. let {event, detail} = e
  106. if(stream._onData && event === 'data')
  107. stream._onData(detail)
  108. else if (stream._onEnd && event === 'end') {
  109. stream._onEnd(detail)
  110. }
  111. else {
  112. stream._onError(detail)
  113. }
  114. // when stream closed or error, remove event handler
  115. if (event === 'error' || event === 'end') {
  116. subscription.remove()
  117. }
  118. })
  119. RNFetchBlob.readStream(path, encoding)
  120. return stream
  121. }
  122. /**
  123. * RNFetchBlob response object class.
  124. */
  125. class FetchBlobResponse {
  126. taskId : string;
  127. path : () => string | null;
  128. type : 'base64' | 'path';
  129. data : any;
  130. blob : (contentType:string, sliceSize:number) => null;
  131. text : () => string;
  132. json : () => any;
  133. base64 : () => any;
  134. flush : () => void;
  135. readStream : (
  136. encode: 'utf8' | 'ascii' | 'base64',
  137. ) => RNFetchBlobStream | null;
  138. constructor(taskId:string, type:'base64' | 'path', data:any) {
  139. this.data = data
  140. this.taskId = taskId
  141. this.type = type
  142. /**
  143. * Convert result to javascript Blob object.
  144. * @param {string} contentType MIME type of the blob object.
  145. * @param {number} sliceSize Slice size.
  146. * @return {blob} Return Blob object.
  147. */
  148. this.blob = (contentType:string, sliceSize:number) => {
  149. console.warn('FetchBlobResponse.blob() is deprecated and has no funtionality.')
  150. return null
  151. }
  152. /**
  153. * Convert result to text.
  154. * @return {string} Decoded base64 string.
  155. */
  156. this.text = ():string => {
  157. return base64.decode(this.data)
  158. }
  159. /**
  160. * Convert result to JSON object.
  161. * @return {object} Parsed javascript object.
  162. */
  163. this.json = ():any => {
  164. return JSON.parse(base64.decode(this.data))
  165. }
  166. /**
  167. * Return BASE64 string directly.
  168. * @return {string} BASE64 string of response body.
  169. */
  170. this.base64 = ():string => {
  171. return this.data
  172. }
  173. /**
  174. * Remove cahced file
  175. * @return {void}
  176. */
  177. this.flush = () => {
  178. RNFetchBlob.flush(this.path())
  179. }
  180. /**
  181. * get path of response temp file
  182. * @return {string} File path of temp file.
  183. */
  184. this.path = () => {
  185. if(this.type === 'path')
  186. return this.data
  187. return null
  188. }
  189. /**
  190. * Start read stream from cached file
  191. * @param {String} encoding Encode type, should be one of `base64`, `ascrii`, `utf8`.
  192. * @param {Function} fn On data event handler
  193. * @return {void}
  194. */
  195. this.readStream = (encode: 'base64' | 'utf8' | 'ascii'):RNFetchBlobStream | null => {
  196. if(this.type === 'path') {
  197. return openReadStream(this.data, encode)
  198. }
  199. else {
  200. console.warn('RNFetchblob', 'this response data does not contains any available stream')
  201. return null
  202. }
  203. }
  204. }
  205. }
  206. function getUUID() {
  207. return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
  208. let r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
  209. return v.toString(16);
  210. });
  211. }
  212. export default {
  213. fetch, base64, config, getSystemDirs, openReadStream
  214. }