@scayle/storefront-nuxt
Version:
Nuxt integration for the SCAYLE Commerce Engine and Storefront API
124 lines (123 loc) • 3.85 kB
JavaScript
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;
}
);
});