@iann29/rastro 0.8.1 → 0.10.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 +9 -1
- package/agent/integration.md +23 -1
- package/agent/manifest.json +4 -4
- package/dist/client/federation.d.ts +114 -9
- package/dist/client/federation.d.ts.map +1 -1
- package/dist/client/federation.js +54 -2
- package/dist/client/federation.js.map +1 -1
- package/dist/client/index.d.ts +641 -8
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +385 -9
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/api.d.ts +14 -0
- package/dist/component/_generated/api.d.ts.map +1 -1
- package/dist/component/_generated/api.js.map +1 -1
- package/dist/component/_generated/component.d.ts +280 -1
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/server.d.ts +4 -0
- package/dist/component/_generated/server.d.ts.map +1 -1
- package/dist/component/_generated/server.js.map +1 -1
- package/dist/component/constants.d.ts +5 -0
- package/dist/component/constants.d.ts.map +1 -1
- package/dist/component/constants.js +10 -0
- package/dist/component/constants.js.map +1 -1
- package/dist/component/convex.config.d.ts +4 -0
- package/dist/component/convex.config.js +9 -0
- package/dist/component/convex.config.js.map +1 -1
- package/dist/component/errors.d.ts +1 -1
- package/dist/component/errors.d.ts.map +1 -1
- package/dist/component/errors.js.map +1 -1
- package/dist/component/ingest.d.ts.map +1 -1
- package/dist/component/ingest.js +5 -2
- package/dist/component/ingest.js.map +1 -1
- package/dist/component/leads.d.ts +69 -3
- package/dist/component/leads.d.ts.map +1 -1
- package/dist/component/leads.js +419 -16
- package/dist/component/leads.js.map +1 -1
- package/dist/component/meta.d.ts +352 -0
- package/dist/component/meta.d.ts.map +1 -0
- package/dist/component/meta.js +1239 -0
- package/dist/component/meta.js.map +1 -0
- package/dist/component/metaCapi.d.ts +162 -0
- package/dist/component/metaCapi.d.ts.map +1 -0
- package/dist/component/metaCapi.js +754 -0
- package/dist/component/metaCapi.js.map +1 -0
- package/dist/component/metaFake.d.ts +5 -0
- package/dist/component/metaFake.d.ts.map +1 -0
- package/dist/component/metaFake.js +225 -0
- package/dist/component/metaFake.js.map +1 -0
- package/dist/component/metaGraph.d.ts +165 -0
- package/dist/component/metaGraph.d.ts.map +1 -0
- package/dist/component/metaGraph.js +507 -0
- package/dist/component/metaGraph.js.map +1 -0
- package/dist/component/metaSync.d.ts +186 -0
- package/dist/component/metaSync.d.ts.map +1 -0
- package/dist/component/metaSync.js +961 -0
- package/dist/component/metaSync.js.map +1 -0
- package/dist/component/pipeline.d.ts +98 -0
- package/dist/component/pipeline.d.ts.map +1 -0
- package/dist/component/pipeline.js +324 -0
- package/dist/component/pipeline.js.map +1 -0
- package/dist/component/sanitize.d.ts +8 -0
- package/dist/component/sanitize.d.ts.map +1 -1
- package/dist/component/sanitize.js +36 -0
- package/dist/component/sanitize.js.map +1 -1
- package/dist/component/schema.d.ts +407 -3
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +168 -2
- package/dist/component/schema.js.map +1 -1
- package/dist/component/secrets.d.ts +15 -0
- package/dist/component/secrets.d.ts.map +1 -0
- package/dist/component/secrets.js +67 -0
- package/dist/component/secrets.js.map +1 -0
- package/dist/component/sites.d.ts +3 -0
- package/dist/component/sites.d.ts.map +1 -1
- package/dist/component/sites.js +7 -1
- package/dist/component/sites.js.map +1 -1
- package/dist/component/validators.d.ts +1060 -16
- package/dist/component/validators.d.ts.map +1 -1
- package/dist/component/validators.js +313 -3
- package/dist/component/validators.js.map +1 -1
- package/dist/tracker/generated.d.ts +1 -1
- package/dist/tracker/generated.d.ts.map +1 -1
- package/dist/tracker/generated.js +1 -1
- package/dist/tracker/generated.js.map +1 -1
- package/dist/ui/campaigns/CampaignsView.d.ts +48 -0
- package/dist/ui/campaigns/CampaignsView.d.ts.map +1 -0
- package/dist/ui/campaigns/CampaignsView.js +47 -0
- package/dist/ui/campaigns/CampaignsView.js.map +1 -0
- package/dist/ui/campaigns/Conversions.d.ts +16 -0
- package/dist/ui/campaigns/Conversions.d.ts.map +1 -0
- package/dist/ui/campaigns/Conversions.js +40 -0
- package/dist/ui/campaigns/Conversions.js.map +1 -0
- package/dist/ui/campaigns/Drawer.d.ts +24 -0
- package/dist/ui/campaigns/Drawer.d.ts.map +1 -0
- package/dist/ui/campaigns/Drawer.js +120 -0
- package/dist/ui/campaigns/Drawer.js.map +1 -0
- package/dist/ui/campaigns/Icons.d.ts +35 -0
- package/dist/ui/campaigns/Icons.d.ts.map +1 -0
- package/dist/ui/campaigns/Icons.js +41 -0
- package/dist/ui/campaigns/Icons.js.map +1 -0
- package/dist/ui/campaigns/Summary.d.ts +21 -0
- package/dist/ui/campaigns/Summary.d.ts.map +1 -0
- package/dist/ui/campaigns/Summary.js +56 -0
- package/dist/ui/campaigns/Summary.js.map +1 -0
- package/dist/ui/campaigns/Thumbnail.d.ts +11 -0
- package/dist/ui/campaigns/Thumbnail.d.ts.map +1 -0
- package/dist/ui/campaigns/Thumbnail.js +23 -0
- package/dist/ui/campaigns/Thumbnail.js.map +1 -0
- package/dist/ui/campaigns/Tree.d.ts +12 -0
- package/dist/ui/campaigns/Tree.d.ts.map +1 -0
- package/dist/ui/campaigns/Tree.js +105 -0
- package/dist/ui/campaigns/Tree.js.map +1 -0
- package/dist/ui/campaigns/glyphs.d.ts +20 -0
- package/dist/ui/campaigns/glyphs.d.ts.map +1 -0
- package/dist/ui/campaigns/glyphs.js +19 -0
- package/dist/ui/campaigns/glyphs.js.map +1 -0
- package/dist/ui/campaigns/index.d.ts +4 -0
- package/dist/ui/campaigns/index.d.ts.map +1 -0
- package/dist/ui/campaigns/index.js +3 -0
- package/dist/ui/campaigns/index.js.map +1 -0
- package/dist/ui/campaigns/labels.d.ts +53 -0
- package/dist/ui/campaigns/labels.d.ts.map +1 -0
- package/dist/ui/campaigns/labels.js +169 -0
- package/dist/ui/campaigns/labels.js.map +1 -0
- package/dist/ui/campaigns/model.d.ts +99 -0
- package/dist/ui/campaigns/model.d.ts.map +1 -0
- package/dist/ui/campaigns/model.js +230 -0
- package/dist/ui/campaigns/model.js.map +1 -0
- package/dist/ui/campaigns/modes.d.ts +18 -0
- package/dist/ui/campaigns/modes.d.ts.map +1 -0
- package/dist/ui/campaigns/modes.js +7 -0
- package/dist/ui/campaigns/modes.js.map +1 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +19 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/integrations/AccountPicker.d.ts +26 -0
- package/dist/ui/integrations/AccountPicker.d.ts.map +1 -0
- package/dist/ui/integrations/AccountPicker.js +97 -0
- package/dist/ui/integrations/AccountPicker.js.map +1 -0
- package/dist/ui/integrations/ClientCards.d.ts +17 -0
- package/dist/ui/integrations/ClientCards.d.ts.map +1 -0
- package/dist/ui/integrations/ClientCards.js +38 -0
- package/dist/ui/integrations/ClientCards.js.map +1 -0
- package/dist/ui/integrations/ClientsTable.d.ts +36 -0
- package/dist/ui/integrations/ClientsTable.d.ts.map +1 -0
- package/dist/ui/integrations/ClientsTable.js +127 -0
- package/dist/ui/integrations/ClientsTable.js.map +1 -0
- package/dist/ui/integrations/Connect.d.ts +29 -0
- package/dist/ui/integrations/Connect.d.ts.map +1 -0
- package/dist/ui/integrations/Connect.js +49 -0
- package/dist/ui/integrations/Connect.js.map +1 -0
- package/dist/ui/integrations/Connection.d.ts +37 -0
- package/dist/ui/integrations/Connection.d.ts.map +1 -0
- package/dist/ui/integrations/Connection.js +94 -0
- package/dist/ui/integrations/Connection.js.map +1 -0
- package/dist/ui/integrations/Dialogs.d.ts +18 -0
- package/dist/ui/integrations/Dialogs.d.ts.map +1 -0
- package/dist/ui/integrations/Dialogs.js +25 -0
- package/dist/ui/integrations/Dialogs.js.map +1 -0
- package/dist/ui/integrations/EventsFeed.d.ts +15 -0
- package/dist/ui/integrations/EventsFeed.d.ts.map +1 -0
- package/dist/ui/integrations/EventsFeed.js +57 -0
- package/dist/ui/integrations/EventsFeed.js.map +1 -0
- package/dist/ui/integrations/EventsSheet.d.ts +28 -0
- package/dist/ui/integrations/EventsSheet.d.ts.map +1 -0
- package/dist/ui/integrations/EventsSheet.js +121 -0
- package/dist/ui/integrations/EventsSheet.js.map +1 -0
- package/dist/ui/integrations/Icons.d.ts +36 -0
- package/dist/ui/integrations/Icons.d.ts.map +1 -0
- package/dist/ui/integrations/Icons.js +38 -0
- package/dist/ui/integrations/Icons.js.map +1 -0
- package/dist/ui/integrations/IntegrationsView.d.ts +14 -0
- package/dist/ui/integrations/IntegrationsView.d.ts.map +1 -0
- package/dist/ui/integrations/IntegrationsView.js +113 -0
- package/dist/ui/integrations/IntegrationsView.js.map +1 -0
- package/dist/ui/integrations/Overlay.d.ts +18 -0
- package/dist/ui/integrations/Overlay.d.ts.map +1 -0
- package/dist/ui/integrations/Overlay.js +36 -0
- package/dist/ui/integrations/Overlay.js.map +1 -0
- package/dist/ui/integrations/RowMenu.d.ts +15 -0
- package/dist/ui/integrations/RowMenu.d.ts.map +1 -0
- package/dist/ui/integrations/RowMenu.js +47 -0
- package/dist/ui/integrations/RowMenu.js.map +1 -0
- package/dist/ui/integrations/UnlinkDialog.d.ts +18 -0
- package/dist/ui/integrations/UnlinkDialog.d.ts.map +1 -0
- package/dist/ui/integrations/UnlinkDialog.js +29 -0
- package/dist/ui/integrations/UnlinkDialog.js.map +1 -0
- package/dist/ui/integrations/format.d.ts +28 -0
- package/dist/ui/integrations/format.d.ts.map +1 -0
- package/dist/ui/integrations/format.js +101 -0
- package/dist/ui/integrations/format.js.map +1 -0
- package/dist/ui/integrations/index.d.ts +5 -0
- package/dist/ui/integrations/index.d.ts.map +1 -0
- package/dist/ui/integrations/index.js +3 -0
- package/dist/ui/integrations/index.js.map +1 -0
- package/dist/ui/integrations/model.d.ts +84 -0
- package/dist/ui/integrations/model.d.ts.map +1 -0
- package/dist/ui/integrations/model.js +131 -0
- package/dist/ui/integrations/model.js.map +1 -0
- package/dist/ui/integrations/overlay.d.ts +10 -0
- package/dist/ui/integrations/overlay.d.ts.map +1 -0
- package/dist/ui/integrations/overlay.js +77 -0
- package/dist/ui/integrations/overlay.js.map +1 -0
- package/dist/ui/integrations/parts.d.ts +36 -0
- package/dist/ui/integrations/parts.d.ts.map +1 -0
- package/dist/ui/integrations/parts.js +85 -0
- package/dist/ui/integrations/parts.js.map +1 -0
- package/dist/ui/integrations/types.d.ts +96 -0
- package/dist/ui/integrations/types.d.ts.map +1 -0
- package/dist/ui/integrations/types.js +2 -0
- package/dist/ui/integrations/types.js.map +1 -0
- package/dist/ui/metrics.d.ts +106 -0
- package/dist/ui/metrics.d.ts.map +1 -0
- package/dist/ui/metrics.js +166 -0
- package/dist/ui/metrics.js.map +1 -0
- package/dist/ui/portfolio/PortfolioView.d.ts +26 -0
- package/dist/ui/portfolio/PortfolioView.d.ts.map +1 -0
- package/dist/ui/portfolio/PortfolioView.js +216 -0
- package/dist/ui/portfolio/PortfolioView.js.map +1 -0
- package/dist/ui/portfolio/SpendChart.d.ts +10 -0
- package/dist/ui/portfolio/SpendChart.d.ts.map +1 -0
- package/dist/ui/portfolio/SpendChart.js +196 -0
- package/dist/ui/portfolio/SpendChart.js.map +1 -0
- package/dist/ui/portfolio/alerts.d.ts +74 -0
- package/dist/ui/portfolio/alerts.d.ts.map +1 -0
- package/dist/ui/portfolio/alerts.js +291 -0
- package/dist/ui/portfolio/alerts.js.map +1 -0
- package/dist/ui/portfolio/figures.d.ts +79 -0
- package/dist/ui/portfolio/figures.d.ts.map +1 -0
- package/dist/ui/portfolio/figures.js +103 -0
- package/dist/ui/portfolio/figures.js.map +1 -0
- package/dist/ui/portfolio/format.d.ts +8 -0
- package/dist/ui/portfolio/format.d.ts.map +1 -0
- package/dist/ui/portfolio/format.js +28 -0
- package/dist/ui/portfolio/format.js.map +1 -0
- package/dist/ui/sample.d.ts +474 -0
- package/dist/ui/sample.d.ts.map +1 -0
- package/dist/ui/sample.js +553 -0
- package/dist/ui/sample.js.map +1 -0
- package/dist/ui/seed.d.ts +188 -0
- package/dist/ui/seed.d.ts.map +1 -0
- package/dist/ui/seed.js +563 -0
- package/dist/ui/seed.js.map +1 -0
- package/dist/ui/ui.css +3356 -0
- package/docs/federation.md +46 -1
- package/docs/meta-ads.md +746 -0
- package/docs/upgrading.md +110 -0
- package/llms.txt +3 -0
- package/package.json +15 -3
- package/src/component/_generated/api.ts +14 -0
- package/src/component/_generated/component.ts +326 -1
- package/src/component/_generated/server.ts +4 -0
- package/src/component/constants.ts +10 -0
- package/src/component/convex.config.ts +9 -0
- package/src/component/errors.ts +5 -1
- package/src/component/ingest.ts +5 -1
- package/src/component/leads.ts +517 -17
- package/src/component/meta.ts +1461 -0
- package/src/component/metaCapi.ts +921 -0
- package/src/component/metaFake.ts +277 -0
- package/src/component/metaGraph.ts +722 -0
- package/src/component/metaSync.ts +1164 -0
- package/src/component/pipeline.ts +392 -0
- package/src/component/sanitize.ts +45 -0
- package/src/component/schema.ts +197 -1
- package/src/component/secrets.ts +98 -0
- package/src/component/sites.ts +8 -0
- package/src/component/validators.ts +382 -3
- package/src/tracker/generated.ts +1 -1
- package/src/ui/seed.ts +755 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { fail } from "./errors.js";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Tokens at rest: AES-256-GCM under the host's RASTRO_SECRETS_KEY (32 random
|
|
5
|
+
* bytes, base64). The key is an env binding the host passes to the component,
|
|
6
|
+
* so a copy of the database alone cannot read a token. Only actions call
|
|
7
|
+
* `decryptSecret`; no query or mutation ever handles the plain text.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const KEY_BYTES = 32;
|
|
11
|
+
const IV_BYTES = 12;
|
|
12
|
+
|
|
13
|
+
function fromBase64(value: string): Uint8Array {
|
|
14
|
+
const binary = atob(value);
|
|
15
|
+
const bytes = new Uint8Array(binary.length);
|
|
16
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
17
|
+
bytes[index] = binary.charCodeAt(index);
|
|
18
|
+
}
|
|
19
|
+
return bytes;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toBase64(bytes: Uint8Array): string {
|
|
23
|
+
let binary = "";
|
|
24
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
25
|
+
return btoa(binary);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The host's key, or `SECRETS_KEY_MISSING` when it is absent or malformed. */
|
|
29
|
+
async function importKey(rawKey: string | undefined): Promise<CryptoKey> {
|
|
30
|
+
let bytes: Uint8Array | undefined;
|
|
31
|
+
try {
|
|
32
|
+
bytes = rawKey ? fromBase64(rawKey.trim()) : undefined;
|
|
33
|
+
} catch {
|
|
34
|
+
bytes = undefined;
|
|
35
|
+
}
|
|
36
|
+
if (!bytes || bytes.length !== KEY_BYTES) {
|
|
37
|
+
fail(
|
|
38
|
+
"SECRETS_KEY_MISSING",
|
|
39
|
+
"RASTRO_SECRETS_KEY must be 32 random bytes, base64-encoded",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return await crypto.subtle.importKey(
|
|
43
|
+
"raw",
|
|
44
|
+
bytes as Uint8Array<ArrayBuffer>,
|
|
45
|
+
"AES-GCM",
|
|
46
|
+
false,
|
|
47
|
+
["encrypt", "decrypt"],
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether the host passed a usable key, without touching any secret. */
|
|
52
|
+
export function hasSecretsKey(rawKey: string | undefined): boolean {
|
|
53
|
+
try {
|
|
54
|
+
return (
|
|
55
|
+
rawKey !== undefined && fromBase64(rawKey.trim()).length === KEY_BYTES
|
|
56
|
+
);
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function encryptSecret(
|
|
63
|
+
rawKey: string | undefined,
|
|
64
|
+
plain: string,
|
|
65
|
+
): Promise<{ cipher: string; iv: string }> {
|
|
66
|
+
const key = await importKey(rawKey);
|
|
67
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
68
|
+
const cipher = await crypto.subtle.encrypt(
|
|
69
|
+
{ name: "AES-GCM", iv },
|
|
70
|
+
key,
|
|
71
|
+
new TextEncoder().encode(plain),
|
|
72
|
+
);
|
|
73
|
+
return { cipher: toBase64(new Uint8Array(cipher)), iv: toBase64(iv) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The plain token, or `SECRETS_KEY_MISSING` when the key is absent or is not
|
|
78
|
+
* the one the token was sealed with (a rotated key: connect again).
|
|
79
|
+
*/
|
|
80
|
+
export async function decryptSecret(
|
|
81
|
+
rawKey: string | undefined,
|
|
82
|
+
sealed: { cipher: string; iv: string },
|
|
83
|
+
): Promise<string> {
|
|
84
|
+
const key = await importKey(rawKey);
|
|
85
|
+
try {
|
|
86
|
+
const plain = await crypto.subtle.decrypt(
|
|
87
|
+
{ name: "AES-GCM", iv: fromBase64(sealed.iv) as Uint8Array<ArrayBuffer> },
|
|
88
|
+
key,
|
|
89
|
+
fromBase64(sealed.cipher) as Uint8Array<ArrayBuffer>,
|
|
90
|
+
);
|
|
91
|
+
return new TextDecoder().decode(plain);
|
|
92
|
+
} catch {
|
|
93
|
+
fail(
|
|
94
|
+
"SECRETS_KEY_MISSING",
|
|
95
|
+
"the stored token was sealed with another RASTRO_SECRETS_KEY; connect again",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/component/sites.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
sanitizeOpaqueId,
|
|
14
14
|
validateTimezone,
|
|
15
15
|
validateRoutePatterns,
|
|
16
|
+
validateTrackedPaths,
|
|
16
17
|
} from "./sanitize.js";
|
|
17
18
|
import { siteFieldsValidator } from "./validators.js";
|
|
18
19
|
|
|
@@ -125,6 +126,8 @@ export const update = mutation({
|
|
|
125
126
|
currency: v.optional(v.string()),
|
|
126
127
|
cookieless: v.optional(v.boolean()),
|
|
127
128
|
routePatterns: v.optional(v.array(v.string())),
|
|
129
|
+
/** The only paths the site records; `[]` records every path again. */
|
|
130
|
+
trackedPaths: v.optional(v.array(v.string())),
|
|
128
131
|
},
|
|
129
132
|
returns: v.null(),
|
|
130
133
|
handler: async (ctx, args) => {
|
|
@@ -139,6 +142,7 @@ export const update = mutation({
|
|
|
139
142
|
currency?: string;
|
|
140
143
|
cookieless?: boolean;
|
|
141
144
|
routePatterns?: string[];
|
|
145
|
+
trackedPaths?: string[];
|
|
142
146
|
updatedAt: number;
|
|
143
147
|
} = { updatedAt: Date.now() };
|
|
144
148
|
if (args.name !== undefined) {
|
|
@@ -174,6 +178,10 @@ export const update = mutation({
|
|
|
174
178
|
if (args.routePatterns !== undefined) {
|
|
175
179
|
patch.routePatterns = validateRoutePatterns(args.routePatterns);
|
|
176
180
|
}
|
|
181
|
+
if (args.trackedPaths !== undefined) {
|
|
182
|
+
const trackedPaths = validateTrackedPaths(args.trackedPaths);
|
|
183
|
+
patch.trackedPaths = trackedPaths.length ? trackedPaths : undefined;
|
|
184
|
+
}
|
|
177
185
|
} catch (error) {
|
|
178
186
|
fail(
|
|
179
187
|
"INVALID_SITE",
|
|
@@ -162,6 +162,12 @@ export const siteFieldsValidator = v.object({
|
|
|
162
162
|
currency: v.optional(v.string()),
|
|
163
163
|
cookieless: v.boolean(),
|
|
164
164
|
routePatterns: v.optional(v.array(v.string())),
|
|
165
|
+
/**
|
|
166
|
+
* Since 0.9.0: the only paths this site records. Absent or empty records
|
|
167
|
+
* every path. Enforced at ingestion, so a stale tab or a script loaded where
|
|
168
|
+
* it should not be cannot put another page's activity in the site.
|
|
169
|
+
*/
|
|
170
|
+
trackedPaths: v.optional(v.array(v.string())),
|
|
165
171
|
createdAt: v.number(),
|
|
166
172
|
updatedAt: v.number(),
|
|
167
173
|
});
|
|
@@ -431,6 +437,9 @@ export const leadFieldsValidator = v.object({
|
|
|
431
437
|
stageAt: v.number(),
|
|
432
438
|
qualifiedAt: v.optional(v.number()),
|
|
433
439
|
wonAt: v.optional(v.number()),
|
|
440
|
+
/** The host's board column the lead is in now (see `setPipeline`). */
|
|
441
|
+
column: v.optional(v.string()),
|
|
442
|
+
columnAt: v.optional(v.number()),
|
|
434
443
|
updatedAt: v.number(),
|
|
435
444
|
});
|
|
436
445
|
|
|
@@ -443,9 +452,132 @@ export const adCatalogFieldsValidator = v.object({
|
|
|
443
452
|
campaignId: v.optional(v.string()),
|
|
444
453
|
campaignName: v.optional(v.string()),
|
|
445
454
|
thumbnailUrl: v.optional(v.string()),
|
|
455
|
+
// Since 0.10.0, filled by the Meta sync (meta.ts).
|
|
456
|
+
adAccountId: v.optional(v.string()),
|
|
457
|
+
adStatus: v.optional(v.string()),
|
|
458
|
+
creativeType: v.optional(
|
|
459
|
+
v.union(
|
|
460
|
+
v.literal("video"),
|
|
461
|
+
v.literal("image"),
|
|
462
|
+
v.literal("carousel"),
|
|
463
|
+
v.literal("other"),
|
|
464
|
+
),
|
|
465
|
+
),
|
|
466
|
+
/** The creative's thumbnail copied into storage: Meta's URLs expire. */
|
|
467
|
+
thumbnailStorageId: v.optional(v.id("_storage")),
|
|
446
468
|
updatedAt: v.number(),
|
|
447
469
|
});
|
|
448
470
|
|
|
471
|
+
export const metaAdAccountSyncStateValidator = v.union(
|
|
472
|
+
v.literal("backfill"),
|
|
473
|
+
v.literal("live"),
|
|
474
|
+
v.literal("error"),
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
/** An ad account as Meta lists it for a connection's token. */
|
|
478
|
+
export const metaAdAccountValidator = v.object({
|
|
479
|
+
adAccountId: v.string(),
|
|
480
|
+
name: v.string(),
|
|
481
|
+
currency: v.string(),
|
|
482
|
+
timezone: v.string(),
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
/** A pixel (dataset) a connection's token can send conversions to. */
|
|
486
|
+
export const metaPixelValidator = v.object({
|
|
487
|
+
pixelId: v.string(),
|
|
488
|
+
name: v.string(),
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
/** Who authorized a Facebook Login for Business connection, as Meta named them. */
|
|
492
|
+
export const metaAuthorizerValidator = v.object({
|
|
493
|
+
id: v.string(),
|
|
494
|
+
name: v.union(v.string(), v.null()),
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
export const metaConnectionFieldsValidator = v.object({
|
|
498
|
+
/** The host's opaque owner, such as `uzeai:affiliate:<id>`. */
|
|
499
|
+
ownerKey: v.string(),
|
|
500
|
+
tokenCipher: v.string(),
|
|
501
|
+
tokenIv: v.string(),
|
|
502
|
+
businessName: v.optional(v.string()),
|
|
503
|
+
status: v.union(v.literal("active"), v.literal("invalid")),
|
|
504
|
+
/** The token's ad accounts as last listed: what a site may link. */
|
|
505
|
+
adAccounts: v.array(metaAdAccountValidator),
|
|
506
|
+
/** The pixels the listed accounts own, deduplicated: what a site may pick. */
|
|
507
|
+
pixels: v.optional(v.array(metaPixelValidator)),
|
|
508
|
+
/** Facebook Login for Business only: who granted the token, and when. */
|
|
509
|
+
authorizedBy: v.optional(metaAuthorizerValidator),
|
|
510
|
+
authorizedAt: v.optional(v.number()),
|
|
511
|
+
/** When Meta first refused the token; cleared by a new one. */
|
|
512
|
+
invalidSince: v.optional(v.number()),
|
|
513
|
+
/** When the token expires, as Meta said at login; absent for never. */
|
|
514
|
+
tokenExpiresAt: v.optional(v.number()),
|
|
515
|
+
/** Set 7 days before `tokenExpiresAt` by a scheduled run. */
|
|
516
|
+
expiringSoon: v.optional(v.boolean()),
|
|
517
|
+
lastCheckedAt: v.number(),
|
|
518
|
+
lastError: v.optional(v.string()),
|
|
519
|
+
createdAt: v.number(),
|
|
520
|
+
updatedAt: v.number(),
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
export const siteAdAccountFieldsValidator = v.object({
|
|
524
|
+
siteId: v.id("sites"),
|
|
525
|
+
connectionId: v.id("metaConnections"),
|
|
526
|
+
...metaAdAccountValidator.fields,
|
|
527
|
+
syncState: metaAdAccountSyncStateValidator,
|
|
528
|
+
lastSyncAt: v.optional(v.number()),
|
|
529
|
+
/** The last resync of the trailing 7 days (daily, 04:00 account-local). */
|
|
530
|
+
lastDailyResyncAt: v.optional(v.number()),
|
|
531
|
+
/** The last resync of the trailing 28 days (weekly). */
|
|
532
|
+
lastFullResyncAt: v.optional(v.number()),
|
|
533
|
+
lastError: v.optional(v.string()),
|
|
534
|
+
nextSyncAt: v.number(),
|
|
535
|
+
/** The run that holds the account now; a write from any other is dropped. */
|
|
536
|
+
runId: v.optional(v.string()),
|
|
537
|
+
/** `syncNow` asked while a run held the account: run again right after. */
|
|
538
|
+
syncRequested: v.optional(v.boolean()),
|
|
539
|
+
/**
|
|
540
|
+
* While backfilling, the next account-local date to read (`YYYY-MM-DD`):
|
|
541
|
+
* one 30-day block per run, so a failed block resumes where it stopped.
|
|
542
|
+
*/
|
|
543
|
+
backfillFrom: v.optional(v.string()),
|
|
544
|
+
/** Consecutive failed runs, for the growing retry delay. */
|
|
545
|
+
failures: v.optional(v.number()),
|
|
546
|
+
createdAt: v.number(),
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
export const metaPixelSourceValidator = v.union(
|
|
550
|
+
v.literal("connection"),
|
|
551
|
+
v.literal("token"),
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
export const sitePixelFieldsValidator = v.object({
|
|
555
|
+
siteId: v.id("sites"),
|
|
556
|
+
pixelId: v.string(),
|
|
557
|
+
/**
|
|
558
|
+
* `token`: a Conversions API token pasted for this pixel, sealed below.
|
|
559
|
+
* `connection`: the pixel was picked from a connection's listing and sends
|
|
560
|
+
* with that connection's token (no token here). Absent on rows from before
|
|
561
|
+
* the choice existed, which are all `token`.
|
|
562
|
+
*/
|
|
563
|
+
source: v.optional(metaPixelSourceValidator),
|
|
564
|
+
connectionId: v.optional(v.id("metaConnections")),
|
|
565
|
+
/** The pixel's name in Meta, when it came from a listing. */
|
|
566
|
+
name: v.optional(v.string()),
|
|
567
|
+
/** The host owner that set the pixel, when the host named one. */
|
|
568
|
+
setBy: v.optional(v.string()),
|
|
569
|
+
tokenCipher: v.optional(v.string()),
|
|
570
|
+
tokenIv: v.optional(v.string()),
|
|
571
|
+
enabled: v.boolean(),
|
|
572
|
+
testEventCode: v.optional(v.string()),
|
|
573
|
+
/** Whether a new lead is sent as `Lead` (off by default). */
|
|
574
|
+
sendLeadEvent: v.optional(v.boolean()),
|
|
575
|
+
updatedAt: v.number(),
|
|
576
|
+
/** When Meta refused the pixel's own token; a new token clears it. */
|
|
577
|
+
invalidSince: v.optional(v.number()),
|
|
578
|
+
lastError: v.optional(v.string()),
|
|
579
|
+
});
|
|
580
|
+
|
|
449
581
|
export const ingestResultValidator = v.object({
|
|
450
582
|
accepted: v.number(),
|
|
451
583
|
duplicates: v.number(),
|
|
@@ -482,20 +614,40 @@ const leadCountersFields = {
|
|
|
482
614
|
revenueCents: v.number(),
|
|
483
615
|
};
|
|
484
616
|
|
|
617
|
+
// What the site's linked Meta ad accounts spent on a row (since 0.10.0).
|
|
618
|
+
// Optional so a dashboard can read hosts on older releases, and null where
|
|
619
|
+
// no linked account covers the row (no Meta connection, an organic origin).
|
|
620
|
+
// Derived figures (cost per lead or per sale, return) are never stored: the
|
|
621
|
+
// `@iann29/rastro/ui` helpers compute them from these totals.
|
|
622
|
+
const nullableCount = v.optional(v.union(v.number(), v.null()));
|
|
623
|
+
const leadSpendFields = {
|
|
624
|
+
spendCents: nullableCount,
|
|
625
|
+
impressions: nullableCount,
|
|
626
|
+
clicks: nullableCount,
|
|
627
|
+
};
|
|
628
|
+
|
|
485
629
|
export const leadAdRowValidator = v.object({
|
|
486
630
|
adId: v.string(),
|
|
631
|
+
/** Since 0.10.0: the site the ad brought leads to, for multi-site reports. */
|
|
632
|
+
siteId: v.optional(v.string()),
|
|
487
633
|
adName: v.union(v.string(), v.null()),
|
|
488
634
|
sourceApp: v.union(v.string(), v.null()),
|
|
489
635
|
sourceUrl: v.union(v.string(), v.null()),
|
|
490
636
|
title: v.union(v.string(), v.null()),
|
|
491
637
|
thumbnailUrl: v.union(v.string(), v.null()),
|
|
638
|
+
/** Since 0.10.0: "video" | "image" | "carousel" | "other", from Meta. */
|
|
639
|
+
creativeType: v.optional(v.union(v.string(), v.null())),
|
|
640
|
+
/** Since 0.10.0: the ad's effective status at Meta ("ACTIVE", "PAUSED"…). */
|
|
641
|
+
adStatus: v.optional(v.union(v.string(), v.null())),
|
|
492
642
|
...leadCountersFields,
|
|
643
|
+
...leadSpendFields,
|
|
493
644
|
});
|
|
494
645
|
|
|
495
646
|
export const leadAdsetRowValidator = v.object({
|
|
496
647
|
adsetId: v.union(v.string(), v.null()),
|
|
497
648
|
adsetName: v.union(v.string(), v.null()),
|
|
498
649
|
...leadCountersFields,
|
|
650
|
+
...leadSpendFields,
|
|
499
651
|
ads: v.array(leadAdRowValidator),
|
|
500
652
|
});
|
|
501
653
|
|
|
@@ -503,21 +655,162 @@ export const leadCampaignRowValidator = v.object({
|
|
|
503
655
|
campaignId: v.union(v.string(), v.null()),
|
|
504
656
|
campaignName: v.union(v.string(), v.null()),
|
|
505
657
|
...leadCountersFields,
|
|
658
|
+
...leadSpendFields,
|
|
506
659
|
adsets: v.array(leadAdsetRowValidator),
|
|
507
660
|
});
|
|
508
661
|
|
|
662
|
+
export const leadSiteRowValidator = v.object({
|
|
663
|
+
siteId: v.string(),
|
|
664
|
+
...leadCountersFields,
|
|
665
|
+
/**
|
|
666
|
+
* The site's leads that came from an ad (origin `ctwa_ad`): what spend buys,
|
|
667
|
+
* so cost per lead and return use these and never the organic leads.
|
|
668
|
+
*/
|
|
669
|
+
paid: v.object(leadCountersFields),
|
|
670
|
+
spendCents: nullableCount,
|
|
671
|
+
/** The site's synced ad accounts' currency; null without one or with two. */
|
|
672
|
+
spendCurrency: v.optional(v.union(v.string(), v.null())),
|
|
673
|
+
/**
|
|
674
|
+
* True when the site's spend is not comparable with its revenue: an account
|
|
675
|
+
* bills in another currency than the site's, or its accounts in two (its
|
|
676
|
+
* spend is then null).
|
|
677
|
+
*/
|
|
678
|
+
currencyMismatch: v.optional(v.boolean()),
|
|
679
|
+
});
|
|
680
|
+
|
|
509
681
|
export const leadsReportValidator = v.object({
|
|
510
682
|
from: v.number(),
|
|
511
683
|
to: v.number(),
|
|
512
684
|
/** The sites' currency, or null when they differ (revenue then mixes). */
|
|
513
685
|
currency: v.union(v.string(), v.null()),
|
|
514
|
-
totals: v.object(leadCountersFields),
|
|
515
|
-
origins: v.array(
|
|
686
|
+
totals: v.object({ ...leadCountersFields, ...leadSpendFields }),
|
|
687
|
+
origins: v.array(
|
|
688
|
+
v.object({ key: v.string(), ...leadCountersFields, ...leadSpendFields }),
|
|
689
|
+
),
|
|
516
690
|
/**
|
|
517
691
|
* Campaign → ad set → ad, with ads the catalog does not name yet grouped
|
|
518
|
-
* under a null campaign and a null ad set.
|
|
692
|
+
* under a null campaign and a null ad set. Since 0.10.0 an ad that spent
|
|
693
|
+
* without bringing a lead is listed too, with zero leads.
|
|
519
694
|
*/
|
|
520
695
|
campaigns: v.array(leadCampaignRowValidator),
|
|
696
|
+
/** Since 0.10.0: the same counters per requested site. */
|
|
697
|
+
bySite: v.optional(v.array(leadSiteRowValidator)),
|
|
698
|
+
/** Since 0.10.0: the linked ad accounts' currency, null without spend. */
|
|
699
|
+
spendCurrency: v.optional(v.union(v.string(), v.null())),
|
|
700
|
+
/**
|
|
701
|
+
* Since 0.10.0: true when an ad account bills in another currency than the
|
|
702
|
+
* site's revenue; spend is then not comparable with revenue.
|
|
703
|
+
*/
|
|
704
|
+
currencyMismatch: v.optional(v.boolean()),
|
|
705
|
+
/**
|
|
706
|
+
* True when the spend rows ran past their read budget: the campaign tree's
|
|
707
|
+
* spend (or, rarer, all spend) is then null. Leads and revenue stay exact.
|
|
708
|
+
*/
|
|
709
|
+
spendIncomplete: v.optional(v.boolean()),
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
/** One cohort day of the lead funnel, with that day's spend (since 0.10.0). */
|
|
713
|
+
export const leadsDailyValidator = v.array(
|
|
714
|
+
v.object({
|
|
715
|
+
dayStart: v.number(),
|
|
716
|
+
...leadCountersFields,
|
|
717
|
+
spendCents: v.union(v.number(), v.null()),
|
|
718
|
+
}),
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
/** Where a site stands with Meta (since 0.10.0). Never carries a token. */
|
|
722
|
+
export const metaStatusValidator = v.array(
|
|
723
|
+
v.object({
|
|
724
|
+
siteId: v.string(),
|
|
725
|
+
linked: v.boolean(),
|
|
726
|
+
adAccounts: v.array(
|
|
727
|
+
v.object({
|
|
728
|
+
...metaAdAccountValidator.fields,
|
|
729
|
+
syncState: metaAdAccountSyncStateValidator,
|
|
730
|
+
lastSyncAt: v.union(v.number(), v.null()),
|
|
731
|
+
lastError: v.union(v.string(), v.null()),
|
|
732
|
+
/** Asked with an owner: whether that owner made this link. */
|
|
733
|
+
own: v.optional(v.boolean()),
|
|
734
|
+
/**
|
|
735
|
+
* The first backfill's 30-day blocks read (`done`) of `total` (3 for
|
|
736
|
+
* 90 days); null once the account is live. Optional for older hosts.
|
|
737
|
+
*/
|
|
738
|
+
backfill: v.optional(
|
|
739
|
+
v.union(v.object({ done: v.number(), total: v.number() }), v.null()),
|
|
740
|
+
),
|
|
741
|
+
}),
|
|
742
|
+
),
|
|
743
|
+
pixel: v.object({
|
|
744
|
+
enabled: v.boolean(),
|
|
745
|
+
pixelId: v.union(v.string(), v.null()),
|
|
746
|
+
// Optional so a dashboard reads hosts from before Facebook Login; this
|
|
747
|
+
// build always sets both.
|
|
748
|
+
source: v.optional(v.union(metaPixelSourceValidator, v.null())),
|
|
749
|
+
name: v.optional(v.union(v.string(), v.null())),
|
|
750
|
+
lastError: v.optional(v.union(v.string(), v.null())),
|
|
751
|
+
/** Asked with an owner: whether that owner set the pixel. */
|
|
752
|
+
own: v.optional(v.boolean()),
|
|
753
|
+
/**
|
|
754
|
+
* A `testEventCode` is set: the pixel's events go to Events Manager's
|
|
755
|
+
* Test Events only, never to the ads. Optional for older hosts.
|
|
756
|
+
*/
|
|
757
|
+
testMode: v.optional(v.boolean()),
|
|
758
|
+
}),
|
|
759
|
+
conversions: v.object({
|
|
760
|
+
pending: v.number(),
|
|
761
|
+
sent: v.number(),
|
|
762
|
+
failed: v.number(),
|
|
763
|
+
/**
|
|
764
|
+
* Accepted by Meta in the last 30 UTC days, by event, and today. Null
|
|
765
|
+
* unless the reader passed `now`; optional for older hosts.
|
|
766
|
+
*/
|
|
767
|
+
last30d: v.optional(
|
|
768
|
+
v.union(
|
|
769
|
+
v.object({
|
|
770
|
+
Purchase: v.number(),
|
|
771
|
+
QualifiedLead: v.number(),
|
|
772
|
+
Lead: v.number(),
|
|
773
|
+
Schedule: v.number(),
|
|
774
|
+
}),
|
|
775
|
+
v.null(),
|
|
776
|
+
),
|
|
777
|
+
),
|
|
778
|
+
sentToday: v.optional(v.union(v.number(), v.null())),
|
|
779
|
+
}),
|
|
780
|
+
}),
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
export const metaConversionEventNameValidator = v.union(
|
|
784
|
+
v.literal("Lead"),
|
|
785
|
+
v.literal("QualifiedLead"),
|
|
786
|
+
v.literal("Schedule"),
|
|
787
|
+
v.literal("Purchase"),
|
|
788
|
+
);
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* What the host knows of the person behind a lead, every value already a
|
|
792
|
+
* SHA-256 hex digest: `ph` the normalized phone numbers, `externalId` the
|
|
793
|
+
* host's own id for the contact. Kept only until the conversion is sent.
|
|
794
|
+
*/
|
|
795
|
+
export const metaUserDataValidator = v.object({
|
|
796
|
+
ph: v.array(v.string()),
|
|
797
|
+
externalId: v.optional(v.string()),
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
/** A conversion sent (or queued) to the site's Meta pixel. No user data. */
|
|
801
|
+
export const metaConversionValidator = v.object({
|
|
802
|
+
eventId: v.string(),
|
|
803
|
+
eventName: metaConversionEventNameValidator,
|
|
804
|
+
/** The lead the conversion reports, so a journey can show it was sent. */
|
|
805
|
+
leadId: v.union(v.string(), v.null()),
|
|
806
|
+
leadLabel: v.union(v.string(), v.null()),
|
|
807
|
+
adId: v.union(v.string(), v.null()),
|
|
808
|
+
/** The ad's name from the catalog, when the sync or the host named it. */
|
|
809
|
+
adName: v.union(v.string(), v.null()),
|
|
810
|
+
valueCents: v.union(v.number(), v.null()),
|
|
811
|
+
status: v.union(v.literal("pending"), v.literal("sent"), v.literal("failed")),
|
|
812
|
+
createdAt: v.number(),
|
|
813
|
+
sentAt: v.union(v.number(), v.null()),
|
|
521
814
|
});
|
|
522
815
|
|
|
523
816
|
export const leadSummaryValidator = v.object({
|
|
@@ -563,3 +856,89 @@ export const leadJourneyValidator = v.union(
|
|
|
563
856
|
),
|
|
564
857
|
}),
|
|
565
858
|
);
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* The events a board column may send to Meta. `Purchase` never comes from a
|
|
862
|
+
* column: it goes when the host records the sale (`won` with a value).
|
|
863
|
+
*/
|
|
864
|
+
export const metaColumnEventValidator = v.union(
|
|
865
|
+
v.literal("Lead"),
|
|
866
|
+
v.literal("QualifiedLead"),
|
|
867
|
+
v.literal("Schedule"),
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
/** One column of the host's board, as `setPipeline` declares it. */
|
|
871
|
+
export const pipelineColumnValidator = v.object({
|
|
872
|
+
key: v.string(),
|
|
873
|
+
name: v.string(),
|
|
874
|
+
color: v.optional(v.string()),
|
|
875
|
+
/** The block the board shows the column in, such as "Prospecção". */
|
|
876
|
+
group: v.optional(v.string()),
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
/** A connection as a screen shows it. Never a token. */
|
|
880
|
+
export const metaConnectionValidator = v.union(
|
|
881
|
+
v.null(),
|
|
882
|
+
v.object({
|
|
883
|
+
status: v.union(v.literal("active"), v.literal("invalid")),
|
|
884
|
+
businessName: v.union(v.string(), v.null()),
|
|
885
|
+
authorizedBy: v.union(
|
|
886
|
+
v.object({ name: v.union(v.string(), v.null()) }),
|
|
887
|
+
v.null(),
|
|
888
|
+
),
|
|
889
|
+
authorizedAt: v.union(v.number(), v.null()),
|
|
890
|
+
invalidSince: v.union(v.number(), v.null()),
|
|
891
|
+
lastError: v.union(v.string(), v.null()),
|
|
892
|
+
/** When the token expires; null when it never does (or is not known). */
|
|
893
|
+
expiresAt: v.union(v.number(), v.null()),
|
|
894
|
+
/** Fewer than 7 days left: the screen asks for a new login. */
|
|
895
|
+
expiringSoon: v.boolean(),
|
|
896
|
+
adAccounts: v.array(
|
|
897
|
+
v.object({
|
|
898
|
+
...metaAdAccountValidator.fields,
|
|
899
|
+
/** The site the account feeds, whoever linked it; null when free. */
|
|
900
|
+
linkedSiteId: v.union(v.string(), v.null()),
|
|
901
|
+
/**
|
|
902
|
+
* Federated reads only: the account feeds a site outside the
|
|
903
|
+
* caller's grant, whose id is withheld (`linkedSiteId` is null).
|
|
904
|
+
*/
|
|
905
|
+
linkedOutsideScope: v.optional(v.boolean()),
|
|
906
|
+
}),
|
|
907
|
+
),
|
|
908
|
+
pixels: v.array(metaPixelValidator),
|
|
909
|
+
}),
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
export const metaEventMapValidator = v.object({
|
|
913
|
+
columns: v.array(
|
|
914
|
+
v.object({
|
|
915
|
+
key: v.string(),
|
|
916
|
+
name: v.string(),
|
|
917
|
+
color: v.union(v.string(), v.null()),
|
|
918
|
+
group: v.union(v.string(), v.null()),
|
|
919
|
+
event: v.union(metaColumnEventValidator, v.null()),
|
|
920
|
+
/** Leads that entered the column in the 30 days before `now`. */
|
|
921
|
+
monthCount: v.union(v.number(), v.null()),
|
|
922
|
+
}),
|
|
923
|
+
),
|
|
924
|
+
sendLeadEvent: v.boolean(),
|
|
925
|
+
purchase: v.object({ enabled: v.boolean() }),
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
export const metaUnlinkPreviewValidator = v.object({
|
|
929
|
+
spendCents: v.number(),
|
|
930
|
+
currency: v.string(),
|
|
931
|
+
/** Days from the account's first to its last day with spend, inclusive. */
|
|
932
|
+
days: v.number(),
|
|
933
|
+
campaigns: v.number(),
|
|
934
|
+
/** A read cap was hit: the figures are at least these. */
|
|
935
|
+
partial: v.boolean(),
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
/** What connecting a Meta token answers: its accounts and their pixels. */
|
|
939
|
+
export const metaConnectResultValidator = v.object({
|
|
940
|
+
connectionId: v.string(),
|
|
941
|
+
businessName: v.union(v.string(), v.null()),
|
|
942
|
+
adAccounts: v.array(metaAdAccountValidator),
|
|
943
|
+
pixels: v.array(metaPixelValidator),
|
|
944
|
+
});
|
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.10.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;
|