@replayio-app-building/netlify-recorder 0.40.0 → 0.41.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 CHANGED
@@ -344,33 +344,38 @@ interface RecordingEndpointResponse {
344
344
  error?: string;
345
345
  }
346
346
  /**
347
- * Creates a Netlify Function handler that other services can call to trigger
348
- * recording creation for a backend request (or retrieve its current status).
347
+ * Payload sent by the recorder service when a recording completes or fails.
348
+ * Used for both the PUT callback to this endpoint and the downstream
349
+ * notification to the user-configured `webhookUrl`.
350
+ */
351
+ interface RecordingWebhookPayload {
352
+ requestId: string;
353
+ status: "recorded" | "failed";
354
+ recordingId?: string;
355
+ error?: string;
356
+ }
357
+ /**
358
+ * Creates a handler that other services can call to trigger recording creation
359
+ * for a backend request or retrieve its current status.
360
+ *
361
+ * ## Endpoints
349
362
  *
350
- * **POST** with `{ "requestId": "<uuid>" }` triggers recording creation if
351
- * needed and returns the current status. Idempotent: re-posting the same
352
- * request ID will not re-queue an already-queued or recorded request.
363
+ * **GET** `?requestId=<uuid>`returns the current recording status.
353
364
  *
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`.
365
+ * **POST** `{ "requestId": "<uuid>" }` triggers recording creation if needed.
366
+ * Idempotent: re-posting the same request ID will not re-queue.
357
367
  *
358
- * **GET** with `?requestId=<uuid>` returns the current recording status
359
- * without triggering any recording.
368
+ * **PUT** `RecordingWebhookPayload`webhook callback from the recorder
369
+ * service when a recording completes or fails. Body must include
370
+ * `requestId`, `status` (`"recorded"` | `"failed"`), and either
371
+ * `recordingId` or `error`.
360
372
  *
361
373
  * When `secret` is provided, every request must include an
362
374
  * `Authorization: Bearer <secret>` header or receive a 401 response.
363
- *
364
- * Response body shape (`RecordingEndpointResponse`):
365
- * - `{ status: "recorded", recordingId, requestId }` — recording exists
366
- * - `{ status: "pending", requestId }` — recording is queued or processing
367
- * - `{ status: "queued", requestId }` — recording was just queued by this call
368
- * - `{ status: "not_found", error }` — request ID not in backend_requests
369
- * - `{ status: "error", requestId?, error }` — recording failed or server error
370
375
  */
371
376
  declare function createRecordingEndpoint(options: CreateRecordingEndpointOptions): (req: Request) => Promise<Response>;
372
377
 
373
378
  declare function runInRequestContext<T>(requestId: string | null, fn: () => Promise<T>): Promise<T>;
374
379
  declare function getCurrentRequestId(): string | null;
375
380
 
376
- export { type BackendRequest, type BlobData, type CapturedData, type CreateRecordingEndpointOptions, type CreateRecordingRequestHandlerOptions, type EnsureRequestRecordingOptions, type EnvRead, type FinishRequestCallbacks, type FinishRequestOptions, type HandlerResponse$1 as HandlerResponse, type NetlifyEvent, type NetlifyV2Request, type NetworkCall, type RecordingEndpointResponse, type RecordingResult, type RequestContext, type RequestInfo, backendRequestsEnsureTable, backendRequestsGet, backendRequestsGetBlobUrl, backendRequestsInsert, backendRequestsList, backendRequestsUpdateStatus, createRecordingEndpoint, createRecordingRequestHandler, createRequestRecording, databaseAuditDumpLogTable, databaseAuditEnsureLogTable, databaseAuditMonitorTable, databaseCallbacks, ensureRequestRecording, finishRequest, getCurrentRequestId, redactBlobData, remoteCallbacks, runInRequestContext, startRequest };
381
+ export { type BackendRequest, type BlobData, type CapturedData, type CreateRecordingEndpointOptions, type CreateRecordingRequestHandlerOptions, type EnsureRequestRecordingOptions, type EnvRead, type FinishRequestCallbacks, type FinishRequestOptions, type HandlerResponse$1 as HandlerResponse, type NetlifyEvent, type NetlifyV2Request, type NetworkCall, type RecordingEndpointResponse, type RecordingResult, type RecordingWebhookPayload, type RequestContext, type RequestInfo, backendRequestsEnsureTable, backendRequestsGet, backendRequestsGetBlobUrl, backendRequestsInsert, backendRequestsList, backendRequestsUpdateStatus, createRecordingEndpoint, createRecordingRequestHandler, createRequestRecording, databaseAuditDumpLogTable, databaseAuditEnsureLogTable, databaseAuditMonitorTable, databaseCallbacks, ensureRequestRecording, finishRequest, getCurrentRequestId, redactBlobData, remoteCallbacks, runInRequestContext, startRequest };
package/dist/index.js CHANGED
@@ -1517,6 +1517,28 @@ async function forwardWebhook(url, payload) {
1517
1517
  }
1518
1518
  function createRecordingEndpoint(options) {
1519
1519
  const { sql, recorderUrl, secret, webhookUrl } = options;
1520
+ async function handleCallback(body) {
1521
+ const { requestId, status, recordingId, error } = body;
1522
+ if (!requestId) {
1523
+ return jsonResponse({ status: "error", error: "Missing requestId in callback body" }, 400);
1524
+ }
1525
+ if (status === "recorded" && recordingId) {
1526
+ await backendRequestsUpdateStatus(sql, requestId, "recorded", recordingId);
1527
+ if (webhookUrl) {
1528
+ await forwardWebhook(webhookUrl, { requestId, status: "recorded", recordingId });
1529
+ }
1530
+ return jsonResponse({ status: "recorded", recordingId, requestId }, 200);
1531
+ }
1532
+ if (status === "failed") {
1533
+ const errorMsg = error ?? "Recording failed";
1534
+ await backendRequestsUpdateStatus(sql, requestId, "failed", void 0, errorMsg);
1535
+ if (webhookUrl) {
1536
+ await forwardWebhook(webhookUrl, { requestId, status: "failed", error: errorMsg });
1537
+ }
1538
+ return jsonResponse({ status: "error", requestId, error: errorMsg }, 200);
1539
+ }
1540
+ return jsonResponse({ status: "error", error: "Invalid callback payload \u2014 expected status 'recorded' or 'failed'" }, 400);
1541
+ }
1520
1542
  return async (req) => {
1521
1543
  if (secret) {
1522
1544
  const auth = req.headers.get("authorization");
@@ -1537,46 +1559,15 @@ function createRecordingEndpoint(options) {
1537
1559
  }
1538
1560
  return jsonResponse(formatStatus(request), 200);
1539
1561
  }
1562
+ if (req.method === "PUT") {
1563
+ const body = await req.json();
1564
+ return handleCallback(body);
1565
+ }
1540
1566
  if (req.method === "POST") {
1541
- const url = new URL(req.url);
1542
- if (url.searchParams.has("_callback")) {
1543
- const callbackRequestId = url.searchParams.get("requestId");
1544
- if (!callbackRequestId) {
1545
- return jsonResponse({ status: "error", error: "Missing requestId in callback" }, 400);
1546
- }
1547
- const callbackBody = await req.json();
1548
- if (callbackBody.status === "recorded" && callbackBody.recordingId) {
1549
- await backendRequestsUpdateStatus(
1550
- sql,
1551
- callbackRequestId,
1552
- "recorded",
1553
- callbackBody.recordingId
1554
- );
1555
- if (webhookUrl) {
1556
- await forwardWebhook(webhookUrl, {
1557
- status: "recorded",
1558
- recordingId: callbackBody.recordingId
1559
- });
1560
- }
1561
- return jsonResponse(
1562
- { status: "recorded", recordingId: callbackBody.recordingId, requestId: callbackRequestId },
1563
- 200
1564
- );
1565
- }
1566
- if (callbackBody.status === "failed") {
1567
- const errorMsg = callbackBody.error ?? "Recording failed";
1568
- await backendRequestsUpdateStatus(sql, callbackRequestId, "failed", void 0, errorMsg);
1569
- if (webhookUrl) {
1570
- await forwardWebhook(webhookUrl, { status: "failed", error: errorMsg });
1571
- }
1572
- return jsonResponse(
1573
- { status: "error", requestId: callbackRequestId, error: errorMsg },
1574
- 200
1575
- );
1576
- }
1577
- return jsonResponse({ status: "error", error: "Invalid callback payload" }, 400);
1578
- }
1579
1567
  const body = await req.json();
1568
+ if (body.status) {
1569
+ return handleCallback(body);
1570
+ }
1580
1571
  const requestId = body.requestId;
1581
1572
  if (!requestId) {
1582
1573
  return jsonResponse({ status: "error", error: "Missing requestId in request body" }, 400);
@@ -1594,11 +1585,12 @@ function createRecordingEndpoint(options) {
1594
1585
  if (request.status === "queued" || request.status === "processing") {
1595
1586
  return jsonResponse({ status: "pending", requestId }, 200);
1596
1587
  }
1597
- const selfCallbackUrl = `${url.origin}${url.pathname}?_callback=1&requestId=${encodeURIComponent(requestId)}`;
1588
+ const url = new URL(req.url);
1589
+ const callbackUrl = `${url.origin}${url.pathname}`;
1598
1590
  try {
1599
1591
  const recordingId = await ensureRequestRecording(sql, requestId, {
1600
1592
  recorderUrl,
1601
- webhookUrl: selfCallbackUrl
1593
+ webhookUrl: callbackUrl
1602
1594
  });
1603
1595
  if (recordingId) {
1604
1596
  return jsonResponse({ status: "recorded", recordingId, requestId }, 200);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio-app-building/netlify-recorder",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Capture and replay Netlify function executions as Replay recordings",
5
5
  "type": "module",
6
6
  "exports": {