@fixback/sdk-core 0.2.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/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/annotation.js +18 -0
- package/dist/annotation.js.map +1 -0
- package/dist/backoff.js +79 -0
- package/dist/backoff.js.map +1 -0
- package/dist/breadcrumb.js +15 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/fingerprint.js +108 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +3 -0
- package/dist/scrub.js +216 -0
- package/dist/scrub.js.map +1 -0
- package/dist/wire.js +16 -0
- package/dist/wire.js.map +1 -0
- package/package.json +50 -0
- package/src/annotation.ts +104 -0
- package/src/backoff.test.ts +94 -0
- package/src/backoff.ts +83 -0
- package/src/breadcrumb.ts +169 -0
- package/src/fingerprint.test.ts +96 -0
- package/src/fingerprint.ts +112 -0
- package/src/index.ts +63 -0
- package/src/scrub.test.ts +215 -0
- package/src/scrub.ts +226 -0
- package/src/wire.ts +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fixback
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @fixback/sdk-core
|
|
2
|
+
|
|
3
|
+
The small, runtime-agnostic core shared by the Fixback capture SDKs
|
|
4
|
+
(`@fixback/sdk` today; `@fixback/node` next). It holds the **pure** capture logic
|
|
5
|
+
so every surface computes it the same way (ADR-0028):
|
|
6
|
+
|
|
7
|
+
- **Fingerprinting** — `computeFingerprint` and its `normalize` / `extractTopFrames`
|
|
8
|
+
helpers, so the same logical error groups identically across surfaces
|
|
9
|
+
(ADR-0027).
|
|
10
|
+
- **Scrubbing** — `scrubUrl`, `redactPii`, and the `runBeforeSend` choke point.
|
|
11
|
+
- **Backoff** — `AutoReportBackoff` + `parseRetryAfter`, honouring ingest's
|
|
12
|
+
`429` / `Retry-After` backpressure.
|
|
13
|
+
- **Wire shapes** — `ReportContent`, `CapturedFrame`, the `Annotation` /
|
|
14
|
+
`Breadcrumb` value types, and the provenance vocabulary (`FeedbackSource`,
|
|
15
|
+
`Platform`).
|
|
16
|
+
|
|
17
|
+
It is deliberately **runtime-agnostic**: no DOM, no Node built-ins, no rrweb —
|
|
18
|
+
those live in the consuming SDKs. This is an internal implementation package for
|
|
19
|
+
the Fixback SDK family, not a public API.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The Annotation & vector-marks **wire types** (spec 0003 §B/§D) — the shared,
|
|
4
|
+
* runtime-agnostic shapes that ride on a report's content (ADR-0028).
|
|
5
|
+
*
|
|
6
|
+
* An Annotation is everything a Reporter marked on the page, as three
|
|
7
|
+
* **composable, optional** layers over one full masked screenshot: the picked
|
|
8
|
+
* `element` (from the element-picker), a drag-captured `region`, and vector
|
|
9
|
+
* `marks` — arrow / box / pen / text. Marks live in the **screenshot's coordinate
|
|
10
|
+
* space**, never baked into the PNG: the dashboard composites them at view time.
|
|
11
|
+
*
|
|
12
|
+
* This module carries only the pure value types; the SDK's geometry and assembly
|
|
13
|
+
* helpers (`normalizeRect`, `arrowHeadPoints`, `assembleAnnotation`) live in the
|
|
14
|
+
* consuming SDK. Keep these in lock-step with the server's ingest contract
|
|
15
|
+
* (spec §D/§H).
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
//# sourceMappingURL=annotation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation.js","sourceRoot":"","sources":["../src/annotation.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG"}
|
package/dist/backoff.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Client-side backpressure for **automatic** error reports (spec 0003 §E/§H,
|
|
4
|
+
* ticket #89) — the pure, runtime-agnostic backoff every SDK shares (ADR-0028).
|
|
5
|
+
* When ingest sheds `source: error` load it answers `429` with a `Retry-After`;
|
|
6
|
+
* the SDK honours it by holding a pause window during which further automatic
|
|
7
|
+
* reports are dropped without touching the network. **Manual** reports — a human
|
|
8
|
+
* clicking Send — never consult this gate.
|
|
9
|
+
*
|
|
10
|
+
* This is the transport's counterpart to the server's per-Project token bucket
|
|
11
|
+
* (`apps/api/src/ingest/auto-report-rate-limiter.ts`): one shared window per page,
|
|
12
|
+
* so a 429 from one auto-report shed applies to the auto-reports that follow it.
|
|
13
|
+
* The clock is injectable so the window is unit-tested deterministically, never on
|
|
14
|
+
* wall time — mirroring the server limiter's `Clock`.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.AutoReportBackoff = exports.DEFAULT_RETRY_AFTER_SECONDS = void 0;
|
|
18
|
+
exports.parseRetryAfter = parseRetryAfter;
|
|
19
|
+
/** The hold window applied when a `429` carries no usable `Retry-After` (spec §E). */
|
|
20
|
+
exports.DEFAULT_RETRY_AFTER_SECONDS = 60;
|
|
21
|
+
/**
|
|
22
|
+
* Parse a `Retry-After` header into whole seconds to hold for. Handles both HTTP
|
|
23
|
+
* forms — a delta-seconds integer and an HTTP-date (measured from `now`, rounded up
|
|
24
|
+
* and clamped at zero) — and falls back to {@link DEFAULT_RETRY_AFTER_SECONDS} when
|
|
25
|
+
* the header is absent, blank, or unparseable. Ingest sends the delta-seconds form;
|
|
26
|
+
* the date form is handled for spec-completeness.
|
|
27
|
+
*/
|
|
28
|
+
function parseRetryAfter(header, now) {
|
|
29
|
+
if (header == null)
|
|
30
|
+
return exports.DEFAULT_RETRY_AFTER_SECONDS;
|
|
31
|
+
const value = header.trim();
|
|
32
|
+
if (value === "")
|
|
33
|
+
return exports.DEFAULT_RETRY_AFTER_SECONDS;
|
|
34
|
+
if (/^\d+$/.test(value)) {
|
|
35
|
+
return Number(value);
|
|
36
|
+
}
|
|
37
|
+
const when = Date.parse(value);
|
|
38
|
+
if (!Number.isNaN(when)) {
|
|
39
|
+
return Math.max(0, Math.ceil((when - now) / 1000));
|
|
40
|
+
}
|
|
41
|
+
return exports.DEFAULT_RETRY_AFTER_SECONDS;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A single pause window for automatic (`source: error`) reports. `hold` opens (or
|
|
45
|
+
* extends) it from a `429`'s `Retry-After`; `isPaused` reports whether it is still
|
|
46
|
+
* open. The default instance in the SDK's transport is shared across a page's
|
|
47
|
+
* reports so the hold persists across successive automatic submissions.
|
|
48
|
+
*/
|
|
49
|
+
class AutoReportBackoff {
|
|
50
|
+
now;
|
|
51
|
+
/** Epoch ms until which automatic reports are held; `0` when clear. */
|
|
52
|
+
pausedUntil = 0;
|
|
53
|
+
constructor(now = Date.now) {
|
|
54
|
+
this.now = now;
|
|
55
|
+
}
|
|
56
|
+
/** Is the automatic-report pause window currently open? */
|
|
57
|
+
isPaused() {
|
|
58
|
+
return this.now() < this.pausedUntil;
|
|
59
|
+
}
|
|
60
|
+
/** Whole seconds remaining in the pause window (`0` when clear). */
|
|
61
|
+
retryAfterSeconds() {
|
|
62
|
+
return Math.max(0, Math.ceil((this.pausedUntil - this.now()) / 1000));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Open (or extend) the window from a `429`'s `Retry-After` value, returning the
|
|
66
|
+
* seconds it will hold for. The window only ever grows — a shorter later hold
|
|
67
|
+
* never clips a longer one already in effect.
|
|
68
|
+
*/
|
|
69
|
+
hold(retryAfterHeader) {
|
|
70
|
+
const now = this.now();
|
|
71
|
+
const seconds = parseRetryAfter(retryAfterHeader, now);
|
|
72
|
+
const until = now + seconds * 1000;
|
|
73
|
+
if (until > this.pausedUntil)
|
|
74
|
+
this.pausedUntil = until;
|
|
75
|
+
return seconds;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.AutoReportBackoff = AutoReportBackoff;
|
|
79
|
+
//# sourceMappingURL=backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAeH,0CAkBC;AA5BD,sFAAsF;AACzE,QAAA,2BAA2B,GAAG,EAAE,CAAC;AAE9C;;;;;;GAMG;AACH,SAAgB,eAAe,CAC7B,MAAiC,EACjC,GAAW;IAEX,IAAI,MAAM,IAAI,IAAI;QAAE,OAAO,mCAA2B,CAAC;IACvD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,mCAA2B,CAAC;IAErD,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,mCAA2B,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAa,iBAAiB;IAIC;IAH7B,uEAAuE;IAC/D,WAAW,GAAG,CAAC,CAAC;IAExB,YAA6B,MAAa,IAAI,CAAC,GAAG;QAArB,QAAG,GAAH,GAAG,CAAkB;IAAG,CAAC;IAEtD,2DAA2D;IAC3D,QAAQ;QACN,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC;IACvC,CAAC;IAED,oEAAoE;IACpE,iBAAiB;QACf,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IACxE,CAAC;IAED;;;;OAIG;IACH,IAAI,CAAC,gBAA2C;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,eAAe,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,GAAG,GAAG,OAAO,GAAG,IAAI,CAAC;QACnC,IAAI,KAAK,GAAG,IAAI,CAAC,WAAW;YAAE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACvD,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AA5BD,8CA4BC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The trace **breadcrumb wire types** (spec 0003 §C, spec #122) — the shared,
|
|
4
|
+
* runtime-agnostic shapes for one entry of a captured trace stream (ADR-0028).
|
|
5
|
+
*
|
|
6
|
+
* A breadcrumb records recent activity — console, navigation, network metadata,
|
|
7
|
+
* masked user actions, and the failing error — that rides on a report as
|
|
8
|
+
* Evidence. This module carries only the value types; the SDK's ring buffer and
|
|
9
|
+
* DOM/Node instrumentation that produce them live in the consuming SDK.
|
|
10
|
+
* Everything private is kept out **at the source** by that instrumentation:
|
|
11
|
+
* `ui.input` records that an input changed, never its value; network crumbs carry
|
|
12
|
+
* method + URL + status only, never bodies.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
//# sourceMappingURL=breadcrumb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"breadcrumb.js","sourceRoot":"","sources":["../src/breadcrumb.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The cross-surface error **fingerprint** (research `sentry-error-capture-findings.md`
|
|
4
|
+
* §7.2; ADR-0027/0028) — the pure, runtime-agnostic key by which the same logical
|
|
5
|
+
* error groups together, computed the **same way** on every SDK.
|
|
6
|
+
*
|
|
7
|
+
* `computeFingerprint(type, value, stack)` hashes the error type, a normalised
|
|
8
|
+
* message, and a compact top-frames signature. It only has to be stable and
|
|
9
|
+
* well-distributed (the client key is a flood guard; the server does canonical
|
|
10
|
+
* cross-session clustering), so a dependency-free FNV-1a hash is right. The
|
|
11
|
+
* `normalize` shape and the frame limit are **starting points** from research
|
|
12
|
+
* (ticket #92), tunable — never a frozen magic set.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.normalize = normalize;
|
|
16
|
+
exports.hashString = hashString;
|
|
17
|
+
exports.extractTopFrames = extractTopFrames;
|
|
18
|
+
exports.computeFingerprint = computeFingerprint;
|
|
19
|
+
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
|
|
20
|
+
const URL_RE = /\bhttps?:\/\/[^\s"')]+/gi;
|
|
21
|
+
const HEX_0X_RE = /\b0x[0-9a-f]+\b/gi;
|
|
22
|
+
const HEX_RUN_RE = /\b[0-9a-f]{8,}\b/gi;
|
|
23
|
+
const DIGIT_RUN_RE = /\d{4,}/g;
|
|
24
|
+
/** How many top stack frames feed the fingerprint (research §7.2). */
|
|
25
|
+
const FINGERPRINT_FRAME_LIMIT = 5;
|
|
26
|
+
/**
|
|
27
|
+
* Collapse the volatile parts of an error message so a changing string doesn't
|
|
28
|
+
* split one bug: UUIDs, URLs, `0x…` and long hex runs, and long digit runs are
|
|
29
|
+
* replaced with stable placeholders. Short numbers and stable text are kept so
|
|
30
|
+
* genuinely distinct bugs stay distinct. A small, dependency-free regex set —
|
|
31
|
+
* tunable per ticket #92, never a frozen magic set.
|
|
32
|
+
*/
|
|
33
|
+
function normalize(value) {
|
|
34
|
+
if (typeof value !== "string" || value.length === 0)
|
|
35
|
+
return "";
|
|
36
|
+
return value
|
|
37
|
+
.replace(UUID_RE, "<uuid>")
|
|
38
|
+
.replace(URL_RE, "<url>")
|
|
39
|
+
.replace(HEX_0X_RE, "<hex>")
|
|
40
|
+
.replace(HEX_RUN_RE, "<hex>")
|
|
41
|
+
.replace(DIGIT_RUN_RE, "<n>")
|
|
42
|
+
.trim();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A dependency-free FNV-1a hash rendered in base-36. It only has to be stable and
|
|
46
|
+
* well-distributed within one session (the client key is a flood guard; the server
|
|
47
|
+
* does canonical cross-session clustering), so a non-cryptographic hash is right.
|
|
48
|
+
*/
|
|
49
|
+
function hashString(input) {
|
|
50
|
+
let h = 0x811c9dc5;
|
|
51
|
+
for (let i = 0; i < input.length; i++) {
|
|
52
|
+
h ^= input.charCodeAt(i);
|
|
53
|
+
h = Math.imul(h, 0x01000193);
|
|
54
|
+
}
|
|
55
|
+
return (h >>> 0).toString(36);
|
|
56
|
+
}
|
|
57
|
+
/** Reduce a frame location to `basename:line:col`, dropping origin and query. */
|
|
58
|
+
function compactLocation(location) {
|
|
59
|
+
const noQuery = location.replace(/\?[^:]*/, "");
|
|
60
|
+
const lastSlash = noQuery.lastIndexOf("/");
|
|
61
|
+
return lastSlash >= 0 ? noQuery.slice(lastSlash + 1) : noQuery;
|
|
62
|
+
}
|
|
63
|
+
/** Parse one stack line into a compact `function@basename:line:col` frame id. */
|
|
64
|
+
function parseFrame(line) {
|
|
65
|
+
// V8: "at fn (loc)" | "at loc"
|
|
66
|
+
const v8Named = line.match(/^at\s+(.+?)\s+\((.+)\)$/);
|
|
67
|
+
if (v8Named)
|
|
68
|
+
return `${v8Named[1] ?? ""}@${compactLocation(v8Named[2] ?? "")}`;
|
|
69
|
+
const v8Bare = line.match(/^at\s+(.+)$/);
|
|
70
|
+
if (v8Bare)
|
|
71
|
+
return `@${compactLocation(v8Bare[1] ?? "")}`;
|
|
72
|
+
// Firefox / Safari: "fn@loc" | "@loc"
|
|
73
|
+
const at = line.indexOf("@");
|
|
74
|
+
if (at >= 0) {
|
|
75
|
+
const fn = line.slice(0, at);
|
|
76
|
+
return `${fn}@${compactLocation(line.slice(at + 1))}`;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Extract a compact, stable signature of the top in-app frames of a stack: up to
|
|
82
|
+
* {@link FINGERPRINT_FRAME_LIMIT} frames as `function@basename:line:col`, origin
|
|
83
|
+
* and cache-busting query stripped so a per-deploy asset hash doesn't matter within
|
|
84
|
+
* a session. Returns `""` when there is no usable stack (message-only fallback).
|
|
85
|
+
*/
|
|
86
|
+
function extractTopFrames(stack, limit = FINGERPRINT_FRAME_LIMIT) {
|
|
87
|
+
if (typeof stack !== "string" || stack.length === 0)
|
|
88
|
+
return "";
|
|
89
|
+
const frames = [];
|
|
90
|
+
for (const raw of stack.split("\n")) {
|
|
91
|
+
const frame = parseFrame(raw.trim());
|
|
92
|
+
if (frame) {
|
|
93
|
+
frames.push(frame);
|
|
94
|
+
if (frames.length >= limit)
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return frames.join(" < ");
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The per-session fingerprint (research §7.2):
|
|
102
|
+
* `hash(errorType + "|" + normalize(value) + "|" + topFrames)`. Stack frames
|
|
103
|
+
* dominate when present; otherwise it falls back to type + normalized value.
|
|
104
|
+
*/
|
|
105
|
+
function computeFingerprint(type, value, stack) {
|
|
106
|
+
return hashString(`${type}|${normalize(value)}|${extractTopFrames(stack)}`);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@fixback/sdk-core` — the runtime-agnostic core shared by the Fixback capture
|
|
4
|
+
* SDKs (ADR-0028). Pure fingerprinting, scrubbing, and backoff logic plus the
|
|
5
|
+
* shared ingest wire shapes — no DOM, no Node built-ins, no rrweb.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.parseRetryAfter = exports.DEFAULT_RETRY_AFTER_SECONDS = exports.AutoReportBackoff = exports.scrubUrl = exports.runBeforeSend = exports.redactPii = exports.applyDefaultScrub = exports.normalize = exports.hashString = exports.extractTopFrames = exports.computeFingerprint = void 0;
|
|
9
|
+
// Fingerprint (ADR-0027) — the cross-surface grouping key.
|
|
10
|
+
var fingerprint_1 = require("./fingerprint");
|
|
11
|
+
Object.defineProperty(exports, "computeFingerprint", { enumerable: true, get: function () { return fingerprint_1.computeFingerprint; } });
|
|
12
|
+
Object.defineProperty(exports, "extractTopFrames", { enumerable: true, get: function () { return fingerprint_1.extractTopFrames; } });
|
|
13
|
+
Object.defineProperty(exports, "hashString", { enumerable: true, get: function () { return fingerprint_1.hashString; } });
|
|
14
|
+
Object.defineProperty(exports, "normalize", { enumerable: true, get: function () { return fingerprint_1.normalize; } });
|
|
15
|
+
// Scrub choke point (spec 0003 §C).
|
|
16
|
+
var scrub_1 = require("./scrub");
|
|
17
|
+
Object.defineProperty(exports, "applyDefaultScrub", { enumerable: true, get: function () { return scrub_1.applyDefaultScrub; } });
|
|
18
|
+
Object.defineProperty(exports, "redactPii", { enumerable: true, get: function () { return scrub_1.redactPii; } });
|
|
19
|
+
Object.defineProperty(exports, "runBeforeSend", { enumerable: true, get: function () { return scrub_1.runBeforeSend; } });
|
|
20
|
+
Object.defineProperty(exports, "scrubUrl", { enumerable: true, get: function () { return scrub_1.scrubUrl; } });
|
|
21
|
+
// Backoff (spec 0003 §E/§H) — honouring ingest's 429 / Retry-After.
|
|
22
|
+
var backoff_1 = require("./backoff");
|
|
23
|
+
Object.defineProperty(exports, "AutoReportBackoff", { enumerable: true, get: function () { return backoff_1.AutoReportBackoff; } });
|
|
24
|
+
Object.defineProperty(exports, "DEFAULT_RETRY_AFTER_SECONDS", { enumerable: true, get: function () { return backoff_1.DEFAULT_RETRY_AFTER_SECONDS; } });
|
|
25
|
+
Object.defineProperty(exports, "parseRetryAfter", { enumerable: true, get: function () { return backoff_1.parseRetryAfter; } });
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,2DAA2D;AAC3D,6CAA4F;AAAnF,iHAAA,kBAAkB,OAAA;AAAE,+GAAA,gBAAgB,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,wGAAA,SAAS,OAAA;AAEpE,oCAAoC;AACpC,iCAOiB;AANf,0GAAA,iBAAiB,OAAA;AAGjB,kGAAA,SAAS,OAAA;AACT,sGAAA,aAAa,OAAA;AACb,iGAAA,QAAQ,OAAA;AAGV,oEAAoE;AACpE,qCAKmB;AAJjB,4GAAA,iBAAiB,OAAA;AAEjB,sHAAA,2BAA2B,OAAA;AAC3B,0GAAA,eAAe,OAAA"}
|
package/dist/scrub.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The single client-side **scrub choke point** every report passes through
|
|
4
|
+
* before transport (spec 0003 §C; research `sentry-error-capture-findings.md`
|
|
5
|
+
* §7.4) — the SDK's `beforeSend` equivalent, shared runtime-agnostically across
|
|
6
|
+
* surfaces (ADR-0028).
|
|
7
|
+
*
|
|
8
|
+
* Masking is the SDK's job, done before anything leaves the process. The
|
|
9
|
+
* screenshot is masked at capture, and breadcrumbs never record a value or a
|
|
10
|
+
* body at the source; `runBeforeSend` is the **last** gate over the assembled
|
|
11
|
+
* report. Its default scrubbers are **on**: they strip credentials, query
|
|
12
|
+
* strings, and bearer tokens from URLs, and redact obvious PII (emails, long
|
|
13
|
+
* digit runs, bearer tokens) from crumb and error text. The result is then handed
|
|
14
|
+
* to an optional per-project hook that can mutate it further or drop the whole
|
|
15
|
+
* report by returning `null`.
|
|
16
|
+
*
|
|
17
|
+
* The hook is **synchronous and network-free** by contract, and both manual
|
|
18
|
+
* (overlay) and automatic (error-capture) reports run through the very same
|
|
19
|
+
* choke point. A project relaxes the defaults with `scrub: false`, or reshapes
|
|
20
|
+
* the draft in its own hook — never a silent raw send.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.redactPii = redactPii;
|
|
24
|
+
exports.scrubUrl = scrubUrl;
|
|
25
|
+
exports.applyDefaultScrub = applyDefaultScrub;
|
|
26
|
+
exports.runBeforeSend = runBeforeSend;
|
|
27
|
+
/** A digit run at least this long is treated as sensitive (phone, card, id). */
|
|
28
|
+
const MIN_DIGIT_RUN = 7;
|
|
29
|
+
const EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
30
|
+
const DIGIT_RUN_RE = new RegExp(`\\d{${MIN_DIGIT_RUN},}`, "g");
|
|
31
|
+
const BEARER_RE = /\b(bearer|token)\s+[\w.\-~+/]+=*/gi;
|
|
32
|
+
/** An http(s) URL embedded in free text (e.g. a console log line). */
|
|
33
|
+
const URL_IN_TEXT_RE = /\bhttps?:\/\/[^\s"'<>]+/gi;
|
|
34
|
+
/**
|
|
35
|
+
* Redact obvious PII from free text: email addresses, `Bearer <token>` /
|
|
36
|
+
* `token <value>` pairs, and long digit runs. Conservative by design — it keeps
|
|
37
|
+
* the shape of the message readable while removing the sensitive spans.
|
|
38
|
+
*/
|
|
39
|
+
function redactPii(text) {
|
|
40
|
+
if (typeof text !== "string" || text.length === 0)
|
|
41
|
+
return text;
|
|
42
|
+
return text
|
|
43
|
+
.replace(EMAIL_RE, "[redacted-email]")
|
|
44
|
+
.replace(BEARER_RE, (_match, keyword) => `${keyword} [redacted]`)
|
|
45
|
+
.replace(DIGIT_RUN_RE, "[redacted-number]");
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Redact PII from the **path** portion of a URL (spec #122 §E, ticket #139):
|
|
49
|
+
* emails and long digit runs in path (or fragment-route) segments — a network
|
|
50
|
+
* list shows URLs prominently, so an identifier baked into a path
|
|
51
|
+
* (`/users/jane@acme.app`, `/card/4111111111111111`) must not leak. Deliberately
|
|
52
|
+
* narrower than {@link redactPii}: no bearer/token rule (URL credentials are
|
|
53
|
+
* already stripped by {@link scrubUrl}) and it is only ever applied to the
|
|
54
|
+
* post-authority remainder, so a host or port with digits is never touched.
|
|
55
|
+
*/
|
|
56
|
+
function redactPathPii(pathAndBeyond) {
|
|
57
|
+
return pathAndBeyond
|
|
58
|
+
.replace(EMAIL_RE, "[redacted-email]")
|
|
59
|
+
.replace(DIGIT_RUN_RE, "[redacted-number]");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Strip the sensitive parts of a URL: userinfo credentials
|
|
63
|
+
* (`scheme://user:pass@host`), the entire query string, a token-bearing
|
|
64
|
+
* fragment (one that carries `key=value`), and PII (emails, long digit runs) in
|
|
65
|
+
* the **path segments** (#139). Plain hash routes (`#/checkout`) are kept. Works
|
|
66
|
+
* on absolute and relative URLs alike, with no dependency and no throw. The host
|
|
67
|
+
* (authority) is never redacted — only the path and any surviving fragment route.
|
|
68
|
+
*/
|
|
69
|
+
function scrubUrl(url) {
|
|
70
|
+
if (typeof url !== "string" || url.length === 0)
|
|
71
|
+
return url;
|
|
72
|
+
let out = url;
|
|
73
|
+
// Drop userinfo credentials: scheme://user:pass@host → scheme://host
|
|
74
|
+
out = out.replace(/(^[a-z][a-z0-9+.-]*:\/\/)[^/@?#]*@/i, "$1");
|
|
75
|
+
// Drop the query string entirely (everything from '?' up to a '#').
|
|
76
|
+
out = out.replace(/\?[^#]*/, "");
|
|
77
|
+
// Drop a token-bearing fragment (it carries '='); keep plain hash routes.
|
|
78
|
+
out = out.replace(/#.*$/, (fragment) => fragment.includes("=") ? "" : fragment);
|
|
79
|
+
// Redact PII in the path (and any kept fragment route), never in the authority.
|
|
80
|
+
// Split off `scheme://authority` (or a protocol-relative `//authority`) so a
|
|
81
|
+
// host/port that contains digits is preserved; the remainder is the path onward.
|
|
82
|
+
const absolute = out.match(/^([a-z][a-z0-9+.-]*:\/\/[^/?#]*)([\s\S]*)$/i);
|
|
83
|
+
if (absolute)
|
|
84
|
+
return absolute[1] + redactPathPii(absolute[2] ?? "");
|
|
85
|
+
const protocolRelative = out.match(/^(\/\/[^/?#]*)([\s\S]*)$/);
|
|
86
|
+
if (protocolRelative) {
|
|
87
|
+
return protocolRelative[1] + redactPathPii(protocolRelative[2] ?? "");
|
|
88
|
+
}
|
|
89
|
+
// A relative URL is all path — redact the whole thing.
|
|
90
|
+
return redactPathPii(out);
|
|
91
|
+
}
|
|
92
|
+
function scrubCrumbData(data) {
|
|
93
|
+
const next = { ...data };
|
|
94
|
+
if (typeof next.url === "string")
|
|
95
|
+
next.url = scrubUrl(next.url);
|
|
96
|
+
if (typeof next.from === "string")
|
|
97
|
+
next.from = scrubUrl(next.from);
|
|
98
|
+
if (typeof next.to === "string")
|
|
99
|
+
next.to = scrubUrl(next.to);
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
/** Scrub a crumb's free-text message: strip URL query strings, then redact PII. */
|
|
103
|
+
function scrubMessage(message) {
|
|
104
|
+
return redactPii(message.replace(URL_IN_TEXT_RE, (url) => scrubUrl(url)));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Scrub the string leaves of a structured console-arg value (spec #122 §C/§E) — the
|
|
108
|
+
* value a `json` arg carries. Walks arrays and plain objects, applying the same
|
|
109
|
+
* URL-strip + PII redaction as a crumb message to every nested string, so a value
|
|
110
|
+
* logged through `console.*` is swept just like the flattened preview.
|
|
111
|
+
*/
|
|
112
|
+
function scrubArgValue(value) {
|
|
113
|
+
if (typeof value === "string")
|
|
114
|
+
return scrubMessage(value);
|
|
115
|
+
if (Array.isArray(value))
|
|
116
|
+
return value.map(scrubArgValue);
|
|
117
|
+
if (value && typeof value === "object") {
|
|
118
|
+
const out = {};
|
|
119
|
+
for (const [key, v] of Object.entries(value)) {
|
|
120
|
+
out[key] = scrubArgValue(v);
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Scrub one structured console argument (spec #122 §C): redact PII / strip URL query
|
|
128
|
+
* strings from a `string` value, from every string leaf of a `json` value, and from an
|
|
129
|
+
* `error` value's `message` and `stack`. Other tags (`number`/`bool`/`null`) carry no
|
|
130
|
+
* free text and pass through unchanged.
|
|
131
|
+
*/
|
|
132
|
+
function scrubConsoleArg(arg) {
|
|
133
|
+
switch (arg.t) {
|
|
134
|
+
case "string":
|
|
135
|
+
return typeof arg.v === "string" ? { t: "string", v: scrubMessage(arg.v) } : arg;
|
|
136
|
+
case "json":
|
|
137
|
+
return { t: "json", v: scrubArgValue(arg.v) };
|
|
138
|
+
case "error": {
|
|
139
|
+
if (!arg.v || typeof arg.v !== "object")
|
|
140
|
+
return arg;
|
|
141
|
+
const v = arg.v;
|
|
142
|
+
const next = { ...v };
|
|
143
|
+
if (typeof next.message === "string")
|
|
144
|
+
next.message = scrubMessage(next.message);
|
|
145
|
+
if (typeof next.stack === "string")
|
|
146
|
+
next.stack = scrubMessage(next.stack);
|
|
147
|
+
return { t: "error", v: next };
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
return arg;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function scrubCrumb(crumb) {
|
|
154
|
+
const next = { ...crumb };
|
|
155
|
+
if (typeof next.message === "string")
|
|
156
|
+
next.message = scrubMessage(next.message);
|
|
157
|
+
if (next.data)
|
|
158
|
+
next.data = scrubCrumbData(next.data);
|
|
159
|
+
// A rich network crumb (#139) carries its scrubbed URL at the top level — sweep it
|
|
160
|
+
// again here (idempotent) so the choke point holds whether the URL rode in `data`
|
|
161
|
+
// (a legacy thin crumb) or top-level, and path-PII redaction is never skipped.
|
|
162
|
+
if (typeof next.url === "string")
|
|
163
|
+
next.url = scrubUrl(next.url);
|
|
164
|
+
// A console crumb's structured args carry the same free text as its preview — sweep
|
|
165
|
+
// them too, so PII redaction holds whether a value is read from `message` or `args`.
|
|
166
|
+
if (Array.isArray(next.args))
|
|
167
|
+
next.args = next.args.map(scrubConsoleArg);
|
|
168
|
+
return next;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Apply the built-in default scrubbers to a report draft: strip the page URL,
|
|
172
|
+
* and scrub every crumb's URLs and redact PII from its text. The Reporter's own
|
|
173
|
+
* `comment` is intentionally left untouched — it is authored on purpose, not
|
|
174
|
+
* scraped. The screenshot and input values are masked elsewhere (at capture and
|
|
175
|
+
* at crumb creation); this is the final URL/PII sweep.
|
|
176
|
+
*/
|
|
177
|
+
function applyDefaultScrub(draft) {
|
|
178
|
+
const next = { ...draft };
|
|
179
|
+
if (typeof next.url === "string")
|
|
180
|
+
next.url = scrubUrl(next.url);
|
|
181
|
+
if (next.trace && next.trace.length > 0) {
|
|
182
|
+
next.trace = next.trace.map(scrubCrumb);
|
|
183
|
+
}
|
|
184
|
+
// The structured stack frames (#117) carry script URLs — sweep them again here
|
|
185
|
+
// (idempotent, like the crumb URLs) so the choke point holds regardless of
|
|
186
|
+
// where the frames were parsed.
|
|
187
|
+
if (next.errorFrames && next.errorFrames.length > 0) {
|
|
188
|
+
next.errorFrames = next.errorFrames.map((frame) => ({
|
|
189
|
+
...frame,
|
|
190
|
+
file: scrubUrl(frame.file),
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
return next;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Run the report draft through the client scrub choke point: the default
|
|
197
|
+
* scrubbers first (unless `scrub` is `false`), then the optional per-project
|
|
198
|
+
* hook. Returns the scrubbed (and possibly hook-mutated) draft, or `null` when
|
|
199
|
+
* the hook drops the report. A hook that throws is treated as a no-op — the
|
|
200
|
+
* already-scrubbed draft is kept, so a buggy hook never breaks the report path
|
|
201
|
+
* nor leaks unscrubbed data.
|
|
202
|
+
*/
|
|
203
|
+
function runBeforeSend(draft, options = {}) {
|
|
204
|
+
const current = options.scrub === false ? draft : applyDefaultScrub(draft);
|
|
205
|
+
const hook = options.hook;
|
|
206
|
+
if (!hook)
|
|
207
|
+
return current;
|
|
208
|
+
try {
|
|
209
|
+
const result = hook(current);
|
|
210
|
+
return result ?? null;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return current;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=scrub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub.js","sourceRoot":"","sources":["../src/scrub.ts"],"names":[],"mappings":"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"}
|
package/dist/wire.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The shared **ingest wire shapes** (ADR-0028) — the value contract a captured
|
|
4
|
+
* report carries to `POST /api/ingest/feedback`, shared by every Fixback capture
|
|
5
|
+
* SDK so they all speak the same shape.
|
|
6
|
+
*
|
|
7
|
+
* Like the rest of the core these are runtime-agnostic value types: the browser
|
|
8
|
+
* and (future) backend SDKs both assemble a {@link ReportContent} from what they
|
|
9
|
+
* captured. Keep them in lock-step with the server: the JSON `payload` part the
|
|
10
|
+
* ingest controller parses (`apps/api/src/ingest/ingest.controller.ts`) and the
|
|
11
|
+
* `SubmissionContent` it maps to (`apps/api/src/ingest/reporter-identity.ts`).
|
|
12
|
+
* Each SDK keeps vendoring its own transport envelope (ticket #47) — the core
|
|
13
|
+
* carries the pure content shape, not the multipart envelope.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
//# sourceMappingURL=wire.js.map
|
package/dist/wire.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.js","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fixback/sdk-core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Runtime-agnostic core shared by the Fixback capture SDKs — fingerprinting, scrubbing, backoff, and the shared wire shapes.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./src/index.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./src/index.ts",
|
|
13
|
+
"import": "./src/index.ts",
|
|
14
|
+
"require": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"fixback",
|
|
26
|
+
"feedback",
|
|
27
|
+
"sdk",
|
|
28
|
+
"fingerprint"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/wemuda/fixback.git",
|
|
33
|
+
"directory": "packages/sdk-core"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/wemuda/fixback/tree/master/packages/sdk-core#readme",
|
|
36
|
+
"bugs": "https://github.com/wemuda/fixback/issues",
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"typescript": "5.9.3",
|
|
42
|
+
"vitest": "^4.1.10"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.build.json && node scripts/finalize-cjs.mjs",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"test": "vitest run"
|
|
49
|
+
}
|
|
50
|
+
}
|