midline-agent 0.3.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 +89 -4
- package/browser/package.json +8 -0
- package/dist/browser/client.d.ts +59 -0
- package/dist/browser/client.js +608 -0
- package/dist/browser/index.d.ts +34 -0
- package/dist/browser/index.js +65 -0
- package/dist/browser/instrument.d.ts +39 -0
- package/dist/browser/instrument.js +217 -0
- package/dist/browser/transport.d.ts +43 -0
- package/dist/browser/transport.js +168 -0
- package/dist/browser/types.d.ts +94 -0
- package/dist/browser/types.js +2 -0
- package/dist/browser/version.d.ts +2 -0
- package/dist/browser/version.js +5 -0
- package/dist/browser/vitals.d.ts +16 -0
- package/dist/browser/vitals.js +135 -0
- package/dist/cli.js +0 -0
- package/dist/esm/browser/client.js +601 -0
- package/dist/esm/browser/index.js +52 -0
- package/dist/esm/browser/instrument.js +210 -0
- package/dist/esm/browser/transport.js +164 -0
- package/dist/esm/browser/types.js +1 -0
- package/dist/esm/browser/version.js +2 -0
- package/dist/esm/browser/vitals.js +132 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/redact.js +224 -0
- package/dist/esm/types.js +1 -0
- package/dist/redact.d.ts +3 -0
- package/dist/redact.js +12 -6
- package/package.json +27 -4
- package/scripts/mark-esm.js +6 -0
- package/src/browser/client.ts +686 -0
- package/src/browser/index.ts +74 -0
- package/src/browser/instrument.ts +275 -0
- package/src/browser/transport.ts +184 -0
- package/src/browser/types.ts +105 -0
- package/src/browser/version.ts +2 -0
- package/src/browser/vitals.ts +149 -0
- package/src/redact.ts +12 -6
- package/test/browser.test.js +328 -0
- package/tsconfig.esm.json +14 -0
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# midline-agent
|
|
2
2
|
|
|
3
3
|
**The Node.js SDK for Midline** — request and error monitoring with security and threat detection.
|
|
4
|
+
It also ships **`midline-agent/browser`** for web apps: see [Browser apps](#browser-apps).
|
|
4
5
|
|
|
5
6
|
Midline itself is not tied to Node. Every event lands on the same stream through a plain JSON
|
|
6
7
|
endpoint, so a Django, Rails, Laravel, Spring, Go or .NET service reports exactly what an Express
|
|
@@ -102,7 +103,8 @@ bootstrap();
|
|
|
102
103
|
```
|
|
103
104
|
|
|
104
105
|
Nest handles exceptions in its own filters, so they rarely reach Express error middleware. The
|
|
105
|
-
request middleware still records every 4xx/5xx response
|
|
106
|
+
request middleware still records every 4xx/5xx response, and Midline groups the 5xx responses into
|
|
107
|
+
Issues by method, route and status.
|
|
106
108
|
|
|
107
109
|
### Plain Node `http`
|
|
108
110
|
|
|
@@ -283,8 +285,9 @@ TARGET_API_URL=http://localhost:4000
|
|
|
283
285
|
## Console output
|
|
284
286
|
|
|
285
287
|
Set `captureConsole: true` (or `MIDLINE_CAPTURE_CONSOLE=true`) and whatever the process prints
|
|
286
|
-
|
|
287
|
-
covers `console.log`, Nest's logger, pino, winston: anything written to stdout or stderr.
|
|
288
|
+
is sent as `console` events, one per line, and shows up on the dashboard's **Terminal** page. That
|
|
289
|
+
covers `console.log`, Nest's logger, pino, winston: anything written to stdout or stderr. The Logs page
|
|
290
|
+
stays request traffic.
|
|
288
291
|
|
|
289
292
|
```ts
|
|
290
293
|
MidlineAgent.init({ apiKey: process.env.MIDLINE_API_KEY, captureConsole: true });
|
|
@@ -299,7 +302,7 @@ const app = await NestFactory.create(AppModule); // startup lines are captured f
|
|
|
299
302
|
medium, and everything else is low.
|
|
300
303
|
- Up to 100 lines a second are sent, with room for a 1,000-line burst such as Nest mapping its routes
|
|
301
304
|
at startup. Lines past that still print but aren't sent.
|
|
302
|
-
- Printed lines don't count towards request totals, error rates or
|
|
305
|
+
- Printed lines don't count towards request totals, error rates, latency or Issues.
|
|
303
306
|
- It needs a Midline server that knows the `console` event type. An older server refuses the first
|
|
304
307
|
line; the agent then turns console capture off, says so once, and keeps sending requests and errors.
|
|
305
308
|
- The agent's own diagnostics are never captured.
|
|
@@ -372,6 +375,88 @@ app.get("/x", (req, res) => { logger.info({ requestId: getRequestContext(req)?.r
|
|
|
372
375
|
|
|
373
376
|
---
|
|
374
377
|
|
|
378
|
+
## Browser apps
|
|
379
|
+
|
|
380
|
+
`midline-agent/browser` monitors a web app from the page: uncaught errors and unhandled rejections,
|
|
381
|
+
failed `fetch` and `XMLHttpRequest` calls, Web Vitals (LCP, INP, CLS, FCP, TTFB) and, if you ask,
|
|
382
|
+
console output. It has no dependencies, no Node code, and works under React, Vue, Angular, Svelte,
|
|
383
|
+
Next.js or no framework at all, because it instruments the page rather than a framework.
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
import * as Midline from "midline-agent/browser";
|
|
387
|
+
|
|
388
|
+
Midline.init({
|
|
389
|
+
apiKey: import.meta.env.VITE_MIDLINE_BROWSER_KEY, // pk_… browser key
|
|
390
|
+
service: "checkout-web",
|
|
391
|
+
environment: import.meta.env.MODE,
|
|
392
|
+
release: import.meta.env.VITE_RELEASE,
|
|
393
|
+
});
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Use a browser key, never a server key.** Anything in a bundle can be read by whoever loads the page,
|
|
397
|
+
so browser keys (`pk_…`) are public by design and the Midline server accepts them only from the
|
|
398
|
+
origins listed on the key (create one under **Project → API Keys → Browser**). Server keys (`ak_…`)
|
|
399
|
+
are refused whenever a browser sends them, and the SDK won't start with one.
|
|
400
|
+
|
|
401
|
+
Errors a framework catches itself never reach the window. Forward them:
|
|
402
|
+
|
|
403
|
+
```tsx
|
|
404
|
+
// React error boundary
|
|
405
|
+
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
406
|
+
Midline.captureException(error, { extra: { componentStack: info.componentStack } });
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Vue
|
|
410
|
+
app.config.errorHandler = (error, _instance, info) => Midline.captureException(error, { extra: { info } });
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Linking a page to your backend.** Same-origin requests get a W3C `traceparent` header, which the
|
|
414
|
+
Node agent on the backend turns into `traceId`/`spanId` (see above), so the page's failed call and
|
|
415
|
+
the server request behind it share a trace id. For an API on another origin, list it in
|
|
416
|
+
`tracePropagationTargets` and allow the `traceparent` header in that API's CORS configuration, or
|
|
417
|
+
the browser will block the call's preflight.
|
|
418
|
+
|
|
419
|
+
| Option | Default | |
|
|
420
|
+
| --- | --- | --- |
|
|
421
|
+
| `apiKey` | — | Browser key (`pk_…`). |
|
|
422
|
+
| `endpoint` | `https://api.usemidline.com` | `http://` only for localhost. |
|
|
423
|
+
| `service`, `environment`, `release` | — | Stamped on every event. |
|
|
424
|
+
| `captureErrors` | `true` | Uncaught errors and unhandled rejections. |
|
|
425
|
+
| `captureRequests` | `"failed"` | `"failed"` (4xx, 5xx, network errors), `"all"`, or `false`. Every call is a breadcrumb either way. |
|
|
426
|
+
| `captureWebVitals` | `true` | Reported once, when the page is first hidden. |
|
|
427
|
+
| `captureConsole` | `false` | `true` for `error` and `warn`, or a list of levels. Lines appear on the Terminal page. Opt-in because wrapped console calls show the SDK as their source in devtools. |
|
|
428
|
+
| `tracePropagationTargets` | same origin | Strings match as URL prefixes (or path prefixes starting with `/`); RegExps match the full URL. |
|
|
429
|
+
| `ignoreErrors`, `ignoreUrls` | `[]` | Strings match as substrings. |
|
|
430
|
+
| `sampleRate` | `1` | Fraction of events sent. |
|
|
431
|
+
| `maxEventsPerMinute` | `120` | So an error in a render loop can't flood the project. |
|
|
432
|
+
| `beforeSend` | — | Return `null` to drop an event; edit `payload` and `metadata` freely. |
|
|
433
|
+
| `redactFields` | — | Extra field names to redact. |
|
|
434
|
+
| `enabled`, `debug` | `true`, `false` | |
|
|
435
|
+
|
|
436
|
+
Also: `captureMessage(message, severity)`, `setUser({ id })`, `setTag(key, value)`,
|
|
437
|
+
`addBreadcrumb(message)`, `flush()` and `close()`, which restores everything the SDK wrapped.
|
|
438
|
+
|
|
439
|
+
What it will not do: it never sends cookies, query strings of the page URL, or request and response
|
|
440
|
+
bodies; it redacts the same credential patterns as the Node agent before an event is queued; it
|
|
441
|
+
never throws into your code; and if Midline is down it keeps a bounded queue and backs off. Pending
|
|
442
|
+
events leave with `keepalive` when the tab is hidden. During server-side rendering, `init` does
|
|
443
|
+
nothing.
|
|
444
|
+
|
|
445
|
+
No bundler? Load the ES module build directly:
|
|
446
|
+
|
|
447
|
+
```html
|
|
448
|
+
<script type="module">
|
|
449
|
+
import * as Midline from "https://cdn.jsdelivr.net/npm/midline-agent@0.4.0/dist/esm/browser/index.js";
|
|
450
|
+
Midline.init({ apiKey: "pk_…", service: "web" });
|
|
451
|
+
</script>
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
The Midline server must be recent enough to accept browser keys (it answers the CORS preflight on
|
|
455
|
+
the ingest routes). Against an older server the SDK logs nothing unless `debug` is on and keeps
|
|
456
|
+
retrying with backoff.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
375
460
|
## Any other backend
|
|
376
461
|
|
|
377
462
|
The agent does two things: it turns a request or an error into an event, and it POSTs that event to
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { EventSeverity } from "../types.js";
|
|
2
|
+
import type { CaptureContext, MidlineBrowserConfig, MidlineUser } from "./types.js";
|
|
3
|
+
export declare class BrowserConfigError extends Error {
|
|
4
|
+
}
|
|
5
|
+
/** Accepts a base URL or the ingest URL, and returns the batch URL. */
|
|
6
|
+
export declare function resolveBatchUrl(endpoint?: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* One installed SDK instance. Everything it does to the page — wrapped fetch,
|
|
9
|
+
* XHR, console and history, window listeners — is undone by `close()`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class BrowserClient {
|
|
12
|
+
private readonly win;
|
|
13
|
+
private readonly config;
|
|
14
|
+
private readonly redactor;
|
|
15
|
+
private readonly transport;
|
|
16
|
+
private readonly teardowns;
|
|
17
|
+
private readonly breadcrumbs;
|
|
18
|
+
private readonly recentErrors;
|
|
19
|
+
private readonly sessionId;
|
|
20
|
+
private readonly originalConsole;
|
|
21
|
+
private traceId;
|
|
22
|
+
private user;
|
|
23
|
+
private tags;
|
|
24
|
+
private tokens;
|
|
25
|
+
private lastRefill;
|
|
26
|
+
private active;
|
|
27
|
+
private inConsoleHook;
|
|
28
|
+
private rateLimitNoted;
|
|
29
|
+
private constructor();
|
|
30
|
+
/** Returns undefined, after one console warning, when the SDK can't run. It never throws into the app. */
|
|
31
|
+
static create(config: MidlineBrowserConfig): BrowserClient | undefined;
|
|
32
|
+
private install;
|
|
33
|
+
captureException(error: unknown, context?: CaptureContext): void;
|
|
34
|
+
captureMessage(message: string, severity?: EventSeverity, context?: CaptureContext): void;
|
|
35
|
+
setUser(user: MidlineUser | null): void;
|
|
36
|
+
setTag(key: string, value: string): void;
|
|
37
|
+
addBreadcrumb(message: string, type?: string): void;
|
|
38
|
+
flush(): Promise<void>;
|
|
39
|
+
close(): Promise<void>;
|
|
40
|
+
private handleError;
|
|
41
|
+
private handleRequest;
|
|
42
|
+
private handleConsole;
|
|
43
|
+
private handleNavigation;
|
|
44
|
+
private handleVitals;
|
|
45
|
+
private propagation;
|
|
46
|
+
private base;
|
|
47
|
+
private emit;
|
|
48
|
+
private takeToken;
|
|
49
|
+
private breadcrumb;
|
|
50
|
+
private contextPayload;
|
|
51
|
+
private applyTags;
|
|
52
|
+
/** The page path, plus a `#/…` hash route when the app routes by hash. Never the query string. */
|
|
53
|
+
private currentRoute;
|
|
54
|
+
private pageUrl;
|
|
55
|
+
private guard;
|
|
56
|
+
private debug;
|
|
57
|
+
}
|
|
58
|
+
/** W3C ids: lowercase hex, never all zeros. */
|
|
59
|
+
export declare function randomHex(bytes: number): string;
|