@sonordev/site-kit 8.8.0 → 8.9.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/CHANGELOG.md +22 -0
- package/README.md +9 -0
- package/agent-manifest.json +8 -1
- package/dist/AnalyticsProvider-X7JJN3QL.js +19 -0
- package/dist/{ArticleViewTracker-PV2RD3P6.js → ArticleViewTracker-7Q6AMCKX.js} +11 -10
- package/dist/{BlocksPopup-NEEFV2L3.js → BlocksPopup-U5VIYEBA.js} +4 -5
- package/dist/ChatWidget-EAATOFCT.js +30 -0
- package/dist/{FileField-MJ3RCNFJ.js → FileField-YWMSK7RF.js} +1 -3
- package/dist/{FormSpotlight-NQPQFZS7.js → FormSpotlight-MAXDW3TK.js} +1 -1
- package/dist/{FormStage-4T5PJR5K.js → FormStage-VAHRVOIZ.js} +1 -1
- package/dist/ManagedForm-7IZT7ZII.js +29 -0
- package/dist/{ManagedNewsletterForm-TUYGN6O3.js → ManagedNewsletterForm-VVXZSEAL.js} +21 -11
- package/dist/{SignalCore-OW3YIH6M.js → SignalCore-HAN4O7QG.js} +90 -36
- package/dist/{SiteChat-XMKHBSEK.js → SiteChat-3JLCEL2N.js} +2 -2
- package/dist/{SiteDesignReporter-BRH7ORQS.js → SiteDesignReporter-6CCO645E.js} +5 -6
- package/dist/SitePopups-ZGHQNGGH.js +19 -0
- package/dist/SitemapSync-5LSYCBKS.js +7 -0
- package/dist/SitemapSync.d.ts +1 -1
- package/dist/_client/booking-widget.d.ts +1 -1
- package/dist/_client/booking-widget.js +6 -7
- package/dist/_client/reviewed-signal-experiment.d.ts +9 -0
- package/dist/_client/reviewed-signal-experiment.js +89 -0
- package/dist/_client/testimonial-section.d.ts +1 -1
- package/dist/admin-auth/index.d.ts +3 -3
- package/dist/affiliates/AffiliatesWidget.d.ts +1 -1
- package/dist/affiliates/api.d.ts +1 -1
- package/dist/affiliates/index.d.ts +6 -6
- package/dist/affiliates/index.js +1 -3
- package/dist/affiliates/useAffiliates.d.ts +1 -1
- package/dist/analytics/AnalyticsProvider.d.ts +1 -1
- package/dist/analytics/dispatch.d.ts +1 -1
- package/dist/analytics/index.d.ts +6 -6
- package/dist/analytics/index.js +22 -12
- package/dist/analytics/publication-cta.d.ts +1 -1
- package/dist/analytics/send-gate.d.ts +8 -4
- package/dist/articles/Article.d.ts +1 -1
- package/dist/articles/ArticleFAQ.d.ts +1 -1
- package/dist/articles/ArticleFaqSection.d.ts +1 -1
- package/dist/articles/ArticleList.d.ts +1 -1
- package/dist/articles/ArticleViewTracker.d.ts +1 -1
- package/dist/articles/AuthorCard.d.ts +1 -1
- package/dist/articles/AuthorPage.d.ts +1 -1
- package/dist/articles/ClusterLandingPage.d.ts +1 -1
- package/dist/articles/ClusterNavigation.d.ts +1 -1
- package/dist/articles/ManagedNewsletterForm.d.ts +2 -2
- package/dist/articles/NewsletterShell.d.ts +1 -1
- package/dist/articles/NewsletterWidget.d.ts +1 -1
- package/dist/articles/PublicationLayout.d.ts +1 -1
- package/dist/articles/PublicationSidebar.d.ts +1 -1
- package/dist/articles/RelatedPosts.d.ts +1 -1
- package/dist/articles/ServiceCallout.d.ts +1 -1
- package/dist/articles/TableOfContents.d.ts +1 -1
- package/dist/articles/article-photos.d.ts +1 -1
- package/dist/articles/article-styles.d.ts +1 -1
- package/dist/articles/article-tables.d.ts +1 -1
- package/dist/articles/artwork.d.ts +1 -1
- package/dist/articles/author-schema.d.ts +1 -1
- package/dist/articles/author-social.d.ts +1 -1
- package/dist/articles/excerpt.d.ts +1 -1
- package/dist/articles/index.d.ts +1 -1
- package/dist/articles/index.js +3 -3
- package/dist/articles/news-sitemap.d.ts +1 -1
- package/dist/articles/processArticleHtml.d.ts +1 -1
- package/dist/articles/public-reads.d.ts +1 -1
- package/dist/articles/reading-time.d.ts +1 -1
- package/dist/articles/routes.d.ts +1 -1
- package/dist/articles/server-core.d.ts +1 -1
- package/dist/articles/server-ui.d.ts +1 -1
- package/dist/articles/server-ui.js +6 -6
- package/dist/articles/server.d.ts +1 -1
- package/dist/articles/server.js +5 -5
- package/dist/articles/types.d.ts +1 -1
- package/dist/articles/widget-styles.d.ts +1 -1
- package/dist/brand/extract-brand-profile.d.ts +1 -1
- package/dist/chat/ChatWidget.d.ts +1 -1
- package/dist/chat/EchoUiActions.d.ts +1 -1
- package/dist/chat/SiteChat.d.ts +2 -2
- package/dist/chat/chat-messages.d.ts +1 -1
- package/dist/chat/index.d.ts +7 -7
- package/dist/chat/index.js +17 -9
- package/dist/chat/launcher-placement.d.ts +1 -1
- package/dist/{chunk-OPUY4RS6.js → chunk-2LBKIE2K.js} +2 -1
- package/dist/{chunk-XIDWNXZN.js → chunk-3W3MN6Z7.js} +2 -2
- package/dist/{chunk-UPFU63L4.js → chunk-55GFMOI6.js} +1 -1
- package/dist/chunk-5IVD6G77.js +37 -0
- package/dist/{chunk-URLTBVRX.js → chunk-6EDE7BOE.js} +3 -3
- package/dist/{chunk-DYROJGI2.js → chunk-6NLVHTYY.js} +1 -1
- package/dist/{chunk-ZQSCMYZO.js → chunk-7643MCD2.js} +6 -3
- package/dist/chunk-7IZUUXXO.js +1 -0
- package/dist/{chunk-C7TLGL3R.js → chunk-7VMA3KCN.js} +30 -4
- package/dist/{chunk-OWZWRP7Y.js → chunk-ADQ3VCKK.js} +2 -2
- package/dist/chunk-AG2RDHYZ.js +45 -0
- package/dist/chunk-AWLPFFPO.js +36 -0
- package/dist/{chunk-LNSNA3W4.js → chunk-C4XK472G.js} +4 -3
- package/dist/{chunk-TP32MRXV.js → chunk-CJ43K7RA.js} +2 -2
- package/dist/chunk-D4M5K6JN.js +25 -0
- package/dist/{chunk-F6WLHVMQ.js → chunk-DXE6SXWW.js} +1 -1
- package/dist/{chunk-YEX5RAEU.js → chunk-EBEM5BI5.js} +38 -27
- package/dist/chunk-F3RKFGRW.js +2 -0
- package/dist/chunk-FAY6MOLL.js +62 -0
- package/dist/chunk-FRY6PBT6.js +31 -0
- package/dist/{chunk-JYITKUUB.js → chunk-G5WUX7VE.js} +1 -1
- package/dist/chunk-HJD6KXKH.js +147 -0
- package/dist/{chunk-6BFQI3ZE.js → chunk-IO6BWRZ5.js} +2 -2
- package/dist/{chunk-THXELZTQ.js → chunk-ISMHDUBW.js} +1 -1
- package/dist/{chunk-SLFYTELD.js → chunk-J4LJE4NB.js} +1 -1
- package/dist/{chunk-LQJC5S2M.js → chunk-J6D3NEDE.js} +57 -0
- package/dist/chunk-JT7GHHMV.js +56 -0
- package/dist/{chunk-NFGAXWCN.js → chunk-KGIQYVQ2.js} +31 -13
- package/dist/chunk-KMNHNCVG.js +20 -0
- package/dist/chunk-KQKUXIUD.js +25 -0
- package/dist/{chunk-M5JIHQ42.js → chunk-L3S5WH4C.js} +2 -2
- package/dist/chunk-MDBXGN6W.js +13 -0
- package/dist/chunk-MOACHHOJ.js +20 -0
- package/dist/{chunk-6DXQHNZK.js → chunk-MP35JETP.js} +1 -1
- package/dist/chunk-NHDRA4Y5.js +6 -0
- package/dist/{chunk-AYQEIMGS.js → chunk-NZHECYLF.js} +103 -22
- package/dist/{chunk-EATJPSTS.js → chunk-O5LQUBBN.js} +1 -1
- package/dist/{chunk-Y6SHFILE.js → chunk-OEOSR5EG.js} +1 -1
- package/dist/{chunk-GQN4BGK6.js → chunk-OUD4RTBD.js} +1 -1
- package/dist/chunk-PMXQPO5Q.js +78 -0
- package/dist/chunk-PVY7X5UR.js +21 -0
- package/dist/{chunk-KG5IEV7I.js → chunk-QLJ2SC3T.js} +1 -1
- package/dist/{chunk-2WM4TNDF.js → chunk-QXAJPB6O.js} +1 -1
- package/dist/{chunk-2WDWQI7K.js → chunk-RYR6N4X4.js} +1 -1
- package/dist/{chunk-G7BRCSV6.js → chunk-S33CUULL.js} +3 -3
- package/dist/{chunk-YRQS7VAH.js → chunk-S63QY5XW.js} +2 -2
- package/dist/{chunk-GXWM5KNS.js → chunk-SO6RSRUP.js} +40 -45
- package/dist/{chunk-ODOV76VW.js → chunk-STIVFCWM.js} +1 -1
- package/dist/{chunk-SVHPILS3.js → chunk-T4A5J6SJ.js} +1 -1
- package/dist/chunk-TPHOIE4I.js +73 -0
- package/dist/{chunk-IWNJCRXW.js → chunk-UG53ZW7S.js} +125 -118
- package/dist/chunk-VXCUXHZO.js +9 -0
- package/dist/{chunk-SBPUFTMP.js → chunk-WHFWDK2H.js} +2 -2
- package/dist/chunk-WYNDBJG7.js +66 -0
- package/dist/{chunk-HN7GVGWC.js → chunk-XDH5B3PR.js} +3 -3
- package/dist/{chunk-PAYCK7FC.js → chunk-XJZLXUTG.js} +1 -1
- package/dist/{chunk-IDC5CVBR.js → chunk-YVEYO3OD.js} +1 -1
- package/dist/{chunk-PSS6M4CG.js → chunk-ZRSXCT4O.js} +2 -2
- package/dist/client/index.d.ts +8 -8
- package/dist/client/index.js +9 -8
- package/dist/config/index.d.ts +1 -1
- package/dist/contracts/broadcast-layouts.d.ts +8 -0
- package/dist/contracts/crm-disposition.d.ts +87 -0
- package/dist/contracts/decision-roles.d.ts +39 -0
- package/dist/contracts/entries.d.ts +1 -1
- package/dist/contracts/funnels.d.ts +85 -2
- package/dist/contracts/portfolio.d.ts +1 -1
- package/dist/contracts/publication-branding.d.ts +3 -3
- package/dist/contracts/resonance-constants.d.ts +4 -0
- package/dist/contracts/resonance.d.ts +8461 -0
- package/dist/contracts/signal-experiments.d.ts +219 -0
- package/dist/contracts/site-edit.d.ts +2 -2
- package/dist/contracts/sites.d.ts +1 -1
- package/dist/contracts/slots.d.ts +2 -2
- package/dist/contracts/wire-schema.d.ts +36 -0
- package/dist/cta-bar/CtaBar.d.ts +1 -1
- package/dist/cta-bar/client.js +3 -2
- package/dist/cta-bar/index.d.ts +4 -4
- package/dist/cta-bar/index.js +4 -3
- package/dist/fleet/FleetHeartbeat.d.ts +2 -2
- package/dist/fleet/contract.d.ts +1 -1
- package/dist/fleet/heartbeat.d.ts +2 -2
- package/dist/fleet/index.d.ts +6 -6
- package/dist/fleet/index.js +4 -5
- package/dist/forms/FileField.d.ts +1 -1
- package/dist/forms/FormClient.d.ts +2 -2
- package/dist/forms/FormEnhancer.d.ts +1 -1
- package/dist/forms/FormField.d.ts +1 -1
- package/dist/forms/FormSpotlight.d.ts +1 -1
- package/dist/forms/FormStage.d.ts +1 -1
- package/dist/forms/ManagedForm.d.ts +1 -1
- package/dist/forms/PrivateFileField.d.ts +1 -1
- package/dist/forms/ServerForm.d.ts +3 -3
- package/dist/forms/StaticForm.d.ts +1 -1
- package/dist/forms/contract.d.ts +1 -1
- package/dist/forms/experience-plan.d.ts +1 -1
- package/dist/forms/field-autocomplete.d.ts +1 -1
- package/dist/forms/field-controls.d.ts +1 -1
- package/dist/forms/field-rules.d.ts +1 -1
- package/dist/forms/form-dom-values.d.ts +1 -1
- package/dist/forms/index.d.ts +18 -18
- package/dist/forms/index.js +21 -11
- package/dist/forms/normalize-config.d.ts +1 -1
- package/dist/forms/server.d.ts +4 -4
- package/dist/forms/server.js +3 -3
- package/dist/forms/static.d.ts +2 -2
- package/dist/forms/submitForm.d.ts +2 -2
- package/dist/forms/tracking-session.d.ts +1 -1
- package/dist/forms/useForm.d.ts +2 -2
- package/dist/forms/webmcp.d.ts +2 -2
- package/dist/images/api.d.ts +1 -1
- package/dist/images/index.d.ts +6 -6
- package/dist/images/index.js +2 -4
- package/dist/images/server.d.ts +2 -2
- package/dist/index.d.ts +13 -13
- package/dist/index.js +1 -1
- package/dist/landing/contract.d.ts +1 -1
- package/dist/landing/contract.js +2 -1
- package/dist/landing/index.d.ts +3 -3
- package/dist/landing/index.js +3 -2
- package/dist/landing/managed-server.d.ts +2 -2
- package/dist/landing/managed.d.ts +10 -2
- package/dist/landing/server.d.ts +7 -7
- package/dist/landing/server.js +11 -6
- package/dist/layout/AiDiscoveryLinks.d.ts +1 -1
- package/dist/layout/SiteKitClientProviders.d.ts +1 -1
- package/dist/layout/SiteKitLayout.d.ts +2 -2
- package/dist/layout/client.d.ts +1 -1
- package/dist/layout/client.js +15 -11
- package/dist/layout/index.d.ts +5 -5
- package/dist/layout/index.js +17 -13
- package/dist/layout/types.d.ts +2 -2
- package/dist/llms/AEOComponents.d.ts +1 -1
- package/dist/llms/SpeakableSchema.d.ts +1 -1
- package/dist/llms/api.d.ts +1 -1
- package/dist/llms/client.d.ts +3 -3
- package/dist/llms/contract.d.ts +1 -1
- package/dist/llms/generateLLMsTxt.d.ts +1 -1
- package/dist/llms/handlers.d.ts +1 -1
- package/dist/llms/index.d.ts +21 -21
- package/dist/llms/index.js +5 -5
- package/dist/llms/merge.d.ts +1 -1
- package/dist/llms/sourcedFacts.d.ts +1 -1
- package/dist/llms/types.d.ts +5 -5
- package/dist/llms/writeLLMsTxt.d.ts +3 -3
- package/dist/maps/GoogleMap.d.ts +1 -1
- package/dist/maps/NeighborhoodMap.d.ts +1 -1
- package/dist/maps/api.d.ts +1 -1
- package/dist/maps/index.d.ts +5 -5
- package/dist/maps/index.js +1 -3
- package/dist/mcp/WebMcpTools.d.ts +1 -1
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/discovery.d.ts +2 -2
- package/dist/mcp/handlers.d.ts +4 -4
- package/dist/mcp/index.d.ts +12 -12
- package/dist/mcp/protocol.d.ts +1 -1
- package/dist/mcp/report.d.ts +1 -1
- package/dist/mcp/serverCard.d.ts +1 -1
- package/dist/mcp/sonor.d.ts +5 -5
- package/dist/mcp/sonor.js +10 -10
- package/dist/mcp/transport.d.ts +1 -1
- package/dist/motion/Parallax.d.ts +1 -1
- package/dist/motion/ScrollScene.d.ts +1 -1
- package/dist/motion/gsap.d.ts +2 -2
- package/dist/motion/index.d.ts +9 -9
- package/dist/motion/three.d.ts +1 -1
- package/dist/og/index.d.ts +5 -5
- package/dist/og/pages.d.ts +1 -1
- package/dist/og/route-fit.d.ts +17 -0
- package/dist/og/route.d.ts +8 -0
- package/dist/og/route.js +1 -1
- package/dist/og/template.d.ts +1 -1
- package/dist/portfolio/contract.d.ts +1 -1
- package/dist/press-kit/articles/Article.d.ts +2 -2
- package/dist/press-kit/articles/ArticleFaqSection.d.ts +1 -1
- package/dist/press-kit/articles/ArticleList.d.ts +2 -2
- package/dist/press-kit/articles/AuthorCard.d.ts +1 -1
- package/dist/press-kit/articles/AuthorPage.d.ts +1 -1
- package/dist/press-kit/articles/ClusterLandingPage.d.ts +2 -2
- package/dist/press-kit/articles/ClusterNavigation.d.ts +2 -2
- package/dist/press-kit/articles/ManagedNewsletterForm.d.ts +2 -2
- package/dist/press-kit/articles/PublicationLayout.d.ts +2 -2
- package/dist/press-kit/articles/PublicationSidebar.d.ts +1 -1
- package/dist/press-kit/articles/RelatedPosts.d.ts +2 -2
- package/dist/press-kit/articles/TableOfContents.d.ts +1 -1
- package/dist/press-kit/articles/article-body.d.ts +1 -1
- package/dist/press-kit/articles/article-headings.d.ts +1 -1
- package/dist/press-kit/articles/article-photos.d.ts +1 -1
- package/dist/press-kit/articles/artwork.d.ts +2 -2
- package/dist/press-kit/articles/author-schema.d.ts +2 -2
- package/dist/press-kit/articles/author-social.d.ts +1 -1
- package/dist/press-kit/articles/index.d.ts +23 -23
- package/dist/press-kit/articles/news-sitemap.d.ts +2 -2
- package/dist/press-kit/articles/public-reads.d.ts +1 -1
- package/dist/press-kit/articles/publication-reads.d.ts +2 -2
- package/dist/press-kit/articles/reading-time.d.ts +1 -1
- package/dist/press-kit/articles/server-core.d.ts +20 -20
- package/dist/press-kit/articles/server-ui.d.ts +20 -20
- package/dist/press-kit/articles/types.d.ts +1 -1
- package/dist/proxy/index.d.ts +5 -5
- package/dist/proxy/index.js +1 -1
- package/dist/reputation/TestimonialSection.d.ts +1 -1
- package/dist/reputation/VisualReviewCard.d.ts +1 -1
- package/dist/reputation/api.d.ts +1 -1
- package/dist/reputation/index.d.ts +4 -4
- package/dist/reputation/server.d.ts +3 -3
- package/dist/resonance/ResonanceRuntime.d.ts +7 -0
- package/dist/resonance/bridge.d.ts +16 -0
- package/dist/resonance/consent-adapter.d.ts +26 -0
- package/dist/resonance/controller-registry.d.ts +14 -0
- package/dist/resonance/controller.d.ts +103 -0
- package/dist/resonance/form-context.d.ts +5 -0
- package/dist/resonance/index.d.ts +4 -0
- package/dist/resonance/index.js +9 -0
- package/dist/resonance/meta-pixel.d.ts +67 -0
- package/dist/resonance/providers.d.ts +32 -0
- package/dist/resonance/runtime-core.d.ts +5 -0
- package/dist/revalidate/index.d.ts +3 -3
- package/dist/revalidate/index.js +2 -2
- package/dist/robots/index.d.ts +3 -3
- package/dist/robots/indexnow.js +2 -2
- package/dist/runtime/index.d.ts +7 -7
- package/dist/runtime/index.js +4 -5
- package/dist/runtime-core-WT7KKLWQ.js +2213 -0
- package/dist/seo/ManagedFAQ.d.ts +1 -1
- package/dist/seo/ManagedInternalLinks.d.ts +1 -1
- package/dist/seo/ManagedSchema.d.ts +1 -1
- package/dist/seo/ManagedScripts.d.ts +1 -1
- package/dist/seo/client.d.ts +1 -1
- package/dist/seo/client.js +3 -5
- package/dist/seo/entry-indexnow.d.ts +1 -1
- package/dist/seo/entry-llms-client.d.ts +1 -1
- package/dist/seo/entry-llms-contract.d.ts +1 -1
- package/dist/seo/entry-llms.d.ts +1 -1
- package/dist/seo/entry-meta-contract.d.ts +1 -1
- package/dist/seo/entry-og-route.d.ts +1 -1
- package/dist/seo/entry-og.d.ts +1 -1
- package/dist/seo/entry-pages-contract.d.ts +1 -1
- package/dist/seo/entry-redirects.d.ts +1 -1
- package/dist/seo/entry-robots.d.ts +1 -1
- package/dist/seo/entry-sitemap.d.ts +1 -1
- package/dist/seo/getManagedMetadata.d.ts +1 -1
- package/dist/seo/index.d.ts +12 -12
- package/dist/seo/index.js +9 -11
- package/dist/seo/indexnow.d.ts +1 -1
- package/dist/seo/indexnow.js +2 -2
- package/dist/seo/llms/client.d.ts +1 -1
- package/dist/seo/llms/contract.d.ts +1 -1
- package/dist/seo/llms.d.ts +1 -1
- package/dist/seo/llms.js +5 -5
- package/dist/seo/meta/contract.d.ts +1 -1
- package/dist/seo/og/route.d.ts +1 -1
- package/dist/seo/og/route.js +1 -1
- package/dist/seo/og.d.ts +1 -1
- package/dist/seo/pages/contract.d.ts +1 -1
- package/dist/seo/redirects.d.ts +1 -1
- package/dist/seo/register-sitemap-cli-impl.d.ts +1 -1
- package/dist/seo/register-sitemap-cli.js +1 -1
- package/dist/seo/robots.d.ts +1 -1
- package/dist/seo/routing.d.ts +1 -1
- package/dist/seo/server.d.ts +4 -4
- package/dist/seo/server.js +3 -3
- package/dist/seo/sitemap.d.ts +1 -1
- package/dist/seo/sitemap.js +3 -3
- package/dist/seo/types.d.ts +1 -1
- package/dist/seo-meta/contract.d.ts +1 -1
- package/dist/seo-pages/contract.d.ts +1 -1
- package/dist/server/index.d.ts +5 -5
- package/dist/server/index.js +3 -3
- package/dist/server/mint-site-token.d.ts +1 -1
- package/dist/server/server-fetch.d.ts +8 -0
- package/dist/{server-api-XIKDWSNO.js → server-api-A34TABT2.js} +2 -2
- package/dist/shared/build-entries.d.ts +3 -1
- package/dist/shared/color.d.ts +1 -1
- package/dist/shared/fetchWithRetry.d.ts +6 -0
- package/dist/shared/frame.d.ts +1 -1
- package/dist/shared/identity-storage.d.ts +6 -4
- package/dist/shared/identity.d.ts +1 -3
- package/dist/shared/index.d.ts +5 -5
- package/dist/shared/index.js +1 -1
- package/dist/shared/measurement-analytics.d.ts +1 -0
- package/dist/shared/measurement-policy.d.ts +6 -0
- package/dist/shared/measurement-proof.d.ts +5 -0
- package/dist/shared/measurement-store.d.ts +34 -0
- package/dist/shared/measurement-writer.d.ts +6 -0
- package/dist/shared/publishCredential.d.ts +1 -1
- package/dist/shared/reporting-gate.d.ts +3 -1
- package/dist/shared/site-token-claims.d.ts +18 -0
- package/dist/shared/site-token-configuration.d.ts +9 -0
- package/dist/shared/site-token-refresh.d.ts +7 -0
- package/dist/shared/site-token-state.d.ts +23 -0
- package/dist/shared/siteToken.d.ts +5 -39
- package/dist/shared/sonor-auth-state.d.ts +3 -0
- package/dist/shared/sonor-beacon.d.ts +13 -0
- package/dist/shared/sonor-fetch-runtime.d.ts +2 -0
- package/dist/shared/sonor-fetch.d.ts +7 -0
- package/dist/shared/sonorFetch.d.ts +4 -49
- package/dist/shared/version.d.ts +1 -1
- package/dist/signal/ReviewedSignalExperiment.d.ts +9 -0
- package/dist/signal/SignalBridge.d.ts +4 -4
- package/dist/signal/SignalCore.d.ts +2 -1
- package/dist/signal/SignalExperiment.d.ts +13 -5
- package/dist/signal/index.d.ts +3 -3
- package/dist/signal/index.js +4 -73
- package/dist/signal/store.d.ts +10 -7
- package/dist/signal/types.d.ts +4 -0
- package/dist/sitemap/index.d.ts +5 -5
- package/dist/sitemap/index.js +3 -3
- package/dist/sites/contract.d.ts +1 -1
- package/dist/sites/normalize.d.ts +1 -1
- package/dist/slots/ManagedLink.d.ts +2 -2
- package/dist/slots/ManagedList.d.ts +2 -2
- package/dist/slots/ManagedRichText.d.ts +1 -1
- package/dist/slots/ManagedSlot.d.ts +1 -1
- package/dist/slots/contract.d.ts +1 -1
- package/dist/slots/edit/locate.d.ts +1 -1
- package/dist/slots/index.d.ts +14 -14
- package/dist/slots/index.js +2 -2
- package/dist/slots/server-api.d.ts +1 -1
- package/dist/{chunk-FLOJUMFR.js → sonor-fetch-runtime-3PFGUXMF.js} +27 -36
- package/dist/sync/AddToCalendar.d.ts +1 -1
- package/dist/sync/BookingWidget.d.ts +1 -1
- package/dist/sync/MeetingFormatStep.d.ts +1 -1
- package/dist/sync/api.d.ts +2 -2
- package/dist/sync/booking-flow.d.ts +1 -1
- package/dist/sync/index.d.ts +3 -3
- package/dist/sync/index.js +6 -7
- package/dist/types.d.ts +6 -6
- package/dist/website/BlocksPopup.d.ts +1 -1
- package/dist/website/PopupBlocks.d.ts +1 -1
- package/dist/website/SiteDesignReporter.d.ts +2 -2
- package/dist/website/SitePopups.d.ts +1 -1
- package/dist/website/contract.d.ts +1 -1
- package/dist/website/cta-bar.d.ts +1 -1
- package/dist/website/cta-bar.js +4 -3
- package/dist/website/design.d.ts +2 -2
- package/dist/website/images/server.d.ts +1 -1
- package/dist/website/images-server.d.ts +1 -1
- package/dist/website/images.d.ts +1 -1
- package/dist/website/images.js +2 -4
- package/dist/website/index.d.ts +4 -4
- package/dist/website/index.js +5 -6
- package/dist/website/landing/server.d.ts +1 -1
- package/dist/website/landing/server.js +11 -6
- package/dist/website/landing-server.d.ts +1 -1
- package/dist/website/landing.d.ts +1 -1
- package/dist/website/landing.js +3 -2
- package/dist/website/popup-rules.d.ts +2 -2
- package/dist/website/popup-types.d.ts +1 -1
- package/dist/website/popups.d.ts +4 -4
- package/dist/website/popups.js +14 -8
- package/dist/website/slots/contract.d.ts +1 -1
- package/dist/website/slots-contract.d.ts +1 -1
- package/dist/website/slots.d.ts +2 -2
- package/dist/website/slots.js +2 -2
- package/dist/{writeLLMsTxt-4HHUAOA6.js → writeLLMsTxt-JUACXQKP.js} +2 -2
- package/docs.json +5 -0
- package/package.json +8 -2
- package/src/analytics/README.md +21 -0
- package/src/cta-bar/README.md +4 -0
- package/src/forms/README.md +29 -0
- package/src/landing/README.md +14 -0
- package/src/layout/README.md +3 -0
- package/src/og/README.md +28 -0
- package/src/resonance/README.md +273 -0
- package/src/runtime/README.md +6 -1
- package/src/signal/README.md +21 -0
- package/src/website/README.md +14 -2
- package/dist/AnalyticsProvider-7SBJ27SI.js +0 -13
- package/dist/ChatWidget-V46UBDOM.js +0 -20
- package/dist/ManagedForm-DDDVMKLJ.js +0 -19
- package/dist/SitePopups-VU2C3Z6B.js +0 -13
- package/dist/SitemapSync-C4H3HF3L.js +0 -9
- package/dist/chunk-5ROTTMBL.js +0 -19
- package/dist/chunk-5TM3WPCI.js +0 -7
- package/dist/chunk-7HW4TZB5.js +0 -148
- package/dist/chunk-IPQKQEWI.js +0 -52
- package/dist/chunk-LCDVPAMR.js +0 -24
- package/dist/chunk-SHAUX2MQ.js +0 -163
- /package/dist/{chunk-3MJNBOUO.js → chunk-2QPVU63T.js} +0 -0
|
@@ -0,0 +1,2213 @@
|
|
|
1
|
+
import { updateMeasurementPolicy } from './chunk-KQKUXIUD.js';
|
|
2
|
+
import { registerResonanceController, subscribeConsentAdapter, setProviderBlockers, getConsentAdapter } from './chunk-WYNDBJG7.js';
|
|
3
|
+
import { isBlockedPublicUrlHost } from './chunk-XS2ORMUG.js';
|
|
4
|
+
import { normalizeCampaignSlug } from './chunk-2QPVU63T.js';
|
|
5
|
+
import './chunk-MDBXGN6W.js';
|
|
6
|
+
import { RESONANCE_ATTRIBUTION_MODEL, RESONANCE_SOURCE_MARKER_PARAMETER, RESONANCE_SUBJECT_HEADER } from './chunk-NHDRA4Y5.js';
|
|
7
|
+
import { uuid } from './chunk-GJWI74ZZ.js';
|
|
8
|
+
import { isNormalizedSiteHost } from './chunk-NZOJHERI.js';
|
|
9
|
+
import { advertisingPurposesPermitted, experimentMeasurementPermitted } from './chunk-KMNHNCVG.js';
|
|
10
|
+
import { resolveAnalyticsTarget, isFramed, isPrivatePreview, isLocalHostname } from './chunk-7643MCD2.js';
|
|
11
|
+
import './chunk-64XODP7M.js';
|
|
12
|
+
import { sonorFetch } from './chunk-VXCUXHZO.js';
|
|
13
|
+
import './chunk-W7OPEMGJ.js';
|
|
14
|
+
import './chunk-A4CC4ZG3.js';
|
|
15
|
+
import './chunk-MOACHHOJ.js';
|
|
16
|
+
import { measurementPolicySnapshot, subscribeMeasurementPolicy, unknownChoices, MEASUREMENT_PURPOSES } from './chunk-FRY6PBT6.js';
|
|
17
|
+
import './chunk-PKBMQBKP.js';
|
|
18
|
+
|
|
19
|
+
// src/contracts/wire-schema.ts
|
|
20
|
+
function createWireSchema(fail3, isValidationError) {
|
|
21
|
+
const own = (v, k) => Object.prototype.hasOwnProperty.call(v, k);
|
|
22
|
+
const field3 = (schema, read) => ({ schema, read });
|
|
23
|
+
const text3 = (max = 200, pattern, min = 1) => field3({ type: "string", minLength: min, maxLength: max, ...pattern ? { pattern: pattern.source } : {} }, (v, p) => typeof v === "string" && v.length >= min && v.length <= max && (!pattern || pattern.test(v)) ? v : fail3(p, "invalid string"));
|
|
24
|
+
const choice3 = (...values) => field3({ type: "string", enum: values }, (v, p) => typeof v === "string" && values.includes(v) ? v : fail3(p, "unsupported value"));
|
|
25
|
+
const literal3 = (value) => field3({ const: value }, (v, p) => v === value ? value : fail3(p, "unexpected value"));
|
|
26
|
+
const integer3 = (min = 0, max = Number.MAX_SAFE_INTEGER) => field3({ type: "integer", minimum: min, maximum: max }, (v, p) => typeof v === "number" && Number.isSafeInteger(v) && v >= min && v <= max ? v : fail3(p, "invalid integer"));
|
|
27
|
+
const bool2 = field3({ type: "boolean" }, (v, p) => typeof v === "boolean" ? v : fail3(p, "must be boolean"));
|
|
28
|
+
const optional3 = (f) => ({ ...f, optional: true });
|
|
29
|
+
const nullable3 = (f) => field3({ anyOf: [f.schema, { type: "null" }] }, (v, p) => v === null ? null : f.read(v, p));
|
|
30
|
+
const list3 = (f, max = 100, min = 0) => field3({ type: "array", items: f.schema, minItems: min, maxItems: max }, (v, p) => {
|
|
31
|
+
if (!Array.isArray(v) || v.length < min || v.length > max) fail3(p, "invalid array");
|
|
32
|
+
return v.map((x, i) => f.read(x, `${p}[${i}]`));
|
|
33
|
+
});
|
|
34
|
+
const object3 = (fields2) => field3({ type: "object", additionalProperties: false, properties: Object.fromEntries(Object.entries(fields2).map(([k, f]) => [k, f.schema])), required: Object.keys(fields2).filter((k) => !("optional" in fields2[k])) }, (v, p) => {
|
|
35
|
+
if (!v || typeof v !== "object" || Array.isArray(v) || ![Object.prototype, null].includes(Object.getPrototypeOf(v))) fail3(p, "must be a plain object");
|
|
36
|
+
const source2 = v, result = {};
|
|
37
|
+
for (const key of Reflect.ownKeys(source2)) if (typeof key !== "string" || !own(fields2, key)) fail3(p, "unknown field");
|
|
38
|
+
for (const [key, f] of Object.entries(fields2)) {
|
|
39
|
+
if (!own(source2, key)) {
|
|
40
|
+
if ("optional" in f) continue;
|
|
41
|
+
fail3(`${p}.${key}`, "required");
|
|
42
|
+
}
|
|
43
|
+
const descriptor = Object.getOwnPropertyDescriptor(source2, key);
|
|
44
|
+
if (!own(descriptor, "value")) fail3(`${p}.${key}`, "accessors are not wire data");
|
|
45
|
+
result[key] = f.read(descriptor.value, `${p}.${key}`);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
49
|
+
const union3 = (...fields2) => field3({ oneOf: fields2.map((f) => f.schema) }, (v, p) => {
|
|
50
|
+
const found = [];
|
|
51
|
+
for (const f of fields2) {
|
|
52
|
+
try {
|
|
53
|
+
found.push(f.read(v, p));
|
|
54
|
+
} catch (e) {
|
|
55
|
+
if (!isValidationError(e)) throw e;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return found.length === 1 ? found[0] : fail3(p, "must match exactly one supported shape");
|
|
59
|
+
});
|
|
60
|
+
const refine3 = (f, check) => field3(f.schema, (v, p) => {
|
|
61
|
+
const result = f.read(v, p);
|
|
62
|
+
check(result, p);
|
|
63
|
+
return result;
|
|
64
|
+
});
|
|
65
|
+
return { field: field3, text: text3, choice: choice3, literal: literal3, integer: integer3, bool: bool2, optional: optional3, nullable: nullable3, list: list3, object: object3, union: union3, refine: refine3 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/contracts/signal-experiments.ts
|
|
69
|
+
var SignalExperimentValidationError = class extends Error {
|
|
70
|
+
constructor(path2, reason) {
|
|
71
|
+
super(`${path2}: ${reason}`);
|
|
72
|
+
this.path = path2;
|
|
73
|
+
this.name = "SignalExperimentValidationError";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var fail = (path2, reason) => {
|
|
77
|
+
throw new SignalExperimentValidationError(path2, reason);
|
|
78
|
+
};
|
|
79
|
+
var { field, text, choice, literal, integer, optional, nullable, list, object, union, refine } = createWireSchema(fail, (error) => error instanceof SignalExperimentValidationError);
|
|
80
|
+
var percent = field({ type: "number", minimum: 0, maximum: 100 }, (v, p) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 100 ? v : fail(p, "must be a finite percentage from 0 to 100"));
|
|
81
|
+
var page = refine(text(2048, /^\/(?!\/)[^?#\s\\]*$/), (v, p) => {
|
|
82
|
+
const parsed = new URL(v, "https://example.com");
|
|
83
|
+
if (parsed.pathname !== v) fail(p, "must be a canonical page path");
|
|
84
|
+
});
|
|
85
|
+
var variant = object({ key: text(80), name: text(160), weight: percent, description: optional(text(1e3, void 0, 0)) });
|
|
86
|
+
var variants = refine(list(variant, 50, 2), (v, p) => {
|
|
87
|
+
if (new Set(v.map((x) => x.key)).size !== v.length) fail(p, "variant keys must be unique");
|
|
88
|
+
if (Math.abs(v.reduce((sum, x) => sum + x.weight, 0) - 100) > 0.01) fail(p, "variant weights must sum to 100");
|
|
89
|
+
});
|
|
90
|
+
var targeting = object({ pages: optional(list(page, 100)), segments: optional(list(text(160), 100)), devices: optional(list(choice("desktop", "mobile", "tablet"), 3)), geo: optional(list(text(160), 100)) });
|
|
91
|
+
var fields = { name: text(160), description: text(1e4, void 0, 0), variants, traffic_allocation: percent, targeting_rules: targeting };
|
|
92
|
+
var create = object({ ...fields, description: optional(fields.description), traffic_allocation: optional(fields.traffic_allocation), targeting_rules: optional(fields.targeting_rules) });
|
|
93
|
+
var update = object({ name: optional(fields.name), description: optional(fields.description), variants: optional(fields.variants), traffic_allocation: optional(fields.traffic_allocation), targeting_rules: optional(fields.targeting_rules) });
|
|
94
|
+
var parseSignalExperimentCreate = (v) => create.read(v, "experimentCreate");
|
|
95
|
+
var parseSignalExperimentUpdate = (v) => update.read(v, "experimentUpdate");
|
|
96
|
+
var SIGNAL_EXPERIMENT_CREATE_JSON_SCHEMA = create.schema;
|
|
97
|
+
var SIGNAL_EXPERIMENT_UPDATE_JSON_SCHEMA = update.schema;
|
|
98
|
+
var measurementUuid = text(36, /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
99
|
+
var measurementRevision = text(128, /^[a-zA-Z0-9._:-]+$/);
|
|
100
|
+
var measurementInstant = refine(text(30, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/), (v, p) => {
|
|
101
|
+
if (!Number.isFinite(Date.parse(v)) || new Date(v).toISOString().replace(".000Z", "Z") !== v.replace(".000Z", "Z")) fail(p, "invalid UTC instant");
|
|
102
|
+
});
|
|
103
|
+
var measurementSite = refine(text(253), (v, p) => {
|
|
104
|
+
if (!isNormalizedSiteHost(v) || isBlockedPublicUrlHost(v)) fail(p, "must be a normalized public site host");
|
|
105
|
+
});
|
|
106
|
+
var measurementCount = text(30, /^(?:0|[1-9][0-9]*)$/);
|
|
107
|
+
var measurementScope = object({
|
|
108
|
+
projectId: measurementUuid,
|
|
109
|
+
experimentId: measurementUuid,
|
|
110
|
+
site: measurementSite,
|
|
111
|
+
experimentRevision: integer(1),
|
|
112
|
+
goalRevision: measurementRevision,
|
|
113
|
+
campaignId: measurementUuid,
|
|
114
|
+
campaignVersionId: measurementUuid,
|
|
115
|
+
assignedFrom: measurementInstant,
|
|
116
|
+
assignedTo: measurementInstant
|
|
117
|
+
});
|
|
118
|
+
var assignmentCounts = {
|
|
119
|
+
variantKey: text(80),
|
|
120
|
+
assignedUnits: measurementCount,
|
|
121
|
+
exposureEvents: measurementCount,
|
|
122
|
+
exposedUnits: measurementCount,
|
|
123
|
+
matureAssignedUnits: measurementCount,
|
|
124
|
+
immatureAssignedUnits: measurementCount,
|
|
125
|
+
assignmentsWithoutExposure: measurementCount
|
|
126
|
+
};
|
|
127
|
+
var availableVariant = object({ ...assignmentCounts, acceptedFormEvents: measurementCount, acceptedFormUnits: measurementCount, matureAcceptedFormUnits: measurementCount });
|
|
128
|
+
var unsupportedVariant = object({ ...assignmentCounts, acceptedFormEvents: literal(null), acceptedFormUnits: literal(null), matureAcceptedFormUnits: literal(null) });
|
|
129
|
+
var businessMetrics = object({
|
|
130
|
+
uniqueQualifiedContacts: object({ state: literal("unavailable"), reasonCode: literal("original_qualified_lineage_not_bound") }),
|
|
131
|
+
wonValue: object({ state: literal("unavailable"), reasonCode: literal("original_deal_lineage_not_bound") }),
|
|
132
|
+
paidRevenue: object({ state: literal("unavailable"), reasonCode: literal("original_paid_source_lineage_not_bound") })
|
|
133
|
+
});
|
|
134
|
+
var goalCountingUnit = choice("unique_source", "unique_qualified_contact", "net_order");
|
|
135
|
+
var originalGoalFields = { sourceId: measurementUuid, sourceRevision: measurementRevision, countingUnit: goalCountingUnit };
|
|
136
|
+
var formSourceKind = choice("accepted_form", "qualified_lead");
|
|
137
|
+
var unsupportedSourceKind = choice("completed_booking", "net_paid_order", "approved_application");
|
|
138
|
+
var observedFields = {
|
|
139
|
+
schemaVersion: literal(1),
|
|
140
|
+
mode: literal("trusted_source"),
|
|
141
|
+
state: literal("observed"),
|
|
142
|
+
observedAt: measurementInstant,
|
|
143
|
+
scope: measurementScope,
|
|
144
|
+
countingUnit: literal("consent_subject"),
|
|
145
|
+
coverage: literal("assigned_population_only"),
|
|
146
|
+
semanticRole: literal("accepted_form_diagnostic"),
|
|
147
|
+
businessMetrics
|
|
148
|
+
};
|
|
149
|
+
var observedMeasurement = refine(union(
|
|
150
|
+
object({
|
|
151
|
+
...observedFields,
|
|
152
|
+
originalGoal: object({ ...originalGoalFields, sourceKind: formSourceKind }),
|
|
153
|
+
diagnosticType: literal("accepted_form"),
|
|
154
|
+
diagnosticState: literal("available"),
|
|
155
|
+
variants: list(availableVariant, 50)
|
|
156
|
+
}),
|
|
157
|
+
object({
|
|
158
|
+
...observedFields,
|
|
159
|
+
originalGoal: object({ ...originalGoalFields, sourceKind: unsupportedSourceKind }),
|
|
160
|
+
diagnosticType: literal(null),
|
|
161
|
+
diagnosticState: literal("unsupported_original_source"),
|
|
162
|
+
variants: list(unsupportedVariant, 50)
|
|
163
|
+
})
|
|
164
|
+
), (value, p) => {
|
|
165
|
+
if (Date.parse(value.scope.assignedFrom) >= Date.parse(value.scope.assignedTo) || Date.parse(value.scope.assignedTo) > Date.parse(value.observedAt))
|
|
166
|
+
fail(p + ".scope", "requires an increasing assignment cohort ending by the native observation");
|
|
167
|
+
if (new Set(value.variants.map((v) => v.variantKey)).size !== value.variants.length) fail(p + ".variants", "variant keys must be unique");
|
|
168
|
+
for (const [index, v] of value.variants.entries()) {
|
|
169
|
+
const assigned = BigInt(v.assignedUnits), exposed = BigInt(v.exposedUnits), mature = BigInt(v.matureAssignedUnits);
|
|
170
|
+
const at = `${p}.variants[${index}]`;
|
|
171
|
+
if (exposed > assigned || BigInt(v.exposureEvents) < exposed || BigInt(v.assignmentsWithoutExposure) !== assigned - exposed || mature + BigInt(v.immatureAssignedUnits) !== assigned)
|
|
172
|
+
fail(at, "assignment, exposure and maturity counts are inconsistent");
|
|
173
|
+
if (v.acceptedFormUnits !== null && v.acceptedFormEvents !== null && v.matureAcceptedFormUnits !== null) {
|
|
174
|
+
const accepted = BigInt(v.acceptedFormUnits), matureAccepted = BigInt(v.matureAcceptedFormUnits);
|
|
175
|
+
if (accepted > exposed || accepted > BigInt(v.acceptedFormEvents) || matureAccepted > mature || matureAccepted > accepted)
|
|
176
|
+
fail(at, "accepted form units must remain within their exposed, event and mature cohorts");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
var measurement = union(
|
|
181
|
+
object({ schemaVersion: literal(1), mode: literal("legacy"), state: literal("unavailable"), reasonCode: literal("legacy_assignment_outcome_events_only") }),
|
|
182
|
+
object({ schemaVersion: literal(1), mode: literal("trusted_source"), state: literal("unavailable"), reasonCode: literal("measurement_scope_required") }),
|
|
183
|
+
observedMeasurement
|
|
184
|
+
);
|
|
185
|
+
measurement.schema;
|
|
186
|
+
var scopeOption = object({
|
|
187
|
+
campaignId: measurementUuid,
|
|
188
|
+
campaignVersionId: measurementUuid,
|
|
189
|
+
campaignDefinitionRevision: integer(1),
|
|
190
|
+
acceptedAt: measurementInstant,
|
|
191
|
+
site: measurementSite,
|
|
192
|
+
experimentId: measurementUuid,
|
|
193
|
+
experimentRevision: integer(1),
|
|
194
|
+
goalRevision: measurementRevision,
|
|
195
|
+
goalLabel: nullable(text(200)),
|
|
196
|
+
assignmentExtent: nullable(object({ firstAssignedAt: measurementInstant, lastAssignedAt: measurementInstant })),
|
|
197
|
+
suggestedCohort: nullable(object({ assignedFrom: measurementInstant, assignedTo: measurementInstant }))
|
|
198
|
+
});
|
|
199
|
+
var scopeOptions = refine(object({ observedAt: measurementInstant, items: list(scopeOption, 100), nextCursor: nullable(text(4096)) }), (value, p) => {
|
|
200
|
+
const observed = Date.parse(value.observedAt), tuples = /* @__PURE__ */ new Set();
|
|
201
|
+
for (const [index, item] of value.items.entries()) {
|
|
202
|
+
const at = `${p}.items[${index}]`, accepted = Date.parse(item.acceptedAt);
|
|
203
|
+
const tuple = JSON.stringify([
|
|
204
|
+
item.campaignId.toLowerCase(),
|
|
205
|
+
item.campaignVersionId.toLowerCase(),
|
|
206
|
+
item.site,
|
|
207
|
+
item.experimentId.toLowerCase(),
|
|
208
|
+
item.experimentRevision,
|
|
209
|
+
item.goalRevision
|
|
210
|
+
]);
|
|
211
|
+
if (tuples.has(tuple)) fail(at, "original scope tuples must be unique");
|
|
212
|
+
tuples.add(tuple);
|
|
213
|
+
if (accepted > observed) fail(at + ".acceptedAt", "must not follow the native observation");
|
|
214
|
+
if (item.assignmentExtent) {
|
|
215
|
+
const first = Date.parse(item.assignmentExtent.firstAssignedAt), last = Date.parse(item.assignmentExtent.lastAssignedAt);
|
|
216
|
+
if (first > last || last > observed) fail(at + ".assignmentExtent", "requires first <= last <= native observation");
|
|
217
|
+
}
|
|
218
|
+
if (item.suggestedCohort) {
|
|
219
|
+
const from = Date.parse(item.suggestedCohort.assignedFrom), to = Date.parse(item.suggestedCohort.assignedTo);
|
|
220
|
+
if (from !== accepted || from >= to || to > observed) fail(at + ".suggestedCohort", "requires original acceptance start and increasing cohort ending by native observation");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
scopeOptions.schema;
|
|
225
|
+
|
|
226
|
+
// src/contracts/resonance.ts
|
|
227
|
+
var ResonanceValidationError = class extends Error {
|
|
228
|
+
constructor(path2, reason) {
|
|
229
|
+
super(`${path2}: ${reason}`);
|
|
230
|
+
this.path = path2;
|
|
231
|
+
this.name = "ResonanceValidationError";
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
var fail2 = (path2, reason) => {
|
|
235
|
+
throw new ResonanceValidationError(path2, reason);
|
|
236
|
+
};
|
|
237
|
+
var { field: field2, text: text2, choice: choice2, literal: literal2, integer: integer2, bool, optional: optional2, nullable: nullable2, list: list2, object: object2, union: union2, refine: refine2 } = createWireSchema(fail2, (error) => error instanceof ResonanceValidationError);
|
|
238
|
+
var uuid2 = text2(36, /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
|
239
|
+
var digest = text2(64, /^[a-f0-9]{64}$/);
|
|
240
|
+
var revision = text2(128, /^[a-zA-Z0-9._:-]+$/);
|
|
241
|
+
var instant = refine2(text2(30, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/), (v, p) => {
|
|
242
|
+
if (!Number.isFinite(Date.parse(v)) || new Date(v).toISOString().replace(".000Z", "Z") !== v.replace(".000Z", "Z")) fail2(p, "invalid UTC instant");
|
|
243
|
+
});
|
|
244
|
+
var timeZone = refine2(text2(80), (v, p) => {
|
|
245
|
+
try {
|
|
246
|
+
new Intl.DateTimeFormat("en", { timeZone: v });
|
|
247
|
+
} catch {
|
|
248
|
+
fail2(p, "invalid IANA timezone");
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
var site = refine2(text2(253), (v, p) => {
|
|
252
|
+
if (!isNormalizedSiteHost(v) || isBlockedPublicUrlHost(v)) fail2(p, "must be a normalized public site host");
|
|
253
|
+
});
|
|
254
|
+
var path = text2(2048, /^\/(?!\/)[^?#\s\\]*$/);
|
|
255
|
+
var decimal = text2(30, /^(?:0|[1-9][0-9]{0,29})$/);
|
|
256
|
+
var operationKey = text2(200, /^[a-zA-Z0-9._:-]+$/);
|
|
257
|
+
var objectiveCountingUnit = choice2("unique_source", "unique_qualified_contact", "net_order");
|
|
258
|
+
var RESONANCE_CURRENCIES = ["AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHF", "CLP", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "INR", "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SLL", "SOS", "SRD", "SSP", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VES", "VND", "VUV", "WST", "XAF", "XCD", "XCG", "XDR", "XOF", "XPF", "XSU", "YER", "ZAR", "ZMW", "ZWG", "ZWL"];
|
|
259
|
+
var currency = choice2(...RESONANCE_CURRENCIES);
|
|
260
|
+
var money = object2({ currency, amountMicros: decimal });
|
|
261
|
+
money.schema;
|
|
262
|
+
var sourceMarker = text2(1536, /^rsm1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{43}$/);
|
|
263
|
+
var parseResonanceSourceMarker = (v) => sourceMarker.read(v, "sourceMarker");
|
|
264
|
+
sourceMarker.schema;
|
|
265
|
+
var scope = { projectId: uuid2, site };
|
|
266
|
+
var resource = object2({ ...scope, module: choice2("funnel", "broadcast", "outreach", "experiment", "files", "business_brain", "publication", "crm", "commerce", "forms", "booking", "prepared_plan"), id: uuid2, revision, digest });
|
|
267
|
+
var source = object2({ ...scope, kind: choice2("accepted_form", "qualified_lead", "completed_booking", "net_paid_order", "approved_application"), id: uuid2, revision });
|
|
268
|
+
var qualificationFields = { schemaVersion: literal2(1), semantics: literal2("current_at_observation"), positiveStageKeys: list2(revision, 500), finalNegativeStageKeys: list2(revision, 500), pendingStageKeys: list2(revision, 500), existingContactPolicy: literal2("include_source_cohort") };
|
|
269
|
+
var checkQualificationPartitions = (v, p) => {
|
|
270
|
+
unique([...v.positiveStageKeys, ...v.finalNegativeStageKeys, ...v.pendingStageKeys], p + ".stagePartitions");
|
|
271
|
+
};
|
|
272
|
+
var qualificationRule = refine2(object2({ ...qualificationFields, attributionModel: literal2(RESONANCE_ATTRIBUTION_MODEL) }), checkQualificationPartitions);
|
|
273
|
+
qualificationRule.schema;
|
|
274
|
+
var resolvedQualificationRule = refine2(object2({
|
|
275
|
+
...qualificationFields,
|
|
276
|
+
ref: resource,
|
|
277
|
+
crmVariant: text2(128),
|
|
278
|
+
ladderSnapshot: list2(object2({ id: nullable2(uuid2), stage_key: revision, semantic_key: nullable2(revision), is_won: bool, is_lost: bool }), 500, 1),
|
|
279
|
+
eligibleFormIds: list2(uuid2, 500, 1),
|
|
280
|
+
attribution: object2({ model: literal2(RESONANCE_ATTRIBUTION_MODEL), resolverRevision: literal2(1) }),
|
|
281
|
+
cohort: object2({ startAt: instant, endAt: instant, maturityDelaySeconds: integer2(0, 31536e3) })
|
|
282
|
+
}), (v, p) => {
|
|
283
|
+
checkQualificationPartitions(v, p);
|
|
284
|
+
if (v.ref.module !== "crm" || v.ref.id !== v.ref.projectId) fail2(p + ".ref", "must reference the actual CRM project configuration");
|
|
285
|
+
unique(v.eligibleFormIds, p + ".eligibleFormIds");
|
|
286
|
+
unique(v.ladderSnapshot.flatMap((x) => x.id ? [x.id] : []), p + ".ladderSnapshot.id");
|
|
287
|
+
unique(v.ladderSnapshot.map((x) => x.stage_key), p + ".ladderSnapshot.stage_key");
|
|
288
|
+
const partitions = [...v.positiveStageKeys, ...v.finalNegativeStageKeys, ...v.pendingStageKeys], keys = new Set(v.ladderSnapshot.map((x) => x.stage_key));
|
|
289
|
+
if (partitions.length !== keys.size || partitions.some((k) => !keys.has(k))) fail2(p + ".stagePartitions", "must exhaust the actual native ladder");
|
|
290
|
+
if (Date.parse(v.cohort.endAt) <= Date.parse(v.cohort.startAt)) fail2(p + ".cohort", "invalid cohort interval");
|
|
291
|
+
});
|
|
292
|
+
resolvedQualificationRule.schema;
|
|
293
|
+
var objective = refine2(object2({ source, measurementRevision: revision, countingUnit: objectiveCountingUnit, attributionWindowDays: integer2(1, 365), maturityDelaySeconds: integer2(0, 31536e3), qualificationRuleRef: nullable2(resource), qualificationRule: optional2(qualificationRule) }), (v, p) => {
|
|
294
|
+
if (v.qualificationRule && v.source.kind !== "qualified_lead") fail2(p + ".qualificationRule", "requires the native qualified-lead source");
|
|
295
|
+
if (v.qualificationRule && v.qualificationRuleRef && (v.qualificationRuleRef.module !== "crm" || v.qualificationRuleRef.id !== v.source.projectId)) fail2(p + ".qualificationRuleRef", "must reference actual CRM project configuration");
|
|
296
|
+
});
|
|
297
|
+
var creative = object2({ id: uuid2, assetRef: resource, preparedArtifactRef: nullable2(resource), headline: text2(160), body: text2(1e4), callToAction: text2(80), cropDigest: digest });
|
|
298
|
+
var destination = union2(
|
|
299
|
+
object2({ kind: literal2("funnel"), id: uuid2, ...scope, path, funnelRef: resource, formSlug: text2(80, /^[a-z0-9]+(?:-[a-z0-9]+)*$/), installationRevision: revision }),
|
|
300
|
+
object2({ kind: literal2("existing_page"), id: uuid2, ...scope, path, pageRevision: revision, evidenceDigest: digest })
|
|
301
|
+
);
|
|
302
|
+
var audience = object2({ ...scope, id: uuid2, revision, policyRevision: revision, retentionDays: integer2(1, 540), exclusionRuleIds: list2(uuid2, 50), minDaysSinceVisit: optional2(integer2(1, 179)), consentPurpose: literal2("ad_personalization") });
|
|
303
|
+
var experiment = object2({ ...scope, id: uuid2, revision: integer2(1), digest });
|
|
304
|
+
var budget = refine2(object2({ total: money, dailyAllocationCeiling: nullable2(money), reserveMargin: money, startAt: instant, endAt: instant, timeZone, maxChange: money, maxChangeBasisPoints: integer2(0, 1e4), cooldownSeconds: integer2(0, 31536e3) }), (v, p) => {
|
|
305
|
+
if (Date.parse(v.endAt) <= Date.parse(v.startAt)) fail2(p, "end must follow start");
|
|
306
|
+
for (const m of [v.reserveMargin, v.maxChange, ...v.dailyAllocationCeiling ? [v.dailyAllocationCeiling] : []]) if (m.currency !== v.total.currency) fail2(p, "currency mismatch");
|
|
307
|
+
if (BigInt(v.reserveMargin.amountMicros) > BigInt(v.total.amountMicros)) fail2(p, "reserve exceeds total");
|
|
308
|
+
});
|
|
309
|
+
var allocationPolicyFields = {
|
|
310
|
+
schemaVersion: literal2(1),
|
|
311
|
+
sourceRef: nullable2(resource),
|
|
312
|
+
decisionKind: literal2("observational_allocation"),
|
|
313
|
+
eligibleGoal: literal2("qualified_lead"),
|
|
314
|
+
algorithm: literal2("mature_cpql_conserved_transfer_v1"),
|
|
315
|
+
minimumMatureExposures: integer2(1),
|
|
316
|
+
minimumMatureOutcomes: integer2(1),
|
|
317
|
+
minimumCoverageBasisPoints: integer2(0, 1e4),
|
|
318
|
+
maxEvidenceAgeSeconds: integer2(1),
|
|
319
|
+
maturityDelaySeconds: integer2(0),
|
|
320
|
+
cooldownSeconds: integer2(0),
|
|
321
|
+
donorTransferBasisPoints: integer2(1, 1e4),
|
|
322
|
+
maxUncertaintyBasisPoints: integer2(0, 1e4),
|
|
323
|
+
uncertaintyMethod: literal2("observational_cpql_missing_outcome_bound_v1"),
|
|
324
|
+
attributionModel: choice2("first_party_source", "first_touch", "last_touch", "provider_reported", RESONANCE_ATTRIBUTION_MODEL),
|
|
325
|
+
requiredChecks: list2(choice2("tracking", "dedupe", "qualification", "uncertainty", "assignment", "power"), 6, 1)
|
|
326
|
+
};
|
|
327
|
+
var matureOptimizationPolicy = object2(allocationPolicyFields);
|
|
328
|
+
var reportedPolicyFields = { ...allocationPolicyFields, algorithm: literal2("reported_cpql_conserved_transfer_v1"), costBasis: literal2("provider_reported_snapshot_v1"), costRevisionRisk: literal2("accepted_provisional") };
|
|
329
|
+
var reportedOptimizationPolicy = object2({ ...reportedPolicyFields, readClockKind: literal2("sql_before_provider_read_lower_bound_v1") });
|
|
330
|
+
function uniqueList(item, max, min = 0) {
|
|
331
|
+
const base = list2(item, max, min);
|
|
332
|
+
return field2({ ...base.schema, uniqueItems: true }, (v, p) => {
|
|
333
|
+
const r = base.read(v, p);
|
|
334
|
+
unique(r, p);
|
|
335
|
+
return r;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
var optimizationCheck = choice2("tracking", "dedupe", "qualification", "uncertainty", "assignment", "power");
|
|
339
|
+
var finiteCandidateAction = choice2("materialize_and_activate", "activate_held", "pause_candidate");
|
|
340
|
+
var finiteStopRule = choice2("exploration_cap", "flight_end", "policy_or_mandate_expiry", "kill", "candidate_identity_or_settings_drift", "source_or_consent_invalid", "required_tracking_unavailable_or_stale", "unknown_effect");
|
|
341
|
+
var positiveMicrosPattern = /^[1-9][0-9]*(?![\s\S])/;
|
|
342
|
+
var positiveMoneyShape = field2({ allOf: [money.schema, { properties: { amountMicros: { pattern: positiveMicrosPattern.source } } }] }, (v, p) => {
|
|
343
|
+
const m = money.read(v, p);
|
|
344
|
+
if (!positiveMicrosPattern.test(m.amountMicros)) fail2(p, "requires positive canonical money");
|
|
345
|
+
return m;
|
|
346
|
+
});
|
|
347
|
+
var finiteMetaCandidatePolicy = object2({
|
|
348
|
+
schemaVersion: literal2(1),
|
|
349
|
+
algorithm: literal2("finite_meta_candidates_v1"),
|
|
350
|
+
decisionKind: literal2("preapproved_candidate_operation"),
|
|
351
|
+
eligibleGoal: literal2("qualified_lead"),
|
|
352
|
+
sourceRef: nullable2(resource),
|
|
353
|
+
candidateChannelIds: uniqueList(uuid2, 100, 1),
|
|
354
|
+
selection: object2({ kind: literal2("fixed_reviewed_order"), candidateOrder: uniqueList(uuid2, 100, 1), eligibilitySchedule: list2(object2({ channelId: uuid2, notBefore: instant, notAfter: instant }), 100, 1) }),
|
|
355
|
+
allowedActions: uniqueList(finiteCandidateAction, 3, 1),
|
|
356
|
+
maxConcurrentCandidates: integer2(1, 100),
|
|
357
|
+
maxActivationsPerCandidate: literal2(1),
|
|
358
|
+
totalExplorationCap: positiveMoneyShape,
|
|
359
|
+
measurementGuard: object2({ sourceRef: source, qualificationRuleRef: resource, attributionModel: literal2(RESONANCE_ATTRIBUTION_MODEL), maturityDelaySeconds: integer2(0, 31536e3), minimumCoverageBasisPoints: integer2(0, 1e4), maxEvidenceAgeSeconds: integer2(1), requiredChecks: uniqueList(optimizationCheck, 6, 1) }),
|
|
360
|
+
performanceDecisionPolicy: literal2(null),
|
|
361
|
+
stopRules: uniqueList(finiteStopRule, 8, 8),
|
|
362
|
+
expiresAt: instant
|
|
363
|
+
});
|
|
364
|
+
var optimizationPolicy = union2(matureOptimizationPolicy, finiteMetaCandidatePolicy, reportedOptimizationPolicy);
|
|
365
|
+
optimizationPolicy.schema;
|
|
366
|
+
var optimization = object2({ enabled: bool, policy: optimizationPolicy, stopLoss: object2({ confirmedSpend: money, unreconciledExposure: money }) });
|
|
367
|
+
var channelBase = { id: uuid2, ...scope, destinationId: uuid2, creativeIds: list2(uuid2, 50, 1) };
|
|
368
|
+
var search = object2({ ...channelBase, localCampaignKey: text2(80), kind: literal2("google_search"), provider: literal2("google_ads"), accountBindingId: uuid2, capabilityRevision: revision, allocation: money, budgetMode: choice2("campaign_total", "average_daily"), keywords: list2(object2({ text: text2(80), match: choice2("exact", "phrase", "broad") }), 200, 1), negativeKeywords: list2(text2(80), 200), bidding: union2(object2({ strategy: literal2("maximize_conversions") }), object2({ strategy: literal2("target_cpa"), target: money })), locationIds: list2(text2(80), 100, 1), network: object2({ googleSearch: literal2(true), searchPartners: bool, displayExpansion: literal2(false) }), geoPresence: choice2("presence", "presence_or_interest"), excludedLocationIds: list2(text2(80), 100), adGroups: list2(object2({ localKey: text2(80), ads: list2(object2({ localKey: text2(80), creativeId: uuid2, headlines: list2(text2(30), 15, 3), descriptions: list2(text2(90), 4, 2) }), 20, 1) }), 100, 1), assetAutomation: literal2("disabled") });
|
|
369
|
+
var metaAgeConstraint = union2(refine2(object2({ mode: literal2("bounded"), min: integer2(1, 120), max: integer2(1, 120) }), (v, p) => {
|
|
370
|
+
if (v.min > v.max) fail2(p, "minimum age exceeds maximum");
|
|
371
|
+
}), object2({ mode: literal2("omitted") }), object2({ mode: literal2("provider_managed") }));
|
|
372
|
+
var propertyKey = object2({ key: text2(40, /^[a-z0-9]+(?:-[a-z0-9]+)*$/), label: text2(80) });
|
|
373
|
+
var meta = object2({
|
|
374
|
+
...channelBase,
|
|
375
|
+
localCampaignKey: text2(80),
|
|
376
|
+
kind: literal2("meta_retargeting"),
|
|
377
|
+
provider: literal2("meta_ads"),
|
|
378
|
+
accountBindingId: uuid2,
|
|
379
|
+
capabilityRevision: revision,
|
|
380
|
+
allocation: money,
|
|
381
|
+
budgetMode: choice2("lifetime", "daily"),
|
|
382
|
+
audienceRuleRefs: list2(audience, 50, 1),
|
|
383
|
+
objective: choice2("OUTCOME_LEADS", "OUTCOME_SALES", "OUTCOME_TRAFFIC"),
|
|
384
|
+
pageRef: object2({ connectionId: uuid2, externalId: text2(160), revision }),
|
|
385
|
+
instagramRef: nullable2(object2({ connectionId: uuid2, externalId: text2(160), revision })),
|
|
386
|
+
datasetRef: object2({ id: text2(160), revision }),
|
|
387
|
+
conversionGoalRef: resource,
|
|
388
|
+
optimization: choice2("landing_page_views", "lead", "purchase"),
|
|
389
|
+
billing: literal2("impressions"),
|
|
390
|
+
bid: union2(object2({ strategy: literal2("lowest_cost") }), object2({ strategy: literal2("cost_cap"), cap: money })),
|
|
391
|
+
localAdSetKey: text2(80),
|
|
392
|
+
localAdKey: text2(80),
|
|
393
|
+
budgetOwner: choice2("campaign", "adset"),
|
|
394
|
+
specialCategories: list2(choice2("housing", "employment", "financial_products_services"), 3),
|
|
395
|
+
countryCodes: list2(text2(2, /^[A-Z]{2}$/), 30, 1),
|
|
396
|
+
placement: literal2("automatic"),
|
|
397
|
+
ageConstraint: optional2(metaAgeConstraint),
|
|
398
|
+
// Property retargeting (one channel per property under one group). Every field is optional and omitted from the canonical form when absent, so no existing reviewed digest changes.
|
|
399
|
+
channelGroup: optional2(propertyKey),
|
|
400
|
+
property: optional2(propertyKey),
|
|
401
|
+
tier: optional2(object2({ label: text2(40) })),
|
|
402
|
+
primaryText: optional2(text2(2200)),
|
|
403
|
+
convertersAcknowledged: optional2(literal2(true))
|
|
404
|
+
});
|
|
405
|
+
var owned = object2({ ...channelBase, kind: choice2("funnel", "broadcast", "outreach"), resourceRef: resource });
|
|
406
|
+
var channel = union2(search, meta, owned);
|
|
407
|
+
var definitionFields = { schemaVersion: literal2(1), projectId: uuid2, name: text2(160), slug: refine2(text2(80), (v, p) => {
|
|
408
|
+
if (normalizeCampaignSlug(v) !== v) fail2(p, "must be canonical campaign slug");
|
|
409
|
+
}), sites: list2(site, 50, 1), objective, brandRef: resource, creativeRefs: list2(creative, 100, 1), destinations: list2(destination, 100, 1), channels: list2(channel, 100, 1), experimentRefs: list2(experiment, 50), budget, optimization };
|
|
410
|
+
var definition = object2(definitionFields);
|
|
411
|
+
var draft = object2({ schemaVersion: literal2(1), projectId: uuid2, name: text2(160, void 0, 0), slug: definitionFields.slug, sites: list2(site, 50), objective: optional2(objective), brandRef: optional2(resource), creativeRefs: optional2(list2(creative, 100)), destinations: optional2(list2(destination, 100)), channels: optional2(list2(channel, 100)), experimentRefs: optional2(list2(experiment, 50)), budget: optional2(budget), optimization: optional2(optimization) });
|
|
412
|
+
function scoped(value, projectId, sites, at) {
|
|
413
|
+
if (!value || typeof value !== "object") return;
|
|
414
|
+
if (Array.isArray(value)) {
|
|
415
|
+
value.forEach((x, i) => scoped(x, projectId, sites, `${at}[${i}]`));
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const v = value;
|
|
419
|
+
if ("projectId" in v && v.projectId !== projectId) fail2(at, "cross-project reference");
|
|
420
|
+
if ("site" in v && !sites.includes(v.site)) fail2(at, "site outside campaign scope");
|
|
421
|
+
for (const [k, x] of Object.entries(v)) scoped(x, projectId, sites, `${at}.${k}`);
|
|
422
|
+
}
|
|
423
|
+
function unique(values, at) {
|
|
424
|
+
if (new Set(values).size !== values.length) fail2(at, "duplicate identity");
|
|
425
|
+
}
|
|
426
|
+
definition.schema;
|
|
427
|
+
draft.schema;
|
|
428
|
+
var capability = object2({ id: uuid2, projectId: uuid2, connectionId: uuid2, provider: choice2("google_ads", "meta_ads"), externalAccountId: text2(160), currency, timeZone, connectionRevision: revision, capabilityRevision: revision, observedAt: instant, expiresAt: instant, advertiserState: choice2("eligible", "blocked", "unknown"), operations: list2(object2({ operation: choice2("discover", "prepare", "launch", "update", "pause", "resume", "finish", "reconcile", "measure", "audience", "conversion", "experiment"), state: choice2("supported", "blocked", "unknown"), reasonCodes: list2(text2(80), 30), prerequisites: list2(text2(100), 30) }), 20), profiles: list2(object2({ profile: choice2("google_search", "meta_retargeting"), state: choice2("supported", "blocked", "unknown"), reasonCodes: list2(text2(80), 30), budgetModes: list2(choice2("campaign_total", "average_daily", "lifetime", "daily"), 4), providerApiVersion: text2(30) }), 2) });
|
|
429
|
+
var mandateRef = object2({ ledgerActionId: uuid2, campaignId: uuid2, preparedPlanId: uuid2, reviewDigest: digest, mandateDigest: digest, expectedKillRevision: integer2(0) });
|
|
430
|
+
var websiteVisitMaxAgeMs = 3e5;
|
|
431
|
+
var RESONANCE_WEBSITE_VISIT_PROFILE = Object.freeze({
|
|
432
|
+
profile: "sonor_sdk_page_view_fresh_v1",
|
|
433
|
+
source: "sdk_reported_first_party_cookie",
|
|
434
|
+
assurance: "sdk_reported",
|
|
435
|
+
matchingFields: Object.freeze(["fbp", "fbc"]),
|
|
436
|
+
maxCaptureAgeMs: websiteVisitMaxAgeMs,
|
|
437
|
+
maxDispatchAgeMs: websiteVisitMaxAgeMs,
|
|
438
|
+
maxFutureSkewMs: 0,
|
|
439
|
+
browserServerPairing: "unsupported"
|
|
440
|
+
});
|
|
441
|
+
var measurementRuleRef = object2({ id: uuid2, revision });
|
|
442
|
+
var measurementConfigRef = object2({ configRevision: revision, policyRevision: revision, digest });
|
|
443
|
+
var matchingProfileRef = object2({ id: uuid2, revision: integer2(1), digest });
|
|
444
|
+
var mandateAllowedOperations = list2(choice2("launch", "update", "resume", "optimize", "deliver_conversion"), 5, 1);
|
|
445
|
+
var websiteVisitFeed = object2({ profile: literal2(RESONANCE_WEBSITE_VISIT_PROFILE.profile), channels: list2(object2({
|
|
446
|
+
channelId: uuid2,
|
|
447
|
+
measurementConfigRef,
|
|
448
|
+
resourceRuleRefs: list2(measurementRuleRef, 500, 1),
|
|
449
|
+
matchingProfileRef
|
|
450
|
+
}), 100, 1) });
|
|
451
|
+
websiteVisitFeed.schema;
|
|
452
|
+
var providerReportingChannelIds = refine2(uniqueList(uuid2, 100, 1), (ids, p) => unique(ids.map((id) => id.toLowerCase()), p));
|
|
453
|
+
var providerReportingSelection = object2({ agentContactId: uuid2, channelIds: providerReportingChannelIds });
|
|
454
|
+
var providerReportingGrant = refine2(object2({ purpose: literal2("provider_reporting_refresh"), orgId: uuid2, projectId: uuid2, agentContactId: uuid2, principalContactId: uuid2, channelIds: providerReportingChannelIds }), (v, p) => {
|
|
455
|
+
if (v.agentContactId.toLowerCase() === v.principalContactId.toLowerCase()) fail2(p + ".principalContactId", "agent and human principal must remain distinct");
|
|
456
|
+
});
|
|
457
|
+
providerReportingSelection.schema;
|
|
458
|
+
providerReportingGrant.schema;
|
|
459
|
+
var mandateScope = object2({ projectId: uuid2, campaignId: uuid2, preparedPlanId: uuid2, reviewDigest: digest, accountBindingIds: list2(uuid2, 100), sites: list2(site, 50, 1), budget, creativeRefs: list2(creative, 100, 1), audienceRuleRefs: list2(audience, 100), goal: objective, allowedOperations: mandateAllowedOperations, allowedTransformationRef: nullable2(resource), expiresAt: instant, websiteVisitFeed: optional2(websiteVisitFeed), providerReporting: optional2(providerReportingGrant) });
|
|
460
|
+
object2({ occurredAt: instant, receivedAt: instant });
|
|
461
|
+
var RESONANCE_GOOGLE_SIGNAL_SEMANTICS = "qualified_at_observation_v1";
|
|
462
|
+
var googleSignalSemantics = literal2(RESONANCE_GOOGLE_SIGNAL_SEMANTICS);
|
|
463
|
+
var RESONANCE_GOOGLE_SOURCE_CATEGORIES = Object.freeze({ accepted_form: "SUBMIT_LEAD_FORM", qualified_lead: "QUALIFIED_LEAD", completed_booking: "BOOK_APPOINTMENT", net_paid_order: "PURCHASE", approved_application: "CONVERTED_LEAD" });
|
|
464
|
+
var googleSemanticsInput = object2({ sourceKind: choice2(...Object.keys(RESONANCE_GOOGLE_SOURCE_CATEGORIES)), counting: choice2("one", "every"), clickWindowDays: integer2(1, 90) });
|
|
465
|
+
function googleConversionActionSemantics(input) {
|
|
466
|
+
const v = googleSemanticsInput.read(input, "googleConversionActionSemantics");
|
|
467
|
+
return { type: "UPLOAD_CLICKS", category: RESONANCE_GOOGLE_SOURCE_CATEGORIES[v.sourceKind], countingType: v.counting === "one" ? "ONE_PER_CLICK" : "MANY_PER_CLICK", clickThroughLookbackWindowDays: v.clickWindowDays, primaryForGoal: false };
|
|
468
|
+
}
|
|
469
|
+
var googleSetupPolicyInput = object2({ source, countingUnit: objectiveCountingUnit, attributionWindowDays: integer2(1, 365) });
|
|
470
|
+
function googleUploadClicksSetupSemantics(input) {
|
|
471
|
+
const v = googleSetupPolicyInput.read(input, "googleSetupPolicy");
|
|
472
|
+
if (v.source.kind !== "qualified_lead" || v.countingUnit !== "unique_qualified_contact") fail2("googleSetupPolicy", "unsupported_counting_policy");
|
|
473
|
+
if (v.attributionWindowDays > 90) fail2("googleSetupPolicy.attributionWindowDays", "unsupported_attribution_window");
|
|
474
|
+
return { ...googleConversionActionSemantics({ sourceKind: v.source.kind, counting: "one", clickWindowDays: v.attributionWindowDays }), category: RESONANCE_GOOGLE_SOURCE_CATEGORIES.qualified_lead, countingType: "ONE_PER_CLICK" };
|
|
475
|
+
}
|
|
476
|
+
var googleGoalChoiceFields = {
|
|
477
|
+
externalId: text2(160, /^customers\/[0-9]+\/customConversionGoals\/[0-9]+$/),
|
|
478
|
+
actionIds: list2(text2(160, /^customers\/[0-9]+\/conversionActions\/[0-9]+$/), 100, 1)
|
|
479
|
+
};
|
|
480
|
+
var googleDeclarationChoiceFields = {
|
|
481
|
+
statement: literal2("DOES_NOT_CONTAIN_EU_POLITICAL_ADVERTISING"),
|
|
482
|
+
category: choice2("general", "housing", "employment", "financial_products_services")
|
|
483
|
+
};
|
|
484
|
+
function normalizeGoogleGoal(value, at) {
|
|
485
|
+
unique(value.actionIds, at + ".actionIds");
|
|
486
|
+
value.actionIds.sort();
|
|
487
|
+
return value;
|
|
488
|
+
}
|
|
489
|
+
var googleSetupSpec = object2({
|
|
490
|
+
schemaVersion: literal2(1),
|
|
491
|
+
kind: literal2("google_upload_clicks_custom_goal_v1"),
|
|
492
|
+
signalSemantics: googleSignalSemantics,
|
|
493
|
+
goalLocalKey: literal2("setup:custom-goal"),
|
|
494
|
+
requestedGoalName: text2(160),
|
|
495
|
+
action: object2({
|
|
496
|
+
localKey: literal2("setup:conversion-action"),
|
|
497
|
+
requestedActionName: text2(160),
|
|
498
|
+
type: literal2("UPLOAD_CLICKS"),
|
|
499
|
+
category: literal2(RESONANCE_GOOGLE_SOURCE_CATEGORIES.qualified_lead),
|
|
500
|
+
countingType: literal2("ONE_PER_CLICK"),
|
|
501
|
+
clickThroughLookbackWindowDays: integer2(1, 90),
|
|
502
|
+
primaryForGoal: literal2(false)
|
|
503
|
+
}),
|
|
504
|
+
goalActionKey: literal2("setup:conversion-action"),
|
|
505
|
+
goalStrategy: literal2("exclusive_custom_goal_v1")
|
|
506
|
+
});
|
|
507
|
+
var googleSaveCommonFields = { schemaVersion: literal2(1), expectedCampaignRevision: integer2(1), expectedRevision: integer2(0), idempotencyKey: operationKey, signalSemantics: googleSignalSemantics, declaration: object2(googleDeclarationChoiceFields) };
|
|
508
|
+
var googleSave = union2(
|
|
509
|
+
object2({ ...googleSaveCommonFields, mode: literal2("existing"), nativeGoal: object2(googleGoalChoiceFields) }),
|
|
510
|
+
object2({ ...googleSaveCommonFields, mode: literal2("admitted_setup"), setupIntent: object2({ requestedGoalName: text2(160), requestedActionName: text2(160) }) })
|
|
511
|
+
);
|
|
512
|
+
var googleGoalDraft = object2({ ...googleGoalChoiceFields, name: nullable2(text2(200, void 0, 0)), revision: nullable2(digest) });
|
|
513
|
+
var googleGoalReviewed = object2({ ...googleGoalChoiceFields, name: text2(200, void 0, 0), revision: digest });
|
|
514
|
+
var googleDeclared = object2({
|
|
515
|
+
...googleDeclarationChoiceFields,
|
|
516
|
+
creativeDigest: digest,
|
|
517
|
+
locationIds: list2(text2(80), 100, 1),
|
|
518
|
+
excludedLocationIds: list2(text2(80), 100),
|
|
519
|
+
declaredByActorId: uuid2,
|
|
520
|
+
declaredAt: instant,
|
|
521
|
+
declaredForCampaignRevision: integer2(1)
|
|
522
|
+
});
|
|
523
|
+
var googleConfigurationFields = {
|
|
524
|
+
projectId: uuid2,
|
|
525
|
+
campaignId: uuid2,
|
|
526
|
+
channelId: uuid2,
|
|
527
|
+
site,
|
|
528
|
+
accountBindingId: uuid2,
|
|
529
|
+
configurationRevision: integer2(1),
|
|
530
|
+
configurationDigest: digest,
|
|
531
|
+
signalSemantics: googleSignalSemantics,
|
|
532
|
+
source,
|
|
533
|
+
measurementRevision: revision,
|
|
534
|
+
countingUnit: objectiveCountingUnit,
|
|
535
|
+
attributionWindowDays: integer2(1, 365),
|
|
536
|
+
declaration: googleDeclared
|
|
537
|
+
};
|
|
538
|
+
var googleExistingChoiceFields = { mode: literal2("existing"), nativeGoal: googleGoalDraft };
|
|
539
|
+
var googleSetupChoiceFields = { mode: literal2("admitted_setup"), setupSpec: googleSetupSpec, setupDigest: digest };
|
|
540
|
+
function checkGoogleConfiguration(value, at) {
|
|
541
|
+
if (value.source.projectId !== value.projectId || value.source.site !== value.site) fail2(at + ".source", "source scope mismatch");
|
|
542
|
+
unique(value.declaration.locationIds, at + ".declaration.locationIds");
|
|
543
|
+
unique(value.declaration.excludedLocationIds, at + ".declaration.excludedLocationIds");
|
|
544
|
+
value.declaration.locationIds.sort();
|
|
545
|
+
value.declaration.excludedLocationIds.sort();
|
|
546
|
+
const expected = googleUploadClicksSetupSemantics({ source: value.source, countingUnit: value.countingUnit, attributionWindowDays: value.attributionWindowDays });
|
|
547
|
+
if (value.mode === "existing") {
|
|
548
|
+
normalizeGoogleGoal(value.nativeGoal, at + ".nativeGoal");
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (canonical({ type: value.setupSpec.action.type, category: value.setupSpec.action.category, countingType: value.setupSpec.action.countingType, clickThroughLookbackWindowDays: value.setupSpec.action.clickThroughLookbackWindowDays, primaryForGoal: value.setupSpec.action.primaryForGoal }) !== canonical(expected)) fail2(at + ".setupSpec.action", "canonical setup semantics mismatch");
|
|
552
|
+
}
|
|
553
|
+
var googleSnapshotBase = union2(object2({ ...googleConfigurationFields, ...googleExistingChoiceFields }), object2({ ...googleConfigurationFields, ...googleSetupChoiceFields }));
|
|
554
|
+
var googleConfigurationSnapshot = refine2(googleSnapshotBase, checkGoogleConfiguration);
|
|
555
|
+
var googleConfigurationReview = refine2(union2(object2({ ...googleConfigurationFields, mode: literal2("existing"), nativeGoal: googleGoalReviewed }), object2({ ...googleConfigurationFields, ...googleSetupChoiceFields })), checkGoogleConfiguration);
|
|
556
|
+
var { configurationRevision: _configurationRevisionField, ...googleViewContentFields } = googleConfigurationFields;
|
|
557
|
+
var googleReadinessFields = { ready: bool, activationReadiness: choice2("blocked", "pending_setup", "requires_final_verification", "verified"), reasonCodes: list2(text2(200), 100) };
|
|
558
|
+
var googleViewFields = { ...googleViewContentFields, revision: integer2(1), ...googleReadinessFields };
|
|
559
|
+
var googleViewBase = union2(object2({ ...googleViewFields, ...googleExistingChoiceFields }), object2({ ...googleViewFields, ...googleSetupChoiceFields }));
|
|
560
|
+
function checkGoogleView(value, at) {
|
|
561
|
+
checkGoogleConfiguration(value, at);
|
|
562
|
+
if (!value.ready && value.activationReadiness !== "blocked") fail2(at + ".activationReadiness", "unready configuration must be blocked");
|
|
563
|
+
if (value.mode === "existing") {
|
|
564
|
+
if (value.ready && (value.nativeGoal.name === null || value.nativeGoal.revision === null)) fail2(at + ".ready", "unavailable native evidence cannot be ready");
|
|
565
|
+
if (value.ready && value.activationReadiness !== "requires_final_verification" && value.activationReadiness !== "verified") fail2(at + ".activationReadiness", "existing native setup needs current verification");
|
|
566
|
+
} else if (value.ready && value.activationReadiness !== "pending_setup") fail2(at + ".activationReadiness", "reviewed setup intent is not native activation");
|
|
567
|
+
}
|
|
568
|
+
var googleConfigurationView = refine2(googleViewBase, checkGoogleView);
|
|
569
|
+
var googleSaveResultFields = { operationId: uuid2, replayed: bool, campaignRevision: integer2(1) };
|
|
570
|
+
var googleConfigurationSaveResponse = refine2(union2(object2({ ...googleViewFields, ...googleExistingChoiceFields, ...googleSaveResultFields }), object2({ ...googleViewFields, ...googleSetupChoiceFields, ...googleSaveResultFields })), checkGoogleView);
|
|
571
|
+
refine2(union2(object2({ ...googleConfigurationFields, ...googleExistingChoiceFields, configurationDigest: optional2(digest) }), object2({ ...googleConfigurationFields, ...googleSetupChoiceFields, configurationDigest: optional2(digest) })), checkGoogleConfiguration);
|
|
572
|
+
googleSetupSpec.schema;
|
|
573
|
+
googleSave.schema;
|
|
574
|
+
googleConfigurationSnapshot.schema;
|
|
575
|
+
googleConfigurationReview.schema;
|
|
576
|
+
googleConfigurationView.schema;
|
|
577
|
+
googleConfigurationSaveResponse.schema;
|
|
578
|
+
var preparedManifestRef = object2({ id: uuid2, digest, providerVersion: text2(30), revision });
|
|
579
|
+
var preview = object2({ channelId: uuid2, site, accountBindingId: nullable2(uuid2), accountName: text2(200), accountTimeZone: timeZone, destinationUrl: text2(4096), attributionMarker: optional2(object2({ parameter: literal2(RESONANCE_SOURCE_MARKER_PARAMETER), value: sourceMarker })), orderedCreatives: list2(creative, 50, 1), audienceRuleRefs: list2(audience, 100), allocation: nullable2(money), capabilityRevision: revision, snapshotDigest: digest, manifestRef: preparedManifestRef, conversionRevision: revision, policyRevision: revision, googleConfiguration: nullable2(googleConfigurationReview) });
|
|
580
|
+
var prepared = object2({ schemaVersion: literal2(1), id: uuid2, campaignId: uuid2, projectId: uuid2, expectedRevision: integer2(1), definitionDigest: digest, reviewDigest: digest, definition, resolvedEvidencePolicy: optimizationPolicy, resolvedQualificationRule: nullable2(resolvedQualificationRule), accounts: list2(capability, 100), channelPreviews: list2(preview, 100, 1), preparedAt: instant, expiresAt: instant, blockingReasons: list2(text2(200), 100) });
|
|
581
|
+
var commandBase = { schemaVersion: literal2(1), campaignId: uuid2, expectedRevision: integer2(1), idempotencyKey: operationKey, channelIds: list2(uuid2, 100) };
|
|
582
|
+
var effect = object2({ ...commandBase, operation: choice2("launch", "update", "resume"), preparedPlanId: uuid2, reviewDigest: digest, mandateRef, expectedRemoteSnapshots: list2(object2({ channelId: uuid2, digest }), 100) });
|
|
583
|
+
var optimize = object2({ ...commandBase, operation: literal2("optimize"), preparedPlanId: uuid2, reviewDigest: digest, mandateRef, decisionRef: object2({ id: uuid2, revision, evidenceDigest: digest }), expectedRemoteSnapshots: list2(object2({ channelId: uuid2, digest }), 100) });
|
|
584
|
+
var safety = object2({ ...commandBase, operation: choice2("pause", "finish", "reconcile") });
|
|
585
|
+
var deliverConversion = object2({ ...commandBase, operation: literal2("deliver_conversion"), channelIds: list2(uuid2, 1, 1), preparedPlanId: uuid2, reviewDigest: digest, mandateRef, conversionId: uuid2 });
|
|
586
|
+
var deliverWebsiteVisit = object2({ ...commandBase, operation: literal2("deliver_website_visit"), channelIds: list2(uuid2, 1, 1), preparedPlanId: uuid2, reviewDigest: digest, mandateRef, occurrenceId: uuid2 });
|
|
587
|
+
var command = union2(effect, optimize, safety, deliverConversion, deliverWebsiteVisit);
|
|
588
|
+
command.schema;
|
|
589
|
+
var step = object2({ id: uuid2, channelId: uuid2, state: choice2("planned", "admitted", "confirmed", "failed", "uncertain"), providerRequestId: nullable2(text2(200)), admission: choice2("not_dispatched", "accepted", "rejected", "unknown"), configuredStatus: nullable2(text2(80)), effectiveStatus: nullable2(text2(80)), reviewStatus: nullable2(text2(80)), retryAfter: nullable2(instant), errorSubcode: nullable2(text2(80)), errorFieldPath: nullable2(text2(200)), resourceRefs: list2(object2({ type: text2(80), externalId: text2(200) }), 100), evidenceDigest: nullable2(digest), errorCode: nullable2(text2(80)), observedAt: nullable2(instant) });
|
|
590
|
+
var receipt = object2({ schemaVersion: literal2(1), id: uuid2, projectId: uuid2, campaignId: uuid2, preparedPlanId: nullable2(uuid2), effectKey: text2(200), requestDigest: digest, ledgerActionId: uuid2, actorId: text2(160), state: choice2("pending", "applying", "accepted", "failed", "needs_reconciliation"), steps: list2(step, 1e3), reservedExposure: money, unreconciledExposure: money, providerSemanticsNotice: list2(text2(500), 30), nextActions: list2(choice2("read", "reconcile", "retry_same_operation", "pause", "escalate"), 5) });
|
|
591
|
+
var lifecycle = choice2("not_materialized", "pending", "partially_live", "live", "pausing", "paused", "finished", "needs_attention", "unknown");
|
|
592
|
+
var view = object2({ id: uuid2, projectId: uuid2, managementMode: choice2("tracking_only", "managed"), revision: integer2(0), definition: nullable2(draft), acceptedPlanId: nullable2(uuid2), desiredLifecycle: choice2("draft", "running", "paused", "finished"), effectiveLifecycle: lifecycle, blockingReasons: list2(text2(200), 100), activeOperation: nullable2(receipt), unresolvedExposure: nullable2(money) });
|
|
593
|
+
var metricValue = union2(object2({ kind: literal2("count"), count: decimal }), object2({ kind: literal2("money"), money }), object2({ kind: literal2("ratio"), numerator: decimal, denominator: refine2(decimal, (v, p) => {
|
|
594
|
+
if (v === "0") fail2(p, "zero denominator");
|
|
595
|
+
}) }));
|
|
596
|
+
var metricBase = { ...scope, campaignId: uuid2, name: choice2("impressions", "clicks", "spend", "accepted_goals", "qualified_goals", "revenue", "conversion_rate", "reach", "results"), source: choice2("first_party", "provider", "experiment"), sourceRef: revision, goalRevision: nullable2(revision), countingUnit: choice2("unique_source", "unique_qualified_contact", "net_order", "provider_aggregate", "provider_unique_reach"), windowStart: instant, windowEnd: instant, timeZone, attributionModel: nullable2(text2(100)), asOf: instant, dataThrough: nullable2(instant), maturityDelaySeconds: integer2(0), coverageBasisPoints: nullable2(integer2(0, 1e4)), eligibleExposures: nullable2(decimal), matureOutcomes: nullable2(decimal), native: nullable2(object2({ unit: text2(80), amount: decimal, currency: nullable2(currency), unitExponent: integer2(0, 9), providerVersion: text2(30) })) };
|
|
597
|
+
var metric = union2(object2({ ...metricBase, state: literal2("observed"), value: metricValue }), object2({ ...metricBase, state: choice2("unknown", "insufficient", "delayed", "unavailable"), value: literal2(null), reasonCode: text2(80) }));
|
|
598
|
+
var purpose = choice2("analytics_storage", "ad_storage", "ad_user_data", "ad_personalization");
|
|
599
|
+
var consent = object2({ ...scope, receiptId: uuid2, visitorNamespace: text2(160), configRevision: revision, policyRevision: revision, consentRevision: integer2(1), state: choice2("unknown", "granted", "denied", "withdrawn"), purpose, observedAt: instant, evidenceRef: revision });
|
|
600
|
+
var exposure = object2({ ...scope, experimentId: uuid2, experimentRevision: integer2(1), assignmentId: uuid2, variantKey: text2(80), exposureId: uuid2, occurredAt: instant });
|
|
601
|
+
var conversion = object2({ schemaVersion: literal2(1), ...scope, eventId: uuid2, sourceRef: source, campaignId: nullable2(uuid2), goalRevision: revision, occurredAt: instant, receivedAt: instant, value: union2(object2({ state: literal2("observed"), money }), object2({ state: choice2("unknown", "unavailable"), money: literal2(null), reasonCode: text2(80) })), consent: list2(consent, 4), experiment: nullable2(exposure) });
|
|
602
|
+
conversion.schema;
|
|
603
|
+
prepared.schema;
|
|
604
|
+
capability.schema;
|
|
605
|
+
mandateScope.schema;
|
|
606
|
+
mandateRef.schema;
|
|
607
|
+
receipt.schema;
|
|
608
|
+
view.schema;
|
|
609
|
+
metric.schema;
|
|
610
|
+
consent.schema;
|
|
611
|
+
var mandateTarget = object2({ project_id: uuid2, campaign_id: uuid2 });
|
|
612
|
+
var mandateApproval = object2({ schemaVersion: literal2(1), expectedRevision: integer2(1), preparedPlanId: uuid2, reviewDigest: digest, mandateDigest: digest, mandate: mandateScope });
|
|
613
|
+
mandateApproval.schema;
|
|
614
|
+
mandateTarget.schema;
|
|
615
|
+
function canonical(value) {
|
|
616
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
617
|
+
if (Array.isArray(value)) return "[" + value.map(canonical).join(",") + "]";
|
|
618
|
+
return "{" + Object.keys(value).sort().map((k) => JSON.stringify(k) + ":" + canonical(value[k])).join(",") + "}";
|
|
619
|
+
}
|
|
620
|
+
var prepareCommand = object2({ expectedRevision: integer2(1), idempotencyKey: operationKey });
|
|
621
|
+
var commandPreparation = object2({ command: nullable2(command), covered: bool, approvalRequirement: nullable2(choice2("existing_mandate", "current_owner")), reasonCodes: list2(text2(200), 100) });
|
|
622
|
+
prepareCommand.schema;
|
|
623
|
+
commandPreparation.schema;
|
|
624
|
+
var saveCommand = object2({ definition: draft, expectedRevision: optional2(integer2(0)), idempotencyKey: operationKey });
|
|
625
|
+
var acceptCommand = object2({ expectedRevision: integer2(1), ledgerActionId: uuid2 });
|
|
626
|
+
var bindingCommand = object2({ connectionId: uuid2, provider: choice2("google_ads", "meta_ads"), externalAccountId: text2(160), expectedConnectionRevision: revision, idempotencyKey: operationKey });
|
|
627
|
+
saveCommand.schema;
|
|
628
|
+
acceptCommand.schema;
|
|
629
|
+
bindingCommand.schema;
|
|
630
|
+
var RESONANCE_CPQL_UNCERTAINTY_METHOD = "observational_cpql_missing_outcome_bound_v1";
|
|
631
|
+
var allocationHorizon = union2(
|
|
632
|
+
refine2(object2({ kind: literal2("finite_period"), startAt: instant, endAt: instant, timeZone, sourceRef: revision }), (r, p) => {
|
|
633
|
+
if (Date.parse(r.endAt) <= Date.parse(r.startAt)) fail2(p, "invalid finite horizon");
|
|
634
|
+
}),
|
|
635
|
+
object2({ kind: literal2("daily_average"), timeZone, sourceRef: revision })
|
|
636
|
+
);
|
|
637
|
+
var uncertaintySource = object2({ method: literal2(RESONANCE_CPQL_UNCERTAINTY_METHOD), eligibleContacts: decimal, fullyResolvedContacts: decimal, qualifiedContacts: decimal, verifiedSpend: money, sourceRevision: revision, qualificationRuleRef: resource, universeComplete: literal2(true), spendPeriodComplete: literal2(true) });
|
|
638
|
+
var allocationEvidenceFields = {
|
|
639
|
+
id: uuid2,
|
|
640
|
+
revision,
|
|
641
|
+
evidenceDigest: digest,
|
|
642
|
+
projectId: uuid2,
|
|
643
|
+
campaignId: uuid2,
|
|
644
|
+
preparedPlanId: uuid2,
|
|
645
|
+
reviewDigest: digest,
|
|
646
|
+
observedAt: instant,
|
|
647
|
+
windowStart: instant,
|
|
648
|
+
windowEnd: instant,
|
|
649
|
+
dataThrough: nullable2(instant),
|
|
650
|
+
lastAdjustmentAt: nullable2(instant),
|
|
651
|
+
currentKillRevision: integer2(0),
|
|
652
|
+
goalRevision: revision,
|
|
653
|
+
objective,
|
|
654
|
+
policyDigest: digest,
|
|
655
|
+
resolvedPolicy: matureOptimizationPolicy,
|
|
656
|
+
trackingState: choice2("healthy", "blocked", "unknown"),
|
|
657
|
+
coverageBasisPoints: nullable2(integer2(0, 1e4)),
|
|
658
|
+
assignmentCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
659
|
+
powerCheck: choice2("pass", "failed", "unknown", "not_applicable")
|
|
660
|
+
};
|
|
661
|
+
var allocationChannelFields = { channelId: uuid2, site, accountBindingId: uuid2, trackingState: choice2("healthy", "blocked", "unknown"), coverageBasisPoints: nullable2(integer2(0, 1e4)), coverageFamily: choice2("crm_attribution", "provider_import", "experiment_exposure"), coverageDefinitionRef: resource, coveredNumerator: nullable2(decimal), eligibleDenominator: nullable2(decimal), uncertaintyBasisPoints: nullable2(integer2(0, 1e4)), uncertaintySource: nullable2(uncertaintySource), dedupeCheck: choice2("pass", "failed", "unknown", "not_applicable"), qualificationCheck: choice2("pass", "failed", "unknown", "not_applicable"), reasonCodes: list2(text2(80), 50), capabilityRevision: revision, budgetMode: choice2("campaign_total", "average_daily", "lifetime", "daily"), allocationHorizon: nullable2(allocationHorizon), matureThrough: nullable2(instant), dataThrough: nullable2(instant), goalRevision: revision, countingUnit: choice2("unique_source", "unique_qualified_contact", "net_order"), attributionModel: text2(100), attributionWindowDays: integer2(1, 365), policyRevision: revision, currentAllocation: money, remoteSnapshotDigest: digest, observations: list2(metric, 100), providerUnitExponent: refine2(integer2(0, 6), (v, p) => {
|
|
662
|
+
if (![0, 2, 3, 6].includes(v)) fail2(p, "unsupported provider exponent");
|
|
663
|
+
}), providerMaxUnits: decimal };
|
|
664
|
+
var { uncertaintySource: _legacyUncertaintyField, ...reportedChannelFields } = allocationChannelFields;
|
|
665
|
+
var matureOptimizationEvidence = object2({ ...allocationEvidenceFields, resolvedPolicy: matureOptimizationPolicy, channels: list2(object2({ ...allocationChannelFields, uncertaintySource: nullable2(uncertaintySource) }), 100, 1) });
|
|
666
|
+
var reportedCostFields = { basis: literal2("provider_reported_snapshot_v1"), observationId: uuid2, sourceEvidenceDigest: digest, reportedSpend: money, queriedFrom: instant, queriedThrough: instant, firstReceivedAt: instant, receivedAt: instant, recordedAt: instant, providerFinality: literal2("unknown"), correctionsPossible: literal2(true) };
|
|
667
|
+
var reportedOutcomeSource = object2({ method: literal2(RESONANCE_CPQL_UNCERTAINTY_METHOD), eligibleContacts: decimal, fullyResolvedContacts: decimal, qualifiedContacts: decimal, sourceRevision: revision, qualificationRuleRef: resource, universeComplete: literal2(true) });
|
|
668
|
+
var nativeSqlReadInstant = refine2(text2(32, /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,6}))?(?:Z|\+00:00)$/), (v, p) => {
|
|
669
|
+
const match = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,6}))?(?:Z|\+00:00)$/.exec(v);
|
|
670
|
+
const projected = match[1] + "." + (match[2] ?? "").padEnd(3, "0").slice(0, 3) + "Z";
|
|
671
|
+
if (!Number.isFinite(Date.parse(projected)) || new Date(projected).toISOString() !== projected) fail2(p, "invalid native SQL UTC instant");
|
|
672
|
+
});
|
|
673
|
+
var providerReadClock = object2({ kind: literal2("sql_before_provider_read_lower_bound_v1"), notBeforeAt: nativeSqlReadInstant });
|
|
674
|
+
var channelReportedCost = object2({ ...reportedCostFields, readClock: providerReadClock });
|
|
675
|
+
var reportedOptimizationEvidence = object2({
|
|
676
|
+
...allocationEvidenceFields,
|
|
677
|
+
evidenceKind: literal2("reported_cpql_sql_clock_v1"),
|
|
678
|
+
resolvedPolicy: reportedOptimizationPolicy,
|
|
679
|
+
channels: list2(object2({ ...reportedChannelFields, channelReportedCost, outcomeSource: reportedOutcomeSource }), 100, 1)
|
|
680
|
+
});
|
|
681
|
+
var optimizationEvidenceRefFields = { id: uuid2, revision, evidenceDigest: digest };
|
|
682
|
+
var optimizationEvidenceRef = object2(optimizationEvidenceRefFields);
|
|
683
|
+
var finiteOwnMeasurement = union2(
|
|
684
|
+
object2({ state: literal2("unobserved"), observations: literal2(null), reasonCodes: list2(text2(80), 50) }),
|
|
685
|
+
object2({ state: choice2("pending", "blocked"), observations: nullable2(list2(metric, 100)), reasonCodes: list2(text2(80), 50) }),
|
|
686
|
+
object2({ state: literal2("mature"), observations: list2(metric, 100, 1), reasonCodes: list2(text2(80), 50) })
|
|
687
|
+
);
|
|
688
|
+
var finiteCandidateObservation = object2({
|
|
689
|
+
channelId: uuid2,
|
|
690
|
+
site,
|
|
691
|
+
accountBindingId: uuid2,
|
|
692
|
+
graphDigest: digest,
|
|
693
|
+
manifestRef: preparedManifestRef,
|
|
694
|
+
cap: positiveMoneyShape,
|
|
695
|
+
flight: object2({ startAt: instant, endAt: instant, timeZone }),
|
|
696
|
+
lifecycle: choice2("not_materialized", "paused", "active", "stopped", "unknown"),
|
|
697
|
+
nativeStateEvidenceRef: nullable2(optimizationEvidenceRef),
|
|
698
|
+
commitmentEvidenceRef: nullable2(object2({ ...optimizationEvidenceRefFields, commitmentOperationId: uuid2 })),
|
|
699
|
+
activationsObserved: integer2(0, 1),
|
|
700
|
+
trackingState: choice2("healthy", "blocked", "unknown"),
|
|
701
|
+
coverageBasisPoints: nullable2(integer2(0, 1e4)),
|
|
702
|
+
dedupeCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
703
|
+
qualificationCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
704
|
+
reasonCodes: list2(text2(80), 50),
|
|
705
|
+
ownMeasurement: finiteOwnMeasurement
|
|
706
|
+
});
|
|
707
|
+
var finiteOptimizationEvidence = object2({
|
|
708
|
+
id: uuid2,
|
|
709
|
+
revision,
|
|
710
|
+
evidenceDigest: digest,
|
|
711
|
+
projectId: uuid2,
|
|
712
|
+
campaignId: uuid2,
|
|
713
|
+
preparedPlanId: uuid2,
|
|
714
|
+
reviewDigest: digest,
|
|
715
|
+
observedAt: instant,
|
|
716
|
+
windowStart: instant,
|
|
717
|
+
windowEnd: instant,
|
|
718
|
+
dataThrough: nullable2(instant),
|
|
719
|
+
lastAdjustmentAt: nullable2(instant),
|
|
720
|
+
currentKillRevision: integer2(0),
|
|
721
|
+
goalRevision: revision,
|
|
722
|
+
objective,
|
|
723
|
+
policyDigest: digest,
|
|
724
|
+
resolvedPolicy: finiteMetaCandidatePolicy,
|
|
725
|
+
trackingState: choice2("healthy", "blocked", "unknown"),
|
|
726
|
+
coverageBasisPoints: nullable2(integer2(0, 1e4)),
|
|
727
|
+
assignmentCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
728
|
+
powerCheck: choice2("pass", "failed", "unknown", "not_applicable"),
|
|
729
|
+
evidenceKind: literal2("finite_candidate_lifecycle_v1"),
|
|
730
|
+
inventoryState: choice2("complete", "incomplete", "unknown"),
|
|
731
|
+
candidates: list2(finiteCandidateObservation, 100, 1)
|
|
732
|
+
});
|
|
733
|
+
var optimizationEvidence = union2(matureOptimizationEvidence, finiteOptimizationEvidence, reportedOptimizationEvidence);
|
|
734
|
+
var matureOptimizationDecision = object2({
|
|
735
|
+
id: uuid2,
|
|
736
|
+
revision,
|
|
737
|
+
projectId: uuid2,
|
|
738
|
+
campaignId: uuid2,
|
|
739
|
+
preparedPlanId: uuid2,
|
|
740
|
+
reviewDigest: digest,
|
|
741
|
+
evidenceRef: optimizationEvidenceRef,
|
|
742
|
+
policyDigest: digest,
|
|
743
|
+
requestDigest: digest,
|
|
744
|
+
expiresAt: instant,
|
|
745
|
+
algorithmRevision: revision,
|
|
746
|
+
sourceRefs: list2(revision, 200),
|
|
747
|
+
state: choice2("actionable", "noop", "blocked"),
|
|
748
|
+
decisionKind: choice2("observational_allocation", "experimental_promotion"),
|
|
749
|
+
executionOrder: literal2("reduce_then_increase"),
|
|
750
|
+
reasonCodes: list2(text2(80), 50),
|
|
751
|
+
adjustments: list2(object2({ channelId: uuid2, site, budgetMode: choice2("campaign_total", "average_daily", "lifetime", "daily"), providerUnitExponent: refine2(integer2(0, 6), (v, p) => {
|
|
752
|
+
if (![0, 2, 3, 6].includes(v)) fail2(p, "unsupported provider exponent");
|
|
753
|
+
}), providerMaxUnits: decimal, before: money, after: money, remoteSnapshotDigest: digest }), 100),
|
|
754
|
+
createdAt: instant
|
|
755
|
+
});
|
|
756
|
+
var finiteCandidateActionFields = { channelId: uuid2, graphDigest: digest, nativeStateEvidenceRef: optimizationEvidenceRef };
|
|
757
|
+
var finiteCandidateActionBody = union2(
|
|
758
|
+
object2({ action: literal2("materialize_and_activate"), ...finiteCandidateActionFields }),
|
|
759
|
+
object2({ action: literal2("activate_held"), ...finiteCandidateActionFields, commitmentOperationId: uuid2 }),
|
|
760
|
+
object2({ action: literal2("pause_candidate"), ...finiteCandidateActionFields })
|
|
761
|
+
);
|
|
762
|
+
var finiteDecisionFields = {
|
|
763
|
+
id: uuid2,
|
|
764
|
+
revision,
|
|
765
|
+
projectId: uuid2,
|
|
766
|
+
campaignId: uuid2,
|
|
767
|
+
preparedPlanId: uuid2,
|
|
768
|
+
reviewDigest: digest,
|
|
769
|
+
evidenceRef: optimizationEvidenceRef,
|
|
770
|
+
policyDigest: digest,
|
|
771
|
+
requestDigest: digest,
|
|
772
|
+
expiresAt: instant,
|
|
773
|
+
createdAt: instant,
|
|
774
|
+
algorithmRevision: literal2("finite_meta_candidates_v1"),
|
|
775
|
+
sourceRefs: list2(revision, 200),
|
|
776
|
+
decisionKind: literal2("preapproved_candidate_operation"),
|
|
777
|
+
reasonCodes: list2(text2(80), 50, 1)
|
|
778
|
+
};
|
|
779
|
+
var finiteOptimizationDecision = union2(
|
|
780
|
+
object2({ ...finiteDecisionFields, state: literal2("actionable"), candidateActions: list2(finiteCandidateActionBody, 1, 1) }),
|
|
781
|
+
object2({ ...finiteDecisionFields, state: choice2("noop", "blocked"), candidateActions: list2(finiteCandidateActionBody, 0) })
|
|
782
|
+
);
|
|
783
|
+
var optimizationDecision = union2(matureOptimizationDecision, finiteOptimizationDecision);
|
|
784
|
+
optimizationEvidence.schema;
|
|
785
|
+
optimizationDecision.schema;
|
|
786
|
+
var metaBrowserWitness = object2({ controlsRevision: text2(60, /^[A-Z][A-Z0-9_]*$/), policyRevision: revision, witnessId: uuid2, assertedAt: instant });
|
|
787
|
+
var publicDestination = union2(
|
|
788
|
+
object2({ provider: literal2("google_ads"), id: uuid2, tagId: text2(40, /^AW-[0-9]+$/) }),
|
|
789
|
+
// browserWitness and tagManagerAttested are optional so configurations from before the Meta loader keep parsing; absent means no Meta browser loading.
|
|
790
|
+
object2({ provider: literal2("meta_ads"), id: uuid2, pixelId: text2(40, /^[0-9]+$/), browserWitness: optional2(metaBrowserWitness), tagManagerAttested: optional2(bool) })
|
|
791
|
+
);
|
|
792
|
+
var interestResource = object2({ ...scope, type: choice2("page", "article", "product", "listing"), id: text2(160, /^[a-zA-Z0-9._:-]+$/), revision });
|
|
793
|
+
var measurementConfig = object2({
|
|
794
|
+
schemaVersion: literal2(1),
|
|
795
|
+
...scope,
|
|
796
|
+
configRevision: revision,
|
|
797
|
+
policyRevision: revision,
|
|
798
|
+
expiresAt: instant,
|
|
799
|
+
enabled: bool,
|
|
800
|
+
consentPolicy: object2({ mode: literal2("basic"), requiredPurposes: list2(purpose, 4, 1) }),
|
|
801
|
+
destinations: list2(publicDestination, 10),
|
|
802
|
+
// metaEligible is set by a person with owner or admin standing; absent means false. The browser never decides it.
|
|
803
|
+
resourceRules: list2(object2({ id: uuid2, revision, site, resource: interestResource, path, interestKey: text2(100, /^[a-zA-Z0-9._:-]+$/), metaEligible: optional2(bool) }), 500)
|
|
804
|
+
});
|
|
805
|
+
function parseResonanceMeasurementConfig(v) {
|
|
806
|
+
const r = measurementConfig.read(v, "measurementConfig");
|
|
807
|
+
scoped(r, r.projectId, [r.site], "measurementConfig");
|
|
808
|
+
unique(r.destinations.map((d) => d.id), "measurementConfig.destinations");
|
|
809
|
+
unique(r.consentPolicy.requiredPurposes, "measurementConfig.consentPolicy.requiredPurposes");
|
|
810
|
+
unique(r.resourceRules.map((rule) => rule.id), "measurementConfig.resourceRules");
|
|
811
|
+
if (r.destinations.length && ["ad_storage", "ad_user_data", "ad_personalization"].some((p) => !r.consentPolicy.requiredPurposes.includes(p))) fail2("measurementConfig.consentPolicy", "provider minimum purposes are mandatory");
|
|
812
|
+
return r;
|
|
813
|
+
}
|
|
814
|
+
var clickIdentifierPattern = /^[^\s\u0000-\u001f\u007f-\u009f]+(?![\s\S])/;
|
|
815
|
+
var clickIdentifier = field2({ type: "string", minLength: 1, maxLength: 1024, pattern: clickIdentifierPattern.source }, (v, p) => typeof v === "string" && v.length <= 2048 && [...v].length <= 1024 && clickIdentifierPattern.test(v) ? v : fail2(p, "invalid click identifier"));
|
|
816
|
+
var clickIdentifiersShape = union2(object2({ gclid: clickIdentifier }), object2({ gbraid: clickIdentifier }), object2({ wbraid: clickIdentifier }));
|
|
817
|
+
var clickIdentifiers = field2(clickIdentifiersShape.schema, clickIdentifiersShape.read);
|
|
818
|
+
var parseResonanceClickIdentifiers = (v) => clickIdentifiers.read(v, "clickIdentifiers");
|
|
819
|
+
clickIdentifiers.schema;
|
|
820
|
+
var observedPathControl = /[\s\u0000-\u001f\u007f-\u009f]/;
|
|
821
|
+
var observedPagePath = field2({ ...path.schema, allOf: [{ not: { pattern: observedPathControl.source } }] }, (v, p) => {
|
|
822
|
+
const result = path.read(v, p);
|
|
823
|
+
if (observedPathControl.test(result)) fail2(p, "invalid observed path");
|
|
824
|
+
return result;
|
|
825
|
+
});
|
|
826
|
+
var metaCookieShape = object2({ source: literal2(RESONANCE_WEBSITE_VISIT_PROFILE.source), fbp: optional2(clickIdentifier), fbc: optional2(clickIdentifier) });
|
|
827
|
+
var metaCookieObservation = field2({ ...metaCookieShape.schema, anyOf: [{ required: ["fbp"] }, { required: ["fbc"] }] }, (v, p) => {
|
|
828
|
+
const result = metaCookieShape.read(v, p);
|
|
829
|
+
if (!result.fbp && !result.fbc) fail2(p, "requires an actual reported cookie value");
|
|
830
|
+
return result;
|
|
831
|
+
});
|
|
832
|
+
var occurrenceShape = object2({
|
|
833
|
+
schemaVersion: literal2(1),
|
|
834
|
+
...scope,
|
|
835
|
+
configRevision: revision,
|
|
836
|
+
eventId: uuid2,
|
|
837
|
+
navigationId: uuid2,
|
|
838
|
+
occurredAt: instant,
|
|
839
|
+
kind: choice2("page_view", "content_interest", "experiment_exposure"),
|
|
840
|
+
sourceMarker: optional2(sourceMarker),
|
|
841
|
+
resourceRef: nullable2(interestResource),
|
|
842
|
+
campaignPlanId: nullable2(uuid2),
|
|
843
|
+
experiment: nullable2(exposure),
|
|
844
|
+
clickIdentifiers: optional2(clickIdentifiers),
|
|
845
|
+
pagePath: optional2(observedPagePath),
|
|
846
|
+
metaCookieObservation: optional2(metaCookieObservation),
|
|
847
|
+
consentReceiptRefs: list2(object2({ purpose, receiptId: uuid2, consentRevision: integer2(1) }), 4)
|
|
848
|
+
});
|
|
849
|
+
var occurrence = field2({ ...occurrenceShape.schema, allOf: [
|
|
850
|
+
{ if: { required: ["clickIdentifiers"] }, then: { properties: { kind: { const: "page_view" } } } },
|
|
851
|
+
{ if: { required: ["pagePath"] }, then: { properties: { kind: { enum: ["page_view", "experiment_exposure"] } } } },
|
|
852
|
+
{ if: { required: ["metaCookieObservation"] }, then: { required: ["pagePath"], properties: { kind: { const: "page_view" }, resourceRef: interestResource.schema } } }
|
|
853
|
+
] }, (v, p) => {
|
|
854
|
+
const r = occurrenceShape.read(v, p);
|
|
855
|
+
if (r.clickIdentifiers && r.kind !== "page_view") fail2(p + ".clickIdentifiers", "requires page_view");
|
|
856
|
+
if (r.pagePath !== void 0 && r.kind !== "page_view" && r.kind !== "experiment_exposure") fail2(p + ".pagePath", "requires page_view or experiment_exposure");
|
|
857
|
+
if (r.metaCookieObservation && (r.kind !== "page_view" || r.pagePath === void 0 || !r.resourceRef)) fail2(p + ".metaCookieObservation", "requires the same page_view path and resource");
|
|
858
|
+
return r;
|
|
859
|
+
});
|
|
860
|
+
function parseResonanceMeasurementOccurrence(v) {
|
|
861
|
+
const r = occurrence.read(v, "occurrence");
|
|
862
|
+
scoped(r, r.projectId, [r.site], "occurrence");
|
|
863
|
+
unique(r.consentReceiptRefs.map((c) => c.purpose), "occurrence.consentReceiptRefs");
|
|
864
|
+
if (r.kind === "content_interest" && !r.resourceRef) fail2("occurrence.resourceRef", "content interest requires resource");
|
|
865
|
+
if (r.kind === "experiment_exposure" && !r.experiment) fail2("occurrence.experiment", "exposure requires assignment context");
|
|
866
|
+
return r;
|
|
867
|
+
}
|
|
868
|
+
var consentUpdate = object2({ schemaVersion: literal2(1), ...scope, configRevision: revision, policyRevision: revision, expectedConsentRevision: integer2(0), occurredAt: instant, idempotencyKey: operationKey, source: choice2("cmp", "user_preference"), purposes: list2(object2({ purpose, state: choice2("unknown", "granted", "denied", "withdrawn") }), 4, 1) });
|
|
869
|
+
function parseResonanceConsentUpdate(v) {
|
|
870
|
+
const r = consentUpdate.read(v, "consentUpdate");
|
|
871
|
+
unique(r.purposes.map((p) => p.purpose), "consentUpdate.purposes");
|
|
872
|
+
return r;
|
|
873
|
+
}
|
|
874
|
+
measurementConfig.schema;
|
|
875
|
+
occurrence.schema;
|
|
876
|
+
consentUpdate.schema;
|
|
877
|
+
var consentResult = object2({ subjectProof: text2(200, /^[a-zA-Z0-9_-]{32,}$/), expiresAt: instant, currentConsentRevision: integer2(1), receipts: list2(consent, 4, 4) });
|
|
878
|
+
function parseResonanceConsentResponse(v) {
|
|
879
|
+
const r = consentResult.read(v, "consentResponse"), first = r.receipts[0];
|
|
880
|
+
if (r.receipts.some((c) => c.consentRevision !== r.currentConsentRevision)) fail2("consentResponse.receipts", "global consent revision mismatch");
|
|
881
|
+
unique(r.receipts.map((c) => c.purpose), "consentResult.receipts");
|
|
882
|
+
for (const c of r.receipts) if (c.projectId !== first.projectId || c.site !== first.site || c.configRevision !== first.configRevision || c.policyRevision !== first.policyRevision || c.visitorNamespace !== first.visitorNamespace) fail2("consentResult.receipts", "mixed consent subjects or scope");
|
|
883
|
+
return r;
|
|
884
|
+
}
|
|
885
|
+
var occurrenceResult = object2({ eventId: uuid2, accepted: literal2(true), replayed: bool, exposureId: nullable2(uuid2) });
|
|
886
|
+
var parseResonanceOccurrenceResponse = (v) => occurrenceResult.read(v, "occurrenceResult");
|
|
887
|
+
consentResult.schema;
|
|
888
|
+
occurrenceResult.schema;
|
|
889
|
+
var subjectRenew = object2({ schemaVersion: literal2(1), ...scope, expectedConsentRevision: integer2(1), configRevision: revision, policyRevision: revision });
|
|
890
|
+
var parseResonanceSubjectRenewCommand = (v) => subjectRenew.read(v, "subjectRenew");
|
|
891
|
+
subjectRenew.schema;
|
|
892
|
+
resource.schema;
|
|
893
|
+
audience.schema;
|
|
894
|
+
experiment.schema;
|
|
895
|
+
var audienceDraft = object2({
|
|
896
|
+
schemaVersion: literal2(1),
|
|
897
|
+
expectedRevision: optional2(integer2(1)),
|
|
898
|
+
idempotencyKey: operationKey,
|
|
899
|
+
definition: object2({ name: text2(160), site, accountBindingId: uuid2, resourceRuleId: uuid2, excludedResourceRuleIds: list2(uuid2, 50), retentionDays: integer2(1, 540), minDaysSinceVisit: optional2(integer2(1, 179)), policyRevision: revision, currentConsentPolicyRevision: revision })
|
|
900
|
+
});
|
|
901
|
+
audienceDraft.schema;
|
|
902
|
+
var managedExperimentBase = { expectedRevision: integer2(1), idempotencyKey: operationKey };
|
|
903
|
+
var experimentCreateField = field2(SIGNAL_EXPERIMENT_CREATE_JSON_SCHEMA, (v, p) => {
|
|
904
|
+
try {
|
|
905
|
+
return parseSignalExperimentCreate(v);
|
|
906
|
+
} catch (error) {
|
|
907
|
+
return fail2(p, error instanceof Error ? error.message : "invalid experiment");
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
var experimentUpdateField = field2(SIGNAL_EXPERIMENT_UPDATE_JSON_SCHEMA, (v, p) => {
|
|
911
|
+
try {
|
|
912
|
+
return parseSignalExperimentUpdate(v);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
return fail2(p, error instanceof Error ? error.message : "invalid experiment");
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
var managedExperimentCreate = object2({ ...managedExperimentBase, experiment: experimentCreateField });
|
|
918
|
+
var managedExperimentUpdate = object2({ ...managedExperimentBase, experiment: experimentUpdateField });
|
|
919
|
+
var managedExperimentLifecycle = object2(managedExperimentBase);
|
|
920
|
+
var managedExperimentComplete = object2({ ...managedExperimentBase, winnerVariant: text2(80) });
|
|
921
|
+
var managedExperimentLink = object2({ ...managedExperimentBase, experimentId: uuid2 });
|
|
922
|
+
managedExperimentCreate.schema;
|
|
923
|
+
managedExperimentUpdate.schema;
|
|
924
|
+
managedExperimentLifecycle.schema;
|
|
925
|
+
managedExperimentComplete.schema;
|
|
926
|
+
managedExperimentLink.schema;
|
|
927
|
+
var nativePageRevision = field2({ ...instant.schema, pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z(?![\\s\\S])" }, (v, p) => {
|
|
928
|
+
const result = instant.read(v, p);
|
|
929
|
+
if (new Date(result).toISOString() !== result) fail2(p, "requires canonical UTC milliseconds");
|
|
930
|
+
return result;
|
|
931
|
+
});
|
|
932
|
+
var positiveRevision = text2(128, /^[1-9][0-9]*(?![\s\S])/);
|
|
933
|
+
var friendlyName = field2({ type: "string", minLength: 0, maxLength: 200 }, (v, p) => typeof v === "string" && [...v].length <= 200 ? v : fail2(p, "invalid friendly name"));
|
|
934
|
+
var setupConsentPolicyShape = object2({ mode: literal2("basic"), requiredPurposes: uniqueList(purpose, 4, 3) });
|
|
935
|
+
var setupConsentPolicy = field2({ ...setupConsentPolicyShape.schema, allOf: [{ properties: { requiredPurposes: { allOf: ["ad_storage", "ad_user_data", "ad_personalization"].map((value) => ({ contains: { const: value } })) } } }] }, (input, p) => {
|
|
936
|
+
const v = setupConsentPolicyShape.read(input, p);
|
|
937
|
+
for (const required of ["ad_storage", "ad_user_data", "ad_personalization"]) if (!v.requiredPurposes.includes(required)) fail2(p, "all advertising purpose requirements are mandatory");
|
|
938
|
+
return v;
|
|
939
|
+
});
|
|
940
|
+
var measurementSave = refine2(object2({
|
|
941
|
+
schemaVersion: literal2(1),
|
|
942
|
+
site,
|
|
943
|
+
expectedCampaignRevision: integer2(1),
|
|
944
|
+
expectedConfigurationRevision: integer2(0),
|
|
945
|
+
idempotencyKey: operationKey,
|
|
946
|
+
enabled: bool,
|
|
947
|
+
consentPolicy: setupConsentPolicy,
|
|
948
|
+
pages: list2(object2({ pageId: uuid2, observedRevision: nativePageRevision, observedDigest: digest }), 500, 1)
|
|
949
|
+
}), (v, p) => unique(v.pages.map((page2) => page2.pageId), p + ".pages"));
|
|
950
|
+
var measurementPage = object2({ pageId: uuid2, label: friendlyName, path: observedPagePath, pageRevision: nativePageRevision, evidenceDigest: digest, resourceRuleId: uuid2, resourceRuleRevision: digest });
|
|
951
|
+
var measurementSavedFields = { configurationRevision: integer2(1), configurationDigest: digest, configuration: measurementConfig, saveOperationId: uuid2, pages: list2(measurementPage, 500, 1) };
|
|
952
|
+
object2(measurementSavedFields);
|
|
953
|
+
function checkMeasurementSaved(v, p) {
|
|
954
|
+
const config = parseResonanceMeasurementConfig(v.configuration);
|
|
955
|
+
if (config.projectId !== v.projectId || config.site !== v.site) fail2(p + ".configuration", "configuration scope mismatch");
|
|
956
|
+
positiveRevision.read(config.configRevision, p + ".configuration.configRevision");
|
|
957
|
+
positiveRevision.read(config.policyRevision, p + ".configuration.policyRevision");
|
|
958
|
+
setupConsentPolicy.read(config.consentPolicy, p + ".configuration.consentPolicy");
|
|
959
|
+
unique(v.pages.map((page2) => page2.pageId), p + ".pages");
|
|
960
|
+
if (v.pages.length !== config.resourceRules.length) fail2(p + ".pages", "must cover the exact native page rules");
|
|
961
|
+
for (const page2 of v.pages) {
|
|
962
|
+
const rule = config.resourceRules.find((rule2) => rule2.id === page2.resourceRuleId);
|
|
963
|
+
if (page2.resourceRuleId !== page2.pageId || page2.resourceRuleRevision !== page2.evidenceDigest || !rule || rule.revision !== page2.evidenceDigest || rule.path !== page2.path || rule.interestKey !== page2.pageId || rule.resource.type !== "page" || rule.resource.id !== page2.pageId || rule.resource.revision !== page2.pageRevision) fail2(p + ".pages", "native page/resource/rule version mismatch");
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
var measurementCurrent = refine2(union2(
|
|
967
|
+
object2({ schemaVersion: literal2(1), kind: literal2("unconfigured"), ...scope, configurationRevision: literal2(0) }),
|
|
968
|
+
object2({ schemaVersion: literal2(1), kind: literal2("configured"), ...scope, ...measurementSavedFields, reasonCodes: uniqueList(text2(200), 100) })
|
|
969
|
+
), (v, p) => {
|
|
970
|
+
if (v.kind === "configured") checkMeasurementSaved(v, p);
|
|
971
|
+
});
|
|
972
|
+
var measurementSnapshotFields = { schemaVersion: literal2(1), ...scope, campaignId: uuid2, operationId: uuid2, configurationRevision: integer2(1), configurationDigest: digest, configuration: measurementConfig, pages: list2(measurementPage, 500, 1) };
|
|
973
|
+
var measurementSnapshotShape = object2(measurementSnapshotFields);
|
|
974
|
+
function checkMeasurementSnapshot(v, p) {
|
|
975
|
+
checkMeasurementSaved({ ...v, saveOperationId: v.operationId }, p);
|
|
976
|
+
}
|
|
977
|
+
var measurementSnapshot = refine2(measurementSnapshotShape, checkMeasurementSnapshot);
|
|
978
|
+
var measurementSaveResponse = refine2(object2({ ...measurementSnapshotFields, replayed: bool }), checkMeasurementSnapshot);
|
|
979
|
+
object2({ projectId: uuid2, current: measurementCurrent, pages: list2(object2({ pageId: uuid2, label: friendlyName, path: observedPagePath, pageRevision: nativePageRevision, evidenceDigest: digest }), 500, 1), expiresAt: instant });
|
|
980
|
+
measurementSave.schema;
|
|
981
|
+
measurementCurrent.schema;
|
|
982
|
+
measurementSnapshot.schema;
|
|
983
|
+
measurementSaveResponse.schema;
|
|
984
|
+
var nativeAudienceRef = object2({ externalId: text2(160, /^[0-9]+(?![\s\S])/), revision: digest });
|
|
985
|
+
var metaSave = refine2(object2({
|
|
986
|
+
schemaVersion: literal2(1),
|
|
987
|
+
expectedCampaignRevision: integer2(1),
|
|
988
|
+
expectedConfigurationRevision: integer2(0),
|
|
989
|
+
idempotencyKey: operationKey,
|
|
990
|
+
audiences: list2(union2(object2({ audienceRuleId: uuid2, mode: literal2("existing"), nativeAudienceRef }), object2({ audienceRuleId: uuid2, mode: literal2("admitted_setup") })), 50, 1)
|
|
991
|
+
}), (v, p) => unique(v.audiences.map((a) => a.audienceRuleId), p + ".audiences"));
|
|
992
|
+
var metaResolvedAudience = object2({ audienceRuleRef: audience, name: friendlyName, resourceRuleRef: measurementRuleRef, excludedResourceRuleRefs: list2(measurementRuleRef, 50), selection: union2(
|
|
993
|
+
object2({ mode: literal2("existing"), nativeAudienceRef, nativeAudienceName: nullable2(friendlyName) }),
|
|
994
|
+
object2({ mode: literal2("admitted_setup"), setup: object2({ kind: literal2("meta_website_audience_v1"), localOutputKey: operationKey, specDigest: digest }) })
|
|
995
|
+
) });
|
|
996
|
+
var metaMaterialFields = {
|
|
997
|
+
schemaVersion: literal2(1),
|
|
998
|
+
projectId: uuid2,
|
|
999
|
+
campaignId: uuid2,
|
|
1000
|
+
channelId: uuid2,
|
|
1001
|
+
site,
|
|
1002
|
+
campaignRevision: integer2(1),
|
|
1003
|
+
reviewedDefinitionDigest: digest,
|
|
1004
|
+
configurationRevision: integer2(1),
|
|
1005
|
+
saveOperationId: uuid2,
|
|
1006
|
+
channel: meta,
|
|
1007
|
+
account: object2({ id: uuid2, externalId: text2(160), name: nullable2(friendlyName) }),
|
|
1008
|
+
datasetName: nullable2(friendlyName),
|
|
1009
|
+
pageName: nullable2(friendlyName),
|
|
1010
|
+
instagramName: nullable2(friendlyName),
|
|
1011
|
+
measurementConfigRef,
|
|
1012
|
+
matchingProfileRef,
|
|
1013
|
+
rules: list2(object2({ id: uuid2, revision, resource: interestResource, path: observedPagePath, label: friendlyName }), 500, 1),
|
|
1014
|
+
audiences: list2(metaResolvedAudience, 50, 1)
|
|
1015
|
+
};
|
|
1016
|
+
object2(metaMaterialFields);
|
|
1017
|
+
function checkMetaMaterial(v, p) {
|
|
1018
|
+
scoped(v, v.projectId, [v.site], p);
|
|
1019
|
+
if (v.channel.id !== v.channelId || v.channel.accountBindingId !== v.account.id || v.matchingProfileRef.id !== v.saveOperationId || v.matchingProfileRef.revision !== v.configurationRevision) fail2(p, "original channel/account/profile receipt mismatch");
|
|
1020
|
+
if (!v.channel.instagramRef && v.instagramName !== null) fail2(p + ".instagramName", "absent original Instagram identity requires null");
|
|
1021
|
+
unique(v.rules.map((rule) => rule.id), p + ".rules");
|
|
1022
|
+
unique(v.audiences.map((a) => a.audienceRuleRef.id), p + ".audiences");
|
|
1023
|
+
unique(v.channel.audienceRuleRefs.map((a) => a.id), p + ".channel.audienceRuleRefs");
|
|
1024
|
+
if (v.audiences.length !== v.channel.audienceRuleRefs.length) fail2(p + ".audiences", "must cover exactly the original logical audiences");
|
|
1025
|
+
checkMetaAudienceBands(v.channel, p + ".channel.audienceRuleRefs");
|
|
1026
|
+
for (const a of v.audiences) {
|
|
1027
|
+
if (!v.channel.audienceRuleRefs.some((ref) => canonical(ref) === canonical(a.audienceRuleRef))) fail2(p + ".audiences", "original logical audience version mismatch");
|
|
1028
|
+
unique(a.excludedResourceRuleRefs.map((ref) => ref.id), p + ".audiences.excludedResourceRuleRefs");
|
|
1029
|
+
unique(a.audienceRuleRef.exclusionRuleIds, p + ".audiences.audienceRuleRef.exclusionRuleIds");
|
|
1030
|
+
if (canonical([...a.audienceRuleRef.exclusionRuleIds].sort()) !== canonical(a.excludedResourceRuleRefs.map((ref) => ref.id).sort()) || a.excludedResourceRuleRefs.some((ref) => ref.id === a.resourceRuleRef.id)) fail2(p + ".audiences", "positive/excluded resource scope mismatch");
|
|
1031
|
+
for (const selected of [a.resourceRuleRef, ...a.excludedResourceRuleRefs]) if (!v.rules.some((rule) => rule.id === selected.id && rule.revision === selected.revision)) fail2(p + ".audiences", "resource rule version mismatch");
|
|
1032
|
+
if (a.selection.mode === "admitted_setup" && a.selection.setup.localOutputKey !== `audience:${a.audienceRuleRef.id}:${a.audienceRuleRef.revision}`) fail2(p + ".audiences.selection.setup.localOutputKey", "must retain the actual original SDK creator key");
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
var metaSnapshot = refine2(object2({ ...metaMaterialFields, configurationDigest: digest }), checkMetaMaterial);
|
|
1036
|
+
var parseResonanceMetaConfigurationSnapshot = (v) => metaSnapshot.read(v, "metaConfigurationSnapshot");
|
|
1037
|
+
refine2(object2({ ...metaMaterialFields, configurationDigest: optional2(digest) }), checkMetaMaterial);
|
|
1038
|
+
object2({ currentCampaignRevision: integer2(1), currentDefinitionDigest: digest, observedAt: instant, expiresAt: nullable2(instant), reasonCodes: uniqueList(text2(200), 100), pendingRequirements: uniqueList(text2(200), 100) });
|
|
1039
|
+
function metaReviewable(snapshot, metadata) {
|
|
1040
|
+
return metadata.currentCampaignRevision >= snapshot.campaignRevision && metadata.currentDefinitionDigest === snapshot.reviewedDefinitionDigest && metadata.reasonCodes.length === 0 && metadata.expiresAt !== null && Date.parse(metadata.expiresAt) > Date.parse(metadata.observedAt);
|
|
1041
|
+
}
|
|
1042
|
+
var metaView = refine2(union2(
|
|
1043
|
+
object2({ schemaVersion: literal2(1), kind: literal2("unconfigured"), projectId: uuid2, campaignId: uuid2, channelId: uuid2, configurationRevision: literal2(0) }),
|
|
1044
|
+
object2({
|
|
1045
|
+
schemaVersion: literal2(1),
|
|
1046
|
+
kind: literal2("configured"),
|
|
1047
|
+
projectId: uuid2,
|
|
1048
|
+
campaignId: uuid2,
|
|
1049
|
+
channelId: uuid2,
|
|
1050
|
+
configurationRevision: integer2(1),
|
|
1051
|
+
snapshot: metaSnapshot,
|
|
1052
|
+
currentCampaignRevision: integer2(1),
|
|
1053
|
+
currentDefinitionDigest: digest,
|
|
1054
|
+
observedAt: instant,
|
|
1055
|
+
expiresAt: nullable2(instant),
|
|
1056
|
+
readyForReview: bool,
|
|
1057
|
+
reasonCodes: uniqueList(text2(200), 100),
|
|
1058
|
+
pendingRequirements: uniqueList(text2(200), 100)
|
|
1059
|
+
})
|
|
1060
|
+
), (v, p) => {
|
|
1061
|
+
if (v.kind !== "configured") return;
|
|
1062
|
+
const s = parseResonanceMetaConfigurationSnapshot(v.snapshot);
|
|
1063
|
+
if (v.projectId !== s.projectId || v.campaignId !== s.campaignId || v.channelId !== s.channelId || v.configurationRevision !== s.configurationRevision) fail2(p, "snapshot identity or configuration CAS mismatch");
|
|
1064
|
+
if (v.readyForReview !== metaReviewable(s, v)) fail2(p + ".readyForReview", "must reflect current review prerequisites only");
|
|
1065
|
+
});
|
|
1066
|
+
var metaSaveResponse = object2({ schemaVersion: literal2(1), snapshot: metaSnapshot, replayed: bool });
|
|
1067
|
+
metaSave.schema;
|
|
1068
|
+
metaSnapshot.schema;
|
|
1069
|
+
metaView.schema;
|
|
1070
|
+
metaSaveResponse.schema;
|
|
1071
|
+
var feedSelection = refine2(object2({ channels: list2(object2({ channelId: uuid2, expectedConfigurationRevision: integer2(1), resourceRuleRefs: list2(measurementRuleRef, 500, 1) }), 100, 1) }), (v, p) => {
|
|
1072
|
+
unique(v.channels.map((c) => c.channelId), p + ".channels");
|
|
1073
|
+
for (const c of v.channels) unique(c.resourceRuleRefs.map((ref) => ref.id), p + ".resourceRuleRefs");
|
|
1074
|
+
});
|
|
1075
|
+
var mandateDraftRequest = object2({ expectedRevision: integer2(1), allowedOperations: mandateAllowedOperations, expiresAt: instant, websiteVisitFeedSelection: optional2(feedSelection), providerReporting: optional2(providerReportingSelection) });
|
|
1076
|
+
mandateDraftRequest.schema;
|
|
1077
|
+
var feedReview = object2({ schemaVersion: literal2(1), approval: mandateApproval, mandateRef, configurations: list2(metaSnapshot, 100), current: object2({ state: choice2("not_enabled", "eligible", "invalidated", "unavailable"), checkedAt: instant, expiresAt: nullable2(instant), reasonCodes: uniqueList(text2(200), 100) }) });
|
|
1078
|
+
feedReview.schema;
|
|
1079
|
+
var observationIdentity = { observationId: uuid2, channelId: uuid2, versionId: uuid2, accountBindingId: uuid2 };
|
|
1080
|
+
var publicObservationMetric = union2(
|
|
1081
|
+
object2({ ...metricBase, ...observationIdentity, source: choice2("provider", "first_party"), state: literal2("observed"), value: metricValue }),
|
|
1082
|
+
object2({ ...metricBase, ...observationIdentity, source: choice2("provider", "first_party"), state: choice2("unknown", "insufficient", "delayed", "unavailable"), value: literal2(null), reasonCode: text2(80) })
|
|
1083
|
+
);
|
|
1084
|
+
var observationDenominators = object2({ eligibleContacts: nullable2(decimal), fullyResolvedContacts: nullable2(decimal), qualifiedContacts: nullable2(decimal) });
|
|
1085
|
+
var observationSupportFields = { ...observationIdentity, currency, fetchedAt: instant, dataThrough: nullable2(instant), coverage: choice2("unknown", "partial", "complete"), reasonCode: nullable2(text2(80)), denominators: observationDenominators };
|
|
1086
|
+
var firstPartyObservationSupport = object2({ ...observationSupportFields, source: literal2("first_party") });
|
|
1087
|
+
var providerObservationSupport = object2({
|
|
1088
|
+
...observationSupportFields,
|
|
1089
|
+
source: literal2("provider"),
|
|
1090
|
+
firstReceivedAt: nullable2(instant),
|
|
1091
|
+
receivedAt: nullable2(instant),
|
|
1092
|
+
recordedAt: nullable2(instant),
|
|
1093
|
+
readClock: nullable2(providerReadClock),
|
|
1094
|
+
providerFinality: literal2("unknown"),
|
|
1095
|
+
correctionsPossible: literal2(true),
|
|
1096
|
+
coverageBasis: choice2("closed_query_snapshot", "not_established")
|
|
1097
|
+
});
|
|
1098
|
+
var observationSupport = union2(firstPartyObservationSupport, providerObservationSupport);
|
|
1099
|
+
var observationsPage = object2({ items: list2(publicObservationMetric, 500), nextCursor: nullable2(uuid2), observedAt: instant, support: list2(observationSupport, 500) });
|
|
1100
|
+
publicObservationMetric.schema;
|
|
1101
|
+
observationSupport.schema;
|
|
1102
|
+
observationsPage.schema;
|
|
1103
|
+
var META_WEBSITE_AUDIENCE_MAX_RETENTION_DAYS = 180;
|
|
1104
|
+
var META_AUDIENCE_RULE_LIMIT = 10;
|
|
1105
|
+
var usesPropertyModel = (c) => c.channelGroup !== void 0 || c.audienceRuleRefs.some((ref) => ref.minDaysSinceVisit !== void 0);
|
|
1106
|
+
function checkAudienceBand(ref, provider = "meta_ads", at = "audienceRuleRef") {
|
|
1107
|
+
const min = ref.minDaysSinceVisit;
|
|
1108
|
+
if (min !== void 0 && min >= ref.retentionDays) fail2(at + ".minDaysSinceVisit", "must be shorter than the retention window");
|
|
1109
|
+
if (provider === "google_ads") {
|
|
1110
|
+
if (min !== void 0) fail2(at + ".minDaysSinceVisit", "Google audiences have no recency band");
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (ref.retentionDays > META_WEBSITE_AUDIENCE_MAX_RETENTION_DAYS) fail2(at + ".retentionDays", `exceeds the ${META_WEBSITE_AUDIENCE_MAX_RETENTION_DAYS}-day Meta website audience maximum`);
|
|
1114
|
+
if (1 + ref.exclusionRuleIds.length + (min === void 0 ? 0 : 1) > META_AUDIENCE_RULE_LIMIT) fail2(at + ".exclusionRuleIds", `a Meta audience holds at most ${META_AUDIENCE_RULE_LIMIT} rules including the page and any recency band`);
|
|
1115
|
+
}
|
|
1116
|
+
function checkMetaAudienceBands(channel2, at) {
|
|
1117
|
+
if (!usesPropertyModel(channel2)) return;
|
|
1118
|
+
channel2.audienceRuleRefs.forEach((ref, index) => checkAudienceBand(ref, "meta_ads", `${at}[${index}]`));
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/resonance/controller.ts
|
|
1122
|
+
var payload = (body) => {
|
|
1123
|
+
if (!body || typeof body !== "object" || body.success !== true || !("data" in body)) throw new Error("measurement_response_unavailable");
|
|
1124
|
+
return body.data;
|
|
1125
|
+
};
|
|
1126
|
+
var ResonanceController = class {
|
|
1127
|
+
constructor(options) {
|
|
1128
|
+
this.options = options;
|
|
1129
|
+
this.config = null;
|
|
1130
|
+
this.proof = null;
|
|
1131
|
+
this.adapter = null;
|
|
1132
|
+
this.unsubscribe = null;
|
|
1133
|
+
this.configEpoch = 0;
|
|
1134
|
+
this.readEpoch = 0;
|
|
1135
|
+
this.deliveryEpoch = 0;
|
|
1136
|
+
this.stopped = false;
|
|
1137
|
+
this.busy = false;
|
|
1138
|
+
this.latest = null;
|
|
1139
|
+
this.latestEpoch = 0;
|
|
1140
|
+
this.completedEpoch = 0;
|
|
1141
|
+
this.path = "";
|
|
1142
|
+
this.sourceMarker = null;
|
|
1143
|
+
this.navigationSequence = 0;
|
|
1144
|
+
this.recordedNavigationKey = "";
|
|
1145
|
+
this.navigation = null;
|
|
1146
|
+
this.renderedFrame = null;
|
|
1147
|
+
this.acceptedExposure = null;
|
|
1148
|
+
this.retry = /* @__PURE__ */ new Map();
|
|
1149
|
+
this.expiryTimer = null;
|
|
1150
|
+
this.configExpiryTimer = null;
|
|
1151
|
+
this.refreshTimer = null;
|
|
1152
|
+
this.refreshing = false;
|
|
1153
|
+
this.renewing = false;
|
|
1154
|
+
this.renewalTimer = null;
|
|
1155
|
+
this.status = { state: "unknown", reason: null };
|
|
1156
|
+
}
|
|
1157
|
+
now() {
|
|
1158
|
+
return this.options.now?.() ?? Date.now();
|
|
1159
|
+
}
|
|
1160
|
+
changed() {
|
|
1161
|
+
this.options.onState?.();
|
|
1162
|
+
}
|
|
1163
|
+
/** One current-navigation slot, not a history cache. A true identity change
|
|
1164
|
+
* retires its receipt and retry work; late deliveries cannot rejoin it. */
|
|
1165
|
+
discardRenderedFrame() {
|
|
1166
|
+
const frame = this.renderedFrame;
|
|
1167
|
+
if (!frame) return;
|
|
1168
|
+
frame.active = false;
|
|
1169
|
+
this.renderedFrame = null;
|
|
1170
|
+
if (this.acceptedExposure?.frame === frame) this.acceptedExposure = null;
|
|
1171
|
+
for (const [id, pending] of this.retry) if (pending.frame === frame) this.retry.delete(id);
|
|
1172
|
+
}
|
|
1173
|
+
invalidate() {
|
|
1174
|
+
this.deliveryEpoch++;
|
|
1175
|
+
this.navigation = null;
|
|
1176
|
+
this.renderedFrame = null;
|
|
1177
|
+
this.acceptedExposure = null;
|
|
1178
|
+
this.retry.clear();
|
|
1179
|
+
updateMeasurementPolicy({ proof: null, preferencePending: !!this.config?.enabled });
|
|
1180
|
+
this.changed();
|
|
1181
|
+
}
|
|
1182
|
+
timerDelay(milliseconds) {
|
|
1183
|
+
return Math.max(0, Math.min(2147483647, milliseconds));
|
|
1184
|
+
}
|
|
1185
|
+
timers() {
|
|
1186
|
+
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
1187
|
+
if (this.renewalTimer) clearTimeout(this.renewalTimer);
|
|
1188
|
+
if (!this.proof) return;
|
|
1189
|
+
const proofRemaining = Date.parse(this.proof.expiresAt) - this.now();
|
|
1190
|
+
this.expiryTimer = setTimeout(() => {
|
|
1191
|
+
if (this.proof && Date.parse(this.proof.expiresAt) > this.now()) {
|
|
1192
|
+
this.timers();
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
this.invalidate();
|
|
1196
|
+
this.status.state = "blocked";
|
|
1197
|
+
this.status.reason = "proof_expired";
|
|
1198
|
+
this.changed();
|
|
1199
|
+
}, this.timerDelay(proofRemaining));
|
|
1200
|
+
this.renewalTimer = setTimeout(() => {
|
|
1201
|
+
void this.renew();
|
|
1202
|
+
}, this.timerDelay(Math.max(1e3, proofRemaining - Math.min(6e4, proofRemaining / 2))));
|
|
1203
|
+
this.scheduleConfigRefresh();
|
|
1204
|
+
}
|
|
1205
|
+
scheduleConfigRefresh(delay) {
|
|
1206
|
+
if (this.refreshTimer) clearTimeout(this.refreshTimer);
|
|
1207
|
+
if (!this.config || this.stopped) return;
|
|
1208
|
+
const remaining = Date.parse(this.config.expiresAt) - this.now();
|
|
1209
|
+
if (this.configExpiryTimer) clearTimeout(this.configExpiryTimer);
|
|
1210
|
+
this.configExpiryTimer = setTimeout(() => {
|
|
1211
|
+
if (this.config && Date.parse(this.config.expiresAt) > this.now()) {
|
|
1212
|
+
this.scheduleConfigRefresh();
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
this.invalidate();
|
|
1216
|
+
updateMeasurementPolicy({ collectionEpoch: measurementPolicySnapshot().collectionEpoch + 1 });
|
|
1217
|
+
this.status.state = "blocked";
|
|
1218
|
+
this.status.reason = "config_expired";
|
|
1219
|
+
this.changed();
|
|
1220
|
+
}, this.timerDelay(remaining));
|
|
1221
|
+
this.refreshTimer = setTimeout(() => {
|
|
1222
|
+
void this.refreshConfig();
|
|
1223
|
+
}, this.timerDelay(delay ?? Math.max(1e3, remaining - Math.min(6e4, remaining / 2))));
|
|
1224
|
+
}
|
|
1225
|
+
/** A safe public read refreshes policy freshness without re-prompting or changing consent. */
|
|
1226
|
+
async refreshConfig() {
|
|
1227
|
+
if (!this.config || this.stopped || this.refreshing) return;
|
|
1228
|
+
if (this.busy) {
|
|
1229
|
+
this.scheduleConfigRefresh(1e3);
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const previous = this.config;
|
|
1233
|
+
this.refreshing = true;
|
|
1234
|
+
try {
|
|
1235
|
+
const response = await this.options.transport.request(`/config?site=${encodeURIComponent(this.options.site)}`, { method: "GET", cache: "no-store" }, () => !this.stopped);
|
|
1236
|
+
if (this.stopped || previous !== this.config) return;
|
|
1237
|
+
if (response.status !== 200) throw new Error("config_refresh_unavailable");
|
|
1238
|
+
const next = parseResonanceMeasurementConfig(payload(response.body));
|
|
1239
|
+
if (next.projectId !== previous.projectId || next.site !== this.options.site || Date.parse(next.expiresAt) <= this.now()) throw new Error("config_scope_or_expiry");
|
|
1240
|
+
if (this.busy) {
|
|
1241
|
+
this.scheduleConfigRefresh(1e3);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
const sameRevision = next.configRevision === previous.configRevision && next.policyRevision === previous.policyRevision;
|
|
1245
|
+
if (sameRevision && JSON.stringify({ ...next, expiresAt: "" }) !== JSON.stringify({ ...previous, expiresAt: "" })) throw new Error("config_revision_inconsistent");
|
|
1246
|
+
this.config = next;
|
|
1247
|
+
if (sameRevision) {
|
|
1248
|
+
const resumable = this.latest?.valid && this.proof && Date.parse(this.proof.expiresAt) > this.now() && this.proof.receipts.every((r) => r.state === this.latest?.choices[r.purpose]);
|
|
1249
|
+
updateMeasurementPolicy({ policy: this.configPolicy(next), config: next, proof: resumable ? this.proof : null });
|
|
1250
|
+
if (this.proof) this.timers();
|
|
1251
|
+
else this.scheduleConfigRefresh();
|
|
1252
|
+
this.recordCurrentNavigation();
|
|
1253
|
+
} else {
|
|
1254
|
+
this.invalidate();
|
|
1255
|
+
this.acceptedExposure = null;
|
|
1256
|
+
this.proof = null;
|
|
1257
|
+
this.latest = null;
|
|
1258
|
+
this.latestEpoch++;
|
|
1259
|
+
this.completedEpoch = this.latestEpoch;
|
|
1260
|
+
updateMeasurementPolicy({
|
|
1261
|
+
policy: this.configPolicy(next),
|
|
1262
|
+
config: next,
|
|
1263
|
+
identityScope: `${next.projectId}:${next.site}:${next.configRevision}`,
|
|
1264
|
+
proof: null,
|
|
1265
|
+
choices: unknownChoices(),
|
|
1266
|
+
cmpValid: false,
|
|
1267
|
+
preferencePending: !!this.adapter,
|
|
1268
|
+
analyticsRequired: next.consentPolicy.requiredPurposes.includes("analytics_storage") || !!this.adapter?.requiredPurposes.includes("analytics_storage")
|
|
1269
|
+
});
|
|
1270
|
+
this.status.state = "waiting_for_consent";
|
|
1271
|
+
this.status.reason = null;
|
|
1272
|
+
this.scheduleConfigRefresh();
|
|
1273
|
+
await this.readConsent();
|
|
1274
|
+
}
|
|
1275
|
+
this.changed();
|
|
1276
|
+
} catch {
|
|
1277
|
+
if (this.stopped || previous !== this.config) return;
|
|
1278
|
+
if (this.configPolicy(previous) !== "legacy_unconfigured") {
|
|
1279
|
+
this.invalidate();
|
|
1280
|
+
updateMeasurementPolicy({ policy: "resolving" });
|
|
1281
|
+
this.status.state = "blocked";
|
|
1282
|
+
}
|
|
1283
|
+
this.status.reason = "config_refresh_unavailable";
|
|
1284
|
+
this.changed();
|
|
1285
|
+
this.scheduleConfigRefresh(1e4);
|
|
1286
|
+
} finally {
|
|
1287
|
+
this.refreshing = false;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
async start() {
|
|
1291
|
+
const epoch = ++this.configEpoch;
|
|
1292
|
+
this.proof = null;
|
|
1293
|
+
this.invalidate();
|
|
1294
|
+
updateMeasurementPolicy({ policy: "resolving", config: null, proof: null, choices: unknownChoices(), cmpValid: false, preferencePending: !!this.adapter });
|
|
1295
|
+
try {
|
|
1296
|
+
const result = await this.options.transport.request(`/config?site=${encodeURIComponent(this.options.site)}`, { method: "GET", cache: "no-store" });
|
|
1297
|
+
if (this.stopped || epoch !== this.configEpoch) return;
|
|
1298
|
+
if (result.status === 404) {
|
|
1299
|
+
this.status.state = "legacy_unconfigured";
|
|
1300
|
+
updateMeasurementPolicy({ policy: "legacy_unconfigured", config: null, analyticsRequired: this.adapter?.requiredPurposes.includes("analytics_storage") ?? false });
|
|
1301
|
+
await this.readConsent();
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
if (result.status !== 200) throw new Error("config_unavailable");
|
|
1305
|
+
const config = parseResonanceMeasurementConfig(payload(result.body));
|
|
1306
|
+
if (config.site !== this.options.site || Date.parse(config.expiresAt) <= this.now()) throw new Error("config_scope_or_expiry");
|
|
1307
|
+
this.config = config;
|
|
1308
|
+
updateMeasurementPolicy({ policy: this.configPolicy(config), config, identityScope: `${config.projectId}:${config.site}:${config.configRevision}`, preferencePending: config.enabled && !!this.adapter, analyticsRequired: config.consentPolicy.requiredPurposes.includes("analytics_storage") || !!this.adapter?.requiredPurposes.includes("analytics_storage") });
|
|
1309
|
+
this.status.state = config.enabled ? "waiting_for_consent" : "blocked";
|
|
1310
|
+
this.status.reason = config.enabled ? null : "measurement_disabled";
|
|
1311
|
+
this.scheduleConfigRefresh();
|
|
1312
|
+
await this.readConsent();
|
|
1313
|
+
} catch {
|
|
1314
|
+
if (this.stopped || epoch !== this.configEpoch) return;
|
|
1315
|
+
this.status.state = "blocked";
|
|
1316
|
+
this.status.reason = "config_unavailable";
|
|
1317
|
+
updateMeasurementPolicy({ policy: "resolving", proof: null });
|
|
1318
|
+
}
|
|
1319
|
+
this.changed();
|
|
1320
|
+
}
|
|
1321
|
+
setAdapter(adapter) {
|
|
1322
|
+
this.unsubscribe?.();
|
|
1323
|
+
this.unsubscribe = null;
|
|
1324
|
+
this.adapter = adapter;
|
|
1325
|
+
this.invalidate();
|
|
1326
|
+
updateMeasurementPolicy({ analyticsRequired: !!adapter?.requiredPurposes.includes("analytics_storage") || !!this.config?.consentPolicy.requiredPurposes.includes("analytics_storage"), choices: unknownChoices(), cmpValid: false, preferencePending: !!adapter });
|
|
1327
|
+
if (adapter) this.unsubscribe = adapter.subscribe(() => {
|
|
1328
|
+
this.observe({ valid: false, choices: unknownChoices(), source: "cmp", observedAt: new Date(this.now()).toISOString() });
|
|
1329
|
+
void this.readConsent();
|
|
1330
|
+
});
|
|
1331
|
+
void this.readConsent();
|
|
1332
|
+
}
|
|
1333
|
+
consentContext() {
|
|
1334
|
+
return {
|
|
1335
|
+
projectId: this.config?.projectId ?? null,
|
|
1336
|
+
site: this.options.site,
|
|
1337
|
+
configRevision: this.config?.configRevision ?? null,
|
|
1338
|
+
policyRevision: this.config?.policyRevision ?? null,
|
|
1339
|
+
requiredPurposes: this.config?.consentPolicy.requiredPurposes ?? this.adapter?.requiredPurposes ?? []
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
configPolicy(config) {
|
|
1343
|
+
return !config.enabled && config.configRevision === "not_configured" && config.policyRevision === "not_configured" && !config.destinations.length && !config.resourceRules.length ? "legacy_unconfigured" : "configured";
|
|
1344
|
+
}
|
|
1345
|
+
async readConsent() {
|
|
1346
|
+
if (!this.adapter || this.stopped) return;
|
|
1347
|
+
const adapter = this.adapter;
|
|
1348
|
+
const readEpoch = ++this.readEpoch;
|
|
1349
|
+
try {
|
|
1350
|
+
const observation = await adapter.read(this.consentContext());
|
|
1351
|
+
if (this.stopped || this.adapter !== adapter || readEpoch !== this.readEpoch) return;
|
|
1352
|
+
this.observe(observation);
|
|
1353
|
+
} catch {
|
|
1354
|
+
if (readEpoch !== this.readEpoch) return;
|
|
1355
|
+
this.observe({ valid: false, choices: unknownChoices(), source: "cmp", observedAt: new Date(this.now()).toISOString() });
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
/** Maintained CMP observations may resume valid consent; this never prompts or infers a grant. */
|
|
1359
|
+
observe(observation) {
|
|
1360
|
+
if (this.stopped) return;
|
|
1361
|
+
if (this.latest?.valid === observation.valid && MEASUREMENT_PURPOSES.every((p) => this.latest?.choices[p] === observation.choices[p]) && (this.busy || this.proof)) return;
|
|
1362
|
+
if (observation.valid && !this.busy && this.proof && this.config && this.proof.receipts.every((r) => r.state === observation.choices[r.purpose])) {
|
|
1363
|
+
this.latest = observation;
|
|
1364
|
+
this.latestEpoch++;
|
|
1365
|
+
this.completedEpoch = this.latestEpoch;
|
|
1366
|
+
const expired = Date.parse(this.proof.expiresAt) <= this.now();
|
|
1367
|
+
updateMeasurementPolicy({ choices: { ...observation.choices }, cmpValid: true, preferencePending: expired, proof: expired ? null : this.proof });
|
|
1368
|
+
if (expired) {
|
|
1369
|
+
void this.renew();
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
this.status.state = "ready";
|
|
1373
|
+
this.status.reason = null;
|
|
1374
|
+
this.timers();
|
|
1375
|
+
this.changed();
|
|
1376
|
+
this.recordCurrentNavigation();
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (observation.valid) this.acceptedExposure = null;
|
|
1380
|
+
this.latest = observation;
|
|
1381
|
+
this.latestEpoch++;
|
|
1382
|
+
this.invalidate();
|
|
1383
|
+
updateMeasurementPolicy({ choices: observation.valid ? { ...observation.choices } : unknownChoices(), cmpValid: observation.valid, preferencePending: !observation.valid || !!this.config?.enabled });
|
|
1384
|
+
this.status.state = this.config ? this.config.enabled ? "waiting_for_consent" : "blocked" : "unknown";
|
|
1385
|
+
this.status.reason = this.config && !this.config.enabled ? "measurement_disabled" : null;
|
|
1386
|
+
if (observation.valid && this.config?.enabled) void this.captureLatest();
|
|
1387
|
+
this.changed();
|
|
1388
|
+
}
|
|
1389
|
+
async captureLatest() {
|
|
1390
|
+
if (this.busy || this.stopped || !this.config || !this.latest?.valid) return;
|
|
1391
|
+
this.busy = true;
|
|
1392
|
+
let processingEpoch = this.latestEpoch;
|
|
1393
|
+
try {
|
|
1394
|
+
while (!this.stopped && this.latest?.valid && this.completedEpoch < this.latestEpoch) {
|
|
1395
|
+
const observation = this.latest, epoch = this.latestEpoch, config = this.config;
|
|
1396
|
+
processingEpoch = epoch;
|
|
1397
|
+
const prior = this.proof;
|
|
1398
|
+
this.status.state = "pending";
|
|
1399
|
+
this.changed();
|
|
1400
|
+
const body = parseResonanceConsentUpdate({
|
|
1401
|
+
schemaVersion: 1,
|
|
1402
|
+
projectId: config.projectId,
|
|
1403
|
+
site: config.site,
|
|
1404
|
+
configRevision: config.configRevision,
|
|
1405
|
+
policyRevision: config.policyRevision,
|
|
1406
|
+
expectedConsentRevision: prior?.currentConsentRevision ?? 0,
|
|
1407
|
+
occurredAt: observation.observedAt,
|
|
1408
|
+
idempotencyKey: uuid(),
|
|
1409
|
+
source: observation.source,
|
|
1410
|
+
purposes: MEASUREMENT_PURPOSES.map((purpose2) => ({ purpose: purpose2, state: observation.choices[purpose2] }))
|
|
1411
|
+
});
|
|
1412
|
+
const result = await this.options.transport.request("/consent", { method: "POST", cache: "no-store", headers: { "Content-Type": "application/json", ...prior ? { [RESONANCE_SUBJECT_HEADER]: prior.subjectProof } : {} }, body: JSON.stringify(body) }, () => !this.stopped && epoch === this.latestEpoch && config === this.config);
|
|
1413
|
+
if (this.stopped) return;
|
|
1414
|
+
if (result.status !== 200) throw new Error("consent_not_accepted");
|
|
1415
|
+
const accepted = parseResonanceConsentResponse(payload(result.body));
|
|
1416
|
+
this.verifyProofScope(accepted, config);
|
|
1417
|
+
if (prior && (accepted.receipts[0].visitorNamespace !== prior.receipts[0].visitorNamespace || accepted.currentConsentRevision < prior.currentConsentRevision)) throw new Error("consent_subject_or_revision_changed");
|
|
1418
|
+
this.proof = accepted;
|
|
1419
|
+
this.completedEpoch = epoch;
|
|
1420
|
+
if (epoch !== this.latestEpoch) continue;
|
|
1421
|
+
for (const receipt2 of accepted.receipts) if (receipt2.state !== observation.choices[receipt2.purpose]) throw new Error("consent_state_mismatch");
|
|
1422
|
+
updateMeasurementPolicy({ proof: accepted, preferencePending: false });
|
|
1423
|
+
this.status.state = "ready";
|
|
1424
|
+
this.status.reason = null;
|
|
1425
|
+
this.timers();
|
|
1426
|
+
this.changed();
|
|
1427
|
+
this.recordCurrentNavigation();
|
|
1428
|
+
}
|
|
1429
|
+
} catch {
|
|
1430
|
+
if (this.stopped) return;
|
|
1431
|
+
this.completedEpoch = processingEpoch;
|
|
1432
|
+
this.status.state = "blocked";
|
|
1433
|
+
this.status.reason = "consent_unavailable";
|
|
1434
|
+
updateMeasurementPolicy({ proof: null });
|
|
1435
|
+
this.changed();
|
|
1436
|
+
} finally {
|
|
1437
|
+
this.busy = false;
|
|
1438
|
+
if (!this.stopped && this.latest?.valid && this.completedEpoch < this.latestEpoch) void this.captureLatest();
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
verifyProofScope(proof, config) {
|
|
1442
|
+
const first = proof.receipts[0];
|
|
1443
|
+
if (first.projectId !== config.projectId || first.site !== config.site || first.configRevision !== config.configRevision || first.policyRevision !== config.policyRevision || Date.parse(proof.expiresAt) <= this.now()) throw new Error("proof_scope_or_expiry");
|
|
1444
|
+
}
|
|
1445
|
+
async renew() {
|
|
1446
|
+
if (!this.proof || !this.config || !this.latest?.valid || this.stopped || this.busy || this.renewing) return;
|
|
1447
|
+
this.renewing = true;
|
|
1448
|
+
const previous = this.proof, config = this.config, epoch = this.latestEpoch;
|
|
1449
|
+
try {
|
|
1450
|
+
const current = await this.adapter?.read(this.consentContext());
|
|
1451
|
+
if (this.stopped || epoch !== this.latestEpoch || this.proof !== previous) return;
|
|
1452
|
+
if (!current?.valid || !MEASUREMENT_PURPOSES.every((p) => current.choices[p] === this.latest?.choices[p])) {
|
|
1453
|
+
if (current) this.observe(current);
|
|
1454
|
+
else this.invalidate();
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const body = parseResonanceSubjectRenewCommand({ schemaVersion: 1, projectId: config.projectId, site: config.site, configRevision: config.configRevision, policyRevision: config.policyRevision, expectedConsentRevision: previous.currentConsentRevision });
|
|
1458
|
+
const result = await this.options.transport.request("/consent/renew", { method: "POST", cache: "no-store", headers: { "Content-Type": "application/json", [RESONANCE_SUBJECT_HEADER]: previous.subjectProof }, body: JSON.stringify(body) }, () => !this.stopped && epoch === this.latestEpoch && previous === this.proof);
|
|
1459
|
+
if (this.stopped || epoch !== this.latestEpoch || this.proof !== previous) return;
|
|
1460
|
+
if (result.status !== 200) throw new Error("renewal_failed");
|
|
1461
|
+
const renewed = parseResonanceConsentResponse(payload(result.body));
|
|
1462
|
+
this.verifyProofScope(renewed, config);
|
|
1463
|
+
if (renewed.currentConsentRevision !== previous.currentConsentRevision || renewed.receipts.some((r) => {
|
|
1464
|
+
const old = previous.receipts.find((p) => p.purpose === r.purpose);
|
|
1465
|
+
return !old || r.visitorNamespace !== old.visitorNamespace || r.state !== old.state || r.receiptId !== old.receiptId;
|
|
1466
|
+
})) throw new Error("renewal_changed_consent_or_subject");
|
|
1467
|
+
this.proof = renewed;
|
|
1468
|
+
updateMeasurementPolicy({ proof: renewed, preferencePending: false });
|
|
1469
|
+
this.status.state = "ready";
|
|
1470
|
+
this.status.reason = null;
|
|
1471
|
+
this.timers();
|
|
1472
|
+
this.changed();
|
|
1473
|
+
this.recordCurrentNavigation();
|
|
1474
|
+
} catch {
|
|
1475
|
+
if (this.stopped || epoch !== this.latestEpoch || this.proof !== previous) return;
|
|
1476
|
+
this.invalidate();
|
|
1477
|
+
this.status.state = "blocked";
|
|
1478
|
+
this.status.reason = "renewal_unavailable";
|
|
1479
|
+
this.changed();
|
|
1480
|
+
} finally {
|
|
1481
|
+
this.renewing = false;
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
/** Connectivity recovery renews existing proof; it never replays a failed grant. */
|
|
1485
|
+
async recover() {
|
|
1486
|
+
if (this.stopped) return;
|
|
1487
|
+
if (this.config) await this.refreshConfig();
|
|
1488
|
+
else await this.start();
|
|
1489
|
+
await this.renew();
|
|
1490
|
+
this.retryPending();
|
|
1491
|
+
}
|
|
1492
|
+
navigate(path2, sourceMarker2 = null) {
|
|
1493
|
+
let marker = null;
|
|
1494
|
+
if (sourceMarker2) {
|
|
1495
|
+
try {
|
|
1496
|
+
marker = parseResonanceSourceMarker(sourceMarker2);
|
|
1497
|
+
} catch {
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (path2 === this.path && marker === this.sourceMarker) return;
|
|
1501
|
+
this.sourceMarker = marker;
|
|
1502
|
+
this.navigationSequence++;
|
|
1503
|
+
this.path = path2;
|
|
1504
|
+
this.navigation = null;
|
|
1505
|
+
this.discardRenderedFrame();
|
|
1506
|
+
this.acceptedExposure = null;
|
|
1507
|
+
this.recordCurrentNavigation();
|
|
1508
|
+
}
|
|
1509
|
+
recordCurrentNavigation() {
|
|
1510
|
+
if (!this.config || !this.path || !advertisingPurposesPermitted(this.now())) return;
|
|
1511
|
+
const navigationKey = `${this.navigationSequence}:${this.proof?.currentConsentRevision}`;
|
|
1512
|
+
if (navigationKey === this.recordedNavigationKey) return;
|
|
1513
|
+
this.recordedNavigationKey = navigationKey;
|
|
1514
|
+
this.navigation = { path: this.path, id: uuid(), epoch: this.deliveryEpoch };
|
|
1515
|
+
const navigation = this.navigation;
|
|
1516
|
+
const rule = this.config.resourceRules.find((r) => r.path === this.path);
|
|
1517
|
+
const pages = this.config.resourceRules.filter((r) => r.path === this.path && r.resource.type === "page");
|
|
1518
|
+
void this.emit("page_view", pages.length === 1 ? pages[0].resource : null);
|
|
1519
|
+
if (rule && navigation === this.navigation) void this.emit("content_interest", rule.resource);
|
|
1520
|
+
}
|
|
1521
|
+
async exposure(experiment2) {
|
|
1522
|
+
await this.submitExposure(experiment2);
|
|
1523
|
+
}
|
|
1524
|
+
/** Internal rendered occurrence seam; retry queues retain the resulting immutable event and ID. */
|
|
1525
|
+
renderedExposure(experiment2, context) {
|
|
1526
|
+
if (!this.exposurePermitted(experiment2) || context && (!context.pagePath || context.pagePath !== this.path)) return null;
|
|
1527
|
+
if (!this.navigation) this.navigation = { path: this.path, id: uuid(), epoch: this.deliveryEpoch };
|
|
1528
|
+
const previous = this.renderedFrame;
|
|
1529
|
+
if (previous && previous.navigation === this.navigation && previous.context?.pagePath === context?.pagePath && previous.context?.pageVersionId === context?.pageVersionId && previous.input.projectId === experiment2.projectId && previous.input.site === experiment2.site && previous.input.experimentId === experiment2.experimentId && previous.input.experimentRevision === experiment2.experimentRevision && previous.input.assignmentId === experiment2.assignmentId && previous.input.variantKey === experiment2.variantKey) {
|
|
1530
|
+
previous.input = experiment2;
|
|
1531
|
+
previous.active = true;
|
|
1532
|
+
return previous.pending ?? Promise.resolve();
|
|
1533
|
+
}
|
|
1534
|
+
const frame = { input: experiment2, context: context ? { ...context } : void 0, navigation: this.navigation, active: true, pending: null };
|
|
1535
|
+
this.discardRenderedFrame();
|
|
1536
|
+
this.renderedFrame = frame;
|
|
1537
|
+
this.acceptedExposure = null;
|
|
1538
|
+
const pending = this.submitExposure(experiment2, frame);
|
|
1539
|
+
frame.pending = pending;
|
|
1540
|
+
if (pending) void pending.then(() => {
|
|
1541
|
+
frame.pending = null;
|
|
1542
|
+
}, () => {
|
|
1543
|
+
frame.pending = null;
|
|
1544
|
+
});
|
|
1545
|
+
return pending;
|
|
1546
|
+
}
|
|
1547
|
+
/** Close form attachment immediately, but retain this single bounded frame
|
|
1548
|
+
* while the current assignment is revalidated. An exact native-identity rejoin
|
|
1549
|
+
* keeps its original occurrence/receipt even after an asynchronous fallback.
|
|
1550
|
+
* Navigation, replacement, consent/scope invalidation and stop retire it. */
|
|
1551
|
+
releaseRenderedExposure(experiment2) {
|
|
1552
|
+
const frame = this.renderedFrame;
|
|
1553
|
+
if (!frame || frame.input !== experiment2) return;
|
|
1554
|
+
frame.active = false;
|
|
1555
|
+
}
|
|
1556
|
+
exposurePermitted(experiment2) {
|
|
1557
|
+
return !this.stopped && !!this.proof && !!this.config && experimentMeasurementPermitted(this.now()) && experiment2.projectId === this.config.projectId && experiment2.site === this.config.site;
|
|
1558
|
+
}
|
|
1559
|
+
submitExposure(experiment2, frame) {
|
|
1560
|
+
if (!this.exposurePermitted(experiment2)) return null;
|
|
1561
|
+
if (!this.navigation) this.navigation = { path: this.path, id: uuid(), epoch: this.deliveryEpoch };
|
|
1562
|
+
const resolved = "exposureId" in experiment2 ? experiment2 : { ...experiment2, exposureId: uuid() };
|
|
1563
|
+
return this.emit("experiment_exposure", null, resolved, frame);
|
|
1564
|
+
}
|
|
1565
|
+
async emit(kind, resourceRef, experiment2 = null, frame) {
|
|
1566
|
+
if (!this.config || !this.proof || !this.navigation) return;
|
|
1567
|
+
const navigation = this.navigation, proof = this.proof, config = this.config, epoch = this.deliveryEpoch;
|
|
1568
|
+
let clickIdentifiers2;
|
|
1569
|
+
if (kind === "page_view" && advertisingPurposesPermitted(this.now())) {
|
|
1570
|
+
try {
|
|
1571
|
+
const observed = this.options.readClickIdentifiers?.(this.navigation.path);
|
|
1572
|
+
if (observed != null) clickIdentifiers2 = parseResonanceClickIdentifiers(observed);
|
|
1573
|
+
} catch {
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
if (navigation !== this.navigation || proof !== this.proof || epoch !== this.deliveryEpoch) return;
|
|
1577
|
+
let event = parseResonanceMeasurementOccurrence({
|
|
1578
|
+
schemaVersion: 1,
|
|
1579
|
+
projectId: config.projectId,
|
|
1580
|
+
site: config.site,
|
|
1581
|
+
configRevision: config.configRevision,
|
|
1582
|
+
eventId: uuid(),
|
|
1583
|
+
navigationId: navigation.id,
|
|
1584
|
+
occurredAt: new Date(this.now()).toISOString(),
|
|
1585
|
+
kind,
|
|
1586
|
+
resourceRef,
|
|
1587
|
+
...clickIdentifiers2 ? { clickIdentifiers: clickIdentifiers2 } : {},
|
|
1588
|
+
...frame?.context ? { pagePath: frame.context.pagePath } : {},
|
|
1589
|
+
...this.sourceMarker && advertisingPurposesPermitted(this.now()) ? { sourceMarker: this.sourceMarker } : {},
|
|
1590
|
+
campaignPlanId: null,
|
|
1591
|
+
experiment: experiment2,
|
|
1592
|
+
consentReceiptRefs: this.proof.receipts.map((r) => ({ purpose: r.purpose, receiptId: r.receiptId, consentRevision: r.consentRevision }))
|
|
1593
|
+
});
|
|
1594
|
+
if (kind === "page_view" && resourceRef?.type === "page" && advertisingPurposesPermitted(this.now())) {
|
|
1595
|
+
try {
|
|
1596
|
+
event = parseResonanceMeasurementOccurrence({ ...event, pagePath: navigation.path });
|
|
1597
|
+
} catch {
|
|
1598
|
+
}
|
|
1599
|
+
if (event.pagePath !== void 0) {
|
|
1600
|
+
try {
|
|
1601
|
+
const observed = this.options.readMetaCookieObservation?.(navigation.path);
|
|
1602
|
+
if (observed != null) event = parseResonanceMeasurementOccurrence({ ...event, metaCookieObservation: observed });
|
|
1603
|
+
} catch {
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
if (navigation !== this.navigation || proof !== this.proof || config !== this.config || epoch !== this.deliveryEpoch) return;
|
|
1608
|
+
await this.deliver(event, epoch, frame);
|
|
1609
|
+
}
|
|
1610
|
+
async deliver(event, epoch, frame) {
|
|
1611
|
+
const allowed = () => !this.stopped && epoch === this.deliveryEpoch && (!frame || this.renderedFrame === frame && frame.navigation === this.navigation) && (event.kind === "experiment_exposure" ? experimentMeasurementPermitted(this.now()) : advertisingPurposesPermitted(this.now()));
|
|
1612
|
+
if (!allowed() || !this.proof) return;
|
|
1613
|
+
try {
|
|
1614
|
+
const result = await this.options.transport.request("/occurrences", { method: "POST", cache: "no-store", headers: { "Content-Type": "application/json", [RESONANCE_SUBJECT_HEADER]: this.proof.subjectProof }, body: JSON.stringify(event) }, allowed);
|
|
1615
|
+
if (!allowed()) return;
|
|
1616
|
+
if (result.status !== 200) throw new Error("occurrence_not_accepted");
|
|
1617
|
+
const receipt2 = parseResonanceOccurrenceResponse(payload(result.body));
|
|
1618
|
+
if (receipt2.eventId !== event.eventId) throw new Error("occurrence_identity_mismatch");
|
|
1619
|
+
this.retry.delete(event.eventId);
|
|
1620
|
+
if (event.kind === "experiment_exposure" && receipt2.exposureId && this.proof && this.navigation?.id === event.navigationId && (!frame || this.renderedFrame === frame && frame.navigation === this.navigation)) {
|
|
1621
|
+
this.acceptedExposure = {
|
|
1622
|
+
id: receipt2.exposureId,
|
|
1623
|
+
namespace: this.proof.receipts[0].visitorNamespace,
|
|
1624
|
+
consentRevision: this.proof.currentConsentRevision,
|
|
1625
|
+
configRevision: this.config.configRevision,
|
|
1626
|
+
policyRevision: this.config.policyRevision,
|
|
1627
|
+
navigation: this.navigation,
|
|
1628
|
+
frame: frame ?? null
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
if (!receipt2.replayed) this.options.onOccurrence?.(event);
|
|
1632
|
+
} catch {
|
|
1633
|
+
if (allowed() && this.retry.size < 50) this.retry.set(event.eventId, { event, epoch, frame });
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
/** Explicit network recovery only; identical occurrence ID, never old preference/scope epochs. */
|
|
1637
|
+
retryPending() {
|
|
1638
|
+
for (const { event, epoch, frame } of this.retry.values()) void this.deliver(event, epoch, frame);
|
|
1639
|
+
}
|
|
1640
|
+
/** Optional server receipt reference, never a claim that a form converted. */
|
|
1641
|
+
formExposureId() {
|
|
1642
|
+
const exposure2 = this.acceptedExposure, proof = this.proof, config = this.config;
|
|
1643
|
+
return exposure2 && proof && config && exposure2.navigation === this.navigation && (!exposure2.frame || exposure2.frame === this.renderedFrame && exposure2.frame.active) && exposure2.namespace === proof.receipts[0].visitorNamespace && exposure2.consentRevision === proof.currentConsentRevision && exposure2.configRevision === config.configRevision && exposure2.policyRevision === config.policyRevision ? exposure2.id : null;
|
|
1644
|
+
}
|
|
1645
|
+
stop() {
|
|
1646
|
+
if (this.stopped) return;
|
|
1647
|
+
this.stopped = true;
|
|
1648
|
+
this.configEpoch++;
|
|
1649
|
+
this.readEpoch++;
|
|
1650
|
+
this.unsubscribe?.();
|
|
1651
|
+
this.unsubscribe = null;
|
|
1652
|
+
if (this.expiryTimer) clearTimeout(this.expiryTimer);
|
|
1653
|
+
if (this.renewalTimer) clearTimeout(this.renewalTimer);
|
|
1654
|
+
if (this.refreshTimer) clearTimeout(this.refreshTimer);
|
|
1655
|
+
if (this.configExpiryTimer) clearTimeout(this.configExpiryTimer);
|
|
1656
|
+
this.invalidate();
|
|
1657
|
+
this.acceptedExposure = null;
|
|
1658
|
+
this.proof = null;
|
|
1659
|
+
updateMeasurementPolicy({ config: null, cmpValid: false, proof: null, choices: unknownChoices(), preferencePending: true });
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
|
|
1663
|
+
// src/resonance/meta-pixel.ts
|
|
1664
|
+
var META_BROWSER_CONTROLS_IMPLEMENTED = [];
|
|
1665
|
+
var META_SDK_URL = "https://connect.facebook.net/en_US/fbevents.js";
|
|
1666
|
+
var ALLOWED_QUERY_KEYS = /* @__PURE__ */ new Set(["fbclid", "sonor_source", "utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]);
|
|
1667
|
+
var QUERY_VALUE = /^[^\s\u0000-\u001f\u007f-\u009f]{1,256}$/;
|
|
1668
|
+
var SONOR_APPLICATION_HOSTS = /* @__PURE__ */ new Set(["app.sonor.io", "api.sonor.io", "signal.sonor.io"]);
|
|
1669
|
+
var DENIED_PATH_ROOTS = ["/admin", "/account", "/dashboard", "/login", "/signin", "/signup", "/checkout", "/cart", "/portal", "/api", "/preview"];
|
|
1670
|
+
var COOKIES = ["_fbp", "_fbc"];
|
|
1671
|
+
var WITNESS_MAX_AGE_MS = 48 * 36e5;
|
|
1672
|
+
function queryClean(search2) {
|
|
1673
|
+
if (!search2) return true;
|
|
1674
|
+
const entries = [...new URLSearchParams(search2).entries()];
|
|
1675
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1676
|
+
for (const [key, value] of entries) {
|
|
1677
|
+
if (!ALLOWED_QUERY_KEYS.has(key) || seen.has(key) || !QUERY_VALUE.test(value)) return false;
|
|
1678
|
+
seen.add(key);
|
|
1679
|
+
}
|
|
1680
|
+
return true;
|
|
1681
|
+
}
|
|
1682
|
+
function referrerClean(referrer) {
|
|
1683
|
+
if (!referrer) return true;
|
|
1684
|
+
let url;
|
|
1685
|
+
try {
|
|
1686
|
+
url = new URL(referrer);
|
|
1687
|
+
} catch {
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
if (url.hash || referrer.includes("#") || url.username || url.password) return false;
|
|
1691
|
+
if (url.hostname === window.location.hostname) return !pathDenied(url.pathname) && queryClean(url.search);
|
|
1692
|
+
return !url.search && !referrer.includes("?") && (url.pathname === "/" || url.pathname === "");
|
|
1693
|
+
}
|
|
1694
|
+
function pathDenied(path2) {
|
|
1695
|
+
const lower = path2.toLowerCase();
|
|
1696
|
+
return DENIED_PATH_ROOTS.some((root) => lower === root || lower.startsWith(root + "/"));
|
|
1697
|
+
}
|
|
1698
|
+
function decideMetaPage(config) {
|
|
1699
|
+
if (typeof window === "undefined" || typeof document === "undefined") return { ok: false, blocker: "meta_page_not_eligible" };
|
|
1700
|
+
const location = window.location;
|
|
1701
|
+
let framed = true;
|
|
1702
|
+
try {
|
|
1703
|
+
framed = isFramed();
|
|
1704
|
+
} catch {
|
|
1705
|
+
}
|
|
1706
|
+
if (framed || isPrivatePreview() || isLocalHostname(location.hostname) || SONOR_APPLICATION_HOSTS.has(location.hostname.toLowerCase()) || document.querySelector("[data-sk-authenticated]") || pathDenied(location.pathname)) return { ok: false, blocker: "meta_page_not_eligible" };
|
|
1707
|
+
const pages = config.resourceRules.filter((rule) => rule.path === location.pathname && rule.resource.type === "page");
|
|
1708
|
+
if (pages.length !== 1 || pages[0].metaEligible !== true) return { ok: false, blocker: "meta_page_not_eligible" };
|
|
1709
|
+
if (location.hash || location.href.includes("#") || !queryClean(location.search) || !referrerClean(document.referrer)) return { ok: false, blocker: "meta_url_not_clean" };
|
|
1710
|
+
return { ok: true };
|
|
1711
|
+
}
|
|
1712
|
+
function expireMetaCookies() {
|
|
1713
|
+
const host = window.location.hostname;
|
|
1714
|
+
if (!host || host.includes(":") || /^\d+(\.\d+){3}$/.test(host)) {
|
|
1715
|
+
for (const name of COOKIES) document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
const labels = host.split(".");
|
|
1719
|
+
const domains = ["", ...labels.map((_, index) => "." + labels.slice(index).join("."))];
|
|
1720
|
+
for (const name of COOKIES) for (const domain of domains) document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/${domain ? `; domain=${domain}` : ""}`;
|
|
1721
|
+
}
|
|
1722
|
+
var MetaPixelLoader = class {
|
|
1723
|
+
constructor(config, hooks) {
|
|
1724
|
+
this.config = config;
|
|
1725
|
+
this.hooks = hooks;
|
|
1726
|
+
this.generation = 0;
|
|
1727
|
+
this.owned = null;
|
|
1728
|
+
this.granted = false;
|
|
1729
|
+
this.cookiesExpired = false;
|
|
1730
|
+
}
|
|
1731
|
+
destinations() {
|
|
1732
|
+
return this.config.destinations.filter((d) => d.provider === "meta_ads");
|
|
1733
|
+
}
|
|
1734
|
+
/** Configuration-level facts, independent of consent and of the page. */
|
|
1735
|
+
inspect() {
|
|
1736
|
+
const destinations = this.destinations();
|
|
1737
|
+
const witness = destinations.length === 1 ? destinations[0].browserWitness : void 0;
|
|
1738
|
+
const implemented = this.hooks.implementedRevisions ?? META_BROWSER_CONTROLS_IMPLEMENTED;
|
|
1739
|
+
const age = witness ? Date.now() - Date.parse(witness.assertedAt) : Number.NaN;
|
|
1740
|
+
return {
|
|
1741
|
+
destinations: destinations.length,
|
|
1742
|
+
multiple: destinations.length > 1,
|
|
1743
|
+
witnessed: !!witness && implemented.includes(witness.controlsRevision) && witness.policyRevision === this.config.policyRevision && age >= -6e5 && age <= WITNESS_MAX_AGE_MS
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
/** Every SDK command re-checks the fence; a withdrawn runtime pushes nothing. */
|
|
1747
|
+
command(...args) {
|
|
1748
|
+
const fbq = this.owned?.fbq;
|
|
1749
|
+
if (!fbq || !this.hooks.allowed()) return false;
|
|
1750
|
+
try {
|
|
1751
|
+
fbq(...args);
|
|
1752
|
+
} catch {
|
|
1753
|
+
}
|
|
1754
|
+
return true;
|
|
1755
|
+
}
|
|
1756
|
+
blockers(decision) {
|
|
1757
|
+
this.hooks.setBlocker("meta_page_not_eligible", !decision.ok && decision.blocker === "meta_page_not_eligible");
|
|
1758
|
+
this.hooks.setBlocker("meta_url_not_clean", !decision.ok && decision.blocker === "meta_url_not_clean");
|
|
1759
|
+
}
|
|
1760
|
+
foreign(w, destination2) {
|
|
1761
|
+
if (w.fbq || w._fbq || document.querySelector('script[src*="connect.facebook.net"][src*="fbevents"]') || document.querySelector('img[src*="facebook.com/tr"]')) return true;
|
|
1762
|
+
for (const noscript of document.querySelectorAll("noscript")) if ((noscript.textContent ?? "").includes("facebook.com/tr") || noscript.innerHTML.includes("facebook.com/tr")) return true;
|
|
1763
|
+
const tagManager = !!w.google_tag_manager || !!document.querySelector('script[src*="googletagmanager.com/gtm.js"]') || w.dataLayer !== void 0 && w.dataLayer !== w.__SONOR_RESONANCE_GOOGLE_V1__?.data;
|
|
1764
|
+
return tagManager && destination2.tagManagerAttested !== true;
|
|
1765
|
+
}
|
|
1766
|
+
update() {
|
|
1767
|
+
const { destinations, multiple, witnessed } = this.inspect();
|
|
1768
|
+
if (typeof window === "undefined" || !destinations) return;
|
|
1769
|
+
if (multiple) {
|
|
1770
|
+
this.hooks.setBlocker("meta_multiple_destinations", true);
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
if (!witnessed) {
|
|
1774
|
+
this.withdraw();
|
|
1775
|
+
return;
|
|
1776
|
+
}
|
|
1777
|
+
if (!this.hooks.allowed()) {
|
|
1778
|
+
this.withdraw();
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
const decision = decideMetaPage(this.config);
|
|
1782
|
+
this.blockers(decision);
|
|
1783
|
+
if (!decision.ok) {
|
|
1784
|
+
this.pause();
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
const w = window, destination2 = this.destinations()[0];
|
|
1788
|
+
const record = w.__SONOR_RESONANCE_META_V1__;
|
|
1789
|
+
if (this.owned || record) return this.resume(w, destination2, record);
|
|
1790
|
+
if (this.foreign(w, destination2)) {
|
|
1791
|
+
this.hooks.setBlocker("meta_duplicate_installation", true);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
this.install(w, destination2);
|
|
1795
|
+
}
|
|
1796
|
+
/** Single-page navigation: re-evaluate; never re-initialize. */
|
|
1797
|
+
navigate() {
|
|
1798
|
+
this.update();
|
|
1799
|
+
}
|
|
1800
|
+
resume(w, destination2, record) {
|
|
1801
|
+
if (!record) return;
|
|
1802
|
+
if (this.owned && this.owned !== record) return;
|
|
1803
|
+
if (record.scope !== `${this.config.projectId}:${this.config.site}` || record.pixelId !== destination2.pixelId) {
|
|
1804
|
+
this.hooks.setBlocker("meta_scope_change_requires_reload", true);
|
|
1805
|
+
return;
|
|
1806
|
+
}
|
|
1807
|
+
if (w.fbq !== record.fbq) {
|
|
1808
|
+
this.hooks.setBlocker("meta_duplicate_installation", true);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
if (!this.owned && !record.loaded && !record.failed) {
|
|
1812
|
+
this.hooks.setBlocker("meta_duplicate_installation", true);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
this.owned = record;
|
|
1816
|
+
if (record.failed) {
|
|
1817
|
+
this.hooks.setBlocker("meta_load_failed", true);
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
if (record.loaded && record.consent === "revoked") this.grant();
|
|
1821
|
+
}
|
|
1822
|
+
install(w, destination2) {
|
|
1823
|
+
const generation = this.generation;
|
|
1824
|
+
const queue = [];
|
|
1825
|
+
const fbq = function(..._args) {
|
|
1826
|
+
if (fbq.callMethod) fbq.callMethod.apply(fbq, arguments);
|
|
1827
|
+
else fbq.queue.push(arguments);
|
|
1828
|
+
};
|
|
1829
|
+
fbq.push = fbq;
|
|
1830
|
+
fbq.loaded = true;
|
|
1831
|
+
fbq.version = "2.0";
|
|
1832
|
+
fbq.queue = queue;
|
|
1833
|
+
const script = document.createElement("script");
|
|
1834
|
+
this.owned = { scope: `${this.config.projectId}:${this.config.site}`, pixelId: destination2.pixelId, fbq, script, loaded: false, failed: false, consent: "revoked" };
|
|
1835
|
+
w.fbq = fbq;
|
|
1836
|
+
w._fbq = fbq;
|
|
1837
|
+
w.__SONOR_RESONANCE_META_V1__ = this.owned;
|
|
1838
|
+
if (!this.command("consent", "revoke") || !this.command("set", "autoConfig", false, destination2.pixelId) || !this.command("init", destination2.pixelId)) {
|
|
1839
|
+
this.discard(w);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
const owned2 = this.owned;
|
|
1843
|
+
script.async = true;
|
|
1844
|
+
script.src = META_SDK_URL;
|
|
1845
|
+
script.referrerPolicy = "no-referrer";
|
|
1846
|
+
script.onload = () => {
|
|
1847
|
+
owned2.loaded = true;
|
|
1848
|
+
this.afterLoad(generation);
|
|
1849
|
+
};
|
|
1850
|
+
script.onerror = () => {
|
|
1851
|
+
owned2.failed = true;
|
|
1852
|
+
this.hooks.setBlocker("meta_load_failed", true);
|
|
1853
|
+
};
|
|
1854
|
+
if (generation !== this.generation || !this.hooks.allowed()) {
|
|
1855
|
+
this.discard(w);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
document.head.appendChild(script);
|
|
1859
|
+
}
|
|
1860
|
+
/** The grant is issued live, after the SDK has executed: a grant queued ahead of the script never releases its lock. */
|
|
1861
|
+
afterLoad(generation) {
|
|
1862
|
+
if (generation !== this.generation || !this.hooks.allowed()) return;
|
|
1863
|
+
const decision = decideMetaPage(this.config);
|
|
1864
|
+
this.blockers(decision);
|
|
1865
|
+
if (decision.ok) this.grant();
|
|
1866
|
+
}
|
|
1867
|
+
grant() {
|
|
1868
|
+
if (!this.owned?.loaded || this.owned.consent === "granted") return;
|
|
1869
|
+
if (this.command("consent", "grant")) {
|
|
1870
|
+
this.owned.consent = "granted";
|
|
1871
|
+
this.granted = true;
|
|
1872
|
+
this.cookiesExpired = false;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
/** The page stopped being eligible or clean: pause the SDK without forgetting the installation. */
|
|
1876
|
+
pause() {
|
|
1877
|
+
const owned2 = this.owned;
|
|
1878
|
+
if (!owned2) return;
|
|
1879
|
+
if (!owned2.loaded) {
|
|
1880
|
+
this.discard(window);
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
if (owned2.consent === "granted") {
|
|
1884
|
+
try {
|
|
1885
|
+
owned2.fbq("consent", "revoke");
|
|
1886
|
+
} catch {
|
|
1887
|
+
}
|
|
1888
|
+
owned2.consent = "revoked";
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
/** Before the SDK has executed nothing needs retracting: remove everything so a late script finds no queue and does nothing. */
|
|
1892
|
+
discard(w) {
|
|
1893
|
+
const owned2 = this.owned;
|
|
1894
|
+
if (!owned2) return;
|
|
1895
|
+
owned2.script.onload = null;
|
|
1896
|
+
owned2.script.onerror = null;
|
|
1897
|
+
if (owned2.script.isConnected) owned2.script.remove();
|
|
1898
|
+
if (w.fbq === owned2.fbq) delete w.fbq;
|
|
1899
|
+
if (w._fbq === owned2.fbq) delete w._fbq;
|
|
1900
|
+
if (w.__SONOR_RESONANCE_META_V1__ === owned2) delete w.__SONOR_RESONANCE_META_V1__;
|
|
1901
|
+
this.owned = null;
|
|
1902
|
+
}
|
|
1903
|
+
withdraw() {
|
|
1904
|
+
this.generation++;
|
|
1905
|
+
const owned2 = this.owned;
|
|
1906
|
+
if (owned2) {
|
|
1907
|
+
if (!owned2.loaded) this.discard(window);
|
|
1908
|
+
else if (owned2.consent === "granted") {
|
|
1909
|
+
try {
|
|
1910
|
+
owned2.fbq("consent", "revoke");
|
|
1911
|
+
} catch {
|
|
1912
|
+
}
|
|
1913
|
+
owned2.consent = "revoked";
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
if ((this.granted || owned2?.loaded) && !this.cookiesExpired) {
|
|
1917
|
+
expireMetaCookies();
|
|
1918
|
+
this.cookiesExpired = true;
|
|
1919
|
+
}
|
|
1920
|
+
this.granted = false;
|
|
1921
|
+
}
|
|
1922
|
+
stop() {
|
|
1923
|
+
this.withdraw();
|
|
1924
|
+
}
|
|
1925
|
+
};
|
|
1926
|
+
|
|
1927
|
+
// src/resonance/providers.ts
|
|
1928
|
+
var denied = { analytics_storage: "denied", ad_storage: "denied", ad_user_data: "denied", ad_personalization: "denied" };
|
|
1929
|
+
var ResonanceProviderRuntime = class {
|
|
1930
|
+
constructor(config, onBlockers, options = {}) {
|
|
1931
|
+
this.config = config;
|
|
1932
|
+
this.onBlockers = onBlockers;
|
|
1933
|
+
this.google = null;
|
|
1934
|
+
this.script = null;
|
|
1935
|
+
this.configured = /* @__PURE__ */ new Set();
|
|
1936
|
+
this.pending = null;
|
|
1937
|
+
this.generation = 0;
|
|
1938
|
+
this.active = false;
|
|
1939
|
+
this.loaded = false;
|
|
1940
|
+
this.finishLoad = null;
|
|
1941
|
+
this.blockers = /* @__PURE__ */ new Set();
|
|
1942
|
+
this.meta = new MetaPixelLoader(config, { allowed: () => this.allowed(), setBlocker: (code, present) => this.setBlocker(code, present), implementedRevisions: options.metaControlsRevisions });
|
|
1943
|
+
const meta2 = this.meta.inspect();
|
|
1944
|
+
if (meta2.destinations && !meta2.witnessed) this.block("meta_browser_collection_unverified");
|
|
1945
|
+
if (meta2.multiple) this.block("meta_multiple_destinations");
|
|
1946
|
+
}
|
|
1947
|
+
block(code) {
|
|
1948
|
+
this.setBlocker(code, true);
|
|
1949
|
+
}
|
|
1950
|
+
setBlocker(code, present) {
|
|
1951
|
+
if (this.blockers.has(code) === present) return;
|
|
1952
|
+
if (present) this.blockers.add(code);
|
|
1953
|
+
else this.blockers.delete(code);
|
|
1954
|
+
this.onBlockers?.([...this.blockers]);
|
|
1955
|
+
}
|
|
1956
|
+
allowed() {
|
|
1957
|
+
const state = measurementPolicySnapshot();
|
|
1958
|
+
return this.active && advertisingPurposesPermitted() && state.config?.projectId === this.config.projectId && state.config.site === this.config.site && state.config.configRevision === this.config.configRevision;
|
|
1959
|
+
}
|
|
1960
|
+
update() {
|
|
1961
|
+
if (!advertisingPurposesPermitted()) {
|
|
1962
|
+
this.withdraw();
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
this.active = true;
|
|
1966
|
+
const meta2 = this.meta.inspect();
|
|
1967
|
+
this.setBlocker("meta_browser_collection_unverified", meta2.destinations > 0 && !meta2.witnessed);
|
|
1968
|
+
this.meta.update();
|
|
1969
|
+
if (!this.pending && !this.google && !this.blockers.has("google_duplicate_installation") && !this.blockers.has("google_scope_change_requires_reload") && !this.blockers.has("google_load_failed")) {
|
|
1970
|
+
this.pending = this.loadGoogle().finally(() => {
|
|
1971
|
+
this.pending = null;
|
|
1972
|
+
});
|
|
1973
|
+
} else if (this.google) {
|
|
1974
|
+
this.applyConsent();
|
|
1975
|
+
if (!this.loaded && this.script && !this.script.isConnected && this.allowed()) document.head.appendChild(this.script);
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
applyConsent() {
|
|
1979
|
+
if (!this.allowed() || !this.google) return;
|
|
1980
|
+
this.google("consent", "update", { ...measurementPolicySnapshot().choices });
|
|
1981
|
+
}
|
|
1982
|
+
async loadGoogle() {
|
|
1983
|
+
const destinations = this.config.destinations.filter((d) => d.provider === "google_ads");
|
|
1984
|
+
if (!destinations.length || !this.allowed() || typeof window === "undefined") return;
|
|
1985
|
+
const w = window;
|
|
1986
|
+
const owned2 = w.__SONOR_RESONANCE_GOOGLE_V1__;
|
|
1987
|
+
if (owned2) {
|
|
1988
|
+
const tags = [...new Set(destinations.map((d) => d.tagId))].sort();
|
|
1989
|
+
if (owned2.scope !== `${this.config.projectId}:${this.config.site}` || JSON.stringify(tags) !== JSON.stringify(owned2.tags)) {
|
|
1990
|
+
this.block("google_scope_change_requires_reload");
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
if (w.gtag !== owned2.command || w.dataLayer !== owned2.data) {
|
|
1994
|
+
this.block("google_duplicate_installation");
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
if (owned2.failed) {
|
|
1998
|
+
this.block("google_load_failed");
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
this.google = owned2.command;
|
|
2002
|
+
this.script = owned2.script;
|
|
2003
|
+
this.loaded = owned2.loaded;
|
|
2004
|
+
for (const tag of tags) this.configured.add(tag);
|
|
2005
|
+
this.applyConsent();
|
|
2006
|
+
if (!this.loaded) {
|
|
2007
|
+
const script2 = this.script;
|
|
2008
|
+
let done;
|
|
2009
|
+
const loaded = () => {
|
|
2010
|
+
this.loaded = true;
|
|
2011
|
+
done();
|
|
2012
|
+
};
|
|
2013
|
+
const failed = () => {
|
|
2014
|
+
this.block("google_load_failed");
|
|
2015
|
+
done();
|
|
2016
|
+
};
|
|
2017
|
+
await new Promise((resolve) => {
|
|
2018
|
+
done = resolve;
|
|
2019
|
+
this.finishLoad = resolve;
|
|
2020
|
+
script2.addEventListener("load", loaded, { once: true });
|
|
2021
|
+
script2.addEventListener("error", failed, { once: true });
|
|
2022
|
+
if (!script2.isConnected && this.allowed()) document.head.appendChild(script2);
|
|
2023
|
+
});
|
|
2024
|
+
script2.removeEventListener("load", loaded);
|
|
2025
|
+
script2.removeEventListener("error", failed);
|
|
2026
|
+
}
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
if (w.gtag || w.dataLayer || document.querySelector('script[src*="googletagmanager.com/gtag/js"],script[src*="googletagmanager.com/gtm.js"]')) {
|
|
2030
|
+
this.block("google_duplicate_installation");
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
const generation = this.generation;
|
|
2034
|
+
const data = [];
|
|
2035
|
+
const command2 = function(..._args) {
|
|
2036
|
+
data.push(arguments);
|
|
2037
|
+
};
|
|
2038
|
+
w.dataLayer = data;
|
|
2039
|
+
w.gtag = command2;
|
|
2040
|
+
this.google = command2;
|
|
2041
|
+
command2("consent", "default", denied);
|
|
2042
|
+
command2("set", "ads_data_redaction", true);
|
|
2043
|
+
command2("set", "url_passthrough", false);
|
|
2044
|
+
this.applyConsent();
|
|
2045
|
+
command2("js", /* @__PURE__ */ new Date());
|
|
2046
|
+
for (const d of destinations) {
|
|
2047
|
+
if (this.configured.has(d.tagId)) continue;
|
|
2048
|
+
command2("config", d.tagId, { send_page_view: false, allow_enhanced_conversions: false, page_location: `https://${this.config.site}/`, page_referrer: "", page_title: "" });
|
|
2049
|
+
this.configured.add(d.tagId);
|
|
2050
|
+
}
|
|
2051
|
+
const script = document.createElement("script");
|
|
2052
|
+
this.script = script;
|
|
2053
|
+
script.async = true;
|
|
2054
|
+
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(destinations[0].tagId)}`;
|
|
2055
|
+
script.referrerPolicy = "no-referrer";
|
|
2056
|
+
const ownership = { scope: `${this.config.projectId}:${this.config.site}`, tags: [...this.configured].sort(), command: command2, data, script, loaded: false, failed: false };
|
|
2057
|
+
w.__SONOR_RESONANCE_GOOGLE_V1__ = ownership;
|
|
2058
|
+
await new Promise((resolve) => {
|
|
2059
|
+
this.finishLoad = resolve;
|
|
2060
|
+
script.onload = () => {
|
|
2061
|
+
this.loaded = true;
|
|
2062
|
+
ownership.loaded = true;
|
|
2063
|
+
resolve();
|
|
2064
|
+
};
|
|
2065
|
+
script.onerror = () => {
|
|
2066
|
+
ownership.failed = true;
|
|
2067
|
+
this.block("google_load_failed");
|
|
2068
|
+
resolve();
|
|
2069
|
+
};
|
|
2070
|
+
if (generation !== this.generation || !this.allowed()) {
|
|
2071
|
+
resolve();
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
document.head.appendChild(script);
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
async occurrence(event) {
|
|
2078
|
+
const generation = this.generation;
|
|
2079
|
+
if (!this.allowed() || event.kind === "experiment_exposure") return;
|
|
2080
|
+
if (this.pending) await this.pending;
|
|
2081
|
+
if (generation !== this.generation || !this.allowed() || !this.google || this.blockers.has("google_load_failed")) return;
|
|
2082
|
+
const rule = event.resourceRef ? this.config.resourceRules.find((r) => r.resource.id === event.resourceRef?.id && r.resource.revision === event.resourceRef.revision && r.resource.type === event.resourceRef.type) : null;
|
|
2083
|
+
for (const tag of this.configured) this.google("event", event.kind === "page_view" ? "page_view" : "sonor_content_interest", {
|
|
2084
|
+
send_to: tag,
|
|
2085
|
+
event_id: event.eventId,
|
|
2086
|
+
page_location: `https://${this.config.site}${rule?.path ?? "/"}`,
|
|
2087
|
+
page_referrer: "",
|
|
2088
|
+
page_title: "",
|
|
2089
|
+
...rule ? { sonor_resource_id: rule.resource.id, sonor_interest_key: rule.interestKey } : {}
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
/** Single-page navigation: Meta re-evaluates the new page's eligibility and URL hygiene. */
|
|
2093
|
+
navigate() {
|
|
2094
|
+
if (this.active) this.meta.navigate();
|
|
2095
|
+
}
|
|
2096
|
+
withdraw() {
|
|
2097
|
+
this.meta.withdraw();
|
|
2098
|
+
if (!this.active) return;
|
|
2099
|
+
this.active = false;
|
|
2100
|
+
this.generation++;
|
|
2101
|
+
this.google?.("consent", "update", denied);
|
|
2102
|
+
if (!this.loaded) this.script?.remove();
|
|
2103
|
+
this.finishLoad?.();
|
|
2104
|
+
this.finishLoad = null;
|
|
2105
|
+
}
|
|
2106
|
+
stop() {
|
|
2107
|
+
this.withdraw();
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
|
|
2111
|
+
// src/resonance/runtime-core.ts
|
|
2112
|
+
function currentSourceMarker() {
|
|
2113
|
+
const values = new URLSearchParams(window.location.search).getAll(RESONANCE_SOURCE_MARKER_PARAMETER);
|
|
2114
|
+
if (values.length !== 1) return null;
|
|
2115
|
+
try {
|
|
2116
|
+
return parseResonanceSourceMarker(values[0]);
|
|
2117
|
+
} catch {
|
|
2118
|
+
return null;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
function mountResonanceRuntime({ site: site2, apiUrl, apiKey }) {
|
|
2122
|
+
if (!site2 || !resolveAnalyticsTarget({ apiUrl, apiKey })) return;
|
|
2123
|
+
let providers = null;
|
|
2124
|
+
let providerRevision = "";
|
|
2125
|
+
const runtime = new ResonanceController({
|
|
2126
|
+
site: site2,
|
|
2127
|
+
readClickIdentifiers(pathname) {
|
|
2128
|
+
if (typeof window === "undefined" || window.location.pathname !== pathname) return null;
|
|
2129
|
+
const parameters = new URLSearchParams(window.location.search);
|
|
2130
|
+
const entries = ["gclid", "gbraid", "wbraid"].flatMap((key) => parameters.getAll(key).map((value) => [key, value]));
|
|
2131
|
+
if (entries.length !== 1) return null;
|
|
2132
|
+
try {
|
|
2133
|
+
return parseResonanceClickIdentifiers(Object.fromEntries(entries));
|
|
2134
|
+
} catch {
|
|
2135
|
+
return null;
|
|
2136
|
+
}
|
|
2137
|
+
},
|
|
2138
|
+
readMetaCookieObservation(pathname) {
|
|
2139
|
+
if (typeof window === "undefined" || typeof document === "undefined" || window.location.pathname !== pathname) return null;
|
|
2140
|
+
const pages = measurementPolicySnapshot().config?.resourceRules.filter((rule) => rule.path === pathname && rule.resource.type === "page") ?? [];
|
|
2141
|
+
if (pages.length !== 1 || pages[0].metaEligible !== true) return null;
|
|
2142
|
+
try {
|
|
2143
|
+
const cookies = document.cookie.split(";").map((part) => {
|
|
2144
|
+
const separator = part.indexOf("=");
|
|
2145
|
+
return separator < 0 ? null : [part.slice(0, separator).trim(), part.slice(separator + 1)];
|
|
2146
|
+
});
|
|
2147
|
+
if (window.location.pathname !== pathname) return null;
|
|
2148
|
+
const observed = {};
|
|
2149
|
+
for (const field3 of RESONANCE_WEBSITE_VISIT_PROFILE.matchingFields) {
|
|
2150
|
+
const matches = cookies.filter((cookie) => cookie?.[0] === `_${field3}`);
|
|
2151
|
+
if (matches.length === 1 && matches[0][1]) observed[field3] = matches[0][1];
|
|
2152
|
+
}
|
|
2153
|
+
return Object.keys(observed).length ? { source: RESONANCE_WEBSITE_VISIT_PROFILE.source, ...observed } : null;
|
|
2154
|
+
} catch {
|
|
2155
|
+
return null;
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
transport: { async request(path2, init, stillAllowed) {
|
|
2159
|
+
const response = await sonorFetch(`${apiUrl.replace(/\/+$/, "")}/api/public/resonance/measurement${path2}`, init, {
|
|
2160
|
+
apiKey,
|
|
2161
|
+
retries: 0,
|
|
2162
|
+
shouldSend: () => !!resolveAnalyticsTarget({ apiUrl, apiKey }) && (stillAllowed?.() ?? true)
|
|
2163
|
+
});
|
|
2164
|
+
return { status: response.status, body: await response.json().catch(() => null) };
|
|
2165
|
+
} },
|
|
2166
|
+
onOccurrence(event) {
|
|
2167
|
+
void providers?.occurrence(event);
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
const unregister = registerResonanceController(runtime);
|
|
2171
|
+
if (!unregister) return;
|
|
2172
|
+
const updateProviders = () => {
|
|
2173
|
+
const config = measurementPolicySnapshot().config;
|
|
2174
|
+
const revision2 = config ? `${config.projectId}:${config.site}:${config.configRevision}:${config.policyRevision}` : "";
|
|
2175
|
+
if (revision2 !== providerRevision) {
|
|
2176
|
+
providers?.stop();
|
|
2177
|
+
providers = null;
|
|
2178
|
+
providerRevision = revision2;
|
|
2179
|
+
}
|
|
2180
|
+
if (config && !providers) providers = new ResonanceProviderRuntime(config, setProviderBlockers);
|
|
2181
|
+
providers?.update();
|
|
2182
|
+
setProviderBlockers(providers ? [...providers.blockers] : []);
|
|
2183
|
+
};
|
|
2184
|
+
const unpolicy = subscribeMeasurementPolicy(updateProviders);
|
|
2185
|
+
const adapter = () => runtime.setAdapter(getConsentAdapter());
|
|
2186
|
+
const unadapter = subscribeConsentAdapter(adapter);
|
|
2187
|
+
adapter();
|
|
2188
|
+
void runtime.start();
|
|
2189
|
+
runtime.navigate(window.location.pathname, currentSourceMarker());
|
|
2190
|
+
const online = () => {
|
|
2191
|
+
void runtime.recover();
|
|
2192
|
+
};
|
|
2193
|
+
window.addEventListener("online", online);
|
|
2194
|
+
const navigation = () => {
|
|
2195
|
+
runtime.navigate(window.location.pathname, currentSourceMarker());
|
|
2196
|
+
providers?.navigate();
|
|
2197
|
+
};
|
|
2198
|
+
window.addEventListener("popstate", navigation);
|
|
2199
|
+
return { navigate(pathname) {
|
|
2200
|
+
runtime.navigate(pathname, currentSourceMarker());
|
|
2201
|
+
providers?.navigate();
|
|
2202
|
+
}, stop() {
|
|
2203
|
+
window.removeEventListener("online", online);
|
|
2204
|
+
window.removeEventListener("popstate", navigation);
|
|
2205
|
+
unadapter();
|
|
2206
|
+
unpolicy();
|
|
2207
|
+
unregister();
|
|
2208
|
+
runtime.stop();
|
|
2209
|
+
providers?.stop();
|
|
2210
|
+
} };
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
export { mountResonanceRuntime };
|