@iann29/rastro 0.6.0 → 0.7.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 (40) hide show
  1. package/README.md +27 -0
  2. package/agent/integration.md +8 -0
  3. package/agent/manifest.json +2 -2
  4. package/dist/client/index.d.ts +15 -2
  5. package/dist/client/index.d.ts.map +1 -1
  6. package/dist/client/index.js +8 -1
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/component/_generated/component.d.ts +5 -0
  9. package/dist/component/_generated/component.d.ts.map +1 -1
  10. package/dist/component/ingest.d.ts.map +1 -1
  11. package/dist/component/ingest.js +7 -5
  12. package/dist/component/ingest.js.map +1 -1
  13. package/dist/component/reports.d.ts +3 -0
  14. package/dist/component/reports.d.ts.map +1 -1
  15. package/dist/component/reports.js +16 -5
  16. package/dist/component/reports.js.map +1 -1
  17. package/dist/component/sanitize.d.ts +3 -1
  18. package/dist/component/sanitize.d.ts.map +1 -1
  19. package/dist/component/sanitize.js +30 -1
  20. package/dist/component/sanitize.js.map +1 -1
  21. package/dist/component/schema.d.ts +3 -1
  22. package/dist/component/sites.d.ts +4 -0
  23. package/dist/component/sites.d.ts.map +1 -1
  24. package/dist/component/sites.js +13 -2
  25. package/dist/component/sites.js.map +1 -1
  26. package/dist/component/validators.d.ts +3 -1
  27. package/dist/component/validators.d.ts.map +1 -1
  28. package/dist/component/validators.js +1 -0
  29. package/dist/component/validators.js.map +1 -1
  30. package/dist/tracker/generated.d.ts +1 -1
  31. package/dist/tracker/generated.js +1 -1
  32. package/docs/upgrading.md +27 -0
  33. package/package.json +1 -1
  34. package/src/component/_generated/component.ts +5 -0
  35. package/src/component/ingest.ts +8 -3
  36. package/src/component/reports.ts +23 -5
  37. package/src/component/sanitize.ts +46 -1
  38. package/src/component/sites.ts +14 -1
  39. package/src/component/validators.ts +1 -0
  40. package/src/tracker/generated.ts +1 -1
@@ -82,6 +82,36 @@ const NUMERIC_SEGMENT = /^\d+$/;
82
82
  const UUID_SEGMENT =
83
83
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
84
84
  const HEX_SEGMENT = /^[0-9a-f]{16,}$/i;
85
+ const ROUTE_PARAMETER = /^:[A-Za-z][A-Za-z0-9_]*$/;
86
+
87
+ /** Ordered, full-path patterns; no regex or wildcard execution from settings. */
88
+ export function validateRoutePatterns(patterns: readonly string[]): string[] {
89
+ if (patterns.length > 32)
90
+ throw new Error("At most 32 route patterns are allowed");
91
+ return [
92
+ ...new Set(
93
+ patterns.map((value) => {
94
+ const pattern = value.trim().replace(/\/$/, "");
95
+ const segments = pattern.slice(1).split("/");
96
+ if (
97
+ !pattern.startsWith("/") ||
98
+ pattern.length > 256 ||
99
+ /[\s?#*\\]/.test(pattern) ||
100
+ !segments.some((segment) => ROUTE_PARAMETER.test(segment)) ||
101
+ segments.some(
102
+ (segment) =>
103
+ !segment ||
104
+ segment === "." ||
105
+ segment === ".." ||
106
+ (segment.includes(":") && !ROUTE_PARAMETER.test(segment)),
107
+ )
108
+ )
109
+ throw new Error("Use full route patterns such as /pedido/:codigo");
110
+ return pattern;
111
+ }),
112
+ ),
113
+ ];
114
+ }
85
115
 
86
116
  /**
87
117
  * Collapses the identifiers that unmistakably vary per record — numbers,
@@ -91,10 +121,25 @@ const HEX_SEGMENT = /^[0-9a-f]{16,}$/i;
91
121
  * spellings serve the same page. Only the map uses this; every other report
92
122
  * keeps the sanitized path exactly as the tracker sent it.
93
123
  */
94
- export function normalizeRoute(path: string): string {
124
+ export function normalizeRoute(
125
+ path: string,
126
+ patterns: readonly string[] = [],
127
+ ): string {
95
128
  const trimmed =
96
129
  path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
97
130
  const segments = trimmed.split("/");
131
+ for (const pattern of patterns) {
132
+ const parts = pattern.split("/");
133
+ if (
134
+ parts.length === segments.length &&
135
+ parts.every(
136
+ (part, index) =>
137
+ part === segments[index] ||
138
+ (ROUTE_PARAMETER.test(part) && Boolean(segments[index])),
139
+ )
140
+ )
141
+ return pattern;
142
+ }
98
143
  for (let index = 1; index < segments.length; index += 1) {
99
144
  const segment = segments[index] ?? "";
100
145
  if (
@@ -12,6 +12,7 @@ import {
12
12
  sanitizeCurrency,
13
13
  sanitizeOpaqueId,
14
14
  validateTimezone,
15
+ validateRoutePatterns,
15
16
  } from "./sanitize.js";
16
17
  import { siteFieldsValidator } from "./validators.js";
17
18
 
@@ -29,6 +30,7 @@ export const create = mutation({
29
30
  timezone: v.optional(v.string()),
30
31
  currency: v.optional(v.string()),
31
32
  cookieless: v.optional(v.boolean()),
33
+ routePatterns: v.optional(v.array(v.string())),
32
34
  },
33
35
  returns: v.id("sites"),
34
36
  handler: async (ctx, args) => {
@@ -41,6 +43,7 @@ export const create = mutation({
41
43
  let timezone: string | undefined;
42
44
  let networkId: string | undefined;
43
45
  let currency: string;
46
+ let routePatterns: string[];
44
47
  try {
45
48
  domains = normalizeDomains(args.domains);
46
49
  timezone = validateTimezone(args.timezone);
@@ -48,6 +51,7 @@ export const create = mutation({
48
51
  ? sanitizeOpaqueId(args.networkId, "networkId")
49
52
  : undefined;
50
53
  currency = sanitizeCurrency(args.currency ?? "USD");
54
+ routePatterns = validateRoutePatterns(args.routePatterns ?? []);
51
55
  } catch (error) {
52
56
  fail(
53
57
  "INVALID_SITE",
@@ -69,7 +73,10 @@ export const create = mutation({
69
73
  existing.networkId !== networkId ||
70
74
  existing.timezone !== timezone ||
71
75
  (existing.currency ?? "USD") !== currency ||
72
- existing.cookieless !== cookieless
76
+ existing.cookieless !== cookieless ||
77
+ (args.routePatterns !== undefined &&
78
+ JSON.stringify(existing.routePatterns ?? []) !==
79
+ JSON.stringify(routePatterns))
73
80
  ) {
74
81
  fail(
75
82
  "CONFLICT",
@@ -99,6 +106,7 @@ export const create = mutation({
99
106
  timezone,
100
107
  currency,
101
108
  cookieless,
109
+ routePatterns,
102
110
  createdAt: now,
103
111
  updatedAt: now,
104
112
  });
@@ -116,6 +124,7 @@ export const update = mutation({
116
124
  timezone: v.optional(v.union(v.string(), v.null())),
117
125
  currency: v.optional(v.string()),
118
126
  cookieless: v.optional(v.boolean()),
127
+ routePatterns: v.optional(v.array(v.string())),
119
128
  },
120
129
  returns: v.null(),
121
130
  handler: async (ctx, args) => {
@@ -129,6 +138,7 @@ export const update = mutation({
129
138
  timezone?: string | undefined;
130
139
  currency?: string;
131
140
  cookieless?: boolean;
141
+ routePatterns?: string[];
132
142
  updatedAt: number;
133
143
  } = { updatedAt: Date.now() };
134
144
  if (args.name !== undefined) {
@@ -161,6 +171,9 @@ export const update = mutation({
161
171
  if (args.currency !== undefined) {
162
172
  patch.currency = sanitizeCurrency(args.currency);
163
173
  }
174
+ if (args.routePatterns !== undefined) {
175
+ patch.routePatterns = validateRoutePatterns(args.routePatterns);
176
+ }
164
177
  } catch (error) {
165
178
  fail(
166
179
  "INVALID_SITE",
@@ -161,6 +161,7 @@ export const siteFieldsValidator = v.object({
161
161
  timezone: v.optional(v.string()),
162
162
  currency: v.optional(v.string()),
163
163
  cookieless: v.boolean(),
164
+ routePatterns: v.optional(v.array(v.string())),
164
165
  createdAt: v.number(),
165
166
  updatedAt: v.number(),
166
167
  });
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/build-tracker.mjs. Do not edit.
2
- export const RASTRO_VERSION = "0.6.0";
2
+ export const RASTRO_VERSION = "0.7.0";
3
3
  export const TRACKER_SOURCE = "(()=>{const t=document.currentScript,e=t?.dataset.site,i=t?.dataset.endpoint||t?.src&&new URL(\"events\",t.src).href;if(!e||!i)return;const n=()=>crypto.randomUUID?.()||Math.random()+\"\",r=new URLSearchParams(location.search),s=r.get(\"ref\")?.slice(0,64);let o;for(const t of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const e=r.get(\"utm_\"+t)?.slice(0,64);e&&((o??={})[\"utm_\"+t]=e)}const a=\"gclid gbraid wbraid msclkid ttclid twclid li_fat_id ScCid rdt_cid dclid epik srsltid mc_cid mc_eid _hsenc igsh igshid fbclid\".split(\" \").filter(t=>r.has(t)).slice(0,8);let c,d,l,h,f=t.dataset.visitor||\"\",p=Boolean(f);try{f||=localStorage._rv||=n()}catch{}try{d=sessionStorage,l=d._r,c=d._ri==f&&d._rt,h=s||d._a,h&&(d._a=h),o||=d._u&&JSON.parse(d._u),o&&(d._u=JSON.stringify(o))}catch{h=s}let v,g=0;const u=[];let m=document.referrer.split(\"/\",3).join(\"/\")||void 0;const _=t=>{const n=u.splice(0,50);if(!n.length)return;const r=()=>new Blob([JSON.stringify({siteId:e,events:n,sentAt:Date.now()})]);let s=r();for(;s.size>64e3&&n.length>1;)u.unshift(n.pop()),s=r();t&&navigator.sendBeacon(i,s)||fetch(i,{method:\"POST\",body:s,keepalive:1}).catch(()=>{}),u.length&&_(t)},y=(t,e,i)=>{const r=Date.now(),s=r-c;c=r,s<18e5||(l=n(),\"pageview\"!=t&&(S=\"\",w()));try{d._r=l,d._ri=f,d._rt=r}catch{}u.push({eventId:`${l}.${r.toString(36)}.${g}`,sessionId:l,visitorId:f||l,type:t,name:e,path:location.pathname.slice(0,256),referrer:m,timestamp:r,sequence:g++,affiliateSlug:h,...i}),clearTimeout(v),v=setTimeout(_,1200)};let S;const w=()=>{const t=location.pathname;t!=S&&(S=t,y(\"pageview\",void 0,{properties:o,clid:a.length?a:void 0}))};window.rastro=(e,i,r)=>{if(\"context\"==e)return{sessionId:l,visitorId:f||l,identified:p};if(\"identify\"==e||\"reset\"==e){if(\"identify\"==e&&(!i||i.length>128||!/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/.test(i)))return;if(\"identify\"==e&&i==f)return;_();const r=\"reset\"==e||p;if(p=\"identify\"==e,f=\"identify\"==e?i:n(),t.dataset.visitor=\"identify\"==e?f:\"\",r){h=void 0,o=void 0,m=void 0,a.length=0;try{\"reset\"==e&&(localStorage._rv=f),d._a=\"\",d._u=\"\"}catch{}}c=0,g=0,S=\"\",w()}\"event\"==e&&y(\"custom\",`${i||\"event\"}`.slice(0,80),r&&JSON.stringify(r).length>2e4?void 0:{properties:r})},document.addEventListener(\"click\",t=>{const e=t.target?.closest(\"a,button,[data-rastro-event]\");if(!e)return;const i=e.dataset.rastroEvent,n=e.href,r=n&&new URL(n),s=/^https?:$/.test(r?.protocol),o=s&&r.origin!=location.origin;y(i?\"custom\":o?\"outbound\":\"click\",i,{target:(e.dataset.rastroLabel||e.innerText||e.tagName).slice(0,64),href:s?((o?r.origin:\"\")+r.pathname).slice(0,256):void 0})},1);for(const t of[\"pushState\",\"replaceState\"]){const e=history[t];history[t]=(...t)=>{e.apply(history,t),w()}}addEventListener(\"popstate\",w);let b=0;const I=()=>{y(\"heartbeat\"),_()},L=()=>{b&&(clearInterval(b),b=0,y(\"leave\"),_(1))},E=()=>{document.hidden?L():b||(I(),b=setInterval(I,2e4))};addEventListener(\"pagehide\",L),addEventListener(\"pageshow\",E),document.addEventListener(\"visibilitychange\",E),b=setInterval(I,2e4),w(),E()})();";
4
4
  export const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA31W23LbOBL9FRmVYgHrNiI7l8pSi6gmO3nwludSq+RlXR4FIpsiNhTAAZrSaET++xRIUYrt1LyQALqBbjTOOQDnQr0/ZM4GmpDKXdZs0JLMGu/R0iLzpiZARXOZa9IBSQZDCObbEbR57YyltqW5DD5LEou7yef/3nGGW7QUGFAcF7L0WMxMwS+wbS+M8EiNt7MhulUxlczva3LSa5u7zefPtz/OJRdt+5Om8jjIxSVj4NUxxgK1z8pftdebwCuXaTLOytCPCgjKyzUSZx4LJuYyVCZDPoW3r8WsQpq4WeE8P25/4op7FlzjM2TANpibZsOAZXpTa7O2DBih70ecJbTEHsSxcjiGaWizZJf0JBImCeduPleHTtyPPg8KRTdM14qts8rkk/XKa5NPdsNvE7Lqq8knRL2Rdv2vMstC09Lkk0X2b5NPfE7LzOSTvLdibb5Ogg8VxQWy3rLJlmjyybIMaLOJWYey/5h8UqziJCZDXRnibMKELExF6Dmp916WOnAS4rSXd0PRMsihghIKRScMbE0w5HzbMga1+uBchdryQszI7w9F26p4NNWCnNdrlEu/bVtluegyTVl56KJXrgKGYJw9ekGlcrn0kPU/o1SRJLFFUKrQtrlcaiiThMeGKgW4to2eTZL8Z/HLz7LWPmA0NgLc4Nao3hLIG7s2xZ47MWZQqtDFvW1hraZHRDbq/qHf8OZMDI8Feo9+LNlLBq+E/L8zNrZF226dySfjCktFJ3ZZ1fST+kq+mYqeB1ZWaNdUPqaC76kQAf6hcit+/yTrQ6TgbZ4iDPRKLQS09AOlP2pCad2Oi048DIcVlOeiR/ksyGD+xPdvX+OrJBkjv7+eiUY2NpSmIG5l7WoueuJwMaMksXpr1pqclwFt/gF15iw3EETbFkhZyQ0cNkily1P26y+LTwxWLt+nAb4i1royW0yvOyH7KvNebDoBzTF4kiw5iQ72ihMgmLMWeXXeS0zmKptlykP41/U7fNO2vIroAVbrNW4N7tiFoiThC8UY7LgQA+4iWlQFA3yK/k/Kj5hrZN2Ekh/6It7m6ZcXh6qTLw5eklv0xeav3oo4su6+wBGbt3lawRHst3latG0FtK8xJbB6gylCralMT0IUe9Fw4tDNm7cCRhSlGyCzwUB6U6ceAv7eoM0wXV9egi4KUxlNuKiadVqClNJ0ArIKtf9kNuga4lsBWxWQxv4Srm+mU9H1J784ommnHmn8s9RmdKEWffEI9vxcUhigDIfauxo9GQypgygYqT6e31yng1MnRDfbGZu7nfQ6kHeKIxjwMbIp+CCZfxBTCo9YP/xNRU2OlkxhME/rLhKFHUf2cYG2ZR4DDosdnlqThF+YtjUnfN+8a9uLl7/d/3D1P3315/Tqnw/n5lKmVw//ePFSEgbiRoiRiM9XjQI0WpdcnKh6zqVt6zivVo9mQvG4PzdphO4z6XziVaTxlhOHUh2PwY2NzdgYT0FNe7SfE0kS/lRuVSGgF0rGoFdCxkYedJmaRtWDkT3dcG0PK+05y5pAbsPgy4uDadujsftyvhamAvxRdc8q5cV4Ajf4ej6knH6LJd+JDk7CqvP8Y1z4zgRCi56zrDLZVwZnBUVFkrRfI81lVrkQj4xpWDVEzsJ9rOfVAL6rPscHNogsPlZXo/BU+8G9DwxWYf9AiW+L8xPGRvl5+VtJVId5OiLFz2XtHbnMVQKcCknipfNmbezFmWDDwGzPzXysYermzDW0co3NWTpu0cBh2FfKn6Z2p1dYtS1KYy36T/gHxQ7p9c96g+LbVwbE3NMwjy+NMZmUMXHpT0wXj1ToRN0OrsWzd1DUxgVpik8hj3WlMxy63zx6ShPI+f09PczOTcWllBR5j1LXdbXnRxuQ6NHVPT/q2tVhiLUbbq3V6RK+HdRrz1mJ2tMKNTEBSy46uBtMqyThvSreWkK/1RVfCVipaRSzCvUWe/9rITr4OMw4ga40eY52fsdFumpbfsvjxIB0WukWbvB11Lbv5KzXWJocGdwJ+L45lG7H4KP4O5RH9q9MZWifldqusff/XhKxdvAx3uxczP4CGKXLCLcLAAA=";
5
5
  export const TRACKER_RAW_BYTES = 2999;