@polishd/next 0.2.2 → 0.2.3-dev.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/AGENTS.md +1 -1
- package/CHANGELOG.md +102 -0
- package/README.md +32 -0
- package/bin/doctor.mjs +8 -1
- package/dist/client/init.d.ts.map +1 -1
- package/dist/client/init.js +39 -8
- package/dist/client/init.js.map +1 -1
- package/dist/client/script-provenance.d.ts +21 -0
- package/dist/client/script-provenance.d.ts.map +1 -0
- package/dist/client/script-provenance.js +127 -0
- package/dist/client/script-provenance.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/dashboard/design-review-card.d.ts.map +1 -1
- package/dist/dashboard/design-review-card.js +3 -3
- package/dist/dashboard/design-review-card.js.map +1 -1
- package/dist/dashboard/design.d.ts +3 -1
- package/dist/dashboard/design.d.ts.map +1 -1
- package/dist/dashboard/design.js +3 -2
- package/dist/dashboard/design.js.map +1 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +24 -19
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/installs.d.ts.map +1 -1
- package/dist/dashboard/installs.js +2 -1
- package/dist/dashboard/installs.js.map +1 -1
- package/dist/dashboard/journeys.d.ts.map +1 -1
- package/dist/dashboard/journeys.js +3 -2
- package/dist/dashboard/journeys.js.map +1 -1
- package/dist/dashboard/pages.d.ts.map +1 -1
- package/dist/dashboard/pages.js +1 -1
- package/dist/dashboard/pages.js.map +1 -1
- package/dist/dashboard/settings.d.ts.map +1 -1
- package/dist/dashboard/settings.js +6 -2
- package/dist/dashboard/settings.js.map +1 -1
- package/dist/dashboard/show-more.d.ts.map +1 -1
- package/dist/dashboard/show-more.js +1 -1
- package/dist/dashboard/show-more.js.map +1 -1
- package/dist/dashboard/snapshot-actions.d.ts +16 -0
- package/dist/dashboard/snapshot-actions.d.ts.map +1 -0
- package/dist/dashboard/snapshot-actions.js +79 -0
- package/dist/dashboard/snapshot-actions.js.map +1 -0
- package/dist/dashboard/snapshots.d.ts +6 -0
- package/dist/dashboard/snapshots.d.ts.map +1 -0
- package/dist/dashboard/snapshots.js +86 -0
- package/dist/dashboard/snapshots.js.map +1 -0
- package/dist/dashboard/summary.d.ts.map +1 -1
- package/dist/dashboard/summary.js +11 -6
- package/dist/dashboard/summary.js.map +1 -1
- package/dist/dashboard/telemetry.d.ts +3 -1
- package/dist/dashboard/telemetry.d.ts.map +1 -1
- package/dist/dashboard/telemetry.js +50 -8
- package/dist/dashboard/telemetry.js.map +1 -1
- package/dist/dashboard.css +79 -2
- package/dist/server/ingest.d.ts +2 -5
- package/dist/server/ingest.d.ts.map +1 -1
- package/dist/server/ingest.js +40 -10
- package/dist/server/ingest.js.map +1 -1
- package/dist/server/queries.d.ts +23 -11
- package/dist/server/queries.d.ts.map +1 -1
- package/dist/server/queries.js +98 -34
- package/dist/server/queries.js.map +1 -1
- package/dist/server/snapshots.d.ts +65 -0
- package/dist/server/snapshots.d.ts.map +1 -0
- package/dist/server/snapshots.js +194 -0
- package/dist/server/snapshots.js.map +1 -0
- package/dist/server/telemetry.d.ts +13 -0
- package/dist/server/telemetry.d.ts.map +1 -1
- package/dist/server/telemetry.js +28 -7
- package/dist/server/telemetry.js.map +1 -1
- package/dist/shared/capture-status.d.ts +99 -0
- package/dist/shared/capture-status.d.ts.map +1 -0
- package/dist/shared/capture-status.js +133 -0
- package/dist/shared/capture-status.js.map +1 -0
- package/dist/shared/error-noise.d.ts +39 -0
- package/dist/shared/error-noise.d.ts.map +1 -0
- package/dist/shared/error-noise.js +101 -0
- package/dist/shared/error-noise.js.map +1 -0
- package/dist/shared/types.d.ts +1 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +4 -0
- package/dist/shared/types.js.map +1 -1
- package/docs/SETUP.md +25 -0
- package/package.json +6 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/server/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/server/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,kEAAkE;AAClE,MAAM,CAAC,MAAM,0BAA0B,GACrC,wDAAwD,CAAC;AAE3D,MAAM,WAAW,GAAG,mBAAmB,CAAC;AA+BxC,MAAM,UAAU,sBAAsB;IACpC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACxC,OAAO,CAAC,KAAK,SAAS,IAAI,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACjE,CAAC;AAMD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,OAAO,CAAC,GAAG,CAAC,wBAAwB,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC;AACrF,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,OAAO,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,0BAA0B,CAAC;AAC9E,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAgB;IACzD,MAAM,OAAO,CACX,WAAW,EACX,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAC3E,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,KAAK,UAAU,gBAAgB;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,EAAE,CAAC;QAC7C,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;YACpC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;SACtB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACtD,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,MAAM,QAAQ,GAAG,wBAAwB,EAAE,CAAC;IAC5C,MAAM,GAAG,GAAG,CAAC,MAA8B,EAAyB,EAAE,CAAC,CAAC;QACtE,MAAM;QACN,QAAQ;QACR,YAAY,EAAE,IAAI;KACnB,CAAC,CAAC;IACH,IAAI,sBAAsB,EAAE;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC;IACrD,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,CAAC,CAAC,MAAM,UAAU,EAAE,CAAC;QAAE,OAAO,GAAG,CAAC,UAAU,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,EAAE,EAAE,CAAC;QACjF,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;IACD,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — capture health (shared: no server, no React).
|
|
3
|
+
*
|
|
4
|
+
* Two things live here, and they are the same subject seen from both ends:
|
|
5
|
+
* the on-disk shape ingest writes when it drops a cookie-less batch, and the
|
|
6
|
+
* rule the dashboard applies to decide whether that means "your proxy isn't
|
|
7
|
+
* running" or "some bots posted to a public endpoint".
|
|
8
|
+
*
|
|
9
|
+
* They are together, and pure, because the rule is the part that has been
|
|
10
|
+
* wrong: it compared an all-time drop total against an all-time event total
|
|
11
|
+
* gated on a one-hour recency check, so a single fresh drop re-armed every
|
|
12
|
+
* drop the site had ever recorded. Anything that can be tested without a
|
|
13
|
+
* database or a renderer belongs in one file that a test can import.
|
|
14
|
+
*/
|
|
15
|
+
/** Granularity drops are bucketed at. One hour. */
|
|
16
|
+
export declare const NO_SESSION_BUCKET_MS = 3600000;
|
|
17
|
+
/** How far back *both* sides of the comparison look. */
|
|
18
|
+
export declare const CAPTURE_WINDOW_MS: number;
|
|
19
|
+
/** One hour's worth of drops. */
|
|
20
|
+
export interface NoSessionBucket {
|
|
21
|
+
/** Hour index — `Math.floor(ms / NO_SESSION_BUCKET_MS)`. */
|
|
22
|
+
h: number;
|
|
23
|
+
/** Drops recorded in that hour. */
|
|
24
|
+
n: number;
|
|
25
|
+
}
|
|
26
|
+
/** What ingest stores under its `polishd_meta` key. */
|
|
27
|
+
export interface NoSessionRecord {
|
|
28
|
+
/** All-time drops. Never decays — a lifetime total, not an alert input. */
|
|
29
|
+
count: number;
|
|
30
|
+
/** Most recent drop (ms epoch). */
|
|
31
|
+
lastAt: number;
|
|
32
|
+
/** Per-hour drops, oldest first, trimmed to {@link MAX_BUCKETS}. */
|
|
33
|
+
buckets: NoSessionBucket[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Read a stored record, tolerating both junk and the pre-0.2.3 `{count,
|
|
37
|
+
* lastAt}` shape. A legacy record parses with no buckets, which is the honest
|
|
38
|
+
* answer: those drops happened, but nothing recorded *when*, so none of them
|
|
39
|
+
* can count toward a window. That is the whole point — history stops voting.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseNoSessionRecord(raw: string | null | undefined): NoSessionRecord | null;
|
|
42
|
+
/** Add `batch` drops observed at `now` to `prev` (null for a first drop). */
|
|
43
|
+
export declare function foldNoSessionDrop(prev: NoSessionRecord | null, now: number, batch: number): NoSessionRecord;
|
|
44
|
+
/**
|
|
45
|
+
* Drops recorded at or after `since`.
|
|
46
|
+
*
|
|
47
|
+
* Hour-granular, so the bucket straddling `since` contributes in full and the
|
|
48
|
+
* count leans high by up to an hour of drops. That bias is the safe direction
|
|
49
|
+
* for a diagnostic and it costs nothing to store.
|
|
50
|
+
*/
|
|
51
|
+
export declare function noSessionCountSince(rec: NoSessionRecord | null, since: number): number;
|
|
52
|
+
/** Whether capture itself is wired up, as opposed to what it captured. */
|
|
53
|
+
export interface CaptureHealth {
|
|
54
|
+
/** All-time beacons dropped for want of a session cookie. */
|
|
55
|
+
noSessionCount: number;
|
|
56
|
+
/** Of those, the ones inside {@link windowMs}. */
|
|
57
|
+
recentNoSessionCount: number;
|
|
58
|
+
/** When the most recent drop happened (ms epoch), or null for none. */
|
|
59
|
+
lastNoSessionAt: number | null;
|
|
60
|
+
/** When an event was last *stored*, by the server's clock, or null. */
|
|
61
|
+
lastStoredAt: number | null;
|
|
62
|
+
/** Events stored inside the window. */
|
|
63
|
+
recentEvents: number;
|
|
64
|
+
/** Batches stored inside the window (distinct arrival stamps — approximate). */
|
|
65
|
+
recentBatches: number;
|
|
66
|
+
/** The window both recent counts were measured over. */
|
|
67
|
+
windowMs: number;
|
|
68
|
+
}
|
|
69
|
+
export type CaptureStatusLevel = "ok" | "notice" | "broken";
|
|
70
|
+
export interface CaptureStatus {
|
|
71
|
+
/**
|
|
72
|
+
* `broken` — nothing is being stored, say so in red.
|
|
73
|
+
* `notice` — real capture plus some cookie-less batches; neutral and true.
|
|
74
|
+
* `ok` — nothing worth a word.
|
|
75
|
+
*/
|
|
76
|
+
level: CaptureStatusLevel;
|
|
77
|
+
/** Drops this status is talking about: the window's, or all-time when empty. */
|
|
78
|
+
droppedBatches: number;
|
|
79
|
+
/** Batches stored alongside them, over the same window. */
|
|
80
|
+
storedBatches: number;
|
|
81
|
+
/** Dropped share of all batches seen in the window, or null if unknowable. */
|
|
82
|
+
sharePct: number | null;
|
|
83
|
+
/** A store landed *after* the last drop — proof the proxy is minting now. */
|
|
84
|
+
storedSinceDrop: boolean;
|
|
85
|
+
/** Nothing has ever been stored. Gates the "nothing is being stored" copy. */
|
|
86
|
+
nothingStored: boolean;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Decide what, if anything, the dashboard should say about cookie-less
|
|
90
|
+
* batches.
|
|
91
|
+
*
|
|
92
|
+
* The load-bearing signal is `storedSinceDrop`. Every event is attributed to
|
|
93
|
+
* the cookie the proxy mints, so a proxy that isn't running drops *every*
|
|
94
|
+
* batch — one successful store after the most recent drop is proof it is
|
|
95
|
+
* running, and no threshold can argue with it. Everything else here only
|
|
96
|
+
* decides how loudly to describe traffic that is already known to be flowing.
|
|
97
|
+
*/
|
|
98
|
+
export declare function captureStatus(health: CaptureHealth, totalEvents: number, now?: number): CaptureStatus;
|
|
99
|
+
//# sourceMappingURL=capture-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-status.d.ts","sourceRoot":"","sources":["../../src/shared/capture-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,mDAAmD;AACnD,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAE9C,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,QAA4B,CAAC;AAoB3D,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,CAAC,EAAE,MAAM,CAAC;IACV,mCAAmC;IACnC,CAAC,EAAE,MAAM,CAAC;CACX;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,GAAG,IAAI,CAgB3F;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,eAAe,GAAG,IAAI,EAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GACZ,eAAe,CAYjB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAItF;AAED,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,uEAAuE;IACvE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uCAAuC;IACvC,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,KAAK,EAAE,kBAAkB,CAAC;IAC1B,gFAAgF;IAChF,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6EAA6E;IAC7E,eAAe,EAAE,OAAO,CAAC;IACzB,8EAA8E;IAC9E,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,EACnB,GAAG,GAAE,MAAmB,GACvB,aAAa,CAqCf"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — capture health (shared: no server, no React).
|
|
3
|
+
*
|
|
4
|
+
* Two things live here, and they are the same subject seen from both ends:
|
|
5
|
+
* the on-disk shape ingest writes when it drops a cookie-less batch, and the
|
|
6
|
+
* rule the dashboard applies to decide whether that means "your proxy isn't
|
|
7
|
+
* running" or "some bots posted to a public endpoint".
|
|
8
|
+
*
|
|
9
|
+
* They are together, and pure, because the rule is the part that has been
|
|
10
|
+
* wrong: it compared an all-time drop total against an all-time event total
|
|
11
|
+
* gated on a one-hour recency check, so a single fresh drop re-armed every
|
|
12
|
+
* drop the site had ever recorded. Anything that can be tested without a
|
|
13
|
+
* database or a renderer belongs in one file that a test can import.
|
|
14
|
+
*/
|
|
15
|
+
/** Granularity drops are bucketed at. One hour. */
|
|
16
|
+
export const NO_SESSION_BUCKET_MS = 3_600_000;
|
|
17
|
+
/** How far back *both* sides of the comparison look. */
|
|
18
|
+
export const CAPTURE_WINDOW_MS = 24 * NO_SESSION_BUCKET_MS;
|
|
19
|
+
/** Buckets retained: the window plus slack, so the oldest ages out cleanly. */
|
|
20
|
+
const MAX_BUCKETS = 26;
|
|
21
|
+
/**
|
|
22
|
+
* Drops in the window before a *neutral* note is worth showing. Below this,
|
|
23
|
+
* cookie-less batches are ordinary background on a public endpoint and saying
|
|
24
|
+
* so is clutter.
|
|
25
|
+
*/
|
|
26
|
+
const MIN_NOTICE_DROPS = 3;
|
|
27
|
+
/**
|
|
28
|
+
* Drops in the window before "nothing stored in the window" is allowed to
|
|
29
|
+
* read as a broken proxy. A real proxy failure drops *every* batch, so on any
|
|
30
|
+
* site with traffic the count climbs fast; the floor is what keeps a dormant
|
|
31
|
+
* site poked twice by a crawler from accusing a working install.
|
|
32
|
+
*/
|
|
33
|
+
const MIN_BROKEN_DROPS = 5;
|
|
34
|
+
/**
|
|
35
|
+
* Read a stored record, tolerating both junk and the pre-0.2.3 `{count,
|
|
36
|
+
* lastAt}` shape. A legacy record parses with no buckets, which is the honest
|
|
37
|
+
* answer: those drops happened, but nothing recorded *when*, so none of them
|
|
38
|
+
* can count toward a window. That is the whole point — history stops voting.
|
|
39
|
+
*/
|
|
40
|
+
export function parseNoSessionRecord(raw) {
|
|
41
|
+
if (!raw)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const rec = JSON.parse(raw);
|
|
45
|
+
if (!rec || typeof rec !== "object")
|
|
46
|
+
return null;
|
|
47
|
+
const count = numOr(rec.count, 0);
|
|
48
|
+
const lastAt = numOr(rec.lastAt, 0);
|
|
49
|
+
const buckets = Array.isArray(rec.buckets)
|
|
50
|
+
? rec.buckets
|
|
51
|
+
.filter((b) => !!b && Number.isFinite(b.h) && Number.isFinite(b.n))
|
|
52
|
+
.map((b) => ({ h: Math.floor(b.h), n: Math.max(0, Math.floor(b.n)) }))
|
|
53
|
+
: [];
|
|
54
|
+
return { count, lastAt, buckets };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Add `batch` drops observed at `now` to `prev` (null for a first drop). */
|
|
61
|
+
export function foldNoSessionDrop(prev, now, batch) {
|
|
62
|
+
const h = Math.floor(now / NO_SESSION_BUCKET_MS);
|
|
63
|
+
const buckets = (prev?.buckets ?? []).filter((b) => b.h > h - MAX_BUCKETS);
|
|
64
|
+
const current = buckets.find((b) => b.h === h);
|
|
65
|
+
if (current)
|
|
66
|
+
current.n += batch;
|
|
67
|
+
else
|
|
68
|
+
buckets.push({ h, n: batch });
|
|
69
|
+
buckets.sort((a, b) => a.h - b.h);
|
|
70
|
+
return {
|
|
71
|
+
count: (prev?.count ?? 0) + batch,
|
|
72
|
+
lastAt: Math.max(now, prev?.lastAt ?? 0),
|
|
73
|
+
buckets: buckets.slice(-MAX_BUCKETS),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Drops recorded at or after `since`.
|
|
78
|
+
*
|
|
79
|
+
* Hour-granular, so the bucket straddling `since` contributes in full and the
|
|
80
|
+
* count leans high by up to an hour of drops. That bias is the safe direction
|
|
81
|
+
* for a diagnostic and it costs nothing to store.
|
|
82
|
+
*/
|
|
83
|
+
export function noSessionCountSince(rec, since) {
|
|
84
|
+
if (!rec)
|
|
85
|
+
return 0;
|
|
86
|
+
const first = Math.floor(since / NO_SESSION_BUCKET_MS);
|
|
87
|
+
return rec.buckets.reduce((sum, b) => (b.h >= first ? sum + b.n : sum), 0);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Decide what, if anything, the dashboard should say about cookie-less
|
|
91
|
+
* batches.
|
|
92
|
+
*
|
|
93
|
+
* The load-bearing signal is `storedSinceDrop`. Every event is attributed to
|
|
94
|
+
* the cookie the proxy mints, so a proxy that isn't running drops *every*
|
|
95
|
+
* batch — one successful store after the most recent drop is proof it is
|
|
96
|
+
* running, and no threshold can argue with it. Everything else here only
|
|
97
|
+
* decides how loudly to describe traffic that is already known to be flowing.
|
|
98
|
+
*/
|
|
99
|
+
export function captureStatus(health, totalEvents, now = Date.now()) {
|
|
100
|
+
const storedSinceDrop = health.lastStoredAt !== null &&
|
|
101
|
+
health.lastNoSessionAt !== null &&
|
|
102
|
+
health.lastStoredAt > health.lastNoSessionAt;
|
|
103
|
+
const nothingStored = totalEvents === 0;
|
|
104
|
+
const dropsInWindow = health.lastNoSessionAt !== null &&
|
|
105
|
+
health.lastNoSessionAt >= now - health.windowMs &&
|
|
106
|
+
health.recentNoSessionCount > 0;
|
|
107
|
+
const droppedBatches = dropsInWindow ? health.recentNoSessionCount : health.noSessionCount;
|
|
108
|
+
const storedBatches = health.recentBatches;
|
|
109
|
+
const totalBatches = droppedBatches + storedBatches;
|
|
110
|
+
const sharePct = totalBatches > 0 ? (droppedBatches * 100) / totalBatches : null;
|
|
111
|
+
const base = { droppedBatches, storedBatches, sharePct, storedSinceDrop, nothingStored };
|
|
112
|
+
// Never stored anything, and beacons are arriving: the drop *is* the whole
|
|
113
|
+
// story, whenever it happened. This is the case the banner was written for.
|
|
114
|
+
if (health.lastNoSessionAt !== null && nothingStored)
|
|
115
|
+
return { ...base, level: "broken" };
|
|
116
|
+
// Storing has happened at some point, so judge only the window: drops in it,
|
|
117
|
+
// nothing stored in it, and no store since the last drop. Enough drops to
|
|
118
|
+
// rule out a dormant site being poked by a crawler.
|
|
119
|
+
if (dropsInWindow &&
|
|
120
|
+
!storedSinceDrop &&
|
|
121
|
+
health.recentEvents === 0 &&
|
|
122
|
+
health.recentNoSessionCount >= MIN_BROKEN_DROPS) {
|
|
123
|
+
return { ...base, level: "broken" };
|
|
124
|
+
}
|
|
125
|
+
if (dropsInWindow && health.recentNoSessionCount >= MIN_NOTICE_DROPS) {
|
|
126
|
+
return { ...base, level: "notice" };
|
|
127
|
+
}
|
|
128
|
+
return { ...base, level: "ok" };
|
|
129
|
+
}
|
|
130
|
+
function numOr(v, fallback) {
|
|
131
|
+
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=capture-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture-status.js","sourceRoot":"","sources":["../../src/shared/capture-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — browser-extension error noise.
|
|
3
|
+
*
|
|
4
|
+
* A page's `error` and `unhandledrejection` handlers hear everything that
|
|
5
|
+
* throws in the tab, not just the site's own code. Extensions inject scripts
|
|
6
|
+
* into the page context, and when they fail — a wallet that can't reach its
|
|
7
|
+
* background worker, a content script whose port closed on navigation — the
|
|
8
|
+
* throw surfaces on `window` exactly like a real bug would. "Failed to connect
|
|
9
|
+
* to MetaMask" is the canonical one: nothing on the site can cause it, nothing
|
|
10
|
+
* on the site can fix it, and left unfiltered it can be the loudest error the
|
|
11
|
+
* dashboard shows.
|
|
12
|
+
*
|
|
13
|
+
* These are pure predicates, shared rather than client-local, so the browser
|
|
14
|
+
* capture layer and the ingest endpoint apply the same rule — a cached bundle
|
|
15
|
+
* can lag a config change by a full session, so the server keeps its own copy
|
|
16
|
+
* of the decision (the same reasoning as the dashboard-path exclusion).
|
|
17
|
+
*/
|
|
18
|
+
/** True when `url` was served by an extension or the browser, not the site. */
|
|
19
|
+
export declare function isExtensionUrl(url: string): boolean;
|
|
20
|
+
/** The shape of an error, reduced to what both the client and ingest can see. */
|
|
21
|
+
export interface ErrorSignature {
|
|
22
|
+
/** The thrown message, already stringified. */
|
|
23
|
+
message: string;
|
|
24
|
+
/** The failing script's URL, if the browser gave one. */
|
|
25
|
+
source?: string;
|
|
26
|
+
/** The stack, when available. Only the client has this. */
|
|
27
|
+
stack?: string;
|
|
28
|
+
/** Extra ignore patterns from the host app's config. */
|
|
29
|
+
ignore?: readonly (string | RegExp)[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* True when this error is noise the site's owner cannot act on.
|
|
33
|
+
*
|
|
34
|
+
* Checked in order of confidence: where the script came from, then what the
|
|
35
|
+
* stack blames, then the small set of messages extension messaging emits, then
|
|
36
|
+
* the opaque cross-origin case, then whatever the host app chose to ignore.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isIgnorableError({ message, source, stack, ignore }: ErrorSignature): boolean;
|
|
39
|
+
//# sourceMappingURL=error-noise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-noise.d.ts","sourceRoot":"","sources":["../../src/shared/error-noise.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AA8CH,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CACvC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAW,EAAE,KAAU,EAAE,MAAM,EAAE,EAAE,cAAc,GAAG,OAAO,CAiBtG"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — browser-extension error noise.
|
|
3
|
+
*
|
|
4
|
+
* A page's `error` and `unhandledrejection` handlers hear everything that
|
|
5
|
+
* throws in the tab, not just the site's own code. Extensions inject scripts
|
|
6
|
+
* into the page context, and when they fail — a wallet that can't reach its
|
|
7
|
+
* background worker, a content script whose port closed on navigation — the
|
|
8
|
+
* throw surfaces on `window` exactly like a real bug would. "Failed to connect
|
|
9
|
+
* to MetaMask" is the canonical one: nothing on the site can cause it, nothing
|
|
10
|
+
* on the site can fix it, and left unfiltered it can be the loudest error the
|
|
11
|
+
* dashboard shows.
|
|
12
|
+
*
|
|
13
|
+
* These are pure predicates, shared rather than client-local, so the browser
|
|
14
|
+
* capture layer and the ingest endpoint apply the same rule — a cached bundle
|
|
15
|
+
* can lag a config change by a full session, so the server keeps its own copy
|
|
16
|
+
* of the decision (the same reasoning as the dashboard-path exclusion).
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* URL schemes that only ever belong to an extension or the browser itself.
|
|
20
|
+
* If the failing script came from one of these, the site did not load it.
|
|
21
|
+
*/
|
|
22
|
+
const EXTENSION_SCHEMES = [
|
|
23
|
+
"chrome-extension://",
|
|
24
|
+
"moz-extension://",
|
|
25
|
+
"safari-extension://",
|
|
26
|
+
"safari-web-extension://",
|
|
27
|
+
"ms-browser-extension://",
|
|
28
|
+
"extension://", // unknown vendors, and the bare form some builds report
|
|
29
|
+
"webkit-masked-url://", // Safari's opaque stand-in for injected scripts
|
|
30
|
+
"resource://", // Firefox internals and legacy add-ons
|
|
31
|
+
"chrome://", // browser-internal pages and resources
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Messages produced by extension plumbing running *in page context*, where the
|
|
35
|
+
* filename is the page's own URL and the scheme check above can't help.
|
|
36
|
+
*
|
|
37
|
+
* Deliberately narrow: each of these is a string the extension messaging layer
|
|
38
|
+
* itself emits, not something a site's code would produce. Broad matching here
|
|
39
|
+
* (say, anything mentioning MetaMask) would swallow a dapp's genuine wallet
|
|
40
|
+
* errors, which are real bugs worth seeing.
|
|
41
|
+
*/
|
|
42
|
+
const EXTENSION_MESSAGE_PATTERNS = [
|
|
43
|
+
/failed to connect to metamask/i,
|
|
44
|
+
/could not establish connection\.\s*receiving end does not exist/i,
|
|
45
|
+
/the message port closed before a response was received/i,
|
|
46
|
+
/extension context invalidated/i,
|
|
47
|
+
/^chrome\.runtime\b/i,
|
|
48
|
+
];
|
|
49
|
+
/**
|
|
50
|
+
* The browser's placeholder for an error thrown by a cross-origin script it
|
|
51
|
+
* won't describe: no message, no file, no line. Extensions and third-party
|
|
52
|
+
* embeds are the usual sources, and either way there is nothing here to act
|
|
53
|
+
* on — the event is a report that *something* failed, with every detail
|
|
54
|
+
* stripped.
|
|
55
|
+
*/
|
|
56
|
+
function isOpaqueError(message, source) {
|
|
57
|
+
return /^script error\.?$/i.test(message.trim()) && !source;
|
|
58
|
+
}
|
|
59
|
+
/** True when `url` was served by an extension or the browser, not the site. */
|
|
60
|
+
export function isExtensionUrl(url) {
|
|
61
|
+
// `blob:` and `filesystem:` wrap the origin that minted them, so an
|
|
62
|
+
// extension's blob reads as `blob:chrome-extension://…` — unwrap before
|
|
63
|
+
// testing the scheme, or code an extension generated at runtime slips past.
|
|
64
|
+
const u = url.trim().toLowerCase().replace(/^(?:blob|filesystem):/, "");
|
|
65
|
+
if (!u)
|
|
66
|
+
return false;
|
|
67
|
+
return EXTENSION_SCHEMES.some((scheme) => u.startsWith(scheme));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* True when this error is noise the site's owner cannot act on.
|
|
71
|
+
*
|
|
72
|
+
* Checked in order of confidence: where the script came from, then what the
|
|
73
|
+
* stack blames, then the small set of messages extension messaging emits, then
|
|
74
|
+
* the opaque cross-origin case, then whatever the host app chose to ignore.
|
|
75
|
+
*/
|
|
76
|
+
export function isIgnorableError({ message, source = "", stack = "", ignore }) {
|
|
77
|
+
if (isExtensionUrl(source))
|
|
78
|
+
return true;
|
|
79
|
+
// A stack is a list of frames, not a single URL, so this looks anywhere in
|
|
80
|
+
// it: injected code often has the page as its top frame and the extension
|
|
81
|
+
// further down.
|
|
82
|
+
const lowerStack = stack.toLowerCase();
|
|
83
|
+
if (lowerStack && EXTENSION_SCHEMES.some((scheme) => lowerStack.includes(scheme)))
|
|
84
|
+
return true;
|
|
85
|
+
if (EXTENSION_MESSAGE_PATTERNS.some((re) => re.test(message)))
|
|
86
|
+
return true;
|
|
87
|
+
if (isOpaqueError(message, source))
|
|
88
|
+
return true;
|
|
89
|
+
if (ignore?.length) {
|
|
90
|
+
for (const pattern of ignore) {
|
|
91
|
+
if (typeof pattern === "string") {
|
|
92
|
+
if (pattern && message.toLowerCase().includes(pattern.toLowerCase()))
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
else if (pattern.test(message))
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=error-noise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-noise.js","sourceRoot":"","sources":["../../src/shared/error-noise.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;GAGG;AACH,MAAM,iBAAiB,GAAG;IACxB,qBAAqB;IACrB,kBAAkB;IAClB,qBAAqB;IACrB,yBAAyB;IACzB,yBAAyB;IACzB,cAAc,EAAE,wDAAwD;IACxE,sBAAsB,EAAE,gDAAgD;IACxE,aAAa,EAAE,uCAAuC;IACtD,WAAW,EAAE,uCAAuC;CACrD,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,0BAA0B,GAAa;IAC3C,gCAAgC;IAChC,kEAAkE;IAClE,yDAAyD;IACzD,gCAAgC;IAChC,qBAAqB;CACtB,CAAC;AAEF;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,OAAe,EAAE,MAAc;IACpD,OAAO,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC;AAC9D,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,oEAAoE;IACpE,wEAAwE;IACxE,4EAA4E;IAC5E,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;IACxE,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AAClE,CAAC;AAcD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG,EAAE,EAAE,MAAM,EAAkB;IAC3F,IAAI,cAAc,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,2EAA2E;IAC3E,0EAA0E;IAC1E,gBAAgB;IAChB,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,UAAU,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/F,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3E,IAAI,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,MAAM,EAAE,MAAM,EAAE,CAAC;QACnB,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;YAC7B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,IAAI,OAAO,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;oBAAE,OAAO,IAAI,CAAC;YACpF,CAAC;iBAAM,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,OAAO,IAAI,CAAC;QAChD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* the host notes app.
|
|
11
11
|
*/
|
|
12
12
|
/** Every kind of signal we capture. Ordered roughly by synthesis value. */
|
|
13
|
-
export type PolishdEventType = "page_view" | "click" | "rage_click" | "dead_click" | "scroll_depth" | "viewport" | "js_error" | "web_vital" | "hover" | "component_view" | "mount" | "session_end" | "design_scan";
|
|
13
|
+
export type PolishdEventType = "page_view" | "click" | "rage_click" | "dead_click" | "scroll_depth" | "viewport" | "js_error" | "web_vital" | "hover" | "component_view" | "mount" | "session_end" | "design_scan" | "install_state";
|
|
14
14
|
/**
|
|
15
15
|
* A single captured event. The client sends these without a session id; the
|
|
16
16
|
* ingest layer attaches `session_id` server-side from the signed cookie, so a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GACxB,WAAW,GACX,OAAO,GACP,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,UAAU,GACV,UAAU,GACV,WAAW,GACX,OAAO,GACP,gBAAgB,GAChB,OAAO,GACP,aAAa,GACb,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GACxB,WAAW,GACX,OAAO,GACP,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,UAAU,GACV,UAAU,GACV,WAAW,GACX,OAAO,GACP,gBAAgB,GAChB,OAAO,GACP,aAAa,GACb,aAAa,GACb,eAAe,CAAC;AAEpB;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,gBAAgB,CAAC;IACvB,8EAA8E;IAC9E,EAAE,EAAE,MAAM,CAAC;IACX,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;CACzD;AAED,iDAAiD;AACjD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,8DAA8D;AAC9D,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,gBAAgB,CAkB3D,CAAC;AASH,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAC/E,mBAAmB;AACnB,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAChD,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;AACtE,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACjD,yBAAyB;AACzB,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE9C,MAAM,WAAW,wBAAwB;IACvC,8BAA8B;IAC9B,CAAC,EAAE,CAAC,CAAC;IACL,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,sDAAsD;IACtD,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACzB,kEAAkE;IAClE,EAAE,EAAE,gBAAgB,EAAE,CAAC;IACvB,mDAAmD;IACnD,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,uDAAuD;IACvD,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,gEAAgE;IAChE,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,kEAAkE;IAClE,GAAG,EAAE,cAAc,EAAE,CAAC;CACvB"}
|
package/dist/shared/types.js
CHANGED
|
@@ -24,5 +24,9 @@ export const CLIENT_EVENT_TYPES = new Set([
|
|
|
24
24
|
"mount",
|
|
25
25
|
"session_end",
|
|
26
26
|
"design_scan",
|
|
27
|
+
// Sent only by the dashboard telemetry emitter (once per session): whether
|
|
28
|
+
// the reporting install has a model connected, and which provider/model.
|
|
29
|
+
// Never carries a key or any secret — see dashboard/telemetry.tsx.
|
|
30
|
+
"install_state",
|
|
27
31
|
]);
|
|
28
32
|
//# sourceMappingURL=types.js.map
|
package/dist/shared/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAiFH,4DAA4D;AAC5D,MAAM,CAAC,MAAM,kBAAkB,GAAkC,IAAI,GAAG,CAAmB;IACzF,WAAW;IACX,OAAO;IACP,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,UAAU;IACV,UAAU;IACV,WAAW;IACX,OAAO;IACP,gBAAgB;IAChB,OAAO;IACP,aAAa;IACb,aAAa;IACb,2EAA2E;IAC3E,yEAAyE;IACzE,mEAAmE;IACnE,eAAe;CAChB,CAAC,CAAC"}
|
package/docs/SETUP.md
CHANGED
|
@@ -476,9 +476,34 @@ export default definePolishdConfig({
|
|
|
476
476
|
rageClick: { count: 3, windowMs: 500 }, // N clicks on one element
|
|
477
477
|
flushIntervalMs: 10_000,
|
|
478
478
|
dashboardRoute: "/polishd", // never captured; see below
|
|
479
|
+
ignoreErrors: [], // extra error messages to drop
|
|
479
480
|
});
|
|
480
481
|
```
|
|
481
482
|
|
|
483
|
+
Errors thrown by **browser extensions are dropped automatically**. Your page's
|
|
484
|
+
`error` handler hears every throw in the tab, not just your code's, so a wallet
|
|
485
|
+
extension failing to reach its background worker ("Failed to connect to
|
|
486
|
+
MetaMask") would otherwise land in your dashboard as one of your site's loudest
|
|
487
|
+
bugs.
|
|
488
|
+
|
|
489
|
+
This isn't a list of known extensions — it's worked out per error. The browser
|
|
490
|
+
records every script your document actually fetched (Resource Timing), and
|
|
491
|
+
extension code is never in it: the browser injects content scripts, your page
|
|
492
|
+
never asked for them. So an error is classified by where its stack points:
|
|
493
|
+
|
|
494
|
+
| Verdict | Meaning | What happens |
|
|
495
|
+
| --- | --- | --- |
|
|
496
|
+
| `site` | every frame is code you shipped — including third-party scripts your page loads, like Stripe | recorded normally |
|
|
497
|
+
| `extension` | a frame names an extension URL | dropped |
|
|
498
|
+
| `foreign` | a frame names a script your document never loaded | recorded, marked *not your code* |
|
|
499
|
+
| `unknown` | nothing attributable — an opaque cross-origin `Script error.`, an empty stack | recorded, marked *unattributable* |
|
|
500
|
+
|
|
501
|
+
Only `extension` is dropped, because only it is certain. The other two are
|
|
502
|
+
shown dimmed in Recent errors rather than hidden — a filter that quietly
|
|
503
|
+
discards what it can't identify is how a real bug goes missing. `ignoreErrors`
|
|
504
|
+
is the escape hatch if you want them gone anyway: strings match
|
|
505
|
+
case-insensitively anywhere in the message, regexes are tested as-is.
|
|
506
|
+
|
|
482
507
|
The dashboard's own traffic is **never captured**. Reading your analytics is
|
|
483
508
|
browsing too, and left alone it would rank "Refresh summary" as your site's
|
|
484
509
|
most-used feature. Nothing on `dashboardRoute` (or below it) is collected, and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polishd/next",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3-dev.0",
|
|
4
4
|
"description": "Drop-in product analytics for Next.js — capture real user behavioral signals (rage/dead clicks, scroll depth, errors, web vitals) and explore them on a built-in dashboard. What gets measured gets improved.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -75,6 +75,7 @@
|
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "tsc -p tsconfig.build.json && npm run build:css",
|
|
77
77
|
"typecheck": "tsc -p tsconfig.json",
|
|
78
|
+
"test": "node --experimental-strip-types --test \"src/**/*.test.ts\"",
|
|
78
79
|
"clean": "rm -rf dist",
|
|
79
80
|
"prepublishOnly": "npm run clean && npm run build",
|
|
80
81
|
"build:css": "node scripts/build-css.mjs"
|
|
@@ -84,6 +85,10 @@
|
|
|
84
85
|
"react": ">=18",
|
|
85
86
|
"react-dom": ">=18"
|
|
86
87
|
},
|
|
88
|
+
"dependencies": {
|
|
89
|
+
"allpages": "^0.3.3",
|
|
90
|
+
"playwright-core": "^1.62.1"
|
|
91
|
+
},
|
|
87
92
|
"optionalDependencies": {
|
|
88
93
|
"pg": "^8.0.0"
|
|
89
94
|
},
|