UNPKG

@scayle/storefront-nuxt

Version:

Nuxt integration for the SCAYLE Commerce Engine and Storefront API

124 lines (123 loc) 3.85 kB
import { defineEventHandler, readBody, createError, getQuery, getRequestURL, appendResponseHeader } from "h3"; import { HttpStatusCode, purifySensitiveData, DEFAULT_RPC_HTTP_METHOD } from "@scayle/storefront-core"; import { trace, SpanStatusCode } from "@opentelemetry/api"; import { invokeRpc } from "../handler.js"; import { resolveError } from "../error/handler.js"; import { useNitroApp } from "nitropack/runtime"; import { rpcHttpMethods } from "#virtual/rpcHttpMethods"; export default defineEventHandler(async (event) => { const $rpcContext = event.context.$rpcContext; if (!$rpcContext) { return new Response("No $rpcContext was found for the request", { status: HttpStatusCode.INTERNAL_SERVER_ERROR }); } const tracer = trace.getTracer("storefront-nuxt", "__sfc_version"); const pathComponents = event.path.split("?")[0].split("/"); const method = pathComponents[pathComponents.length - 1]; const httpMethod = rpcHttpMethods[method] ?? DEFAULT_RPC_HTTP_METHOD; const isGetMethod = httpMethod === "GET"; let payload; if (isGetMethod) { const query = getQuery(event); const rawPayload = query.payload; if (rawPayload !== void 0) { if (typeof rawPayload !== "string") { throw createError({ statusCode: 400, statusMessage: "Invalid payload: expected a single query parameter" }); } try { payload = JSON.parse(rawPayload); } catch { throw createError({ statusCode: 400, statusMessage: "Invalid payload: malformed JSON" }); } } } else { const body = await readBody(event); payload = body?.payload; } const url = getRequestURL(event, { xForwardedHost: true, xForwardedProto: true }); const payloadToBeLogged = JSON.stringify( purifySensitiveData( typeof payload !== "object" || payload === null ? { [typeof payload]: payload } : payload ) ); $rpcContext.log.space("sfc").debug(`RPC Handler: ${method}, Payload: ${payloadToBeLogged}`); const nitroApp = useNitroApp(); appendResponseHeader(event, "Vary", "x-shop-id"); return await tracer.startActiveSpan( `storefront-nuxt.rpc/${method}`, { attributes: { // https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#common-attributes "rpc.method": method, "rpc.service": "storefront-nuxt.rpc", "rpc.payload": payloadToBeLogged, // https://opentelemetry.io/docs/specs/semconv/http/http-spans/#http-server "http.method": httpMethod, "server.address": url.hostname, "server.port": url.port } }, async (span) => { let result; try { await nitroApp.hooks?.callHook( "storefront:rpc:before", method, $rpcContext, payload ); result = await invokeRpc(method, payload, $rpcContext); await nitroApp.hooks.callHook( "storefront:rpc:after", method, $rpcContext, result ); if (result instanceof Response && !result.ok) { span.setStatus({ code: SpanStatusCode.ERROR }); } else { span.setStatus({ code: SpanStatusCode.OK }); } } catch (e) { $rpcContext.log.space("sfc").error(`RPC Handler failed: ${method}.`, e); await nitroApp.hooks.callHook( "storefront:rpc:error", method, $rpcContext, e ); result = createError(resolveError(e)); if (e instanceof Error) { span.recordException(e); if (e.cause && e.cause instanceof Error) { span.recordException(e.cause); } } span.setStatus({ code: SpanStatusCode.ERROR }); } span.end(); return result; } ); });