UNPKG

@fiberplane/hono-otel

Version:

Hono middleware to forward OpenTelemetry traces to a local instance of @fiberplane/studio

119 lines (118 loc) 3.89 kB
import { FPX_REQUEST_BODY } from "../../constants.js"; export function formatBody(body) { if (body instanceof FormData) { return formDataToJson(body); } if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) { return "#fpx.arrayBuffer"; } if (body instanceof ReadableStream) { return "#fpx.stream"; } if (body instanceof Blob) { return "#fpx.blob"; } if (body instanceof URLSearchParams) { return body.toString(); } return body; } export async function formatRootRequestBody(request) { if (!request.body) { return null; } const contentType = request.headers.get("content-type"); const shouldParseAsText = contentType?.includes("application/json") || contentType?.includes("text/") || contentType?.includes("x-www-form-urlencoded"); if (shouldParseAsText) { // Return as text return { [FPX_REQUEST_BODY]: await request.text(), }; } // TODO - Check how files are handled if (contentType?.includes("multipart/form-data")) { const formData = await request.formData(); const textifiedFormData = formDataToJson(formData); return { [FPX_REQUEST_BODY]: textifiedFormData, }; } return { [FPX_REQUEST_BODY]: formatBody(request.body), }; } function formDataToJson(formData) { const jsonObject = {}; for (const [key, value] of formData.entries()) { // Handle multiple values for the same key (e.g., checkboxes) if (jsonObject[key]) { if (!Array.isArray(jsonObject[key])) { jsonObject[key] = [jsonObject[key]]; } jsonObject[key].push(value ? formDataValueToString(value) : value); } else { jsonObject[key] = value ? formDataValueToString(value) : value; } } return JSON.stringify(jsonObject); } export function formDataValueToString(value) { if (value instanceof File) { return value.name ?? `#fpx.file.{${value.name}}.{${value.size}}`; } return value; } export async function tryGetResponseBodyAsText(response) { const contentType = response.headers.get("content-type"); if (contentType?.includes("image/")) { return "#fpx.image"; } if (contentType?.includes("application/pdf")) { return "#fpx.pdf"; } if (contentType?.includes("application/zip")) { return "#fpx.zip"; } if (contentType?.includes("audio/")) { return "#fpx.audio"; } if (contentType?.includes("video/")) { return "#fpx.video"; } if (contentType?.includes("application/octet-stream") || contentType?.includes("application/vnd.ms-excel") || contentType?.includes("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") || contentType?.includes("application/vnd.ms-powerpoint") || contentType?.includes("application/vnd.openxmlformats-officedocument.presentationml.presentation") || contentType?.includes("application/msword") || contentType?.includes("application/vnd.openxmlformats-officedocument.wordprocessingml.document")) { return "#fpx.binary"; } try { if (response.body) { const clonedResponse = response.clone(); return await streamToString(clonedResponse.body); } } catch { // swallow error } return null; } // Helper function to convert a ReadableStream to a string async function streamToString(stream) { const reader = stream.getReader(); const decoder = new TextDecoder(); let result = ""; while (true) { const { done, value } = await reader.read(); result += decoder.decode(value, { stream: true }); if (done) { break; } } return result; }