UNPKG

@datalayer/core

Version:
146 lines (145 loc) 4.44 kB
/* * Copyright (c) 2023-2025 Datalayer, Inc. * Distributed under the terms of the Modified BSD License. */ import { URLExt } from '@jupyterlab/coreutils'; import { sleep } from '../utils'; /** * A wrapped error for a fetch response. */ export class RunResponseError extends Error { /** * Create a RunResponseError from a response, * handling the traceback and message as appropriate. * * @param response The response object. * * @returns A promise that resolves with a `RunResponseError` object. */ static async create(response) { try { const data = await response.json(); const { message, errors, warnings, traceback, exception } = data; if (traceback) { console.error(traceback); } const responseError = new RunResponseError(response, message ?? RunResponseError._defaultMessage(response), warnings, errors, exception, traceback ?? ''); return responseError; } catch (e) { console.debug(e); return new RunResponseError(response); } } /** * Create a new response error. */ constructor(response, message = RunResponseError._defaultMessage(response), warnings = undefined, errors = undefined, exceptionMessage = undefined, traceback = '') { super(message); this.name = 'RunResponseError'; this.warnings = warnings ?? []; this.errors = errors ?? []; this.response = response; this.exceptionMessage = exceptionMessage; this.traceback = traceback; } /** * Warnings listed in the response. */ warnings; /** * Errors listed in the response. */ errors; /** * The response associated with the error. */ response; /** * The exception associated with the error. */ exceptionMessage; /** * The traceback associated with the error. */ traceback; static _defaultMessage(response) { return `Invalid response: ${response.status} ${response.statusText}`; } } /** * A wrapped error for a network error. */ export class NetworkError extends TypeError { /** * Create a new network error. */ constructor(original) { super(original.message); this.name = 'NetworkError'; this.stack = original.stack; } } export async function requestDatalayerAPI({ url, method, body, token, signal, headers = {} }) { const headers_ = new Headers(headers); if (!headers_.has('Accept')) { headers_.set('Accept', 'application/json'); } if (!headers_.has('Content-Type')) { headers_.set('Content-Type', 'application/json'); } if (token) { headers_.set('Authorization', `Bearer ${token}`); } // headers_.set('Origin', currentUri()); let response; try { response = await fetch(url, { method: method ?? 'GET', headers: headers_, body: body ? JSON.stringify(body) : undefined, // credentials: token ? 'include' : 'omit', credentials: 'include', mode: 'cors', cache: 'no-store', signal }); } catch (error) { throw new NetworkError(error); } if (response.ok) { response = await wait_for_redirection(response, url, headers_); const content = await response.text(); return (content ? JSON.parse(content) : null); } else { throw await RunResponseError.create(response); } } async function wait_for_redirection(response, url, headers_) { let redirect = response.headers.get('Location'); if (redirect) { const parsedURL = URLExt.parse(url); const baseUrl = parsedURL.protocol + '//' + parsedURL.hostname; if (!redirect.startsWith(baseUrl)) { redirect = URLExt.join(baseUrl, redirect); } } let sleepTimeout = 1000; while (response.status === 202 && redirect) { await sleep(sleepTimeout); sleepTimeout *= 2; response = await fetch(redirect, { method: 'GET', headers: headers_, credentials: 'include', mode: 'cors', cache: 'no-store' }); if (!response.ok) { throw await RunResponseError.create(response); } } return response; }