@replayio-app-building/netlify-recorder 0.36.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -0
- package/dist/index.js +84 -2
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -351,6 +351,10 @@ interface RecordingEndpointResponse {
|
|
|
351
351
|
* needed and returns the current status. Idempotent: re-posting the same
|
|
352
352
|
* request ID will not re-queue an already-queued or recorded request.
|
|
353
353
|
*
|
|
354
|
+
* **POST** with `?_callback=1&requestId=<uuid>` — webhook callback from the
|
|
355
|
+
* recorder service. Updates `backend_requests` status and optionally forwards
|
|
356
|
+
* to the configured `webhookUrl`.
|
|
357
|
+
*
|
|
354
358
|
* **GET** with `?requestId=<uuid>` — returns the current recording status
|
|
355
359
|
* without triggering any recording.
|
|
356
360
|
*
|
package/dist/index.js
CHANGED
|
@@ -890,11 +890,21 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
|
|
|
890
890
|
const nodeCrypto = __require("crypto");
|
|
891
891
|
g.crypto = {
|
|
892
892
|
randomUUID: () => nodeCrypto.randomUUID(),
|
|
893
|
-
getRandomValues: (buf) => nodeCrypto.getRandomValues(buf)
|
|
893
|
+
getRandomValues: (buf) => nodeCrypto.getRandomValues(buf),
|
|
894
|
+
subtle: nodeCrypto.webcrypto?.subtle
|
|
894
895
|
};
|
|
895
896
|
} catch {
|
|
896
897
|
}
|
|
897
898
|
}
|
|
899
|
+
if (g.crypto && typeof g.crypto.subtle === "undefined") {
|
|
900
|
+
try {
|
|
901
|
+
const nodeCrypto = __require("crypto");
|
|
902
|
+
if (nodeCrypto.webcrypto?.subtle) {
|
|
903
|
+
g.crypto.subtle = nodeCrypto.webcrypto.subtle;
|
|
904
|
+
}
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
}
|
|
898
908
|
if (typeof g.Headers === "undefined") {
|
|
899
909
|
const HeadersShim = class Headers {
|
|
900
910
|
_map;
|
|
@@ -1032,6 +1042,24 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
|
|
|
1032
1042
|
handler = handlerModule;
|
|
1033
1043
|
isV2 = true;
|
|
1034
1044
|
}
|
|
1045
|
+
if (!handler && requestInfo.method) {
|
|
1046
|
+
const httpMethodNames = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
1047
|
+
let target = handlerModule;
|
|
1048
|
+
for (let depth = 0; depth < 6 && target && typeof target === "object"; depth++) {
|
|
1049
|
+
const obj = target;
|
|
1050
|
+
const method = requestInfo.method.toUpperCase();
|
|
1051
|
+
if (httpMethodNames.includes(method) && typeof obj[method] === "function") {
|
|
1052
|
+
handler = obj[method];
|
|
1053
|
+
isV2 = true;
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
1056
|
+
if (obj.default && typeof obj.default === "object") {
|
|
1057
|
+
target = obj.default;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1035
1063
|
if (!handler) {
|
|
1036
1064
|
throw new Error(
|
|
1037
1065
|
`Could not resolve handler from ${handlerPath}. Module exports: [${Object.keys(handlerModule).join(", ")}]`
|
|
@@ -1055,6 +1083,9 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
|
|
|
1055
1083
|
);
|
|
1056
1084
|
}
|
|
1057
1085
|
const req = new RequestCtor(url, reqInit);
|
|
1086
|
+
const cookieKey = Object.keys(requestInfo.headers).find((k) => k.toLowerCase() === "cookie");
|
|
1087
|
+
g.__REPLAY_REQUEST_COOKIES__ = cookieKey ? requestInfo.headers[cookieKey] : "";
|
|
1088
|
+
g.__REPLAY_REQUEST_HEADERS__ = requestInfo.headers;
|
|
1058
1089
|
const context = { geo: {}, ip: "127.0.0.1", requestId: "replay", server: { region: "local" } };
|
|
1059
1090
|
const response = await handler(req, context);
|
|
1060
1091
|
if (response && typeof response === "object" && typeof response.status === "number" && typeof response.text === "function") {
|
|
@@ -1144,6 +1175,8 @@ async function createRequestRecording(blobUrlOrData, handlerPath, requestInfo) {
|
|
|
1144
1175
|
console.log(` [network-replay] All ${total} recorded network call(s) were consumed.`);
|
|
1145
1176
|
}
|
|
1146
1177
|
globalThis.__REPLAY_RECORDING_MODE__ = false;
|
|
1178
|
+
delete g.__REPLAY_REQUEST_COOKIES__;
|
|
1179
|
+
delete g.__REPLAY_REQUEST_HEADERS__;
|
|
1147
1180
|
networkHandle.restore();
|
|
1148
1181
|
envHandle.restore();
|
|
1149
1182
|
}
|
|
@@ -1466,6 +1499,16 @@ function formatStatus(request) {
|
|
|
1466
1499
|
}
|
|
1467
1500
|
return { status: "pending", requestId: request.id };
|
|
1468
1501
|
}
|
|
1502
|
+
async function forwardWebhook(url, payload) {
|
|
1503
|
+
try {
|
|
1504
|
+
await fetch(url, {
|
|
1505
|
+
method: "POST",
|
|
1506
|
+
headers: { "Content-Type": "application/json" },
|
|
1507
|
+
body: JSON.stringify(payload)
|
|
1508
|
+
});
|
|
1509
|
+
} catch {
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1469
1512
|
function createRecordingEndpoint(options) {
|
|
1470
1513
|
const { sql, recorderUrl, secret, webhookUrl } = options;
|
|
1471
1514
|
return async (req) => {
|
|
@@ -1489,6 +1532,44 @@ function createRecordingEndpoint(options) {
|
|
|
1489
1532
|
return jsonResponse(formatStatus(request), 200);
|
|
1490
1533
|
}
|
|
1491
1534
|
if (req.method === "POST") {
|
|
1535
|
+
const url = new URL(req.url);
|
|
1536
|
+
if (url.searchParams.has("_callback")) {
|
|
1537
|
+
const callbackRequestId = url.searchParams.get("requestId");
|
|
1538
|
+
if (!callbackRequestId) {
|
|
1539
|
+
return jsonResponse({ status: "error", error: "Missing requestId in callback" }, 400);
|
|
1540
|
+
}
|
|
1541
|
+
const callbackBody = await req.json();
|
|
1542
|
+
if (callbackBody.status === "recorded" && callbackBody.recordingId) {
|
|
1543
|
+
await backendRequestsUpdateStatus(
|
|
1544
|
+
sql,
|
|
1545
|
+
callbackRequestId,
|
|
1546
|
+
"recorded",
|
|
1547
|
+
callbackBody.recordingId
|
|
1548
|
+
);
|
|
1549
|
+
if (webhookUrl) {
|
|
1550
|
+
await forwardWebhook(webhookUrl, {
|
|
1551
|
+
status: "recorded",
|
|
1552
|
+
recordingId: callbackBody.recordingId
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
return jsonResponse(
|
|
1556
|
+
{ status: "recorded", recordingId: callbackBody.recordingId, requestId: callbackRequestId },
|
|
1557
|
+
200
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
if (callbackBody.status === "failed") {
|
|
1561
|
+
const errorMsg = callbackBody.error ?? "Recording failed";
|
|
1562
|
+
await backendRequestsUpdateStatus(sql, callbackRequestId, "failed", void 0, errorMsg);
|
|
1563
|
+
if (webhookUrl) {
|
|
1564
|
+
await forwardWebhook(webhookUrl, { status: "failed", error: errorMsg });
|
|
1565
|
+
}
|
|
1566
|
+
return jsonResponse(
|
|
1567
|
+
{ status: "error", requestId: callbackRequestId, error: errorMsg },
|
|
1568
|
+
200
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
return jsonResponse({ status: "error", error: "Invalid callback payload" }, 400);
|
|
1572
|
+
}
|
|
1492
1573
|
const body = await req.json();
|
|
1493
1574
|
const requestId = body.requestId;
|
|
1494
1575
|
if (!requestId) {
|
|
@@ -1507,10 +1588,11 @@ function createRecordingEndpoint(options) {
|
|
|
1507
1588
|
if (request.status === "queued" || request.status === "processing") {
|
|
1508
1589
|
return jsonResponse({ status: "pending", requestId }, 200);
|
|
1509
1590
|
}
|
|
1591
|
+
const selfCallbackUrl = `${url.origin}${url.pathname}?_callback=1&requestId=${encodeURIComponent(requestId)}`;
|
|
1510
1592
|
try {
|
|
1511
1593
|
const recordingId = await ensureRequestRecording(sql, requestId, {
|
|
1512
1594
|
recorderUrl,
|
|
1513
|
-
webhookUrl
|
|
1595
|
+
webhookUrl: selfCallbackUrl
|
|
1514
1596
|
});
|
|
1515
1597
|
if (recordingId) {
|
|
1516
1598
|
return jsonResponse({ status: "recorded", recordingId, requestId }, 200);
|