Skip to content

Commit 46d4679

Browse files
nickmisasiCopilotlarkoxmattermost-buildclaude
authored
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>
1 parent 7afdadd commit 46d4679

File tree

5 files changed

+77
-21
lines changed

5 files changed

+77
-21
lines changed

app/components/expo_image/index.tsx

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import React, {forwardRef, useMemo} from 'react';
66
import Animated from 'react-native-reanimated';
77

88
import {useServerUrl} from '@context/server';
9+
import NetworkManager from '@managers/network_manager';
910
import {urlSafeBase64Encode} from '@utils/security';
1011

1112
type ExpoImagePropsWithId = ImageProps & {id: string};
@@ -16,8 +17,36 @@ type ExpoImageBackgroundPropsWithId = ImageBackgroundProps & {id: string};
1617
type ExpoImageBackgroundPropsMemoryOnly = ImageBackgroundProps & {cachePolicy: 'memory'; id?: string};
1718
type ExpoImageBackgroundProps = ExpoImageBackgroundPropsWithId | ExpoImageBackgroundPropsMemoryOnly;
1819

20+
function shouldAttachServerAuthHeaders(uri: string | undefined, serverUrl: string) {
21+
if (!uri) {
22+
return false;
23+
}
24+
25+
try {
26+
const requestUrl = new URL(uri);
27+
const serverBaseUrl = new URL(serverUrl);
28+
29+
if (requestUrl.origin !== serverBaseUrl.origin) {
30+
return false;
31+
}
32+
33+
return requestUrl.pathname.startsWith('/api/v4/');
34+
} catch {
35+
// On any parsing error, do not attach auth headers
36+
return false;
37+
}
38+
}
39+
1940
const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
2041
const serverUrl = useServerUrl();
42+
const requestHeaders = useMemo(() => {
43+
try {
44+
const client = NetworkManager.getClient(serverUrl);
45+
return client.getRequestHeaders('GET');
46+
} catch {
47+
return undefined;
48+
}
49+
}, [serverUrl]);
2150

2251
/**
2352
* SECURITY NOTE: cachePath uses base64 encoding for URL safety, NOT encryption.
@@ -30,35 +59,49 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
3059
return props.source;
3160
}
3261

62+
const sourceHeaders = shouldAttachServerAuthHeaders(props.source?.uri, serverUrl) && requestHeaders ? {...requestHeaders, ...props.source?.headers} : props.source?.headers;
63+
delete sourceHeaders?.Accept;
64+
3365
// Only add cacheKey and cachePath if id is provided (i.e., not memory-only caching)
3466
if (id) {
3567
return {
3668
...props.source,
69+
headers: sourceHeaders,
3770
cacheKey: id,
3871
cachePath,
3972
};
4073
}
4174

42-
return props.source;
43-
}, [id, props.source, cachePath]);
75+
return {
76+
...props.source,
77+
headers: sourceHeaders,
78+
};
79+
}, [id, props.source, cachePath, requestHeaders, serverUrl]);
4480

4581
// Process placeholder to add cachePath and cacheKey if it has a uri
4682
const placeholder: ImageSource | undefined = useMemo(() => {
4783
if (!props.placeholder || typeof props.placeholder === 'number' || typeof props.placeholder === 'string') {
4884
return props.placeholder;
4985
}
5086

87+
const placeholderHeaders = shouldAttachServerAuthHeaders(props.placeholder?.uri, serverUrl) && requestHeaders ? {...requestHeaders, ...props.placeholder?.headers} : props.placeholder?.headers;
88+
delete placeholderHeaders?.Accept;
89+
5190
// If placeholder has a uri and id is provided, add cachePath and cacheKey
5291
if (props.placeholder.uri && id) {
5392
return {
5493
...props.placeholder,
94+
headers: placeholderHeaders,
5595
cacheKey: `${id}-thumb`,
5696
cachePath,
5797
};
5898
}
5999

60-
return props.placeholder;
61-
}, [props.placeholder, id, cachePath]);
100+
return {
101+
...props.placeholder,
102+
headers: placeholderHeaders,
103+
};
104+
}, [props.placeholder, id, cachePath, requestHeaders, serverUrl]);
62105

63106
return (
64107
<Image

app/components/post_list/post/body/content/message_attachments/__snapshots__/attachment_author.test.tsx.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ exports[`AttachmentAuthor it matches snapshot when both name and icon are provid
3131
{
3232
"cacheKey": "attachment-author-icon-aHR0cHM6Ly9pbWFnZXMuY29tL2ltYWdlLnBuZw==",
3333
"cachePath": "",
34+
"headers": undefined,
3435
"uri": "https://images.com/image.png",
3536
},
3637
]
@@ -95,6 +96,7 @@ exports[`AttachmentAuthor it matches snapshot when only icon is provided 1`] = `
9596
{
9697
"cacheKey": "attachment-author-icon-aHR0cHM6Ly9pbWFnZXMuY29tL2ltYWdlLnBuZw==",
9798
"cachePath": "",
99+
"headers": undefined,
98100
"uri": "https://images.com/image.png",
99101
},
100102
]

app/components/post_list/post/body/content/message_attachments/__snapshots__/attachment_footer.test.tsx.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ exports[`AttachmentFooter it matches snapshot when both footer and footer_icon a
3333
{
3434
"cacheKey": "attachment-footer-icon-aHR0cHM6Ly9pbWFnZXMuY29tL2ltYWdlLnBuZw==",
3535
"cachePath": "",
36+
"headers": undefined,
3637
"uri": "https://images.com/image.png",
3738
},
3839
]

app/components/user_list/__snapshots__/index.test.tsx.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,7 @@ exports[`components/channel_list_row should show results and tutorial 1`] = `
809809
{
810810
"cacheKey": "user-1-123456",
811811
"cachePath": "",
812+
"headers": undefined,
812813
"uri": "https://community.mattermost.com/api/v4/users/1/image?_=123456",
813814
},
814815
]
@@ -1152,6 +1153,7 @@ exports[`components/channel_list_row should show results no tutorial 1`] = `
11521153
{
11531154
"cacheKey": "user-1-123456",
11541155
"cachePath": "",
1156+
"headers": undefined,
11551157
"uri": "https://community.mattermost.com/api/v4/users/1/image?_=123456",
11561158
},
11571159
]
@@ -1533,6 +1535,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
15331535
{
15341536
"cacheKey": "user-1-123456",
15351537
"cachePath": "",
1538+
"headers": undefined,
15361539
"uri": "https://community.mattermost.com/api/v4/users/1/image?_=123456",
15371540
},
15381541
]
@@ -1775,6 +1778,7 @@ exports[`components/channel_list_row should show results no tutorial 2 users 1`]
17751778
{
17761779
"cacheKey": "user-2-123456",
17771780
"cachePath": "",
1781+
"headers": undefined,
17781782
"uri": "https://community.mattermost.com/api/v4/users/2/image?_=123456",
17791783
},
17801784
]

scripts/android-16kb-pagesize/9325-full.diff

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,19 @@ index 08eb45fcd9..914911c17a 100644
3636
+ ]
3737
}
3838
diff --git a/app/components/expo_image/index.tsx b/app/components/expo_image/index.tsx
39-
index 9dbffd7ca6..bd70fffb9c 100644
39+
index b2723fb..1ed5e15 100644
4040
--- a/app/components/expo_image/index.tsx
4141
+++ b/app/components/expo_image/index.tsx
42-
@@ -8,6 +8,8 @@ import Animated from 'react-native-reanimated';
43-
import {useServerUrl} from '@context/server';
42+
@@ -9,6 +9,8 @@ import {useServerUrl} from '@context/server';
43+
import NetworkManager from '@managers/network_manager';
4444
import {urlSafeBase64Encode} from '@utils/security';
45-
45+
4646
+import type {SharedRefType} from 'expo';
4747
+
4848
type ExpoImagePropsWithId = ImageProps & {id: string};
4949
type ExpoImagePropsMemoryOnly = ImageProps & {cachePolicy: 'memory'; id?: string};
5050
type ExpoImageProps = ExpoImagePropsWithId | ExpoImagePropsMemoryOnly;
51-
@@ -25,13 +27,13 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
51+
@@ -54,8 +56,8 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
5252
* for filesystem path compatibility (avoiding special characters in directory names).
5353
*/
5454
const cachePath = useMemo(() => urlSafeBase64Encode(serverUrl), [serverUrl]);
@@ -58,31 +58,37 @@ index 9dbffd7ca6..bd70fffb9c 100644
5858
+ if (typeof props.source === 'number' || typeof props.source === 'string' || Array.isArray(props.source) || !props.source) {
5959
return props.source;
6060
}
61-
61+
62+
@@ -63,7 +65,7 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
63+
delete sourceHeaders?.Accept;
64+
6265
// Only add cacheKey and cachePath if id is provided (i.e., not memory-only caching)
6366
- if (id) {
6467
+ if (id && typeof props.source === 'object' && 'uri' in props.source) {
6568
return {
6669
...props.source,
67-
cacheKey: id,
68-
@@ -43,13 +45,13 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
69-
}, [id, props.source, cachePath]);
70-
70+
headers: sourceHeaders,
71+
@@ -79,8 +81,8 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
72+
}, [id, props.source, cachePath, requestHeaders, serverUrl]);
73+
7174
// Process placeholder to add cachePath and cacheKey if it has a uri
7275
- const placeholder: ImageSource | undefined = useMemo(() => {
7376
- if (!props.placeholder || typeof props.placeholder === 'number' || typeof props.placeholder === 'string') {
7477
+ const placeholder: ImageSource | string | number | ImageSource[] | string[] | SharedRefType<'image'> | null | undefined = useMemo(() => {
7578
+ if (!props.placeholder || typeof props.placeholder === 'number' || typeof props.placeholder === 'string' || Array.isArray(props.placeholder)) {
7679
return props.placeholder;
7780
}
78-
81+
82+
@@ -88,7 +90,7 @@ const ExpoImage = forwardRef<Image, ExpoImageProps>(({id, ...props}, ref) => {
83+
delete placeholderHeaders?.Accept;
84+
7985
// If placeholder has a uri and id is provided, add cachePath and cacheKey
8086
- if (props.placeholder.uri && id) {
8187
+ if (typeof props.placeholder === 'object' && 'uri' in props.placeholder && props.placeholder.uri && id) {
8288
return {
8389
...props.placeholder,
84-
cacheKey: `${id}-thumb`,
85-
@@ -74,13 +76,13 @@ ExpoImage.displayName = 'ExpoImage';
90+
headers: placeholderHeaders,
91+
@@ -117,13 +119,13 @@ ExpoImage.displayName = 'ExpoImage';
8692
const ExpoImageBackground = ({id, ...props}: ExpoImageBackgroundProps) => {
8793
const serverUrl = useServerUrl();
8894
const cachePath = useMemo(() => urlSafeBase64Encode(serverUrl), [serverUrl]);
@@ -92,24 +98,24 @@ index 9dbffd7ca6..bd70fffb9c 100644
9298
+ if (typeof props.source === 'number' || typeof props.source === 'string' || Array.isArray(props.source) || !props.source) {
9399
return props.source;
94100
}
95-
101+
96102
// Only add cacheKey and cachePath if id is provided (i.e., not memory-only caching)
97103
- if (id) {
98104
+ if (id && typeof props.source === 'object' && 'uri' in props.source) {
99105
return {
100106
...props.source,
101107
cacheKey: id,
102-
@@ -92,13 +94,13 @@ const ExpoImageBackground = ({id, ...props}: ExpoImageBackgroundProps) => {
108+
@@ -135,13 +137,13 @@ const ExpoImageBackground = ({id, ...props}: ExpoImageBackgroundProps) => {
103109
}, [id, props.source, cachePath]);
104-
110+
105111
// Process placeholder to add cachePath and cacheKey if it has a uri
106112
- const placeholder: ImageSource | undefined = useMemo(() => {
107113
- if (!props.placeholder || typeof props.placeholder === 'number' || typeof props.placeholder === 'string') {
108114
+ const placeholder: ImageSource | string | number | ImageSource[] | string[] | SharedRefType<'image'> | null | undefined = useMemo(() => {
109115
+ if (!props.placeholder || typeof props.placeholder === 'number' || typeof props.placeholder === 'string' || Array.isArray(props.placeholder)) {
110116
return props.placeholder;
111117
}
112-
118+
113119
// If placeholder has a uri and id is provided, add cachePath and cacheKey
114120
- if (props.placeholder.uri && id) {
115121
+ if (typeof props.placeholder === 'object' && 'uri' in props.placeholder && props.placeholder.uri && id) {

0 commit comments

Comments
 (0)