@aglyn/plugins-marketing 1.0.0-beta.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +56 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/announcement-bar-card.component.d.ts +20 -0
  8. package/src/lib/components/announcement-bar-card.component.js +292 -0
  9. package/src/lib/components/announcement-bar-card.component.js.map +1 -0
  10. package/src/lib/components/campaign-composer.d.ts +84 -0
  11. package/src/lib/components/campaign-composer.js +1643 -0
  12. package/src/lib/components/campaign-composer.js.map +1 -0
  13. package/src/lib/components/campaign-conversions-card.d.ts +22 -0
  14. package/src/lib/components/campaign-conversions-card.js +642 -0
  15. package/src/lib/components/campaign-conversions-card.js.map +1 -0
  16. package/src/lib/components/campaign-detail-card.d.ts +55 -0
  17. package/src/lib/components/campaign-detail-card.js +771 -0
  18. package/src/lib/components/campaign-detail-card.js.map +1 -0
  19. package/src/lib/components/campaign-edit-drawer.d.ts +60 -0
  20. package/src/lib/components/campaign-edit-drawer.js +252 -0
  21. package/src/lib/components/campaign-edit-drawer.js.map +1 -0
  22. package/src/lib/components/campaign-email-zones.d.ts +111 -0
  23. package/src/lib/components/campaign-email-zones.js +54 -0
  24. package/src/lib/components/campaign-email-zones.js.map +1 -0
  25. package/src/lib/components/campaign-glance-card.component.d.ts +18 -0
  26. package/src/lib/components/campaign-glance-card.component.js +151 -0
  27. package/src/lib/components/campaign-glance-card.component.js.map +1 -0
  28. package/src/lib/components/campaign-members-section.d.ts +87 -0
  29. package/src/lib/components/campaign-members-section.js +499 -0
  30. package/src/lib/components/campaign-members-section.js.map +1 -0
  31. package/src/lib/components/campaign-reach-sections.d.ts +137 -0
  32. package/src/lib/components/campaign-reach-sections.js +694 -0
  33. package/src/lib/components/campaign-reach-sections.js.map +1 -0
  34. package/src/lib/components/campaign-report-card.d.ts +91 -0
  35. package/src/lib/components/campaign-report-card.js +667 -0
  36. package/src/lib/components/campaign-report-card.js.map +1 -0
  37. package/src/lib/components/campaign-test-send-drawer.d.ts +45 -0
  38. package/src/lib/components/campaign-test-send-drawer.js +238 -0
  39. package/src/lib/components/campaign-test-send-drawer.js.map +1 -0
  40. package/src/lib/components/campaigns-card.d.ts +31 -0
  41. package/src/lib/components/campaigns-card.js +667 -0
  42. package/src/lib/components/campaigns-card.js.map +1 -0
  43. package/src/lib/components/conversion-attribution.component.d.ts +62 -0
  44. package/src/lib/components/conversion-attribution.component.js +127 -0
  45. package/src/lib/components/conversion-attribution.component.js.map +1 -0
  46. package/src/lib/components/email-compose-card.d.ts +12 -0
  47. package/src/lib/components/email-compose-card.js +157 -0
  48. package/src/lib/components/email-compose-card.js.map +1 -0
  49. package/src/lib/components/email-detail.d.ts +40 -0
  50. package/src/lib/components/email-detail.js +1180 -0
  51. package/src/lib/components/email-detail.js.map +1 -0
  52. package/src/lib/components/email-edit-drawer.d.ts +54 -0
  53. package/src/lib/components/email-edit-drawer.js +172 -0
  54. package/src/lib/components/email-edit-drawer.js.map +1 -0
  55. package/src/lib/components/email-messages-widget.d.ts +22 -0
  56. package/src/lib/components/email-messages-widget.js +49 -0
  57. package/src/lib/components/email-messages-widget.js.map +1 -0
  58. package/src/lib/components/email-recipients-card.d.ts +50 -0
  59. package/src/lib/components/email-recipients-card.js +338 -0
  60. package/src/lib/components/email-recipients-card.js.map +1 -0
  61. package/src/lib/components/emails-list-card.d.ts +36 -0
  62. package/src/lib/components/emails-list-card.js +544 -0
  63. package/src/lib/components/emails-list-card.js.map +1 -0
  64. package/src/lib/components/experiment-zones.d.ts +78 -0
  65. package/src/lib/components/experiment-zones.js +65 -0
  66. package/src/lib/components/experiment-zones.js.map +1 -0
  67. package/src/lib/components/host-experiments-card.component.d.ts +17 -0
  68. package/src/lib/components/host-experiments-card.component.js +887 -0
  69. package/src/lib/components/host-experiments-card.component.js.map +1 -0
  70. package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
  71. package/src/lib/components/host-marketing-summary-card.component.js +161 -0
  72. package/src/lib/components/host-marketing-summary-card.component.js.map +1 -0
  73. package/src/lib/components/host-overlays-card.component.d.ts +19 -0
  74. package/src/lib/components/host-overlays-card.component.js +783 -0
  75. package/src/lib/components/host-overlays-card.component.js.map +1 -0
  76. package/src/lib/components/marketing-console-page.d.ts +28 -0
  77. package/src/lib/components/marketing-console-page.js +166 -0
  78. package/src/lib/components/marketing-console-page.js.map +1 -0
  79. package/src/lib/components/marketing-console-sections.d.ts +34 -0
  80. package/src/lib/components/marketing-console-sections.js +77 -0
  81. package/src/lib/components/marketing-console-sections.js.map +1 -0
  82. package/src/lib/components/overlay-stats-row.component.d.ts +15 -0
  83. package/src/lib/components/overlay-stats-row.component.js +78 -0
  84. package/src/lib/components/overlay-stats-row.component.js.map +1 -0
  85. package/src/lib/components/popup-card.component.d.ts +18 -0
  86. package/src/lib/components/popup-card.component.js +462 -0
  87. package/src/lib/components/popup-card.component.js.map +1 -0
  88. package/src/lib/components/record-attribution-widget.d.ts +35 -0
  89. package/src/lib/components/record-attribution-widget.js +33 -0
  90. package/src/lib/components/record-attribution-widget.js.map +1 -0
  91. package/src/lib/components/site-runtime.d.ts +9 -0
  92. package/src/lib/components/site-runtime.js +1081 -0
  93. package/src/lib/components/site-runtime.js.map +1 -0
  94. package/src/lib/components/use-campaign-send-api.d.ts +35 -0
  95. package/src/lib/components/use-campaign-send-api.js +99 -0
  96. package/src/lib/components/use-campaign-send-api.js.map +1 -0
  97. package/src/lib/components/use-campaign-topic-options.d.ts +20 -0
  98. package/src/lib/components/use-campaign-topic-options.js +44 -0
  99. package/src/lib/components/use-campaign-topic-options.js.map +1 -0
  100. package/src/lib/components/use-emails-hub-path.d.ts +39 -0
  101. package/src/lib/components/use-emails-hub-path.js +70 -0
  102. package/src/lib/components/use-emails-hub-path.js.map +1 -0
  103. package/src/lib/components/use-marketing-hub-path.d.ts +21 -0
  104. package/src/lib/components/use-marketing-hub-path.js +50 -0
  105. package/src/lib/components/use-marketing-hub-path.js.map +1 -0
  106. package/src/lib/components/use-overlay-copy-editor.d.ts +79 -0
  107. package/src/lib/components/use-overlay-copy-editor.js +110 -0
  108. package/src/lib/components/use-overlay-copy-editor.js.map +1 -0
  109. package/src/lib/constants/bundle-common.d.ts +8 -0
  110. package/src/lib/constants/bundle-common.js +9 -0
  111. package/src/lib/constants/bundle-common.js.map +1 -0
  112. package/src/lib/model/campaign-membership-figures.d.ts +109 -0
  113. package/src/lib/model/campaign-membership-figures.js +113 -0
  114. package/src/lib/model/campaign-membership-figures.js.map +1 -0
  115. package/src/lib/model/compile-client-automations.d.ts +65 -0
  116. package/src/lib/model/compile-client-automations.js +95 -0
  117. package/src/lib/model/compile-client-automations.js.map +1 -0
  118. package/src/lib/model/experiments.d.ts +130 -0
  119. package/src/lib/model/experiments.js +186 -0
  120. package/src/lib/model/experiments.js.map +1 -0
  121. package/src/lib/model/index.d.ts +26 -0
  122. package/src/lib/model/index.js +26 -0
  123. package/src/lib/model/index.js.map +1 -0
  124. package/src/lib/model/marketing-record-routes.d.ts +6 -0
  125. package/src/lib/model/marketing-record-routes.js +46 -0
  126. package/src/lib/model/marketing-record-routes.js.map +1 -0
  127. package/src/lib/model/overlay-copy.d.ts +62 -0
  128. package/src/lib/model/overlay-copy.js +78 -0
  129. package/src/lib/model/overlay-copy.js.map +1 -0
  130. package/src/lib/model/overlays.d.ts +113 -0
  131. package/src/lib/model/overlays.js +93 -0
  132. package/src/lib/model/overlays.js.map +1 -0
  133. package/src/lib/model/site-contract.d.ts +100 -0
  134. package/src/lib/model/site-contract.js +23 -0
  135. package/src/lib/model/site-contract.js.map +1 -0
  136. package/src/lib/plugin.d.ts +27 -0
  137. package/src/lib/plugin.js +168 -0
  138. package/src/lib/plugin.js.map +1 -0
  139. package/src/lib/preview-props.d.ts +31 -0
  140. package/src/lib/preview-props.js +54 -0
  141. package/src/lib/preview-props.js.map +1 -0
  142. package/src/lib/server/campaign-manage.d.ts +62 -0
  143. package/src/lib/server/campaign-manage.js +829 -0
  144. package/src/lib/server/campaign-manage.js.map +1 -0
  145. package/src/lib/server/campaign-process-scheduled.d.ts +26 -0
  146. package/src/lib/server/campaign-process-scheduled.js +198 -0
  147. package/src/lib/server/campaign-process-scheduled.js.map +1 -0
  148. package/src/lib/server/campaign-recipients.d.ts +55 -0
  149. package/src/lib/server/campaign-recipients.js +219 -0
  150. package/src/lib/server/campaign-recipients.js.map +1 -0
  151. package/src/lib/server/campaign-send.d.ts +487 -0
  152. package/src/lib/server/campaign-send.js +3157 -0
  153. package/src/lib/server/campaign-send.js.map +1 -0
  154. package/src/lib/server/email-campaign-reach.d.ts +174 -0
  155. package/src/lib/server/email-campaign-reach.js +254 -0
  156. package/src/lib/server/email-campaign-reach.js.map +1 -0
  157. package/src/lib/server/email-events.d.ts +27 -0
  158. package/src/lib/server/email-events.js +714 -0
  159. package/src/lib/server/email-events.js.map +1 -0
  160. package/src/lib/server/get-client-automations.d.ts +60 -0
  161. package/src/lib/server/get-client-automations.js +87 -0
  162. package/src/lib/server/get-client-automations.js.map +1 -0
  163. package/src/lib/server/get-overlays.d.ts +28 -0
  164. package/src/lib/server/get-overlays.js +65 -0
  165. package/src/lib/server/get-overlays.js.map +1 -0
  166. package/src/lib/server/get-screen-experiments.d.ts +42 -0
  167. package/src/lib/server/get-screen-experiments.js +82 -0
  168. package/src/lib/server/get-screen-experiments.js.map +1 -0
  169. package/src/lib/server/lists-materialize.d.ts +18 -0
  170. package/src/lib/server/lists-materialize.js +130 -0
  171. package/src/lib/server/lists-materialize.js.map +1 -0
  172. package/src/lib/server/marketing-figures.d.ts +37 -0
  173. package/src/lib/server/marketing-figures.js +248 -0
  174. package/src/lib/server/marketing-figures.js.map +1 -0
  175. package/src/lib/server/site-page-enricher.d.ts +26 -0
  176. package/src/lib/server/site-page-enricher.js +210 -0
  177. package/src/lib/server/site-page-enricher.js.map +1 -0
  178. package/src/lib/server.d.ts +20 -0
  179. package/src/lib/server.js +152 -0
  180. package/src/lib/server.js.map +1 -0
  181. package/src/lib/site.d.ts +22 -0
  182. package/src/lib/site.js +48 -0
  183. package/src/lib/site.js.map +1 -0
@@ -0,0 +1,210 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import * as Aglyn from "@aglyn/aglyn/server";
18
+ import getVariables from "@aglyn/tenant-runtime/get-variables";
19
+ import * as MarketingModel from "../model/index.js";
20
+ import { OVERLAY_COPY_FIELDS, resolveOverlayCopy } from "../model/overlay-copy.js";
21
+ import { getClientAutomations } from "./get-client-automations.js";
22
+ import { getScreenExperiments } from "./get-screen-experiments.js";
23
+ import getOverlays from "./get-overlays.js";
24
+ /**
25
+ * Marketing contributions to a rendered site page (AGL-418), relocated
26
+ * verbatim from the tenant loader: overlays (announcement bar + popup with
27
+ * server-resolved binding tokens, AGL-195/196/251), site-event automations
28
+ * (AGL-256/257), and screen/section experiments (AGL-253). Every block is
29
+ * entitlement-gated on the owning org's plan exactly as before; the shapes
30
+ * written here are the wire contract the tenant page runtime consumes.
31
+ */ export const marketingSitePageEnricher = async ({ hostId, host, org, path, screenId, screen, // Composed nodes (AGL-659), also read here for the interactions authored
32
+ // on the elements themselves — see `getClientAutomations`.
33
+ nodes, // No request path belongs to this surface (AGL-2511) — see the context
34
+ // field. Everything below that reads `path` narrows to what is
35
+ // path-independent instead of guessing an address.
36
+ pathUnknown })=>{
37
+ // Marketing overlays (AGL-195/196/247): marketingOverlays-gated on the
38
+ // effective plan (plan-less = free = no overlays); binding tokens
39
+ // resolve server-side so the client ships plain text.
40
+ const overlaysEntitled = Aglyn.resolveOrgEntitlements(org).features.marketingOverlays;
41
+ const contentHash = (value)=>{
42
+ let hash = 0;
43
+ for(let index = 0; index < value.length; index += 1){
44
+ hash = hash * 31 + value.charCodeAt(index) | 0;
45
+ }
46
+ return Math.abs(hash).toString(36);
47
+ };
48
+ // Marketing hub overlays (AGL-251): scheduled/targeted overlay docs
49
+ // win over the legacy single announcementBar/popup host fields.
50
+ const overlayPath = `/${path.replace(/^\/+/, '')}`.replace(/\/{2,}/g, '/');
51
+ const allOverlayDocs = overlaysEntitled ? await getOverlays({
52
+ hostId
53
+ }) : [];
54
+ // A path-targeted overlay cannot be resolved without a path, so on such a
55
+ // surface only the ones that target every page remain — which is what an
56
+ // overlay with no patterns already means. Filtered rather than skipped
57
+ // wholesale: a site-wide announcement bar belongs on a designed 404 as much
58
+ // as on any other page the site serves.
59
+ const overlayDocs = pathUnknown ? allOverlayDocs.filter((overlay)=>{
60
+ var _overlay_pathPatterns, _overlay_excludePathPatterns;
61
+ return !((_overlay_pathPatterns = overlay.pathPatterns) == null ? void 0 : _overlay_pathPatterns.some((pattern)=>pattern.trim())) && !((_overlay_excludePathPatterns = overlay.excludePathPatterns) == null ? void 0 : _overlay_excludePathPatterns.some((pattern)=>pattern.trim()));
62
+ }) : allOverlayDocs;
63
+ const activeOverlays = MarketingModel.resolveActiveOverlays(overlayDocs, {
64
+ path: overlayPath
65
+ });
66
+ const barConfig = activeOverlays.bar ? _extends({
67
+ enabled: true
68
+ }, activeOverlays.bar.bar) : host == null ? void 0 : host.announcementBar;
69
+ const popupConfig = activeOverlays.popup ? _extends({
70
+ enabled: true
71
+ }, activeOverlays.popup.popup, activeOverlays.popup.startAtMs ? {
72
+ startAtMs: activeOverlays.popup.startAtMs
73
+ } : {}, activeOverlays.popup.endAtMs ? {
74
+ endAtMs: activeOverlays.popup.endAtMs
75
+ } : {}) : host == null ? void 0 : host.popup;
76
+ /**
77
+ * Overlay copy as the visitor reads it, through the one resolution every
78
+ * overlay path shares.
79
+ *
80
+ * The site's variables are read at most once per page, and only for copy
81
+ * that holds a token: most bars and popups are plain text and cost no read.
82
+ */ let overlayVariables;
83
+ const overlayCopy = async (text)=>{
84
+ if (!text.includes('{{')) return text;
85
+ overlayVariables != null ? overlayVariables : overlayVariables = getVariables({
86
+ hostId
87
+ });
88
+ return resolveOverlayCopy(text, await overlayVariables, host);
89
+ };
90
+ let announcementBar = null;
91
+ if (overlaysEntitled && (barConfig == null ? void 0 : barConfig.enabled) && barConfig.text) {
92
+ var _activeOverlays_bar;
93
+ const text = await overlayCopy(barConfig.text);
94
+ announcementBar = _extends({
95
+ text
96
+ }, barConfig.href ? {
97
+ href: barConfig.href
98
+ } : {}, barConfig.backgroundColor ? {
99
+ backgroundColor: barConfig.backgroundColor
100
+ } : {}, barConfig.textColor ? {
101
+ textColor: barConfig.textColor
102
+ } : {}, {
103
+ dismissible: barConfig.dismissible !== false,
104
+ contentHash: contentHash(text)
105
+ }, ((_activeOverlays_bar = activeOverlays.bar) == null ? void 0 : _activeOverlays_bar.$id) ? {
106
+ overlayId: activeOverlays.bar.$id
107
+ } : {});
108
+ }
109
+ let popup = null;
110
+ if (overlaysEntitled && (popupConfig == null ? void 0 : popupConfig.enabled) && popupConfig.body) {
111
+ var _popupConfig_trigger, _popupConfig_triggerValue, _popupConfig_frequencyDays;
112
+ var _activeOverlays_popup;
113
+ const body = await overlayCopy(popupConfig.body);
114
+ const headline = popupConfig.headline ? await overlayCopy(popupConfig.headline) : undefined;
115
+ popup = _extends({}, headline ? {
116
+ headline
117
+ } : {}, {
118
+ body
119
+ }, popupConfig.imageUrl ? {
120
+ imageUrl: popupConfig.imageUrl
121
+ } : {}, popupConfig.imageUrl && popupConfig.imageAlt ? {
122
+ imageAlt: popupConfig.imageAlt
123
+ } : {}, popupConfig.ctaLabel ? {
124
+ ctaLabel: popupConfig.ctaLabel
125
+ } : {}, popupConfig.ctaHref ? {
126
+ ctaHref: popupConfig.ctaHref
127
+ } : {}, {
128
+ trigger: (_popupConfig_trigger = popupConfig.trigger) != null ? _popupConfig_trigger : 'delay',
129
+ triggerValue: Number((_popupConfig_triggerValue = popupConfig.triggerValue) != null ? _popupConfig_triggerValue : 3),
130
+ frequencyDays: Math.max(1, Number((_popupConfig_frequencyDays = popupConfig.frequencyDays) != null ? _popupConfig_frequencyDays : 7))
131
+ }, popupConfig.oncePerSession ? {
132
+ oncePerSession: true
133
+ } : {}, popupConfig.collectEmail ? {
134
+ collectEmail: true
135
+ } : {}, popupConfig.startAtMs ? {
136
+ startAtMs: popupConfig.startAtMs
137
+ } : {}, popupConfig.endAtMs ? {
138
+ endAtMs: popupConfig.endAtMs
139
+ } : {}, {
140
+ contentHash: contentHash(`${headline != null ? headline : ''}|${body}`)
141
+ }, ((_activeOverlays_popup = activeOverlays.popup) == null ? void 0 : _activeOverlays_popup.$id) ? {
142
+ overlayId: activeOverlays.popup.$id
143
+ } : {});
144
+ }
145
+ // Site-event automations (AGL-256/577): the engine loads on every plan
146
+ // so basic presentational interactions (menu/drawer/show-hide/class/
147
+ // nav/alert) work everywhere. Powerful steps are gated per-step inside
148
+ // getClientAutomations — `actions` for server/overlay/analytics steps,
149
+ // `webhooks` for runJs.
150
+ const orgFeatures = Aglyn.resolveOrgEntitlements(org).features;
151
+ const clientAutomations = await getClientAutomations(_extends({
152
+ hostId,
153
+ path: overlayPath,
154
+ actionsEntitled: !!orgFeatures.actions,
155
+ allowJs: !!orgFeatures.webhooks,
156
+ // The COMPOSED tree, so a layout's chrome and a reusable component's
157
+ // internals contribute their own interactions under the graft ids the
158
+ // renderer actually stamps.
159
+ nodes: Aglyn.walkInteractionNodes(nodes)
160
+ }, pathUnknown ? {
161
+ dropPathScoped: true
162
+ } : {}));
163
+ // Screen/section experiments (AGL-253): Business-gated; composing a
164
+ // tree per divergent variant is bounded (≤4) and ISR-cached.
165
+ const experiments = Aglyn.resolveOrgEntitlements(org).features.abTesting && screenId && screen ? await getScreenExperiments({
166
+ hostId,
167
+ screenId,
168
+ screen,
169
+ host: host
170
+ }) : [];
171
+ /**
172
+ * A stored bar or popup with its copy resolved and every other field as
173
+ * stored. The site runtime renders these payloads as they arrive, so copy
174
+ * left unresolved here reaches the visitor as the raw token.
175
+ */ const withOverlayCopy = async (payload)=>{
176
+ const resolved = _extends({}, payload);
177
+ for (const field of OVERLAY_COPY_FIELDS){
178
+ const value = resolved[field];
179
+ if (typeof value === 'string') resolved[field] = await overlayCopy(value);
180
+ }
181
+ return resolved;
182
+ };
183
+ // Overlay payloads showOverlay steps reference (AGL-257).
184
+ const automationOverlays = {};
185
+ for (const automation of clientAutomations){
186
+ for (const step of automation.steps){
187
+ if (step.type === 'showOverlay' && step.overlayId && !automationOverlays[step.overlayId]) {
188
+ const overlay = allOverlayDocs.find((candidate)=>candidate.$id === step.overlayId);
189
+ if (overlay) {
190
+ automationOverlays[step.overlayId] = _extends({
191
+ kind: overlay.kind
192
+ }, overlay.bar ? {
193
+ bar: await withOverlayCopy(overlay.bar)
194
+ } : {}, overlay.popup ? {
195
+ popup: await withOverlayCopy(overlay.popup)
196
+ } : {});
197
+ }
198
+ }
199
+ }
200
+ }
201
+ return {
202
+ announcementBar,
203
+ popup,
204
+ clientAutomations: JSON.parse(JSON.stringify(clientAutomations)),
205
+ experiments: JSON.parse(JSON.stringify(experiments)),
206
+ automationOverlays: Object.keys(automationOverlays).length ? JSON.parse(JSON.stringify(automationOverlays)) : null
207
+ };
208
+ };
209
+
210
+ //# sourceMappingURL=site-page-enricher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/server/site-page-enricher.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport type { SitePageEnricher } from '@aglyn/aglyn/server'\nimport getVariables from '@aglyn/tenant-runtime/get-variables'\nimport * as MarketingModel from '../model'\nimport { OVERLAY_COPY_FIELDS, resolveOverlayCopy } from '../model/overlay-copy'\nimport { getClientAutomations, type ClientAutomation } from './get-client-automations'\nimport { getScreenExperiments, type ScreenExperiment } from './get-screen-experiments'\nimport getOverlays from './get-overlays'\n\n/**\n * Marketing contributions to a rendered site page (AGL-418), relocated\n * verbatim from the tenant loader: overlays (announcement bar + popup with\n * server-resolved binding tokens, AGL-195/196/251), site-event automations\n * (AGL-256/257), and screen/section experiments (AGL-253). Every block is\n * entitlement-gated on the owning org's plan exactly as before; the shapes\n * written here are the wire contract the tenant page runtime consumes.\n */\nexport const marketingSitePageEnricher: SitePageEnricher = async ({\n hostId,\n host,\n org,\n path,\n screenId,\n screen,\n // Composed nodes (AGL-659), also read here for the interactions authored\n // on the elements themselves — see `getClientAutomations`.\n nodes,\n // No request path belongs to this surface (AGL-2511) — see the context\n // field. Everything below that reads `path` narrows to what is\n // path-independent instead of guessing an address.\n pathUnknown,\n}) => {\n // Marketing overlays (AGL-195/196/247): marketingOverlays-gated on the\n // effective plan (plan-less = free = no overlays); binding tokens\n // resolve server-side so the client ships plain text.\n const overlaysEntitled = Aglyn.resolveOrgEntitlements(org).features\n .marketingOverlays\n const contentHash = (value: string) => {\n let hash = 0\n for (let index = 0; index < value.length; index += 1) {\n hash = (hash * 31 + value.charCodeAt(index)) | 0\n }\n return Math.abs(hash).toString(36)\n }\n // Marketing hub overlays (AGL-251): scheduled/targeted overlay docs\n // win over the legacy single announcementBar/popup host fields.\n const overlayPath = `/${path.replace(/^\\/+/, '')}`.replace(/\\/{2,}/g, '/')\n const allOverlayDocs = overlaysEntitled ? await getOverlays({ hostId }) : []\n // A path-targeted overlay cannot be resolved without a path, so on such a\n // surface only the ones that target every page remain — which is what an\n // overlay with no patterns already means. Filtered rather than skipped\n // wholesale: a site-wide announcement bar belongs on a designed 404 as much\n // as on any other page the site serves.\n const overlayDocs = pathUnknown\n ? allOverlayDocs.filter(\n (overlay) =>\n !overlay.pathPatterns?.some((pattern) => pattern.trim()) &&\n !overlay.excludePathPatterns?.some((pattern) => pattern.trim()),\n )\n : allOverlayDocs\n const activeOverlays = MarketingModel.resolveActiveOverlays(overlayDocs, {\n path: overlayPath,\n })\n const barConfig: Aglyn.HostAnnouncementBar | undefined = activeOverlays.bar\n ? { enabled: true, ...activeOverlays.bar.bar }\n : ((host as any)?.announcementBar as\n | Aglyn.HostAnnouncementBar\n | undefined)\n const popupConfig: Aglyn.HostPopup | undefined = activeOverlays.popup\n ? {\n enabled: true,\n ...activeOverlays.popup.popup,\n ...(activeOverlays.popup.startAtMs\n ? { startAtMs: activeOverlays.popup.startAtMs }\n : {}),\n ...(activeOverlays.popup.endAtMs\n ? { endAtMs: activeOverlays.popup.endAtMs }\n : {}),\n }\n : ((host as any)?.popup as Aglyn.HostPopup | undefined)\n /**\n * Overlay copy as the visitor reads it, through the one resolution every\n * overlay path shares.\n *\n * The site's variables are read at most once per page, and only for copy\n * that holds a token: most bars and popups are plain text and cost no read.\n */\n let overlayVariables:\n | Promise<Record<string, Aglyn.HostVariable>>\n | undefined\n const overlayCopy = async (text: string): Promise<string> => {\n if (!text.includes('{{')) return text\n overlayVariables ??= getVariables({ hostId })\n return resolveOverlayCopy(text, await overlayVariables, host)\n }\n let announcementBar: Record<string, unknown> | null = null\n if (overlaysEntitled && barConfig?.enabled && barConfig.text) {\n const text = await overlayCopy(barConfig.text)\n announcementBar = {\n text,\n ...(barConfig.href ? { href: barConfig.href } : {}),\n ...(barConfig.backgroundColor\n ? { backgroundColor: barConfig.backgroundColor }\n : {}),\n ...(barConfig.textColor ? { textColor: barConfig.textColor } : {}),\n dismissible: barConfig.dismissible !== false,\n contentHash: contentHash(text),\n ...(activeOverlays.bar?.$id\n ? { overlayId: activeOverlays.bar.$id }\n : {}),\n }\n }\n let popup: Record<string, unknown> | null = null\n if (overlaysEntitled && popupConfig?.enabled && popupConfig.body) {\n const body = await overlayCopy(popupConfig.body)\n const headline = popupConfig.headline\n ? await overlayCopy(popupConfig.headline)\n : undefined\n popup = {\n ...(headline ? { headline } : {}),\n body,\n ...(popupConfig.imageUrl ? { imageUrl: popupConfig.imageUrl } : {}),\n // Only alongside an image, and only when non-empty (AGL-1896) — same\n // bytes-on-every-request rule as `oncePerSession` below. An absent\n // `imageAlt` renders `alt=\"\"`, which is what the popup always did.\n ...(popupConfig.imageUrl && popupConfig.imageAlt\n ? { imageAlt: popupConfig.imageAlt }\n : {}),\n ...(popupConfig.ctaLabel ? { ctaLabel: popupConfig.ctaLabel } : {}),\n ...(popupConfig.ctaHref ? { ctaHref: popupConfig.ctaHref } : {}),\n trigger: popupConfig.trigger ?? 'delay',\n triggerValue: Number(popupConfig.triggerValue ?? 3),\n frequencyDays: Math.max(1, Number(popupConfig.frequencyDays ?? 7)),\n // AGL-2174. Passed through only when set: the site contract is\n // serialised into every page, and an explicit `false` on every\n // popup that never asked for it is bytes on every request.\n ...(popupConfig.oncePerSession ? { oncePerSession: true } : {}),\n ...(popupConfig.collectEmail ? { collectEmail: true } : {}),\n ...(popupConfig.startAtMs ? { startAtMs: popupConfig.startAtMs } : {}),\n ...(popupConfig.endAtMs ? { endAtMs: popupConfig.endAtMs } : {}),\n contentHash: contentHash(`${headline ?? ''}|${body}`),\n ...(activeOverlays.popup?.$id\n ? { overlayId: activeOverlays.popup.$id }\n : {}),\n }\n }\n\n // Site-event automations (AGL-256/577): the engine loads on every plan\n // so basic presentational interactions (menu/drawer/show-hide/class/\n // nav/alert) work everywhere. Powerful steps are gated per-step inside\n // getClientAutomations — `actions` for server/overlay/analytics steps,\n // `webhooks` for runJs.\n const orgFeatures = Aglyn.resolveOrgEntitlements(org).features\n const clientAutomations: ClientAutomation[] = await getClientAutomations({\n hostId,\n path: overlayPath,\n actionsEntitled: !!orgFeatures.actions,\n allowJs: !!orgFeatures.webhooks,\n // The COMPOSED tree, so a layout's chrome and a reusable component's\n // internals contribute their own interactions under the graft ids the\n // renderer actually stamps.\n nodes: Aglyn.walkInteractionNodes(nodes),\n ...(pathUnknown ? { dropPathScoped: true } : {}),\n })\n // Screen/section experiments (AGL-253): Business-gated; composing a\n // tree per divergent variant is bounded (≤4) and ISR-cached.\n const experiments: ScreenExperiment[] =\n Aglyn.resolveOrgEntitlements(org).features.abTesting &&\n screenId &&\n screen\n ? await getScreenExperiments({\n hostId,\n screenId,\n screen,\n host: host as Aglyn.HostTokenSource,\n })\n : []\n\n /**\n * A stored bar or popup with its copy resolved and every other field as\n * stored. The site runtime renders these payloads as they arrive, so copy\n * left unresolved here reaches the visitor as the raw token.\n */\n const withOverlayCopy = async <T extends object>(payload: T): Promise<T> => {\n const resolved = { ...payload } as Record<string, unknown>\n for (const field of OVERLAY_COPY_FIELDS) {\n const value = resolved[field]\n if (typeof value === 'string') resolved[field] = await overlayCopy(value)\n }\n return resolved as T\n }\n\n // Overlay payloads showOverlay steps reference (AGL-257).\n const automationOverlays: Record<string, any> = {}\n for (const automation of clientAutomations) {\n for (const step of automation.steps) {\n if (\n step.type === 'showOverlay' &&\n step.overlayId &&\n !automationOverlays[step.overlayId]\n ) {\n const overlay = allOverlayDocs.find(\n (candidate) => candidate.$id === step.overlayId,\n )\n if (overlay) {\n automationOverlays[step.overlayId] = {\n kind: overlay.kind,\n ...(overlay.bar ? { bar: await withOverlayCopy(overlay.bar) } : {}),\n ...(overlay.popup\n ? { popup: await withOverlayCopy(overlay.popup) }\n : {}),\n }\n }\n }\n }\n }\n\n return {\n announcementBar,\n popup,\n clientAutomations: JSON.parse(JSON.stringify(clientAutomations)),\n experiments: JSON.parse(JSON.stringify(experiments)),\n automationOverlays: Object.keys(automationOverlays).length\n ? JSON.parse(JSON.stringify(automationOverlays))\n : null,\n }\n}\n"],"names":["Aglyn","getVariables","MarketingModel","OVERLAY_COPY_FIELDS","resolveOverlayCopy","getClientAutomations","getScreenExperiments","getOverlays","marketingSitePageEnricher","hostId","host","org","path","screenId","screen","nodes","pathUnknown","overlaysEntitled","resolveOrgEntitlements","features","marketingOverlays","contentHash","value","hash","index","length","charCodeAt","Math","abs","toString","overlayPath","replace","allOverlayDocs","overlayDocs","filter","overlay","pathPatterns","some","pattern","trim","excludePathPatterns","activeOverlays","resolveActiveOverlays","barConfig","bar","enabled","announcementBar","popupConfig","popup","startAtMs","endAtMs","overlayVariables","overlayCopy","text","includes","href","backgroundColor","textColor","dismissible","$id","overlayId","body","headline","undefined","imageUrl","imageAlt","ctaLabel","ctaHref","trigger","triggerValue","Number","frequencyDays","max","oncePerSession","collectEmail","orgFeatures","clientAutomations","actionsEntitled","actions","allowJs","webhooks","walkInteractionNodes","dropPathScoped","experiments","abTesting","withOverlayCopy","payload","resolved","field","automationOverlays","automation","step","steps","type","find","candidate","kind","JSON","parse","stringify","Object","keys"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /** Registers the marketing plugin's public (site-facing) API routes (AGL-396). */
18
+ export declare function registerMarketingApi(): void;
19
+ /** Registers the marketing plugin's console-side API routes (AGL-396). */
20
+ export declare function registerMarketingConsoleApi(): void;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { checkEntitlement } from "@aglyn/aglyn/server";
17
+ import { evaluateAutoWinner } from "./model/index.js";
18
+ import { registerPluginApiRoute, registerSitePageEnricher } from "@aglyn/aglyn/server";
19
+ import { emailEventsHandler } from "./server/email-events.js";
20
+ import { registerMarketingFigureReaders } from "./server/marketing-figures.js";
21
+ import { marketingSitePageEnricher } from "./server/site-page-enricher.js";
22
+ import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
23
+ import { FieldValue } from "firebase-admin/firestore";
24
+ import { campaignProcessScheduledHandler } from "./server/campaign-process-scheduled.js";
25
+ import { listsMaterializeHandler } from "./server/lists-materialize.js";
26
+ import { campaignManageHandler, registerCampaignDraftWriter } from "./server/campaign-manage.js";
27
+ import { campaignSendHandler } from "./server/campaign-send.js";
28
+ import { campaignRecipientsHandler } from "./server/campaign-recipients.js";
29
+ /**
30
+ * Experiment beacon (AGL-252): counts an exposure or conversion on
31
+ * `hosts/{hostId}/experiments/{id}/stats/{variantId}`. Sampled counters —
32
+ * fire-and-forget from the page, abTesting-gated, no visitor identity
33
+ * stored server-side (assignment is deterministic client-side).
34
+ */ const trackHandler = async (req, res)=>{
35
+ var _ref, _ref1, _ref2, _ref3;
36
+ var _req_body, _req_body1, _req_body2, _req_body3;
37
+ if (req.method !== 'POST') {
38
+ return res.status(405).json({
39
+ error: 'Method not allowed'
40
+ });
41
+ }
42
+ const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
43
+ const experimentId = String((_ref1 = (_req_body1 = req.body) == null ? void 0 : _req_body1.experimentId) != null ? _ref1 : '');
44
+ const variantId = String((_ref2 = (_req_body2 = req.body) == null ? void 0 : _req_body2.variantId) != null ? _ref2 : '');
45
+ const kind = String((_ref3 = (_req_body3 = req.body) == null ? void 0 : _req_body3.kind) != null ? _ref3 : '');
46
+ if (!hostId || !experimentId || !variantId || ![
47
+ 'exposure',
48
+ 'conversion'
49
+ ].includes(kind)) {
50
+ return res.status(400).json({
51
+ error: 'Bad beacon'
52
+ });
53
+ }
54
+ try {
55
+ var _experiment_get;
56
+ var _this;
57
+ const org = (_this = await getOrgForHost(hostId)) == null ? void 0 : _this.org;
58
+ if (!checkEntitlement(org, 'abTesting')) {
59
+ return res.status(200).json({
60
+ ok: true
61
+ });
62
+ }
63
+ const experimentRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId).collection('experiments').doc(experimentId);
64
+ const experiment = await experimentRef.get();
65
+ if (!experiment.exists || experiment.get('status') !== 'running') {
66
+ return res.status(200).json({
67
+ ok: true
68
+ });
69
+ }
70
+ // Ended experiments (AGL-273) stop counting: stale cached pages may
71
+ // still beacon briefly, and post-end stats would skew the decision.
72
+ const endAtMs = Number((_experiment_get = experiment.get('endAtMs')) != null ? _experiment_get : 0);
73
+ if (endAtMs && Date.now() > endAtMs) {
74
+ return res.status(200).json({
75
+ ok: true
76
+ });
77
+ }
78
+ await experimentRef.collection('stats').doc(variantId).set({
79
+ [kind === 'exposure' ? 'exposures' : 'conversions']: FieldValue.increment(1),
80
+ updatedAt: FieldValue.serverTimestamp()
81
+ }, {
82
+ merge: true
83
+ });
84
+ // Auto-winner (AGL-273): conversions re-evaluate the opt-in decision
85
+ // rule; a decided experiment finishes itself and serves the winner.
86
+ const data = experiment.data();
87
+ if (kind === 'conversion' && data.autoWinner) {
88
+ const statsSnapshot = await experimentRef.collection('stats').get();
89
+ const statsByVariant = {};
90
+ for (const doc of statsSnapshot.docs){
91
+ var _doc_get, _doc_get1;
92
+ statsByVariant[doc.id] = {
93
+ exposures: Number((_doc_get = doc.get('exposures')) != null ? _doc_get : 0),
94
+ conversions: Number((_doc_get1 = doc.get('conversions')) != null ? _doc_get1 : 0)
95
+ };
96
+ }
97
+ const decision = evaluateAutoWinner(data, statsByVariant);
98
+ if (decision) {
99
+ await experimentRef.update({
100
+ status: 'done',
101
+ winnerVariantId: decision.variantId,
102
+ autoCompleted: true,
103
+ completedAt: FieldValue.serverTimestamp()
104
+ });
105
+ }
106
+ }
107
+ return res.status(200).json({
108
+ ok: true
109
+ });
110
+ } catch (error) {
111
+ console.error(error);
112
+ return res.status(200).json({
113
+ ok: true
114
+ }) // beacons never error the page
115
+ ;
116
+ }
117
+ };
118
+ /** Registers the marketing plugin's public (site-facing) API routes (AGL-396). */ export function registerMarketingApi() {
119
+ registerPluginApiRoute('experiments/track', trackHandler);
120
+ // Site-page contributions (AGL-418): overlays, automations, experiments.
121
+ registerSitePageEnricher(marketingSitePageEnricher);
122
+ }
123
+ /** Registers the marketing plugin's console-side API routes (AGL-396). */ export function registerMarketingConsoleApi() {
124
+ // The campaign draft writer another plugin reaches through the core's
125
+ // resource-drafts seam (AGL-2912). Registered here alone: the AI jobs that
126
+ // write through it run only on the console, inline and on the beat
127
+ // (AGL-3026).
128
+ registerCampaignDraftWriter();
129
+ // Campaign and A/B testing results as figure tables (AGL-2915), for the AI
130
+ // plugin's insights to read by id; the console runs insight jobs.
131
+ registerMarketingFigureReaders(()=>firebaseAdmin.app().firestore());
132
+ registerPluginApiRoute('campaigns/send', campaignSendHandler);
133
+ // Taking a campaign or an abandoned draft away, which is the one class of
134
+ // change the send route may not carry: both write to the SEND collection,
135
+ // which no client may touch, and neither of them mails anything.
136
+ registerPluginApiRoute('campaigns/manage', campaignManageHandler);
137
+ // The other direction through the per-recipient delivery log: not "what did
138
+ // we send this person" but "who did this design reach, and which of them
139
+ // opened it". Server-side because the log is a platform-level collection no
140
+ // security rule can scope to one site's campaigns.
141
+ registerPluginApiRoute('campaigns/recipients', campaignRecipientsHandler);
142
+ // Relocated console route (AGL-418): the Resend/Svix webhook keeps its
143
+ // /api/email/events URL via the dispatcher; ownership is recorded at
144
+ // registration so the org gate attributes it to marketing, not email.
145
+ registerPluginApiRoute('email/events', emailEventsHandler);
146
+ registerPluginApiRoute('campaigns/process-scheduled', campaignProcessScheduledHandler);
147
+ // The dynamic-list sweep rides the same `*/15` fast-cron as the campaign
148
+ // processor rather than provisioning a scheduler job of its own.
149
+ registerPluginApiRoute('lists/materialize', listsMaterializeHandler);
150
+ }
151
+
152
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/server'\nimport { evaluateAutoWinner, type HostExperiment } from './model'\nimport {\n registerPluginApiRoute,\n registerSitePageEnricher,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport { emailEventsHandler } from './server/email-events'\nimport { registerMarketingFigureReaders } from './server/marketing-figures'\nimport { marketingSitePageEnricher } from './server/site-page-enricher'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport { campaignProcessScheduledHandler } from './server/campaign-process-scheduled'\nimport { listsMaterializeHandler } from './server/lists-materialize'\nimport {\n campaignManageHandler,\n registerCampaignDraftWriter,\n} from './server/campaign-manage'\nimport { campaignSendHandler } from './server/campaign-send'\nimport { campaignRecipientsHandler } from './server/campaign-recipients'\n\n/**\n * Experiment beacon (AGL-252): counts an exposure or conversion on\n * `hosts/{hostId}/experiments/{id}/stats/{variantId}`. Sampled counters —\n * fire-and-forget from the page, abTesting-gated, no visitor identity\n * stored server-side (assignment is deterministic client-side).\n */\nconst trackHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const hostId = String(req.body?.hostId ?? '')\n const experimentId = String(req.body?.experimentId ?? '')\n const variantId = String(req.body?.variantId ?? '')\n const kind = String(req.body?.kind ?? '')\n if (\n !hostId ||\n !experimentId ||\n !variantId ||\n !['exposure', 'conversion'].includes(kind)\n ) {\n return res.status(400).json({ error: 'Bad beacon' })\n }\n try {\n const org = (await getOrgForHost(hostId))?.org\n if (!checkEntitlement(org as any, 'abTesting')) {\n return res.status(200).json({ ok: true })\n }\n const experimentRef = firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('experiments')\n .doc(experimentId)\n const experiment = await experimentRef.get()\n if (!experiment.exists || experiment.get('status') !== 'running') {\n return res.status(200).json({ ok: true })\n }\n // Ended experiments (AGL-273) stop counting: stale cached pages may\n // still beacon briefly, and post-end stats would skew the decision.\n const endAtMs = Number(experiment.get('endAtMs') ?? 0)\n if (endAtMs && Date.now() > endAtMs) {\n return res.status(200).json({ ok: true })\n }\n await experimentRef\n .collection('stats')\n .doc(variantId)\n .set(\n {\n [kind === 'exposure' ? 'exposures' : 'conversions']:\n FieldValue.increment(1),\n updatedAt: FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n // Auto-winner (AGL-273): conversions re-evaluate the opt-in decision\n // rule; a decided experiment finishes itself and serves the winner.\n const data = experiment.data() as HostExperiment\n if (kind === 'conversion' && data.autoWinner) {\n const statsSnapshot = await experimentRef.collection('stats').get()\n const statsByVariant: Record<\n string,\n { exposures?: number; conversions?: number }\n > = {}\n for (const doc of statsSnapshot.docs) {\n statsByVariant[doc.id] = {\n exposures: Number(doc.get('exposures') ?? 0),\n conversions: Number(doc.get('conversions') ?? 0),\n }\n }\n const decision = evaluateAutoWinner(data, statsByVariant)\n if (decision) {\n await experimentRef.update({\n status: 'done',\n winnerVariantId: decision.variantId,\n autoCompleted: true,\n completedAt: FieldValue.serverTimestamp(),\n })\n }\n }\n return res.status(200).json({ ok: true })\n } catch (error) {\n console.error(error)\n return res.status(200).json({ ok: true }) // beacons never error the page\n }\n}\n\n/** Registers the marketing plugin's public (site-facing) API routes (AGL-396). */\nexport function registerMarketingApi(): void {\n registerPluginApiRoute('experiments/track', trackHandler)\n // Site-page contributions (AGL-418): overlays, automations, experiments.\n registerSitePageEnricher(marketingSitePageEnricher)\n}\n\n/** Registers the marketing plugin's console-side API routes (AGL-396). */\nexport function registerMarketingConsoleApi(): void {\n // The campaign draft writer another plugin reaches through the core's\n // resource-drafts seam (AGL-2912). Registered here alone: the AI jobs that\n // write through it run only on the console, inline and on the beat\n // (AGL-3026).\n registerCampaignDraftWriter()\n // Campaign and A/B testing results as figure tables (AGL-2915), for the AI\n // plugin's insights to read by id; the console runs insight jobs.\n registerMarketingFigureReaders(() => firebaseAdmin.app().firestore())\n registerPluginApiRoute('campaigns/send', campaignSendHandler)\n // Taking a campaign or an abandoned draft away, which is the one class of\n // change the send route may not carry: both write to the SEND collection,\n // which no client may touch, and neither of them mails anything.\n registerPluginApiRoute('campaigns/manage', campaignManageHandler)\n // The other direction through the per-recipient delivery log: not \"what did\n // we send this person\" but \"who did this design reach, and which of them\n // opened it\". Server-side because the log is a platform-level collection no\n // security rule can scope to one site's campaigns.\n registerPluginApiRoute('campaigns/recipients', campaignRecipientsHandler)\n // Relocated console route (AGL-418): the Resend/Svix webhook keeps its\n // /api/email/events URL via the dispatcher; ownership is recorded at\n // registration so the org gate attributes it to marketing, not email.\n registerPluginApiRoute('email/events', emailEventsHandler)\n registerPluginApiRoute(\n 'campaigns/process-scheduled',\n campaignProcessScheduledHandler,\n )\n // The dynamic-list sweep rides the same `*/15` fast-cron as the campaign\n // processor rather than provisioning a scheduler job of its own.\n registerPluginApiRoute('lists/materialize', listsMaterializeHandler)\n}\n"],"names":["checkEntitlement","evaluateAutoWinner","registerPluginApiRoute","registerSitePageEnricher","emailEventsHandler","registerMarketingFigureReaders","marketingSitePageEnricher","firebaseAdmin","getOrgForHost","FieldValue","campaignProcessScheduledHandler","listsMaterializeHandler","campaignManageHandler","registerCampaignDraftWriter","campaignSendHandler","campaignRecipientsHandler","trackHandler","req","res","method","status","json","error","hostId","String","body","experimentId","variantId","kind","includes","experiment","org","ok","experimentRef","app","firestore","collection","doc","get","exists","endAtMs","Number","Date","now","set","increment","updatedAt","serverTimestamp","merge","data","autoWinner","statsSnapshot","statsByVariant","docs","id","exposures","conversions","decision","update","winnerVariantId","autoCompleted","completedAt","console","registerMarketingApi","registerMarketingConsoleApi"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Site half (AGL-419): the overlays/experiments/automations runtime the
19
+ * tenant page renders generically — reads back the slices the plugin's
20
+ * server enricher wrote. Registered via the loader's 'site' surface.
21
+ */
22
+ export declare function registerMarketingPlugin(): void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { registerSiteRuntime } from "@aglyn/aglyn";
17
+ import { MarketingSiteRuntime } from "./components/site-runtime.js";
18
+ /**
19
+ * The preview slice is fetched only when a preview asks for it (AGL-1151).
20
+ *
21
+ * `loadMarketingPreviewProps` reads Firestore directly, so naming it statically
22
+ * put the whole Firestore client in this plugin's chunk — which a PUBLISHED
23
+ * site downloads and evaluates the moment marketing is enabled on it. Nothing
24
+ * on a published page ever calls this: the server enricher writes the slice
25
+ * there, and this path exists for the editor Preview alone, where no enricher
26
+ * runs.
27
+ *
28
+ * A RELATIVE deferred import, deliberately. The specifier crosses no project
29
+ * boundary, so nx records no lazy edge on the pair and
30
+ * `@nx/enforce-module-boundaries` has nothing to forbid — the distinction
31
+ * `aglyn/no-dynamic-first-party-import` exists to keep.
32
+ */ const loadMarketingPreviewProps = async (ctx)=>(await import("./preview-props.js")).loadMarketingPreviewProps(ctx);
33
+ /**
34
+ * Site half (AGL-419): the overlays/experiments/automations runtime the
35
+ * tenant page renders generically — reads back the slices the plugin's
36
+ * server enricher wrote. Registered via the loader's 'site' surface.
37
+ */ export function registerMarketingPlugin() {
38
+ registerSiteRuntime({
39
+ pluginId: 'marketing',
40
+ runtimeId: 'marketing-site-runtime',
41
+ Component: MarketingSiteRuntime,
42
+ // Editor Preview (AGL-830): rebuild the automations slice client-side so
43
+ // the same runtime drives hover menus/drawers in preview as on the site.
44
+ loadPreviewProps: loadMarketingPreviewProps
45
+ });
46
+ }
47
+
48
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/site.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { registerSiteRuntime } from '@aglyn/aglyn'\nimport { MarketingSiteRuntime } from './components/site-runtime'\n\n/**\n * The preview slice is fetched only when a preview asks for it (AGL-1151).\n *\n * `loadMarketingPreviewProps` reads Firestore directly, so naming it statically\n * put the whole Firestore client in this plugin's chunk — which a PUBLISHED\n * site downloads and evaluates the moment marketing is enabled on it. Nothing\n * on a published page ever calls this: the server enricher writes the slice\n * there, and this path exists for the editor Preview alone, where no enricher\n * runs.\n *\n * A RELATIVE deferred import, deliberately. The specifier crosses no project\n * boundary, so nx records no lazy edge on the pair and\n * `@nx/enforce-module-boundaries` has nothing to forbid — the distinction\n * `aglyn/no-dynamic-first-party-import` exists to keep.\n */\nconst loadMarketingPreviewProps: NonNullable<\n Parameters<typeof registerSiteRuntime>[0]['loadPreviewProps']\n> = async (ctx) =>\n (await import('./preview-props')).loadMarketingPreviewProps(ctx)\n\n/**\n * Site half (AGL-419): the overlays/experiments/automations runtime the\n * tenant page renders generically — reads back the slices the plugin's\n * server enricher wrote. Registered via the loader's 'site' surface.\n */\nexport function registerMarketingPlugin(): void {\n registerSiteRuntime({\n pluginId: 'marketing',\n runtimeId: 'marketing-site-runtime',\n Component: MarketingSiteRuntime,\n // Editor Preview (AGL-830): rebuild the automations slice client-side so\n // the same runtime drives hover menus/drawers in preview as on the site.\n loadPreviewProps: loadMarketingPreviewProps,\n })\n}\n"],"names":["registerSiteRuntime","MarketingSiteRuntime","loadMarketingPreviewProps","ctx","registerMarketingPlugin","pluginId","runtimeId","Component","loadPreviewProps"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,mBAAmB,QAAQ,eAAc;AAClD,SAASC,oBAAoB,QAAQ,+BAA2B;AAEhE;;;;;;;;;;;;;;CAcC,GACD,MAAMC,4BAEF,OAAOC,MACT,AAAC,CAAA,MAAM,MAAM,CAAC,qBAAiB,EAAGD,yBAAyB,CAACC;AAE9D;;;;CAIC,GACD,OAAO,SAASC;IACdJ,oBAAoB;QAClBK,UAAU;QACVC,WAAW;QACXC,WAAWN;QACX,yEAAyE;QACzE,yEAAyE;QACzEO,kBAAkBN;IACpB;AACF"}