@iann29/rastro 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -15
- package/agent/integration.md +4 -0
- package/agent/manifest.json +8 -4
- package/agent/manifest.schema.json +7 -1
- package/dist/client/federation.d.ts +37 -11
- package/dist/client/federation.d.ts.map +1 -1
- package/dist/client/federation.js +80 -4
- package/dist/client/federation.js.map +1 -1
- package/dist/client/index.d.ts +140 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +38 -5
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +45 -0
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +19 -6
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/reports.d.ts +47 -0
- package/dist/component/reports.d.ts.map +1 -1
- package/dist/component/reports.js +75 -0
- package/dist/component/reports.js.map +1 -1
- package/dist/tracker/generated.d.ts +1 -1
- package/dist/tracker/generated.js +1 -1
- package/docs/federation-setup.md +63 -0
- package/docs/federation.md +9 -1
- package/docs/upgrading.md +51 -0
- package/package.json +1 -1
- package/src/component/_generated/component.ts +48 -0
- package/src/component/ingest.ts +30 -6
- package/src/component/reports.ts +96 -0
- package/src/tracker/generated.ts +1 -1
package/docs/upgrading.md
CHANGED
|
@@ -22,12 +22,35 @@ hosts still on them.
|
|
|
22
22
|
| `0.1.0-alpha.10` | Install `0.1.0-alpha.12` directly; new tables and fields are additive; re-export the configure functions to opt in | `/rastro/health` reports `tracker.version: "0.1.0-alpha.12"` |
|
|
23
23
|
| `0.1.0-alpha.11` | Install `0.1.0-alpha.12` directly; same code, the package now ships `src/tracker/generated.ts` | A `convex/` test importing `@iann29/rastro/test` typechecks on push |
|
|
24
24
|
| `0.1.0-alpha.12` | Install `0.1.0` directly; every schema change is an optional field or a new table; add `analytics:public` to a grant only to allow public links | `/rastro/health` reports `tracker.version: "0.1.0"` |
|
|
25
|
+
| `0.1.0` | Install `0.2.0` directly; add the optional host callback and grant version to enable transfers | Health reports `0.2.0`; opted-in connectors advertise `transfer` |
|
|
25
26
|
| Any release whose packed declarations lack federation | Stop and request an eligible exact registry release | Packed declarations contain the federation exports |
|
|
26
27
|
|
|
27
28
|
Do not upgrade a populated legacy deployment directly to a release that removes
|
|
28
29
|
the `events` table. Schema acceptance alone does not prove that telemetry was
|
|
29
30
|
migrated.
|
|
30
31
|
|
|
32
|
+
## Organization transfers (0.2.0)
|
|
33
|
+
|
|
34
|
+
Install `@iann29/rastro@0.2.0` after rehearsing on a non-production snapshot.
|
|
35
|
+
The analytics component has no schema change or telemetry migration in this
|
|
36
|
+
release. The tracker reports the new package version; its behavior is unchanged.
|
|
37
|
+
|
|
38
|
+
To enable **Conexões → Transferir organização**, follow
|
|
39
|
+
[the host changes in the federation setup guide](federation-setup.md#transfer-a-connection-to-another-organization):
|
|
40
|
+
add optional `version` to the host's grant table, return it from
|
|
41
|
+
`resolveConnection`, persist the destination organization and incremented
|
|
42
|
+
version in `transferConnection`, and export that mutation. Keep existing
|
|
43
|
+
versions when provisioning grants. Transfers also require `analytics:configure`
|
|
44
|
+
in the local grant; read-only grants remain read-only after the package upgrade.
|
|
45
|
+
|
|
46
|
+
Reverify the connection in **Conexões** after deploying so the control plane
|
|
47
|
+
refreshes its capabilities and permissions. Acceptance: `/rastro/health` reports
|
|
48
|
+
`tracker.version: "0.2.0"`, the opted-in connector manifest includes `transfer`,
|
|
49
|
+
and a rehearsal transfer preserves site IDs and reports while rejecting the
|
|
50
|
+
source organization's old token. If a response is lost, resume the same transfer
|
|
51
|
+
through the dashboard; do not reset grant versions or clear the pending intent
|
|
52
|
+
manually.
|
|
53
|
+
|
|
31
54
|
## Legacy event migration
|
|
32
55
|
|
|
33
56
|
`0.1.0-alpha.2` is the published migration bridge. A DEV or snapshot deployment
|
|
@@ -384,3 +407,31 @@ control-plane issuer.
|
|
|
384
407
|
|
|
385
408
|
Record the package version, deployment target, migration output, and verifier in
|
|
386
409
|
the project's release evidence.
|
|
410
|
+
|
|
411
|
+
### Session details and complete revenue lists
|
|
412
|
+
|
|
413
|
+
After upgrading the package, export `getSession` and `revenueSummary` from the
|
|
414
|
+
object returned by `exposeFederatedAnalyticsApi` in
|
|
415
|
+
`convex/rastroFederation.ts`, then deploy the host and reverify its dashboard
|
|
416
|
+
connection. The manifest advertises `sessionDetails` and `revenueSummary`;
|
|
417
|
+
advertising them without exporting the functions leaves an incomplete host
|
|
418
|
+
surface.
|
|
419
|
+
|
|
420
|
+
`getSession({siteId, sessionId})` returns the retained session metadata (or
|
|
421
|
+
null). The dashboard pairs it with the existing paginated `sessionJourney` so a
|
|
422
|
+
copied session link keeps its duration, entry/exit and counters even after a
|
|
423
|
+
reload. Older hosts still provide scoped session events, but cannot restore all
|
|
424
|
+
metadata.
|
|
425
|
+
|
|
426
|
+
`revenueSummary({siteIds, from, to})` returns exact `commissionCents` for the
|
|
427
|
+
inclusive period. Revenue, conversion count, average order and currency reuse
|
|
428
|
+
`overview` totals. The commission report reads existing daily affiliate stats
|
|
429
|
+
for complete UTC days and trusted conversion records only for partial edge days;
|
|
430
|
+
renamed/deleted affiliate definitions do not remove historical commissions. It
|
|
431
|
+
rejects unavailable retained sources, mixed currencies and periods requiring
|
|
432
|
+
more than 5,000 source rows instead of presenting partial totals. No new table,
|
|
433
|
+
backfill or ingestion change is required. Older hosts show commissions as
|
|
434
|
+
unavailable, never as the sum of a loaded list page.
|
|
435
|
+
|
|
436
|
+
Session and conversion lists now expose per-site keyset continuation, merge rows
|
|
437
|
+
in chronological order and label search as filtering only loaded records.
|
package/package.json
CHANGED
|
@@ -362,6 +362,47 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
362
362
|
}>,
|
|
363
363
|
Name
|
|
364
364
|
>;
|
|
365
|
+
getSession: FunctionReference<
|
|
366
|
+
"query",
|
|
367
|
+
"internal",
|
|
368
|
+
{ sessionId: string; siteId: string },
|
|
369
|
+
{
|
|
370
|
+
_creationTime: number;
|
|
371
|
+
_id: string;
|
|
372
|
+
affiliateAttributedAt?: number;
|
|
373
|
+
affiliateExpiresAt?: number;
|
|
374
|
+
affiliateId?: string;
|
|
375
|
+
affiliateSlug?: string;
|
|
376
|
+
browser: string;
|
|
377
|
+
city?: string;
|
|
378
|
+
conversionCount: number;
|
|
379
|
+
country?: string;
|
|
380
|
+
currency?: string;
|
|
381
|
+
device: string;
|
|
382
|
+
durationMs: number;
|
|
383
|
+
entryPath: string;
|
|
384
|
+
eventCount: number;
|
|
385
|
+
exitPath: string;
|
|
386
|
+
lastSeenAt: number;
|
|
387
|
+
latitude?: number;
|
|
388
|
+
longitude?: number;
|
|
389
|
+
newVisitor?: boolean;
|
|
390
|
+
os: string;
|
|
391
|
+
pageviewCount: number;
|
|
392
|
+
referrer?: string;
|
|
393
|
+
revenueCents: number;
|
|
394
|
+
sessionId: string;
|
|
395
|
+
siteId: string;
|
|
396
|
+
source: string;
|
|
397
|
+
startedAt: number;
|
|
398
|
+
utmCampaign?: string;
|
|
399
|
+
utmMedium?: string;
|
|
400
|
+
utmSource?: string;
|
|
401
|
+
visitorId: string;
|
|
402
|
+
visitorKey?: string;
|
|
403
|
+
} | null,
|
|
404
|
+
Name
|
|
405
|
+
>;
|
|
365
406
|
goalsReport: FunctionReference<
|
|
366
407
|
"query",
|
|
367
408
|
"internal",
|
|
@@ -676,6 +717,13 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
|
676
717
|
},
|
|
677
718
|
Name
|
|
678
719
|
>;
|
|
720
|
+
revenueSummary: FunctionReference<
|
|
721
|
+
"query",
|
|
722
|
+
"internal",
|
|
723
|
+
{ from: number; siteIds: Array<string>; to: number },
|
|
724
|
+
{ commissionCents: number },
|
|
725
|
+
Name
|
|
726
|
+
>;
|
|
679
727
|
sessionJourney: FunctionReference<
|
|
680
728
|
"query",
|
|
681
729
|
"internal",
|
package/src/component/ingest.ts
CHANGED
|
@@ -413,10 +413,8 @@ export const ingestBatch = mutation({
|
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
if (event.type === "leave") {
|
|
416
|
-
// A departure
|
|
417
|
-
//
|
|
418
|
-
// or late leave is inert. The read side filters by expiresAt and the
|
|
419
|
-
// sweep deletes the row, so the dashboard sees the exit in seconds.
|
|
416
|
+
// A departure closes the final duration interval without telemetry
|
|
417
|
+
// or new presence. Older departures cannot retire a bfcache return.
|
|
420
418
|
if (!existingSession) {
|
|
421
419
|
rejected += 1;
|
|
422
420
|
continue;
|
|
@@ -441,7 +439,33 @@ export const ingestBatch = mutation({
|
|
|
441
439
|
batchState,
|
|
442
440
|
);
|
|
443
441
|
const liveSession = liveState.current;
|
|
444
|
-
if (liveSession) {
|
|
442
|
+
if (liveSession && event.timestamp >= liveSession.lastSeenAt) {
|
|
443
|
+
// Only the still-covered tail is trusted; a delayed departure
|
|
444
|
+
// cannot lengthen an expired visit or move its start backwards.
|
|
445
|
+
if (
|
|
446
|
+
event.timestamp > existingSession.lastSeenAt &&
|
|
447
|
+
event.timestamp >= now - LIVE_SESSION_TTL_MS &&
|
|
448
|
+
event.timestamp <= liveSession.expiresAt
|
|
449
|
+
) {
|
|
450
|
+
const durationMs = Math.max(
|
|
451
|
+
existingSession.durationMs,
|
|
452
|
+
event.timestamp - existingSession.startedAt,
|
|
453
|
+
);
|
|
454
|
+
sessionRow.current = {
|
|
455
|
+
...existingSession,
|
|
456
|
+
lastSeenAt: event.timestamp,
|
|
457
|
+
exitPath: event.path,
|
|
458
|
+
durationMs,
|
|
459
|
+
};
|
|
460
|
+
sessionRow.dirty = true;
|
|
461
|
+
foldSessionDuration(
|
|
462
|
+
aggregateDeltas,
|
|
463
|
+
args.siteId,
|
|
464
|
+
event,
|
|
465
|
+
durationMs - existingSession.durationMs,
|
|
466
|
+
timezone,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
445
469
|
const expiresAt = Math.min(
|
|
446
470
|
liveSession.expiresAt,
|
|
447
471
|
now + LIVE_LEAVE_GRACE_MS,
|
|
@@ -2404,7 +2428,7 @@ function aggregateBucketStart(
|
|
|
2404
2428
|
return Math.floor(timestamp / interval) * interval;
|
|
2405
2429
|
}
|
|
2406
2430
|
|
|
2407
|
-
/** Folds
|
|
2431
|
+
/** Folds session duration growth without telemetry, for heartbeats and leaves. */
|
|
2408
2432
|
function foldSessionDuration(
|
|
2409
2433
|
aggregateDeltas: AggregateDeltas,
|
|
2410
2434
|
siteId: Id<"sites">,
|
package/src/component/reports.ts
CHANGED
|
@@ -932,6 +932,102 @@ async function readRouteRollups(
|
|
|
932
932
|
return { rows, completeFrom: cutAt + DAY_MS };
|
|
933
933
|
}
|
|
934
934
|
|
|
935
|
+
/** Metadata for a selected session, independent of list pagination. */
|
|
936
|
+
export const getSession = query({
|
|
937
|
+
args: { siteId: v.id("sites"), sessionId: v.string() },
|
|
938
|
+
returns: v.union(sessionDocumentValidator, v.null()),
|
|
939
|
+
handler: async (ctx, args) => {
|
|
940
|
+
const session = await ctx.db
|
|
941
|
+
.query("sessions")
|
|
942
|
+
.withIndex("by_siteId_and_sessionId", (q) =>
|
|
943
|
+
q.eq("siteId", args.siteId).eq("sessionId", args.sessionId),
|
|
944
|
+
)
|
|
945
|
+
.unique();
|
|
946
|
+
if (!session) return null;
|
|
947
|
+
const {
|
|
948
|
+
geoLookupAttemptedAt: _geo,
|
|
949
|
+
lastPageviewAt: _pageview,
|
|
950
|
+
...result
|
|
951
|
+
} = session;
|
|
952
|
+
return result;
|
|
953
|
+
},
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
/** Exact commissions alongside overview's period revenue/conversion totals. */
|
|
957
|
+
export const revenueSummary = query({
|
|
958
|
+
args: { siteIds: v.array(v.id("sites")), from: v.number(), to: v.number() },
|
|
959
|
+
returns: v.object({ commissionCents: v.number() }),
|
|
960
|
+
handler: async (ctx, args) => {
|
|
961
|
+
const siteIds = validateSiteIds(args.siteIds);
|
|
962
|
+
validateRange(args.from, args.to, MAX_REPORT_RANGE_DAYS);
|
|
963
|
+
const fullFrom = Math.ceil(args.from / DAY_MS) * DAY_MS;
|
|
964
|
+
const fullTo = Math.floor((args.to + 1) / DAY_MS) * DAY_MS;
|
|
965
|
+
let commissionCents = 0;
|
|
966
|
+
let rowsRead = 0;
|
|
967
|
+
const currencies = new Set<string>();
|
|
968
|
+
for (const siteId of siteIds) {
|
|
969
|
+
const site = await ctx.db.get("sites", siteId);
|
|
970
|
+
if (!site) fail("NOT_FOUND", "site not found");
|
|
971
|
+
currencies.add(site.currency ?? "USD");
|
|
972
|
+
if (currencies.size > 1)
|
|
973
|
+
fail("CURRENCY_MISMATCH", "revenue sites must use the same currency");
|
|
974
|
+
if (fullFrom < fullTo) {
|
|
975
|
+
await assertSourceAvailable(
|
|
976
|
+
ctx,
|
|
977
|
+
[siteId],
|
|
978
|
+
["affiliateStats"],
|
|
979
|
+
fullFrom,
|
|
980
|
+
);
|
|
981
|
+
const stats = await ctx.db
|
|
982
|
+
.query("affiliateDailyStats")
|
|
983
|
+
.withIndex("by_siteId_and_bucketStart", (q) =>
|
|
984
|
+
q
|
|
985
|
+
.eq("siteId", siteId)
|
|
986
|
+
.gte("bucketStart", fullFrom)
|
|
987
|
+
.lt("bucketStart", fullTo),
|
|
988
|
+
)
|
|
989
|
+
.take(MAX_REPORT_BUCKETS - rowsRead + 1);
|
|
990
|
+
rowsRead += stats.length;
|
|
991
|
+
failIfIncomplete(
|
|
992
|
+
stats,
|
|
993
|
+
MAX_REPORT_BUCKETS - (rowsRead - stats.length),
|
|
994
|
+
"commission buckets",
|
|
995
|
+
);
|
|
996
|
+
for (const row of stats) commissionCents += row.commissionCents;
|
|
997
|
+
}
|
|
998
|
+
// ponytail: boundary scans cap at MAX_REPORT_BUCKETS; add hourly commission
|
|
999
|
+
// rollups if busy partial days exceed this explicit report limit.
|
|
1000
|
+
// Daily stats cannot answer partial days (24h and local-day windows).
|
|
1001
|
+
// Read only those edges from the trusted ledger, never round the period.
|
|
1002
|
+
const edges: Array<[number, number]> =
|
|
1003
|
+
fullFrom < fullTo
|
|
1004
|
+
? [
|
|
1005
|
+
[args.from, fullFrom - 1],
|
|
1006
|
+
[fullTo, args.to],
|
|
1007
|
+
]
|
|
1008
|
+
: [[args.from, args.to]];
|
|
1009
|
+
for (const [from, to] of edges) {
|
|
1010
|
+
if (from > to) continue;
|
|
1011
|
+
await assertSourceAvailable(ctx, [siteId], ["conversions"], from);
|
|
1012
|
+
const rows = await ctx.db
|
|
1013
|
+
.query("conversions")
|
|
1014
|
+
.withIndex("by_siteId_and_timestamp", (q) =>
|
|
1015
|
+
q.eq("siteId", siteId).gte("timestamp", from).lte("timestamp", to),
|
|
1016
|
+
)
|
|
1017
|
+
.take(MAX_REPORT_BUCKETS - rowsRead + 1);
|
|
1018
|
+
rowsRead += rows.length;
|
|
1019
|
+
failIfIncomplete(
|
|
1020
|
+
rows,
|
|
1021
|
+
MAX_REPORT_BUCKETS - (rowsRead - rows.length),
|
|
1022
|
+
"commission boundary conversions",
|
|
1023
|
+
);
|
|
1024
|
+
for (const row of rows) commissionCents += row.commissionCents;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return { commissionCents };
|
|
1028
|
+
},
|
|
1029
|
+
});
|
|
1030
|
+
|
|
935
1031
|
export const listSessions = query({
|
|
936
1032
|
args: {
|
|
937
1033
|
siteId: v.id("sites"),
|
package/src/tracker/generated.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated by scripts/build-tracker.mjs. Do not edit.
|
|
2
|
-
export const RASTRO_VERSION = "0.
|
|
2
|
+
export const RASTRO_VERSION = "0.3.0";
|
|
3
3
|
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 a,i,c;for(const e of[\"source\",\"medium\",\"campaign\",\"term\",\"content\"]){const t=n.get(\"utm_\"+e)?.slice(0,64);t&&((a??={})[\"utm_\"+e]=t)}try{i=sessionStorage._r||o(),sessionStorage._r=i,c=r||sessionStorage._a,c&&(sessionStorage._a=c),a||=sessionStorage._u&&JSON.parse(sessionStorage._u),a&&(sessionStorage._u=JSON.stringify(a))}catch{i=o(),c=r}let d=e.dataset.visitor;try{d||=localStorage._rv||=o()}catch{}d||=i;let l,p=0;const g=[],h=document.referrer?new URL(document.referrer).origin:void 0,u=e=>{const o=g.splice(0,50);if(!o.length)return;const n=()=>new Blob([JSON.stringify({siteId:t,events:o,sentAt:Date.now()})]);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&&u(e)},m=(e,t,s)=>{const o=Date.now();g.push({eventId:`${i}.${o.toString(36)}.${p}`,sessionId:i,visitorId:d,type:e,name:t,path:location.pathname.slice(0,256),referrer:h,timestamp:o,sequence:p++,affiliateSlug:c,...s}),clearTimeout(l),l=setTimeout(u,1200)};let v;const f=()=>{const e=location.pathname;e!==v&&(v=e,m(\"pageview\",void 0,a&&{properties:a}))};window.rastro=(e,t,s)=>{if(\"context\"===e)return{sessionId:i,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),a=r&&n.origin!==location.origin;m(s?\"custom\":a?\"outbound\":\"click\",s,{target:(t.dataset.rastroLabel||t.innerText||t.tagName).slice(0,64),href:r?((a?n.origin:\"\")+n.pathname).slice(0,256):void 0})},1);const S=e=>{const t=history[e];history[e]=(...e)=>{t.apply(history,e),f()}};S(\"pushState\"),S(\"replaceState\"),addEventListener(\"popstate\",f),addEventListener(\"pagehide\",()=>{m(\"leave\"),u(1)}),addEventListener(\"pageshow\",e=>{e.persisted&&(m(\"heartbeat\"),u(1))}),setInterval(()=>{m(\"heartbeat\"),u()},2e4),f()})();";
|
|
4
4
|
export const TRACKER_GZIP_BASE64 = "H4sIAAAAAAACA3VV227cNhD9FZswBBKeMLbjGIUEZtEgfXDhJkE3eTLcmKZGEhGJVMnROtuV/r2QVuv1JXmTZsi5nDlnyLlQ7zbGu0gHqHJvugYdSdOFgI6WJtiWgBQuZK5JRyQZLSHExxZ0eeuto77HhYzBJInD+4Ovf19xhit0FBngaBeyClhktuCH1PeHUQSkLrhsm92rsRQT1i15GbTLffP16+WHheSi7//SVM1GLo4ZA6fmHEvUwVSfddBN5LU3mqx3Mk5WAUE5WSJxFrBgYiFjbQ3yE7g4F1mNdKDBgskKH/gMwYEvrln0XTDIgDWY265hwIxuWm1Lx4ARhsniHaEjdiNm9GiXqqPmGzvGZ9koSTjXi4XaDOJ6d+ZGkRgorDdWRYzRerckH3SJ8lvoe88FvDArC0aFvn/u0GCShL+wKiNA9/2L8F2S/Ln89FG2OkR8ca0ToH8SrVPTnUjButIWa66FGIwmU22sGqs1KgwjrLnCB3asbLTkQza2mfe9GmdU7/tZ9f14dQ4zjCfsNJoaWnUyc6NU1zdQ7ekZsMAQMCx2RHvhEdIHW1qXrrzND06gU/jAc69KGdt5Nm9PxMRIL2t0JVVPSekmUo5Z3tf+jl8/638ziuEyTwm2RE89RHT0O6UfNKF0/p6LQdxsuRaU42LiWhZktP/hu4tzfJMku8zvTjNRys7FyhbEvWx9y8VEYS4yTBKnV7bU5IOM6PL3qI13PEIQfV8gmYpH2DRIlc9T9vnT8guDO5+v0wDfEVtd2xWmp4OQE9B8kv0goJyTJ0nHUQzQKI5AEMUjtPa9ZKVsu1jxzdTuZZ7eHm3sII82XpJfTrDwNxditLTD7Y68l3lqYabBZZ7mQOsWUwSnG0wJWk1V+qDc8W90PIjn7O2FgN1Y0wrINhhJN+0E9r8dOoNpe3wMuihsbTXhsu7K1ICUMg4CTI06fLEN+o54LaBWEWn338Hp2cmJGKYBreahF+rJUnxRWoaHSq2ShK8UQsNZq0tcWbxnMJNNJ8mmDb7FQBZjqgchhuzeutzfy6AjBf8IZVvw7TL5QUwphTMBN78Ab8i2S3U6myQNZ6aL5BsGt0cb6vvZO9w+APjbiYA4y31P3Sh2tDvD88W28PRx1XEQAzzoSuf5H2PgKxsJHQbOTG3NdwZ7WZFCSTqUSAtpah8xEmca7joi7+B63Aevtt2/mmq8YVvl0VPJRUUPu2N7fEoMXtH0foBTfv/C+FEgr/+piNq4SI9eSxrTuoVsgydvfC1Aq5Akbt4Hh2o/z60la3hc7EBM9YL5ju5853KW7nqMsNk2lvLntV3pO6z7nqR1DsMX/EHjD+nyo25QPH4BYCw+DYvxFdjlThkTx3tiiSekn1fXOIZTMYOzVI/xrmwkH9bXeJPtPxWXUuLILJK6bes1n32AAgouhiFbcjbKeEmakAlYjs9jW2uDO8vLabe+jZMTip+6dYmVzZHBJJ2Gsxr1agzV8VMx/OpKrPz9lkIoWwxxdOZJwhvOKtSB7lDTHGMMEpEuHWFY6Zrv8jw9JwY4w/Ntn4KL7H/zTKaM3wgAAA==";
|
|
5
5
|
export const TRACKER_RAW_BYTES = 2271;
|