@replayio-app-building/netlify-recorder 0.2.0 → 0.4.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/README.md +76 -39
- package/dist/index.d.ts +10 -3
- package/dist/index.js +20 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,29 @@ There are two ways to use this package:
|
|
|
22
22
|
|
|
23
23
|
The Netlify Recorder app (`https://netlify-recorder-bm4wmw.netlify.app`) provides a hosted service that stores captured request data, manages a pool of recording containers, and creates Replay recordings on demand. Your app needs zero database or infrastructure setup.
|
|
24
24
|
|
|
25
|
-
### 1.
|
|
25
|
+
### 1. Set required environment variables
|
|
26
|
+
|
|
27
|
+
`finishRequest` needs to know which repository, branch, and commit your deployed code belongs to. Set these three environment variables on your Netlify site:
|
|
28
|
+
|
|
29
|
+
| Variable | Description | How to set |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `REPOSITORY_URL` | Your app's git repository URL (e.g. `https://github.com/org/repo.git`) | Set in your deploy script or Netlify site settings |
|
|
32
|
+
| `COMMIT_SHA` | The git commit hash of the deployed code | Set in your deploy script via `git rev-parse HEAD` |
|
|
33
|
+
| `BRANCH_NAME` | The git branch of the deployed code | Set in your deploy script via `git rev-parse --abbrev-ref HEAD` |
|
|
34
|
+
|
|
35
|
+
**These are required.** `finishRequest` will throw an error if any are missing. Your deploy script should resolve them from git and set them on the Netlify site before deploying. Example:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// In your deploy script:
|
|
39
|
+
const commitSha = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
40
|
+
const branchName = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
41
|
+
const repositoryUrl = execSync("git remote get-url origin", { encoding: "utf-8" }).trim()
|
|
42
|
+
.replace(/\/\/[^@]+@/, "//"); // strip embedded credentials
|
|
43
|
+
|
|
44
|
+
// Set these on your Netlify site via the Netlify API or CLI
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Wrap your Netlify function
|
|
26
48
|
|
|
27
49
|
Use `startRequest` / `finishRequest` with `remoteCallbacks()` to capture request data and send it to the service:
|
|
28
50
|
|
|
@@ -66,7 +88,7 @@ const handler: Handler = async (event) => {
|
|
|
66
88
|
export { handler };
|
|
67
89
|
```
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
The `remoteCallbacks(url)` helper sends the captured blob data — including the repository URL, branch, and commit — to the Netlify Recorder service's `/api/store-request` endpoint, which uploads it to blob storage and stores the request metadata. The response includes an `X-Replay-Request-Id` header with the request ID managed by the service.
|
|
70
92
|
|
|
71
93
|
### 2. Create recordings
|
|
72
94
|
|
|
@@ -74,40 +96,21 @@ When you want to turn a captured request into a Replay recording, POST to the se
|
|
|
74
96
|
|
|
75
97
|
```typescript
|
|
76
98
|
const response = await fetch(
|
|
77
|
-
`${RECORDER_URL}
|
|
78
|
-
{
|
|
79
|
-
method: "POST",
|
|
80
|
-
headers: { "Content-Type": "application/json" },
|
|
81
|
-
body: JSON.stringify({ requestId }),
|
|
82
|
-
}
|
|
83
|
-
);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
The service looks up the stored blob data, dispatches the work to a pool container, and creates the recording. You can check the recording status via the service's Requests page, or provide a webhook to be notified on completion.
|
|
87
|
-
|
|
88
|
-
### 3. (Optional) Get notified when recordings complete
|
|
89
|
-
|
|
90
|
-
If you want to be notified when a recording finishes, call the blob endpoint directly with a `webhookUrl`:
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
await fetch(
|
|
94
|
-
`${RECORDER_URL}/.netlify/functions/create-recording-from-blob-background`,
|
|
99
|
+
`${RECORDER_URL}/api/create-recording`,
|
|
95
100
|
{
|
|
96
101
|
method: "POST",
|
|
97
102
|
headers: { "Content-Type": "application/json" },
|
|
98
103
|
body: JSON.stringify({
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
commitSha: "a1b2c3d4...",
|
|
102
|
-
branchName: "main",
|
|
103
|
-
repositoryUrl: "https://github.com/your-org/your-repo",
|
|
104
|
-
webhookUrl: "https://your-app.netlify.app/.netlify/functions/on-recording-complete",
|
|
104
|
+
requestId,
|
|
105
|
+
webhookUrl: "https://your-app.netlify.app/api/on-recording-complete", // optional
|
|
105
106
|
}),
|
|
106
107
|
}
|
|
107
108
|
);
|
|
108
109
|
```
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
The service looks up the stored blob data, dispatches the work to a pool container, and creates the recording.
|
|
112
|
+
|
|
113
|
+
If `webhookUrl` is provided, the service will POST the result to that URL when the recording completes (or fails):
|
|
111
114
|
|
|
112
115
|
On success:
|
|
113
116
|
```json
|
|
@@ -119,13 +122,30 @@ On failure:
|
|
|
119
122
|
{ "status": "failed", "error": "Error message" }
|
|
120
123
|
```
|
|
121
124
|
|
|
125
|
+
### 3. Check recording status
|
|
126
|
+
|
|
127
|
+
You can poll the recording status at any time:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const res = await fetch(
|
|
131
|
+
`${RECORDER_URL}/api/get-request?requestId=${requestId}`
|
|
132
|
+
);
|
|
133
|
+
const { status, recordingId } = await res.json();
|
|
134
|
+
// status: "captured" | "processing" | "recorded" | "failed"
|
|
135
|
+
// recordingId: string | null
|
|
136
|
+
```
|
|
137
|
+
|
|
122
138
|
---
|
|
123
139
|
|
|
124
140
|
## Option B: Self-Hosted
|
|
125
141
|
|
|
126
142
|
If you need full control, you can manage your own blob storage, database, and recording containers. This requires more setup but gives you complete ownership of the data pipeline.
|
|
127
143
|
|
|
128
|
-
### 1.
|
|
144
|
+
### 1. Set required environment variables
|
|
145
|
+
|
|
146
|
+
Same as Option A — you must set `REPOSITORY_URL`, `COMMIT_SHA`, and `BRANCH_NAME` on your Netlify site. See the table in Option A, Step 1 above.
|
|
147
|
+
|
|
148
|
+
### 2. Wrap your Netlify function
|
|
129
149
|
|
|
130
150
|
Use `startRequest` / `finishRequest` with custom callbacks that write to your own storage and database:
|
|
131
151
|
|
|
@@ -156,10 +176,10 @@ const handler: Handler = async (event) => {
|
|
|
156
176
|
const { url } = await res.json();
|
|
157
177
|
return url;
|
|
158
178
|
},
|
|
159
|
-
storeRequestData: async ({ blobUrl, commitSha, branchName, handlerPath }) => {
|
|
179
|
+
storeRequestData: async ({ blobUrl, commitSha, branchName, repositoryUrl, handlerPath }) => {
|
|
160
180
|
const [row] = await sql`
|
|
161
|
-
INSERT INTO requests (blob_url, commit_sha, branch_name, handler_path, status)
|
|
162
|
-
VALUES (${blobUrl}, ${commitSha}, ${branchName}, ${handlerPath}, 'captured')
|
|
181
|
+
INSERT INTO requests (blob_url, commit_sha, branch_name, repository_url, handler_path, status)
|
|
182
|
+
VALUES (${blobUrl}, ${commitSha}, ${branchName}, ${repositoryUrl}, ${handlerPath}, 'captured')
|
|
163
183
|
RETURNING id
|
|
164
184
|
`;
|
|
165
185
|
return row.id;
|
|
@@ -180,13 +200,15 @@ const handler: Handler = async (event) => {
|
|
|
180
200
|
export { handler };
|
|
181
201
|
```
|
|
182
202
|
|
|
183
|
-
###
|
|
203
|
+
### 3. Create a requests database table
|
|
184
204
|
|
|
185
205
|
```sql
|
|
186
206
|
CREATE TABLE IF NOT EXISTS requests (
|
|
187
207
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
188
208
|
blob_url TEXT,
|
|
189
209
|
commit_sha TEXT,
|
|
210
|
+
branch_name TEXT,
|
|
211
|
+
repository_url TEXT,
|
|
190
212
|
handler_path TEXT,
|
|
191
213
|
recording_id TEXT,
|
|
192
214
|
status TEXT NOT NULL DEFAULT 'captured'
|
|
@@ -196,7 +218,7 @@ CREATE TABLE IF NOT EXISTS requests (
|
|
|
196
218
|
);
|
|
197
219
|
```
|
|
198
220
|
|
|
199
|
-
###
|
|
221
|
+
### 4. Create a background function to produce recordings
|
|
200
222
|
|
|
201
223
|
```typescript
|
|
202
224
|
import { ensureRequestRecording } from "@replayio-app-building/netlify-recorder";
|
|
@@ -240,7 +262,7 @@ const handler: Handler = async (event) => {
|
|
|
240
262
|
export { handler };
|
|
241
263
|
```
|
|
242
264
|
|
|
243
|
-
###
|
|
265
|
+
### 5. Create a container script
|
|
244
266
|
|
|
245
267
|
This script runs inside the recording container under `replay-node`:
|
|
246
268
|
|
|
@@ -294,11 +316,16 @@ Begins capturing a Netlify handler execution. Patches `globalThis.fetch` and `pr
|
|
|
294
316
|
|
|
295
317
|
Finalizes the request capture. Restores original `fetch` and `process.env`, serializes the captured data, uploads it via the callbacks, and returns the response with `X-Replay-Request-Id` header set.
|
|
296
318
|
|
|
319
|
+
**Requires** the following environment variables (or equivalent options overrides): `COMMIT_SHA`, `BRANCH_NAME`, `REPOSITORY_URL`. Throws if any are missing.
|
|
320
|
+
|
|
297
321
|
**Parameters:**
|
|
298
322
|
- `requestContext` — The context returned by `startRequest`
|
|
299
323
|
- `callbacks` — Either `remoteCallbacks(serviceUrl)` or custom `{ uploadBlob, storeRequestData }` callbacks
|
|
300
324
|
- `response` — The handler's response object (`{ statusCode, headers?, body? }`)
|
|
301
325
|
- `options.handlerPath` — Path to the handler file (used for recording metadata)
|
|
326
|
+
- `options.commitSha` — Override `COMMIT_SHA` env var
|
|
327
|
+
- `options.branchName` — Override `BRANCH_NAME` env var
|
|
328
|
+
- `options.repositoryUrl` — Override `REPOSITORY_URL` env var
|
|
302
329
|
|
|
303
330
|
### `remoteCallbacks(serviceUrl): FinishRequestCallbacks`
|
|
304
331
|
|
|
@@ -329,12 +356,22 @@ Called inside a container running under `replay-node`. Downloads the captured da
|
|
|
329
356
|
|
|
330
357
|
## Environment Variables
|
|
331
358
|
|
|
332
|
-
|
|
359
|
+
### Required for all setups (read by `finishRequest`)
|
|
360
|
+
|
|
361
|
+
These must be set on your Netlify site. Your deploy script should resolve them from git and push them to the Netlify environment before deploying. `finishRequest` will throw an error if any are missing.
|
|
362
|
+
|
|
363
|
+
| Variable | Description | How to resolve |
|
|
333
364
|
|---|---|---|
|
|
334
|
-
| `COMMIT_SHA` |
|
|
335
|
-
| `BRANCH_NAME` |
|
|
336
|
-
| `
|
|
337
|
-
|
|
365
|
+
| `COMMIT_SHA` | Git commit hash of the deployed code | `git rev-parse HEAD` |
|
|
366
|
+
| `BRANCH_NAME` | Git branch of the deployed code | `git rev-parse --abbrev-ref HEAD` |
|
|
367
|
+
| `REPOSITORY_URL` | Git repository URL (no embedded credentials) | `git remote get-url origin` (strip tokens) |
|
|
368
|
+
|
|
369
|
+
### Required for self-hosted recording (Option B)
|
|
370
|
+
|
|
371
|
+
| Variable | Description |
|
|
372
|
+
|---|---|
|
|
373
|
+
| `RECORD_REPLAY_API_KEY` | Replay API key for uploading recordings |
|
|
374
|
+
| `APP_REPOSITORY_URL` | Git repository URL for container cloning |
|
|
338
375
|
|
|
339
376
|
## How It Works
|
|
340
377
|
|
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,7 @@ interface FinishRequestCallbacks {
|
|
|
46
46
|
blobUrl: string;
|
|
47
47
|
commitSha: string;
|
|
48
48
|
branchName: string;
|
|
49
|
+
repositoryUrl: string;
|
|
49
50
|
handlerPath: string;
|
|
50
51
|
}) => Promise<string>;
|
|
51
52
|
}
|
|
@@ -109,6 +110,12 @@ interface HandlerResponse {
|
|
|
109
110
|
}
|
|
110
111
|
interface FinishRequestOptions {
|
|
111
112
|
handlerPath?: string;
|
|
113
|
+
/** Override COMMIT_SHA env var. */
|
|
114
|
+
commitSha?: string;
|
|
115
|
+
/** Override BRANCH_NAME env var. */
|
|
116
|
+
branchName?: string;
|
|
117
|
+
/** Override REPOSITORY_URL env var. */
|
|
118
|
+
repositoryUrl?: string;
|
|
112
119
|
}
|
|
113
120
|
/**
|
|
114
121
|
* Called at the end of the handler execution.
|
|
@@ -124,8 +131,8 @@ declare function finishRequest(requestContext: RequestContext, callbacks: Finish
|
|
|
124
131
|
* to set up its own blob storage or database tables.
|
|
125
132
|
*
|
|
126
133
|
* The callbacks upload blob data and store request metadata via the
|
|
127
|
-
* service's
|
|
128
|
-
* and database insertion.
|
|
134
|
+
* service's `/api/store-request` endpoint, which handles UploadThing
|
|
135
|
+
* storage and database insertion.
|
|
129
136
|
*
|
|
130
137
|
* @param serviceUrl - Base URL of the Netlify Recorder service
|
|
131
138
|
* (e.g. "https://netlify-recorder-bm4wmw.netlify.app")
|
|
@@ -220,4 +227,4 @@ interface SpawnRecordingContainerOptions {
|
|
|
220
227
|
*/
|
|
221
228
|
declare function spawnRecordingContainer(options: SpawnRecordingContainerOptions): Promise<string>;
|
|
222
229
|
|
|
223
|
-
export { type BlobData, type CapturedData, type ContainerInfraConfig, type EnsureRecordingOptions, type EnvRead, type FinishRequestCallbacks, type NetworkCall, type RequestContext, type RequestInfo, type SpawnRecordingContainerOptions, createRequestRecording, ensureRequestRecording, finishRequest, readInfraConfigFromEnv, redactBlobData, remoteCallbacks, spawnRecordingContainer, startRequest };
|
|
230
|
+
export { type BlobData, type CapturedData, type ContainerInfraConfig, type EnsureRecordingOptions, type EnvRead, type FinishRequestCallbacks, type FinishRequestOptions, type NetworkCall, type RequestContext, type RequestInfo, type SpawnRecordingContainerOptions, createRequestRecording, ensureRequestRecording, finishRequest, readInfraConfigFromEnv, redactBlobData, remoteCallbacks, spawnRecordingContainer, startRequest };
|
package/dist/index.js
CHANGED
|
@@ -168,6 +168,7 @@ var ENV_ALLOW_LIST = /* @__PURE__ */ new Set([
|
|
|
168
168
|
"LOGNAME",
|
|
169
169
|
// Deploy metadata (non-secret identifiers)
|
|
170
170
|
"COMMIT_SHA",
|
|
171
|
+
"BRANCH_NAME",
|
|
171
172
|
// Common non-secret Netlify build vars
|
|
172
173
|
"NETLIFY",
|
|
173
174
|
"NETLIFY_DEV",
|
|
@@ -274,8 +275,21 @@ async function finishRequest(requestContext, callbacks, response, options) {
|
|
|
274
275
|
if (globalThis.__REPLAY_RECORDING_MODE__ === true) {
|
|
275
276
|
return response;
|
|
276
277
|
}
|
|
277
|
-
const
|
|
278
|
-
const
|
|
278
|
+
const rawCommitSha = options?.commitSha ?? process.env.COMMIT_SHA;
|
|
279
|
+
const rawBranchName = options?.branchName ?? process.env.BRANCH_NAME;
|
|
280
|
+
const rawRepositoryUrl = options?.repositoryUrl ?? process.env.REPOSITORY_URL;
|
|
281
|
+
const missing = [];
|
|
282
|
+
if (!rawCommitSha) missing.push("COMMIT_SHA");
|
|
283
|
+
if (!rawBranchName) missing.push("BRANCH_NAME");
|
|
284
|
+
if (!rawRepositoryUrl) missing.push("REPOSITORY_URL");
|
|
285
|
+
if (missing.length > 0) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`netlify-recorder: missing required configuration: ${missing.join(", ")}. Set these as environment variables on your Netlify site, or pass them via the options parameter. See the package README for setup instructions.`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const commitSha = rawCommitSha;
|
|
291
|
+
const branchName = rawBranchName;
|
|
292
|
+
const repositoryUrl = rawRepositoryUrl;
|
|
279
293
|
const rawBlobData = {
|
|
280
294
|
requestInfo: requestContext.requestInfo,
|
|
281
295
|
capturedData: requestContext.capturedData,
|
|
@@ -290,6 +304,7 @@ async function finishRequest(requestContext, callbacks, response, options) {
|
|
|
290
304
|
blobUrl,
|
|
291
305
|
commitSha,
|
|
292
306
|
branchName,
|
|
307
|
+
repositoryUrl,
|
|
293
308
|
handlerPath: options?.handlerPath ?? "unknown"
|
|
294
309
|
});
|
|
295
310
|
return {
|
|
@@ -319,7 +334,7 @@ function remoteCallbacks(serviceUrl) {
|
|
|
319
334
|
);
|
|
320
335
|
}
|
|
321
336
|
const res = await fetch(
|
|
322
|
-
`${base}
|
|
337
|
+
`${base}/api/store-request`,
|
|
323
338
|
{
|
|
324
339
|
method: "POST",
|
|
325
340
|
headers: { "Content-Type": "application/json" },
|
|
@@ -327,7 +342,8 @@ function remoteCallbacks(serviceUrl) {
|
|
|
327
342
|
blobData,
|
|
328
343
|
handlerPath: metadata.handlerPath,
|
|
329
344
|
commitSha: metadata.commitSha,
|
|
330
|
-
branchName: metadata.branchName
|
|
345
|
+
branchName: metadata.branchName,
|
|
346
|
+
repositoryUrl: metadata.repositoryUrl
|
|
331
347
|
})
|
|
332
348
|
}
|
|
333
349
|
);
|