@seneris/nosework 0.1.0 → 0.3.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/README.md +76 -54
- package/dist/client.d.ts +9 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +26 -17
- package/dist/client.js.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +123 -117
- package/dist/error.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/query.d.ts +1 -11
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +174 -213
- package/dist/query.js.map +1 -1
- package/dist/retention.d.ts +19 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +34 -0
- package/dist/retention.js.map +1 -0
- package/dist/schema.d.ts +979 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +101 -0
- package/dist/schema.js.map +1 -0
- package/dist/sites.d.ts +15 -0
- package/dist/sites.d.ts.map +1 -0
- package/dist/sites.js +31 -0
- package/dist/sites.js.map +1 -0
- package/dist/track.d.ts.map +1 -1
- package/dist/track.js +29 -32
- package/dist/track.js.map +1 -1
- package/dist/utils.d.ts +15 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +60 -31
- package/dist/utils.js.map +1 -1
- package/package.json +9 -10
- package/prisma/schema.prisma +0 -145
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuCrB,CAAC;AAGF,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqBlB,CAAC;AAGF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGrB,CAAC;AAGH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoC3B,CAAC;AAGF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBvB,CAAC;AAGF,MAAM,MAAM,QAAQ,GAAG,OAAO,SAAS,CAAC,YAAY,CAAC;AACrD,MAAM,MAAM,WAAW,GAAG,OAAO,SAAS,CAAC,YAAY,CAAC;AACxD,MAAM,MAAM,KAAK,GAAG,OAAO,MAAM,CAAC,YAAY,CAAC;AAC/C,MAAM,MAAM,QAAQ,GAAG,OAAO,MAAM,CAAC,YAAY,CAAC;AAClD,MAAM,MAAM,SAAS,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AACvD,MAAM,MAAM,cAAc,GAAG,OAAO,eAAe,CAAC,YAAY,CAAC;AACjE,MAAM,MAAM,iBAAiB,GAAG,OAAO,eAAe,CAAC,YAAY,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,OAAO,WAAW,CAAC,YAAY,CAAC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { pgTable, text, timestamp, boolean, integer, json, date, index, unique, } from "drizzle-orm/pg-core";
|
|
2
|
+
// Page view events
|
|
3
|
+
export const pageViews = pgTable("page_views", {
|
|
4
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
5
|
+
siteId: text("siteId").notNull(),
|
|
6
|
+
// Page info
|
|
7
|
+
url: text("url").notNull(),
|
|
8
|
+
pathname: text("pathname").notNull(),
|
|
9
|
+
referrer: text("referrer"),
|
|
10
|
+
// Visitor (cookieless identification)
|
|
11
|
+
visitorHash: text("visitorHash").notNull(),
|
|
12
|
+
sessionId: text("sessionId").notNull(),
|
|
13
|
+
// Location (from Vercel geo headers)
|
|
14
|
+
country: text("country"),
|
|
15
|
+
countryCode: text("countryCode"),
|
|
16
|
+
region: text("region"),
|
|
17
|
+
city: text("city"),
|
|
18
|
+
// Device (parsed from User-Agent)
|
|
19
|
+
browser: text("browser"),
|
|
20
|
+
browserVer: text("browserVer"),
|
|
21
|
+
os: text("os"),
|
|
22
|
+
osVer: text("osVer"),
|
|
23
|
+
device: text("device"),
|
|
24
|
+
// Optional user link
|
|
25
|
+
userId: text("userId"),
|
|
26
|
+
isBot: boolean("isBot").default(false).notNull(),
|
|
27
|
+
timestamp: timestamp("timestamp").defaultNow().notNull(),
|
|
28
|
+
}, (table) => [
|
|
29
|
+
index("page_views_site_timestamp_idx").on(table.siteId, table.timestamp),
|
|
30
|
+
index("page_views_site_pathname_idx").on(table.siteId, table.pathname),
|
|
31
|
+
index("page_views_session_idx").on(table.sessionId),
|
|
32
|
+
index("page_views_user_idx").on(table.userId),
|
|
33
|
+
]);
|
|
34
|
+
// Custom events
|
|
35
|
+
export const events = pgTable("events", {
|
|
36
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
37
|
+
siteId: text("siteId").notNull(),
|
|
38
|
+
name: text("name").notNull(),
|
|
39
|
+
properties: json("properties"),
|
|
40
|
+
visitorHash: text("visitorHash").notNull(),
|
|
41
|
+
sessionId: text("sessionId").notNull(),
|
|
42
|
+
userId: text("userId"),
|
|
43
|
+
url: text("url"),
|
|
44
|
+
timestamp: timestamp("timestamp").defaultNow().notNull(),
|
|
45
|
+
}, (table) => [
|
|
46
|
+
index("events_site_name_timestamp_idx").on(table.siteId, table.name, table.timestamp),
|
|
47
|
+
index("events_site_timestamp_idx").on(table.siteId, table.timestamp),
|
|
48
|
+
index("events_user_idx").on(table.userId),
|
|
49
|
+
]);
|
|
50
|
+
// Daily salt for visitor hashing (privacy - rotates daily)
|
|
51
|
+
export const dailySalts = pgTable("daily_salts", {
|
|
52
|
+
date: date("date").primaryKey(),
|
|
53
|
+
salt: text("salt").notNull(),
|
|
54
|
+
});
|
|
55
|
+
// Error tracking - individual occurrences
|
|
56
|
+
export const analyticsErrors = pgTable("analytics_errors", {
|
|
57
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
58
|
+
siteId: text("siteId").notNull(),
|
|
59
|
+
// Error details
|
|
60
|
+
message: text("message").notNull(),
|
|
61
|
+
stack: text("stack"),
|
|
62
|
+
fingerprint: text("fingerprint").notNull(),
|
|
63
|
+
// Context
|
|
64
|
+
url: text("url").notNull(),
|
|
65
|
+
pathname: text("pathname").notNull(),
|
|
66
|
+
// Visitor context
|
|
67
|
+
visitorHash: text("visitorHash"),
|
|
68
|
+
sessionId: text("sessionId"),
|
|
69
|
+
userId: text("userId"),
|
|
70
|
+
// Browser context
|
|
71
|
+
browser: text("browser"),
|
|
72
|
+
browserVer: text("browserVer"),
|
|
73
|
+
os: text("os"),
|
|
74
|
+
device: text("device"),
|
|
75
|
+
// Metadata
|
|
76
|
+
metadata: json("metadata"),
|
|
77
|
+
timestamp: timestamp("timestamp").defaultNow().notNull(),
|
|
78
|
+
}, (table) => [
|
|
79
|
+
index("errors_site_timestamp_idx").on(table.siteId, table.timestamp),
|
|
80
|
+
index("errors_site_fingerprint_idx").on(table.siteId, table.fingerprint),
|
|
81
|
+
index("errors_user_idx").on(table.userId),
|
|
82
|
+
]);
|
|
83
|
+
// Error groups - aggregated by fingerprint
|
|
84
|
+
export const errorGroups = pgTable("error_groups", {
|
|
85
|
+
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
86
|
+
siteId: text("siteId").notNull(),
|
|
87
|
+
fingerprint: text("fingerprint").notNull(),
|
|
88
|
+
// Representative error
|
|
89
|
+
message: text("message").notNull(),
|
|
90
|
+
stack: text("stack"),
|
|
91
|
+
// Counts
|
|
92
|
+
count: integer("count").default(1).notNull(),
|
|
93
|
+
lastSeen: timestamp("lastSeen").notNull(),
|
|
94
|
+
firstSeen: timestamp("firstSeen").notNull(),
|
|
95
|
+
// Status: open, resolved, ignored
|
|
96
|
+
status: text("status").default("open").notNull(),
|
|
97
|
+
}, (table) => [
|
|
98
|
+
unique("error_groups_site_fingerprint_unique").on(table.siteId, table.fingerprint),
|
|
99
|
+
index("error_groups_site_status_lastseen_idx").on(table.siteId, table.status, table.lastSeen),
|
|
100
|
+
]);
|
|
101
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/sites.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface SiteSummary {
|
|
2
|
+
siteId: string;
|
|
3
|
+
pageViews: number;
|
|
4
|
+
visitors: number;
|
|
5
|
+
lastSeen: Date | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* List every site that has ever reported a page view, busiest first.
|
|
9
|
+
*
|
|
10
|
+
* There is no sites table — siteId is a bare column written by whichever app
|
|
11
|
+
* is reporting — so the list is derived from the data. A site appears here as
|
|
12
|
+
* soon as it sends its first non-bot page view, with no registration step.
|
|
13
|
+
*/
|
|
14
|
+
export declare function listSites(): Promise<SiteSummary[]>;
|
|
15
|
+
//# sourceMappingURL=sites.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sites.d.ts","sourceRoot":"","sources":["../src/sites.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAqBxD"}
|
package/dist/sites.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getClient } from "./client.js";
|
|
2
|
+
import { pageViews } from "./schema.js";
|
|
3
|
+
import { count, countDistinct, desc, eq, max } from "drizzle-orm";
|
|
4
|
+
/**
|
|
5
|
+
* List every site that has ever reported a page view, busiest first.
|
|
6
|
+
*
|
|
7
|
+
* There is no sites table — siteId is a bare column written by whichever app
|
|
8
|
+
* is reporting — so the list is derived from the data. A site appears here as
|
|
9
|
+
* soon as it sends its first non-bot page view, with no registration step.
|
|
10
|
+
*/
|
|
11
|
+
export async function listSites() {
|
|
12
|
+
const db = getClient();
|
|
13
|
+
const rows = await db
|
|
14
|
+
.select({
|
|
15
|
+
siteId: pageViews.siteId,
|
|
16
|
+
pageViewCount: count(),
|
|
17
|
+
visitorCount: countDistinct(pageViews.visitorHash),
|
|
18
|
+
lastSeen: max(pageViews.timestamp),
|
|
19
|
+
})
|
|
20
|
+
.from(pageViews)
|
|
21
|
+
.where(eq(pageViews.isBot, false))
|
|
22
|
+
.groupBy(pageViews.siteId)
|
|
23
|
+
.orderBy(desc(count()));
|
|
24
|
+
return rows.map((r) => ({
|
|
25
|
+
siteId: r.siteId,
|
|
26
|
+
pageViews: r.pageViewCount,
|
|
27
|
+
visitors: r.visitorCount,
|
|
28
|
+
lastSeen: r.lastSeen ?? null,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=sites.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sites.js","sourceRoot":"","sources":["../src/sites.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AASlE;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IAEvB,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,CAAC;QACN,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,aAAa,EAAE,KAAK,EAAE;QACtB,YAAY,EAAE,aAAa,CAAC,SAAS,CAAC,WAAW,CAAC;QAClD,QAAQ,EAAE,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC;KACnC,CAAC;SACD,IAAI,CAAC,SAAS,CAAC;SACf,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;SACjC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC;SACzB,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAE1B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,SAAS,EAAE,CAAC,CAAC,aAAa;QAC1B,QAAQ,EAAE,CAAC,CAAC,YAAY;QACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,IAAI;KAC7B,CAAC,CAAC,CAAC;AACN,CAAC"}
|
package/dist/track.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../src/track.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../src/track.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE1E,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAoEf;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB1E"}
|
package/dist/track.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getClient } from "./client.js";
|
|
2
|
+
import { pageViews, events } from "./schema.js";
|
|
2
3
|
import { parseUserAgent } from "./ua.js";
|
|
3
4
|
import { isBot, getVisitorInfo, extractPathname } from "./utils.js";
|
|
4
5
|
export async function trackPageView(options) {
|
|
@@ -9,7 +10,7 @@ export async function trackPageView(options) {
|
|
|
9
10
|
const ipNorm = ip ?? null;
|
|
10
11
|
const uaNorm = userAgent ?? null;
|
|
11
12
|
// Get visitor info (hashes for privacy)
|
|
12
|
-
const { visitorHash, sessionId } = await getVisitorInfo(ipNorm, uaNorm);
|
|
13
|
+
const { visitorHash, sessionId } = await getVisitorInfo(siteId, ipNorm, uaNorm);
|
|
13
14
|
// Parse user agent
|
|
14
15
|
const ua = parseUserAgent(uaNorm);
|
|
15
16
|
// Detect bots
|
|
@@ -29,26 +30,24 @@ export async function trackPageView(options) {
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
const db = getClient();
|
|
32
|
-
await db.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
isBot: isBotRequest,
|
|
51
|
-
},
|
|
33
|
+
await db.insert(pageViews).values({
|
|
34
|
+
siteId,
|
|
35
|
+
url,
|
|
36
|
+
pathname,
|
|
37
|
+
referrer: cleanReferrer,
|
|
38
|
+
visitorHash,
|
|
39
|
+
sessionId,
|
|
40
|
+
country: country ?? null,
|
|
41
|
+
countryCode: countryCode ?? null,
|
|
42
|
+
region: region ?? null,
|
|
43
|
+
city: city ?? null,
|
|
44
|
+
browser: ua.browser,
|
|
45
|
+
browserVer: ua.browserVer,
|
|
46
|
+
os: ua.os,
|
|
47
|
+
osVer: ua.osVer,
|
|
48
|
+
device: ua.device,
|
|
49
|
+
userId: userId ?? null,
|
|
50
|
+
isBot: isBotRequest,
|
|
52
51
|
});
|
|
53
52
|
}
|
|
54
53
|
export async function trackEvent(options) {
|
|
@@ -57,18 +56,16 @@ export async function trackEvent(options) {
|
|
|
57
56
|
const ipNorm = ip ?? null;
|
|
58
57
|
const uaNorm = userAgent ?? null;
|
|
59
58
|
// Get visitor info (hashes for privacy)
|
|
60
|
-
const { visitorHash, sessionId } = await getVisitorInfo(ipNorm, uaNorm);
|
|
59
|
+
const { visitorHash, sessionId } = await getVisitorInfo(siteId, ipNorm, uaNorm);
|
|
61
60
|
const db = getClient();
|
|
62
|
-
await db.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
userId,
|
|
71
|
-
},
|
|
61
|
+
await db.insert(events).values({
|
|
62
|
+
siteId,
|
|
63
|
+
name,
|
|
64
|
+
properties: properties ?? null,
|
|
65
|
+
url: url ?? null,
|
|
66
|
+
visitorHash,
|
|
67
|
+
sessionId,
|
|
68
|
+
userId: userId ?? null,
|
|
72
69
|
});
|
|
73
70
|
}
|
|
74
71
|
//# sourceMappingURL=track.js.map
|
package/dist/track.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track.js","sourceRoot":"","sources":["../src/track.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGpE,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAA6B;IAE7B,MAAM,EACJ,MAAM,EACN,GAAG,EACH,QAAQ,EACR,EAAE,EACF,SAAS,EACT,MAAM;IACN,uDAAuD;IACvD,OAAO,EACP,WAAW,EACX,MAAM,EACN,IAAI,GACL,GAAG,OAAO,CAAC;IAEZ,8BAA8B;IAC9B,MAAM,MAAM,GAAG,EAAE,IAAI,IAAI,CAAC;IAC1B,MAAM,MAAM,GAAG,SAAS,IAAI,IAAI,CAAC;IAEjC,wCAAwC;IACxC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"track.js","sourceRoot":"","sources":["../src/track.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGpE,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAA6B;IAE7B,MAAM,EACJ,MAAM,EACN,GAAG,EACH,QAAQ,EACR,EAAE,EACF,SAAS,EACT,MAAM;IACN,uDAAuD;IACvD,OAAO,EACP,WAAW,EACX,MAAM,EACN,IAAI,GACL,GAAG,OAAO,CAAC;IAEZ,8BAA8B;IAC9B,MAAM,MAAM,GAAG,EAAE,IAAI,IAAI,CAAC;IAC1B,MAAM,MAAM,GAAG,SAAS,IAAI,IAAI,CAAC;IAEjC,wCAAwC;IACxC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,cAAc,CACrD,MAAM,EACN,MAAM,EACN,MAAM,CACP,CAAC;IAEF,mBAAmB;IACnB,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAElC,cAAc;IACd,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAEnC,4BAA4B;IAC5B,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IAEtC,mDAAmD;IACnD,IAAI,aAAa,GAAG,QAAQ,IAAI,IAAI,CAAC;IACrC,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC;YACtC,8CAA8C;YAC9C,aAAa,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,gCAAgC;QAClC,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IAEvB,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAChC,MAAM;QACN,GAAG;QACH,QAAQ;QACR,QAAQ,EAAE,aAAa;QACvB,WAAW;QACX,SAAS;QACT,OAAO,EAAE,OAAO,IAAI,IAAI;QACxB,WAAW,EAAE,WAAW,IAAI,IAAI;QAChC,MAAM,EAAE,MAAM,IAAI,IAAI;QACtB,IAAI,EAAE,IAAI,IAAI,IAAI;QAClB,OAAO,EAAE,EAAE,CAAC,OAAO;QACnB,UAAU,EAAE,EAAE,CAAC,UAAU;QACzB,EAAE,EAAE,EAAE,CAAC,EAAE;QACT,KAAK,EAAE,EAAE,CAAC,KAAK;QACf,MAAM,EAAE,EAAE,CAAC,MAAM;QACjB,MAAM,EAAE,MAAM,IAAI,IAAI;QACtB,KAAK,EAAE,YAAY;KACpB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAA0B;IACzD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAEzE,8BAA8B;IAC9B,MAAM,MAAM,GAAG,EAAE,IAAI,IAAI,CAAC;IAC1B,MAAM,MAAM,GAAG,SAAS,IAAI,IAAI,CAAC;IAEjC,wCAAwC;IACxC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,cAAc,CACrD,MAAM,EACN,MAAM,EACN,MAAM,CACP,CAAC;IAEF,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IAEvB,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;QAC7B,MAAM;QACN,IAAI;QACJ,UAAU,EAAE,UAAU,IAAI,IAAI;QAC9B,GAAG,EAAE,GAAG,IAAI,IAAI;QAChB,WAAW;QACX,SAAS;QACT,MAAM,EAAE,MAAM,IAAI,IAAI;KACvB,CAAC,CAAC;AACL,CAAC"}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type { VisitorInfo } from "./types.js";
|
|
2
2
|
export declare function isBot(userAgent: string | null): boolean;
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Pure derivation of visitor and session identifiers.
|
|
5
|
+
*
|
|
6
|
+
* Separated from getVisitorInfo() so the privacy-critical logic can be tested
|
|
7
|
+
* without a database. The inputs and their order are load-bearing: changing
|
|
8
|
+
* them resets every visitor's identity.
|
|
9
|
+
*
|
|
10
|
+
* siteId is part of the hash input so that the same person, on the same day,
|
|
11
|
+
* gets a different visitorHash on each site in a deployment. The daily salt is
|
|
12
|
+
* one row for the whole database, so without the site component a group-by on
|
|
13
|
+
* visitorHash would reconstruct a day of cross-site browsing. This is the same
|
|
14
|
+
* reason Plausible folds the domain into its hash.
|
|
15
|
+
*/
|
|
16
|
+
export declare function computeVisitorIds(siteId: string, ip: string | null, userAgent: string | null, salt: string, now: Date): VisitorInfo;
|
|
17
|
+
export declare function getVisitorInfo(siteId: string, ip: string | null, userAgent: string | null): Promise<VisitorInfo>;
|
|
4
18
|
export declare function extractPathname(url: string): string;
|
|
5
19
|
export declare function cleanupOldSalts(): Promise<number>;
|
|
6
20
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA6C9C,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIvD;AAsCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,GAAG,IAAI,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,IAAI,GACR,WAAW,CAWb;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,GAAG,IAAI,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,WAAW,CAAC,CAItB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASnD;AASD,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAYvD"}
|
package/dist/utils.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "crypto";
|
|
2
2
|
import { getClient } from "./client.js";
|
|
3
|
+
import { dailySalts } from "./schema.js";
|
|
4
|
+
import { eq, lt } from "drizzle-orm";
|
|
3
5
|
// Bot patterns to detect
|
|
4
6
|
const BOT_PATTERNS = [
|
|
5
7
|
/bot/i,
|
|
@@ -52,42 +54,61 @@ function hash(input) {
|
|
|
52
54
|
}
|
|
53
55
|
async function getDailySalt(date) {
|
|
54
56
|
const db = getClient();
|
|
55
|
-
|
|
57
|
+
// Format date as YYYY-MM-DD string for the date column
|
|
58
|
+
const dateStr = date.toISOString().split("T")[0];
|
|
56
59
|
// Try to get existing salt
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
const [existingSalt] = await db
|
|
61
|
+
.select()
|
|
62
|
+
.from(dailySalts)
|
|
63
|
+
.where(eq(dailySalts.date, dateStr))
|
|
64
|
+
.limit(1);
|
|
65
|
+
if (existingSalt) {
|
|
66
|
+
return existingSalt.salt;
|
|
67
|
+
}
|
|
60
68
|
// Create if doesn't exist
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
const salt = randomBytes(32).toString("hex");
|
|
70
|
+
try {
|
|
71
|
+
await db.insert(dailySalts).values({ date: dateStr, salt });
|
|
72
|
+
return salt;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Race condition - another request created it, try to fetch again
|
|
76
|
+
const [retry] = await db
|
|
77
|
+
.select()
|
|
78
|
+
.from(dailySalts)
|
|
79
|
+
.where(eq(dailySalts.date, dateStr))
|
|
80
|
+
.limit(1);
|
|
81
|
+
return retry?.salt ?? salt;
|
|
74
82
|
}
|
|
75
|
-
return dailySalt?.salt ?? randomBytes(32).toString("hex");
|
|
76
83
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Pure derivation of visitor and session identifiers.
|
|
86
|
+
*
|
|
87
|
+
* Separated from getVisitorInfo() so the privacy-critical logic can be tested
|
|
88
|
+
* without a database. The inputs and their order are load-bearing: changing
|
|
89
|
+
* them resets every visitor's identity.
|
|
90
|
+
*
|
|
91
|
+
* siteId is part of the hash input so that the same person, on the same day,
|
|
92
|
+
* gets a different visitorHash on each site in a deployment. The daily salt is
|
|
93
|
+
* one row for the whole database, so without the site component a group-by on
|
|
94
|
+
* visitorHash would reconstruct a day of cross-site browsing. This is the same
|
|
95
|
+
* reason Plausible folds the domain into its hash.
|
|
96
|
+
*/
|
|
97
|
+
export function computeVisitorIds(siteId, ip, userAgent, salt, now) {
|
|
98
|
+
// Visitor hash: site-scoped, and rotates daily because the salt does
|
|
99
|
+
const visitorInput = `${siteId}|${ip ?? ""}|${userAgent ?? ""}|${salt}`;
|
|
83
100
|
const visitorHash = hash(visitorInput);
|
|
84
101
|
// Session hash: rotates every 30 minutes
|
|
85
102
|
// This provides session-like behavior without cookies
|
|
86
103
|
const thirtyMinWindow = Math.floor(now.getTime() / (30 * 60 * 1000));
|
|
87
|
-
const
|
|
88
|
-
const sessionId = hash(sessionInput);
|
|
104
|
+
const sessionId = hash(`${visitorInput}|${thirtyMinWindow}`);
|
|
89
105
|
return { visitorHash, sessionId };
|
|
90
106
|
}
|
|
107
|
+
export async function getVisitorInfo(siteId, ip, userAgent) {
|
|
108
|
+
const now = new Date();
|
|
109
|
+
const salt = await getDailySalt(now);
|
|
110
|
+
return computeVisitorIds(siteId, ip, userAgent, salt, now);
|
|
111
|
+
}
|
|
91
112
|
export function extractPathname(url) {
|
|
92
113
|
try {
|
|
93
114
|
const parsed = new URL(url);
|
|
@@ -100,13 +121,21 @@ export function extractPathname(url) {
|
|
|
100
121
|
}
|
|
101
122
|
}
|
|
102
123
|
// Clean up old daily salts (older than 7 days)
|
|
124
|
+
//
|
|
125
|
+
// The arithmetic is done in UTC throughout: salts are keyed by UTC calendar
|
|
126
|
+
// day (getDailySalt() derives its key from toISOString()), so subtracting in
|
|
127
|
+
// local time and then formatting in UTC would land the cutoff a day early or
|
|
128
|
+
// late on any host that isn't at UTC+0 — salts would live 6 or 8 days rather
|
|
129
|
+
// than 7. docs/PRIVACY.md makes the 7-day number load-bearing.
|
|
103
130
|
export async function cleanupOldSalts() {
|
|
104
131
|
const db = getClient();
|
|
105
132
|
const sevenDaysAgo = new Date();
|
|
106
|
-
sevenDaysAgo.
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
sevenDaysAgo.setUTCDate(sevenDaysAgo.getUTCDate() - 7);
|
|
134
|
+
const dateStr = sevenDaysAgo.toISOString().split("T")[0];
|
|
135
|
+
const deleted = await db
|
|
136
|
+
.delete(dailySalts)
|
|
137
|
+
.where(lt(dailySalts.date, dateStr))
|
|
138
|
+
.returning({ date: dailySalts.date });
|
|
139
|
+
return deleted.length;
|
|
111
140
|
}
|
|
112
141
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seneris/nosework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Privacy-focused, self-hosted analytics for your app suite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,17 +17,15 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"dist"
|
|
21
|
-
"prisma"
|
|
20
|
+
"dist"
|
|
22
21
|
],
|
|
23
22
|
"scripts": {
|
|
24
23
|
"build": "tsc",
|
|
25
24
|
"dev": "tsc --watch",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"db:
|
|
29
|
-
"
|
|
30
|
-
"prepublishOnly": "bun run build"
|
|
25
|
+
"test": "bun test",
|
|
26
|
+
"prepublishOnly": "bun run build",
|
|
27
|
+
"db:generate": "drizzle-kit generate",
|
|
28
|
+
"db:migrate": "drizzle-kit migrate"
|
|
31
29
|
},
|
|
32
30
|
"keywords": [
|
|
33
31
|
"analytics",
|
|
@@ -39,13 +37,14 @@
|
|
|
39
37
|
"author": "",
|
|
40
38
|
"license": "MIT",
|
|
41
39
|
"dependencies": {
|
|
42
|
-
"
|
|
40
|
+
"drizzle-orm": "^0.38.3",
|
|
41
|
+
"postgres": "^3.4.5",
|
|
43
42
|
"ua-parser-js": "^2.0.1"
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
45
|
"@types/bun": "^1.3.5",
|
|
47
46
|
"@types/ua-parser-js": "^0.7.39",
|
|
48
|
-
"
|
|
47
|
+
"drizzle-kit": "^0.31.10",
|
|
49
48
|
"typescript": "^5.7.3"
|
|
50
49
|
},
|
|
51
50
|
"peerDependencies": {
|
package/prisma/schema.prisma
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
generator client {
|
|
2
|
-
provider = "prisma-client-js"
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
datasource db {
|
|
6
|
-
provider = "postgresql"
|
|
7
|
-
url = env("ANALYTICS_DATABASE_URL")
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
model Site {
|
|
11
|
-
id String @id @default(cuid())
|
|
12
|
-
name String
|
|
13
|
-
domain String @unique
|
|
14
|
-
createdAt DateTime @default(now())
|
|
15
|
-
|
|
16
|
-
pageViews PageView[]
|
|
17
|
-
events Event[]
|
|
18
|
-
errors Error[]
|
|
19
|
-
errorGroups ErrorGroup[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
model PageView {
|
|
23
|
-
id String @id @default(cuid())
|
|
24
|
-
siteId String
|
|
25
|
-
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
26
|
-
|
|
27
|
-
// Page info
|
|
28
|
-
url String
|
|
29
|
-
pathname String
|
|
30
|
-
referrer String?
|
|
31
|
-
|
|
32
|
-
// Visitor (cookieless)
|
|
33
|
-
visitorHash String // Hash of IP + UA + daily salt
|
|
34
|
-
sessionId String // Hash with shorter rotation (30 min)
|
|
35
|
-
|
|
36
|
-
// Location (from GeoIP)
|
|
37
|
-
country String?
|
|
38
|
-
countryCode String?
|
|
39
|
-
region String?
|
|
40
|
-
city String?
|
|
41
|
-
|
|
42
|
-
// Device (from User-Agent)
|
|
43
|
-
browser String?
|
|
44
|
-
browserVer String?
|
|
45
|
-
os String?
|
|
46
|
-
osVer String?
|
|
47
|
-
device String? // desktop, mobile, tablet
|
|
48
|
-
|
|
49
|
-
// User context (optional, from your OAuth)
|
|
50
|
-
userId String?
|
|
51
|
-
|
|
52
|
-
// Bot detection
|
|
53
|
-
isBot Boolean @default(false)
|
|
54
|
-
|
|
55
|
-
timestamp DateTime @default(now())
|
|
56
|
-
|
|
57
|
-
@@index([siteId, timestamp])
|
|
58
|
-
@@index([siteId, pathname])
|
|
59
|
-
@@index([siteId, country])
|
|
60
|
-
@@index([visitorHash, timestamp])
|
|
61
|
-
@@index([sessionId])
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
model Event {
|
|
65
|
-
id String @id @default(cuid())
|
|
66
|
-
siteId String
|
|
67
|
-
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
68
|
-
|
|
69
|
-
name String // e.g., "signup", "purchase", "button_click"
|
|
70
|
-
properties Json? // Flexible event data
|
|
71
|
-
|
|
72
|
-
visitorHash String
|
|
73
|
-
sessionId String
|
|
74
|
-
userId String?
|
|
75
|
-
|
|
76
|
-
url String?
|
|
77
|
-
|
|
78
|
-
timestamp DateTime @default(now())
|
|
79
|
-
|
|
80
|
-
@@index([siteId, name, timestamp])
|
|
81
|
-
@@index([siteId, timestamp])
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Daily salt for visitor hashing (privacy - rotates daily)
|
|
85
|
-
model DailySalt {
|
|
86
|
-
date DateTime @id @db.Date
|
|
87
|
-
salt String
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Error tracking
|
|
91
|
-
model Error {
|
|
92
|
-
id String @id @default(cuid())
|
|
93
|
-
siteId String
|
|
94
|
-
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
95
|
-
|
|
96
|
-
// Error details
|
|
97
|
-
message String
|
|
98
|
-
stack String? @db.Text
|
|
99
|
-
fingerprint String // Hash for grouping similar errors
|
|
100
|
-
|
|
101
|
-
// Context
|
|
102
|
-
url String
|
|
103
|
-
pathname String
|
|
104
|
-
|
|
105
|
-
// Visitor context (reuse existing pattern)
|
|
106
|
-
visitorHash String?
|
|
107
|
-
sessionId String?
|
|
108
|
-
userId String?
|
|
109
|
-
|
|
110
|
-
// Browser context
|
|
111
|
-
browser String?
|
|
112
|
-
browserVer String?
|
|
113
|
-
os String?
|
|
114
|
-
device String?
|
|
115
|
-
|
|
116
|
-
// Metadata
|
|
117
|
-
metadata Json? // Custom context from app
|
|
118
|
-
|
|
119
|
-
timestamp DateTime @default(now())
|
|
120
|
-
|
|
121
|
-
@@index([siteId, timestamp])
|
|
122
|
-
@@index([siteId, fingerprint])
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
model ErrorGroup {
|
|
126
|
-
id String @id @default(cuid())
|
|
127
|
-
siteId String
|
|
128
|
-
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
129
|
-
fingerprint String
|
|
130
|
-
|
|
131
|
-
// Representative error
|
|
132
|
-
message String
|
|
133
|
-
stack String? @db.Text
|
|
134
|
-
|
|
135
|
-
// Counts
|
|
136
|
-
count Int @default(1)
|
|
137
|
-
lastSeen DateTime
|
|
138
|
-
firstSeen DateTime
|
|
139
|
-
|
|
140
|
-
// Status: open, resolved, ignored
|
|
141
|
-
status String @default("open")
|
|
142
|
-
|
|
143
|
-
@@unique([siteId, fingerprint])
|
|
144
|
-
@@index([siteId, status, lastSeen])
|
|
145
|
-
}
|