-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34505 from kidroca/kidroca/feat/attachment-with-h…
…eaders Image Web/Desktop: Add support for http headers
- Loading branch information
Showing
7 changed files
with
290 additions
and
98 deletions.
There are no files selected for viewing
200 changes: 200 additions & 0 deletions
200
patches/react-native-web+0.19.9+005+image-header-support.patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
diff --git a/node_modules/react-native-web/dist/exports/Image/index.js b/node_modules/react-native-web/dist/exports/Image/index.js | ||
index 95355d5..19109fc 100644 | ||
--- a/node_modules/react-native-web/dist/exports/Image/index.js | ||
+++ b/node_modules/react-native-web/dist/exports/Image/index.js | ||
@@ -135,7 +135,22 @@ function resolveAssetUri(source) { | ||
} | ||
return uri; | ||
} | ||
-var Image = /*#__PURE__*/React.forwardRef((props, ref) => { | ||
+function raiseOnErrorEvent(uri, _ref) { | ||
+ var onError = _ref.onError, | ||
+ onLoadEnd = _ref.onLoadEnd; | ||
+ if (onError) { | ||
+ onError({ | ||
+ nativeEvent: { | ||
+ error: "Failed to load resource " + uri + " (404)" | ||
+ } | ||
+ }); | ||
+ } | ||
+ if (onLoadEnd) onLoadEnd(); | ||
+} | ||
+function hasSourceDiff(a, b) { | ||
+ return a.uri !== b.uri || JSON.stringify(a.headers) !== JSON.stringify(b.headers); | ||
+} | ||
+var BaseImage = /*#__PURE__*/React.forwardRef((props, ref) => { | ||
var ariaLabel = props['aria-label'], | ||
blurRadius = props.blurRadius, | ||
defaultSource = props.defaultSource, | ||
@@ -236,16 +251,10 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { | ||
} | ||
}, function error() { | ||
updateState(ERRORED); | ||
- if (onError) { | ||
- onError({ | ||
- nativeEvent: { | ||
- error: "Failed to load resource " + uri + " (404)" | ||
- } | ||
- }); | ||
- } | ||
- if (onLoadEnd) { | ||
- onLoadEnd(); | ||
- } | ||
+ raiseOnErrorEvent(uri, { | ||
+ onError, | ||
+ onLoadEnd | ||
+ }); | ||
}); | ||
} | ||
function abortPendingRequest() { | ||
@@ -277,10 +286,78 @@ var Image = /*#__PURE__*/React.forwardRef((props, ref) => { | ||
suppressHydrationWarning: true | ||
}), hiddenImage, createTintColorSVG(tintColor, filterRef.current)); | ||
}); | ||
-Image.displayName = 'Image'; | ||
+BaseImage.displayName = 'Image'; | ||
+ | ||
+/** | ||
+ * This component handles specifically loading an image source with headers | ||
+ * default source is never loaded using headers | ||
+ */ | ||
+var ImageWithHeaders = /*#__PURE__*/React.forwardRef((props, ref) => { | ||
+ // $FlowIgnore: This component would only be rendered when `source` matches `ImageSource` | ||
+ var nextSource = props.source; | ||
+ var _React$useState3 = React.useState(''), | ||
+ blobUri = _React$useState3[0], | ||
+ setBlobUri = _React$useState3[1]; | ||
+ var request = React.useRef({ | ||
+ cancel: () => {}, | ||
+ source: { | ||
+ uri: '', | ||
+ headers: {} | ||
+ }, | ||
+ promise: Promise.resolve('') | ||
+ }); | ||
+ var onError = props.onError, | ||
+ onLoadStart = props.onLoadStart, | ||
+ onLoadEnd = props.onLoadEnd; | ||
+ React.useEffect(() => { | ||
+ if (!hasSourceDiff(nextSource, request.current.source)) { | ||
+ return; | ||
+ } | ||
+ | ||
+ // When source changes we want to clean up any old/running requests | ||
+ request.current.cancel(); | ||
+ if (onLoadStart) { | ||
+ onLoadStart(); | ||
+ } | ||
+ | ||
+ // Store a ref for the current load request so we know what's the last loaded source, | ||
+ // and so we can cancel it if a different source is passed through props | ||
+ request.current = ImageLoader.loadWithHeaders(nextSource); | ||
+ request.current.promise.then(uri => setBlobUri(uri)).catch(() => raiseOnErrorEvent(request.current.source.uri, { | ||
+ onError, | ||
+ onLoadEnd | ||
+ })); | ||
+ }, [nextSource, onLoadStart, onError, onLoadEnd]); | ||
+ | ||
+ // Cancel any request on unmount | ||
+ React.useEffect(() => request.current.cancel, []); | ||
+ var propsToPass = _objectSpread(_objectSpread({}, props), {}, { | ||
+ // `onLoadStart` is called from the current component | ||
+ // We skip passing it down to prevent BaseImage raising it a 2nd time | ||
+ onLoadStart: undefined, | ||
+ // Until the current component resolves the request (using headers) | ||
+ // we skip forwarding the source so the base component doesn't attempt | ||
+ // to load the original source | ||
+ source: blobUri ? _objectSpread(_objectSpread({}, nextSource), {}, { | ||
+ uri: blobUri | ||
+ }) : undefined | ||
+ }); | ||
+ return /*#__PURE__*/React.createElement(BaseImage, _extends({ | ||
+ ref: ref | ||
+ }, propsToPass)); | ||
+}); | ||
|
||
// $FlowIgnore: This is the correct type, but casting makes it unhappy since the variables aren't defined yet | ||
-var ImageWithStatics = Image; | ||
+var ImageWithStatics = /*#__PURE__*/React.forwardRef((props, ref) => { | ||
+ if (props.source && props.source.headers) { | ||
+ return /*#__PURE__*/React.createElement(ImageWithHeaders, _extends({ | ||
+ ref: ref | ||
+ }, props)); | ||
+ } | ||
+ return /*#__PURE__*/React.createElement(BaseImage, _extends({ | ||
+ ref: ref | ||
+ }, props)); | ||
+}); | ||
ImageWithStatics.getSize = function (uri, success, failure) { | ||
ImageLoader.getSize(uri, success, failure); | ||
}; | ||
diff --git a/node_modules/react-native-web/dist/modules/ImageLoader/index.js b/node_modules/react-native-web/dist/modules/ImageLoader/index.js | ||
index bc06a87..e309394 100644 | ||
--- a/node_modules/react-native-web/dist/modules/ImageLoader/index.js | ||
+++ b/node_modules/react-native-web/dist/modules/ImageLoader/index.js | ||
@@ -76,7 +76,7 @@ var ImageLoader = { | ||
var image = requests["" + requestId]; | ||
if (image) { | ||
var naturalHeight = image.naturalHeight, | ||
- naturalWidth = image.naturalWidth; | ||
+ naturalWidth = image.naturalWidth; | ||
if (naturalHeight && naturalWidth) { | ||
success(naturalWidth, naturalHeight); | ||
complete = true; | ||
@@ -102,11 +102,19 @@ var ImageLoader = { | ||
id += 1; | ||
var image = new window.Image(); | ||
image.onerror = onError; | ||
- image.onload = e => { | ||
+ image.onload = nativeEvent => { | ||
// avoid blocking the main thread | ||
- var onDecode = () => onLoad({ | ||
- nativeEvent: e | ||
- }); | ||
+ var onDecode = () => { | ||
+ // Append `source` to match RN's ImageLoadEvent interface | ||
+ nativeEvent.source = { | ||
+ uri: image.src, | ||
+ width: image.naturalWidth, | ||
+ height: image.naturalHeight | ||
+ }; | ||
+ onLoad({ | ||
+ nativeEvent | ||
+ }); | ||
+ }; | ||
if (typeof image.decode === 'function') { | ||
// Safari currently throws exceptions when decoding svgs. | ||
// We want to catch that error and allow the load handler | ||
@@ -120,6 +128,32 @@ var ImageLoader = { | ||
requests["" + id] = image; | ||
return id; | ||
}, | ||
+ loadWithHeaders(source) { | ||
+ var uri; | ||
+ var abortController = new AbortController(); | ||
+ var request = new Request(source.uri, { | ||
+ headers: source.headers, | ||
+ signal: abortController.signal | ||
+ }); | ||
+ request.headers.append('accept', 'image/*'); | ||
+ var promise = fetch(request).then(response => response.blob()).then(blob => { | ||
+ uri = URL.createObjectURL(blob); | ||
+ return uri; | ||
+ }).catch(error => { | ||
+ if (error.name === 'AbortError') { | ||
+ return ''; | ||
+ } | ||
+ throw error; | ||
+ }); | ||
+ return { | ||
+ promise, | ||
+ source, | ||
+ cancel: () => { | ||
+ abortController.abort(); | ||
+ URL.revokeObjectURL(uri); | ||
+ } | ||
+ }; | ||
+ }, | ||
prefetch(uri) { | ||
return new Promise((resolve, reject) => { | ||
ImageLoader.load(uri, () => { |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import {Image as ExpoImage} from 'expo-image'; | ||
import type {ImageProps as ExpoImageProps, ImageLoadEventData} from 'expo-image'; | ||
import {useCallback} from 'react'; | ||
import type {BaseImageProps} from './types'; | ||
|
||
function BaseImage({onLoad, ...props}: ExpoImageProps & BaseImageProps) { | ||
const imageLoadedSuccessfully = useCallback( | ||
(event: ImageLoadEventData) => { | ||
if (!onLoad) { | ||
return; | ||
} | ||
|
||
// We override `onLoad`, so both web and native have the same signature | ||
const {width, height} = event.source; | ||
onLoad({nativeEvent: {width, height}}); | ||
}, | ||
[onLoad], | ||
); | ||
|
||
return ( | ||
<ExpoImage | ||
// Only subscribe to onLoad when a handler is provided to avoid unnecessary event registrations, optimizing performance. | ||
onLoad={onLoad ? imageLoadedSuccessfully : undefined} | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
/> | ||
); | ||
} | ||
|
||
BaseImage.displayName = 'BaseImage'; | ||
|
||
export default BaseImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React, {useCallback} from 'react'; | ||
import {Image as RNImage} from 'react-native'; | ||
import type {ImageLoadEventData, ImageProps as WebImageProps} from 'react-native'; | ||
import type {BaseImageProps} from './types'; | ||
|
||
function BaseImage({onLoad, ...props}: WebImageProps & BaseImageProps) { | ||
const imageLoadedSuccessfully = useCallback( | ||
(event: {nativeEvent: ImageLoadEventData}) => { | ||
if (!onLoad) { | ||
return; | ||
} | ||
|
||
// We override `onLoad`, so both web and native have the same signature | ||
const {width, height} = event.nativeEvent.source; | ||
onLoad({nativeEvent: {width, height}}); | ||
}, | ||
[onLoad], | ||
); | ||
|
||
return ( | ||
<RNImage | ||
// Only subscribe to onLoad when a handler is provided to avoid unnecessary event registrations, optimizing performance. | ||
onLoad={onLoad ? imageLoadedSuccessfully : undefined} | ||
// eslint-disable-next-line react/jsx-props-no-spreading | ||
{...props} | ||
/> | ||
); | ||
} | ||
|
||
BaseImage.displayName = 'BaseImage'; | ||
|
||
export default BaseImage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.