@iann29/rastro 0.1.0-alpha.4 → 0.1.0-alpha.5
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 +128 -29
- package/agent/manifest.json +1 -1
- package/dist/client/index.d.ts +69 -11
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +19 -1
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +6 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +19 -1
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/constants.d.ts +1 -0
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +1 -0
- package/dist/component/constants.js.map +1 -1
- package/dist/component/eventStore.d.ts +1 -0
- package/dist/component/eventStore.d.ts.map +1 -1
- package/dist/component/http.d.ts.map +1 -1
- package/dist/component/http.js +40 -29
- package/dist/component/http.js.map +1 -1
- package/dist/component/identity.d.ts +13 -0
- package/dist/component/identity.d.ts.map +1 -0
- package/dist/component/identity.js +58 -0
- package/dist/component/identity.js.map +1 -0
- package/dist/component/ingest.d.ts +1 -0
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +25 -6
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/reports.d.ts +10 -1
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +22 -15
- package/dist/component/reports.js.map +1 -1
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +4 -1
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +37 -6
- package/dist/component/schema.js +13 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/component/useragent.d.ts +9 -0
- package/dist/component/useragent.d.ts.map +1 -0
- package/dist/component/useragent.js +152 -0
- package/dist/component/useragent.js.map +1 -0
- package/dist/component/validators.d.ts +16 -10
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +3 -1
- package/dist/component/validators.js.map +1 -1
- package/dist/component/visitors.d.ts +19 -0
- package/dist/component/visitors.d.ts.map +1 -0
- package/dist/component/visitors.js +86 -0
- package/dist/component/visitors.js.map +1 -0
- package/dist/tracker/generated.d.ts +4 -4
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +4 -4
- package/dist/tracker/generated.js.map +1 -1
- package/dist/tracker/tracker.js +23 -11
- package/dist/tracker/tracker.js.map +1 -1
- package/dist/tracker.min.js +1 -1
- package/docs/federation.md +1 -1
- package/package.json +12 -8
- package/src/component/_generated/api.ts +6 -0
- package/src/component/_generated/component.ts +18 -1
- package/src/component/constants.ts +1 -0
- package/src/component/http.ts +55 -34
- package/src/component/identity.ts +74 -0
- package/src/component/ingest.ts +29 -3
- package/src/component/reports.ts +21 -14
- package/src/component/sanitize.ts +4 -1
- package/src/component/schema.ts +15 -0
- package/src/component/useragent.ts +171 -0
- package/src/component/validators.ts +3 -0
- package/src/component/visitors.ts +105 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { MAX_VISITOR_ALIASES } from "./constants.js";
|
|
3
|
+
import { fail } from "./errors.js";
|
|
4
|
+
import { sanitizeOpaqueId } from "./sanitize.js";
|
|
5
|
+
import { mutation } from "./_generated/server.js";
|
|
6
|
+
/**
|
|
7
|
+
* Folds an anonymous visitor id into the pseudonymous identity the host
|
|
8
|
+
* supplies after signup or sign-in. Links are one level deep: an alias can
|
|
9
|
+
* belong to one identity, an identity can never become an alias, and an
|
|
10
|
+
* alias can never own aliases, so journeys stay bounded and unambiguous.
|
|
11
|
+
*/
|
|
12
|
+
export const link = mutation({
|
|
13
|
+
args: {
|
|
14
|
+
siteId: v.id("sites"),
|
|
15
|
+
visitorId: v.string(),
|
|
16
|
+
previousVisitorId: v.string(),
|
|
17
|
+
},
|
|
18
|
+
returns: v.object({ linked: v.boolean(), aliasCount: v.number() }),
|
|
19
|
+
handler: async (ctx, args) => {
|
|
20
|
+
const site = await ctx.db.get("sites", args.siteId);
|
|
21
|
+
if (!site)
|
|
22
|
+
fail("NOT_FOUND", "site not found");
|
|
23
|
+
let visitorId;
|
|
24
|
+
let previousVisitorId;
|
|
25
|
+
try {
|
|
26
|
+
visitorId = sanitizeOpaqueId(args.visitorId, "visitorId");
|
|
27
|
+
previousVisitorId = sanitizeOpaqueId(args.previousVisitorId, "previousVisitorId");
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
fail("INVALID_ARGUMENT", error instanceof Error ? error.message : "invalid visitor id");
|
|
31
|
+
}
|
|
32
|
+
if (visitorId === previousVisitorId) {
|
|
33
|
+
fail("INVALID_ARGUMENT", "previousVisitorId must differ from visitorId");
|
|
34
|
+
}
|
|
35
|
+
if (await findAlias(ctx, args.siteId, visitorId)) {
|
|
36
|
+
fail("CONFLICT", "visitorId is already linked to another visitor");
|
|
37
|
+
}
|
|
38
|
+
const previousOwnsAliases = await ctx.db
|
|
39
|
+
.query("visitorAliases")
|
|
40
|
+
.withIndex("by_siteId_and_visitorId", (range) => range.eq("siteId", args.siteId).eq("visitorId", previousVisitorId))
|
|
41
|
+
.first();
|
|
42
|
+
if (previousOwnsAliases) {
|
|
43
|
+
fail("CONFLICT", "previousVisitorId already owns linked visitors");
|
|
44
|
+
}
|
|
45
|
+
const aliases = await ctx.db
|
|
46
|
+
.query("visitorAliases")
|
|
47
|
+
.withIndex("by_siteId_and_visitorId", (range) => range.eq("siteId", args.siteId).eq("visitorId", visitorId))
|
|
48
|
+
.take(MAX_VISITOR_ALIASES);
|
|
49
|
+
const existing = await findAlias(ctx, args.siteId, previousVisitorId);
|
|
50
|
+
if (existing) {
|
|
51
|
+
if (existing.visitorId !== visitorId) {
|
|
52
|
+
fail("CONFLICT", "previousVisitorId is already linked to a different visitor");
|
|
53
|
+
}
|
|
54
|
+
return { linked: false, aliasCount: aliases.length };
|
|
55
|
+
}
|
|
56
|
+
if (aliases.length >= MAX_VISITOR_ALIASES) {
|
|
57
|
+
fail("LIMIT_EXCEEDED", `a visitor can hold at most ${MAX_VISITOR_ALIASES} aliases`, {
|
|
58
|
+
limit: MAX_VISITOR_ALIASES,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
await ctx.db.insert("visitorAliases", {
|
|
62
|
+
siteId: args.siteId,
|
|
63
|
+
visitorId,
|
|
64
|
+
previousVisitorId,
|
|
65
|
+
linkedAt: Date.now(),
|
|
66
|
+
});
|
|
67
|
+
return { linked: true, aliasCount: aliases.length + 1 };
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
/** The identity a visitor id resolves to, followed by every alias it owns. */
|
|
71
|
+
export async function resolveVisitorIdentities(ctx, siteId, visitorId) {
|
|
72
|
+
const alias = await findAlias(ctx, siteId, visitorId);
|
|
73
|
+
const identity = alias?.visitorId ?? visitorId;
|
|
74
|
+
const aliases = await ctx.db
|
|
75
|
+
.query("visitorAliases")
|
|
76
|
+
.withIndex("by_siteId_and_visitorId", (range) => range.eq("siteId", siteId).eq("visitorId", identity))
|
|
77
|
+
.take(MAX_VISITOR_ALIASES);
|
|
78
|
+
return [identity, ...aliases.map((row) => row.previousVisitorId)];
|
|
79
|
+
}
|
|
80
|
+
function findAlias(ctx, siteId, previousVisitorId) {
|
|
81
|
+
return ctx.db
|
|
82
|
+
.query("visitorAliases")
|
|
83
|
+
.withIndex("by_siteId_and_previousVisitorId", (range) => range.eq("siteId", siteId).eq("previousVisitorId", previousVisitorId))
|
|
84
|
+
.unique();
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=visitors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visitors.js","sourceRoot":"","sources":["../../src/component/visitors.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const TRACKER_SOURCE = "(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL(\"events\",e.src).href;if(!t||!s)return;const
|
|
2
|
-
export declare const TRACKER_GZIP_BASE64 = "
|
|
3
|
-
export declare const TRACKER_RAW_BYTES =
|
|
4
|
-
export declare const TRACKER_GZIP_BYTES =
|
|
1
|
+
export declare const TRACKER_SOURCE = "(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL(\"events\",e.src).href;if(!t||!s)return;const o=()=>crypto.randomUUID?.()||Math.random()+\"\",n=new URLSearchParams(location.search),r=n.get(\"ref\")?.slice(0,64);let i,a,c;for(const e of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const t=n.get(\"utm_\"+e)?.slice(0,64);t&&((i??={})[\"utm_\"+e]=t)}try{a=sessionStorage._r||o(),sessionStorage._r=a,c=r||sessionStorage._a,c&&(sessionStorage._a=c),i||=sessionStorage._u&&JSON.parse(sessionStorage._u),i&&(sessionStorage._u=JSON.stringify(i))}catch{a=o(),c=r}const d=e.dataset.visitor||a;let l,p=0;const g=[],u=document.referrer?new URL(document.referrer).origin:void 0,h=e=>{const o=g.splice(0,50);if(!o.length)return;const n=()=>new Blob([JSON.stringify({siteId:t,events:o})]);let r=n();for(;r.size>64e3&&o.length>1;)g.unshift(o.pop()),r=n();e&&navigator.sendBeacon(s,r)||fetch(s,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),g.length&&h(e)},m=(e,t,s)=>{const o=Date.now();g.push({eventId:`${a}.${o.toString(36)}.${p}`,sessionId:a,visitorId:d,type:e,name:t,path:location.pathname.slice(0,256),referrer:u,timestamp:o,sequence:p++,affiliateSlug:c,...s}),clearTimeout(l),l=setTimeout(h,1200)};let f;const v=()=>{const e=location.pathname;e!==f&&(f=e,m(\"pageview\",void 0,i&&{properties:i}))};window.rastro=(e,t,s)=>{if(\"context\"===e)return{sessionId:a,visitorId:d};\"event\"===e&&m(\"custom\",`${t||\"event\"}`.slice(0,80),s&&JSON.stringify(s).length>2e4?void 0:{properties:s})},document.addEventListener(\"click\",e=>{const t=e.target?.closest(\"a,button,[data-rastro-event]\");if(!t)return;const s=t.dataset.rastroEvent,o=t.href,n=o&&new URL(o),r=/^https?:$/.test(n?.protocol),i=r&&n.origin!==location.origin;m(s?\"custom\":i?\"outbound\":\"click\",s,{target:(t.dataset.rastroLabel||t.innerText||t.tagName).slice(0,64),href:r?((i?n.origin:\"\")+n.pathname).slice(0,256):void 0})},1);const S=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),v()}};S(\"pushState\"),S(\"replaceState\"),addEventListener(\"popstate\",v),addEventListener(\"pagehide\",()=>h(1)),setInterval(()=>{m(\"heartbeat\"),h()},2e4),v()})();";
|
|
2
|
+
export declare const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VVXW/cNhD8KzZhCCS8Yc6OYxQSmAOC9MFFmgS95MlwY5paSUQkUiVX51xP+u+FdDqfP9I3cZbiLmdnlpwL9W5rvIt0hCr3pmvQkTRdCOhoZYJtCUjhUuaadESS0RJCfIygy1tvHfU9LmUMJkkc3h99++sjZ7hGR5EBjriQVcAiswU/pr4/jiIgdcFlu+xejaWYsGnJy6Bd7ptv364+LCUXff+npmoGuThlDJyac6xQB1N90UE3kdfeaLLeyTihAoJyskTiLGDBxFLG2hrkC7i8EFmNdGRBg8kKH/hMwZEvrln0XTDIgDWY265hwIxuWm1Lx4ARhgnxjtARuxEze7RP1VHznZ3is2yUJJzb5VJtB3G933OjSAwUNlutIsZovVuRD7pE+T30vecCXsBKg1Gh758HNJgk4S9QZQTYvn9xfJckf6w+f5KtDhFf/NYJsL84rVPTP5GCdaUtNtwKMRhNptpqNVZrVBh2bOQKH/SxttGSD32vJ85raNVibnqprm+gO+guYIEhYFjuFfQiIqQPtrQuXXubHy2gUvggYK9KGduZ9LcLMUnNyxpdSdVTtblJbWOW97W/49fPLrYdVX6VpwQ7Bad+EDc7yQTluJgkkwUZ7b/47vIC3yTJPs+7s0yUsnOxsgVxL1vfcjEpkYsMk8TptS01+SAjuvw9auMdjxBE3xdIpuIRtg1S5fOUffm8+srgzuebNMAPxFbXdo3p2SDkRDuf3DsIKOfkSVJxFAM0iiMQRPGImw+aUDp/z0VWyraLFd9Ol7vK09uTrR7kydZL8quJBP7mUoxIO9zuNXiVpxrmXl7laQ60aTFFcLrBlKDVVKUPBhxXY+DBA+dvLwXsm5h2QLbBSLppUw8R/+nQGUzb01PQRWFrqwlXdVemBqSUcRBgatThq23Qd8RrAbWKSPt1BWfni4UYpgYVc4vX6slse1FahsdKFUnCC4XQcNbqEtcW7xnM0rJJsm2DbzGQxZjaQYghu7cu9/cy6EjBP2LZFnw3E34SU0rhLLft/5A3ZLvZOO1NkoYz00XyDYPbky31/Rwdbh8I/G0hIM6uPQg1ir3szvFiuSs8fVx1HMQADy7Sef77ePBHGwkdBs5Mbc0PBgcTkUJJOpRIS2lqHzESZxruOiLv4Ho09avd7V9NNd6wnc/oqcGioocBsNs+JQavaHoGwCl/eCj8aJDXf1dEbVymJ68ljWndUrbBkze+FmBVSBI3u/9YHfq5Q7KGx+WexNQume/ozncuZ+n+jhG2u4ul/HltH/Ud1n1P0jqH4Sv+pHFBuvykGxSPBzmMxadhOQ7zfe6UMXF6EJZ4Ivp5UI1tOBMzOSv1mO/KRvJhc4032eFTcSkljsoiqdu23vA5BihgzcUwZCvORhuvSBMyAavxlWtrbXCPvOx269s4BWH9y7AusbI5MhitU/EzMb5AdOUIw1rXu3HTcFahDnSHmpiAiosBzvFiV5XgIvsPR/8Yx1QIAAA=";
|
|
3
|
+
export declare const TRACKER_RAW_BYTES = 2132;
|
|
4
|
+
export declare const TRACKER_GZIP_BYTES = 1127;
|
|
5
5
|
//# sourceMappingURL=generated.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,cAAc,+oEAA+oE,CAAC;AAC3qE,eAAO,MAAM,mBAAmB,q+CAAq+C,CAAC;AACtgD,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,kBAAkB,OAAO,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/build-tracker.mjs. Do not edit.
|
|
2
|
-
export const TRACKER_SOURCE = "(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL(\"events\",e.src).href;if(!t||!s)return;const
|
|
3
|
-
export const TRACKER_GZIP_BASE64 = "
|
|
4
|
-
export const TRACKER_RAW_BYTES =
|
|
5
|
-
export const TRACKER_GZIP_BYTES =
|
|
2
|
+
export const TRACKER_SOURCE = "(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL(\"events\",e.src).href;if(!t||!s)return;const o=()=>crypto.randomUUID?.()||Math.random()+\"\",n=new URLSearchParams(location.search),r=n.get(\"ref\")?.slice(0,64);let i,a,c;for(const e of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const t=n.get(\"utm_\"+e)?.slice(0,64);t&&((i??={})[\"utm_\"+e]=t)}try{a=sessionStorage._r||o(),sessionStorage._r=a,c=r||sessionStorage._a,c&&(sessionStorage._a=c),i||=sessionStorage._u&&JSON.parse(sessionStorage._u),i&&(sessionStorage._u=JSON.stringify(i))}catch{a=o(),c=r}const d=e.dataset.visitor||a;let l,p=0;const g=[],u=document.referrer?new URL(document.referrer).origin:void 0,h=e=>{const o=g.splice(0,50);if(!o.length)return;const n=()=>new Blob([JSON.stringify({siteId:t,events:o})]);let r=n();for(;r.size>64e3&&o.length>1;)g.unshift(o.pop()),r=n();e&&navigator.sendBeacon(s,r)||fetch(s,{method:\"POST\",body:r,keepalive:1}).catch(()=>{}),g.length&&h(e)},m=(e,t,s)=>{const o=Date.now();g.push({eventId:`${a}.${o.toString(36)}.${p}`,sessionId:a,visitorId:d,type:e,name:t,path:location.pathname.slice(0,256),referrer:u,timestamp:o,sequence:p++,affiliateSlug:c,...s}),clearTimeout(l),l=setTimeout(h,1200)};let f;const v=()=>{const e=location.pathname;e!==f&&(f=e,m(\"pageview\",void 0,i&&{properties:i}))};window.rastro=(e,t,s)=>{if(\"context\"===e)return{sessionId:a,visitorId:d};\"event\"===e&&m(\"custom\",`${t||\"event\"}`.slice(0,80),s&&JSON.stringify(s).length>2e4?void 0:{properties:s})},document.addEventListener(\"click\",e=>{const t=e.target?.closest(\"a,button,[data-rastro-event]\");if(!t)return;const s=t.dataset.rastroEvent,o=t.href,n=o&&new URL(o),r=/^https?:$/.test(n?.protocol),i=r&&n.origin!==location.origin;m(s?\"custom\":i?\"outbound\":\"click\",s,{target:(t.dataset.rastroLabel||t.innerText||t.tagName).slice(0,64),href:r?((i?n.origin:\"\")+n.pathname).slice(0,256):void 0})},1);const S=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),v()}};S(\"pushState\"),S(\"replaceState\"),addEventListener(\"popstate\",v),addEventListener(\"pagehide\",()=>h(1)),setInterval(()=>{m(\"heartbeat\"),h()},2e4),v()})();";
|
|
3
|
+
export const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VVXW/cNhD8KzZhCCS8Yc6OYxQSmAOC9MFFmgS95MlwY5paSUQkUiVX51xP+u+FdDqfP9I3cZbiLmdnlpwL9W5rvIt0hCr3pmvQkTRdCOhoZYJtCUjhUuaadESS0RJCfIygy1tvHfU9LmUMJkkc3h99++sjZ7hGR5EBjriQVcAiswU/pr4/jiIgdcFlu+xejaWYsGnJy6Bd7ptv364+LCUXff+npmoGuThlDJyac6xQB1N90UE3kdfeaLLeyTihAoJyskTiLGDBxFLG2hrkC7i8EFmNdGRBg8kKH/hMwZEvrln0XTDIgDWY265hwIxuWm1Lx4ARhgnxjtARuxEze7RP1VHznZ3is2yUJJzb5VJtB3G933OjSAwUNlutIsZovVuRD7pE+T30vecCXsBKg1Gh758HNJgk4S9QZQTYvn9xfJckf6w+f5KtDhFf/NYJsL84rVPTP5GCdaUtNtwKMRhNptpqNVZrVBh2bOQKH/SxttGSD32vJ85raNVibnqprm+gO+guYIEhYFjuFfQiIqQPtrQuXXubHy2gUvggYK9KGduZ9LcLMUnNyxpdSdVTtblJbWOW97W/49fPLrYdVX6VpwQ7Bad+EDc7yQTluJgkkwUZ7b/47vIC3yTJPs+7s0yUsnOxsgVxL1vfcjEpkYsMk8TptS01+SAjuvw9auMdjxBE3xdIpuIRtg1S5fOUffm8+srgzuebNMAPxFbXdo3p2SDkRDuf3DsIKOfkSVJxFAM0iiMQRPGImw+aUDp/z0VWyraLFd9Ol7vK09uTrR7kydZL8quJBP7mUoxIO9zuNXiVpxrmXl7laQ60aTFFcLrBlKDVVKUPBhxXY+DBA+dvLwXsm5h2QLbBSLppUw8R/+nQGUzb01PQRWFrqwlXdVemBqSUcRBgatThq23Qd8RrAbWKSPt1BWfni4UYpgYVc4vX6slse1FahsdKFUnCC4XQcNbqEtcW7xnM0rJJsm2DbzGQxZjaQYghu7cu9/cy6EjBP2LZFnw3E34SU0rhLLft/5A3ZLvZOO1NkoYz00XyDYPbky31/Rwdbh8I/G0hIM6uPQg1ir3szvFiuSs8fVx1HMQADy7Sef77ePBHGwkdBs5Mbc0PBgcTkUJJOpRIS2lqHzESZxruOiLv4Ho09avd7V9NNd6wnc/oqcGioocBsNs+JQavaHoGwCl/eCj8aJDXf1dEbVymJ68ljWndUrbBkze+FmBVSBI3u/9YHfq5Q7KGx+WexNQume/ozncuZ+n+jhG2u4ul/HltH/Ud1n1P0jqH4Sv+pHFBuvykGxSPBzmMxadhOQ7zfe6UMXF6EJZ4Ivp5UI1tOBMzOSv1mO/KRvJhc4032eFTcSkljsoiqdu23vA5BihgzcUwZCvORhuvSBMyAavxlWtrbXCPvOx269s4BWH9y7AusbI5MhitU/EzMb5AdOUIw1rXu3HTcFahDnSHmpiAiosBzvFiV5XgIvsPR/8Yx1QIAAA=";
|
|
4
|
+
export const TRACKER_RAW_BYTES = 2132;
|
|
5
|
+
export const TRACKER_GZIP_BYTES = 1127;
|
|
6
6
|
//# sourceMappingURL=generated.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated.js","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,
|
|
1
|
+
{"version":3,"file":"generated.js","sourceRoot":"","sources":["../../src/tracker/generated.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,MAAM,CAAC,MAAM,cAAc,GAAG,4oEAA4oE,CAAC;AAC3qE,MAAM,CAAC,MAAM,mBAAmB,GAAG,k+CAAk+C,CAAC;AACtgD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC"}
|
package/dist/tracker/tracker.js
CHANGED
|
@@ -7,9 +7,15 @@
|
|
|
7
7
|
if (!siteId || !endpoint)
|
|
8
8
|
return;
|
|
9
9
|
const createId = () => crypto.randomUUID?.() || Math.random() + "";
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
const params = new URLSearchParams(location.search);
|
|
11
|
+
const referral = params.get("ref")?.slice(0, 64);
|
|
12
|
+
// Only the utm_* campaign keys leave the page; other query parameters never do.
|
|
13
|
+
let utm;
|
|
14
|
+
for (const key of ["source", "medium", "campaign", "term", "content"]) {
|
|
15
|
+
const value = params.get("utm_" + key)?.slice(0, 64);
|
|
16
|
+
if (value)
|
|
17
|
+
(utm ??= {})["utm_" + key] = value;
|
|
18
|
+
}
|
|
13
19
|
let sessionId;
|
|
14
20
|
let affiliate;
|
|
15
21
|
try {
|
|
@@ -18,6 +24,9 @@
|
|
|
18
24
|
affiliate = referral || sessionStorage._a;
|
|
19
25
|
if (affiliate)
|
|
20
26
|
sessionStorage._a = affiliate;
|
|
27
|
+
utm ||= sessionStorage._u && JSON.parse(sessionStorage._u);
|
|
28
|
+
if (utm)
|
|
29
|
+
sessionStorage._u = JSON.stringify(utm);
|
|
21
30
|
}
|
|
22
31
|
catch {
|
|
23
32
|
sessionId = createId();
|
|
@@ -72,7 +81,7 @@
|
|
|
72
81
|
if (path === lastPath)
|
|
73
82
|
return;
|
|
74
83
|
lastPath = path;
|
|
75
|
-
emit("pageview");
|
|
84
|
+
emit("pageview", undefined, utm && { properties: utm });
|
|
76
85
|
};
|
|
77
86
|
window.rastro = (command, value, properties) => {
|
|
78
87
|
if (command === "context")
|
|
@@ -91,13 +100,16 @@
|
|
|
91
100
|
const href = element.href;
|
|
92
101
|
const url = (href && new URL(href));
|
|
93
102
|
const web = /^https?:$/.test(url?.protocol);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
103
|
+
const external = web && url.origin !== location.origin;
|
|
104
|
+
// The visible label is UI copy, never input contents; hosts override it
|
|
105
|
+
// with data-rastro-label. The server trims and collapses whitespace.
|
|
106
|
+
emit(name ? "custom" : external ? "outbound" : "click", name, {
|
|
107
|
+
target: (element.dataset.rastroLabel ||
|
|
108
|
+
element.innerText ||
|
|
109
|
+
element.tagName).slice(0, 64),
|
|
110
|
+
href: web
|
|
111
|
+
? ((external ? url.origin : "") + url.pathname).slice(0, 256)
|
|
112
|
+
: undefined,
|
|
101
113
|
});
|
|
102
114
|
}, true);
|
|
103
115
|
const wrapHistory = (method) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../src/tracker/tracker.ts"],"names":[],"mappings":";AAyCA,CAAC,GAAG,EAAE;IACJ,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAyC,CAAC;IAClE,MAAM,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC;IACpC,MAAM,QAAQ,GAAG,MAAM,EAAE,OAAO,CAAC,QAAQ;QACvC,CAAC,MAAM,EAAE,GAAG,IAAI,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ;QAAE,OAAO;IAEjC,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;IACnE,MAAM,
|
|
1
|
+
{"version":3,"file":"tracker.js","sourceRoot":"","sources":["../../src/tracker/tracker.ts"],"names":[],"mappings":"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"}
|
package/dist/tracker.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL("events",e.src).href;if(!t||!s)return;const
|
|
1
|
+
(()=>{const e=document.currentScript,t=e?.dataset.site,s=e?.dataset.endpoint||e?.src&&new URL("events",e.src).href;if(!t||!s)return;const o=()=>crypto.randomUUID?.()||Math.random()+"",n=new URLSearchParams(location.search),r=n.get("ref")?.slice(0,64);let i,a,c;for(const e of["source","medium","campaign","term","content"]){const t=n.get("utm_"+e)?.slice(0,64);t&&((i??={})["utm_"+e]=t)}try{a=sessionStorage._r||o(),sessionStorage._r=a,c=r||sessionStorage._a,c&&(sessionStorage._a=c),i||=sessionStorage._u&&JSON.parse(sessionStorage._u),i&&(sessionStorage._u=JSON.stringify(i))}catch{a=o(),c=r}const d=e.dataset.visitor||a;let l,p=0;const g=[],u=document.referrer?new URL(document.referrer).origin:void 0,h=e=>{const o=g.splice(0,50);if(!o.length)return;const n=()=>new Blob([JSON.stringify({siteId:t,events:o})]);let r=n();for(;r.size>64e3&&o.length>1;)g.unshift(o.pop()),r=n();e&&navigator.sendBeacon(s,r)||fetch(s,{method:"POST",body:r,keepalive:1}).catch(()=>{}),g.length&&h(e)},m=(e,t,s)=>{const o=Date.now();g.push({eventId:`${a}.${o.toString(36)}.${p}`,sessionId:a,visitorId:d,type:e,name:t,path:location.pathname.slice(0,256),referrer:u,timestamp:o,sequence:p++,affiliateSlug:c,...s}),clearTimeout(l),l=setTimeout(h,1200)};let f;const v=()=>{const e=location.pathname;e!==f&&(f=e,m("pageview",void 0,i&&{properties:i}))};window.rastro=(e,t,s)=>{if("context"===e)return{sessionId:a,visitorId:d};"event"===e&&m("custom",`${t||"event"}`.slice(0,80),s&&JSON.stringify(s).length>2e4?void 0:{properties:s})},document.addEventListener("click",e=>{const t=e.target?.closest("a,button,[data-rastro-event]");if(!t)return;const s=t.dataset.rastroEvent,o=t.href,n=o&&new URL(o),r=/^https?:$/.test(n?.protocol),i=r&&n.origin!==location.origin;m(s?"custom":i?"outbound":"click",s,{target:(t.dataset.rastroLabel||t.innerText||t.tagName).slice(0,64),href:r?((i?n.origin:"")+n.pathname).slice(0,256):void 0})},1);const S=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),v()}};S("pushState"),S("replaceState"),addEventListener("popstate",v),addEventListener("pagehide",()=>h(1)),setInterval(()=>{m("heartbeat"),h()},2e4),v()})();
|
package/docs/federation.md
CHANGED
|
@@ -239,7 +239,7 @@ either overview dataset.
|
|
|
239
239
|
## Control-plane status
|
|
240
240
|
|
|
241
241
|
The production control plane at `https://www.amagerastro.com` and its reference
|
|
242
|
-
implementation under `
|
|
242
|
+
implementation under `control-plane/` provide Better Auth sessions,
|
|
243
243
|
organization-scoped connections, OIDC discovery, JWKS, ten-minute RS256 tokens,
|
|
244
244
|
manifest verification, redacted site discovery, audit records, revocation of new
|
|
245
245
|
token issuance, and dynamic browser subscriptions to active deployments.
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iann29/rastro",
|
|
3
|
-
"description": "Privacy-first, real-time web analytics for Convex: live visitor atlas, journeys, revenue, goals, funnels, affiliates, and a
|
|
3
|
+
"description": "Privacy-first, real-time web analytics for Convex: live visitor atlas, journeys, revenue, goals, funnels, affiliates, and a 1.1 KB tracker.",
|
|
4
4
|
"repository": "github:amageweb/amage-rastro",
|
|
5
5
|
"homepage": "https://github.com/amageweb/amage-rastro#readme",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/amageweb/amage-rastro/issues"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.5",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"jsdelivr": "./dist/tracker.min.js",
|
|
28
28
|
"scripts": {
|
|
29
29
|
"dev": "synapse dev --start 'npm run dev:build'",
|
|
30
|
-
"dev:frontend": "cd
|
|
30
|
+
"dev:frontend": "cd control-plane && vite",
|
|
31
31
|
"dev:build": "chokidar 'tsconfig*.json' 'src/**/*.{ts,js}' 'scripts/build-tracker.mjs' -i '**/*.test.ts' -c 'npm run build:codegen' --initial",
|
|
32
|
-
"deploy:dashboard": "static-hosting upload --build --dist
|
|
32
|
+
"deploy:dashboard": "static-hosting upload --build --dist control-plane/dist --build-command \"npm exec vite -- build control-plane\"",
|
|
33
33
|
"predev": "npm run build:codegen",
|
|
34
34
|
"prebuild": "rm -rf dist *.tsbuildinfo",
|
|
35
35
|
"build": "npm run build:tracker && tsc --project ./tsconfig.build.json",
|
|
@@ -38,16 +38,17 @@
|
|
|
38
38
|
"benchmark:ingest": "node ./scripts/benchmark-ingest.mjs",
|
|
39
39
|
"benchmark:local": "vitest run src/component/benchmark.test.ts",
|
|
40
40
|
"verify:agent-docs": "node ./scripts/verify-agent-docs.mjs",
|
|
41
|
-
"build:codegen": "npm run build && synapse convex codegen --component-dir ./src/component && npm run build && synapse dev --once",
|
|
41
|
+
"build:codegen": "npm run build && synapse convex codegen --component-dir ./src/component && npm run build && npm run codegen:example && synapse dev --once",
|
|
42
|
+
"codegen:example": "npm run --workspace example codegen",
|
|
42
43
|
"build:clean": "npm run build:codegen",
|
|
43
|
-
"typecheck": "tsc --noEmit && tsc -p
|
|
44
|
+
"typecheck": "tsc --noEmit && tsc -p control-plane && tsc -p control-plane/convex && tsc -p example/convex",
|
|
44
45
|
"lint": "eslint .",
|
|
45
46
|
"test": "vitest run --typecheck",
|
|
46
47
|
"test:watch": "vitest --typecheck --clearScreen false",
|
|
47
48
|
"test:debug": "vitest --inspect-brk --no-file-parallelism",
|
|
48
49
|
"test:coverage": "vitest run --coverage --coverage.reporter=text",
|
|
49
50
|
"prepare": "npm run build",
|
|
50
|
-
"preversion": "npm run build && npm exec vite -- build
|
|
51
|
+
"preversion": "npm run build && npm exec vite -- build control-plane && npm run test && npm run lint && npm run typecheck && npm run benchmark:local && npm run verify:agent-docs"
|
|
51
52
|
},
|
|
52
53
|
"files": [
|
|
53
54
|
"dist",
|
|
@@ -149,5 +150,8 @@
|
|
|
149
150
|
"@convex-dev/better-auth": "^0.12.5",
|
|
150
151
|
"better-auth": "~1.6.15",
|
|
151
152
|
"jose": "^6.2.10"
|
|
152
|
-
}
|
|
153
|
+
},
|
|
154
|
+
"workspaces": [
|
|
155
|
+
"example"
|
|
156
|
+
]
|
|
153
157
|
}
|
|
@@ -20,13 +20,16 @@ import type * as geo from "../geo.js";
|
|
|
20
20
|
import type * as goals from "../goals.js";
|
|
21
21
|
import type * as guards from "../guards.js";
|
|
22
22
|
import type * as http from "../http.js";
|
|
23
|
+
import type * as identity from "../identity.js";
|
|
23
24
|
import type * as ingest from "../ingest.js";
|
|
24
25
|
import type * as live from "../live.js";
|
|
25
26
|
import type * as reports from "../reports.js";
|
|
26
27
|
import type * as retention from "../retention.js";
|
|
27
28
|
import type * as sanitize from "../sanitize.js";
|
|
28
29
|
import type * as sites from "../sites.js";
|
|
30
|
+
import type * as useragent from "../useragent.js";
|
|
29
31
|
import type * as validators from "../validators.js";
|
|
32
|
+
import type * as visitors from "../visitors.js";
|
|
30
33
|
|
|
31
34
|
import type {
|
|
32
35
|
ApiFromModules,
|
|
@@ -48,13 +51,16 @@ const fullApi: ApiFromModules<{
|
|
|
48
51
|
goals: typeof goals;
|
|
49
52
|
guards: typeof guards;
|
|
50
53
|
http: typeof http;
|
|
54
|
+
identity: typeof identity;
|
|
51
55
|
ingest: typeof ingest;
|
|
52
56
|
live: typeof live;
|
|
53
57
|
reports: typeof reports;
|
|
54
58
|
retention: typeof retention;
|
|
55
59
|
sanitize: typeof sanitize;
|
|
56
60
|
sites: typeof sites;
|
|
61
|
+
useragent: typeof useragent;
|
|
57
62
|
validators: typeof validators;
|
|
63
|
+
visitors: typeof visitors;
|
|
58
64
|
}> = anyApi as any;
|
|
59
65
|
|
|
60
66
|
/**
|
|
@@ -185,6 +185,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
185
185
|
latitude?: number;
|
|
186
186
|
longitude?: number;
|
|
187
187
|
os?: string;
|
|
188
|
+
visitorKey?: string;
|
|
188
189
|
};
|
|
189
190
|
events: Array<{
|
|
190
191
|
affiliateSlug?: string;
|
|
@@ -488,6 +489,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
488
489
|
utmMedium?: string;
|
|
489
490
|
utmSource?: string;
|
|
490
491
|
visitorId: string;
|
|
492
|
+
visitorKey?: string;
|
|
491
493
|
}>;
|
|
492
494
|
pageStatus?: "SplitRecommended" | "SplitRequired" | null;
|
|
493
495
|
splitCursor?: string | null;
|
|
@@ -537,6 +539,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
537
539
|
metadata: {
|
|
538
540
|
breakdowns: {
|
|
539
541
|
topBrowsers: "eventVolume";
|
|
542
|
+
topCampaigns: "eventVolume";
|
|
540
543
|
topCountries: "eventVolume";
|
|
541
544
|
topDevices: "eventVolume";
|
|
542
545
|
topEvents: "eventVolume";
|
|
@@ -552,7 +555,7 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
552
555
|
state: "complete" | "partial" | "retained" | "unavailable";
|
|
553
556
|
};
|
|
554
557
|
features: {
|
|
555
|
-
botDetection: "
|
|
558
|
+
botDetection: "userAgent";
|
|
556
559
|
errorInsights: "unsupported";
|
|
557
560
|
outboundLinks: "eventVolume";
|
|
558
561
|
sessionReplay: "unsupported";
|
|
@@ -589,6 +592,11 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
589
592
|
revenueCents: number;
|
|
590
593
|
value: string;
|
|
591
594
|
}>;
|
|
595
|
+
topCampaigns: Array<{
|
|
596
|
+
count: number;
|
|
597
|
+
revenueCents: number;
|
|
598
|
+
value: string;
|
|
599
|
+
}>;
|
|
592
600
|
topCountries: Array<{
|
|
593
601
|
count: number;
|
|
594
602
|
revenueCents: number;
|
|
@@ -926,4 +934,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
926
934
|
Name
|
|
927
935
|
>;
|
|
928
936
|
};
|
|
937
|
+
visitors: {
|
|
938
|
+
link: FunctionReference<
|
|
939
|
+
"mutation",
|
|
940
|
+
"internal",
|
|
941
|
+
{ previousVisitorId: string; siteId: string; visitorId: string },
|
|
942
|
+
{ aliasCount: number; linked: boolean },
|
|
943
|
+
Name
|
|
944
|
+
>;
|
|
945
|
+
};
|
|
929
946
|
};
|
package/src/component/http.ts
CHANGED
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
} from "./geo.js";
|
|
21
21
|
import { isPlainRecord } from "./guards.js";
|
|
22
22
|
import { sanitizeEvent } from "./sanitize.js";
|
|
23
|
+
import { deriveVisitorKey, randomSecret } from "./identity.js";
|
|
24
|
+
import { classifyClient, isKnownBot } from "./useragent.js";
|
|
23
25
|
import type { TrackerEvent } from "./validators.js";
|
|
24
26
|
import {
|
|
25
27
|
TRACKER_GZIP_BASE64,
|
|
@@ -88,9 +90,34 @@ http.route({
|
|
|
88
90
|
const edgeGeo = proxyRequestTrusted ? geoFromHeaders(request) : {};
|
|
89
91
|
const userAgent =
|
|
90
92
|
metadata.userAgent ?? request.headers.get("user-agent") ?? "";
|
|
93
|
+
// Self-declared bots never reach a transaction; the response keeps the
|
|
94
|
+
// counter shape the tracker expects and names the reason in a header.
|
|
95
|
+
if (isKnownBot(userAgent)) {
|
|
96
|
+
return json(
|
|
97
|
+
{ accepted: 0, duplicates: 0, rejected: payload.events.length },
|
|
98
|
+
200,
|
|
99
|
+
{ ...headers, "X-Rastro-Rejected": "bot" },
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
// Anonymous batches (visitorId === sessionId) get a salted daily key so
|
|
103
|
+
// repeat visits count once; the IP itself stops here.
|
|
104
|
+
const anonymous = payload.events.some(
|
|
105
|
+
(event) => event.visitorId === event.sessionId,
|
|
106
|
+
);
|
|
107
|
+
const visitorKey =
|
|
108
|
+
anonymous && clientIp.ip && userAgent
|
|
109
|
+
? await deriveVisitorKey({
|
|
110
|
+
secret: await visitorSecret(ctx),
|
|
111
|
+
now: Date.now(),
|
|
112
|
+
siteId: payload.siteId,
|
|
113
|
+
ip: clientIp.ip,
|
|
114
|
+
userAgent,
|
|
115
|
+
})
|
|
116
|
+
: undefined;
|
|
91
117
|
const context = {
|
|
92
118
|
...edgeGeo,
|
|
93
|
-
...
|
|
119
|
+
...classifyClient(userAgent, request.headers),
|
|
120
|
+
...(visitorKey ? { visitorKey } : {}),
|
|
94
121
|
};
|
|
95
122
|
|
|
96
123
|
try {
|
|
@@ -246,6 +273,13 @@ http.route({
|
|
|
246
273
|
edgeHeadersAvailable:
|
|
247
274
|
proxyRequestTrusted && Object.keys(geoFromHeaders(request)).length > 0,
|
|
248
275
|
},
|
|
276
|
+
features: {
|
|
277
|
+
botDetection: "userAgent",
|
|
278
|
+
uniqueVisitors:
|
|
279
|
+
clientIp.ip && (metadata.userAgent ?? request.headers.get("user-agent"))
|
|
280
|
+
? "dailyHash"
|
|
281
|
+
: "session",
|
|
282
|
+
},
|
|
249
283
|
},
|
|
250
284
|
200,
|
|
251
285
|
{
|
|
@@ -495,39 +529,6 @@ function decodeHeader(value: string | undefined): string | undefined {
|
|
|
495
529
|
}
|
|
496
530
|
}
|
|
497
531
|
|
|
498
|
-
function classifyUserAgent(userAgent: string): {
|
|
499
|
-
browser: string;
|
|
500
|
-
os: string;
|
|
501
|
-
device: string;
|
|
502
|
-
} {
|
|
503
|
-
const browser = /Edg\//.test(userAgent)
|
|
504
|
-
? "Edge"
|
|
505
|
-
: /Firefox\//.test(userAgent)
|
|
506
|
-
? "Firefox"
|
|
507
|
-
: /Chrome\//.test(userAgent)
|
|
508
|
-
? "Chrome"
|
|
509
|
-
: /Safari\//.test(userAgent)
|
|
510
|
-
? "Safari"
|
|
511
|
-
: "Other";
|
|
512
|
-
const os = /Android/.test(userAgent)
|
|
513
|
-
? "Android"
|
|
514
|
-
: /iPhone|iPad/.test(userAgent)
|
|
515
|
-
? "iOS"
|
|
516
|
-
: /Windows/.test(userAgent)
|
|
517
|
-
? "Windows"
|
|
518
|
-
: /Mac OS X/.test(userAgent)
|
|
519
|
-
? "macOS"
|
|
520
|
-
: /Linux/.test(userAgent)
|
|
521
|
-
? "Linux"
|
|
522
|
-
: "Other";
|
|
523
|
-
const device = /iPad|Tablet/.test(userAgent)
|
|
524
|
-
? "tablet"
|
|
525
|
-
: /Mobile|Android|iPhone/.test(userAgent)
|
|
526
|
-
? "mobile"
|
|
527
|
-
: "desktop";
|
|
528
|
-
return { browser, os, device };
|
|
529
|
-
}
|
|
530
|
-
|
|
531
532
|
type GeoIpFailureStage = "claim" | "resolve" | "apply";
|
|
532
533
|
|
|
533
534
|
function logInternalHttpError(requestId: string): void {
|
|
@@ -579,3 +580,23 @@ function json(value: unknown, status: number, headers?: HeadersInit): Response {
|
|
|
579
580
|
}
|
|
580
581
|
|
|
581
582
|
export default http;
|
|
583
|
+
|
|
584
|
+
let cachedVisitorSecret: string | undefined;
|
|
585
|
+
|
|
586
|
+
/** The per-deployment salt behind anonymous visitor keys, created on first use. */
|
|
587
|
+
async function visitorSecret(ctx: {
|
|
588
|
+
runQuery: (reference: typeof internal.identity.secret, args: Record<string, never>) => Promise<string | null>;
|
|
589
|
+
runMutation: (
|
|
590
|
+
reference: typeof internal.identity.ensureSecret,
|
|
591
|
+
args: { candidate: string },
|
|
592
|
+
) => Promise<string>;
|
|
593
|
+
}): Promise<string> {
|
|
594
|
+
if (cachedVisitorSecret) return cachedVisitorSecret;
|
|
595
|
+
const stored = await ctx.runQuery(internal.identity.secret, {});
|
|
596
|
+
cachedVisitorSecret =
|
|
597
|
+
stored ??
|
|
598
|
+
(await ctx.runMutation(internal.identity.ensureSecret, {
|
|
599
|
+
candidate: randomSecret(),
|
|
600
|
+
}));
|
|
601
|
+
return cachedVisitorSecret;
|
|
602
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { DAY_MS } from "./constants.js";
|
|
3
|
+
import { internalMutation, internalQuery } from "./_generated/server.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Anonymous visitors (no host-supplied id) are counted through a salted hash of
|
|
7
|
+
* the request IP and User-Agent that rotates every UTC day. The hash is derived
|
|
8
|
+
* in the HTTP action, so the raw IP never reaches a mutation or a table, and the
|
|
9
|
+
* salt is a per-deployment secret, so nobody holding a key can walk the IPv4
|
|
10
|
+
* space back to an address. Same device and network on the same day count once;
|
|
11
|
+
* the next day they are a new visitor by design.
|
|
12
|
+
*/
|
|
13
|
+
const KEY_HEX_LENGTH = 24;
|
|
14
|
+
const SECRET_PATTERN = /^[0-9a-f]{64}$/;
|
|
15
|
+
|
|
16
|
+
export const secret = internalQuery({
|
|
17
|
+
args: {},
|
|
18
|
+
returns: v.union(v.string(), v.null()),
|
|
19
|
+
handler: async (ctx) => {
|
|
20
|
+
const row = await ctx.db
|
|
21
|
+
.query("visitorSecrets")
|
|
22
|
+
.withIndex("by_key", (range) => range.eq("key", "current"))
|
|
23
|
+
.unique();
|
|
24
|
+
return row?.value ?? null;
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const ensureSecret = internalMutation({
|
|
29
|
+
args: { candidate: v.string() },
|
|
30
|
+
returns: v.string(),
|
|
31
|
+
handler: async (ctx, args) => {
|
|
32
|
+
const existing = await ctx.db
|
|
33
|
+
.query("visitorSecrets")
|
|
34
|
+
.withIndex("by_key", (range) => range.eq("key", "current"))
|
|
35
|
+
.unique();
|
|
36
|
+
if (existing) return existing.value;
|
|
37
|
+
if (!SECRET_PATTERN.test(args.candidate)) {
|
|
38
|
+
throw new Error("visitor secret candidate must be 64 hex characters");
|
|
39
|
+
}
|
|
40
|
+
await ctx.db.insert("visitorSecrets", {
|
|
41
|
+
key: "current",
|
|
42
|
+
value: args.candidate,
|
|
43
|
+
createdAt: Date.now(),
|
|
44
|
+
});
|
|
45
|
+
return args.candidate;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export function randomSecret(): string {
|
|
50
|
+
return hex(crypto.getRandomValues(new Uint8Array(32)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function deriveVisitorKey(input: {
|
|
54
|
+
secret: string;
|
|
55
|
+
now: number;
|
|
56
|
+
siteId: string;
|
|
57
|
+
ip: string;
|
|
58
|
+
userAgent: string;
|
|
59
|
+
}): Promise<string> {
|
|
60
|
+
const dayStart = Math.floor(input.now / DAY_MS) * DAY_MS;
|
|
61
|
+
const material = new TextEncoder().encode(
|
|
62
|
+
[input.secret, dayStart, input.siteId, input.ip, input.userAgent].join(" "),
|
|
63
|
+
);
|
|
64
|
+
const digest = new Uint8Array(
|
|
65
|
+
await crypto.subtle.digest("SHA-256", material),
|
|
66
|
+
);
|
|
67
|
+
return hex(digest).slice(0, KEY_HEX_LENGTH);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hex(bytes: Uint8Array): string {
|
|
71
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
72
|
+
"",
|
|
73
|
+
);
|
|
74
|
+
}
|