diff --git a/patches/react-native-web+0.19.9+005+image-header-support.patch b/patches/react-native-web+0.19.9+005+image-header-support.patch
deleted file mode 100644
index 4652e22662f0..000000000000
--- a/patches/react-native-web+0.19.9+005+image-header-support.patch
+++ /dev/null
@@ -1,200 +0,0 @@
-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, () => {
diff --git a/src/components/Image/BaseImage.native.tsx b/src/components/Image/BaseImage.native.tsx
deleted file mode 100644
index c517efd04515..000000000000
--- a/src/components/Image/BaseImage.native.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-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 (
-
- );
-}
-
-BaseImage.displayName = 'BaseImage';
-
-export default BaseImage;
diff --git a/src/components/Image/BaseImage.tsx b/src/components/Image/BaseImage.tsx
deleted file mode 100644
index ebdd76840267..000000000000
--- a/src/components/Image/BaseImage.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-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 (
-
- );
-}
-
-BaseImage.displayName = 'BaseImage';
-
-export default BaseImage;
diff --git a/src/components/Image/index.js b/src/components/Image/index.js
index 8cee1cf95e14..ef1a69e19c12 100644
--- a/src/components/Image/index.js
+++ b/src/components/Image/index.js
@@ -1,35 +1,51 @@
import lodashGet from 'lodash/get';
-import React, {useMemo} from 'react';
+import React, {useEffect, useMemo} from 'react';
+import {Image as RNImage} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import CONST from '@src/CONST';
+import _ from 'underscore';
import ONYXKEYS from '@src/ONYXKEYS';
-import BaseImage from './BaseImage';
import {defaultProps, imagePropTypes} from './imagePropTypes';
import RESIZE_MODES from './resizeModes';
-function Image({source: propsSource, isAuthTokenRequired, session, ...forwardedProps}) {
- // Update the source to include the auth token if required
+function Image(props) {
+ const {source: propsSource, isAuthTokenRequired, onLoad, session} = props;
+ /**
+ * Check if the image source is a URL - if so the `encryptedAuthToken` is appended
+ * to the source.
+ */
const source = useMemo(() => {
- if (typeof lodashGet(propsSource, 'uri') === 'number') {
- return propsSource.uri;
+ if (isAuthTokenRequired) {
+ // There is currently a `react-native-web` bug preventing the authToken being passed
+ // in the headers of the image request so the authToken is added as a query param.
+ // On native the authToken IS passed in the image request headers
+ const authToken = lodashGet(session, 'encryptedAuthToken', null);
+ return {uri: `${propsSource.uri}?encryptedAuthToken=${encodeURIComponent(authToken)}`};
}
- if (typeof propsSource !== 'number' && isAuthTokenRequired) {
- const authToken = lodashGet(session, 'encryptedAuthToken');
- return {
- ...propsSource,
- headers: {
- [CONST.CHAT_ATTACHMENT_TOKEN_KEY]: authToken,
- },
- };
- }
-
return propsSource;
// The session prop is not required, as it causes the image to reload whenever the session changes. For more information, please refer to issue #26034.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [propsSource, isAuthTokenRequired]);
+ /**
+ * The natural image dimensions are retrieved using the updated source
+ * and as a result the `onLoad` event needs to be manually invoked to return these dimensions
+ */
+ useEffect(() => {
+ // If an onLoad callback was specified then manually call it and pass
+ // the natural image dimensions to match the native API
+ if (onLoad == null) {
+ return;
+ }
+ RNImage.getSize(source.uri, (width, height) => {
+ onLoad({nativeEvent: {width, height}});
+ });
+ }, [onLoad, source]);
+
+ // Omit the props which the underlying RNImage won't use
+ const forwardedProps = _.omit(props, ['source', 'onLoad', 'session', 'isAuthTokenRequired']);
+
return (
- {
+ const {width, height, url} = evt.source;
+ dimensionsCache.set(url, {width, height});
+ if (props.onLoad) {
+ props.onLoad({nativeEvent: {width, height}});
+ }
+ }}
+ />
+ );
+}
+
+Image.propTypes = imagePropTypes;
+Image.defaultProps = defaultProps;
+Image.displayName = 'Image';
+const ImageWithOnyx = withOnyx({
+ session: {
+ key: ONYXKEYS.SESSION,
+ },
+})(Image);
+ImageWithOnyx.resizeMode = RESIZE_MODES;
+ImageWithOnyx.resolveDimensions = resolveDimensions;
+
+export default ImageWithOnyx;
diff --git a/src/components/Image/types.ts b/src/components/Image/types.ts
deleted file mode 100644
index 5a4c94364a46..000000000000
--- a/src/components/Image/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-type BaseImageProps = {
- /** Event called with image dimensions when image is loaded */
- onLoad?: (event: {nativeEvent: {width: number; height: number}}) => void;
-};
-
-export type {BaseImageProps};
-
-export default BaseImageProps;
diff --git a/src/pages/DetailsPage.js b/src/pages/DetailsPage.js
index df71bf47b0ac..e22efbf4deb1 100755
--- a/src/pages/DetailsPage.js
+++ b/src/pages/DetailsPage.js
@@ -136,6 +136,7 @@ function DetailsPage(props) {