Update selected item color in Pass menu
[ProtonMail-WebClient.git] / packages / pass / lib / api / images.ts
blobe43ef434fc51bf22060cd2b32d3b8ca75331d337
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';
7 import {
8     CACHED_IMAGE_DEFAULT_MAX_AGE,
9     CACHED_IMAGE_FALLBACK_MAX_AGE,
10     getCache,
11     shouldRevalidate,
12     withMaxAgeHeaders,
13 } from './cache';
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) => {
19         try {
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);
25         } catch {
26             reject();
27         }
28     }).catch(noop);
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;
37     }
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(), {
46                 status: res.status,
47                 statusText: res.statusText,
48                 headers: withMaxAgeHeaders(res, CACHED_IMAGE_DEFAULT_MAX_AGE),
49             });
51             cache?.put(url, response.clone()).catch(noop);
52             return response;
53         })
54         .catch((err) => {
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();
61                 if (status === 422) {
62                     const response = new Response('Unprocessable Content', {
63                         status,
64                         statusText: res.statusText,
65                         headers: withMaxAgeHeaders(res, CACHED_IMAGE_FALLBACK_MAX_AGE),
66                     });
68                     void cache?.put(url, response.clone()).catch(noop);
69                     return response;
70                 }
72                 return res;
73             }
75             return createNetworkError(408);
76         });
78     if (cachedResponse) {
79         /* FIXME: stale-while-revalidate window should be computed */
80         logger.debug(`[ImageProxy] Serving ${url} from cache`);
81         return cachedResponse;
82     }
84     logger.debug(`[ImageProxy] Attempting to serve ${url} from network`);
85     return response;