1 import type { Api, Maybe } from '@proton/pass/types';
2 import { logger } from '@proton/pass/utils/logger';
3 import { getApiError } from '@proton/shared/lib/api/helpers/apiErrorHelper';
4 import { ApiError } from '@proton/shared/lib/fetch/ApiError';
5 import noop from '@proton/utils/noop';
8 CACHED_IMAGE_DEFAULT_MAX_AGE,
9 CACHED_IMAGE_FALLBACK_MAX_AGE,
14 import { createAbortResponse, createEmptyResponse, createNetworkError } from './fetch-controller';
15 import { API_BODYLESS_STATUS_CODES } from './utils';
17 export const imageResponsetoDataURL = (response: Response): Promise<Maybe<string>> =>
18 new Promise<Maybe<string>>(async (resolve, reject) => {
20 const blob = await response.blob();
21 const reader = new FileReader();
22 reader.onloadend = () => resolve(reader.result?.toString());
23 reader.onerror = reject;
24 reader.readAsDataURL(blob);
30 export const createImageProxyHandler = (api: Api) => async (url: string, signal?: AbortSignal) => {
31 const cache = await getCache();
32 const cachedResponse = await cache?.match(url).catch(noop);
34 if (cachedResponse && !shouldRevalidate(cachedResponse)) {
35 logger.debug(`[ImageProxy] Serving GET ${url} from cache without revalidation`);
36 return cachedResponse;
39 logger.debug(`[ImageProxy] Attempting to revalidate GET ${url} from network`);
41 const response = api<Response>({ url, output: 'raw', signal, sideEffects: false })
42 .then(async (res) => {
43 logger.debug('[Image] Caching succesfull network response', res.url);
45 const response = new Response(API_BODYLESS_STATUS_CODES.includes(res.status) ? null : await res.blob(), {
47 statusText: res.statusText,
48 headers: withMaxAgeHeaders(res, CACHED_IMAGE_DEFAULT_MAX_AGE),
51 cache?.put(url, response.clone()).catch(noop);
55 if (err?.name === 'AbortError') return createAbortResponse();
57 if (err instanceof ApiError) {
58 const { status } = getApiError(err);
59 const res = err?.response?.bodyUsed ? createEmptyResponse(err.response) : createEmptyResponse();
62 const response = new Response('Unprocessable Content', {
64 statusText: res.statusText,
65 headers: withMaxAgeHeaders(res, CACHED_IMAGE_FALLBACK_MAX_AGE),
68 void cache?.put(url, response.clone()).catch(noop);
75 return createNetworkError(408);
79 /* FIXME: stale-while-revalidate window should be computed */
80 logger.debug(`[ImageProxy] Serving ${url} from cache`);
81 return cachedResponse;
84 logger.debug(`[ImageProxy] Attempting to serve ${url} from network`);