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

    let response: Data<T> | T;
P
Peter Pan 已提交
83 84
    if (res.headers.get('content-type')?.includes('application/json')) {
        try {
P
Peter Pan 已提交
85
            response = await res.json();
P
Peter Pan 已提交
86 87 88 89 90 91 92 93 94 95
        } catch (e) {
            const t = await logErrorAndReturnT(e);
            throw new Error(t('errors:parse-error'));
        }
        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;
P
Peter Pan 已提交
96
            }
P
Peter Pan 已提交
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
        }
        return response;
    } else {
        let data: Blob;
        try {
            data = await res.blob();
        } catch (e) {
            const t = await logErrorAndReturnT(e);
            throw new Error(t('errors:parse-error'));
        }
        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, '');
P
Peter Pan 已提交
124
            }
125
        }
P
Peter Pan 已提交
126
        return {data, type: res.headers.get('Content-Type'), filename};
127
    }
P
Peter Pan 已提交
128
}
P
Peter Pan 已提交
129

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