Fix Android image loads by attaching auth headers (#9554)
* Fix images not loading on android" * Update app/components/expo_image/index.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update snapshots and remove accept * Fix 16KB page size patch failing due to stale diff context (#9555) The expo_image/index.tsx hunks in 9325-full.diff were generated before the NetworkManager/header-handling code was added to ExpoImage. The stale context lines caused patch's fuzzy matching to apply ExpoImage hunks to ExpoImageBackground instead, then ExpoImageBackground hunks failed because those sections were already modified. Regenerated the expo_image diff hunks to match the current file state with proper context lines for both ExpoImage (with header handling) and ExpoImageBackground components. https://claude.ai/code/session_01SGXWQzxrcPpWsq2YrjoUwd Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Your Name <larkox@gmail.com> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7afdaddd2b
commit
46d46790b5
5 changed files with 77 additions and 21 deletions
|
|
@ -6,6 +6,7 @@ import React, {forwardRef, useMemo} from 'react';
|
|||
import Animated from 'react-native-reanimated';
|
||||
|
||||
import {useServerUrl} from '@context/server';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {urlSafeBase64Encode} from '@utils/security';
|
||||
|
||||
type ExpoImagePropsWithId = ImageProps & {id: string};
|
||||
|
|
@ -16,8 +17,36 @@ type ExpoImageBackgroundPropsWithId = ImageBackgroundProps & {id: string};
|
|||
type ExpoImageBackgroundPropsMemoryOnly = ImageBackgroundProps & {cachePolicy: 'memory'; id?: string};
|
||||
type ExpoImageBackgroundProps = ExpoImageBackgroundPropsWithId | ExpoImageBackgroundPropsMemoryOnly;
|
||||
|
||||
function shouldAttachServerAuthHeaders(uri: string | undefined, serverUrl: string) {
|
||||
if (!uri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const requestUrl = new URL(uri);
|
||||
const serverBaseUrl = new URL(serverUrl);
|
||||
|
||||
if (requestUrl.origin !== serverBaseUrl.origin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return requestUrl.pathname.startsWith('/api/v4/');
|
||||
} catch {
|
||||
// On any parsing error, do not attach auth headers
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const requestHeaders = useMemo(() => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
return client.getRequestHeaders('GET');
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}, [serverUrl]);
|
||||
|
||||
/**
|
||||
* SECURITY NOTE: cachePath uses base64 encoding for URL safety, NOT encryption.
|
||||
|
|
@ -30,17 +59,24 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
|||
return props.source;
|
||||
}
|
||||
|
||||
const sourceHeaders = shouldAttachServerAuthHeaders(props.source?.uri, serverUrl) && requestHeaders ? {...requestHeaders, ...props.source?.headers} : props.source?.headers;
|
||||
delete sourceHeaders?.Accept;
|
||||
|
||||
// Only add cacheKey and cachePath if id is provided (i.e., not memory-only caching)
|
||||
if (id) {
|
||||
return {
|
||||
...props.source,
|
||||
headers: sourceHeaders,
|
||||
cacheKey: id,
|
||||
cachePath,
|
||||
};
|
||||
}
|
||||
|
||||
return props.source;
|
||||
}, [id, props.source, cachePath]);
|
||||
return {
|
||||
...props.source,
|
||||
headers: sourceHeaders,
|
||||
};
|
||||
}, [id, props.source, cachePath, requestHeaders, serverUrl]);
|
||||
|
||||
// Process placeholder to add cachePath and cacheKey if it has a uri
|
||||
const placeholder: ImageSource | undefined = useMemo(() => {
|
||||
|
|
@ -48,17 +84,24 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
|||
return props.placeholder;
|
||||
}
|
||||
|
||||
const placeholderHeaders = shouldAttachServerAuthHeaders(props.placeholder?.uri, serverUrl) && requestHeaders ? {...requestHeaders, ...props.placeholder?.headers} : props.placeholder?.headers;
|
||||
delete placeholderHeaders?.Accept;
|
||||
|
||||
// If placeholder has a uri and id is provided, add cachePath and cacheKey
|
||||
if (props.placeholder.uri && id) {
|
||||
return {
|
||||
...props.placeholder,
|
||||
headers: placeholderHeaders,
|
||||
cacheKey: `${id}-thumb`,
|
||||
cachePath,
|
||||
};
|
||||
}
|
||||
|
||||
return props.placeholder;
|
||||
}, [props.placeholder, id, cachePath]);
|
||||
return {
|
||||
...props.placeholder,
|
||||
headers: placeholderHeaders,
|
||||
};
|
||||
}, [props.placeholder, id, cachePath, requestHeaders, serverUrl]);
|
||||
|
||||
return (
|
||||
<Image
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ exports[`AttachmentAuthor it matches snapshot when both name and icon are provid
|
|||
{
|
||||
"cacheKey": "attachment-author-icon-aHR0cHM6Ly9pbWFnZXMuY29tL2ltYWdlLnBuZw==",
|
||||
"cachePath": "",
|
||||
"headers": undefined,
|
||||
"uri": "https://images.com/image.png",
|
||||
},
|
||||
]
|
||||
|
|
@ -95,6 +96,7 @@ exports[`AttachmentAuthor it matches snapshot when only icon is provided 1`] = `
|
|||
{
|
||||
"cacheKey": "attachment-author-icon-aHR0cHM6Ly9pbWFnZXMuY29tL2ltYWdlLnBuZw==",
|
||||
"cachePath": "",
|
||||
"headers": undefined,
|
||||
"uri": "https://images.com/image.png",
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ exports[`AttachmentFooter it matches snapshot when both footer and footer_icon a
|
|||
{
|
||||
"cacheKey": "attachment-footer-icon-aHR0cHM6Ly9pbWFnZXMuY29tL2ltYWdlLnBuZw==",
|
||||
"cachePath": "",
|
||||
"headers": undefined,
|
||||
"uri": "https://images.com/image.png",
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -809,6 +809,7 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
|
|||
{
|
||||
"cacheKey": "user-1-123456",
|
||||
"cachePath": "",
|
||||
"headers": undefined,
|
||||
"uri": "https://community.mattermost.com/api/v4/users/1/image?_=123456",
|
||||
},
|
||||
]
|
||||
|
|
@ -1152,6 +1153,7 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
|
|||
{
|
||||
"cacheKey": "user-1-123456",
|
||||
"cachePath": "",
|
||||
"headers": undefined,
|
||||
"uri": "https://community.mattermost.com/api/v4/users/1/image?_=123456",
|
||||
},
|
||||
]
|
||||
|
|
@ -1533,6 +1535,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
|
|||
{
|
||||
"cacheKey": "user-1-123456",
|
||||
"cachePath": "",
|
||||
"headers": undefined,
|
||||
"uri": "https://community.mattermost.com/api/v4/users/1/image?_=123456",
|
||||
},
|
||||
]
|
||||
|
|
@ -1775,6 +1778,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
|
|||
{
|
||||
"cacheKey": "user-2-123456",
|
||||
"cachePath": "",
|
||||
"headers": undefined,
|
||||
"uri": "https://community.mattermost.com/api/v4/users/2/image?_=123456",
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ index 08eb45fcd9..914911c17a 100644
|
|||
+ ]
|
||||
}
|
||||
diff --git a/app/components/expo_image/index.tsx b/app/components/expo_image/index.tsx
|
||||
index 9dbffd7ca6..bd70fffb9c 100644
|
||||
index b2723fb..1ed5e15 100644
|
||||
--- a/app/components/expo_image/index.tsx
|
||||
+++ b/app/components/expo_image/index.tsx
|
||||
@@ -8,6 +8,8 @@ import Animated from 'react-native-reanimated';
|
||||
import {useServerUrl} from '@context/server';
|
||||
@@ -9,6 +9,8 @@ import {useServerUrl} from '@context/server';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {urlSafeBase64Encode} from '@utils/security';
|
||||
|
||||
+import type {SharedRefType} from 'expo';
|
||||
|
|
@ -48,7 +48,7 @@ index 9dbffd7ca6..bd70fffb9c 100644
|
|||
type ExpoImagePropsWithId = ImageProps & {id: string};
|
||||
type ExpoImagePropsMemoryOnly = ImageProps & {cachePolicy: 'memory'; id?: string};
|
||||
type ExpoImageProps = ExpoImagePropsWithId | ExpoImagePropsMemoryOnly;
|
||||
@@ -25,13 +27,13 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
||||
@@ -54,8 +56,8 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
||||
* for filesystem path compatibility (avoiding special characters in directory names).
|
||||
*/
|
||||
const cachePath = useMemo(() => urlSafeBase64Encode(serverUrl), [serverUrl]);
|
||||
|
|
@ -59,14 +59,17 @@ index 9dbffd7ca6..bd70fffb9c 100644
|
|||
return props.source;
|
||||
}
|
||||
|
||||
@@ -63,7 +65,7 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
||||
delete sourceHeaders?.Accept;
|
||||
|
||||
// Only add cacheKey and cachePath if id is provided (i.e., not memory-only caching)
|
||||
- if (id) {
|
||||
+ if (id && typeof props.source === 'object' && 'uri' in props.source) {
|
||||
return {
|
||||
...props.source,
|
||||
cacheKey: id,
|
||||
@@ -43,13 +45,13 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
||||
}, [id, props.source, cachePath]);
|
||||
headers: sourceHeaders,
|
||||
@@ -79,8 +81,8 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
||||
}, [id, props.source, cachePath, requestHeaders, serverUrl]);
|
||||
|
||||
// Process placeholder to add cachePath and cacheKey if it has a uri
|
||||
- const placeholder: ImageSource | undefined = useMemo(() => {
|
||||
|
|
@ -76,13 +79,16 @@ index 9dbffd7ca6..bd70fffb9c 100644
|
|||
return props.placeholder;
|
||||
}
|
||||
|
||||
@@ -88,7 +90,7 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
|
||||
delete placeholderHeaders?.Accept;
|
||||
|
||||
// If placeholder has a uri and id is provided, add cachePath and cacheKey
|
||||
- if (props.placeholder.uri && id) {
|
||||
+ if (typeof props.placeholder === 'object' && 'uri' in props.placeholder && props.placeholder.uri && id) {
|
||||
return {
|
||||
...props.placeholder,
|
||||
cacheKey: `${id}-thumb`,
|
||||
@@ -74,13 +76,13 @@ ExpoImage.displayName = 'ExpoImage';
|
||||
headers: placeholderHeaders,
|
||||
@@ -117,13 +119,13 @@ ExpoImage.displayName = 'ExpoImage';
|
||||
const ExpoImageBackground = ({id, ...props}: ExpoImageBackgroundProps) => {
|
||||
const serverUrl = useServerUrl();
|
||||
const cachePath = useMemo(() => urlSafeBase64Encode(serverUrl), [serverUrl]);
|
||||
|
|
@ -99,7 +105,7 @@ index 9dbffd7ca6..bd70fffb9c 100644
|
|||
return {
|
||||
...props.source,
|
||||
cacheKey: id,
|
||||
@@ -92,13 +94,13 @@ const ExpoImageBackground = ({id, ...props}: ExpoImageBackgroundProps) => {
|
||||
@@ -135,13 +137,13 @@ const ExpoImageBackground = ({id, ...props}: ExpoImageBackgroundProps) => {
|
||||
}, [id, props.source, cachePath]);
|
||||
|
||||
// Process placeholder to add cachePath and cacheKey if it has a uri
|
||||
|
|
|
|||
Loading…
Reference in a new issue