@polishd/next 0.2.2-dev.0 → 0.2.2-dev.2
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/CHANGELOG.md +44 -0
- package/DATABASE.md +9 -1
- package/README.md +32 -0
- package/dist/client/dom.d.ts +23 -0
- package/dist/client/dom.d.ts.map +1 -0
- package/dist/client/dom.js +71 -0
- package/dist/client/dom.js.map +1 -0
- package/dist/client/init.d.ts.map +1 -1
- package/dist/client/init.js +3 -62
- package/dist/client/init.js.map +1 -1
- package/dist/dashboard/chrome.d.ts +4 -2
- package/dist/dashboard/chrome.d.ts.map +1 -1
- package/dist/dashboard/chrome.js +9 -3
- package/dist/dashboard/chrome.js.map +1 -1
- package/dist/dashboard/index.d.ts +3 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +32 -13
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/installs.d.ts +6 -0
- package/dist/dashboard/installs.d.ts.map +1 -0
- package/dist/dashboard/installs.js +36 -0
- package/dist/dashboard/installs.js.map +1 -0
- package/dist/dashboard/telemetry-consent.d.ts +3 -0
- package/dist/dashboard/telemetry-consent.d.ts.map +1 -0
- package/dist/dashboard/telemetry-consent.js +20 -0
- package/dist/dashboard/telemetry-consent.js.map +1 -0
- package/dist/dashboard/telemetry.d.ts +20 -0
- package/dist/dashboard/telemetry.d.ts.map +1 -0
- package/dist/dashboard/telemetry.js +213 -0
- package/dist/dashboard/telemetry.js.map +1 -0
- package/dist/dashboard/ui.d.ts +1 -0
- package/dist/dashboard/ui.d.ts.map +1 -1
- package/dist/dashboard/ui.js +3 -0
- package/dist/dashboard/ui.js.map +1 -1
- package/dist/dashboard.css +24 -0
- package/dist/server/ingest.d.ts +33 -0
- package/dist/server/ingest.d.ts.map +1 -1
- package/dist/server/ingest.js +79 -0
- package/dist/server/ingest.js.map +1 -1
- package/dist/server/queries.d.ts +31 -0
- package/dist/server/queries.d.ts.map +1 -1
- package/dist/server/queries.js +68 -7
- package/dist/server/queries.js.map +1 -1
- package/dist/server/store.d.ts +6 -2
- package/dist/server/store.d.ts.map +1 -1
- package/dist/server/store.js +34 -13
- package/dist/server/store.js.map +1 -1
- package/dist/server/telemetry.d.ts +22 -0
- package/dist/server/telemetry.d.ts.map +1 -0
- package/dist/server/telemetry.js +60 -0
- package/dist/server/telemetry.js.map +1 -0
- package/dist/shared/types.d.ts +18 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/telemetry.d.ts +43 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +84 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — dogfood telemetry state (server only).
|
|
3
|
+
*
|
|
4
|
+
* Polishd can report anonymous usage of its *own dashboard* back to the
|
|
5
|
+
* polishd project, so the dashboard is improved by the same signals it
|
|
6
|
+
* preaches. This module owns everything about whether that happens:
|
|
7
|
+
*
|
|
8
|
+
* • Consent, stored in `polishd_meta` — asked once on the dashboard, opt-in,
|
|
9
|
+
* nothing is ever sent before the owner says yes.
|
|
10
|
+
* • The endpoint — the polishd site's `@polishd/next/telemetry` route,
|
|
11
|
+
* overridable for testing via `POLISHD_TELEMETRY_ENDPOINT`.
|
|
12
|
+
* • The kill switch — `POLISHD_TELEMETRY=off` (or 0/false/no) disables the
|
|
13
|
+
* whole feature, banner included, regardless of stored consent.
|
|
14
|
+
*
|
|
15
|
+
* The installation carries no id of its own: the collecting side names it by
|
|
16
|
+
* the hostname in the request's Origin header, which the browser sets and the
|
|
17
|
+
* consent prompt discloses. What gets sent is decided by the emitter
|
|
18
|
+
* (`dashboard/telemetry.tsx`), and is only ever dashboard interaction — never
|
|
19
|
+
* the host site's analytics, never its visitors' events.
|
|
20
|
+
*/
|
|
21
|
+
import { getMeta, setMeta, storeReady } from "./store";
|
|
22
|
+
/** Where telemetry lands: the polishd site's own installation. */
|
|
23
|
+
export const DEFAULT_TELEMETRY_ENDPOINT = "https://polishd.muskanvaswan.xyz/api/polishd-telemetry";
|
|
24
|
+
const CONSENT_KEY = "telemetry_consent";
|
|
25
|
+
export function telemetryDisabledByEnv() {
|
|
26
|
+
const v = process.env.POLISHD_TELEMETRY;
|
|
27
|
+
return v !== undefined && /^(0|false|off|no)$/i.test(v.trim());
|
|
28
|
+
}
|
|
29
|
+
export function resolveTelemetryEndpoint() {
|
|
30
|
+
return process.env.POLISHD_TELEMETRY_ENDPOINT || DEFAULT_TELEMETRY_ENDPOINT;
|
|
31
|
+
}
|
|
32
|
+
/** Record the owner's decision. Overwrites any previous one. */
|
|
33
|
+
export async function saveTelemetryConsent(granted) {
|
|
34
|
+
await setMeta(CONSENT_KEY, JSON.stringify({ status: granted ? "granted" : "denied", at: Date.now() }));
|
|
35
|
+
}
|
|
36
|
+
/** What the dashboard should do about telemetry on this render. */
|
|
37
|
+
export async function loadPolishdTelemetryState() {
|
|
38
|
+
const endpoint = resolveTelemetryEndpoint();
|
|
39
|
+
if (telemetryDisabledByEnv())
|
|
40
|
+
return { status: "disabled", endpoint };
|
|
41
|
+
// Without a writable store there is nowhere to keep the answer, so asking
|
|
42
|
+
// would mean asking on every single render. Stay quiet instead.
|
|
43
|
+
if (!(await storeReady()))
|
|
44
|
+
return { status: "disabled", endpoint };
|
|
45
|
+
const raw = await getMeta(CONSENT_KEY);
|
|
46
|
+
if (!raw)
|
|
47
|
+
return { status: "unset", endpoint };
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
if (parsed.status === "granted")
|
|
51
|
+
return { status: "granted", endpoint };
|
|
52
|
+
if (parsed.status === "denied")
|
|
53
|
+
return { status: "denied", endpoint };
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Unreadable record — treat as never asked rather than silently sending.
|
|
57
|
+
}
|
|
58
|
+
return { status: "unset", endpoint };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +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;AAEvD,kEAAkE;AAClE,MAAM,CAAC,MAAM,0BAA0B,GACrC,wDAAwD,CAAC;AAE3D,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAiBxC,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;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,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,yBAAyB;IAC7C,MAAM,QAAQ,GAAG,wBAAwB,EAAE,CAAC;IAC5C,IAAI,sBAAsB,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IACtE,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,CAAC,CAAC,MAAM,UAAU,EAAE,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC;IAEnE,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QACxE,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACvC,CAAC"}
|
package/dist/shared/types.d.ts
CHANGED
|
@@ -44,12 +44,30 @@ export interface PolishdIngestBody {
|
|
|
44
44
|
/** Page that produced this batch, for sanity/debugging. */
|
|
45
45
|
page?: string;
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* What a telemetry emitter POSTs to a `@polishd/next/telemetry` route.
|
|
49
|
+
*
|
|
50
|
+
* Unlike first-party ingest, the session id travels in the body: the session
|
|
51
|
+
* cookie is httpOnly and first-party, so a cross-origin POST arrives without
|
|
52
|
+
* it. The *installation's* identity does not travel in the body at all — the
|
|
53
|
+
* receiving route derives it from the request's `Origin` header (the
|
|
54
|
+
* emitter's hostname), which the browser sets and page script cannot forge.
|
|
55
|
+
*/
|
|
56
|
+
export interface PolishdTelemetryBody extends PolishdIngestBody {
|
|
57
|
+
/** Anonymous session id minted by the emitter (no cookie crosses origins). */
|
|
58
|
+
sessionId: string;
|
|
59
|
+
}
|
|
47
60
|
/** A stored event row, as it comes back from the database. */
|
|
48
61
|
export interface PolishdEventRow extends PolishdEvent {
|
|
49
62
|
id: number;
|
|
50
63
|
session_id: string;
|
|
51
64
|
/** Server receive time, ms since epoch — authoritative for retention. */
|
|
52
65
|
received_at: number;
|
|
66
|
+
/**
|
|
67
|
+
* Hostname of the installation that sent this event (from the request's
|
|
68
|
+
* Origin header). Only set on telemetry-ingested rows; null on first-party.
|
|
69
|
+
*/
|
|
70
|
+
install_id?: string;
|
|
53
71
|
}
|
|
54
72
|
/** The set of event types the client is allowed to send. */
|
|
55
73
|
export declare const CLIENT_EVENT_TYPES: ReadonlySet<PolishdEventType>;
|
|
@@ -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;AAElB;;;;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,8DAA8D;AAC9D,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,EAAE,WAAW,CAAC,gBAAgB,CAc3D,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"}
|
|
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;AAElB;;;;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,CAc3D,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.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;AAgFH,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;CACd,CAAC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @polishd/next/telemetry — the cross-install ingest endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Mount this in the app that *collects* telemetry from other polishd
|
|
5
|
+
* installations (for polishd itself, the landing site). It differs from the
|
|
6
|
+
* first-party `@polishd/next/route` in that it accepts POSTs from any origin:
|
|
7
|
+
* remote dashboards live on other people's domains, so the endpoint answers
|
|
8
|
+
* CORS preflights and takes identity from the request body instead of the
|
|
9
|
+
* first-party session cookie (see `ingestTelemetry` for the full contrast).
|
|
10
|
+
*
|
|
11
|
+
* Consumers wire it up with a one-line re-export:
|
|
12
|
+
*
|
|
13
|
+
* // src/app/api/polishd-telemetry/route.ts
|
|
14
|
+
* export { POST, OPTIONS, runtime, dynamic } from "@polishd/next/telemetry";
|
|
15
|
+
*
|
|
16
|
+
* Or build a configured handler:
|
|
17
|
+
*
|
|
18
|
+
* const { POST, OPTIONS } = createPolishdTelemetryRoute({
|
|
19
|
+
* allowedOrigins: ["https://example.com"],
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
23
|
+
import { type PolishdConfig } from "./config";
|
|
24
|
+
export declare const runtime = "nodejs";
|
|
25
|
+
export declare const dynamic = "force-dynamic";
|
|
26
|
+
export interface PolishdTelemetryRouteOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Origins allowed to POST telemetry. Omit to accept any origin — the right
|
|
29
|
+
* default here, since dashboards report in from domains that can't be known
|
|
30
|
+
* in advance. The endpoint never uses cookies or credentialed CORS, so a
|
|
31
|
+
* wildcard exposes nothing; an allowlist merely narrows who may write.
|
|
32
|
+
*/
|
|
33
|
+
allowedOrigins?: string[];
|
|
34
|
+
/** Host-app config overrides (only `enabled` matters to this route). */
|
|
35
|
+
config?: Partial<PolishdConfig>;
|
|
36
|
+
}
|
|
37
|
+
export declare function createPolishdTelemetryRoute(options?: PolishdTelemetryRouteOptions): {
|
|
38
|
+
POST: (req: NextRequest) => Promise<NextResponse<import("./server").IngestResult>>;
|
|
39
|
+
OPTIONS: (req: NextRequest) => Promise<NextResponse<unknown>>;
|
|
40
|
+
};
|
|
41
|
+
/** Default handlers — used by the one-line re-export. */
|
|
42
|
+
export declare const POST: (req: NextRequest) => Promise<NextResponse<import("./server").IngestResult>>, OPTIONS: (req: NextRequest) => Promise<NextResponse<unknown>>;
|
|
43
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAIpE,eAAO,MAAM,OAAO,WAAW,CAAC;AAEhC,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAEvC,MAAM,WAAW,4BAA4B;IAC3C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC;CACjC;AAmBD,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,4BAAiC;gBAmB3D,WAAW;mBAdR,WAAW;EAyCxC;AAED,yDAAyD;AACzD,eAAO,MAAQ,IAAI,QA9BQ,WAAW,6DA8BjB,OAAO,QA5CE,WAAW,mCA4CqB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @polishd/next/telemetry — the cross-install ingest endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Mount this in the app that *collects* telemetry from other polishd
|
|
5
|
+
* installations (for polishd itself, the landing site). It differs from the
|
|
6
|
+
* first-party `@polishd/next/route` in that it accepts POSTs from any origin:
|
|
7
|
+
* remote dashboards live on other people's domains, so the endpoint answers
|
|
8
|
+
* CORS preflights and takes identity from the request body instead of the
|
|
9
|
+
* first-party session cookie (see `ingestTelemetry` for the full contrast).
|
|
10
|
+
*
|
|
11
|
+
* Consumers wire it up with a one-line re-export:
|
|
12
|
+
*
|
|
13
|
+
* // src/app/api/polishd-telemetry/route.ts
|
|
14
|
+
* export { POST, OPTIONS, runtime, dynamic } from "@polishd/next/telemetry";
|
|
15
|
+
*
|
|
16
|
+
* Or build a configured handler:
|
|
17
|
+
*
|
|
18
|
+
* const { POST, OPTIONS } = createPolishdTelemetryRoute({
|
|
19
|
+
* allowedOrigins: ["https://example.com"],
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
import { NextResponse } from "next/server";
|
|
23
|
+
import { defaultPolishdConfig } from "./config";
|
|
24
|
+
import { ingestTelemetry, telemetryInstallId } from "./server/ingest";
|
|
25
|
+
// node:sqlite / pg need the Node runtime — never the Edge runtime.
|
|
26
|
+
export const runtime = "nodejs";
|
|
27
|
+
// This route mutates per-request; it must never be statically cached.
|
|
28
|
+
export const dynamic = "force-dynamic";
|
|
29
|
+
/**
|
|
30
|
+
* CORS response headers for this request, or null when the origin is
|
|
31
|
+
* disallowed. A request with no Origin header (same-origin, server-to-server)
|
|
32
|
+
* is outside CORS entirely and passes with no headers added.
|
|
33
|
+
*/
|
|
34
|
+
function corsHeaders(origin, allowedOrigins) {
|
|
35
|
+
if (!origin)
|
|
36
|
+
return {};
|
|
37
|
+
if (!allowedOrigins)
|
|
38
|
+
return { "Access-Control-Allow-Origin": "*" };
|
|
39
|
+
if (allowedOrigins.includes(origin)) {
|
|
40
|
+
return { "Access-Control-Allow-Origin": origin, Vary: "Origin" };
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
export function createPolishdTelemetryRoute(options = {}) {
|
|
45
|
+
const { allowedOrigins } = options;
|
|
46
|
+
const cfg = { ...defaultPolishdConfig, ...options.config };
|
|
47
|
+
/** Answers the browser's CORS preflight for the cross-origin POST. */
|
|
48
|
+
async function OPTIONS(req) {
|
|
49
|
+
const cors = corsHeaders(req.headers.get("origin"), allowedOrigins);
|
|
50
|
+
if (!cors)
|
|
51
|
+
return new NextResponse(null, { status: 403 });
|
|
52
|
+
return new NextResponse(null, {
|
|
53
|
+
status: 204,
|
|
54
|
+
headers: {
|
|
55
|
+
...cors,
|
|
56
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
57
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
58
|
+
"Access-Control-Max-Age": "86400",
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function POST(req) {
|
|
63
|
+
const cors = corsHeaders(req.headers.get("origin"), allowedOrigins);
|
|
64
|
+
if (!cors) {
|
|
65
|
+
return NextResponse.json({ ok: false, stored: 0, reason: "origin_not_allowed" }, { status: 403 });
|
|
66
|
+
}
|
|
67
|
+
let body;
|
|
68
|
+
try {
|
|
69
|
+
body = await req.json();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return NextResponse.json({ ok: false, stored: 0, reason: "bad_json" }, { status: 400, headers: cors });
|
|
73
|
+
}
|
|
74
|
+
// The reporting installation is named by where the request came from —
|
|
75
|
+
// the browser-set Origin header — not by anything the payload claims.
|
|
76
|
+
const install = telemetryInstallId(req.headers.get("origin"), req.headers.get("host"));
|
|
77
|
+
const result = await ingestTelemetry(body, install, cfg);
|
|
78
|
+
return NextResponse.json(result, { status: result.ok ? 200 : 400, headers: cors });
|
|
79
|
+
}
|
|
80
|
+
return { POST, OPTIONS };
|
|
81
|
+
}
|
|
82
|
+
/** Default handlers — used by the one-line re-export. */
|
|
83
|
+
export const { POST, OPTIONS } = createPolishdTelemetryRoute();
|
|
84
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polishd/next",
|
|
3
|
-
"version": "0.2.2-dev.
|
|
3
|
+
"version": "0.2.2-dev.2",
|
|
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",
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"types": "./dist/route.d.ts",
|
|
58
58
|
"default": "./dist/route.js"
|
|
59
59
|
},
|
|
60
|
+
"./telemetry": {
|
|
61
|
+
"types": "./dist/telemetry.d.ts",
|
|
62
|
+
"default": "./dist/telemetry.js"
|
|
63
|
+
},
|
|
60
64
|
"./proxy": {
|
|
61
65
|
"types": "./dist/proxy.d.ts",
|
|
62
66
|
"default": "./dist/proxy.js"
|