@safaricom-mxl/nextjs
Version:
MXL Javascript RUM agent for nextjs
106 lines (103 loc) • 3.45 kB
JavaScript
// src/create-next-route-handler.ts
import { createHash } from "crypto";
import { NextResponse } from "next/server.js";
// src/constants.ts
var SCRIPT_URL = "https://mxl-scripts.apps.ocpthikadev01.safaricom.net/v1";
var DEFAULT_API_URL = "https://mxl-server.apps.ocpthikadev01.safaricom.net";
var SCRIPT_PATH = "/mxl.js";
// src/create-next-route-handler.ts
function getClientHeaders(req) {
const headers = new Headers();
const ip = req.headers.get("cf-connecting-ip") ?? req.headers.get("x-forwarded-for")?.split(",")[0] ?? req.headers.get("x-vercel-forwarded-for");
headers.set("Content-Type", "application/json");
headers.set("mxl-client-id", req.headers.get("mxl-client-id") ?? "");
const origin = req.headers.get("origin") ?? (() => {
const url = new URL(req.url);
return `${url.protocol}//${url.host}`;
})();
headers.set("origin", origin);
headers.set("User-Agent", req.headers.get("user-agent") ?? "");
if (ip) {
headers.set("mxl-client-ip", ip);
}
return headers;
}
async function handleApiRoute(req, apiUrl, apiPath) {
const headers = getClientHeaders(req);
try {
const res = await fetch(`${apiUrl}${apiPath}`, {
method: req.method,
headers,
body: req.method === "POST" ? JSON.stringify(await req.json()) : void 0
});
if (res.headers.get("content-type")?.includes("application/json")) {
return NextResponse.json(await res.json(), { status: res.status });
}
return NextResponse.json(await res.text(), { status: res.status });
} catch (e) {
return NextResponse.json(
{
error: "Failed to proxy request",
message: e instanceof Error ? e.message : String(e)
},
{ status: 500 }
);
}
}
async function handleScriptProxyRoute(req) {
const url = new URL(req.url);
const pathname = url.pathname;
if (!pathname.endsWith(SCRIPT_PATH)) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
let scriptUrl = `${SCRIPT_URL}${SCRIPT_PATH}`;
if (url.searchParams.size > 0) {
scriptUrl += `?${url.searchParams.toString()}`;
}
try {
const res = await fetch(scriptUrl, {
// @ts-expect-error
next: { revalidate: 86400 }
});
const text = await res.text();
const etag = `"${createHash("md5").update(scriptUrl + text).digest("hex")}"`;
return new NextResponse(text, {
headers: {
"Content-Type": "text/javascript",
"Cache-Control": "public, max-age=86400, stale-while-revalidate=86400",
ETag: etag
}
});
} catch (e) {
return NextResponse.json(
{
error: "Failed to fetch script",
message: e instanceof Error ? e.message : String(e)
},
{ status: 500 }
);
}
}
function createRouteHandler(options) {
const apiUrl = options?.apiUrl ?? DEFAULT_API_URL;
const handler = async function handler2(req) {
const url = new URL(req.url);
const pathname = url.pathname;
const method = req.method;
if (method === "GET" && pathname.endsWith(SCRIPT_PATH)) {
return handleScriptProxyRoute(req);
}
const apiPathMatch = pathname.indexOf("/track");
if (apiPathMatch === -1) {
return NextResponse.json({ error: "Not found" }, { status: 404 });
}
const apiPath = pathname.substring(apiPathMatch);
return handleApiRoute(req, apiUrl, apiPath);
};
handler.GET = handler;
handler.POST = handler;
return handler;
}
export {
createRouteHandler
};