fetch.ts 4.2 KB
Newer Older
P
Peter Pan 已提交
1 2
import type {TFunction} from 'i18next';
import i18next from 'i18next';
3
import queryString from 'query-string';
4

5 6
const API_TOKEN_KEY: string = import.meta.env.SNOWPACK_PUBLIC_API_TOKEN_KEY;
const API_URL: string = import.meta.env.SNOWPACK_PUBLIC_API_URL;
7

8 9
const API_TOKEN_HEADER = 'X-VisualDL-Instance-ID';

10
const instanceId = API_TOKEN_KEY ? queryString.parse(window.location.search)[API_TOKEN_KEY] : '';
11 12 13 14 15

export function getApiToken(): string | string[] | null {
    return instanceId ?? null;
}

16
function addApiToken(options?: RequestInit): RequestInit | undefined {
17 18
    const id = getApiToken();
    if (!API_TOKEN_KEY || !id) {
19 20 21
        return options;
    }
    const {headers, ...rest} = options || {};
22 23 24 25 26 27
    const newHeaders = new Headers(headers);
    if (Array.isArray(id)) {
        id.forEach(value => newHeaders.append(API_TOKEN_HEADER, value));
    } else {
        newHeaders.append(API_TOKEN_HEADER, id);
    }
28 29
    return {
        ...rest,
30
        headers: newHeaders
31 32 33
    };
}

P
Peter Pan 已提交
34 35 36 37
interface SuccessData<D> {
    status: 0;
    data: D;
}
38

P
Peter Pan 已提交
39 40 41 42 43 44
interface ErrorData {
    status: number;
    msg?: string;
}

type Data<D> = SuccessData<D> | ErrorData;
45

46 47 48
export type BlobResponse = {
    data: Blob;
    type: string | null;
49
    filename: string | null;
50 51
};

P
Peter Pan 已提交
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
function getT(): Promise<TFunction> {
    return new Promise(resolve => {
        // Bug of i18next
        i18next.changeLanguage((undefined as unknown) as string).then(t => resolve(t));
    });
}

function logErrorAndReturnT(e: unknown) {
    if (import.meta.env.MODE === 'development') {
        console.error(e); // eslint-disable-line no-console
    }
    return getT();
}

export function fetcher(url: string, options?: RequestInit): Promise<BlobResponse>;
export function fetcher<T = unknown>(url: string, options?: RequestInit): Promise<T>;
export async function fetcher<T = unknown>(url: string, options?: RequestInit): Promise<BlobResponse | T> {
    let res: Response;
    try {
        res = await fetch(API_URL + url, addApiToken(options));
    } catch (e) {
        const t = await logErrorAndReturnT(e);
        throw new Error(t('errors:network-error'));
    }

    if (!res.ok) {
        const t = await logErrorAndReturnT(res);
        throw new Error(t([`errors:response-error.${res.status}`, 'errors:response-error.unknown']));
P
Peter Pan 已提交
80
    }
P
Peter Pan 已提交
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

    let response: Data<T> | T;
    try {
        if (res.headers.get('content-type')?.includes('application/json')) {
            response = await res.json();
            if (response && 'status' in response) {
                if (response.status !== 0) {
                    const t = await logErrorAndReturnT(response);
                    throw new Error((response as ErrorData).msg || t('errors:error'));
                } else {
                    return (response as SuccessData<T>).data;
                }
            }
            return response;
        } else {
            const data = await res.blob();
            const disposition = res.headers.get('Content-Disposition');
            // support safari
            if (!data.arrayBuffer) {
                data.arrayBuffer = async () =>
                    new Promise<ArrayBuffer>((resolve, reject) => {
                        const fileReader = new FileReader();
                        fileReader.addEventListener('load', e =>
                            e.target ? resolve(e.target.result as ArrayBuffer) : reject()
                        );
                        fileReader.readAsArrayBuffer(data);
                    });
            }
            let filename: string | null = null;
            if (disposition && disposition.indexOf('attachment') !== -1) {
                const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(disposition);
                if (matches != null && matches[1]) {
                    filename = matches[1].replace(/['"]/g, '');
                }
            }
            return {data, type: res.headers.get('Content-Type'), filename};
117
        }
P
Peter Pan 已提交
118 119 120
    } catch (e) {
        const t = await logErrorAndReturnT(e);
        throw new Error(t('errors:parse-error'));
121
    }
P
Peter Pan 已提交
122
}
P
Peter Pan 已提交
123

124
export const cycleFetcher = async <T = unknown>(urls: string[], options?: RequestInit): Promise<T[]> => {
P
Peter Pan 已提交
125
    return await Promise.all(urls.map(url => fetcher<T>(url, options)));
126
};