Filter out emulated Cache-Control: max-stale on iOS (#870)

* Filter out emulated Cache-Control: max-stale on iOS

The header has the opposite effect on iOS: instead of having the cache
return stale data it disables the cache altogether. The header is
emulated by the component as of #853. This change adds filtering so that
RN doesn't see the header.

* PR feedback: add headers to state

* PR feedback: use _.omit()
This commit is contained in:
Ladi Prosek 2018-10-23 18:43:26 +02:00 коммит произвёл Eric Traut
Родитель e1d8be8b7a
Коммит 71f7682712
1 изменённых файлов: 22 добавлений и 14 удалений

Просмотреть файл

@ -34,6 +34,7 @@ export interface ImageContext {
export interface ImageState {
forceCache?: boolean;
lastNativeError?: any;
headers?: Types.Headers;
}
export class Image extends React.Component<Types.ImageProps, ImageState> implements React.ChildContextProvider<ImageContext> {
@ -64,7 +65,7 @@ export class Image extends React.Component<Types.ImageProps, ImageState> 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<Types.ImageProps, ImageState> 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<Types.ImageProps, ImageState> 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<Types.ImageProps, ImageState> 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<Types.ImageProps, ImageState> 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.