@iann29/rastro 0.9.0 → 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 (257) 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 +637 -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 +277 -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/leads.d.ts +69 -3
  31. package/dist/component/leads.d.ts.map +1 -1
  32. package/dist/component/leads.js +419 -16
  33. package/dist/component/leads.js.map +1 -1
  34. package/dist/component/meta.d.ts +352 -0
  35. package/dist/component/meta.d.ts.map +1 -0
  36. package/dist/component/meta.js +1239 -0
  37. package/dist/component/meta.js.map +1 -0
  38. package/dist/component/metaCapi.d.ts +162 -0
  39. package/dist/component/metaCapi.d.ts.map +1 -0
  40. package/dist/component/metaCapi.js +754 -0
  41. package/dist/component/metaCapi.js.map +1 -0
  42. package/dist/component/metaFake.d.ts +5 -0
  43. package/dist/component/metaFake.d.ts.map +1 -0
  44. package/dist/component/metaFake.js +225 -0
  45. package/dist/component/metaFake.js.map +1 -0
  46. package/dist/component/metaGraph.d.ts +165 -0
  47. package/dist/component/metaGraph.d.ts.map +1 -0
  48. package/dist/component/metaGraph.js +507 -0
  49. package/dist/component/metaGraph.js.map +1 -0
  50. package/dist/component/metaSync.d.ts +186 -0
  51. package/dist/component/metaSync.d.ts.map +1 -0
  52. package/dist/component/metaSync.js +961 -0
  53. package/dist/component/metaSync.js.map +1 -0
  54. package/dist/component/pipeline.d.ts +98 -0
  55. package/dist/component/pipeline.d.ts.map +1 -0
  56. package/dist/component/pipeline.js +324 -0
  57. package/dist/component/pipeline.js.map +1 -0
  58. package/dist/component/schema.d.ts +404 -2
  59. package/dist/component/schema.d.ts.map +1 -1
  60. package/dist/component/schema.js +168 -2
  61. package/dist/component/schema.js.map +1 -1
  62. package/dist/component/secrets.d.ts +15 -0
  63. package/dist/component/secrets.d.ts.map +1 -0
  64. package/dist/component/secrets.js +67 -0
  65. package/dist/component/secrets.js.map +1 -0
  66. package/dist/component/validators.d.ts +1052 -15
  67. package/dist/component/validators.d.ts.map +1 -1
  68. package/dist/component/validators.js +307 -3
  69. package/dist/component/validators.js.map +1 -1
  70. package/dist/tracker/generated.d.ts +1 -1
  71. package/dist/tracker/generated.d.ts.map +1 -1
  72. package/dist/tracker/generated.js +1 -1
  73. package/dist/tracker/generated.js.map +1 -1
  74. package/dist/ui/campaigns/CampaignsView.d.ts +48 -0
  75. package/dist/ui/campaigns/CampaignsView.d.ts.map +1 -0
  76. package/dist/ui/campaigns/CampaignsView.js +47 -0
  77. package/dist/ui/campaigns/CampaignsView.js.map +1 -0
  78. package/dist/ui/campaigns/Conversions.d.ts +16 -0
  79. package/dist/ui/campaigns/Conversions.d.ts.map +1 -0
  80. package/dist/ui/campaigns/Conversions.js +40 -0
  81. package/dist/ui/campaigns/Conversions.js.map +1 -0
  82. package/dist/ui/campaigns/Drawer.d.ts +24 -0
  83. package/dist/ui/campaigns/Drawer.d.ts.map +1 -0
  84. package/dist/ui/campaigns/Drawer.js +120 -0
  85. package/dist/ui/campaigns/Drawer.js.map +1 -0
  86. package/dist/ui/campaigns/Icons.d.ts +35 -0
  87. package/dist/ui/campaigns/Icons.d.ts.map +1 -0
  88. package/dist/ui/campaigns/Icons.js +41 -0
  89. package/dist/ui/campaigns/Icons.js.map +1 -0
  90. package/dist/ui/campaigns/Summary.d.ts +21 -0
  91. package/dist/ui/campaigns/Summary.d.ts.map +1 -0
  92. package/dist/ui/campaigns/Summary.js +56 -0
  93. package/dist/ui/campaigns/Summary.js.map +1 -0
  94. package/dist/ui/campaigns/Thumbnail.d.ts +11 -0
  95. package/dist/ui/campaigns/Thumbnail.d.ts.map +1 -0
  96. package/dist/ui/campaigns/Thumbnail.js +23 -0
  97. package/dist/ui/campaigns/Thumbnail.js.map +1 -0
  98. package/dist/ui/campaigns/Tree.d.ts +12 -0
  99. package/dist/ui/campaigns/Tree.d.ts.map +1 -0
  100. package/dist/ui/campaigns/Tree.js +105 -0
  101. package/dist/ui/campaigns/Tree.js.map +1 -0
  102. package/dist/ui/campaigns/glyphs.d.ts +20 -0
  103. package/dist/ui/campaigns/glyphs.d.ts.map +1 -0
  104. package/dist/ui/campaigns/glyphs.js +19 -0
  105. package/dist/ui/campaigns/glyphs.js.map +1 -0
  106. package/dist/ui/campaigns/index.d.ts +4 -0
  107. package/dist/ui/campaigns/index.d.ts.map +1 -0
  108. package/dist/ui/campaigns/index.js +3 -0
  109. package/dist/ui/campaigns/index.js.map +1 -0
  110. package/dist/ui/campaigns/labels.d.ts +53 -0
  111. package/dist/ui/campaigns/labels.d.ts.map +1 -0
  112. package/dist/ui/campaigns/labels.js +169 -0
  113. package/dist/ui/campaigns/labels.js.map +1 -0
  114. package/dist/ui/campaigns/model.d.ts +99 -0
  115. package/dist/ui/campaigns/model.d.ts.map +1 -0
  116. package/dist/ui/campaigns/model.js +230 -0
  117. package/dist/ui/campaigns/model.js.map +1 -0
  118. package/dist/ui/campaigns/modes.d.ts +18 -0
  119. package/dist/ui/campaigns/modes.d.ts.map +1 -0
  120. package/dist/ui/campaigns/modes.js +7 -0
  121. package/dist/ui/campaigns/modes.js.map +1 -0
  122. package/dist/ui/index.d.ts +21 -0
  123. package/dist/ui/index.d.ts.map +1 -0
  124. package/dist/ui/index.js +19 -0
  125. package/dist/ui/index.js.map +1 -0
  126. package/dist/ui/integrations/AccountPicker.d.ts +26 -0
  127. package/dist/ui/integrations/AccountPicker.d.ts.map +1 -0
  128. package/dist/ui/integrations/AccountPicker.js +97 -0
  129. package/dist/ui/integrations/AccountPicker.js.map +1 -0
  130. package/dist/ui/integrations/ClientCards.d.ts +17 -0
  131. package/dist/ui/integrations/ClientCards.d.ts.map +1 -0
  132. package/dist/ui/integrations/ClientCards.js +38 -0
  133. package/dist/ui/integrations/ClientCards.js.map +1 -0
  134. package/dist/ui/integrations/ClientsTable.d.ts +36 -0
  135. package/dist/ui/integrations/ClientsTable.d.ts.map +1 -0
  136. package/dist/ui/integrations/ClientsTable.js +127 -0
  137. package/dist/ui/integrations/ClientsTable.js.map +1 -0
  138. package/dist/ui/integrations/Connect.d.ts +29 -0
  139. package/dist/ui/integrations/Connect.d.ts.map +1 -0
  140. package/dist/ui/integrations/Connect.js +49 -0
  141. package/dist/ui/integrations/Connect.js.map +1 -0
  142. package/dist/ui/integrations/Connection.d.ts +37 -0
  143. package/dist/ui/integrations/Connection.d.ts.map +1 -0
  144. package/dist/ui/integrations/Connection.js +94 -0
  145. package/dist/ui/integrations/Connection.js.map +1 -0
  146. package/dist/ui/integrations/Dialogs.d.ts +18 -0
  147. package/dist/ui/integrations/Dialogs.d.ts.map +1 -0
  148. package/dist/ui/integrations/Dialogs.js +25 -0
  149. package/dist/ui/integrations/Dialogs.js.map +1 -0
  150. package/dist/ui/integrations/EventsFeed.d.ts +15 -0
  151. package/dist/ui/integrations/EventsFeed.d.ts.map +1 -0
  152. package/dist/ui/integrations/EventsFeed.js +57 -0
  153. package/dist/ui/integrations/EventsFeed.js.map +1 -0
  154. package/dist/ui/integrations/EventsSheet.d.ts +28 -0
  155. package/dist/ui/integrations/EventsSheet.d.ts.map +1 -0
  156. package/dist/ui/integrations/EventsSheet.js +121 -0
  157. package/dist/ui/integrations/EventsSheet.js.map +1 -0
  158. package/dist/ui/integrations/Icons.d.ts +36 -0
  159. package/dist/ui/integrations/Icons.d.ts.map +1 -0
  160. package/dist/ui/integrations/Icons.js +38 -0
  161. package/dist/ui/integrations/Icons.js.map +1 -0
  162. package/dist/ui/integrations/IntegrationsView.d.ts +14 -0
  163. package/dist/ui/integrations/IntegrationsView.d.ts.map +1 -0
  164. package/dist/ui/integrations/IntegrationsView.js +113 -0
  165. package/dist/ui/integrations/IntegrationsView.js.map +1 -0
  166. package/dist/ui/integrations/Overlay.d.ts +18 -0
  167. package/dist/ui/integrations/Overlay.d.ts.map +1 -0
  168. package/dist/ui/integrations/Overlay.js +36 -0
  169. package/dist/ui/integrations/Overlay.js.map +1 -0
  170. package/dist/ui/integrations/RowMenu.d.ts +15 -0
  171. package/dist/ui/integrations/RowMenu.d.ts.map +1 -0
  172. package/dist/ui/integrations/RowMenu.js +47 -0
  173. package/dist/ui/integrations/RowMenu.js.map +1 -0
  174. package/dist/ui/integrations/UnlinkDialog.d.ts +18 -0
  175. package/dist/ui/integrations/UnlinkDialog.d.ts.map +1 -0
  176. package/dist/ui/integrations/UnlinkDialog.js +29 -0
  177. package/dist/ui/integrations/UnlinkDialog.js.map +1 -0
  178. package/dist/ui/integrations/format.d.ts +28 -0
  179. package/dist/ui/integrations/format.d.ts.map +1 -0
  180. package/dist/ui/integrations/format.js +101 -0
  181. package/dist/ui/integrations/format.js.map +1 -0
  182. package/dist/ui/integrations/index.d.ts +5 -0
  183. package/dist/ui/integrations/index.d.ts.map +1 -0
  184. package/dist/ui/integrations/index.js +3 -0
  185. package/dist/ui/integrations/index.js.map +1 -0
  186. package/dist/ui/integrations/model.d.ts +84 -0
  187. package/dist/ui/integrations/model.d.ts.map +1 -0
  188. package/dist/ui/integrations/model.js +131 -0
  189. package/dist/ui/integrations/model.js.map +1 -0
  190. package/dist/ui/integrations/overlay.d.ts +10 -0
  191. package/dist/ui/integrations/overlay.d.ts.map +1 -0
  192. package/dist/ui/integrations/overlay.js +77 -0
  193. package/dist/ui/integrations/overlay.js.map +1 -0
  194. package/dist/ui/integrations/parts.d.ts +36 -0
  195. package/dist/ui/integrations/parts.d.ts.map +1 -0
  196. package/dist/ui/integrations/parts.js +85 -0
  197. package/dist/ui/integrations/parts.js.map +1 -0
  198. package/dist/ui/integrations/types.d.ts +96 -0
  199. package/dist/ui/integrations/types.d.ts.map +1 -0
  200. package/dist/ui/integrations/types.js +2 -0
  201. package/dist/ui/integrations/types.js.map +1 -0
  202. package/dist/ui/metrics.d.ts +106 -0
  203. package/dist/ui/metrics.d.ts.map +1 -0
  204. package/dist/ui/metrics.js +166 -0
  205. package/dist/ui/metrics.js.map +1 -0
  206. package/dist/ui/portfolio/PortfolioView.d.ts +26 -0
  207. package/dist/ui/portfolio/PortfolioView.d.ts.map +1 -0
  208. package/dist/ui/portfolio/PortfolioView.js +216 -0
  209. package/dist/ui/portfolio/PortfolioView.js.map +1 -0
  210. package/dist/ui/portfolio/SpendChart.d.ts +10 -0
  211. package/dist/ui/portfolio/SpendChart.d.ts.map +1 -0
  212. package/dist/ui/portfolio/SpendChart.js +196 -0
  213. package/dist/ui/portfolio/SpendChart.js.map +1 -0
  214. package/dist/ui/portfolio/alerts.d.ts +74 -0
  215. package/dist/ui/portfolio/alerts.d.ts.map +1 -0
  216. package/dist/ui/portfolio/alerts.js +291 -0
  217. package/dist/ui/portfolio/alerts.js.map +1 -0
  218. package/dist/ui/portfolio/figures.d.ts +79 -0
  219. package/dist/ui/portfolio/figures.d.ts.map +1 -0
  220. package/dist/ui/portfolio/figures.js +103 -0
  221. package/dist/ui/portfolio/figures.js.map +1 -0
  222. package/dist/ui/portfolio/format.d.ts +8 -0
  223. package/dist/ui/portfolio/format.d.ts.map +1 -0
  224. package/dist/ui/portfolio/format.js +28 -0
  225. package/dist/ui/portfolio/format.js.map +1 -0
  226. package/dist/ui/sample.d.ts +474 -0
  227. package/dist/ui/sample.d.ts.map +1 -0
  228. package/dist/ui/sample.js +553 -0
  229. package/dist/ui/sample.js.map +1 -0
  230. package/dist/ui/seed.d.ts +188 -0
  231. package/dist/ui/seed.d.ts.map +1 -0
  232. package/dist/ui/seed.js +563 -0
  233. package/dist/ui/seed.js.map +1 -0
  234. package/dist/ui/ui.css +3356 -0
  235. package/docs/federation.md +46 -1
  236. package/docs/meta-ads.md +746 -0
  237. package/docs/upgrading.md +95 -0
  238. package/llms.txt +3 -0
  239. package/package.json +15 -3
  240. package/src/component/_generated/api.ts +14 -0
  241. package/src/component/_generated/component.ts +323 -1
  242. package/src/component/_generated/server.ts +4 -0
  243. package/src/component/constants.ts +10 -0
  244. package/src/component/convex.config.ts +9 -0
  245. package/src/component/errors.ts +5 -1
  246. package/src/component/leads.ts +517 -17
  247. package/src/component/meta.ts +1461 -0
  248. package/src/component/metaCapi.ts +921 -0
  249. package/src/component/metaFake.ts +277 -0
  250. package/src/component/metaGraph.ts +722 -0
  251. package/src/component/metaSync.ts +1164 -0
  252. package/src/component/pipeline.ts +392 -0
  253. package/src/component/schema.ts +197 -1
  254. package/src/component/secrets.ts +98 -0
  255. package/src/component/validators.ts +376 -3
  256. package/src/tracker/generated.ts +1 -1
  257. 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
+ }
@@ -437,6 +437,9 @@ export const leadFieldsValidator = v.object({
437
437
  stageAt: v.number(),
438
438
  qualifiedAt: v.optional(v.number()),
439
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()),
440
443
  updatedAt: v.number(),
441
444
  });
442
445
 
@@ -449,7 +452,130 @@ export const adCatalogFieldsValidator = v.object({
449
452
  campaignId: v.optional(v.string()),
450
453
  campaignName: v.optional(v.string()),
451
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")),
468
+ updatedAt: v.number(),
469
+ });
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()),
452
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()),
453
579
  });
454
580
 
455
581
  export const ingestResultValidator = v.object({
@@ -488,20 +614,40 @@ const leadCountersFields = {
488
614
  revenueCents: v.number(),
489
615
  };
490
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
+
491
629
  export const leadAdRowValidator = v.object({
492
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()),
493
633
  adName: v.union(v.string(), v.null()),
494
634
  sourceApp: v.union(v.string(), v.null()),
495
635
  sourceUrl: v.union(v.string(), v.null()),
496
636
  title: v.union(v.string(), v.null()),
497
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())),
498
642
  ...leadCountersFields,
643
+ ...leadSpendFields,
499
644
  });
500
645
 
501
646
  export const leadAdsetRowValidator = v.object({
502
647
  adsetId: v.union(v.string(), v.null()),
503
648
  adsetName: v.union(v.string(), v.null()),
504
649
  ...leadCountersFields,
650
+ ...leadSpendFields,
505
651
  ads: v.array(leadAdRowValidator),
506
652
  });
507
653
 
@@ -509,21 +655,162 @@ export const leadCampaignRowValidator = v.object({
509
655
  campaignId: v.union(v.string(), v.null()),
510
656
  campaignName: v.union(v.string(), v.null()),
511
657
  ...leadCountersFields,
658
+ ...leadSpendFields,
512
659
  adsets: v.array(leadAdsetRowValidator),
513
660
  });
514
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
+
515
681
  export const leadsReportValidator = v.object({
516
682
  from: v.number(),
517
683
  to: v.number(),
518
684
  /** The sites' currency, or null when they differ (revenue then mixes). */
519
685
  currency: v.union(v.string(), v.null()),
520
- totals: v.object(leadCountersFields),
521
- 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
+ ),
522
690
  /**
523
691
  * Campaign → ad set → ad, with ads the catalog does not name yet grouped
524
- * 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.
525
694
  */
526
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()),
527
814
  });
528
815
 
529
816
  export const leadSummaryValidator = v.object({
@@ -569,3 +856,89 @@ export const leadJourneyValidator = v.union(
569
856
  ),
570
857
  }),
571
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.9.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 = "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";
5
5
  export const TRACKER_RAW_BYTES = 2999;