88 * @flow
99 */
1010
11- import type { ImageProps } from './types' ;
11+ import type { ImageProps , SourceObject } from './types' ;
1212
1313import * as React from 'react' ;
1414import createElement from '../createElement' ;
@@ -151,6 +151,12 @@ function resolveAssetUri(source): ?string {
151151 return uri ;
152152}
153153
154+ function hasSourceDiff ( a : SourceObject , b : SourceObject ) {
155+ return (
156+ a . uri !== b . uri || JSON . stringify ( a . headers ) !== JSON . stringify ( b . headers )
157+ ) ;
158+ }
159+
154160interface ImageStatics {
155161 getSize : (
156162 uri : string ,
@@ -163,10 +169,12 @@ interface ImageStatics {
163169 ) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
164170}
165171
166- const Image : React . AbstractComponent <
172+ type ImageComponent = React . AbstractComponent <
167173 ImageProps ,
168174 React . ElementRef < typeof View >
169- > = React . forwardRef ( ( props , ref ) => {
175+ > ;
176+
177+ const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
170178 const {
171179 accessibilityLabel,
172180 blurRadius,
@@ -337,24 +345,91 @@ const Image: React.AbstractComponent<
337345 ) ;
338346} ) ;
339347
340- Image . displayName = 'Image' ;
348+ /**
349+ * This component handles specifically loading an image source with header
350+ */
351+ const ImageWithHeaders : ImageComponent = React . forwardRef ( ( props , ref ) => {
352+ // $FlowIgnore
353+ const nextSource : SourceObject = props . source ;
354+ const prevSource = React . useRef < SourceObject > ( { } ) ;
355+ const cleanup = React . useRef < Function > ( ( ) => { } ) ;
356+ const [ blobUri , setBlobUri ] = React . useState ( '' ) ;
357+
358+ const { onError, onLoadStart } = props ;
359+
360+ React . useEffect ( ( ) => {
361+ if ( ! hasSourceDiff ( nextSource , prevSource . current ) ) return ;
362+
363+ // When source changes we want to clean up any old/running requests
364+ cleanup . current ( ) ;
365+
366+ prevSource . current = nextSource ;
367+
368+ let uri ;
369+ const abortCtrl = new AbortController ( ) ;
370+ const request = new Request ( nextSource . uri , {
371+ headers : nextSource . headers ,
372+ signal : abortCtrl . signal
373+ } ) ;
374+ request . headers . append ( 'accept' , 'image/*' ) ;
375+
376+ if ( onLoadStart ) onLoadStart ( ) ;
377+
378+ fetch ( request )
379+ . then ( ( response ) => response . blob ( ) )
380+ . then ( ( blob ) => {
381+ uri = URL . createObjectURL ( blob ) ;
382+ setBlobUri ( uri ) ;
383+ } )
384+ . catch ( ( error ) => {
385+ if ( error . name !== 'AbortError' && onError ) {
386+ onError ( { nativeEvent : error . message } ) ;
387+ }
388+ } ) ;
389+
390+ // Capture a cleanup function for the current request
391+ // The reason for using a Ref is to avoid making this function a dependency
392+ // Because the change of a dependency would otherwise would re-trigger a hook
393+ cleanup . current = ( ) => {
394+ abortCtrl . abort ( ) ;
395+ setBlobUri ( '' ) ;
396+ URL . revokeObjectURL ( uri ) ;
397+ } ;
398+ } , [ nextSource , onLoadStart , onError ] ) ;
399+
400+ // Run the cleanup function on unmount
401+ React . useEffect ( ( ) => cleanup . current , [ ] ) ;
402+
403+ const propsToPass = {
404+ ...props ,
405+ // Omit load start because we already triggered it here
406+ onLoadStart : undefined ,
407+ source : { ...nextSource , uri : blobUri }
408+ } ;
409+
410+ return < BaseImage ref = { ref } { ...propsToPass } /> ;
411+ } ) ;
341412
342413// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet
343- const ImageWithStatics = ( Image : React . AbstractComponent <
344- ImageProps ,
345- React . ElementRef < typeof View >
346- > &
347- ImageStatics ) ;
414+ const Image : ImageComponent & ImageStatics = React . forwardRef ( ( props , ref ) => {
415+ if ( props . source && props . source . headers ) {
416+ return < ImageWithHeaders ref = { ref } { ...props } /> ;
417+ }
418+
419+ return < BaseImage ref = { ref } { ...props } /> ;
420+ } ) ;
421+
422+ Image . displayName = 'Image' ;
348423
349- ImageWithStatics . getSize = function ( uri , success , failure ) {
424+ Image . getSize = function ( uri , success , failure ) {
350425 ImageLoader . getSize ( uri , success , failure ) ;
351426} ;
352427
353- ImageWithStatics . prefetch = function ( uri ) {
428+ Image . prefetch = function ( uri ) {
354429 return ImageLoader . prefetch ( uri ) ;
355430} ;
356431
357- ImageWithStatics . queryCache = function ( uris ) {
432+ Image . queryCache = function ( uris ) {
358433 return ImageLoader . queryCache ( uris ) ;
359434} ;
360435
@@ -410,4 +485,4 @@ const resizeModeStyles = StyleSheet.create({
410485 }
411486} ) ;
412487
413- export default ImageWithStatics ;
488+ export default Image ;
0 commit comments