diff --git a/src/native-common/Image.tsx b/src/native-common/Image.tsx index 31ab51a0..1a78cf50 100644 --- a/src/native-common/Image.tsx +++ b/src/native-common/Image.tsx @@ -34,6 +34,7 @@ export interface ImageContext { export interface ImageState { forceCache?: boolean; lastNativeError?: any; + headers?: Types.Headers; } export class Image extends React.Component implements React.ChildContextProvider { @@ -64,7 +65,7 @@ export class Image extends React.Component impleme protected _mountedComponent: RN.Image | null = null; private _nativeImageWidth: number | undefined; private _nativeImageHeight: number | undefined; - readonly state: ImageState = { forceCache: false, lastNativeError: undefined }; + readonly state: ImageState = { forceCache: false, lastNativeError: undefined, headers: this._buildHeaders() }; protected _getAdditionalProps(): RN.ImageProperties | {} { return {}; @@ -121,7 +122,7 @@ export class Image extends React.Component impleme const sourceOrHeaderChanged = (nextProps.source !== this.props.source || !_.isEqual(nextProps.headers || {}, this.props.headers || {})); if (sourceOrHeaderChanged) { - this.setState({ forceCache: false, lastNativeError: undefined }); + this.setState({ forceCache: false, lastNativeError: undefined, headers: this._buildHeaders() }); } } @@ -180,7 +181,7 @@ export class Image extends React.Component impleme return; } - if (!this.state.forceCache && this._shouldForceCacheOnError()) { + if (!this.state.forceCache && !!this._getMaxStaleHeader()) { // Some platforms will not use expired cache data unless explicitly told so. // Let's try again with cache: 'only-if-cached'. this.setState({ forceCache: true, lastNativeError: e.nativeEvent.error }); @@ -194,16 +195,26 @@ export class Image extends React.Component impleme } } + private _buildHeaders(): Types.Headers|undefined { + if (this.props.headers) { + const cacheControlHeader = this._getMaxStaleHeader(); + if (cacheControlHeader) { + // Filter out Cache-Control: max-stale. It has the opposite effect on iOS: instead of having + // the cache return stale data it disables the cache altogether. We emulate the header by + // retrying with cache: 'only-if-cached'. + return _.omit(this.props.headers, [cacheControlHeader]); + } + } + return this.props.headers; + } + private _buildSource(): RN.ImageSourcePropType { // Check if require'd image resource if (typeof this.props.source === 'number') { return this.props.source; } - const source: RN.ImageSourcePropType = { uri: this.props.source }; - if (this.props.headers) { - source.headers = this.props.headers; - } + const source: RN.ImageSourcePropType = { uri: this.props.source, headers: this.state.headers }; if (this.state.forceCache) { source.cache = 'only-if-cached'; } @@ -211,20 +222,17 @@ export class Image extends React.Component impleme return source; } - private _shouldForceCacheOnError(): boolean { - if (Platform.getType() !== 'ios') { - return false; - } - if (this.props.headers) { + private _getMaxStaleHeader(): string|undefined { + if (Platform.getType() === 'ios' && this.props.headers) { for (let key in this.props.headers) { // We don't know how stale the cached data is so we're matching only the simple 'max-stale' attribute // without a value. if (key.toLowerCase() === 'cache-control' && this.props.headers[key].toLowerCase() === 'max-stale') { - return true; + return key; } } } - return false; + return undefined; } // Note: This works only if you have an onLoaded handler and wait for the image to load.