UNPKG

@codesandbox/api

Version:
222 lines 8.94 kB
import { __rest } from "tslib"; import humps from "humps"; const { camelizeKeys, camelize } = humps; export class ApiResponseError extends Error { constructor(status, message, parsedErrorMessage) { super(`(${status}) ${message}`); this.type = "ERROR"; this.parsedErrorMessage = parsedErrorMessage; if (status === 404 || message.toLowerCase().includes("not found")) { this.type = "NOT_FOUND"; } else if (status === 402) { this.type = "WORKSPACE_FROZEN"; } } } export const onRequest = (request) => { return fetch(request.url, { method: request.method, headers: request.headers, body: request.method === "POST" || request.method === "PATCH" || request.method === "PUT" ? JSON.stringify(request.data) : undefined, }).then(async (response) => { var _a; return { status: response.status, data: await response.text(), cookies: (_a = response.headers.get("set-cookie")) !== null && _a !== void 0 ? _a : "", }; }); }; /** * Combines the passed in OnRequest with custom headers, including authorization. Also detects * if a response indicates being unauthorized */ export function createRESTRequester(options) { // We have to type the generic explicitly return (_a) => { var { path } = _a, requestOptions = __rest(_a, ["path"]); const headers = Object.assign(Object.assign({}, requestOptions.headers), { "Content-Type": "application/json" }); const bearerToken = options.getBearerToken(); if (bearerToken) { headers.Authorization = `Bearer ${bearerToken}`; } /** * We add this header to identify the client type, but not when * in the browser and the baseUrl differs from your location. This * would create a CORS error */ if (typeof window === "undefined" || options.baseUrl.includes(window.location.origin)) { headers["x-codesandbox-client"] = options.clientType; } const url = options.baseUrl + path; return handleRestResponse(options .onRequest(Object.assign(Object.assign({}, requestOptions), { url, headers })) .then((response) => { options.onResponse(response); return response; }), url); }; } const customResponseHandlers = [ (path, data) => path.includes("/v1/sandboxes/") && path.endsWith("/env") ? data : undefined, (path, data) => (path.includes("/beta/repos/secrets") ? data : undefined), (path, data) => { // The sandbox path, should really use a regexp here though if (path.includes("/v1/sandboxes/") && !path.endsWith("/env")) { if (!("data" in data) || data.data == null) { return undefined; } const sandboxData = data.data; return { data: Object.keys(sandboxData).reduce((aggr, key) => { // Do not camelize the keys of NPM dependencies aggr[camelize(key)] = key === "npm_dependencies" ? sandboxData[key] : camelizeKeys(sandboxData[key]); return aggr; }, {}), }; } return undefined; }, ]; /** * The response of REST is in the format of { data?, errors? } | data */ export function handleRestResponse(responsePromise, requestUrl) { return responsePromise.then((response) => { try { if (response.status === 404) { throw new ApiResponseError(response.status, "Not found"); } if (response.status === 402) { throw new ApiResponseError(response.status, "WORKSPACE_FROZEN"); } // Delete operations return a 204 (no-content) which cannot be deserialized as json // Since there's no response object, we pass an empty object if (response.status === 204) { return {}; } if (response.status >= 200 && response.status < 300) { const parsedResponse = JSON.parse(response.data || "{}"); const customResponse = customResponseHandlers .map((cb) => cb(requestUrl, parsedResponse)) .filter((value) => value !== undefined)[0]; const isGQLErrorResponse = !parsedResponse.data && Array.isArray(parsedResponse.errors); if (isGQLErrorResponse) { throw new ApiResponseError(response.status, `\n${JSON.stringify(parsedResponse.errors, null, 2)}`, parsedResponse.errors[0].message); } return (customResponse ? customResponse : camelizeKeys(parsedResponse)); } // At this point we can only have error states const errorData = response.data ? JSON.parse(response.data) : undefined; // When getting a response with a "data" property we also possibly get "errors", which // is either an array of errors or has a "detail" property with an array of errors if (errorData && "errors" in errorData) { let errors; if (Array.isArray(errorData.errors)) { errors = errorData.errors; } else if ("id" in errorData.errors) { errors = errorData.errors.id; } else if ("code" in errorData.errors) { errors = errorData.errors.code; } else if (errorData.errors.detail) { errors = errorData.errors.detail; } else { errors = ["Unknown error"]; } throw new ApiResponseError(response.status, `\n${errors.join("\n")}`, errors[0]); } if (errorData && "error" in errorData) { throw new ApiResponseError(response.status, `\n${typeof errorData.error === "string" ? errorData.error : errorData.error.message}`); } if (errorData && "code" in errorData && "message" in errorData) { throw new ApiResponseError(response.status, `\n${JSON.stringify(errorData.message)}`); } throw new ApiResponseError(response.status, `Unknown error`); } catch (error) { if (error instanceof ApiResponseError) { throw error; } throw new ApiResponseError(response.status, `- URL: ${requestUrl} - DATA: ${String(response.data)} - ERROR: ${String(error)} `); } }); } function getPopupOffset({ width, height }) { const wLeft = window.screenLeft ? window.screenLeft : window.screenX; const wTop = window.screenTop ? window.screenTop : window.screenY; const left = wLeft + window.innerWidth / 2 - width / 2; const top = wTop + window.innerHeight / 2 - height / 2; return { top, left }; } function getPopupSize() { return { width: 1020, height: 618 }; } function getPopupDimensions() { const { width, height } = getPopupSize(); const { top, left } = getPopupOffset({ width, height }); return `width=${width},height=${height},top=${top},left=${left}`; } export function openPopup(url, name) { let closeResolver; const popup = window.open(url, name, `scrollbars=no,toolbar=no,location=no,titlebar=no,directories=no,status=no,menubar=no, ${getPopupDimensions()}`); if (popup) { const timer = setInterval(function () { if (popup.closed) { clearInterval(timer); closeResolver(); } }, 500); } const close = () => { popup === null || popup === void 0 ? void 0 : popup.close(); }; const closePromise = new Promise((resolve) => { closeResolver = resolve; }); return { close, closePromise, }; } export function waitForMessage(cb) { return new Promise((resolve) => { window.addEventListener("message", function onMessage(event) { if (typeof cb === "function" && cb(event.data)) { window.removeEventListener("message", onMessage); resolve(event.data); } }); }); } /** Auth utils */ // Used at the sign in, 1:1 map to an actual GitHub scope. export const GITHUB_BASE_SCOPE = "user:email"; // Map scope options to the corresponding GitHub scopes. export const MAP_GITHUB_SCOPE_OPTIONS = { public_repos: "public_repo,read:org,workflow", private_repos: "repo,read:org,workflow", }; //# sourceMappingURL=utils.js.map