@polishd/next 0.2.2-dev.0 → 0.2.2-dev.1

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/DATABASE.md +8 -1
  3. package/README.md +25 -0
  4. package/dist/client/dom.d.ts +23 -0
  5. package/dist/client/dom.d.ts.map +1 -0
  6. package/dist/client/dom.js +71 -0
  7. package/dist/client/dom.js.map +1 -0
  8. package/dist/client/init.d.ts.map +1 -1
  9. package/dist/client/init.js +3 -62
  10. package/dist/client/init.js.map +1 -1
  11. package/dist/dashboard/index.d.ts.map +1 -1
  12. package/dist/dashboard/index.js +16 -10
  13. package/dist/dashboard/index.js.map +1 -1
  14. package/dist/dashboard/telemetry-consent.d.ts +3 -0
  15. package/dist/dashboard/telemetry-consent.d.ts.map +1 -0
  16. package/dist/dashboard/telemetry-consent.js +20 -0
  17. package/dist/dashboard/telemetry-consent.js.map +1 -0
  18. package/dist/dashboard/telemetry.d.ts +21 -0
  19. package/dist/dashboard/telemetry.d.ts.map +1 -0
  20. package/dist/dashboard/telemetry.js +212 -0
  21. package/dist/dashboard/telemetry.js.map +1 -0
  22. package/dist/dashboard.css +20 -0
  23. package/dist/server/ingest.d.ts +20 -0
  24. package/dist/server/ingest.d.ts.map +1 -1
  25. package/dist/server/ingest.js +49 -0
  26. package/dist/server/ingest.js.map +1 -1
  27. package/dist/server/store.d.ts +6 -2
  28. package/dist/server/store.d.ts.map +1 -1
  29. package/dist/server/store.js +34 -13
  30. package/dist/server/store.js.map +1 -1
  31. package/dist/server/telemetry.d.ts +28 -0
  32. package/dist/server/telemetry.d.ts.map +1 -0
  33. package/dist/server/telemetry.js +83 -0
  34. package/dist/server/telemetry.js.map +1 -0
  35. package/dist/shared/types.d.ts +16 -0
  36. package/dist/shared/types.d.ts.map +1 -1
  37. package/dist/shared/types.js.map +1 -1
  38. package/dist/telemetry.d.ts +43 -0
  39. package/dist/telemetry.d.ts.map +1 -0
  40. package/dist/telemetry.js +81 -0
  41. package/dist/telemetry.js.map +1 -0
  42. package/package.json +5 -1
@@ -0,0 +1,83 @@
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 install id — a random UUID minted on first grant, meaning nothing
11
+ * and stable per installation, so the collecting side can tell
12
+ * installations apart without identifying anyone.
13
+ * • The endpoint — the polishd site's `@polishd/next/telemetry` route,
14
+ * overridable for testing via `POLISHD_TELEMETRY_ENDPOINT`.
15
+ * • The kill switch — `POLISHD_TELEMETRY=off` (or 0/false/no) disables the
16
+ * whole feature, banner included, regardless of stored consent.
17
+ *
18
+ * What gets sent is decided by the emitter (`dashboard/telemetry.tsx`), and is
19
+ * only ever dashboard interaction — never the host site's analytics, never
20
+ * its visitors' events.
21
+ */
22
+ import { getMeta, setMeta, storeReady } from "./store";
23
+ /**
24
+ * Where telemetry lands: the polishd landing site's own installation. One
25
+ * constant on purpose — when the site moves to a real domain, this is the
26
+ * single line to change.
27
+ */
28
+ export const DEFAULT_TELEMETRY_ENDPOINT = "https://polishd-lac.vercel.app/api/polishd-telemetry";
29
+ const CONSENT_KEY = "telemetry_consent";
30
+ const INSTALL_ID_KEY = "install_id";
31
+ export function telemetryDisabledByEnv() {
32
+ const v = process.env.POLISHD_TELEMETRY;
33
+ return v !== undefined && /^(0|false|off|no)$/i.test(v.trim());
34
+ }
35
+ export function resolveTelemetryEndpoint() {
36
+ return process.env.POLISHD_TELEMETRY_ENDPOINT || DEFAULT_TELEMETRY_ENDPOINT;
37
+ }
38
+ /**
39
+ * The stable anonymous id of this installation, minted on first use. Returns
40
+ * null when the store can't persist it — an id that changes every restart
41
+ * would be worse than none, inflating the install count on the other end.
42
+ */
43
+ async function getInstallId() {
44
+ const existing = await getMeta(INSTALL_ID_KEY);
45
+ if (existing)
46
+ return existing;
47
+ const id = crypto.randomUUID();
48
+ return (await setMeta(INSTALL_ID_KEY, id)) ? id : null;
49
+ }
50
+ /** Record the owner's decision. Overwrites any previous one. */
51
+ export async function saveTelemetryConsent(granted) {
52
+ await setMeta(CONSENT_KEY, JSON.stringify({ status: granted ? "granted" : "denied", at: Date.now() }));
53
+ }
54
+ /** What the dashboard should do about telemetry on this render. */
55
+ export async function loadPolishdTelemetryState() {
56
+ const endpoint = resolveTelemetryEndpoint();
57
+ const off = { status: "disabled", installId: null, endpoint };
58
+ if (telemetryDisabledByEnv())
59
+ return off;
60
+ // Without a writable store there is nowhere to keep the answer, so asking
61
+ // would mean asking on every single render. Stay quiet instead.
62
+ if (!(await storeReady()))
63
+ return off;
64
+ const raw = await getMeta(CONSENT_KEY);
65
+ if (!raw)
66
+ return { status: "unset", installId: null, endpoint };
67
+ try {
68
+ const parsed = JSON.parse(raw);
69
+ if (parsed.status === "granted") {
70
+ const installId = await getInstallId();
71
+ return installId
72
+ ? { status: "granted", installId, endpoint }
73
+ : off;
74
+ }
75
+ if (parsed.status === "denied")
76
+ return { status: "denied", installId: null, endpoint };
77
+ }
78
+ catch {
79
+ // Unreadable record — treat as never asked rather than silently sending.
80
+ }
81
+ return { status: "unset", installId: null, endpoint };
82
+ }
83
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/server/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GACrC,sDAAsD,CAAC;AAEzD,MAAM,WAAW,GAAG,mBAAmB,CAAC;AACxC,MAAM,cAAc,GAAG,YAAY,CAAC;AAmBpC,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;;;;GAIG;AACH,KAAK,UAAU,YAAY;IACzB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IAC/B,OAAO,CAAC,MAAM,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,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,MAAM,GAAG,GAA0B,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACrF,IAAI,sBAAsB,EAAE;QAAE,OAAO,GAAG,CAAC;IACzC,0EAA0E;IAC1E,gEAAgE;IAChE,IAAI,CAAC,CAAC,MAAM,UAAU,EAAE,CAAC;QAAE,OAAO,GAAG,CAAC;IAEtC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,SAAS,GAAG,MAAM,YAAY,EAAE,CAAC;YACvC,OAAO,SAAS;gBACd,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE;gBAC5C,CAAC,CAAC,GAAG,CAAC;QACV,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACxD,CAAC"}
@@ -44,12 +44,28 @@ 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, identity travels in the body: the session cookie
51
+ * is httpOnly and first-party, so a cross-origin POST arrives without it. Both
52
+ * ids are anonymous and client-minted; the receiving end validates their shape
53
+ * but has no way (and no need) to tie them to anything.
54
+ */
55
+ export interface PolishdTelemetryBody extends PolishdIngestBody {
56
+ /** Stable, anonymous id of the installation the events came from. */
57
+ installId: string;
58
+ /** Anonymous session id minted by the emitter (no cookie crosses origins). */
59
+ sessionId: string;
60
+ }
47
61
  /** A stored event row, as it comes back from the database. */
48
62
  export interface PolishdEventRow extends PolishdEvent {
49
63
  id: number;
50
64
  session_id: string;
51
65
  /** Server receive time, ms since epoch — authoritative for retention. */
52
66
  received_at: number;
67
+ /** Which installation sent this event. Only set on telemetry-ingested rows. */
68
+ install_id?: string;
53
69
  }
54
70
  /** The set of event types the client is allowed to send. */
55
71
  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;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,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,+EAA+E;IAC/E,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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA6DH,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"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shared/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8EH,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;EAsCxC;AAED,yDAAyD;AACzD,eAAO,MAAQ,IAAI,QA3BQ,WAAW,6DA2BjB,OAAO,QAzCE,WAAW,mCAyCqB,CAAC"}
@@ -0,0 +1,81 @@
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 } 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
+ const result = await ingestTelemetry(body, cfg);
75
+ return NextResponse.json(result, { status: result.ok ? 200 : 400, headers: cors });
76
+ }
77
+ return { POST, OPTIONS };
78
+ }
79
+ /** Default handlers — used by the one-line re-export. */
80
+ export const { POST, OPTIONS } = createPolishdTelemetryRoute();
81
+ //# 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.0",
3
+ "version": "0.2.2-dev.1",
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"