fetch.ts 5.3 KB
Newer Older
P
Peter Pan 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright 2020 Baidu Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

P
Peter Pan 已提交
17 18
import type {TFunction} from 'i18next';
import i18next from 'i18next';
19
import queryString from 'query-string';
20

21 22
const API_TOKEN_KEY: string = import.meta.env.SNOWPACK_PUBLIC_API_TOKEN_KEY;
const API_URL: string = import.meta.env.SNOWPACK_PUBLIC_API_URL;
23

24 25
const API_TOKEN_HEADER = 'X-VisualDL-Instance-ID';

26
const instanceId = API_TOKEN_KEY ? queryString.parse(window.location.search)[API_TOKEN_KEY] : '';
27 28 29 30 31

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

32
function addApiToken(options?: RequestInit): RequestInit | undefined {
33 34
    const id = getApiToken();
    if (!API_TOKEN_KEY || !id) {
35 36 37
        return options;
    }
    const {headers, ...rest} = options || {};
38 39 40 41 42 43
    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);
    }
44 45
    return {
        ...rest,
46
        headers: newHeaders
47 48 49
    };
}

P
Peter Pan 已提交
50 51 52 53
interface SuccessData<D> {
    status: 0;
    data: D;
}
54

P
Peter Pan 已提交
55 56 57 58 59 60
interface ErrorData {
    status: number;
    msg?: string;
}

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

62 63 64
export type BlobResponse = {
    data: Blob;
    type: string | null;
65
    filename: string | null;
66 67
};

P
Peter Pan 已提交
68 69 70
function getT(): Promise<TFunction> {
    return new Promise(resolve => {
        // Bug of i18next
P
Peter Pan 已提交
71
        i18next.changeLanguage(undefined as unknown as string).then(t => resolve(t));
P
Peter Pan 已提交
72 73 74 75 76 77 78 79 80 81 82
    });
}

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>;
P
Peter Pan 已提交
83
export function fetcher(url: string, options?: RequestInit): Promise<string>;
P
Peter Pan 已提交
84
export function fetcher<T = unknown>(url: string, options?: RequestInit): Promise<T>;
P
Peter Pan 已提交
85
export async function fetcher<T = unknown>(url: string, options?: RequestInit): Promise<BlobResponse | string | T> {
P
Peter Pan 已提交
86 87 88 89 90 91 92 93 94 95 96
    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 已提交
97
    }
P
Peter Pan 已提交
98

P
Peter Pan 已提交
99 100 101
    const contentType = res.headers.get('content-type') ?? '';
    if (contentType.includes('application/json')) {
        let response: Data<T> | T;
P
Peter Pan 已提交
102
        try {
P
Peter Pan 已提交
103
            response = await res.json();
P
Peter Pan 已提交
104 105 106 107 108 109 110 111 112 113
        } 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 已提交
114
            }
P
Peter Pan 已提交
115 116
        }
        return response;
P
Peter Pan 已提交
117 118 119 120 121 122 123 124 125
    } else if (contentType.startsWith('text/')) {
        let response: string;
        try {
            response = await res.text();
        } catch (e) {
            const t = await logErrorAndReturnT(e);
            throw new Error(t('errors:parse-error'));
        }
        return response;
P
Peter Pan 已提交
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
    } 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 已提交
151
            }
152
        }
P
Peter Pan 已提交
153
        return {data, type: res.headers.get('Content-Type'), filename};
154
    }
P
Peter Pan 已提交
155
}
P
Peter Pan 已提交
156

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