88 * @flow
99 */
1010
11+ import type { ImageSource , LoadRequest } from '../../modules/ImageLoader' ;
1112import type { ImageProps } from './types' ;
1213
1314import * as React from 'react' ;
@@ -151,6 +152,23 @@ function resolveAssetUri(source): ?string {
151152 return uri ;
152153}
153154
155+ function raiseOnErrorEvent ( uri , { onError, onLoadEnd } ) {
156+ if ( onError ) {
157+ onError ( {
158+ nativeEvent : {
159+ error : `Failed to load resource ${ uri } (404)`
160+ }
161+ } ) ;
162+ }
163+ if ( onLoadEnd ) onLoadEnd ( ) ;
164+ }
165+
166+ function hasSourceDiff ( a : ImageSource , b : ImageSource ) {
167+ return (
168+ a . uri !== b . uri || JSON . stringify ( a . headers ) !== JSON . stringify ( b . headers )
169+ ) ;
170+ }
171+
154172interface ImageStatics {
155173 getSize : (
156174 uri : string ,
@@ -163,10 +181,12 @@ interface ImageStatics {
163181 ) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
164182}
165183
166- const Image : React . AbstractComponent <
184+ type ImageComponent = React . AbstractComponent <
167185 ImageProps ,
168186 React . ElementRef < typeof View >
169- > = React . forwardRef ( ( props , ref ) => {
187+ > ;
188+
189+ const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
170190 const {
171191 accessibilityLabel,
172192 blurRadius,
@@ -284,16 +304,7 @@ const Image: React.AbstractComponent<
284304 } ,
285305 function error ( ) {
286306 updateState ( ERRORED ) ;
287- if ( onError ) {
288- onError ( {
289- nativeEvent : {
290- error : `Failed to load resource ${ uri } (404)`
291- }
292- } ) ;
293- }
294- if ( onLoadEnd ) {
295- onLoadEnd ( ) ;
296- }
307+ raiseOnErrorEvent ( uri , { onError, onLoadEnd } ) ;
297308 }
298309 ) ;
299310 }
@@ -337,14 +348,76 @@ const Image: React.AbstractComponent<
337348 ) ;
338349} ) ;
339350
340- Image . displayName = 'Image' ;
351+ BaseImage . displayName = 'Image' ;
352+
353+ /**
354+ * This component handles specifically loading an image source with headers
355+ * default source is never loaded using headers
356+ */
357+ const ImageWithHeaders : ImageComponent = React . forwardRef ( ( props , ref ) => {
358+ // $FlowIgnore: This component would only be rendered when `source` matches `ImageSource`
359+ const nextSource : ImageSource = props . source ;
360+ const [ blobUri , setBlobUri ] = React . useState ( '' ) ;
361+ const request = React . useRef < LoadRequest > ( {
362+ cancel : ( ) => { } ,
363+ source : { uri : '' , headers : { } } ,
364+ promise : Promise . resolve ( '' )
365+ } ) ;
366+
367+ const { onError, onLoadStart, onLoadEnd } = props ;
368+
369+ React . useEffect ( ( ) => {
370+ if ( ! hasSourceDiff ( nextSource , request . current . source ) ) {
371+ return ;
372+ }
373+
374+ // When source changes we want to clean up any old/running requests
375+ request . current . cancel ( ) ;
376+
377+ if ( onLoadStart ) {
378+ onLoadStart ( ) ;
379+ }
380+
381+ // Store a ref for the current load request so we know what's the last loaded source,
382+ // and so we can cancel it if a different source is passed through props
383+ request . current = ImageLoader . loadWithHeaders ( nextSource ) ;
384+
385+ request . current . promise
386+ . then ( ( uri ) => setBlobUri ( uri ) )
387+ . catch ( ( ) =>
388+ raiseOnErrorEvent ( request . current . source . uri , { onError, onLoadEnd } )
389+ ) ;
390+ } , [ nextSource , onLoadStart , onError , onLoadEnd ] ) ;
391+
392+ // Cancel any request on unmount
393+ React . useEffect ( ( ) => request . current . cancel , [ ] ) ;
394+
395+ const propsToPass = {
396+ ...props ,
397+
398+ // `onLoadStart` is called from the current component
399+ // We skip passing it down to prevent BaseImage raising it a 2nd time
400+ onLoadStart : undefined ,
401+
402+ // Until the current component resolves the request (using headers)
403+ // we skip forwarding the source so the base component doesn't attempt
404+ // to load the original source
405+ source : blobUri ? { ...nextSource , uri : blobUri } : undefined
406+ } ;
407+
408+ return < BaseImage ref = { ref } { ...propsToPass } /> ;
409+ } ) ;
341410
342411// $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 ) ;
412+ const ImageWithStatics : ImageComponent & ImageStatics = React . forwardRef (
413+ ( props , ref ) => {
414+ if ( props . source && props . source . headers ) {
415+ return < ImageWithHeaders ref = { ref } { ...props } /> ;
416+ }
417+
418+ return < BaseImage ref = { ref } { ...props } /> ;
419+ }
420+ ) ;
348421
349422ImageWithStatics . getSize = function ( uri , success , failure ) {
350423 ImageLoader . getSize ( uri , success , failure ) ;
0 commit comments