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

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
@@ -112,7 +112,7 @@ export default createRecordingRequestHandler(
112
112
 
113
113
  ### 2. Create recordings
114
114
 
115
- When you want to turn a captured request into a Replay recording, POST to the service's `create-recording` endpoint with the request ID:
115
+ When you want to turn a captured request into a Replay recording, POST to the service's `create-recording` endpoint with the request ID. If the request was created with a secret, you must include it:
116
116
 
117
117
  ```typescript
118
118
  const response = await fetch(
@@ -122,6 +122,7 @@ const response = await fetch(
122
122
  headers: { "Content-Type": "application/json" },
123
123
  body: JSON.stringify({
124
124
  requestId,
125
+ secret,
125
126
  webhookUrl: "https://your-app.netlify.app/api/on-recording-complete", // optional
126
127
  }),
127
128
  }
@@ -144,17 +145,95 @@ On failure:
144
145
 
145
146
  ### 3. Check recording status
146
147
 
147
- You can poll the recording status at any time:
148
+ You can poll the recording status at any time. If the request was created with a secret, you must include it:
148
149
 
149
150
  ```typescript
150
151
  const res = await fetch(
151
- `${RECORDER_URL}/api/get-request?requestId=${requestId}`
152
+ `${RECORDER_URL}/api/get-request?requestId=${requestId}&secret=${secret}`
152
153
  );
153
154
  const { status, recordingId } = await res.json();
154
155
  // status: "captured" | "processing" | "recorded" | "failed"
155
156
  // recordingId: string | null
156
157
  ```
157
158
 
159
+ Requests created with a secret return 403 if the secret is missing or incorrect.
160
+
161
+ ### 4. Access control with secrets
162
+
163
+ 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.
164
+
165
+ #### Setting a secret
166
+
167
+ Pass `secret` in the options when wrapping your handler:
168
+
169
+ ```typescript
170
+ export default createRecordingRequestHandler(
171
+ async (req) => {
172
+ const result = await myBusinessLogic();
173
+ return { statusCode: 200, body: JSON.stringify(result) };
174
+ },
175
+ {
176
+ callbacks: remoteCallbacks(RECORDER_URL),
177
+ handlerPath: "netlify/functions/my-handler",
178
+ secret: process.env.NETLIFY_RECORDER_SECRET,
179
+ }
180
+ );
181
+ ```
182
+
183
+ #### Listing requests by secret
184
+
185
+ Use the `requests` endpoint with a `secret` parameter to retrieve all requests associated with your secret, with optional filtering by status, handler path, and time range:
186
+
187
+ ```typescript
188
+ const res = await fetch(
189
+ `${RECORDER_URL}/api/requests?secret=${secret}&status=recorded&handlerPath=netlify/functions/my-handler&after=2025-01-01T00:00:00Z&before=2025-12-31T23:59:59Z`
190
+ );
191
+ const { rows, total, page, limit } = await res.json();
192
+ ```
193
+
194
+ Query parameters:
195
+
196
+ | Parameter | Description |
197
+ |---|---|
198
+ | `secret` | **(required)** The secret string used when creating the requests |
199
+ | `id` | Search by request ID prefix |
200
+ | `status` | Filter by status: `captured`, `queued`, `processing`, `recorded`, `failed`, `all` |
201
+ | `handlerPath` | Filter by handler path (exact match) |
202
+ | `after` | Only include requests created at or after this ISO timestamp |
203
+ | `before` | Only include requests created at or before this ISO timestamp |
204
+ | `page` | Page number (default 1) |
205
+ | `limit` | Page size (default 20, max 100) |
206
+
207
+ Without a `secret` parameter, only requests created without a secret are returned.
208
+
209
+ #### Checking request status with a secret
210
+
211
+ When a request was created with a secret, you must include the secret when polling its status:
212
+
213
+ ```typescript
214
+ const res = await fetch(
215
+ `${RECORDER_URL}/api/get-request?requestId=${requestId}&secret=${secret}`
216
+ );
217
+ ```
218
+
219
+ Requests created without a secret remain accessible without one (backward compatible).
220
+
221
+ #### Managing your secret
222
+
223
+ Store your secret as a `NETLIFY_RECORDER_SECRET` environment variable. To keep it secure:
224
+
225
+ 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.
226
+
227
+ 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:
228
+
229
+ ```bash
230
+ set-branch-secret NETLIFY_RECORDER_SECRET "your-secret-value"
231
+ ```
232
+
233
+ 3. **Local development:** Add `NETLIFY_RECORDER_SECRET` to your `.env` file (make sure `.env` is in `.gitignore`).
234
+
235
+ 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.
236
+
158
237
  ---
159
238
 
160
239
  ## Option B: Self-Hosted
@@ -406,6 +485,7 @@ When `context.waitUntil` is not available (v1 handlers or missing context), the
406
485
  - `options.commitSha` — Override `COMMIT_SHA` env var
407
486
  - `options.branchName` — Override `BRANCH_NAME` env var
408
487
  - `options.repositoryUrl` — Override `REPLAY_REPOSITORY_URL` env var
488
+ - `options.secret` — Optional secret string. When set, the stored request is only accessible via API calls that provide the same secret value.
409
489
 
410
490
  **Returns:** A wrapped handler function with the same signature.
411
491
 
@@ -443,6 +523,7 @@ Logs a `console.warn` when the total duration exceeds 2 seconds or when individu
443
523
  - `options.branchName` — Override `BRANCH_NAME` env var
444
524
  - `options.repositoryUrl` — Override `REPLAY_REPOSITORY_URL` env var
445
525
  - `options.requestId` — Pre-generated request ID (used by `createRecordingRequestHandler` in the `waitUntil` flow)
526
+ - `options.secret` — Optional secret string. When set, the stored request is only accessible via API calls that provide the same secret value.
446
527
 
447
528
  ### `remoteCallbacks(serviceUrl): FinishRequestCallbacks`
448
529
 
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.5",
4
4
  "description": "Capture and replay Netlify function executions as Replay recordings",
5
5
  "type": "module",
6
6
  "exports": {