88 * @flow
99 */
1010
11+ import type { ImageSource , LoadRequest } from '../../modules/ImageLoader' ;
1112import type { ImageProps } from './types' ;
1213
1314import * as React from 'react' ;
@@ -165,6 +166,23 @@ function resolveAssetUri(source): ?string {
165166 return uri ;
166167}
167168
169+ function raiseOnErrorEvent ( uri , { onError, onLoadEnd } ) {
170+ if ( onError ) {
171+ onError ( {
172+ nativeEvent : {
173+ error : `Failed to load resource ${ uri } (404)`
174+ }
175+ } ) ;
176+ }
177+ if ( onLoadEnd ) onLoadEnd ( ) ;
178+ }
179+
180+ function hasSourceDiff ( a : ImageSource , b : ImageSource ) {
181+ return (
182+ a . uri !== b . uri || JSON . stringify ( a . headers ) !== JSON . stringify ( b . headers )
183+ ) ;
184+ }
185+
168186interface ImageStatics {
169187 getSize : (
170188 uri : string ,
@@ -177,10 +195,12 @@ interface ImageStatics {
177195 ) => Promise < { | [ uri : string ] : 'disk/memory' | } > ;
178196}
179197
180- const Image : React . AbstractComponent <
198+ type ImageComponent = React . AbstractComponent <
181199 ImageProps ,
182200 React . ElementRef < typeof View >
183- > = React . forwardRef ( ( props , ref ) => {
201+ > ;
202+
203+ const BaseImage : ImageComponent = React . forwardRef ( ( props , ref ) => {
184204 const {
185205 'aria-label' : ariaLabel ,
186206 blurRadius,
@@ -300,16 +320,7 @@ const Image: React.AbstractComponent<
300320 } ,
301321 function error ( ) {
302322 updateState ( ERRORED ) ;
303- if ( onError ) {
304- onError ( {
305- nativeEvent : {
306- error : `Failed to load resource ${ uri } (404)`
307- }
308- } ) ;
309- }
310- if ( onLoadEnd ) {
311- onLoadEnd ( ) ;
312- }
323+ raiseOnErrorEvent ( uri , { onError, onLoadEnd } ) ;
313324 }
314325 ) ;
315326 }
@@ -353,14 +364,76 @@ const Image: React.AbstractComponent<
353364 ) ;
354365} ) ;
355366
356- Image . displayName = 'Image' ;
367+ BaseImage . displayName = 'Image' ;
368+
369+ /**
370+ * This component handles specifically loading an image source with headers
371+ * default source is never loaded using headers
372+ */
373+ const ImageWithHeaders : ImageComponent = React . forwardRef ( ( props , ref ) => {
374+ // $FlowIgnore: This component would only be rendered when `source` matches `ImageSource`
375+ const nextSource : ImageSource = props . source ;
376+ const [ blobUri , setBlobUri ] = React . useState ( '' ) ;
377+ const request = React . useRef < LoadRequest > ( {
378+ cancel : ( ) => { } ,
379+ source : { uri : '' , headers : { } } ,
380+ promise : Promise . resolve ( '' )
381+ } ) ;
382+
383+ const { onError, onLoadStart, onLoadEnd } = props ;
384+
385+ React . useEffect ( ( ) => {
386+ if ( ! hasSourceDiff ( nextSource , request . current . source ) ) {
387+ return ;
388+ }
389+
390+ // When source changes we want to clean up any old/running requests
391+ request . current . cancel ( ) ;
392+
393+ if ( onLoadStart ) {
394+ onLoadStart ( ) ;
395+ }
396+
397+ // Store a ref for the current load request so we know what's the last loaded source,
398+ // and so we can cancel it if a different source is passed through props
399+ request . current = ImageLoader . loadWithHeaders ( nextSource ) ;
400+
401+ request . current . promise
402+ . then ( ( uri ) => setBlobUri ( uri ) )
403+ . catch ( ( ) =>
404+ raiseOnErrorEvent ( request . current . source . uri , { onError, onLoadEnd } )
405+ ) ;
406+ } , [ nextSource , onLoadStart , onError , onLoadEnd ] ) ;
407+
408+ // Cancel any request on unmount
409+ React . useEffect ( ( ) => request . current . cancel , [ ] ) ;
410+
411+ const propsToPass = {
412+ ...props ,
413+
414+ // `onLoadStart` is called from the current component
415+ // We skip passing it down to prevent BaseImage raising it a 2nd time
416+ onLoadStart : undefined ,
417+
418+ // Until the current component resolves the request (using headers)
419+ // we skip forwarding the source so the base component doesn't attempt
420+ // to load the original source
421+ source : blobUri ? { ...nextSource , uri : blobUri } : undefined
422+ } ;
423+
424+ return < BaseImage ref = { ref } { ...propsToPass } /> ;
425+ } ) ;
357426
358427// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet
359- const ImageWithStatics = ( Image : React . AbstractComponent <
360- ImageProps ,
361- React . ElementRef < typeof View >
362- > &
363- ImageStatics ) ;
428+ const ImageWithStatics : ImageComponent & ImageStatics = React . forwardRef (
429+ ( props , ref ) => {
430+ if ( props . source && props . source . headers ) {
431+ return < ImageWithHeaders ref = { ref } { ...props } /> ;
432+ }
433+
434+ return < BaseImage ref = { ref } { ...props } /> ;
435+ }
436+ ) ;
364437
365438ImageWithStatics . getSize = function ( uri , success , failure ) {
366439 ImageLoader . getSize ( uri , success , failure ) ;
0 commit comments