@replayio-app-building/netlify-recorder 0.15.3 → 0.15.4

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/README.md CHANGED
@@ -155,6 +155,79 @@ const { status, recordingId } = await res.json();
155
155
  // recordingId: string | null
156
156
  ```
157
157
 
158
+ ### 4. Access control with secrets
159
+
160
+ You can restrict access to captured requests by setting a `secret` string. When a secret is set, the request is only accessible via API calls that provide the same secret value. This lets each app isolate its requests from other apps sharing the same Netlify Recorder service.
161
+
162
+ #### Setting a secret
163
+
164
+ Pass `secret` in the options when wrapping your handler:
165
+
166
+ ```typescript
167
+ export default createRecordingRequestHandler(
168
+ async (req) => {
169
+ const result = await myBusinessLogic();
170
+ return { statusCode: 200, body: JSON.stringify(result) };
171
+ },
172
+ {
173
+ callbacks: remoteCallbacks(RECORDER_URL),
174
+ handlerPath: "netlify/functions/my-handler",
175
+ secret: process.env.NETLIFY_RECORDER_SECRET,
176
+ }
177
+ );
178
+ ```
179
+
180
+ #### Listing requests by secret
181
+
182
+ Use the `list-by-secret` endpoint to retrieve all requests associated with a secret, with optional filtering by status, handler path, and time range:
183
+
184
+ ```typescript
185
+ const res = await fetch(
186
+ `${RECORDER_URL}/api/list-by-secret?secret=${secret}&status=recorded&handlerPath=netlify/functions/my-handler&after=2025-01-01T00:00:00Z&before=2025-12-31T23:59:59Z`
187
+ );
188
+ const { rows, total, page, limit } = await res.json();
189
+ ```
190
+
191
+ Query parameters:
192
+
193
+ | Parameter | Description |
194
+ |---|---|
195
+ | `secret` | **(required)** The secret string used when creating the requests |
196
+ | `status` | Filter by status: `captured`, `queued`, `processing`, `recorded`, `failed` |
197
+ | `handlerPath` | Filter by handler path (exact match) |
198
+ | `after` | Only include requests created at or after this ISO timestamp |
199
+ | `before` | Only include requests created at or before this ISO timestamp |
200
+ | `page` | Page number (default 1) |
201
+ | `limit` | Page size (default 20, max 100) |
202
+
203
+ #### Checking request status with a secret
204
+
205
+ When a request was created with a secret, you must include the secret when polling its status:
206
+
207
+ ```typescript
208
+ const res = await fetch(
209
+ `${RECORDER_URL}/api/get-request?requestId=${requestId}&secret=${secret}`
210
+ );
211
+ ```
212
+
213
+ Requests created without a secret remain accessible without one (backward compatible).
214
+
215
+ #### Managing your secret
216
+
217
+ Store your secret as a `NETLIFY_RECORDER_SECRET` environment variable. To keep it secure:
218
+
219
+ 1. **Netlify site environment:** Add `NETLIFY_RECORDER_SECRET` in your Netlify site's environment variables (Site settings → Environment variables). This makes it available to all deployed functions.
220
+
221
+ 2. **Branch-level secret** (for CI/development): If you're using the Netlify Recorder agent infrastructure, store the secret as a branch secret so deploy scripts and background agents can access it:
222
+
223
+ ```bash
224
+ set-branch-secret NETLIFY_RECORDER_SECRET "your-secret-value"
225
+ ```
226
+
227
+ 3. **Local development:** Add `NETLIFY_RECORDER_SECRET` to your `.env` file (make sure `.env` is in `.gitignore`).
228
+
229
+ Use a strong random string (e.g. `openssl rand -base64 32`) and rotate it if compromised. All requests created under the old secret remain accessible only with the old secret value — there is no migration mechanism, so plan rotations during low-traffic windows.
230
+
158
231
  ---
159
232
 
160
233
  ## Option B: Self-Hosted
@@ -406,6 +479,7 @@ When `context.waitUntil` is not available (v1 handlers or missing context), the
406
479
  - `options.commitSha` — Override `COMMIT_SHA` env var
407
480
  - `options.branchName` — Override `BRANCH_NAME` env var
408
481
  - `options.repositoryUrl` — Override `REPLAY_REPOSITORY_URL` env var
482
+ - `options.secret` — Optional secret string. When set, the stored request is only accessible via API calls that provide the same secret value.
409
483
 
410
484
  **Returns:** A wrapped handler function with the same signature.
411
485
 
@@ -443,6 +517,7 @@ Logs a `console.warn` when the total duration exceeds 2 seconds or when individu
443
517
  - `options.branchName` — Override `BRANCH_NAME` env var
444
518
  - `options.repositoryUrl` — Override `REPLAY_REPOSITORY_URL` env var
445
519
  - `options.requestId` — Pre-generated request ID (used by `createRecordingRequestHandler` in the `waitUntil` flow)
520
+ - `options.secret` — Optional secret string. When set, the stored request is only accessible via API calls that provide the same secret value.
446
521
 
447
522
  ### `remoteCallbacks(serviceUrl): FinishRequestCallbacks`
448
523
 
package/dist/index.d.ts CHANGED
@@ -111,6 +111,8 @@ interface FinishRequestCallbacks {
111
111
  handlerPath: string;
112
112
  /** Pre-generated request ID. Use as the row ID when provided. */
113
113
  requestId?: string;
114
+ /** Optional secret that restricts access to this request. */
115
+ secret?: string;
114
116
  }) => Promise<string>;
115
117
  }
116
118
  /**
@@ -217,6 +219,11 @@ interface FinishRequestOptions {
217
219
  * the response is returned before `finishRequest` runs.
218
220
  */
219
221
  requestId?: string;
222
+ /**
223
+ * Optional secret string. When set, the stored request is only
224
+ * accessible via API calls that provide the same secret value.
225
+ */
226
+ secret?: string;
220
227
  }
221
228
  /**
222
229
  * Called at the end of the handler execution.
package/dist/index.js CHANGED
@@ -458,7 +458,8 @@ async function finishRequest(requestContext, callbacks, response, options) {
458
458
  branchName,
459
459
  repositoryUrl,
460
460
  handlerPath,
461
- requestId: options?.requestId
461
+ requestId: options?.requestId,
462
+ secret: options?.secret
462
463
  });
463
464
  const storeDuration = Date.now() - storeStart;
464
465
  if (storeDuration > SLOW_STEP_THRESHOLD_MS) {
@@ -564,7 +565,8 @@ function remoteCallbacks(serviceUrl) {
564
565
  commitSha: metadata.commitSha,
565
566
  branchName: metadata.branchName,
566
567
  repositoryUrl: metadata.repositoryUrl,
567
- requestId: metadata.requestId
568
+ requestId: metadata.requestId,
569
+ secret: metadata.secret
568
570
  })
569
571
  }
570
572
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio-app-building/netlify-recorder",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "description": "Capture and replay Netlify function executions as Replay recordings",
5
5
  "type": "module",
6
6
  "exports": {