UNPKG

@chillicream/nitro-server-adapter-plugin

Version:

Server Adapter Plugin for Nitro GraphQL IDE

185 lines (181 loc) 6.97 kB
/*! * @license ChilliCream License 1.0 * * Copyright (c) ChilliCream, Inc. * * This source code is licensed under the ChilliCream License 1.0 found in the * LICENSE file in the root directory of this source tree. */ import fs from 'node:fs'; import module from 'node:module'; import path from 'node:path'; const Defaults = { CDN_URL: "https://cdn.chillicream.com/web", VERSION: "latest", EMBEDDED_ID: "@chillicream/nitro-embedded", CONFIG: { mode: "cdn", }, OPTIONS: { useBrowserUrlAsEndpoint: true, }, }; const Paths = { ROOT: "/", INDEX: "/index.html", CONFIG: "/nitro-config.json", }; function resolveUrl(target) { return typeof target === "string" ? target : `${target?.baseUrl || Defaults.CDN_URL}/${target?.version || Defaults.VERSION}`; } function resolvePath(target) { const require = module.createRequire(import.meta.url); return path.dirname(require.resolve(target || Defaults.EMBEDDED_ID)); } function resolveConfig(config) { return Object.assign({}, Defaults.CONFIG, config); } function resolveOptions(options) { return Object.assign({}, Defaults.OPTIONS, options); } function useNitro(config) { const resolvedConfig = resolveConfig(config); const basePath = resolveBasePath(resolvedConfig.path); return { onRequest({ request, url, fetchAPI, endResponse }) { if (request.method === "GET" || request.method === "HEAD") { const acceptHeader = request.headers.get("accept"); // handle root request if (url.pathname === basePath && (acceptHeader?.includes("*/*") || acceptHeader?.includes("text/html"))) { const [path, query] = request.url.split("?"); if (!path.endsWith("/")) { const newPath = path + "/" + (query?.length ? "?" + query : ""); return endResponse(fetchAPI.Response.redirect(newPath, 301)); } } const configPath = basePath + Paths.CONFIG; // handle config request if (url.pathname === configPath && (acceptHeader?.includes("*/*") || acceptHeader?.includes("application/json"))) { const resolvedOptions = resolveOptions(resolvedConfig.options); const responseMetadata = { status: 200, statusText: "OK", headers: { "Content-Type": "application/json", "Content-Length": JSON.stringify(resolvedOptions).length + "", }, }; if (request.method === "GET") { return endResponse(fetchAPI.Response.json(resolvedOptions, responseMetadata)); } else { return endResponse(new fetchAPI.Response(undefined, responseMetadata)); } } const pathname = url.pathname.substring(basePath.length); if (resolvedConfig.mode === "cdn") { const baseUrl = resolveUrl(resolvedConfig.target); const requestHeaders = new Headers(request.headers); requestHeaders.delete("host"); requestHeaders.delete("accept-encoding"); return fetchAPI .fetch(baseUrl + pathname, { method: request.method, headers: requestHeaders, body: request.body, mode: request.mode, credentials: request.credentials, cache: request.cache, redirect: request.redirect, referrer: request.referrer, referrerPolicy: request.referrerPolicy, integrity: request.integrity, keepalive: request.keepalive, // NOTE: if uncommented, leads to an unhandled exception in the // fetchNodeHttp module; needs to be investigated! //signal: request.signal, }) .then(endResponse, () => endResponse(fetchAPI.Response.error())); } else { const baseFilePath = resolvePath(resolvedConfig.target); return serverStatic(pathname, baseFilePath, fetchAPI, endResponse); } } }, }; } function resolveBasePath(path) { return path?.length ? ensureStartsWithSlash(ensureEndsNotWithSlash(path)) : "/graphql"; } function ensureStartsWithSlash(path) { return path.startsWith("/") ? path : "/" + path; } function ensureEndsNotWithSlash(path) { return path.endsWith("/") && path !== Paths.ROOT ? path.substring(0, path.length - 1) : path; } function serverStatic(path, baseFilePath, fetchAPI, endResponse) { function write(filePath, contentType) { try { const data = fs.readFileSync(filePath); return endResponse(new fetchAPI.Response(data, { status: 200, statusText: "OK", headers: { "Content-Type": contentType, "Content-Length": data.length + "", }, })); } catch { } } if (path.endsWith(".css")) { return write(baseFilePath + path, "text/css"); } if (path.endsWith(".html")) { return write(baseFilePath + path, "text/html"); } if (path.endsWith(".ico")) { return write(baseFilePath + path, "image/x-icon"); } if (path.endsWith(".js")) { return write(baseFilePath + path, "application/javascript"); } if (path.endsWith(".json")) { return write(baseFilePath + path, "application/json"); } if (path.endsWith(".png")) { return write(baseFilePath + path, "image/png"); } if (path.endsWith(".svg")) { return write(baseFilePath + path, "image/svg+xml"); } if (path.endsWith(".ttf")) { return write(baseFilePath + path, "font/ttf"); } if (path.endsWith(".txt")) { return write(baseFilePath + path, "text/plain"); } if (path.endsWith(".webmanifest")) { return write(baseFilePath + path, "application/manifest+json"); } if (path.endsWith(".woff2")) { return write(baseFilePath + path, "font/woff2"); } if (path.endsWith(".woff2")) { return write(baseFilePath + path, "font/woff2"); } if (path === Paths.ROOT) { return write(baseFilePath + "/index.html", "text/html"); } } export { resolveBasePath, useNitro };