@iann29/rastro 0.4.0 → 0.6.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 (112) hide show
  1. package/README.md +146 -24
  2. package/agent/integration.md +66 -29
  3. package/agent/manifest.json +19 -8
  4. package/agent/manifest.schema.json +28 -9
  5. package/dist/client/federation.d.ts +38 -8
  6. package/dist/client/federation.d.ts.map +1 -1
  7. package/dist/client/federation.js +18 -1
  8. package/dist/client/federation.js.map +1 -1
  9. package/dist/client/identity.d.ts +11 -0
  10. package/dist/client/identity.d.ts.map +1 -0
  11. package/dist/client/identity.js +123 -0
  12. package/dist/client/identity.js.map +1 -0
  13. package/dist/client/index.d.ts +501 -11
  14. package/dist/client/index.d.ts.map +1 -1
  15. package/dist/client/index.js +220 -4
  16. package/dist/client/index.js.map +1 -1
  17. package/dist/component/_generated/api.d.ts +8 -0
  18. package/dist/component/_generated/api.d.ts.map +1 -1
  19. package/dist/component/_generated/api.js.map +1 -1
  20. package/dist/component/_generated/component.d.ts +158 -1
  21. package/dist/component/_generated/component.d.ts.map +1 -1
  22. package/dist/component/constants.d.ts +2 -0
  23. package/dist/component/constants.d.ts.map +1 -1
  24. package/dist/component/constants.js +7 -0
  25. package/dist/component/constants.js.map +1 -1
  26. package/dist/component/coverage.d.ts +1 -0
  27. package/dist/component/coverage.d.ts.map +1 -1
  28. package/dist/component/coverage.js +6 -1
  29. package/dist/component/coverage.js.map +1 -1
  30. package/dist/component/eventStore.d.ts +2 -0
  31. package/dist/component/eventStore.d.ts.map +1 -1
  32. package/dist/component/http.d.ts.map +1 -1
  33. package/dist/component/http.js +51 -1
  34. package/dist/component/http.js.map +1 -1
  35. package/dist/component/ingest.d.ts +2 -0
  36. package/dist/component/ingest.d.ts.map +1 -1
  37. package/dist/component/ingest.js +72 -22
  38. package/dist/component/ingest.js.map +1 -1
  39. package/dist/component/origin.d.ts +70 -0
  40. package/dist/component/origin.d.ts.map +1 -0
  41. package/dist/component/origin.js +230 -0
  42. package/dist/component/origin.js.map +1 -0
  43. package/dist/component/people.d.ts +79 -0
  44. package/dist/component/people.d.ts.map +1 -0
  45. package/dist/component/people.js +249 -0
  46. package/dist/component/people.js.map +1 -0
  47. package/dist/component/platforms.d.ts +33 -0
  48. package/dist/component/platforms.d.ts.map +1 -0
  49. package/dist/component/platforms.js +328 -0
  50. package/dist/component/platforms.js.map +1 -0
  51. package/dist/component/reports.d.ts +35 -74
  52. package/dist/component/reports.d.ts.map +1 -1
  53. package/dist/component/reports.js +59 -14
  54. package/dist/component/reports.js.map +1 -1
  55. package/dist/component/sanitize.d.ts +5 -0
  56. package/dist/component/sanitize.d.ts.map +1 -1
  57. package/dist/component/sanitize.js +33 -1
  58. package/dist/component/sanitize.js.map +1 -1
  59. package/dist/component/schema.d.ts +101 -7
  60. package/dist/component/schema.js +45 -1
  61. package/dist/component/schema.js.map +1 -1
  62. package/dist/component/trackedLinks.d.ts +91 -0
  63. package/dist/component/trackedLinks.d.ts.map +1 -0
  64. package/dist/component/trackedLinks.js +314 -0
  65. package/dist/component/trackedLinks.js.map +1 -0
  66. package/dist/component/useragent.d.ts +6 -0
  67. package/dist/component/useragent.d.ts.map +1 -1
  68. package/dist/component/useragent.js +9 -0
  69. package/dist/component/useragent.js.map +1 -1
  70. package/dist/component/validators.d.ts +101 -11
  71. package/dist/component/validators.d.ts.map +1 -1
  72. package/dist/component/validators.js +62 -1
  73. package/dist/component/validators.js.map +1 -1
  74. package/dist/component/visitors.d.ts +38 -2
  75. package/dist/component/visitors.d.ts.map +1 -1
  76. package/dist/component/visitors.js +162 -42
  77. package/dist/component/visitors.js.map +1 -1
  78. package/dist/react/index.d.ts +9 -5
  79. package/dist/react/index.d.ts.map +1 -1
  80. package/dist/react/index.js +36 -5
  81. package/dist/react/index.js.map +1 -1
  82. package/dist/tracker/generated.d.ts +6 -6
  83. package/dist/tracker/generated.d.ts.map +1 -1
  84. package/dist/tracker/generated.js +6 -6
  85. package/dist/tracker/generated.js.map +1 -1
  86. package/dist/tracker/tracker.d.ts +3 -1
  87. package/dist/tracker/tracker.d.ts.map +1 -1
  88. package/dist/tracker/tracker.js +56 -5
  89. package/dist/tracker/tracker.js.map +1 -1
  90. package/dist/tracker.min.js +1 -1
  91. package/docs/federation.md +24 -0
  92. package/docs/identity.md +307 -0
  93. package/docs/upgrading.md +190 -19
  94. package/llms.txt +11 -2
  95. package/package.json +5 -3
  96. package/src/component/_generated/api.ts +8 -0
  97. package/src/component/_generated/component.ts +251 -1
  98. package/src/component/constants.ts +7 -0
  99. package/src/component/coverage.ts +7 -1
  100. package/src/component/http.ts +56 -1
  101. package/src/component/ingest.ts +100 -28
  102. package/src/component/origin.ts +273 -0
  103. package/src/component/people.ts +321 -0
  104. package/src/component/platforms.ts +359 -0
  105. package/src/component/reports.ts +84 -13
  106. package/src/component/sanitize.ts +39 -1
  107. package/src/component/schema.ts +53 -0
  108. package/src/component/trackedLinks.ts +384 -0
  109. package/src/component/useragent.ts +11 -0
  110. package/src/component/validators.ts +120 -0
  111. package/src/component/visitors.ts +232 -55
  112. package/src/tracker/generated.ts +6 -6
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [![CI](https://github.com/amageweb/amage-rastro/actions/workflows/ci.yml/badge.svg)](https://github.com/amageweb/amage-rastro/actions/workflows/ci.yml)
4
4
 
5
- Privacy-first, real-time web analytics built as an isolated Convex component.
5
+ Real-time product and web analytics built as an isolated Convex component.
6
6
 
7
- Amage Rastro combines a 1.1 KB gzip browser tracker with reactive traffic
7
+ Amage Rastro combines a lightweight browser tracker with reactive traffic
8
8
  metrics, a live visitor atlas, journey timelines, trusted payment attribution,
9
9
  goals, funnels, affiliates, and multi-site reports. Data and functions stay
10
10
  inside your Convex deployment.
@@ -55,7 +55,7 @@ capacity claim.
55
55
  | Auto-captured clicks | Anchors, buttons, and `data-rastro-event` controls with a bounded visible label; outbound origin+path |
56
56
  | Bot filtering | Self-declared crawlers, unfurlers, monitors, HTTP libraries, and headless browsers rejected at ingestion |
57
57
  | Cookieless mode | No cookies: pseudonymous visitor id in `localStorage`; sessions end after 30 idle minutes or a sign-in |
58
- | Tiny tracker | 2,407 bytes raw, 1,278 bytes gzip; the test suite enforces `< 1,280` bytes gzip |
58
+ | Tiny tracker | 2,999 bytes raw, 1,565 bytes gzip; the test suite enforces `< 1,700` bytes gzip |
59
59
 
60
60
  ## Quickstart
61
61
 
@@ -82,7 +82,7 @@ Never append `/rastro/tracker.js` to the functions URL.
82
82
  npm install @iann29/rastro
83
83
  ```
84
84
 
85
- The untagged npm `latest` channel is `0.4.0`. Pin the exact version in the host;
85
+ The untagged npm `latest` channel is `0.6.0`. Pin the exact version in the host;
86
86
  the changelog and the upgrading guide say what each release changes.
87
87
 
88
88
  ### 2. Mount the component
@@ -237,8 +237,9 @@ until it reloads.
237
237
 
238
238
  The tracker sends no client identification. Ingestion derives `browser`, `os`,
239
239
  and `device` from headers the browser attaches itself, and persists only those
240
- three normalized values; the raw `User-Agent` never reaches a mutation or a
241
- component table.
240
+ three normalized values; it also reads the in-app browser's platform id
241
+ (`instagram`, `facebook`, `tiktok`…) for the session's origin, below. The raw
242
+ `User-Agent` never reaches a mutation or a component table.
242
243
 
243
244
  Chromium attaches the low-entropy `Sec-CH-UA`, `Sec-CH-UA-Mobile`, and
244
245
  `Sec-CH-UA-Platform` client hints to cross-origin requests from secure origins,
@@ -499,10 +500,11 @@ export function Analytics({ siteId, siteUrl, analyticsId }) {
499
500
  }
500
501
  ```
501
502
 
502
- `visitorId` must be pseudonymous and supplied before the first pageview. Reuse
503
- the same value on authorized sites to connect their journeys. Amage Rastro
504
- intentionally has no mutable post-load `identify` command: changing identity
505
- after a session starts would make the session ambiguous.
503
+ Supply a server-issued `visitorId` before the first pageview when available. For
504
+ login after tracking starts, use the `identify` command described in the
505
+ [identity guide](docs/identity.md); it closes the previous session before
506
+ switching IDs. Call `reset` on logout. Released 0.5.x trackers require a reload
507
+ to change the initial ID.
506
508
 
507
509
  ### 5. Verify ingestion
508
510
 
@@ -577,19 +579,132 @@ payload-bound by the site's financial ledger.
577
579
  The tracker reads `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, and
578
580
  `utm_content` from the landing URL, keeps them in `sessionStorage` for the rest
579
581
  of the tab — a later session the tab opens after a sign-in or 30 idle minutes
580
- keeps them too — and sends them as properties of every pageview; no other query
581
- parameter ever leaves the page. A session's `source` is the lowercase
582
- `utm_source` when present and the referrer host otherwise, so campaign traffic
583
- no longer reads as `direct`. `utmSource`, `utmMedium`, and `utmCampaign` stay on
584
- the session for journeys; `overview.topCampaigns` ranks `utm_campaign` and
585
- `overview.topMediums` the lowercase `utm_medium` next to `topSources`, by
586
- sessions once the range is covered by the counters. `overview.topOutbound` ranks
587
- the hosts outbound clicks reached the host alone, never the path or query — by
588
- clicks. Both lists are recorded from `metadata.dimensionsSince` (each site's
589
- first ingest on 0.1.0 plus the rollup delay); a range that starts earlier ranks
590
- them from partial data, and the dashboard says so.
591
-
592
- ## Identify visitors after signup
582
+ keeps them too — and sends them as properties of every pageview. Besides those
583
+ five, the only thing the landing URL ever yields is the **name** of a click-ID
584
+ key it carried (below); no other query parameter, and no query value at all,
585
+ ever leaves the page. A session's `source` is the lowercase `utm_source` when
586
+ present and the referrer host otherwise, so campaign traffic no longer reads as
587
+ `direct`. `utmSource`, `utmMedium`, and `utmCampaign` stay on the session for
588
+ journeys; `overview.topCampaigns` ranks `utm_campaign` and `overview.topMediums`
589
+ the lowercase `utm_medium` next to `topSources`, by sessions once the range is
590
+ covered by the counters. `overview.topOutbound` ranks the hosts outbound clicks
591
+ reached the host alone, never the path or query by clicks. Both lists are
592
+ recorded from `metadata.dimensionsSince` (each site's first ingest on 0.1.0 plus
593
+ the rollup delay); a range that starts earlier ranks them from partial data, and
594
+ the dashboard says so.
595
+
596
+ A session also keeps where it came from, classified once on its first touch and
597
+ never guessed: `platform` (an id from a closed, versioned table of about forty —
598
+ `instagram`, `google`, `whatsapp`, `chatgpt` — or `unknown` for a host or
599
+ `utm_source` outside it, or `none`), `channel` (`direct`, `organic_search`,
600
+ `paid_search`, `organic_social`, `paid_social`, `messaging`, `email`,
601
+ `affiliate`, `ai`, `display`, `referral`, `offline`), and `evidence`, the rule
602
+ that decided it. One rule decides and the next only runs when it did not:
603
+ `utm_source` through the alias table, then click-ID names, then the referrer
604
+ host by registrable domain, then the in-app browser, then an affiliate `ref`,
605
+ which sets the channel and keeps the platform an earlier rule found. Hosts match
606
+ by whole label, never by substring, so `l.instagram.com`, `instagram.com` and
607
+ `com.instagram.android` are one platform while `docs.google.com` is not Google;
608
+ `utm_medium` picks the channel from a closed table that also accepts the channel
609
+ ids themselves, which is what a tracked link (below) emits, `offline` included.
610
+ Nothing at all is `direct`, the only way to be direct, and a referrer on the
611
+ site's own domains is an internal hop, never an origin. `overview.topPlatforms`,
612
+ `topChannels` and `topEvidence` rank them beside `topSources`; a `topPlatforms`
613
+ value is the platform id alone when the session arrived by that platform's
614
+ default channel and `platform@channel` when it did not (`google@paid_search`),
615
+ so one platform ranks once per channel it was reached by —
616
+ `splitPlatformDimension` reads the pair back. `dataCoverage`'s `origin` dataset
617
+ says from when a site carries them; sessions older than that are classified on
618
+ read and marked `evidence: "legacy"`. `source` keeps the raw `utm_source` or
619
+ host for the drill-down, exactly as before.
620
+
621
+ The tracker reads the click-ID keys of its landing URL and sends them in `clid`:
622
+ their **names** only (`fbclid`, `gclid`, `ttclid`, `mc_cid`…), never their
623
+ values, so no advertising identifier leaves the page. It matches the same
624
+ allowlist the server does, in the same ranking order, and sends at most eight;
625
+ ingestion drops any name outside the allowlist and keeps at most eight of what
626
+ is left. The referrer it sends is scheme and host, so a referrer of the form
627
+ `android-app://<package>` keeps the package alone and the app that opened the
628
+ link is a platform (`com.google.android.gm` is Gmail) instead of nothing.
629
+
630
+ ## Tracked links
631
+
632
+ Some traffic arrives with nothing to read. A link tapped inside WhatsApp,
633
+ Telegram, an SMS or a native e-mail app, or scanned from a QR code, opens with
634
+ no referrer and no campaign, and any analytics tool files it as `direct`. A
635
+ tracked link closes that gap: the dashboard's **Links** tab (or the host API)
636
+ creates `/rastro/l/<slug>` for one place the link will be published, and every
637
+ click lands on the site with that place in its `utm_*`.
638
+
639
+ `GET /rastro/l/<slug>` answers `302` with `Cache-Control: no-store` to the
640
+ link's destination, after appending:
641
+
642
+ | Parameter | Value |
643
+ | -------------- | ------------------------------------------------------------------------------------------------------------------- |
644
+ | `utm_source` | the platform: `instagram`, `whatsapp`, `qr`, or any id of 2–24 lowercase letters, digits or underscores |
645
+ | `utm_medium` | the channel: `organic_social`, `paid_social`, `messaging`, `email`, `affiliate`, `display`, `referral` or `offline` |
646
+ | `utm_campaign` | the campaign, or the slug when the link has none |
647
+ | `utm_content` | the slug |
648
+
649
+ The destination's own query stays exactly as it was, and a `utm_*` key it
650
+ already carries is never overridden. Unknown and disabled slugs answer `404` in
651
+ plain text. The tracker needs nothing new: the landing reads the `utm_*` like
652
+ any campaign link.
653
+
654
+ - **Clicks and bots.** Each click lands in a daily counter spread over four
655
+ shards, so a link that goes viral never serializes on one document. A
656
+ self-declared bot, including the WhatsApp, Facebook and Telegram link
657
+ previews, is still redirected but counted under `bots`, never `clicks`.
658
+ `listTrackedLinks` reports both, all time and over the 30 UTC days ending on
659
+ the day of the `now` it is given.
660
+ - **No open redirect.** A destination must be an absolute `http(s)` URL without
661
+ credentials on one of the site's domains (exact, or a `*.` pattern for
662
+ subdomains). The rule is checked when a link is created or changed and again
663
+ on every click, so a domain removed from the site stops redirecting at once.
664
+ - **Slugs** are 3–32 lowercase letters, digits or hyphens, starting and ending
665
+ with a letter or digit, and unique across the deployment because the route
666
+ names no site. A site keeps at most 200 links. Deleting a link frees its slug
667
+ and removes its counters; disabling it keeps both.
668
+ - **A short domain.** The route lives on the deployment's HTTP origin
669
+ (`https://<deployment>.convex.site/rastro/l/promo`). A host that wants a
670
+ presentable link serves that origin under a short domain of its own, such as
671
+ `go.example.com/rastro/l/promo`; nothing in the component changes.
672
+
673
+ ```ts
674
+ await analytics.createTrackedLink(ctx, {
675
+ siteId,
676
+ slug: "bio-instagram",
677
+ destination: "https://www.example.com/promo",
678
+ platform: "instagram",
679
+ channel: "organic_social",
680
+ campaign: "spring-sale",
681
+ createdBy: userId, // an opaque id, never an e-mail address
682
+ });
683
+ ```
684
+
685
+ A federated dashboard reaches the same functions once the host re-exports
686
+ `listTrackedLinks`, `createTrackedLink`, `updateTrackedLink`, and
687
+ `deleteTrackedLink` from `convex/rastroFederation.ts`. The connection then
688
+ advertises the `trackedLinks` capability: any reader lists the links, and
689
+ changing them needs `analytics:configure`, like goals and affiliates.
690
+
691
+ ## Identify people with any authentication
692
+
693
+ Rastro connects names and emails to visits, actions and conversions. Use
694
+ `identifyVisitorMutation` with your backend's authenticated-user resolver for
695
+ Better Auth, custom auth or another Convex JWT provider. Other backends use
696
+ `identifyVisitorHttpAction` over authenticated HTTP. No auth provider is a
697
+ runtime requirement of the SDK.
698
+
699
+ See the [identity integration guide](docs/identity.md) for the complete
700
+ server/browser flow, login/logout behavior, profile updates, and examples. The
701
+ dashboard presents profiles in Ao vivo and Jornadas, and the Pessoas directory
702
+ searches all profiles by name, email, ID or an exact typed attribute. Attributes
703
+ are optional strings, numbers and booleans defined by your project. Each person
704
+ opens a history scoped to their site and the selected period. These additions
705
+ require 0.6.0 or later; see [upgrade notes](docs/upgrading.md).
706
+
707
+ ### Link existing visitor IDs
593
708
 
594
709
  Anonymous sessions carry a random `visitorId` until the host supplies a
595
710
  pseudonymous one through `data-visitor` or the `RastroScript` `visitorId` prop.
@@ -754,6 +869,8 @@ The `Rastro` class wraps the component boundary for host functions:
754
869
  - Configuration: `upsertGoal`, `listGoals`, `removeGoal`, `upsertFunnel`,
755
870
  `listFunnels`, `removeFunnel`, `upsertAffiliate`, `listAffiliates`,
756
871
  `removeAffiliate`
872
+ - Tracked links: `createTrackedLink`, `updateTrackedLink`, `deleteTrackedLink`,
873
+ `listTrackedLinks`
757
874
  - Retention: `cleanup`, `setRetentionPolicy`, `disableRetentionPolicy`,
758
875
  `runRetentionPolicy`, `retentionStatus`
759
876
 
@@ -1049,7 +1166,12 @@ Amage Rastro's default tracker:
1049
1166
  clicked link or button (at most 64 characters, replaced by `data-rastro-label`
1050
1167
  when present), and outbound link origins and pathnames (the overview ranks
1051
1168
  destinations by host alone);
1052
- - strips query strings and fragments from stored paths and links.
1169
+ - strips query strings and fragments from stored paths and links; of the landing
1170
+ query it keeps the five `utm_*` values and the bare **names** of the
1171
+ advertising click-ID keys it recognizes (`fbclid`, `gclid`…), never a click-ID
1172
+ value, and never any other parameter;
1173
+ - sends the referrer as scheme and host only — an `android-app://<package>`
1174
+ included — never its path or query.
1053
1175
 
1054
1176
  The optional Web Vitals module inherits this contract: it reports at most five
1055
1177
  timing numbers per page load (milliseconds, or CLS scaled by 1000) against the
@@ -494,6 +494,8 @@ export const {
494
494
  overview,
495
495
  liveVisitors,
496
496
  getSession,
497
+ getVisitorProfile,
498
+ listPeople,
497
499
  revenueSummary,
498
500
  listSessions,
499
501
  sessionJourney,
@@ -519,21 +521,25 @@ export const {
519
521
  retentionStatus,
520
522
  setRetentionPolicy,
521
523
  disableRetentionPolicy,
524
+ listTrackedLinks,
525
+ createTrackedLink,
526
+ updateTrackedLink,
527
+ deleteTrackedLink,
522
528
  } = federated;
523
529
  ```
524
530
 
525
531
  `manifest` is public static metadata. Every report function is read-only and
526
532
  requires both a valid JWT and a matching, non-revoked local grant. The functions
527
- from `siteSettings` down form the configure scope (optional capability
528
- `configure`, hosts from `alpha.11`): they additionally require
529
- `analytics:configure` on the token — the control plane claims it only for
530
- organization owners and admins — and in the grant's `permissions`, and fail with
531
- `FEDERATION_CONFIGURE_FORBIDDEN` otherwise. Export them even when every grant
532
- stays read only, so a later opt-in needs no host deploy; the dashboard never
533
- calls them for a read-only connection. The issuer falls back to the production
534
- control plane so the module compiles and pushes before the environment variable
535
- exists; tokens are still rejected until `auth.config.ts` trusts the same issuer,
536
- which needs both variables set and a push afterwards.
533
+ from `siteSettings` to `disableRetentionPolicy` form the configure scope
534
+ (optional capability `configure`, hosts from `alpha.11`): they additionally
535
+ require `analytics:configure` on the token — the control plane claims it only
536
+ for organization owners and admins — and in the grant's `permissions`, and fail
537
+ with `FEDERATION_CONFIGURE_FORBIDDEN` otherwise. Export them even when every
538
+ grant stays read only, so a later opt-in needs no host deploy; the dashboard
539
+ never calls them for a read-only connection. The issuer falls back to the
540
+ production control plane so the module compiles and pushes before the
541
+ environment variable exists; tokens are still rejected until `auth.config.ts`
542
+ trusts the same issuer, which needs both variables set and a push afterwards.
537
543
 
538
544
  A public link's token carries `analytics:public` instead of `analytics:read` and
539
545
  `rastro_site_ids` naming the link's sites. It runs only `manifest`,
@@ -547,25 +553,27 @@ permission rejects the token with `FEDERATION_INVALID_CLAIMS`.
547
553
 
548
554
  ## Function contract
549
555
 
550
- | Function | Input summary | Important output/behavior |
551
- | ------------------ | ------------------------------------------------------- | --------------------------------------------------------------------- |
552
- | `manifest` | `{}` | Static protocol, capabilities, names, and limits; no auth |
553
- | `connectionStatus` | `{}` | Redacted status, protocol version, capabilities, site count |
554
- | `listSites` | `{}` | Only `siteId`, `name`, `currency`, `timezone`, `cookieless` |
555
- | `overview` | `siteIds`, `from`, `to`, optional hour/day interval | Aggregate metrics and time series |
556
- | `liveVisitors` | `siteIds`, `now`, optional limit | At most 500 visitors seen in the last five minutes, with intent facts |
557
- | `getSession` | one `siteId`, `sessionId` | Selected session metadata, or null when no retained session exists |
558
- | `revenueSummary` | `siteIds`, inclusive `from`/`to` | Period commissions; pair with overview totals/currency |
559
- | `listSessions` | one `siteId`, range, `{numItems,cursor}` keyset options | Paginated sessions |
560
- | `sessionJourney` | one site/session, range, `{numItems,cursor}` options | Paginated bounded events |
561
- | `listConversions` | one site, range, `{numItems,cursor}` options | Paginated trusted server-side conversion ledger |
562
- | `visitorJourney` | site IDs, visitor ID, range, optional limit | Cross-site journey inside the grant only |
563
- | `goalsReport` | one site and range | Read-only goal definitions plus aggregates |
564
- | `funnelsReport` | one site and complete UTC-day range | Read-only definitions/steps plus aggregates; max 90 days |
565
- | `affiliatesReport` | one site and complete UTC-day range | Read-only affiliate definitions plus aggregates |
566
- | `vitalsReport` | one site and complete UTC-day range | Web Vitals p75/ratings per metric, page, and device; max 90 days |
567
- | `siteMap` | site IDs and complete UTC-day range | Routes with derived exits/bounces plus transitions; max 90 days |
568
- | `dataCoverage` | one site and inclusive integer range | Coverage metadata; echoes site ID, no event/visitor/session IDs |
556
+ | Function | Input summary | Important output/behavior |
557
+ | ------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
558
+ | `manifest` | `{}` | Static protocol, capabilities, names, and limits; no auth |
559
+ | `connectionStatus` | `{}` | Redacted status, protocol version, capabilities, site count |
560
+ | `listSites` | `{}` | Only `siteId`, `name`, `currency`, `timezone`, `cookieless` |
561
+ | `overview` | `siteIds`, `from`, `to`, optional hour/day interval | Aggregate metrics and time series |
562
+ | `liveVisitors` | `siteIds`, `now`, optional limit | At most 500 visitors seen in the last five minutes, with intent facts |
563
+ | `getSession` | one `siteId`, `sessionId` | Selected session metadata, or null when no retained session exists |
564
+ | `getVisitorProfile` | one `siteId`, `visitorId` | Identified profile through the optional `visitorProfiles` capability; reader permission required |
565
+ | `listPeople` | one `siteId`, optional `filter`, `paginationOpts` | Indexed profile directory through optional `people`; reader permission required; up to 50 rows per page |
566
+ | `revenueSummary` | `siteIds`, inclusive `from`/`to` | Period commissions; pair with overview totals/currency |
567
+ | `listSessions` | one `siteId`, range, `{numItems,cursor}` keyset options | Paginated sessions |
568
+ | `sessionJourney` | one site/session, range, `{numItems,cursor}` options | Paginated bounded events |
569
+ | `listConversions` | one site, range, `{numItems,cursor}` options | Paginated trusted server-side conversion ledger |
570
+ | `visitorJourney` | site IDs, visitor ID, range, optional limit | Cross-site journey inside the grant only |
571
+ | `goalsReport` | one site and range | Read-only goal definitions plus aggregates |
572
+ | `funnelsReport` | one site and complete UTC-day range | Read-only definitions/steps plus aggregates; max 90 days |
573
+ | `affiliatesReport` | one site and complete UTC-day range | Read-only affiliate definitions plus aggregates |
574
+ | `vitalsReport` | one site and complete UTC-day range | Web Vitals p75/ratings per metric, page, and device; max 90 days |
575
+ | `siteMap` | site IDs and complete UTC-day range | Routes with derived exits/bounces plus transitions; max 90 days |
576
+ | `dataCoverage` | one site and inclusive integer range | Coverage metadata; echoes site ID, no event/visitor/session IDs |
569
577
 
570
578
  The configure scope, every function of which requires `analytics:configure` on
571
579
  the token and in the grant:
@@ -587,6 +595,18 @@ the token and in the grant:
587
595
  | `setRetentionPolicy` | `siteId` and five day counts between 1 and 3650 | Starts the recurring cleanup chains |
588
596
  | `disableRetentionPolicy` | one `siteId` | Stops automatic cleanup; nothing is restored |
589
597
 
598
+ Tracked links, behind the optional `trackedLinks` capability. Listing needs only
599
+ a reader; the three writes require `analytics:configure` like the configure
600
+ scope above. A host serves them by re-exporting the four functions; one that
601
+ does not keeps working and advertises no `trackedLinks` capability.
602
+
603
+ | Function | Input summary | Important output/behavior |
604
+ | ------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
605
+ | `listTrackedLinks` | one `siteId` and `now` | Definitions with clicks and bot hits, all time and over 30 UTC days |
606
+ | `createTrackedLink` | `siteId`, slug, destination, platform, channel, campaign, `createdBy` | Returns the link ID; the destination must be on one of the site's domains |
607
+ | `updateTrackedLink` | `siteId`, `linkId`, optional destination, platform, channel, campaign, disabled | Changes everything but the slug; a disabled link answers 404 |
608
+ | `deleteTrackedLink` | `siteId`, `linkId` | Deletes the link, frees its slug, and purges its click counters |
609
+
590
610
  The federated surface deliberately excludes ingestion, owner IDs, network IDs,
591
611
  and arbitrary host functions, and shows a site's domains only through
592
612
  `siteSettings` to a connection allowed to change them. Goal, funnel, and
@@ -821,3 +841,20 @@ Production changes performed: yes/no
821
841
 
822
842
  Do not include private keys, admin keys, session cookies, JWTs, or raw customer
823
843
  telemetry in the report.
844
+
845
+ ## Identified people
846
+
847
+ Follow
848
+ [the identity guide](https://www.amagerastro.com/docs/reference/identity.md) to
849
+ integrate Better Auth, custom authentication or an external backend. The host
850
+ owns authenticated-user resolution and site authorization. Use
851
+ `Rastro.identifyVisitorMutation` for the current user, `identifyVisitor` from
852
+ trusted server code, and `identifyVisitorHttpAction` for authenticated
853
+ server-to-server HTTP. Attributes are optional, project-defined strings, numbers
854
+ and booleans; no user-table shape or business field is required. Re-export
855
+ `getVisitorProfile` and `listPeople` for the optional `visitorProfiles` and
856
+ `people` capabilities. Existing profiles need the bounded, owner-authorized
857
+ `backfillVisitorProfiles` upgrade before name/email search; see the identity
858
+ guide. Live/session reports may carry `profile`; public live reports remove it.
859
+ Apply the server-returned visitor ID in the tracker, and reset the tracker on
860
+ logout.
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://www.amagerastro.com/agent/manifest.schema.json",
3
3
  "name": "amage-rastro",
4
4
  "title": "Amage Rastro",
5
- "description": "Privacy-first, real-time web analytics for Convex with a federated dashboard that reads every granted site and configures only those whose host opts in.",
5
+ "description": "Real-time product and web analytics with identified people, journeys and conversions; profiles and telemetry stay in each product deployment.",
6
6
  "manifestVersion": "1.0.0",
7
7
  "language": "en",
8
8
  "canonicalBaseUrl": "https://www.amagerastro.com",
@@ -15,12 +15,14 @@
15
15
  "upgrading": "https://www.amagerastro.com/docs/reference/upgrading.md",
16
16
  "integrity": {
17
17
  "algorithm": "sha256",
18
- "agent": "e26de56729dd0b474669d2b40292afb314224200da4e70f6dabd407ce7aea6e7",
19
- "llms": "2072c0c06b5ec130c59edc269d66665ffd042053a9f7238a16811431a9251c19",
20
- "protocol": "f2be7e3db250824d0c1293eb31b2e4514ebed8b21fe4974d4f4797fd86abe04f",
18
+ "agent": "35d1c2d73eef8c5266368453dd4eb1343ecc70650e0c0b1d6e7dfcae4b5ecdcc",
19
+ "llms": "71be7d69dec942407cbe08db75490de50e2b3b399cdd611049cfb6d99b551e13",
20
+ "protocol": "d56dd4dca8c11a4d00303c48abfbcde4ed7cee6f415b2c96b971ba9efff2467c",
21
21
  "setup": "bf2509a87a8eab9c047276b813f029bb2c9ddd58c0e54754432e3fef7891f3c9",
22
- "upgrading": "fee72acf124487309a739f8344dc507346712c68d11a957c7068d4838aa79f6b"
23
- }
22
+ "upgrading": "4b72c250839e159b07c345ef7c71f2fdfe16a50470790fa5154cb6eaa6b72300",
23
+ "identity": "2d23399ae4e469b505790d9c7cc8a2012da96a49bd2e776a8118b51b52c8390e"
24
+ },
25
+ "identity": "https://www.amagerastro.com/docs/reference/identity.md"
24
26
  },
25
27
  "controlPlane": {
26
28
  "dashboardUrl": "https://www.amagerastro.com",
@@ -85,7 +87,13 @@
85
87
  "removeAffiliate": "removeAffiliate",
86
88
  "retentionStatus": "retentionStatus",
87
89
  "setRetentionPolicy": "setRetentionPolicy",
88
- "disableRetentionPolicy": "disableRetentionPolicy"
90
+ "disableRetentionPolicy": "disableRetentionPolicy",
91
+ "listTrackedLinks": "listTrackedLinks",
92
+ "createTrackedLink": "createTrackedLink",
93
+ "updateTrackedLink": "updateTrackedLink",
94
+ "deleteTrackedLink": "deleteTrackedLink",
95
+ "getVisitorProfile": "getVisitorProfile",
96
+ "listPeople": "listPeople"
89
97
  },
90
98
  "capabilities": [
91
99
  "siteSummaries",
@@ -100,11 +108,14 @@
100
108
  "affiliates",
101
109
  "dataCoverage",
102
110
  "sessionDetails",
111
+ "visitorProfiles",
112
+ "people",
103
113
  "revenueSummary",
104
114
  "vitals",
105
115
  "siteMap",
106
116
  "localDays",
107
- "configure"
117
+ "configure",
118
+ "trackedLinks"
108
119
  ],
109
120
  "limits": {
110
121
  "maxSitesPerRequest": 10,
@@ -44,7 +44,8 @@
44
44
  "protocol",
45
45
  "setup",
46
46
  "upgrading",
47
- "integrity"
47
+ "integrity",
48
+ "identity"
48
49
  ],
49
50
  "properties": {
50
51
  "human": { "$ref": "#/$defs/httpsUrl" },
@@ -61,7 +62,8 @@
61
62
  "llms",
62
63
  "protocol",
63
64
  "setup",
64
- "upgrading"
65
+ "upgrading",
66
+ "identity"
65
67
  ],
66
68
  "properties": {
67
69
  "algorithm": { "const": "sha256" },
@@ -69,10 +71,12 @@
69
71
  "llms": { "$ref": "#/$defs/sha256" },
70
72
  "protocol": { "$ref": "#/$defs/sha256" },
71
73
  "setup": { "$ref": "#/$defs/sha256" },
72
- "upgrading": { "$ref": "#/$defs/sha256" }
74
+ "upgrading": { "$ref": "#/$defs/sha256" },
75
+ "identity": { "$ref": "#/$defs/sha256" }
73
76
  },
74
77
  "additionalProperties": false
75
- }
78
+ },
79
+ "identity": { "$ref": "#/$defs/httpsUrl" }
76
80
  },
77
81
  "additionalProperties": false
78
82
  },
@@ -185,7 +189,13 @@
185
189
  "removeAffiliate",
186
190
  "retentionStatus",
187
191
  "setRetentionPolicy",
188
- "disableRetentionPolicy"
192
+ "disableRetentionPolicy",
193
+ "listTrackedLinks",
194
+ "createTrackedLink",
195
+ "updateTrackedLink",
196
+ "deleteTrackedLink",
197
+ "getVisitorProfile",
198
+ "listPeople"
189
199
  ],
190
200
  "properties": {
191
201
  "manifest": { "const": "manifest" },
@@ -218,14 +228,20 @@
218
228
  "removeAffiliate": { "const": "removeAffiliate" },
219
229
  "retentionStatus": { "const": "retentionStatus" },
220
230
  "setRetentionPolicy": { "const": "setRetentionPolicy" },
221
- "disableRetentionPolicy": { "const": "disableRetentionPolicy" }
231
+ "disableRetentionPolicy": { "const": "disableRetentionPolicy" },
232
+ "listTrackedLinks": { "const": "listTrackedLinks" },
233
+ "createTrackedLink": { "const": "createTrackedLink" },
234
+ "updateTrackedLink": { "const": "updateTrackedLink" },
235
+ "deleteTrackedLink": { "const": "deleteTrackedLink" },
236
+ "getVisitorProfile": { "const": "getVisitorProfile" },
237
+ "listPeople": { "const": "listPeople" }
222
238
  },
223
239
  "additionalProperties": false
224
240
  },
225
241
  "capabilities": {
226
242
  "type": "array",
227
- "minItems": 15,
228
- "maxItems": 17,
243
+ "minItems": 16,
244
+ "maxItems": 20,
229
245
  "uniqueItems": true,
230
246
  "items": {
231
247
  "enum": [
@@ -245,7 +261,10 @@
245
261
  "vitals",
246
262
  "siteMap",
247
263
  "localDays",
248
- "configure"
264
+ "configure",
265
+ "trackedLinks",
266
+ "visitorProfiles",
267
+ "people"
249
268
  ]
250
269
  }
251
270
  },