@iann29/rastro 0.6.0 → 0.8.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 (63) hide show
  1. package/README.md +90 -0
  2. package/agent/integration.md +19 -0
  3. package/agent/manifest.json +4 -4
  4. package/dist/client/federation.d.ts +34 -9
  5. package/dist/client/federation.d.ts.map +1 -1
  6. package/dist/client/federation.js +14 -2
  7. package/dist/client/federation.js.map +1 -1
  8. package/dist/client/index.d.ts +365 -5
  9. package/dist/client/index.d.ts.map +1 -1
  10. package/dist/client/index.js +98 -6
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/component/_generated/api.d.ts +2 -0
  13. package/dist/component/_generated/api.d.ts.map +1 -1
  14. package/dist/component/_generated/api.js.map +1 -1
  15. package/dist/component/_generated/component.d.ts +163 -0
  16. package/dist/component/_generated/component.d.ts.map +1 -1
  17. package/dist/component/constants.d.ts +5 -0
  18. package/dist/component/constants.d.ts.map +1 -1
  19. package/dist/component/constants.js +8 -0
  20. package/dist/component/constants.js.map +1 -1
  21. package/dist/component/ingest.d.ts.map +1 -1
  22. package/dist/component/ingest.js +7 -5
  23. package/dist/component/ingest.js.map +1 -1
  24. package/dist/component/leads.d.ts +190 -0
  25. package/dist/component/leads.d.ts.map +1 -0
  26. package/dist/component/leads.js +689 -0
  27. package/dist/component/leads.js.map +1 -0
  28. package/dist/component/reports.d.ts +3 -0
  29. package/dist/component/reports.d.ts.map +1 -1
  30. package/dist/component/reports.js +16 -5
  31. package/dist/component/reports.js.map +1 -1
  32. package/dist/component/sanitize.d.ts +3 -1
  33. package/dist/component/sanitize.d.ts.map +1 -1
  34. package/dist/component/sanitize.js +30 -1
  35. package/dist/component/sanitize.js.map +1 -1
  36. package/dist/component/schema.d.ts +122 -1
  37. package/dist/component/schema.js +39 -1
  38. package/dist/component/schema.js.map +1 -1
  39. package/dist/component/sites.d.ts +4 -0
  40. package/dist/component/sites.d.ts.map +1 -1
  41. package/dist/component/sites.js +13 -2
  42. package/dist/component/sites.js.map +1 -1
  43. package/dist/component/validators.d.ts +696 -1
  44. package/dist/component/validators.d.ts.map +1 -1
  45. package/dist/component/validators.js +130 -0
  46. package/dist/component/validators.js.map +1 -1
  47. package/dist/tracker/generated.d.ts +1 -1
  48. package/dist/tracker/generated.js +1 -1
  49. package/docs/federation-setup.md +29 -0
  50. package/docs/upgrading.md +57 -0
  51. package/llms.txt +2 -0
  52. package/package.json +1 -1
  53. package/src/component/_generated/api.ts +2 -0
  54. package/src/component/_generated/component.ts +186 -0
  55. package/src/component/constants.ts +8 -0
  56. package/src/component/ingest.ts +8 -3
  57. package/src/component/leads.ts +823 -0
  58. package/src/component/reports.ts +23 -5
  59. package/src/component/sanitize.ts +46 -1
  60. package/src/component/schema.ts +45 -0
  61. package/src/component/sites.ts +14 -1
  62. package/src/component/validators.ts +165 -0
  63. package/src/tracker/generated.ts +1 -1
@@ -56,7 +56,7 @@ import {
56
56
  } from "./eventStore.js";
57
57
  import { isPlainRecord } from "./guards.js";
58
58
  import { classifyOrigin } from "./origin.js";
59
- import { externalReferrer } from "./sanitize.js";
59
+ import { externalReferrer, normalizeRoute } from "./sanitize.js";
60
60
  import {
61
61
  affiliateFieldsValidator,
62
62
  eventFieldsValidator,
@@ -711,6 +711,7 @@ export const siteMap = query({
711
711
  window: v.union(v.object({ from: v.number(), to: v.number() }), v.null()),
712
712
  routes: v.array(siteMapRouteValidator),
713
713
  transitions: v.array(siteMapTransitionValidator),
714
+ routePatterns: v.optional(v.record(v.string(), v.array(v.string()))),
714
715
  truncated: v.object({ routes: v.boolean(), transitions: v.boolean() }),
715
716
  metadata: reportMetadataValidator.extend({
716
717
  visitors: v.object({
@@ -728,6 +729,17 @@ export const siteMap = query({
728
729
  const siteIds = validateSiteIds(args.siteIds);
729
730
  validateRange(args.from, args.to, MAX_FEATURE_ROLLUP_DAYS);
730
731
  requireAlignedRange(args.from, args.to, DAY_MS, "site map");
732
+ const routePatterns = Object.fromEntries(
733
+ await Promise.all(
734
+ siteIds.map(
735
+ async (siteId) =>
736
+ [
737
+ siteId,
738
+ (await ctx.db.get("sites", siteId))?.routePatterns ?? [],
739
+ ] as const,
740
+ ),
741
+ ),
742
+ );
731
743
  const controls = await Promise.all(
732
744
  siteIds.map((siteId) =>
733
745
  ctx.db
@@ -775,6 +787,7 @@ export const siteMap = query({
775
787
  window: null,
776
788
  routes: [],
777
789
  transitions: [],
790
+ routePatterns,
778
791
  truncated: { routes: false, transitions: false },
779
792
  metadata: metadata("unavailable"),
780
793
  };
@@ -842,7 +855,10 @@ export const siteMap = query({
842
855
  >();
843
856
  for (const row of rows) {
844
857
  if (row.bucketStart < completeFrom) continue;
845
- const current = totalsFor(row.route);
858
+ // Apply current patterns to retained rollups too, before merging sketches.
859
+ const patterns = routePatterns[row.siteId];
860
+ const route = normalizeRoute(row.route, patterns);
861
+ const current = totalsFor(route);
846
862
  current.pageviews += row.pageviews;
847
863
  current.entries += row.entries;
848
864
  current.continued += row.continued;
@@ -854,10 +870,11 @@ export const siteMap = query({
854
870
  (current.daily.get(row.bucketStart) ?? 0) + row.pageviews,
855
871
  );
856
872
  for (const target of row.outgoing) {
857
- const key = `${row.route}\u0000${target.route}`;
873
+ const to = normalizeRoute(target.route, patterns);
874
+ const key = `${route}\u0000${to}`;
858
875
  const edge = edges.get(key) ?? {
859
- from: row.route,
860
- to: target.route,
876
+ from: route,
877
+ to,
861
878
  count: 0,
862
879
  };
863
880
  edge.count += target.count;
@@ -913,6 +930,7 @@ export const siteMap = query({
913
930
  from: args.from,
914
931
  to: args.to,
915
932
  window: completeFrom > args.to ? null : { from: windowFrom, to: args.to },
933
+ routePatterns,
916
934
  routes: routeRows,
917
935
  transitions: transitionRows,
918
936
  truncated: {
@@ -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 (
@@ -1,8 +1,11 @@
1
1
  import { defineSchema, defineTable } from "convex/server";
2
2
  import { v } from "convex/values";
3
3
  import {
4
+ adCatalogFieldsValidator,
4
5
  affiliateFieldsValidator,
5
6
  aggregateFieldsValidator,
7
+ leadEventTypeValidator,
8
+ leadFieldsValidator,
6
9
  batchedEventValidator,
7
10
  funnelFieldsValidator,
8
11
  goalFieldsValidator,
@@ -391,6 +394,48 @@ export default defineSchema({
391
394
  bots: v.number(),
392
395
  }).index("by_linkId_and_dayStart_and_shard", ["linkId", "dayStart", "shard"]),
393
396
 
397
+ // Leads the host's server reports (see leads.ts), one per host conversation.
398
+ leads: defineTable(leadFieldsValidator)
399
+ .index("by_siteId_and_leadKey", ["siteId", "leadKey"])
400
+ .index("by_siteId_and_firstAt", ["siteId", "firstAt"])
401
+ .index("by_siteId_and_ad_and_firstAt", ["siteId", "ad.adId", "firstAt"]),
402
+
403
+ // What happened to a lead, append-only; `eventId` makes a retry a no-op.
404
+ leadEvents: defineTable({
405
+ siteId: v.id("sites"),
406
+ leadId: v.id("leads"),
407
+ eventId: v.string(),
408
+ type: leadEventTypeValidator,
409
+ at: v.number(),
410
+ revenueCents: v.optional(v.number()),
411
+ })
412
+ .index("by_siteId_and_eventId", ["siteId", "eventId"])
413
+ .index("by_leadId_and_at", ["leadId", "at"]),
414
+
415
+ // One row per site, UTC cohort day, dimension and key. Lead volume is human
416
+ // scale (a conversation each), so a row is never a write hot spot.
417
+ leadDailyRollups: defineTable({
418
+ siteId: v.id("sites"),
419
+ dayStart: v.number(),
420
+ dimension: v.union(v.literal("origin"), v.literal("ad")),
421
+ key: v.string(),
422
+ leads: v.number(),
423
+ qualified: v.number(),
424
+ won: v.number(),
425
+ revenueCents: v.number(),
426
+ }).index("by_siteId_and_dimension_and_dayStart_and_key", [
427
+ "siteId",
428
+ "dimension",
429
+ "dayStart",
430
+ "key",
431
+ ]),
432
+
433
+ // Names the host fetches from the Meta Marketing API for the ads it saw.
434
+ adCatalog: defineTable(adCatalogFieldsValidator).index("by_siteId_and_adId", [
435
+ "siteId",
436
+ "adId",
437
+ ]),
438
+
394
439
  visitorSecrets: defineTable({
395
440
  key: v.literal("current"),
396
441
  value: v.string(),
@@ -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
  });
@@ -372,6 +373,79 @@ export const trackedLinkFieldsValidator = v.object({
372
373
  disabledAt: v.optional(v.number()),
373
374
  });
374
375
 
376
+ /**
377
+ * Leads (0.8.0): a conversation the host's server reports, with no web session
378
+ * behind it. Today the channel is WhatsApp; the stage follows the host's
379
+ * pipeline as `lead` → `qualified` → `won`, or `lost`.
380
+ */
381
+ export const leadChannelValidator = v.literal("whatsapp");
382
+ export const leadStageValidator = v.union(
383
+ v.literal("lead"),
384
+ v.literal("qualified"),
385
+ v.literal("won"),
386
+ v.literal("lost"),
387
+ );
388
+ /** What the host may move a lead to; `open` undoes a sale or a loss. */
389
+ export const leadStageChangeValidator = v.union(
390
+ v.literal("qualified"),
391
+ v.literal("won"),
392
+ v.literal("lost"),
393
+ v.literal("open"),
394
+ );
395
+ export const leadEventTypeValidator = v.union(
396
+ v.literal("created"),
397
+ v.literal("qualified"),
398
+ v.literal("won"),
399
+ v.literal("lost"),
400
+ v.literal("open"),
401
+ );
402
+
403
+ /** The Meta Click-to-WhatsApp ad a lead came from, as the message carried it. */
404
+ export const leadAdValidator = v.object({
405
+ adId: v.optional(v.string()),
406
+ sourceApp: v.optional(v.string()),
407
+ sourceUrl: v.optional(v.string()),
408
+ ctwaClid: v.optional(v.string()),
409
+ title: v.optional(v.string()),
410
+ });
411
+
412
+ export const leadFieldsValidator = v.object({
413
+ siteId: v.id("sites"),
414
+ /** The host's opaque id for the lead (never a phone number). */
415
+ leadKey: v.string(),
416
+ channel: leadChannelValidator,
417
+ /** The channel's raw entry point, as the host received it. */
418
+ entryPoint: v.optional(v.string()),
419
+ /** The bucket reports group by: `ctwa_ad` for any ad, else the entry point or `none`. */
420
+ originKey: v.string(),
421
+ ad: v.optional(leadAdValidator),
422
+ /** A short display name the host chooses to share, if any. */
423
+ label: v.optional(v.string()),
424
+ /** The web visitor this lead came from, when a tracked link bridged them. */
425
+ visitorId: v.optional(v.string()),
426
+ stage: leadStageValidator,
427
+ revenueCents: v.number(),
428
+ firstAt: v.number(),
429
+ /** UTC day of `firstAt`: the cohort every later stage is counted in. */
430
+ dayStart: v.number(),
431
+ stageAt: v.number(),
432
+ qualifiedAt: v.optional(v.number()),
433
+ wonAt: v.optional(v.number()),
434
+ updatedAt: v.number(),
435
+ });
436
+
437
+ export const adCatalogFieldsValidator = v.object({
438
+ siteId: v.id("sites"),
439
+ adId: v.string(),
440
+ adName: v.optional(v.string()),
441
+ adsetId: v.optional(v.string()),
442
+ adsetName: v.optional(v.string()),
443
+ campaignId: v.optional(v.string()),
444
+ campaignName: v.optional(v.string()),
445
+ thumbnailUrl: v.optional(v.string()),
446
+ updatedAt: v.number(),
447
+ });
448
+
375
449
  export const ingestResultValidator = v.object({
376
450
  accepted: v.number(),
377
451
  duplicates: v.number(),
@@ -398,3 +472,94 @@ export type FunnelStep = Infer<typeof funnelStepValidator>;
398
472
  export type EventType = Infer<typeof eventTypeValidator>;
399
473
  export type Channel = Infer<typeof channelValidator>;
400
474
  export type Evidence = Infer<typeof evidenceValidator>;
475
+
476
+ // Lead reports. Ids are plain strings so the host's own functions can return
477
+ // these shapes unchanged across the component boundary.
478
+ const leadCountersFields = {
479
+ leads: v.number(),
480
+ qualified: v.number(),
481
+ won: v.number(),
482
+ revenueCents: v.number(),
483
+ };
484
+
485
+ export const leadAdRowValidator = v.object({
486
+ adId: v.string(),
487
+ adName: v.union(v.string(), v.null()),
488
+ sourceApp: v.union(v.string(), v.null()),
489
+ sourceUrl: v.union(v.string(), v.null()),
490
+ title: v.union(v.string(), v.null()),
491
+ thumbnailUrl: v.union(v.string(), v.null()),
492
+ ...leadCountersFields,
493
+ });
494
+
495
+ export const leadAdsetRowValidator = v.object({
496
+ adsetId: v.union(v.string(), v.null()),
497
+ adsetName: v.union(v.string(), v.null()),
498
+ ...leadCountersFields,
499
+ ads: v.array(leadAdRowValidator),
500
+ });
501
+
502
+ export const leadCampaignRowValidator = v.object({
503
+ campaignId: v.union(v.string(), v.null()),
504
+ campaignName: v.union(v.string(), v.null()),
505
+ ...leadCountersFields,
506
+ adsets: v.array(leadAdsetRowValidator),
507
+ });
508
+
509
+ export const leadsReportValidator = v.object({
510
+ from: v.number(),
511
+ to: v.number(),
512
+ /** The sites' currency, or null when they differ (revenue then mixes). */
513
+ currency: v.union(v.string(), v.null()),
514
+ totals: v.object(leadCountersFields),
515
+ origins: v.array(v.object({ key: v.string(), ...leadCountersFields })),
516
+ /**
517
+ * Campaign → ad set → ad, with ads the catalog does not name yet grouped
518
+ * under a null campaign and a null ad set.
519
+ */
520
+ campaigns: v.array(leadCampaignRowValidator),
521
+ });
522
+
523
+ export const leadSummaryValidator = v.object({
524
+ leadId: v.string(),
525
+ siteId: v.string(),
526
+ label: v.union(v.string(), v.null()),
527
+ channel: leadChannelValidator,
528
+ originKey: v.string(),
529
+ entryPoint: v.union(v.string(), v.null()),
530
+ adId: v.union(v.string(), v.null()),
531
+ sourceApp: v.union(v.string(), v.null()),
532
+ stage: leadStageValidator,
533
+ revenueCents: v.number(),
534
+ firstAt: v.number(),
535
+ stageAt: v.number(),
536
+ /** Whether a web journey is linked, through a tracked link. */
537
+ hasVisitor: v.boolean(),
538
+ });
539
+
540
+ export const leadJourneyValidator = v.union(
541
+ v.null(),
542
+ v.object({
543
+ lead: leadSummaryValidator,
544
+ ad: v.union(
545
+ v.null(),
546
+ v.object({
547
+ adId: v.union(v.string(), v.null()),
548
+ adName: v.union(v.string(), v.null()),
549
+ adsetName: v.union(v.string(), v.null()),
550
+ campaignName: v.union(v.string(), v.null()),
551
+ sourceApp: v.union(v.string(), v.null()),
552
+ sourceUrl: v.union(v.string(), v.null()),
553
+ title: v.union(v.string(), v.null()),
554
+ }),
555
+ ),
556
+ visitorId: v.union(v.string(), v.null()),
557
+ events: v.array(
558
+ v.object({
559
+ type: leadEventTypeValidator,
560
+ at: v.number(),
561
+ revenueCents: v.union(v.number(), v.null()),
562
+ }),
563
+ ),
564
+ }),
565
+ );
@@ -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.8.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;