@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.
- package/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +56 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/announcement-bar-card.component.d.ts +20 -0
- package/src/lib/components/announcement-bar-card.component.js +292 -0
- package/src/lib/components/announcement-bar-card.component.js.map +1 -0
- package/src/lib/components/campaign-composer.d.ts +84 -0
- package/src/lib/components/campaign-composer.js +1643 -0
- package/src/lib/components/campaign-composer.js.map +1 -0
- package/src/lib/components/campaign-conversions-card.d.ts +22 -0
- package/src/lib/components/campaign-conversions-card.js +642 -0
- package/src/lib/components/campaign-conversions-card.js.map +1 -0
- package/src/lib/components/campaign-detail-card.d.ts +55 -0
- package/src/lib/components/campaign-detail-card.js +771 -0
- package/src/lib/components/campaign-detail-card.js.map +1 -0
- package/src/lib/components/campaign-edit-drawer.d.ts +60 -0
- package/src/lib/components/campaign-edit-drawer.js +252 -0
- package/src/lib/components/campaign-edit-drawer.js.map +1 -0
- package/src/lib/components/campaign-email-zones.d.ts +111 -0
- package/src/lib/components/campaign-email-zones.js +54 -0
- package/src/lib/components/campaign-email-zones.js.map +1 -0
- package/src/lib/components/campaign-glance-card.component.d.ts +18 -0
- package/src/lib/components/campaign-glance-card.component.js +151 -0
- package/src/lib/components/campaign-glance-card.component.js.map +1 -0
- package/src/lib/components/campaign-members-section.d.ts +87 -0
- package/src/lib/components/campaign-members-section.js +499 -0
- package/src/lib/components/campaign-members-section.js.map +1 -0
- package/src/lib/components/campaign-reach-sections.d.ts +137 -0
- package/src/lib/components/campaign-reach-sections.js +694 -0
- package/src/lib/components/campaign-reach-sections.js.map +1 -0
- package/src/lib/components/campaign-report-card.d.ts +91 -0
- package/src/lib/components/campaign-report-card.js +667 -0
- package/src/lib/components/campaign-report-card.js.map +1 -0
- package/src/lib/components/campaign-test-send-drawer.d.ts +45 -0
- package/src/lib/components/campaign-test-send-drawer.js +238 -0
- package/src/lib/components/campaign-test-send-drawer.js.map +1 -0
- package/src/lib/components/campaigns-card.d.ts +31 -0
- package/src/lib/components/campaigns-card.js +667 -0
- package/src/lib/components/campaigns-card.js.map +1 -0
- package/src/lib/components/conversion-attribution.component.d.ts +62 -0
- package/src/lib/components/conversion-attribution.component.js +127 -0
- package/src/lib/components/conversion-attribution.component.js.map +1 -0
- package/src/lib/components/email-compose-card.d.ts +12 -0
- package/src/lib/components/email-compose-card.js +157 -0
- package/src/lib/components/email-compose-card.js.map +1 -0
- package/src/lib/components/email-detail.d.ts +40 -0
- package/src/lib/components/email-detail.js +1180 -0
- package/src/lib/components/email-detail.js.map +1 -0
- package/src/lib/components/email-edit-drawer.d.ts +54 -0
- package/src/lib/components/email-edit-drawer.js +172 -0
- package/src/lib/components/email-edit-drawer.js.map +1 -0
- package/src/lib/components/email-messages-widget.d.ts +22 -0
- package/src/lib/components/email-messages-widget.js +49 -0
- package/src/lib/components/email-messages-widget.js.map +1 -0
- package/src/lib/components/email-recipients-card.d.ts +50 -0
- package/src/lib/components/email-recipients-card.js +338 -0
- package/src/lib/components/email-recipients-card.js.map +1 -0
- package/src/lib/components/emails-list-card.d.ts +36 -0
- package/src/lib/components/emails-list-card.js +544 -0
- package/src/lib/components/emails-list-card.js.map +1 -0
- package/src/lib/components/experiment-zones.d.ts +78 -0
- package/src/lib/components/experiment-zones.js +65 -0
- package/src/lib/components/experiment-zones.js.map +1 -0
- package/src/lib/components/host-experiments-card.component.d.ts +17 -0
- package/src/lib/components/host-experiments-card.component.js +887 -0
- package/src/lib/components/host-experiments-card.component.js.map +1 -0
- package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
- package/src/lib/components/host-marketing-summary-card.component.js +161 -0
- package/src/lib/components/host-marketing-summary-card.component.js.map +1 -0
- package/src/lib/components/host-overlays-card.component.d.ts +19 -0
- package/src/lib/components/host-overlays-card.component.js +783 -0
- package/src/lib/components/host-overlays-card.component.js.map +1 -0
- package/src/lib/components/marketing-console-page.d.ts +28 -0
- package/src/lib/components/marketing-console-page.js +166 -0
- package/src/lib/components/marketing-console-page.js.map +1 -0
- package/src/lib/components/marketing-console-sections.d.ts +34 -0
- package/src/lib/components/marketing-console-sections.js +77 -0
- package/src/lib/components/marketing-console-sections.js.map +1 -0
- package/src/lib/components/overlay-stats-row.component.d.ts +15 -0
- package/src/lib/components/overlay-stats-row.component.js +78 -0
- package/src/lib/components/overlay-stats-row.component.js.map +1 -0
- package/src/lib/components/popup-card.component.d.ts +18 -0
- package/src/lib/components/popup-card.component.js +462 -0
- package/src/lib/components/popup-card.component.js.map +1 -0
- package/src/lib/components/record-attribution-widget.d.ts +35 -0
- package/src/lib/components/record-attribution-widget.js +33 -0
- package/src/lib/components/record-attribution-widget.js.map +1 -0
- package/src/lib/components/site-runtime.d.ts +9 -0
- package/src/lib/components/site-runtime.js +1081 -0
- package/src/lib/components/site-runtime.js.map +1 -0
- package/src/lib/components/use-campaign-send-api.d.ts +35 -0
- package/src/lib/components/use-campaign-send-api.js +99 -0
- package/src/lib/components/use-campaign-send-api.js.map +1 -0
- package/src/lib/components/use-campaign-topic-options.d.ts +20 -0
- package/src/lib/components/use-campaign-topic-options.js +44 -0
- package/src/lib/components/use-campaign-topic-options.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +39 -0
- package/src/lib/components/use-emails-hub-path.js +70 -0
- package/src/lib/components/use-emails-hub-path.js.map +1 -0
- package/src/lib/components/use-marketing-hub-path.d.ts +21 -0
- package/src/lib/components/use-marketing-hub-path.js +50 -0
- package/src/lib/components/use-marketing-hub-path.js.map +1 -0
- package/src/lib/components/use-overlay-copy-editor.d.ts +79 -0
- package/src/lib/components/use-overlay-copy-editor.js +110 -0
- package/src/lib/components/use-overlay-copy-editor.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/model/campaign-membership-figures.d.ts +109 -0
- package/src/lib/model/campaign-membership-figures.js +113 -0
- package/src/lib/model/campaign-membership-figures.js.map +1 -0
- package/src/lib/model/compile-client-automations.d.ts +65 -0
- package/src/lib/model/compile-client-automations.js +95 -0
- package/src/lib/model/compile-client-automations.js.map +1 -0
- package/src/lib/model/experiments.d.ts +130 -0
- package/src/lib/model/experiments.js +186 -0
- package/src/lib/model/experiments.js.map +1 -0
- package/src/lib/model/index.d.ts +26 -0
- package/src/lib/model/index.js +26 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/marketing-record-routes.d.ts +6 -0
- package/src/lib/model/marketing-record-routes.js +46 -0
- package/src/lib/model/marketing-record-routes.js.map +1 -0
- package/src/lib/model/overlay-copy.d.ts +62 -0
- package/src/lib/model/overlay-copy.js +78 -0
- package/src/lib/model/overlay-copy.js.map +1 -0
- package/src/lib/model/overlays.d.ts +113 -0
- package/src/lib/model/overlays.js +93 -0
- package/src/lib/model/overlays.js.map +1 -0
- package/src/lib/model/site-contract.d.ts +100 -0
- package/src/lib/model/site-contract.js +23 -0
- package/src/lib/model/site-contract.js.map +1 -0
- package/src/lib/plugin.d.ts +27 -0
- package/src/lib/plugin.js +168 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/preview-props.d.ts +31 -0
- package/src/lib/preview-props.js +54 -0
- package/src/lib/preview-props.js.map +1 -0
- package/src/lib/server/campaign-manage.d.ts +62 -0
- package/src/lib/server/campaign-manage.js +829 -0
- package/src/lib/server/campaign-manage.js.map +1 -0
- package/src/lib/server/campaign-process-scheduled.d.ts +26 -0
- package/src/lib/server/campaign-process-scheduled.js +198 -0
- package/src/lib/server/campaign-process-scheduled.js.map +1 -0
- package/src/lib/server/campaign-recipients.d.ts +55 -0
- package/src/lib/server/campaign-recipients.js +219 -0
- package/src/lib/server/campaign-recipients.js.map +1 -0
- package/src/lib/server/campaign-send.d.ts +487 -0
- package/src/lib/server/campaign-send.js +3157 -0
- package/src/lib/server/campaign-send.js.map +1 -0
- package/src/lib/server/email-campaign-reach.d.ts +174 -0
- package/src/lib/server/email-campaign-reach.js +254 -0
- package/src/lib/server/email-campaign-reach.js.map +1 -0
- package/src/lib/server/email-events.d.ts +27 -0
- package/src/lib/server/email-events.js +714 -0
- package/src/lib/server/email-events.js.map +1 -0
- package/src/lib/server/get-client-automations.d.ts +60 -0
- package/src/lib/server/get-client-automations.js +87 -0
- package/src/lib/server/get-client-automations.js.map +1 -0
- package/src/lib/server/get-overlays.d.ts +28 -0
- package/src/lib/server/get-overlays.js +65 -0
- package/src/lib/server/get-overlays.js.map +1 -0
- package/src/lib/server/get-screen-experiments.d.ts +42 -0
- package/src/lib/server/get-screen-experiments.js +82 -0
- package/src/lib/server/get-screen-experiments.js.map +1 -0
- package/src/lib/server/lists-materialize.d.ts +18 -0
- package/src/lib/server/lists-materialize.js +130 -0
- package/src/lib/server/lists-materialize.js.map +1 -0
- package/src/lib/server/marketing-figures.d.ts +37 -0
- package/src/lib/server/marketing-figures.js +248 -0
- package/src/lib/server/marketing-figures.js.map +1 -0
- package/src/lib/server/site-page-enricher.d.ts +26 -0
- package/src/lib/server/site-page-enricher.js +210 -0
- package/src/lib/server/site-page-enricher.js.map +1 -0
- package/src/lib/server.d.ts +20 -0
- package/src/lib/server.js +152 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +22 -0
- package/src/lib/site.js +48 -0
- 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;
|
package/src/lib/site.js
ADDED
|
@@ -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"}
|