@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.
Files changed (271) hide show
  1. package/README.md +9 -1
  2. package/agent/integration.md +23 -1
  3. package/agent/manifest.json +4 -4
  4. package/dist/client/federation.d.ts +114 -9
  5. package/dist/client/federation.d.ts.map +1 -1
  6. package/dist/client/federation.js +54 -2
  7. package/dist/client/federation.js.map +1 -1
  8. package/dist/client/index.d.ts +641 -8
  9. package/dist/client/index.d.ts.map +1 -1
  10. package/dist/client/index.js +385 -9
  11. package/dist/client/index.js.map +1 -1
  12. package/dist/component/_generated/api.d.ts +14 -0
  13. package/dist/component/_generated/api.d.ts.map +1 -1
  14. package/dist/component/_generated/api.js.map +1 -1
  15. package/dist/component/_generated/component.d.ts +280 -1
  16. package/dist/component/_generated/component.d.ts.map +1 -1
  17. package/dist/component/_generated/server.d.ts +4 -0
  18. package/dist/component/_generated/server.d.ts.map +1 -1
  19. package/dist/component/_generated/server.js.map +1 -1
  20. package/dist/component/constants.d.ts +5 -0
  21. package/dist/component/constants.d.ts.map +1 -1
  22. package/dist/component/constants.js +10 -0
  23. package/dist/component/constants.js.map +1 -1
  24. package/dist/component/convex.config.d.ts +4 -0
  25. package/dist/component/convex.config.js +9 -0
  26. package/dist/component/convex.config.js.map +1 -1
  27. package/dist/component/errors.d.ts +1 -1
  28. package/dist/component/errors.d.ts.map +1 -1
  29. package/dist/component/errors.js.map +1 -1
  30. package/dist/component/ingest.d.ts.map +1 -1
  31. package/dist/component/ingest.js +5 -2
  32. package/dist/component/ingest.js.map +1 -1
  33. package/dist/component/leads.d.ts +69 -3
  34. package/dist/component/leads.d.ts.map +1 -1
  35. package/dist/component/leads.js +419 -16
  36. package/dist/component/leads.js.map +1 -1
  37. package/dist/component/meta.d.ts +352 -0
  38. package/dist/component/meta.d.ts.map +1 -0
  39. package/dist/component/meta.js +1239 -0
  40. package/dist/component/meta.js.map +1 -0
  41. package/dist/component/metaCapi.d.ts +162 -0
  42. package/dist/component/metaCapi.d.ts.map +1 -0
  43. package/dist/component/metaCapi.js +754 -0
  44. package/dist/component/metaCapi.js.map +1 -0
  45. package/dist/component/metaFake.d.ts +5 -0
  46. package/dist/component/metaFake.d.ts.map +1 -0
  47. package/dist/component/metaFake.js +225 -0
  48. package/dist/component/metaFake.js.map +1 -0
  49. package/dist/component/metaGraph.d.ts +165 -0
  50. package/dist/component/metaGraph.d.ts.map +1 -0
  51. package/dist/component/metaGraph.js +507 -0
  52. package/dist/component/metaGraph.js.map +1 -0
  53. package/dist/component/metaSync.d.ts +186 -0
  54. package/dist/component/metaSync.d.ts.map +1 -0
  55. package/dist/component/metaSync.js +961 -0
  56. package/dist/component/metaSync.js.map +1 -0
  57. package/dist/component/pipeline.d.ts +98 -0
  58. package/dist/component/pipeline.d.ts.map +1 -0
  59. package/dist/component/pipeline.js +324 -0
  60. package/dist/component/pipeline.js.map +1 -0
  61. package/dist/component/sanitize.d.ts +8 -0
  62. package/dist/component/sanitize.d.ts.map +1 -1
  63. package/dist/component/sanitize.js +36 -0
  64. package/dist/component/sanitize.js.map +1 -1
  65. package/dist/component/schema.d.ts +407 -3
  66. package/dist/component/schema.d.ts.map +1 -1
  67. package/dist/component/schema.js +168 -2
  68. package/dist/component/schema.js.map +1 -1
  69. package/dist/component/secrets.d.ts +15 -0
  70. package/dist/component/secrets.d.ts.map +1 -0
  71. package/dist/component/secrets.js +67 -0
  72. package/dist/component/secrets.js.map +1 -0
  73. package/dist/component/sites.d.ts +3 -0
  74. package/dist/component/sites.d.ts.map +1 -1
  75. package/dist/component/sites.js +7 -1
  76. package/dist/component/sites.js.map +1 -1
  77. package/dist/component/validators.d.ts +1060 -16
  78. package/dist/component/validators.d.ts.map +1 -1
  79. package/dist/component/validators.js +313 -3
  80. package/dist/component/validators.js.map +1 -1
  81. package/dist/tracker/generated.d.ts +1 -1
  82. package/dist/tracker/generated.d.ts.map +1 -1
  83. package/dist/tracker/generated.js +1 -1
  84. package/dist/tracker/generated.js.map +1 -1
  85. package/dist/ui/campaigns/CampaignsView.d.ts +48 -0
  86. package/dist/ui/campaigns/CampaignsView.d.ts.map +1 -0
  87. package/dist/ui/campaigns/CampaignsView.js +47 -0
  88. package/dist/ui/campaigns/CampaignsView.js.map +1 -0
  89. package/dist/ui/campaigns/Conversions.d.ts +16 -0
  90. package/dist/ui/campaigns/Conversions.d.ts.map +1 -0
  91. package/dist/ui/campaigns/Conversions.js +40 -0
  92. package/dist/ui/campaigns/Conversions.js.map +1 -0
  93. package/dist/ui/campaigns/Drawer.d.ts +24 -0
  94. package/dist/ui/campaigns/Drawer.d.ts.map +1 -0
  95. package/dist/ui/campaigns/Drawer.js +120 -0
  96. package/dist/ui/campaigns/Drawer.js.map +1 -0
  97. package/dist/ui/campaigns/Icons.d.ts +35 -0
  98. package/dist/ui/campaigns/Icons.d.ts.map +1 -0
  99. package/dist/ui/campaigns/Icons.js +41 -0
  100. package/dist/ui/campaigns/Icons.js.map +1 -0
  101. package/dist/ui/campaigns/Summary.d.ts +21 -0
  102. package/dist/ui/campaigns/Summary.d.ts.map +1 -0
  103. package/dist/ui/campaigns/Summary.js +56 -0
  104. package/dist/ui/campaigns/Summary.js.map +1 -0
  105. package/dist/ui/campaigns/Thumbnail.d.ts +11 -0
  106. package/dist/ui/campaigns/Thumbnail.d.ts.map +1 -0
  107. package/dist/ui/campaigns/Thumbnail.js +23 -0
  108. package/dist/ui/campaigns/Thumbnail.js.map +1 -0
  109. package/dist/ui/campaigns/Tree.d.ts +12 -0
  110. package/dist/ui/campaigns/Tree.d.ts.map +1 -0
  111. package/dist/ui/campaigns/Tree.js +105 -0
  112. package/dist/ui/campaigns/Tree.js.map +1 -0
  113. package/dist/ui/campaigns/glyphs.d.ts +20 -0
  114. package/dist/ui/campaigns/glyphs.d.ts.map +1 -0
  115. package/dist/ui/campaigns/glyphs.js +19 -0
  116. package/dist/ui/campaigns/glyphs.js.map +1 -0
  117. package/dist/ui/campaigns/index.d.ts +4 -0
  118. package/dist/ui/campaigns/index.d.ts.map +1 -0
  119. package/dist/ui/campaigns/index.js +3 -0
  120. package/dist/ui/campaigns/index.js.map +1 -0
  121. package/dist/ui/campaigns/labels.d.ts +53 -0
  122. package/dist/ui/campaigns/labels.d.ts.map +1 -0
  123. package/dist/ui/campaigns/labels.js +169 -0
  124. package/dist/ui/campaigns/labels.js.map +1 -0
  125. package/dist/ui/campaigns/model.d.ts +99 -0
  126. package/dist/ui/campaigns/model.d.ts.map +1 -0
  127. package/dist/ui/campaigns/model.js +230 -0
  128. package/dist/ui/campaigns/model.js.map +1 -0
  129. package/dist/ui/campaigns/modes.d.ts +18 -0
  130. package/dist/ui/campaigns/modes.d.ts.map +1 -0
  131. package/dist/ui/campaigns/modes.js +7 -0
  132. package/dist/ui/campaigns/modes.js.map +1 -0
  133. package/dist/ui/index.d.ts +21 -0
  134. package/dist/ui/index.d.ts.map +1 -0
  135. package/dist/ui/index.js +19 -0
  136. package/dist/ui/index.js.map +1 -0
  137. package/dist/ui/integrations/AccountPicker.d.ts +26 -0
  138. package/dist/ui/integrations/AccountPicker.d.ts.map +1 -0
  139. package/dist/ui/integrations/AccountPicker.js +97 -0
  140. package/dist/ui/integrations/AccountPicker.js.map +1 -0
  141. package/dist/ui/integrations/ClientCards.d.ts +17 -0
  142. package/dist/ui/integrations/ClientCards.d.ts.map +1 -0
  143. package/dist/ui/integrations/ClientCards.js +38 -0
  144. package/dist/ui/integrations/ClientCards.js.map +1 -0
  145. package/dist/ui/integrations/ClientsTable.d.ts +36 -0
  146. package/dist/ui/integrations/ClientsTable.d.ts.map +1 -0
  147. package/dist/ui/integrations/ClientsTable.js +127 -0
  148. package/dist/ui/integrations/ClientsTable.js.map +1 -0
  149. package/dist/ui/integrations/Connect.d.ts +29 -0
  150. package/dist/ui/integrations/Connect.d.ts.map +1 -0
  151. package/dist/ui/integrations/Connect.js +49 -0
  152. package/dist/ui/integrations/Connect.js.map +1 -0
  153. package/dist/ui/integrations/Connection.d.ts +37 -0
  154. package/dist/ui/integrations/Connection.d.ts.map +1 -0
  155. package/dist/ui/integrations/Connection.js +94 -0
  156. package/dist/ui/integrations/Connection.js.map +1 -0
  157. package/dist/ui/integrations/Dialogs.d.ts +18 -0
  158. package/dist/ui/integrations/Dialogs.d.ts.map +1 -0
  159. package/dist/ui/integrations/Dialogs.js +25 -0
  160. package/dist/ui/integrations/Dialogs.js.map +1 -0
  161. package/dist/ui/integrations/EventsFeed.d.ts +15 -0
  162. package/dist/ui/integrations/EventsFeed.d.ts.map +1 -0
  163. package/dist/ui/integrations/EventsFeed.js +57 -0
  164. package/dist/ui/integrations/EventsFeed.js.map +1 -0
  165. package/dist/ui/integrations/EventsSheet.d.ts +28 -0
  166. package/dist/ui/integrations/EventsSheet.d.ts.map +1 -0
  167. package/dist/ui/integrations/EventsSheet.js +121 -0
  168. package/dist/ui/integrations/EventsSheet.js.map +1 -0
  169. package/dist/ui/integrations/Icons.d.ts +36 -0
  170. package/dist/ui/integrations/Icons.d.ts.map +1 -0
  171. package/dist/ui/integrations/Icons.js +38 -0
  172. package/dist/ui/integrations/Icons.js.map +1 -0
  173. package/dist/ui/integrations/IntegrationsView.d.ts +14 -0
  174. package/dist/ui/integrations/IntegrationsView.d.ts.map +1 -0
  175. package/dist/ui/integrations/IntegrationsView.js +113 -0
  176. package/dist/ui/integrations/IntegrationsView.js.map +1 -0
  177. package/dist/ui/integrations/Overlay.d.ts +18 -0
  178. package/dist/ui/integrations/Overlay.d.ts.map +1 -0
  179. package/dist/ui/integrations/Overlay.js +36 -0
  180. package/dist/ui/integrations/Overlay.js.map +1 -0
  181. package/dist/ui/integrations/RowMenu.d.ts +15 -0
  182. package/dist/ui/integrations/RowMenu.d.ts.map +1 -0
  183. package/dist/ui/integrations/RowMenu.js +47 -0
  184. package/dist/ui/integrations/RowMenu.js.map +1 -0
  185. package/dist/ui/integrations/UnlinkDialog.d.ts +18 -0
  186. package/dist/ui/integrations/UnlinkDialog.d.ts.map +1 -0
  187. package/dist/ui/integrations/UnlinkDialog.js +29 -0
  188. package/dist/ui/integrations/UnlinkDialog.js.map +1 -0
  189. package/dist/ui/integrations/format.d.ts +28 -0
  190. package/dist/ui/integrations/format.d.ts.map +1 -0
  191. package/dist/ui/integrations/format.js +101 -0
  192. package/dist/ui/integrations/format.js.map +1 -0
  193. package/dist/ui/integrations/index.d.ts +5 -0
  194. package/dist/ui/integrations/index.d.ts.map +1 -0
  195. package/dist/ui/integrations/index.js +3 -0
  196. package/dist/ui/integrations/index.js.map +1 -0
  197. package/dist/ui/integrations/model.d.ts +84 -0
  198. package/dist/ui/integrations/model.d.ts.map +1 -0
  199. package/dist/ui/integrations/model.js +131 -0
  200. package/dist/ui/integrations/model.js.map +1 -0
  201. package/dist/ui/integrations/overlay.d.ts +10 -0
  202. package/dist/ui/integrations/overlay.d.ts.map +1 -0
  203. package/dist/ui/integrations/overlay.js +77 -0
  204. package/dist/ui/integrations/overlay.js.map +1 -0
  205. package/dist/ui/integrations/parts.d.ts +36 -0
  206. package/dist/ui/integrations/parts.d.ts.map +1 -0
  207. package/dist/ui/integrations/parts.js +85 -0
  208. package/dist/ui/integrations/parts.js.map +1 -0
  209. package/dist/ui/integrations/types.d.ts +96 -0
  210. package/dist/ui/integrations/types.d.ts.map +1 -0
  211. package/dist/ui/integrations/types.js +2 -0
  212. package/dist/ui/integrations/types.js.map +1 -0
  213. package/dist/ui/metrics.d.ts +106 -0
  214. package/dist/ui/metrics.d.ts.map +1 -0
  215. package/dist/ui/metrics.js +166 -0
  216. package/dist/ui/metrics.js.map +1 -0
  217. package/dist/ui/portfolio/PortfolioView.d.ts +26 -0
  218. package/dist/ui/portfolio/PortfolioView.d.ts.map +1 -0
  219. package/dist/ui/portfolio/PortfolioView.js +216 -0
  220. package/dist/ui/portfolio/PortfolioView.js.map +1 -0
  221. package/dist/ui/portfolio/SpendChart.d.ts +10 -0
  222. package/dist/ui/portfolio/SpendChart.d.ts.map +1 -0
  223. package/dist/ui/portfolio/SpendChart.js +196 -0
  224. package/dist/ui/portfolio/SpendChart.js.map +1 -0
  225. package/dist/ui/portfolio/alerts.d.ts +74 -0
  226. package/dist/ui/portfolio/alerts.d.ts.map +1 -0
  227. package/dist/ui/portfolio/alerts.js +291 -0
  228. package/dist/ui/portfolio/alerts.js.map +1 -0
  229. package/dist/ui/portfolio/figures.d.ts +79 -0
  230. package/dist/ui/portfolio/figures.d.ts.map +1 -0
  231. package/dist/ui/portfolio/figures.js +103 -0
  232. package/dist/ui/portfolio/figures.js.map +1 -0
  233. package/dist/ui/portfolio/format.d.ts +8 -0
  234. package/dist/ui/portfolio/format.d.ts.map +1 -0
  235. package/dist/ui/portfolio/format.js +28 -0
  236. package/dist/ui/portfolio/format.js.map +1 -0
  237. package/dist/ui/sample.d.ts +474 -0
  238. package/dist/ui/sample.d.ts.map +1 -0
  239. package/dist/ui/sample.js +553 -0
  240. package/dist/ui/sample.js.map +1 -0
  241. package/dist/ui/seed.d.ts +188 -0
  242. package/dist/ui/seed.d.ts.map +1 -0
  243. package/dist/ui/seed.js +563 -0
  244. package/dist/ui/seed.js.map +1 -0
  245. package/dist/ui/ui.css +3356 -0
  246. package/docs/federation.md +46 -1
  247. package/docs/meta-ads.md +746 -0
  248. package/docs/upgrading.md +110 -0
  249. package/llms.txt +3 -0
  250. package/package.json +15 -3
  251. package/src/component/_generated/api.ts +14 -0
  252. package/src/component/_generated/component.ts +326 -1
  253. package/src/component/_generated/server.ts +4 -0
  254. package/src/component/constants.ts +10 -0
  255. package/src/component/convex.config.ts +9 -0
  256. package/src/component/errors.ts +5 -1
  257. package/src/component/ingest.ts +5 -1
  258. package/src/component/leads.ts +517 -17
  259. package/src/component/meta.ts +1461 -0
  260. package/src/component/metaCapi.ts +921 -0
  261. package/src/component/metaFake.ts +277 -0
  262. package/src/component/metaGraph.ts +722 -0
  263. package/src/component/metaSync.ts +1164 -0
  264. package/src/component/pipeline.ts +392 -0
  265. package/src/component/sanitize.ts +45 -0
  266. package/src/component/schema.ts +197 -1
  267. package/src/component/secrets.ts +98 -0
  268. package/src/component/sites.ts +8 -0
  269. package/src/component/validators.ts +382 -3
  270. package/src/tracker/generated.ts +1 -1
  271. 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
+ }
@@ -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(v.object({ key: v.string(), ...leadCountersFields })),
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
+ });
@@ -1,5 +1,5 @@
1
1
  // Generated by scripts/build-tracker.mjs. Do not edit.
2
- export const RASTRO_VERSION = "0.8.1";
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 = "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";
5
5
  export const TRACKER_RAW_BYTES = 2999;