feat: Add detailed logging for user profile components and image metadata extraction; enhance color handling utilities
This commit is contained in:
@@ -170,6 +170,8 @@ export function detectImageFormat(data: ArrayBuffer | Uint8Array): ImageFormat {
|
||||
export function extractColorFromImage(imageData: ArrayBuffer | Uint8Array): string | undefined {
|
||||
const format = detectImageFormat(imageData);
|
||||
|
||||
console.log('[extractColorFromImage] Detected format:', format);
|
||||
|
||||
switch (format) {
|
||||
case 'png':
|
||||
return extractColorFromPNG(imageData);
|
||||
@@ -211,14 +213,19 @@ export function extractBannerFromImage(imageData: ArrayBuffer | Uint8Array): str
|
||||
export function extractMetadataFromImage(imageData: ArrayBuffer | Uint8Array): ImageMetadata {
|
||||
const format = detectImageFormat(imageData);
|
||||
|
||||
console.log('[extractMetadataFromImage] Detected format:', format);
|
||||
|
||||
switch (format) {
|
||||
case 'png':
|
||||
return extractMetadataFromPNG(imageData);
|
||||
const pngMetadata = extractMetadataFromPNG(imageData);
|
||||
console.log('[extractMetadataFromImage] PNG metadata:', pngMetadata);
|
||||
return pngMetadata;
|
||||
// For other formats, extract color only for now
|
||||
case 'webp':
|
||||
case 'jpeg':
|
||||
case 'gif': {
|
||||
const color = extractColorFromImage(imageData);
|
||||
console.log('[extractMetadataFromImage] Non-PNG color:', color);
|
||||
return color ? { color } : {};
|
||||
}
|
||||
default:
|
||||
@@ -344,6 +351,10 @@ export function getExtension(format: ImageFormat): string {
|
||||
const avatarMetadataCache = new LRUCache<string, { metadata: ImageMetadata; timestamp: number }>(100);
|
||||
const METADATA_CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
// In-flight fetch tracking to prevent duplicate parallel requests
|
||||
const inflightColorFetches = new Map<string, Promise<string | undefined>>();
|
||||
const inflightMetadataFetches = new Map<string, Promise<ImageMetadata>>();
|
||||
|
||||
/**
|
||||
* Fetch and extract color from an image URL (supports all formats)
|
||||
* @param url - HTTP URL to fetch the image from
|
||||
@@ -354,29 +365,51 @@ export async function fetchAndExtractColor(url: string, accessToken?: string | n
|
||||
// Check if full metadata is cached
|
||||
const cached = avatarMetadataCache.get(url);
|
||||
if (cached && Date.now() - cached.timestamp < METADATA_CACHE_TTL_MS) {
|
||||
console.log('[fetchAndExtractColor] Using cached color for', url, ':', cached.metadata.color);
|
||||
return cached.metadata.color;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
});
|
||||
if (!response.ok) return undefined;
|
||||
|
||||
const data = await response.arrayBuffer();
|
||||
const color = extractColorFromImage(data);
|
||||
|
||||
// Cache the color result as part of metadata
|
||||
avatarMetadataCache.set(url, {
|
||||
metadata: { color },
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return color;
|
||||
} catch {
|
||||
return undefined;
|
||||
// Check if fetch already in progress
|
||||
const inflightKey = `${url}:${accessToken || ''}`;
|
||||
const existingFetch = inflightColorFetches.get(inflightKey);
|
||||
if (existingFetch) {
|
||||
console.log('[fetchAndExtractColor] Reusing in-flight fetch for', url);
|
||||
return existingFetch;
|
||||
}
|
||||
|
||||
console.log('[fetchAndExtractColor] Fetching and extracting color from', url);
|
||||
|
||||
const fetchPromise = (async () => {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
});
|
||||
|
||||
console.log('[fetchAndExtractColor] Fetch response status:', response.ok, response.status);
|
||||
|
||||
if (!response.ok) return undefined;
|
||||
|
||||
const data = await response.arrayBuffer();
|
||||
console.log('[fetchAndExtractColor] Downloaded', data.byteLength, 'bytes');
|
||||
|
||||
const color = extractColorFromImage(data);
|
||||
console.log('[fetchAndExtractColor] Extracted color:', color);
|
||||
|
||||
// Don't cache here - fetchAndExtractMetadata will cache full metadata
|
||||
// This avoids cache collision where color-only fetch overwrites full metadata
|
||||
|
||||
return color;
|
||||
} catch (error) {
|
||||
console.error('[fetchAndExtractColor] Error fetching/extracting color:', error);
|
||||
return undefined;
|
||||
} finally {
|
||||
inflightColorFetches.delete(inflightKey);
|
||||
}
|
||||
})();
|
||||
|
||||
inflightColorFetches.set(inflightKey, fetchPromise);
|
||||
return fetchPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,27 +422,51 @@ export async function fetchAndExtractMetadata(url: string, accessToken?: string
|
||||
// Check cache first
|
||||
const cached = avatarMetadataCache.get(url);
|
||||
if (cached && Date.now() - cached.timestamp < METADATA_CACHE_TTL_MS) {
|
||||
console.log('[fetchAndExtractMetadata] Using cached metadata for', url, ':', cached.metadata);
|
||||
return cached.metadata;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
});
|
||||
if (!response.ok) return {};
|
||||
|
||||
const data = await response.arrayBuffer();
|
||||
const metadata = extractMetadataFromImage(data);
|
||||
|
||||
// Cache the result
|
||||
avatarMetadataCache.set(url, {
|
||||
metadata,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return metadata;
|
||||
} catch {
|
||||
return {};
|
||||
// Check if fetch already in progress
|
||||
const inflightKey = `${url}:${accessToken || ''}`;
|
||||
const existingFetch = inflightMetadataFetches.get(inflightKey);
|
||||
if (existingFetch) {
|
||||
console.log('[fetchAndExtractMetadata] Reusing in-flight fetch for', url);
|
||||
return existingFetch;
|
||||
}
|
||||
|
||||
console.log('[fetchAndExtractMetadata] Fetching metadata from', url);
|
||||
|
||||
const fetchPromise = (async () => {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
|
||||
});
|
||||
|
||||
console.log('[fetchAndExtractMetadata] Fetch response status:', response.ok, response.status);
|
||||
|
||||
if (!response.ok) return {};
|
||||
|
||||
const data = await response.arrayBuffer();
|
||||
console.log('[fetchAndExtractMetadata] Downloaded', data.byteLength, 'bytes');
|
||||
|
||||
const metadata = extractMetadataFromImage(data);
|
||||
console.log('[fetchAndExtractMetadata] Extracted metadata:', metadata);
|
||||
|
||||
// Cache the result
|
||||
avatarMetadataCache.set(url, {
|
||||
metadata,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
return metadata;
|
||||
} catch {
|
||||
return {};
|
||||
} finally {
|
||||
inflightMetadataFetches.delete(inflightKey);
|
||||
}
|
||||
})();
|
||||
|
||||
inflightMetadataFetches.set(inflightKey, fetchPromise);
|
||||
return fetchPromise;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user