(cmd: string, params?: Dict, options: ApiCallOptions={})
| 39 | type ApiData<FT> = Jsonify<FT extends (...args: any[]) => infer R ? SanitizeReturn<R> : SanitizeReturn<FT>> |
| 40 | type SanitizeReturn<R> = Exclude<Awaited<R>, BackendApiError | Readable | AsyncGenerator<any>> |
| 41 | export function apiCall<FT=any>(cmd: string, params?: Dict, options: ApiCallOptions={}) { |
| 42 | _.defaults(options, defaultApiCallOptions) |
| 43 | const stop = options.modal?.(cmd, params) |
| 44 | const controller = window.AbortController ? new AbortController() : undefined |
| 45 | let aborted = '' |
| 46 | const ms = 1000 * (timeoutByApi[cmd] ?? options.timeout ?? 10) |
| 47 | const timeout = ms && setTimeout(() => { |
| 48 | controller?.abort(aborted = 'timeout') |
| 49 | console.debug('API TIMEOUT', cmd, params??'') |
| 50 | }, ms) |
| 51 | const asRest = options.restUri |
| 52 | const started = new Date |
| 53 | // rebuilding the whole url makes it resistant to url-with-credentials |
| 54 | return Object.assign(fetch(`${location.origin}${asRest || (getPrefixUrl() + API_URL + cmd)}`, { |
| 55 | method: asRest ? cmd : (options.method || 'POST'), |
| 56 | headers: { 'content-type': 'application/json', 'x-hfs-anti-csrf': '1' }, |
| 57 | signal: controller?.signal, |
| 58 | body: params && JSON.stringify(params), |
| 59 | }).then(async res => { |
| 60 | stop?.() |
| 61 | let body: any = await res.text() |
| 62 | let data: ApiData<FT> |
| 63 | try { data = options.skipParse ? body : JSON.parse(body) } |
| 64 | catch { data = body } |
| 65 | if (!options?.skipLog) |
| 66 | console.debug(res.ok ? 'API' : 'API FAILED', cmd, params??'', '>>', data, { started: formatTime(started), duration: (Date.now() - +started) }) |
| 67 | await options.onResponse?.(res, data) |
| 68 | if (!res.ok) |
| 69 | throw new ApiError(res.status, data === body ? body : `Failed API ${cmd}: ${res.statusText}`, data) |
| 70 | return data |
| 71 | }, err => { |
| 72 | stop?.() |
| 73 | if (err?.message?.includes('fetch')) { |
| 74 | console.error(err.message) |
| 75 | throw Error("Server unreachable") |
| 76 | } |
| 77 | throw aborted || err |
| 78 | }).finally(() => clearTimeout(timeout)), { |
| 79 | abort() { |
| 80 | controller?.abort(aborted='cancel') |
| 81 | }, |
| 82 | aborted: () => controller?.signal.aborted |
| 83 | }) |
| 84 | } |
| 85 | |
| 86 | export class ApiError extends Error { |
| 87 | constructor(readonly code:number, message: string, data?: any) { |
no test coverage detected