@uptimizr/collector-server 0.1.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.
Files changed (67) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +169 -0
  3. package/dist/app.d.ts +22 -0
  4. package/dist/app.d.ts.map +1 -0
  5. package/dist/app.js +96 -0
  6. package/dist/app.js.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +148 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +59 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +55 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/csp.d.ts +18 -0
  16. package/dist/csp.d.ts.map +1 -0
  17. package/dist/csp.js +91 -0
  18. package/dist/csp.js.map +1 -0
  19. package/dist/duckdbStore.d.ts +14 -0
  20. package/dist/duckdbStore.d.ts.map +1 -0
  21. package/dist/duckdbStore.js +69 -0
  22. package/dist/duckdbStore.js.map +1 -0
  23. package/dist/enrich.d.ts +8 -0
  24. package/dist/enrich.d.ts.map +1 -0
  25. package/dist/enrich.js +9 -0
  26. package/dist/enrich.js.map +1 -0
  27. package/dist/liveBus.d.ts +86 -0
  28. package/dist/liveBus.d.ts.map +1 -0
  29. package/dist/liveBus.js +195 -0
  30. package/dist/liveBus.js.map +1 -0
  31. package/dist/liveToken.d.ts +11 -0
  32. package/dist/liveToken.d.ts.map +1 -0
  33. package/dist/liveToken.js +44 -0
  34. package/dist/liveToken.js.map +1 -0
  35. package/dist/memoryStore.d.ts +22 -0
  36. package/dist/memoryStore.d.ts.map +1 -0
  37. package/dist/memoryStore.js +207 -0
  38. package/dist/memoryStore.js.map +1 -0
  39. package/dist/routes/collect.d.ts +25 -0
  40. package/dist/routes/collect.d.ts.map +1 -0
  41. package/dist/routes/collect.js +55 -0
  42. package/dist/routes/collect.js.map +1 -0
  43. package/dist/routes/live.d.ts +18 -0
  44. package/dist/routes/live.d.ts.map +1 -0
  45. package/dist/routes/live.js +179 -0
  46. package/dist/routes/live.js.map +1 -0
  47. package/dist/routes/query.d.ts +14 -0
  48. package/dist/routes/query.d.ts.map +1 -0
  49. package/dist/routes/query.js +610 -0
  50. package/dist/routes/query.js.map +1 -0
  51. package/dist/serve.d.ts +18 -0
  52. package/dist/serve.d.ts.map +1 -0
  53. package/dist/serve.js +48 -0
  54. package/dist/serve.js.map +1 -0
  55. package/dist/server.d.ts +3 -0
  56. package/dist/server.d.ts.map +1 -0
  57. package/dist/server.js +12 -0
  58. package/dist/server.js.map +1 -0
  59. package/dist/store.d.ts +263 -0
  60. package/dist/store.d.ts.map +1 -0
  61. package/dist/store.js +2 -0
  62. package/dist/store.js.map +1 -0
  63. package/dist/visitor.d.ts +12 -0
  64. package/dist/visitor.d.ts.map +1 -0
  65. package/dist/visitor.js +18 -0
  66. package/dist/visitor.js.map +1 -0
  67. package/package.json +64 -0
@@ -0,0 +1,195 @@
1
+ import { DEFAULT_SCENE_ID } from "@uptimizr/schema";
2
+ const SUBKEY = "\u0000";
3
+ const key = (projectId, sessionId) => `${projectId}${SUBKEY}${sessionId}`;
4
+ class Subscriber {
5
+ queueLimit;
6
+ onClose;
7
+ queue = [];
8
+ waiting = null;
9
+ closed = false;
10
+ dropped = 0;
11
+ constructor(queueLimit, onClose) {
12
+ this.queueLimit = queueLimit;
13
+ this.onClose = onClose;
14
+ }
15
+ /** Internal: enqueue an event, dropping the oldest when full (ADR §6). */
16
+ push(event) {
17
+ if (this.closed)
18
+ return;
19
+ if (this.waiting) {
20
+ const resolve = this.waiting;
21
+ this.waiting = null;
22
+ resolve({ value: event, done: false });
23
+ return;
24
+ }
25
+ this.queue.push(event);
26
+ if (this.queue.length > this.queueLimit) {
27
+ this.queue.shift();
28
+ this.dropped += 1;
29
+ }
30
+ }
31
+ close() {
32
+ if (this.closed)
33
+ return;
34
+ this.closed = true;
35
+ if (this.waiting) {
36
+ const resolve = this.waiting;
37
+ this.waiting = null;
38
+ resolve({ value: undefined, done: true });
39
+ }
40
+ this.onClose(this);
41
+ }
42
+ [Symbol.asyncIterator]() {
43
+ return {
44
+ next: () => {
45
+ const next = this.queue.shift();
46
+ if (next !== undefined)
47
+ return Promise.resolve({ value: next, done: false });
48
+ if (this.closed)
49
+ return Promise.resolve({ value: undefined, done: true });
50
+ return new Promise((resolve) => {
51
+ this.waiting = resolve;
52
+ });
53
+ },
54
+ return: () => {
55
+ this.close();
56
+ return Promise.resolve({ value: undefined, done: true });
57
+ },
58
+ };
59
+ }
60
+ }
61
+ /** Create the default in-process live bus (ADR 0032 §2). */
62
+ export function createLiveBus(options = {}) {
63
+ const windowMs = options.windowMs ?? 30_000;
64
+ const backfillRingSize = options.backfillRingSize ?? 200;
65
+ const defaultQueueLimit = options.subscriberQueueLimit ?? 1_000;
66
+ const now = options.now ?? Date.now;
67
+ /** presenceKey -> entry */
68
+ const presence = new Map();
69
+ /** presenceKey -> bounded ring of recent events */
70
+ const rings = new Map();
71
+ const subscribers = new Set();
72
+ function isLive(entry, at) {
73
+ return at - entry.lastSeen <= windowMs;
74
+ }
75
+ function prune(at) {
76
+ for (const [k, entry] of presence) {
77
+ if (!isLive(entry, at)) {
78
+ presence.delete(k);
79
+ rings.delete(k);
80
+ }
81
+ }
82
+ }
83
+ function activityOf(lastSeen, at) {
84
+ const age = at - lastSeen;
85
+ if (age <= 3_000)
86
+ return "active";
87
+ if (age <= 15_000)
88
+ return "recent";
89
+ return "idle";
90
+ }
91
+ function track(event, at) {
92
+ const sessionId = event.sessionId;
93
+ const k = key(event.projectId, sessionId);
94
+ if (event.type === "session_end") {
95
+ presence.delete(k);
96
+ rings.delete(k);
97
+ return;
98
+ }
99
+ const existing = presence.get(k);
100
+ if (existing) {
101
+ existing.lastSeen = at;
102
+ if (event.sceneId)
103
+ existing.sceneId = event.sceneId;
104
+ if (event.visitorId)
105
+ existing.visitorId = event.visitorId;
106
+ }
107
+ else {
108
+ presence.set(k, {
109
+ projectId: event.projectId,
110
+ sessionId,
111
+ visitorId: event.visitorId ?? sessionId,
112
+ sceneId: event.sceneId ?? DEFAULT_SCENE_ID,
113
+ startedAt: at,
114
+ lastSeen: at,
115
+ });
116
+ }
117
+ const ring = rings.get(k);
118
+ if (ring) {
119
+ ring.push(event);
120
+ if (ring.length > backfillRingSize)
121
+ ring.shift();
122
+ }
123
+ else {
124
+ rings.set(k, [event]);
125
+ }
126
+ }
127
+ function matches(sub, event) {
128
+ if (event.projectId !== sub.projectId)
129
+ return false;
130
+ if (sub.sessionId && event.sessionId !== sub.sessionId)
131
+ return false;
132
+ if (sub.types && !sub.types.has(event.type))
133
+ return false;
134
+ return true;
135
+ }
136
+ const optionsBySub = new WeakMap();
137
+ return {
138
+ publish(events) {
139
+ if (events.length === 0)
140
+ return;
141
+ const at = now();
142
+ for (const event of events) {
143
+ track(event, at);
144
+ for (const sub of subscribers) {
145
+ const opts = optionsBySub.get(sub);
146
+ if (opts && matches(opts, event))
147
+ sub.push(event);
148
+ }
149
+ }
150
+ },
151
+ presence(projectId) {
152
+ const at = now();
153
+ prune(at);
154
+ const visitors = new Set();
155
+ const sessions = [];
156
+ for (const entry of presence.values()) {
157
+ if (entry.projectId !== projectId)
158
+ continue;
159
+ visitors.add(entry.visitorId);
160
+ sessions.push({
161
+ sessionId: entry.sessionId,
162
+ sceneId: entry.sceneId,
163
+ startedAt: entry.startedAt,
164
+ lastSeen: entry.lastSeen,
165
+ activity: activityOf(entry.lastSeen, at),
166
+ });
167
+ }
168
+ sessions.sort((a, b) => b.lastSeen - a.lastSeen);
169
+ return { activeSessions: sessions.length, activeVisitors: visitors.size, sessions };
170
+ },
171
+ recentForSession(projectId, sessionId) {
172
+ const ring = rings.get(key(projectId, sessionId));
173
+ return ring ? [...ring] : [];
174
+ },
175
+ subscribe(subOptions) {
176
+ const sub = new Subscriber(subOptions.queueLimit ?? defaultQueueLimit, (s) => {
177
+ subscribers.delete(s);
178
+ optionsBySub.delete(s);
179
+ });
180
+ optionsBySub.set(sub, subOptions);
181
+ subscribers.add(sub);
182
+ return sub;
183
+ },
184
+ get subscriberCount() {
185
+ return subscribers.size;
186
+ },
187
+ stop() {
188
+ for (const sub of [...subscribers])
189
+ sub.close();
190
+ presence.clear();
191
+ rings.clear();
192
+ },
193
+ };
194
+ }
195
+ //# sourceMappingURL=liveBus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveBus.js","sourceRoot":"","sources":["../src/liveBus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /** Mint a token authorizing `projectId` until `now + ttlMs`. */
2
+ export declare function mintLiveToken(projectId: string, secret: string, ttlMs: number, now?: number): {
3
+ token: string;
4
+ expiresAt: number;
5
+ };
6
+ /**
7
+ * Verify a token and return its project id, or `null` if malformed, tampered,
8
+ * or expired. Uses a constant-time signature comparison.
9
+ */
10
+ export declare function verifyLiveToken(token: string, secret: string, now?: number): string | null;
11
+ //# sourceMappingURL=liveToken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveToken.d.ts","sourceRoot":"","sources":["../src/liveToken.ts"],"names":[],"mappings":"AA6BA,gEAAgE;AAChE,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,MAAmB,GACvB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAMtC;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,MAAmB,GACvB,MAAM,GAAG,IAAI,CAoBf"}
@@ -0,0 +1,44 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
2
+ function b64url(input) {
3
+ return Buffer.from(input).toString("base64url");
4
+ }
5
+ function sign(payloadB64, secret) {
6
+ return createHmac("sha256", secret).update(payloadB64).digest("base64url");
7
+ }
8
+ /** Mint a token authorizing `projectId` until `now + ttlMs`. */
9
+ export function mintLiveToken(projectId, secret, ttlMs, now = Date.now()) {
10
+ const expiresAt = now + ttlMs;
11
+ const payload = { p: projectId, e: expiresAt };
12
+ const payloadB64 = b64url(JSON.stringify(payload));
13
+ const token = `${payloadB64}.${sign(payloadB64, secret)}`;
14
+ return { token, expiresAt };
15
+ }
16
+ /**
17
+ * Verify a token and return its project id, or `null` if malformed, tampered,
18
+ * or expired. Uses a constant-time signature comparison.
19
+ */
20
+ export function verifyLiveToken(token, secret, now = Date.now()) {
21
+ const dot = token.indexOf(".");
22
+ if (dot <= 0 || dot === token.length - 1)
23
+ return null;
24
+ const payloadB64 = token.slice(0, dot);
25
+ const sig = token.slice(dot + 1);
26
+ const expected = sign(payloadB64, secret);
27
+ const sigBuf = Buffer.from(sig);
28
+ const expBuf = Buffer.from(expected);
29
+ if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf))
30
+ return null;
31
+ let payload;
32
+ try {
33
+ payload = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ if (typeof payload.p !== "string" || typeof payload.e !== "number")
39
+ return null;
40
+ if (now >= payload.e)
41
+ return null;
42
+ return payload.p;
43
+ }
44
+ //# sourceMappingURL=liveToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"liveToken.js","sourceRoot":"","sources":["../src/liveToken.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAqB1D,SAAS,MAAM,CAAC,KAAsB;IACpC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,IAAI,CAAC,UAAkB,EAAE,MAAc;IAC9C,OAAO,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC7E,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAC3B,SAAiB,EACjB,MAAc,EACd,KAAa,EACb,MAAc,IAAI,CAAC,GAAG,EAAE;IAExB,MAAM,SAAS,GAAG,GAAG,GAAG,KAAK,CAAC;IAC9B,MAAM,OAAO,GAAiB,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;IAC7D,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,GAAG,UAAU,IAAI,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC;IAC1D,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAa,EACb,MAAc,EACd,MAAc,IAAI,CAAC,GAAG,EAAE;IAExB,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAErF,IAAI,OAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAiB,CAAC;IAC9F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,OAAO,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAChF,IAAI,GAAG,IAAI,OAAO,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,OAAO,CAAC,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { ApiKeyCapability } from "@uptimizr/db";
2
+ import type { CollectorStore } from "./store.js";
3
+ export interface MemoryStoreOptions {
4
+ /** The single project id this store serves. */
5
+ projectId: string;
6
+ /** The plaintext API key that resolves to {@link projectId}. */
7
+ apiKey: string;
8
+ /** Capability the {@link apiKey} resolves with. Defaults to `query` (reads). */
9
+ capability?: ApiKeyCapability;
10
+ }
11
+ /**
12
+ * In-memory {@link CollectorStore} for local development and end-to-end tests —
13
+ * it boots the collector without ClickHouse or Postgres. Events are kept in a
14
+ * plain array; the replay-relevant reads (session list, ordered timeline, coarse
15
+ * meta) and the lightweight aggregates (scenes, time-series, event-type counts)
16
+ * are served from it so the playground's filters and timeline work. The
17
+ * heavier spatial aggregates (heatmaps, perf, top meshes) are intentionally not
18
+ * implemented here and return empty results; use the ClickHouse-backed store for
19
+ * those. Never use this in production.
20
+ */
21
+ export declare function createMemoryStore({ projectId, apiKey, capability, }: MemoryStoreOptions): CollectorStore;
22
+ //# sourceMappingURL=memoryStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoryStore.d.ts","sourceRoot":"","sources":["../src/memoryStore.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAoC,MAAM,cAAc,CAAC;AACvF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAgBjD,MAAM,WAAW,kBAAkB;IACjC,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,MAAM,EACN,UAAoB,GACrB,EAAE,kBAAkB,GAAG,cAAc,CAgMrC"}
@@ -0,0 +1,207 @@
1
+ function isSessionStart(event) {
2
+ return event.type === "session_start";
3
+ }
4
+ /**
5
+ * In-memory {@link CollectorStore} for local development and end-to-end tests —
6
+ * it boots the collector without ClickHouse or Postgres. Events are kept in a
7
+ * plain array; the replay-relevant reads (session list, ordered timeline, coarse
8
+ * meta) and the lightweight aggregates (scenes, time-series, event-type counts)
9
+ * are served from it so the playground's filters and timeline work. The
10
+ * heavier spatial aggregates (heatmaps, perf, top meshes) are intentionally not
11
+ * implemented here and return empty results; use the ClickHouse-backed store for
12
+ * those. Never use this in production.
13
+ */
14
+ export function createMemoryStore({ projectId, apiKey, capability = "query", }) {
15
+ const events = [];
16
+ const representations = new Map();
17
+ const forSession = (sid) => events
18
+ .filter((e) => e.projectId === projectId && e.sessionId === sid)
19
+ .sort((a, b) => a.ts - b.ts);
20
+ const forProject = () => events.filter((e) => e.projectId === projectId);
21
+ const sceneOf = (e) => {
22
+ const s = e.sceneId;
23
+ return typeof s === "string" && s.length > 0 ? s : "default";
24
+ };
25
+ const inRange = (e, opts) => (opts.since == null || e.ts >= opts.since) && (opts.until == null || e.ts < opts.until);
26
+ return {
27
+ resolveApiKey: async (key) => (key === apiKey ? { projectId, capability } : null),
28
+ projectExists: async (id) => id === projectId,
29
+ insertEvents: async (incoming) => {
30
+ events.push(...incoming);
31
+ },
32
+ listSessions: async () => {
33
+ const bySession = new Map();
34
+ for (const e of events) {
35
+ if (e.projectId !== projectId)
36
+ continue;
37
+ const list = bySession.get(e.sessionId) ?? [];
38
+ list.push(e);
39
+ bySession.set(e.sessionId, list);
40
+ }
41
+ return [...bySession.entries()].map(([sessionId, list]) => {
42
+ const sorted = [...list].sort((a, b) => a.ts - b.ts);
43
+ const first = sorted[0];
44
+ const last = sorted[sorted.length - 1];
45
+ const visitor = sorted.find((e) => typeof e.visitorId === "string");
46
+ return {
47
+ session_id: sessionId,
48
+ visitor_id: visitor?.visitorId ?? "",
49
+ events: sorted.length,
50
+ started_at: new Date(first.ts).toISOString(),
51
+ ended_at: new Date(last.ts).toISOString(),
52
+ };
53
+ });
54
+ },
55
+ pointerHeatmap: async () => [],
56
+ worldHeatmap: async () => [],
57
+ gazeHeatmap: async () => [],
58
+ cameraHeatmap: async () => [],
59
+ cameraPositionHeatmap: async () => [],
60
+ sessionTrajectory: async () => [],
61
+ clickGazeRays: async () => [],
62
+ flowHeatmap: async () => [],
63
+ topMeshes: async () => [],
64
+ meshDwell: async () => [],
65
+ deadClicks: async () => [],
66
+ rageClicks: async () => [],
67
+ hoverDwell: async () => [],
68
+ compileStalls: async () => [],
69
+ resourceSummary: async () => [],
70
+ capabilityChanges: async () => [],
71
+ cameraGestures: async () => [],
72
+ perfSummary: async () => [],
73
+ perfDistribution: async () => [],
74
+ fpsHistogram: async () => [],
75
+ frameTimePercentiles: async () => [],
76
+ jankRate: async () => [],
77
+ perfByDevice: async () => [],
78
+ perfByScene: async () => [],
79
+ resourcePercentiles: async () => [],
80
+ stabilityCounts: async () => [],
81
+ sceneCoverage: async () => [],
82
+ cameraDistance: async () => [],
83
+ navigationStats: async () => [],
84
+ xrRotationRate: async () => [],
85
+ xrSourceUsage: async () => [],
86
+ xrAbandonment: async () => [],
87
+ interactionsBySource: async () => [],
88
+ scenes: async (_projectId, opts = {}) => {
89
+ const map = new Map();
90
+ for (const e of forProject()) {
91
+ if (!inRange(e, opts))
92
+ continue;
93
+ const sid = sceneOf(e);
94
+ const cur = map.get(sid) ?? { events: 0, last: 0 };
95
+ cur.events += 1;
96
+ cur.last = Math.max(cur.last, e.ts);
97
+ map.set(sid, cur);
98
+ }
99
+ return [...map.entries()]
100
+ .map(([scene_id, v]) => ({
101
+ scene_id,
102
+ events: v.events,
103
+ last_seen: new Date(v.last).toISOString(),
104
+ }))
105
+ .sort((a, b) => b.events - a.events)
106
+ .slice(0, opts.limit ?? 200);
107
+ },
108
+ timeseries: async (_projectId, opts = {}) => {
109
+ const interval = (opts.interval ?? 3600) * 1000;
110
+ const buckets = new Map();
111
+ for (const e of forProject()) {
112
+ if (!inRange(e, opts))
113
+ continue;
114
+ if (opts.scene != null && opts.scene.length > 0 && sceneOf(e) !== opts.scene)
115
+ continue;
116
+ if (opts.type != null && opts.type.length > 0 && e.type !== opts.type)
117
+ continue;
118
+ const bucket = Math.floor(e.ts / interval) * interval;
119
+ const cur = buckets.get(bucket) ?? { events: 0, fpsSum: 0, fpsCount: 0 };
120
+ cur.events += 1;
121
+ if (e.type === "frame_perf") {
122
+ const fps = e.fps;
123
+ if (typeof fps === "number") {
124
+ cur.fpsSum += fps;
125
+ cur.fpsCount += 1;
126
+ }
127
+ }
128
+ buckets.set(bucket, cur);
129
+ }
130
+ return [...buckets.entries()]
131
+ .map(([bucket, v]) => ({
132
+ bucket,
133
+ events: v.events,
134
+ avg_fps: v.fpsCount > 0 ? v.fpsSum / v.fpsCount : 0,
135
+ }))
136
+ .sort((a, b) => a.bucket - b.bucket);
137
+ },
138
+ eventTypeCounts: async (_projectId, opts = {}) => {
139
+ const map = new Map();
140
+ for (const e of forProject()) {
141
+ if (!inRange(e, opts))
142
+ continue;
143
+ if (opts.scene != null && opts.scene.length > 0 && sceneOf(e) !== opts.scene)
144
+ continue;
145
+ map.set(e.type, (map.get(e.type) ?? 0) + 1);
146
+ }
147
+ return [...map.entries()]
148
+ .map(([event_type, count]) => ({ event_type, count }))
149
+ .sort((a, b) => b.count - a.count);
150
+ },
151
+ getSessionEvents: async (_projectId, sessionId) => forSession(sessionId),
152
+ streamSessionEvents: async function* (_projectId, sessionId) {
153
+ for (const e of forSession(sessionId))
154
+ yield e;
155
+ },
156
+ getSessionMeta: async (_projectId, sessionId) => {
157
+ const start = forSession(sessionId).find(isSessionStart);
158
+ if (!start)
159
+ return null;
160
+ return {
161
+ sessionId,
162
+ startedAt: new Date(start.ts).toISOString(),
163
+ device: start.device,
164
+ scene: start.scene,
165
+ user: start.user,
166
+ };
167
+ },
168
+ putSceneProxy: async (_projectId, proxy, label) => {
169
+ const existing = representations.get(proxy.sceneId);
170
+ const now = new Date();
171
+ const representation = {
172
+ projectId,
173
+ sceneId: proxy.sceneId,
174
+ label: label ?? existing?.label ?? null,
175
+ kind: "proxy",
176
+ upAxis: proxy.upAxis,
177
+ unitScale: proxy.unitScale,
178
+ bounds: proxy.bounds,
179
+ proxy,
180
+ assetUrl: null,
181
+ contentHash: proxy.contentHash,
182
+ proxyVersion: proxy.version,
183
+ capturedAt: new Date(proxy.capturedAt),
184
+ updatedAt: now,
185
+ };
186
+ representations.set(proxy.sceneId, representation);
187
+ return representation;
188
+ },
189
+ getSceneRepresentation: async (_projectId, sceneId) => representations.get(sceneId) ?? null,
190
+ listSceneRepresentations: async () => [...representations.values()]
191
+ .sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())
192
+ .map((r) => ({
193
+ sceneId: r.sceneId,
194
+ label: r.label,
195
+ kind: r.kind,
196
+ bounds: r.bounds,
197
+ contentHash: r.contentHash,
198
+ capturedAt: r.capturedAt,
199
+ updatedAt: r.updatedAt,
200
+ })),
201
+ close: async () => {
202
+ events.length = 0;
203
+ representations.clear();
204
+ },
205
+ };
206
+ }
207
+ //# sourceMappingURL=memoryStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoryStore.js","sourceRoot":"","sources":["../src/memoryStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { FastifyPluginAsync } from "fastify";
2
+ import type { CollectorConfig } from "../config.js";
3
+ import type { CollectorStore } from "../store.js";
4
+ import type { LiveBus } from "../liveBus.js";
5
+ interface Options {
6
+ store: CollectorStore;
7
+ config: CollectorConfig;
8
+ liveBus: LiveBus;
9
+ }
10
+ /**
11
+ * Ingestion route. Validates the batch against the schema, authorizes it against
12
+ * the (public) project id, enriches each event with the cookieless visitor id
13
+ * (raw IP never stored), publishes to the live bus (ADR 0032 §2), and inserts.
14
+ * Kept thin — all storage logic lives in the store.
15
+ *
16
+ * Browser beacons (`navigator.sendBeacon`) cannot carry a secret header, so the
17
+ * `projectId` in the envelope is the public ingest credential (like a GA
18
+ * measurement id). The route therefore (a) requires every event in a batch to
19
+ * share one `projectId` and (b) rejects batches for a project that does not
20
+ * exist, so events can never be written under an arbitrary/spoofed project. CORS
21
+ * origin allow-listing and a dedicated per-client rate limit bound the rest.
22
+ */
23
+ export declare const collectRoutes: FastifyPluginAsync<Options>;
24
+ export {};
25
+ //# sourceMappingURL=collect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../src/routes/collect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,SAAS,CAAC;AAGhE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,UAAU,OAAO;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;CAClB;AAOD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,aAAa,EAAE,kBAAkB,CAAC,OAAO,CA8CrD,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { collectRequestSchema, collectResponseSchema } from "@uptimizr/schema";
2
+ import { enrichEvents } from "../enrich.js";
3
+ import { dailySalt, visitorHash } from "../visitor.js";
4
+ /** Reject an ingest batch with a status + error message (plain reply, unconstrained by the 200 schema). */
5
+ function rejectIngest(reply, code, error) {
6
+ return reply.code(code).send({ error });
7
+ }
8
+ /**
9
+ * Ingestion route. Validates the batch against the schema, authorizes it against
10
+ * the (public) project id, enriches each event with the cookieless visitor id
11
+ * (raw IP never stored), publishes to the live bus (ADR 0032 §2), and inserts.
12
+ * Kept thin — all storage logic lives in the store.
13
+ *
14
+ * Browser beacons (`navigator.sendBeacon`) cannot carry a secret header, so the
15
+ * `projectId` in the envelope is the public ingest credential (like a GA
16
+ * measurement id). The route therefore (a) requires every event in a batch to
17
+ * share one `projectId` and (b) rejects batches for a project that does not
18
+ * exist, so events can never be written under an arbitrary/spoofed project. CORS
19
+ * origin allow-listing and a dedicated per-client rate limit bound the rest.
20
+ */
21
+ export const collectRoutes = async (app, { store, config, liveBus }) => {
22
+ const r = app.withTypeProvider();
23
+ r.post("/api/v1/collect", {
24
+ config: {
25
+ rateLimit: {
26
+ max: config.ingestRateLimitMax,
27
+ timeWindow: config.ingestRateLimitWindowMs,
28
+ },
29
+ },
30
+ schema: {
31
+ body: collectRequestSchema,
32
+ response: { 200: collectResponseSchema },
33
+ },
34
+ }, async (request, reply) => {
35
+ const { events } = request.body;
36
+ const projectId = events[0].projectId;
37
+ // A batch belongs to exactly one project; mixed ids are rejected outright.
38
+ if (events.some((event) => event.projectId !== projectId)) {
39
+ return rejectIngest(reply, 400, "all events in a batch must share one projectId");
40
+ }
41
+ // Reject events for an unknown project so data is never stored under a
42
+ // spoofed/non-existent id.
43
+ if (!(await store.projectExists(projectId))) {
44
+ return rejectIngest(reply, 401, "unknown project");
45
+ }
46
+ const ip = request.ip ?? "";
47
+ const ua = request.headers["user-agent"] ?? "";
48
+ const visitorId = visitorHash(ip, ua, dailySalt(config.visitorHashSecret));
49
+ const enriched = enrichEvents(events, visitorId);
50
+ liveBus.publish(enriched);
51
+ await store.insertEvents(enriched);
52
+ return { accepted: enriched.length, rejected: 0 };
53
+ });
54
+ };
55
+ //# sourceMappingURL=collect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect.js","sourceRoot":"","sources":["../../src/routes/collect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAI/E,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAQvD,2GAA2G;AAC3G,SAAS,YAAY,CAAC,KAAmB,EAAE,IAAY,EAAE,KAAa;IACpE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,aAAa,GAAgC,KAAK,EAC7D,GAAG,EACH,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,EAC1B,EAAE;IACF,MAAM,CAAC,GAAG,GAAG,CAAC,gBAAgB,EAAmB,CAAC;IAElD,CAAC,CAAC,IAAI,CACJ,iBAAiB,EACjB;QACE,MAAM,EAAE;YACN,SAAS,EAAE;gBACT,GAAG,EAAE,MAAM,CAAC,kBAAkB;gBAC9B,UAAU,EAAE,MAAM,CAAC,uBAAuB;aAC3C;SACF;QACD,MAAM,EAAE;YACN,IAAI,EAAE,oBAAoB;YAC1B,QAAQ,EAAE,EAAE,GAAG,EAAE,qBAAqB,EAAE;SACzC;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;QAChC,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,CAAC;QAEvC,2EAA2E;QAC3E,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,CAAC;YAC1D,OAAO,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,gDAAgD,CAAC,CAAC;QACpF,CAAC;QAED,uEAAuE;QACvE,2BAA2B;QAC3B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YAC5C,OAAO,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,iBAAiB,CAAC,CAAC;QACrD,CAAC;QAED,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAI,OAAO,CAAC,OAAO,CAAC,YAAY,CAAwB,IAAI,EAAE,CAAC;QACvE,MAAM,SAAS,GAAG,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAE3E,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QACjD,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC1B,MAAM,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAEnC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACpD,CAAC,CACF,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { FastifyPluginAsync } from "fastify";
2
+ import type { CollectorConfig } from "../config.js";
3
+ import type { CollectorStore } from "../store.js";
4
+ import type { LiveBus } from "../liveBus.js";
5
+ interface Options {
6
+ store: CollectorStore;
7
+ config: CollectorConfig;
8
+ liveBus: LiveBus;
9
+ }
10
+ /**
11
+ * Live SSE endpoints (ADR 0032 §3): presence (aggregate, always on), the
12
+ * project firehose (drives live dashboard updates), and the per-session
13
+ * live-follow tail (gated by raw-session retention, like replay). All fan out
14
+ * from the in-process bus and are bounded (ADR §6).
15
+ */
16
+ export declare const liveRoutes: FastifyPluginAsync<Options>;
17
+ export {};
18
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/routes/live.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAgC,MAAM,SAAS,CAAC;AAGhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C,UAAU,OAAO;IACf,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;CAClB;AA2ED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,kBAAkB,CAAC,OAAO,CA2HlD,CAAC"}