Send gallery image requests with bearer token fallback (#31)
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { buildResourceUrl } from '../../utils/url';
|
import { buildResourceUrl } from '../../utils/url';
|
||||||
|
import {
|
||||||
|
getActiveGallerySlug,
|
||||||
|
getGalleryToken,
|
||||||
|
inferGallerySlugFromLocation,
|
||||||
|
resolveSlugFromRequestUrl,
|
||||||
|
} from '../../utils/galleryAuthStorage';
|
||||||
|
|
||||||
interface AuthenticatedImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
interface AuthenticatedImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
||||||
src: string;
|
src: string;
|
||||||
@@ -52,7 +58,6 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const unusedProps = {
|
const unusedProps = {
|
||||||
protectFromDownload,
|
protectFromDownload,
|
||||||
slug,
|
|
||||||
photoId,
|
photoId,
|
||||||
requiresToken,
|
requiresToken,
|
||||||
secureUrlTemplate,
|
secureUrlTemplate,
|
||||||
@@ -76,7 +81,8 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let objectUrl: string | null = null;
|
let aborted = false;
|
||||||
|
const objectUrls: string[] = [];
|
||||||
|
|
||||||
// Determine which token to use based on context
|
// Determine which token to use based on context
|
||||||
if (!src) {
|
if (!src) {
|
||||||
@@ -88,37 +94,79 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
|
|
||||||
// Create a new URL with auth header
|
const resolveSlug = (candidateSrc?: string): string | null => {
|
||||||
|
if (slug) {
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
const fromUrl = candidateSrc ? resolveSlugFromRequestUrl(candidateSrc) : null;
|
||||||
|
if (fromUrl) {
|
||||||
|
return fromUrl;
|
||||||
|
}
|
||||||
|
return getActiveGallerySlug() || inferGallerySlugFromLocation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchWithAuth = async (rawUrl: string | undefined | null): Promise<string> => {
|
||||||
|
if (!rawUrl) {
|
||||||
|
throw new Error('No URL provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build full URL for the image
|
||||||
|
const fullImageUrl = rawUrl.startsWith('/admin')
|
||||||
|
? buildResourceUrl(`/api${rawUrl}`)
|
||||||
|
: rawUrl.startsWith('/')
|
||||||
|
? buildResourceUrl(rawUrl)
|
||||||
|
: rawUrl;
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {};
|
||||||
|
const slugForRequest = resolveSlug(rawUrl);
|
||||||
|
const token = getGalleryToken(slugForRequest);
|
||||||
|
if (token) {
|
||||||
|
headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(fullImageUrl, {
|
||||||
|
credentials: 'include',
|
||||||
|
headers: Object.keys(headers).length ? headers : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob();
|
||||||
|
const objectUrl = URL.createObjectURL(blob);
|
||||||
|
objectUrls.push(objectUrl);
|
||||||
|
return objectUrl;
|
||||||
|
};
|
||||||
|
|
||||||
const fetchImage = async () => {
|
const fetchImage = async () => {
|
||||||
try {
|
try {
|
||||||
// Use the src as-is since it should already be the correct endpoint
|
const primaryUrl = await fetchWithAuth(src);
|
||||||
let imageUrl = src;
|
if (!aborted) {
|
||||||
|
setImageSrc(primaryUrl);
|
||||||
// Build full URL for the image
|
setError(false);
|
||||||
// For API paths that start with /admin, we need to prepend /api
|
|
||||||
const fullImageUrl = imageUrl.startsWith('/admin')
|
|
||||||
? buildResourceUrl(`/api${imageUrl}`)
|
|
||||||
: imageUrl.startsWith('/')
|
|
||||||
? buildResourceUrl(imageUrl)
|
|
||||||
: imageUrl;
|
|
||||||
|
|
||||||
// Fetch authenticated image
|
|
||||||
const response = await fetch(fullImageUrl, {
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await response.blob();
|
|
||||||
objectUrl = URL.createObjectURL(blob);
|
|
||||||
setImageSrc(objectUrl);
|
|
||||||
setIsLoading(false);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Image loading failed - use fallback
|
setIsLoading(false);
|
||||||
setError(true);
|
if (fallbackSrc && fallbackSrc !== src) {
|
||||||
setImageSrc(fallbackSrc || '');
|
try {
|
||||||
|
const fallbackUrl = await fetchWithAuth(fallbackSrc);
|
||||||
|
if (!aborted) {
|
||||||
|
setImageSrc(fallbackUrl);
|
||||||
|
setError(false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (fallbackError) {
|
||||||
|
// Swallow and mark error below
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!aborted) {
|
||||||
|
setError(true);
|
||||||
|
setImageSrc('');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!aborted) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -127,11 +175,11 @@ export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
|||||||
|
|
||||||
// Cleanup function
|
// Cleanup function
|
||||||
return () => {
|
return () => {
|
||||||
if (objectUrl) {
|
aborted = true;
|
||||||
URL.revokeObjectURL(objectUrl);
|
objectUrls.forEach((url) => URL.revokeObjectURL(url));
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}, [src, fallbackSrc, useWatermark, isGallery]);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [src, fallbackSrc, slug]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user