@aglyn/aglyn 1.0.0-beta.233 → 1.0.0-beta.235
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/package.json +11 -11
- package/src/lib/app-utils/activity-labels.d.ts +117 -0
- package/src/lib/app-utils/activity-labels.js +343 -0
- package/src/lib/app-utils/activity-labels.js.map +1 -0
- package/src/lib/app-utils/admin-audit-index.d.ts +11 -0
- package/src/lib/app-utils/admin-audit-index.js +12 -1
- package/src/lib/app-utils/admin-audit-index.js.map +1 -1
- package/src/lib/app-utils/advertising-consent.d.ts +111 -0
- package/src/lib/app-utils/advertising-consent.js +216 -0
- package/src/lib/app-utils/advertising-consent.js.map +1 -0
- package/src/lib/app-utils/advertising-events.d.ts +98 -0
- package/src/lib/app-utils/advertising-events.js +340 -0
- package/src/lib/app-utils/advertising-events.js.map +1 -0
- package/src/lib/app-utils/advertising-tag-mounts.js +15 -8
- package/src/lib/app-utils/advertising-tag-mounts.js.map +1 -1
- package/src/lib/app-utils/advertising-tags.d.ts +54 -8
- package/src/lib/app-utils/advertising-tags.js +126 -28
- package/src/lib/app-utils/advertising-tags.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +15 -86
- package/src/lib/app-utils/analytics-events.js +21 -6
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/consent-banner-ui.d.ts +14 -0
- package/src/lib/app-utils/consent-banner-ui.js +23 -2
- package/src/lib/app-utils/consent-banner-ui.js.map +1 -1
- package/src/lib/app-utils/docs-help-section-excerpt-text.d.ts +14 -0
- package/src/lib/app-utils/docs-help-section-excerpt-text.js +31 -0
- package/src/lib/app-utils/docs-help-section-excerpt-text.js.map +1 -0
- package/src/lib/app-utils/docs-help-section-excerpt.d.ts +13 -0
- package/src/lib/app-utils/docs-help-section-excerpt.js +37 -0
- package/src/lib/app-utils/docs-help-section-excerpt.js.map +1 -0
- package/src/lib/app-utils/docs-help-sections.generated.d.ts +31 -0
- package/src/lib/app-utils/docs-help-sections.generated.js +660 -0
- package/src/lib/app-utils/docs-help-sections.generated.js.map +1 -0
- package/src/lib/app-utils/docs-help.d.ts +16 -4
- package/src/lib/app-utils/docs-help.generated.d.ts +171 -34
- package/src/lib/app-utils/docs-help.generated.js +1104 -3
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-help.js +25 -6
- package/src/lib/app-utils/docs-help.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.js +1008 -91
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/health-report.js +12 -0
- package/src/lib/app-utils/health-report.js.map +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +1 -1
- package/src/lib/app-utils/plugin-release-flags.generated.js +7 -1
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -1
- package/src/lib/app-utils/realm-host-surface.generated.js +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/variables.d.ts +2 -0
- package/src/lib/app-utils/variables.js +3 -0
- package/src/lib/app-utils/variables.js.map +1 -1
- package/src/lib/app-utils/visitor-consent.d.ts +34 -0
- package/src/lib/app-utils/visitor-consent.js +49 -2
- package/src/lib/app-utils/visitor-consent.js.map +1 -1
- package/src/lib/app-utils/where-used-summary.d.ts +41 -0
- package/src/lib/app-utils/where-used-summary.js +33 -0
- package/src/lib/app-utils/where-used-summary.js.map +1 -0
- package/src/lib/app-utils/where-used.d.ts +2 -20
- package/src/lib/app-utils/where-used.js +13 -12
- package/src/lib/app-utils/where-used.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +33 -0
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +7 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +166 -2
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +51 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js +43 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-advertising-conversions.d.ts +84 -0
- package/src/lib/plugin-manager/plugin-advertising-conversions.js +86 -0
- package/src/lib/plugin-manager/plugin-advertising-conversions.js.map +1 -0
- package/src/lib/plugin-manager/plugin-config.d.ts +23 -0
- package/src/lib/plugin-manager/plugin-config.js.map +1 -1
- package/src/lib/plugin-manager/plugin-local-deliveries.d.ts +161 -0
- package/src/lib/plugin-manager/plugin-local-deliveries.js +46 -0
- package/src/lib/plugin-manager/plugin-local-deliveries.js.map +1 -0
- package/src/lib/plugin-manager/plugin-media-ingest.d.ts +87 -0
- package/src/lib/plugin-manager/plugin-media-ingest.js +34 -0
- package/src/lib/plugin-manager/plugin-media-ingest.js.map +1 -0
- package/src/lib/plugin-manager/plugin-order-email-copies.d.ts +93 -0
- package/src/lib/plugin-manager/plugin-order-email-copies.js +104 -0
- package/src/lib/plugin-manager/plugin-order-email-copies.js.map +1 -0
- package/src/lib/plugin-manager/plugin-shipment-records.d.ts +7 -0
- package/src/lib/plugin-manager/plugin-shipment-records.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-csp.d.ts +58 -0
- package/src/lib/plugin-manager/plugin-site-csp.js +103 -0
- package/src/lib/plugin-manager/plugin-site-csp.js.map +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +1 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/site-page-hooks.d.ts +13 -0
- package/src/lib/plugin-manager/site-page-hooks.js +31 -0
- package/src/lib/plugin-manager/site-page-hooks.js.map +1 -1
- package/src/lib/plugin-manager/stock-photo-provider.d.ts +141 -0
- package/src/lib/plugin-manager/stock-photo-provider.js +59 -0
- package/src/lib/plugin-manager/stock-photo-provider.js.map +1 -0
|
@@ -0,0 +1,340 @@
|
|
|
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
|
+
*/ /**
|
|
18
|
+
* Conversion events for a site's OWN advertising tags (AGL-3694).
|
|
19
|
+
*
|
|
20
|
+
* ## What this is
|
|
21
|
+
*
|
|
22
|
+
* The analytics taxonomy (`analytics-events.ts`) already says what a visitor
|
|
23
|
+
* did — `purchase`, `generate_lead`, `begin_checkout`, `add_to_cart`,
|
|
24
|
+
* `view_item` — and delivers it to the site's Google tag. A merchant who runs
|
|
25
|
+
* a Meta pixel, a TikTok pixel or a Pinterest tag wants the same moments in
|
|
26
|
+
* those accounts, under each vendor's own standard event name. This module is
|
|
27
|
+
* the translation and the delivery, and nothing else: it never loads a tag.
|
|
28
|
+
*
|
|
29
|
+
* ## Why it cannot fire before consent
|
|
30
|
+
*
|
|
31
|
+
* It sends only to a tag that is ALREADY IN THE DOCUMENT and carries both of
|
|
32
|
+
* this module's marks: {@link ADVERTISING_TAG_ATTRIBUTE}, which only
|
|
33
|
+
* `advertising-tag-mounts.tsx` writes and only where `resolveAdvertisingTags`
|
|
34
|
+
* said the visitor granted advertising, and {@link ADVERTISING_EVENTS_ATTRIBUTE},
|
|
35
|
+
* which the tenant writes only on a merchant's own site. No mark, no call: a
|
|
36
|
+
* visitor who did not grant has no tag, and an event raised for them goes
|
|
37
|
+
* nowhere, exactly as the Google path drops one when `gtag` is absent. A
|
|
38
|
+
* pixel a merchant pasted into Custom HTML carries neither mark and is never
|
|
39
|
+
* called — it runs on a basis that is not ours.
|
|
40
|
+
*
|
|
41
|
+
* Aglyn's own surfaces carry the first mark and never the second, so their
|
|
42
|
+
* tags keep reporting only the page views they always have.
|
|
43
|
+
*
|
|
44
|
+
* ## The event id, and why both halves derive it
|
|
45
|
+
*
|
|
46
|
+
* A purchase or a lead can reach a vendor twice: from this browser call and
|
|
47
|
+
* from the server's Conversions API (the ad-conversions plugin). Each vendor
|
|
48
|
+
* de-duplicates the pair by an event id the two sides share, so the id is
|
|
49
|
+
* DERIVED rather than minted wherever both sides can know the same key:
|
|
50
|
+
* {@link advertisingEventId} turns a purchase's transaction id, or a lead's
|
|
51
|
+
* id, into the one string both send.
|
|
52
|
+
*
|
|
53
|
+
* Kept free of the vendor descriptors on purpose: the console imports the
|
|
54
|
+
* analytics taxonomy, which imports this, and the console may not carry the
|
|
55
|
+
* module that mounts a vendor's script.
|
|
56
|
+
*/ /**
|
|
57
|
+
* The attribute every script element the advertising gate renders carries; its
|
|
58
|
+
* value is the vendor id. Declared here and re-exported by
|
|
59
|
+
* `advertising-tags.ts`, so the event delivery below can find a tag without
|
|
60
|
+
* importing the module that mounts one.
|
|
61
|
+
*/ export const ADVERTISING_TAG_ATTRIBUTE = 'data-aglyn-ad-tag';
|
|
62
|
+
/**
|
|
63
|
+
* The second mark (AGL-3694): this tag is a SITE OWNER's, mounted on their own
|
|
64
|
+
* site, and takes the site's conversion events. Aglyn's own surfaces never
|
|
65
|
+
* write it.
|
|
66
|
+
*/ export const ADVERTISING_EVENTS_ATTRIBUTE = 'data-aglyn-ad-events';
|
|
67
|
+
const EVENT_KEY = /^[A-Za-z0-9_.:-]{1,120}$/;
|
|
68
|
+
/**
|
|
69
|
+
* The event id the browser tag and the server's Conversions API both send for
|
|
70
|
+
* one conversion, or `null` for a key that cannot be one. `purchase` takes the
|
|
71
|
+
* order's transaction id (the Stripe Checkout Session id the order is stored
|
|
72
|
+
* under); `lead` takes the id the form minted for its submission.
|
|
73
|
+
*/ export function advertisingEventId(kind, key) {
|
|
74
|
+
const value = String(key != null ? key : '').trim();
|
|
75
|
+
if (!EVENT_KEY.test(value)) return null;
|
|
76
|
+
return `${kind}.${value}`;
|
|
77
|
+
}
|
|
78
|
+
/** A fresh id for a lead, minted where the form is submitted. */ export function mintAdvertisingLeadKey() {
|
|
79
|
+
try {
|
|
80
|
+
var _globalThis_crypto_randomUUID, _globalThis_crypto;
|
|
81
|
+
const id = (_globalThis_crypto = globalThis.crypto) == null ? void 0 : (_globalThis_crypto_randomUUID = _globalThis_crypto.randomUUID) == null ? void 0 : _globalThis_crypto_randomUUID.call(_globalThis_crypto);
|
|
82
|
+
if (id) return id;
|
|
83
|
+
} catch (unused) {
|
|
84
|
+
// Fall through to the time-based id below.
|
|
85
|
+
}
|
|
86
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
|
|
87
|
+
}
|
|
88
|
+
function itemsOf(params) {
|
|
89
|
+
const items = Array.isArray(params['items']) ? params['items'] : [];
|
|
90
|
+
return items.flatMap((entry)=>{
|
|
91
|
+
var _item_item_id, _item_quantity;
|
|
92
|
+
const item = entry != null ? entry : {};
|
|
93
|
+
const id = String((_item_item_id = item['item_id']) != null ? _item_item_id : '').trim();
|
|
94
|
+
if (!id) return [];
|
|
95
|
+
const price = Number(item['price']);
|
|
96
|
+
const quantity = Math.max(1, Math.floor(Number((_item_quantity = item['quantity']) != null ? _item_quantity : 1)) || 1);
|
|
97
|
+
return [
|
|
98
|
+
_extends({
|
|
99
|
+
id
|
|
100
|
+
}, typeof item['item_name'] === 'string' ? {
|
|
101
|
+
name: item['item_name']
|
|
102
|
+
} : {}, Number.isFinite(price) ? {
|
|
103
|
+
price
|
|
104
|
+
} : {}, {
|
|
105
|
+
quantity
|
|
106
|
+
})
|
|
107
|
+
];
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function money(params) {
|
|
111
|
+
const value = Number(params['value']);
|
|
112
|
+
const currency = typeof params['currency'] === 'string' ? params['currency'].toUpperCase() : '';
|
|
113
|
+
return Number.isFinite(value) && currency ? {
|
|
114
|
+
value,
|
|
115
|
+
currency
|
|
116
|
+
} : {};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Each vendor's standard event for each taxonomy event, in that vendor's own
|
|
120
|
+
* parameter shape. An event a vendor has no standard name for is not sent to
|
|
121
|
+
* it at all rather than invented.
|
|
122
|
+
*/ const TRANSLATIONS = {
|
|
123
|
+
meta: {
|
|
124
|
+
purchase: (params)=>{
|
|
125
|
+
const items = itemsOf(params);
|
|
126
|
+
return {
|
|
127
|
+
name: 'Purchase',
|
|
128
|
+
params: _extends({}, money(params), {
|
|
129
|
+
content_type: 'product',
|
|
130
|
+
content_ids: items.map((item)=>item.id),
|
|
131
|
+
contents: items.map((item)=>({
|
|
132
|
+
id: item.id,
|
|
133
|
+
quantity: item.quantity,
|
|
134
|
+
item_price: item.price
|
|
135
|
+
})),
|
|
136
|
+
num_items: items.reduce((sum, item)=>sum + item.quantity, 0)
|
|
137
|
+
})
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
generate_lead: ()=>({
|
|
141
|
+
name: 'Lead',
|
|
142
|
+
params: {}
|
|
143
|
+
}),
|
|
144
|
+
begin_checkout: (params)=>{
|
|
145
|
+
const items = itemsOf(params);
|
|
146
|
+
return {
|
|
147
|
+
name: 'InitiateCheckout',
|
|
148
|
+
params: _extends({}, money(params), {
|
|
149
|
+
content_ids: items.map((item)=>item.id),
|
|
150
|
+
num_items: items.length
|
|
151
|
+
})
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
add_to_cart: (params)=>({
|
|
155
|
+
name: 'AddToCart',
|
|
156
|
+
params: _extends({}, money(params), {
|
|
157
|
+
content_type: 'product',
|
|
158
|
+
content_ids: itemsOf(params).map((item)=>item.id)
|
|
159
|
+
})
|
|
160
|
+
}),
|
|
161
|
+
view_item: (params)=>({
|
|
162
|
+
name: 'ViewContent',
|
|
163
|
+
params: {
|
|
164
|
+
content_type: 'product',
|
|
165
|
+
content_ids: itemsOf(params).map((item)=>item.id)
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
},
|
|
169
|
+
tiktok: {
|
|
170
|
+
purchase: (params)=>({
|
|
171
|
+
name: 'CompletePayment',
|
|
172
|
+
params: _extends({}, money(params), {
|
|
173
|
+
content_type: 'product',
|
|
174
|
+
contents: itemsOf(params).map((item)=>({
|
|
175
|
+
content_id: item.id,
|
|
176
|
+
content_name: item.name,
|
|
177
|
+
quantity: item.quantity,
|
|
178
|
+
price: item.price
|
|
179
|
+
}))
|
|
180
|
+
})
|
|
181
|
+
}),
|
|
182
|
+
generate_lead: ()=>({
|
|
183
|
+
name: 'SubmitForm',
|
|
184
|
+
params: {}
|
|
185
|
+
}),
|
|
186
|
+
begin_checkout: (params)=>({
|
|
187
|
+
name: 'InitiateCheckout',
|
|
188
|
+
params: _extends({}, money(params), {
|
|
189
|
+
contents: itemsOf(params).map((item)=>({
|
|
190
|
+
content_id: item.id,
|
|
191
|
+
quantity: item.quantity
|
|
192
|
+
}))
|
|
193
|
+
})
|
|
194
|
+
}),
|
|
195
|
+
add_to_cart: (params)=>({
|
|
196
|
+
name: 'AddToCart',
|
|
197
|
+
params: _extends({}, money(params), {
|
|
198
|
+
content_type: 'product',
|
|
199
|
+
contents: itemsOf(params).map((item)=>({
|
|
200
|
+
content_id: item.id,
|
|
201
|
+
quantity: item.quantity
|
|
202
|
+
}))
|
|
203
|
+
})
|
|
204
|
+
}),
|
|
205
|
+
view_item: (params)=>({
|
|
206
|
+
name: 'ViewContent',
|
|
207
|
+
params: {
|
|
208
|
+
content_type: 'product',
|
|
209
|
+
contents: itemsOf(params).map((item)=>({
|
|
210
|
+
content_id: item.id
|
|
211
|
+
}))
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
},
|
|
215
|
+
pinterest: {
|
|
216
|
+
purchase: (params)=>{
|
|
217
|
+
const items = itemsOf(params);
|
|
218
|
+
return {
|
|
219
|
+
name: 'checkout',
|
|
220
|
+
params: _extends({}, money(params), {
|
|
221
|
+
order_id: typeof params['transaction_id'] === 'string' ? params['transaction_id'] : undefined,
|
|
222
|
+
order_quantity: items.reduce((sum, item)=>sum + item.quantity, 0),
|
|
223
|
+
line_items: items.map((item)=>({
|
|
224
|
+
product_id: item.id,
|
|
225
|
+
product_name: item.name,
|
|
226
|
+
product_price: item.price,
|
|
227
|
+
product_quantity: item.quantity
|
|
228
|
+
}))
|
|
229
|
+
})
|
|
230
|
+
};
|
|
231
|
+
},
|
|
232
|
+
generate_lead: ()=>({
|
|
233
|
+
name: 'lead',
|
|
234
|
+
params: {}
|
|
235
|
+
}),
|
|
236
|
+
add_to_cart: (params)=>{
|
|
237
|
+
const items = itemsOf(params);
|
|
238
|
+
return {
|
|
239
|
+
name: 'addtocart',
|
|
240
|
+
params: _extends({}, money(params), {
|
|
241
|
+
order_quantity: items.reduce((sum, item)=>sum + item.quantity, 0),
|
|
242
|
+
line_items: items.map((item)=>({
|
|
243
|
+
product_id: item.id,
|
|
244
|
+
product_quantity: item.quantity
|
|
245
|
+
}))
|
|
246
|
+
})
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
/** The vendors that take conversion events, by `analytics.adTags` key. */ export const ADVERTISING_EVENT_VENDORS = Object.keys(TRANSLATIONS);
|
|
252
|
+
const SENDERS = {
|
|
253
|
+
// `fbq('track', name, params, { eventID })` is Meta's documented
|
|
254
|
+
// de-duplication call: the fourth argument pairs this hit with the
|
|
255
|
+
// Conversions API event carrying the same `event_id`.
|
|
256
|
+
meta: (scope, call, eventId)=>{
|
|
257
|
+
const fbq = scope['fbq'];
|
|
258
|
+
if (typeof fbq === 'function') {
|
|
259
|
+
;
|
|
260
|
+
fbq('track', call.name, call.params, {
|
|
261
|
+
eventID: eventId
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
// `event_id` in the third argument is TikTok's de-duplication key against
|
|
266
|
+
// the Events API.
|
|
267
|
+
tiktok: (scope, call, eventId)=>{
|
|
268
|
+
const ttq = scope['ttq'];
|
|
269
|
+
if (ttq && typeof ttq.track === 'function') {
|
|
270
|
+
;
|
|
271
|
+
ttq.track.call(ttq, call.name, call.params, {
|
|
272
|
+
event_id: eventId
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
// Pinterest reads `event_id` from the event's own data.
|
|
277
|
+
pinterest: (scope, call, eventId)=>{
|
|
278
|
+
const pintrk = scope['pintrk'];
|
|
279
|
+
if (typeof pintrk === 'function') {
|
|
280
|
+
;
|
|
281
|
+
pintrk('track', call.name, _extends({}, call.params, {
|
|
282
|
+
event_id: eventId
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
/** Whether this document holds a merchant's own tag for `vendorId`, mounted by the gate. */ function merchantTagResident(vendorId) {
|
|
288
|
+
if (typeof document === 'undefined') return false;
|
|
289
|
+
try {
|
|
290
|
+
return Boolean(document.querySelector(`script[${ADVERTISING_TAG_ATTRIBUTE}="${vendorId}"][${ADVERTISING_EVENTS_ATTRIBUTE}]`));
|
|
291
|
+
} catch (unused) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/** Whether any merchant tag that takes conversion events is in the document. */ export function merchantAdvertisingTagsResident() {
|
|
296
|
+
return ADVERTISING_EVENT_VENDORS.some(merchantTagResident);
|
|
297
|
+
}
|
|
298
|
+
/** The id one browser event is sent under, for the vendors' de-duplication. */ function eventIdFor(name, params, explicit) {
|
|
299
|
+
if (explicit) return explicit;
|
|
300
|
+
if (name === 'purchase') {
|
|
301
|
+
var _params_transaction_id;
|
|
302
|
+
const derived = advertisingEventId('purchase', String((_params_transaction_id = params['transaction_id']) != null ? _params_transaction_id : ''));
|
|
303
|
+
if (derived) return derived;
|
|
304
|
+
}
|
|
305
|
+
// No server counterpart: an id of its own, so a vendor still sees one.
|
|
306
|
+
return `${name}.${mintAdvertisingLeadKey()}`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Sends one taxonomy event to every merchant tag in the document that takes
|
|
310
|
+
* it, and answers the vendor ids it reached. Never throws and never queues:
|
|
311
|
+
* with no marked tag — the visitor did not grant advertising, or the site runs
|
|
312
|
+
* none — it does nothing at all.
|
|
313
|
+
*
|
|
314
|
+
* `options.eventId` is the shared id for a conversion the server also reports
|
|
315
|
+
* (a lead); a purchase derives its own from `transaction_id`.
|
|
316
|
+
*/ export function sendAdvertisingEvent(name, params, options = {}) {
|
|
317
|
+
if (typeof window === 'undefined') return [];
|
|
318
|
+
const reached = [];
|
|
319
|
+
const scope = window;
|
|
320
|
+
let eventId = null;
|
|
321
|
+
for (const vendorId of ADVERTISING_EVENT_VENDORS){
|
|
322
|
+
var _TRANSLATIONS_vendorId;
|
|
323
|
+
const translate = (_TRANSLATIONS_vendorId = TRANSLATIONS[vendorId]) == null ? void 0 : _TRANSLATIONS_vendorId[name];
|
|
324
|
+
if (!translate || !merchantTagResident(vendorId)) continue;
|
|
325
|
+
try {
|
|
326
|
+
var _options_eventId;
|
|
327
|
+
var _SENDERS_vendorId;
|
|
328
|
+
const call = translate(params);
|
|
329
|
+
if (!call) continue;
|
|
330
|
+
eventId != null ? eventId : eventId = eventIdFor(name, params, (_options_eventId = options.eventId) != null ? _options_eventId : undefined);
|
|
331
|
+
(_SENDERS_vendorId = SENDERS[vendorId]) == null ? void 0 : _SENDERS_vendorId.call(SENDERS, scope, call, eventId);
|
|
332
|
+
reached.push(vendorId);
|
|
333
|
+
} catch (unused) {
|
|
334
|
+
// A vendor whose own call throws is skipped; the others still hear it.
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return reached;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
//# sourceMappingURL=advertising-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/advertising-events.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\n/**\n * Conversion events for a site's OWN advertising tags (AGL-3694).\n *\n * ## What this is\n *\n * The analytics taxonomy (`analytics-events.ts`) already says what a visitor\n * did — `purchase`, `generate_lead`, `begin_checkout`, `add_to_cart`,\n * `view_item` — and delivers it to the site's Google tag. A merchant who runs\n * a Meta pixel, a TikTok pixel or a Pinterest tag wants the same moments in\n * those accounts, under each vendor's own standard event name. This module is\n * the translation and the delivery, and nothing else: it never loads a tag.\n *\n * ## Why it cannot fire before consent\n *\n * It sends only to a tag that is ALREADY IN THE DOCUMENT and carries both of\n * this module's marks: {@link ADVERTISING_TAG_ATTRIBUTE}, which only\n * `advertising-tag-mounts.tsx` writes and only where `resolveAdvertisingTags`\n * said the visitor granted advertising, and {@link ADVERTISING_EVENTS_ATTRIBUTE},\n * which the tenant writes only on a merchant's own site. No mark, no call: a\n * visitor who did not grant has no tag, and an event raised for them goes\n * nowhere, exactly as the Google path drops one when `gtag` is absent. A\n * pixel a merchant pasted into Custom HTML carries neither mark and is never\n * called — it runs on a basis that is not ours.\n *\n * Aglyn's own surfaces carry the first mark and never the second, so their\n * tags keep reporting only the page views they always have.\n *\n * ## The event id, and why both halves derive it\n *\n * A purchase or a lead can reach a vendor twice: from this browser call and\n * from the server's Conversions API (the ad-conversions plugin). Each vendor\n * de-duplicates the pair by an event id the two sides share, so the id is\n * DERIVED rather than minted wherever both sides can know the same key:\n * {@link advertisingEventId} turns a purchase's transaction id, or a lead's\n * id, into the one string both send.\n *\n * Kept free of the vendor descriptors on purpose: the console imports the\n * analytics taxonomy, which imports this, and the console may not carry the\n * module that mounts a vendor's script.\n */\n\n/**\n * The attribute every script element the advertising gate renders carries; its\n * value is the vendor id. Declared here and re-exported by\n * `advertising-tags.ts`, so the event delivery below can find a tag without\n * importing the module that mounts one.\n */\nexport const ADVERTISING_TAG_ATTRIBUTE = 'data-aglyn-ad-tag'\n\n/**\n * The second mark (AGL-3694): this tag is a SITE OWNER's, mounted on their own\n * site, and takes the site's conversion events. Aglyn's own surfaces never\n * write it.\n */\nexport const ADVERTISING_EVENTS_ATTRIBUTE = 'data-aglyn-ad-events'\n\n/** The conversions a server also reports, and so share an id with it. */\nexport type AdvertisingEventKind = 'purchase' | 'lead'\n\nconst EVENT_KEY = /^[A-Za-z0-9_.:-]{1,120}$/\n\n/**\n * The event id the browser tag and the server's Conversions API both send for\n * one conversion, or `null` for a key that cannot be one. `purchase` takes the\n * order's transaction id (the Stripe Checkout Session id the order is stored\n * under); `lead` takes the id the form minted for its submission.\n */\nexport function advertisingEventId(\n kind: AdvertisingEventKind,\n key: string | null | undefined,\n): string | null {\n const value = String(key ?? '').trim()\n if (!EVENT_KEY.test(value)) return null\n return `${kind}.${value}`\n}\n\n/** A fresh id for a lead, minted where the form is submitted. */\nexport function mintAdvertisingLeadKey(): string {\n try {\n const id = globalThis.crypto?.randomUUID?.()\n if (id) return id\n } catch {\n // Fall through to the time-based id below.\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`\n}\n\ntype Params = Record<string, unknown>\n\ninterface EventItem {\n id: string\n name?: string\n price?: number\n quantity: number\n}\n\nfunction itemsOf(params: Params): EventItem[] {\n const items = Array.isArray(params['items']) ? params['items'] : []\n return items.flatMap((entry) => {\n const item = (entry ?? {}) as Params\n const id = String(item['item_id'] ?? '').trim()\n if (!id) return []\n const price = Number(item['price'])\n const quantity = Math.max(1, Math.floor(Number(item['quantity'] ?? 1)) || 1)\n return [\n {\n id,\n ...(typeof item['item_name'] === 'string' ? { name: item['item_name'] } : {}),\n ...(Number.isFinite(price) ? { price } : {}),\n quantity,\n },\n ]\n })\n}\n\nfunction money(params: Params): { value?: number; currency?: string } {\n const value = Number(params['value'])\n const currency = typeof params['currency'] === 'string' ? params['currency'].toUpperCase() : ''\n return Number.isFinite(value) && currency ? { value, currency } : {}\n}\n\n/** One vendor's call for one event: its event name and its own parameters. */\ninterface VendorCall {\n name: string\n params: Params\n}\n\ntype Translate = (params: Params) => VendorCall | null\n\n/**\n * Each vendor's standard event for each taxonomy event, in that vendor's own\n * parameter shape. An event a vendor has no standard name for is not sent to\n * it at all rather than invented.\n */\nconst TRANSLATIONS: Readonly<Record<string, Readonly<Record<string, Translate>>>> = {\n meta: {\n purchase: (params) => {\n const items = itemsOf(params)\n return {\n name: 'Purchase',\n params: {\n ...money(params),\n content_type: 'product',\n content_ids: items.map((item) => item.id),\n contents: items.map((item) => ({ id: item.id, quantity: item.quantity, item_price: item.price })),\n num_items: items.reduce((sum, item) => sum + item.quantity, 0),\n },\n }\n },\n generate_lead: () => ({ name: 'Lead', params: {} }),\n begin_checkout: (params) => {\n const items = itemsOf(params)\n return {\n name: 'InitiateCheckout',\n params: { ...money(params), content_ids: items.map((item) => item.id), num_items: items.length },\n }\n },\n add_to_cart: (params) => ({\n name: 'AddToCart',\n params: { ...money(params), content_type: 'product', content_ids: itemsOf(params).map((item) => item.id) },\n }),\n view_item: (params) => ({\n name: 'ViewContent',\n params: { content_type: 'product', content_ids: itemsOf(params).map((item) => item.id) },\n }),\n },\n tiktok: {\n purchase: (params) => ({\n name: 'CompletePayment',\n params: {\n ...money(params),\n content_type: 'product',\n contents: itemsOf(params).map((item) => ({\n content_id: item.id,\n content_name: item.name,\n quantity: item.quantity,\n price: item.price,\n })),\n },\n }),\n generate_lead: () => ({ name: 'SubmitForm', params: {} }),\n begin_checkout: (params) => ({\n name: 'InitiateCheckout',\n params: { ...money(params), contents: itemsOf(params).map((item) => ({ content_id: item.id, quantity: item.quantity })) },\n }),\n add_to_cart: (params) => ({\n name: 'AddToCart',\n params: { ...money(params), content_type: 'product', contents: itemsOf(params).map((item) => ({ content_id: item.id, quantity: item.quantity })) },\n }),\n view_item: (params) => ({\n name: 'ViewContent',\n params: { content_type: 'product', contents: itemsOf(params).map((item) => ({ content_id: item.id })) },\n }),\n },\n pinterest: {\n purchase: (params) => {\n const items = itemsOf(params)\n return {\n name: 'checkout',\n params: {\n ...money(params),\n order_id: typeof params['transaction_id'] === 'string' ? params['transaction_id'] : undefined,\n order_quantity: items.reduce((sum, item) => sum + item.quantity, 0),\n line_items: items.map((item) => ({\n product_id: item.id,\n product_name: item.name,\n product_price: item.price,\n product_quantity: item.quantity,\n })),\n },\n }\n },\n generate_lead: () => ({ name: 'lead', params: {} }),\n add_to_cart: (params) => {\n const items = itemsOf(params)\n return {\n name: 'addtocart',\n params: {\n ...money(params),\n order_quantity: items.reduce((sum, item) => sum + item.quantity, 0),\n line_items: items.map((item) => ({ product_id: item.id, product_quantity: item.quantity })),\n },\n }\n },\n },\n}\n\n/** The vendors that take conversion events, by `analytics.adTags` key. */\nexport const ADVERTISING_EVENT_VENDORS: readonly string[] = Object.keys(TRANSLATIONS)\n\ntype Send = (scope: Params, call: VendorCall, eventId: string) => void\n\nconst SENDERS: Readonly<Record<string, Send>> = {\n // `fbq('track', name, params, { eventID })` is Meta's documented\n // de-duplication call: the fourth argument pairs this hit with the\n // Conversions API event carrying the same `event_id`.\n meta: (scope, call, eventId) => {\n const fbq = scope['fbq']\n if (typeof fbq === 'function') {\n ;(fbq as (...args: unknown[]) => void)('track', call.name, call.params, { eventID: eventId })\n }\n },\n // `event_id` in the third argument is TikTok's de-duplication key against\n // the Events API.\n tiktok: (scope, call, eventId) => {\n const ttq = scope['ttq'] as { track?: unknown } | undefined\n if (ttq && typeof ttq.track === 'function') {\n ;(ttq.track as (...args: unknown[]) => void).call(ttq, call.name, call.params, { event_id: eventId })\n }\n },\n // Pinterest reads `event_id` from the event's own data.\n pinterest: (scope, call, eventId) => {\n const pintrk = scope['pintrk']\n if (typeof pintrk === 'function') {\n ;(pintrk as (...args: unknown[]) => void)('track', call.name, { ...call.params, event_id: eventId })\n }\n },\n}\n\n/** Whether this document holds a merchant's own tag for `vendorId`, mounted by the gate. */\nfunction merchantTagResident(vendorId: string): boolean {\n if (typeof document === 'undefined') return false\n try {\n return Boolean(\n document.querySelector(\n `script[${ADVERTISING_TAG_ATTRIBUTE}=\"${vendorId}\"][${ADVERTISING_EVENTS_ATTRIBUTE}]`,\n ),\n )\n } catch {\n return false\n }\n}\n\n/** Whether any merchant tag that takes conversion events is in the document. */\nexport function merchantAdvertisingTagsResident(): boolean {\n return ADVERTISING_EVENT_VENDORS.some(merchantTagResident)\n}\n\n/** The id one browser event is sent under, for the vendors' de-duplication. */\nfunction eventIdFor(name: string, params: Params, explicit: string | undefined): string {\n if (explicit) return explicit\n if (name === 'purchase') {\n const derived = advertisingEventId('purchase', String(params['transaction_id'] ?? ''))\n if (derived) return derived\n }\n // No server counterpart: an id of its own, so a vendor still sees one.\n return `${name}.${mintAdvertisingLeadKey()}`\n}\n\n/**\n * Sends one taxonomy event to every merchant tag in the document that takes\n * it, and answers the vendor ids it reached. Never throws and never queues:\n * with no marked tag — the visitor did not grant advertising, or the site runs\n * none — it does nothing at all.\n *\n * `options.eventId` is the shared id for a conversion the server also reports\n * (a lead); a purchase derives its own from `transaction_id`.\n */\nexport function sendAdvertisingEvent(\n name: string,\n params: Params,\n options: { eventId?: string | null } = {},\n): string[] {\n if (typeof window === 'undefined') return []\n const reached: string[] = []\n const scope = window as unknown as Params\n let eventId: string | null = null\n for (const vendorId of ADVERTISING_EVENT_VENDORS) {\n const translate = TRANSLATIONS[vendorId]?.[name]\n if (!translate || !merchantTagResident(vendorId)) continue\n try {\n const call = translate(params)\n if (!call) continue\n eventId ??= eventIdFor(name, params, options.eventId ?? undefined)\n SENDERS[vendorId]?.(scope, call, eventId)\n reached.push(vendorId)\n } catch {\n // A vendor whose own call throws is skipped; the others still hear it.\n }\n }\n return reached\n}\n"],"names":["ADVERTISING_TAG_ATTRIBUTE","ADVERTISING_EVENTS_ATTRIBUTE","EVENT_KEY","advertisingEventId","kind","key","value","String","trim","test","mintAdvertisingLeadKey","globalThis","id","crypto","randomUUID","Date","now","toString","Math","random","slice","itemsOf","params","items","Array","isArray","flatMap","entry","item","price","Number","quantity","max","floor","name","isFinite","money","currency","toUpperCase","TRANSLATIONS","meta","purchase","content_type","content_ids","map","contents","item_price","num_items","reduce","sum","generate_lead","begin_checkout","length","add_to_cart","view_item","tiktok","content_id","content_name","pinterest","order_id","undefined","order_quantity","line_items","product_id","product_name","product_price","product_quantity","ADVERTISING_EVENT_VENDORS","Object","keys","SENDERS","scope","call","eventId","fbq","eventID","ttq","track","event_id","pintrk","merchantTagResident","vendorId","document","Boolean","querySelector","merchantAdvertisingTagsResident","some","eventIdFor","explicit","derived","sendAdvertisingEvent","options","window","reached","translate","push"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
3
|
/**
|
|
3
4
|
* @license
|
|
@@ -24,7 +25,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
24
25
|
// file is reached from `site-analytics.tsx`, whose import closure
|
|
25
26
|
// `site-analytics-independence.spec.ts` walks and which must stay independent
|
|
26
27
|
// of the site-plugin gate.
|
|
27
|
-
import { ADVERTISING_TAG_ATTRIBUTE, restoreAdvertisingTags, revokeAdvertisingTags } from "./advertising-tags.js";
|
|
28
|
+
import { ADVERTISING_EVENTS_ATTRIBUTE, ADVERTISING_TAG_ATTRIBUTE, restoreAdvertisingTags, revokeAdvertisingTags } from "./advertising-tags.js";
|
|
28
29
|
import { usePageIdle } from "./page-idle.js";
|
|
29
30
|
import { VISITOR_CONSENT_CHANGED_EVENT } from "./visitor-consent.js";
|
|
30
31
|
import Script from "next/script";
|
|
@@ -93,7 +94,7 @@ export default function AdvertisingTagMounts({ active, tags, resolve, nonce, sha
|
|
|
93
94
|
const pageIsIdle = usePageIdle();
|
|
94
95
|
if (!active || tags.length === 0 || !pageIsIdle) return null;
|
|
95
96
|
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
96
|
-
children: tags.map(({ vendor, accountId })=>// A PAIR per vendor, inline boot first and library second — the same
|
|
97
|
+
children: tags.map(({ vendor, accountId, siteOwned })=>// A PAIR per vendor, inline boot first and library second — the same
|
|
97
98
|
// shape as the GA `ga-init` / `ga-src` pair, and for the same reason:
|
|
98
99
|
// the boot defines the vendor's queue shim and declares the consent
|
|
99
100
|
// state, so nothing the library later drains was queued under a state
|
|
@@ -101,23 +102,29 @@ export default function AdvertisingTagMounts({ active, tags, resolve, nonce, sha
|
|
|
101
102
|
// elements carrying it are ever revoked, removed or cookie-swept.
|
|
102
103
|
/*#__PURE__*/ _jsxs(Fragment, {
|
|
103
104
|
children: [
|
|
104
|
-
/*#__PURE__*/ _jsx(Script, {
|
|
105
|
+
/*#__PURE__*/ _jsx(Script, _extends({
|
|
105
106
|
id: `ad-tag-${vendor.id}-init`,
|
|
106
107
|
strategy: "afterInteractive",
|
|
107
108
|
nonce: nonce,
|
|
108
|
-
[ADVERTISING_TAG_ATTRIBUTE]: vendor.id
|
|
109
|
+
[ADVERTISING_TAG_ATTRIBUTE]: vendor.id
|
|
110
|
+
}, siteOwned ? {
|
|
111
|
+
[ADVERTISING_EVENTS_ATTRIBUTE]: '1'
|
|
112
|
+
} : {}, {
|
|
109
113
|
children: vendor.bootSnippet ? vendor.bootSnippet(accountId) : ''
|
|
110
|
-
}),
|
|
111
|
-
vendor.sharesLibrary && libraryProvided(vendor.sharesLibrary) ? null : /*#__PURE__*/ _jsx(Script, {
|
|
114
|
+
})),
|
|
115
|
+
vendor.sharesLibrary && libraryProvided(vendor.sharesLibrary) ? null : /*#__PURE__*/ _jsx(Script, _extends({
|
|
112
116
|
id: `ad-tag-${vendor.id}-src`,
|
|
113
117
|
strategy: "afterInteractive",
|
|
114
118
|
nonce: nonce,
|
|
115
|
-
[ADVERTISING_TAG_ATTRIBUTE]: vendor.id
|
|
119
|
+
[ADVERTISING_TAG_ATTRIBUTE]: vendor.id
|
|
120
|
+
}, siteOwned ? {
|
|
121
|
+
[ADVERTISING_EVENTS_ATTRIBUTE]: '1'
|
|
122
|
+
} : {}, {
|
|
116
123
|
// `scriptSrcFor` where the vendor has one: gtag reads the
|
|
117
124
|
// container out of the loader's query, so the copy we bring
|
|
118
125
|
// ourselves has to name the account. See `scriptSrcFor`.
|
|
119
126
|
src: vendor.scriptSrcFor ? vendor.scriptSrcFor(accountId) : vendor.scriptSrc
|
|
120
|
-
})
|
|
127
|
+
}))
|
|
121
128
|
]
|
|
122
129
|
}, vendor.id))
|
|
123
130
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/advertising-tag-mounts.tsx"],"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\n// NO `'use client'` here, and it is a lint rule rather than a preference\n// (AGL-52): a directive inside `@aglyn/aglyn` makes the bundler split a\n// duplicate module graph, and the second canvas/emitter singleton renders the\n// tenant site blank. Every component that mounts this one carries the\n// directive itself, which is where the client boundary belongs.\n//\n// Deep app-utils modules, never the `@aglyn/aglyn` barrel (AGL-1550): this\n// file is reached from `site-analytics.tsx`, whose import closure\n// `site-analytics-independence.spec.ts` walks and which must stay independent\n// of the site-plugin gate.\nimport {\n ADVERTISING_TAG_ATTRIBUTE,\n type ResolvedAdvertisingTag,\n restoreAdvertisingTags,\n revokeAdvertisingTags,\n} from './advertising-tags'\nimport { usePageIdle } from './page-idle'\nimport { VISITOR_CONSENT_CHANGED_EVENT } from './visitor-consent'\nimport Script from 'next/script'\nimport { Fragment, useEffect, useRef, type ReactElement } from 'react'\n\n/**\n * The MOUNT and the WITHDRAWAL for consent-gated advertising tags, with no\n * opinion about where the verdict came from.\n *\n * ## Why this is a shared component and not a second copy per surface\n *\n * Aglyn runs advertising tags on three first-party surfaces and they resolve\n * consent through three different mechanisms: the tenant runtime reads a host\n * document and a per-host record, the console reads the platform record its\n * own posture machinery wrote, and the docs site reads the registrable-domain\n * mirror of that record. Those are genuinely different questions.\n *\n * What is NOT different is what happens once the answer is known: mount an\n * inline boot and a library per vendor, and — the half that is easy to forget\n * and impossible to retrofit — stop them the moment the answer changes. A\n * second copy of that half is how one surface comes to keep firing after\n * consent is withdrawn on another, because the copy that was not updated\n * still looks exactly like the one that was.\n *\n * So the verdict is a PROP and the machinery is shared. Each surface answers\n * its own question with its own resolver; none of them owns a teardown.\n *\n * ## Why it renders even when the answer is no\n *\n * Because the withdrawal path needs a listener. A visitor who accepts and then\n * turns advertising off must stop being tracked in THAT pageview, and by then\n * the vendor library has executed — React dropping the `<Script>` does not\n * unload it (AGL-1608). So this component stays mounted whenever the surface\n * participates at all, and subscribes to\n * {@link VISITOR_CONSENT_CHANGED_EVENT}; the teardown runs from the event,\n * synchronously with the visitor's click, rather than waiting on a re-render.\n *\n * Both paths run and they agree, which is deliberate: the render gate is what\n * keeps the tag out of a fresh pageview, the listener is what removes one that\n * is already there, and neither can do the other's job.\n */\nexport interface AdvertisingTagMountsProps {\n /**\n * Whether this surface participates in the gate AT ALL.\n *\n * False installs nothing — no listener, no scripts. That is the clause that\n * keeps the tenant runtime's teardown off a customer's site: we did not load\n * their pixel, we do not know what basis it runs on, and reaching into their\n * page to kill it would be its own breach. `revokeAdvertisingTags` is\n * additionally attribute-scoped, so there are two independent scopes.\n */\n readonly active: boolean\n /** The verdict for THIS render: the tags that may exist right now. */\n readonly tags: readonly ResolvedAdvertisingTag[]\n /**\n * Re-read the verdict from live state, for the withdrawal listener.\n *\n * A callback rather than the `tags` prop, because the listener fires from\n * the visitor's own click in the same tick as the record is written — the\n * props for the current render are by definition the state before it.\n */\n readonly resolve: () => readonly ResolvedAdvertisingTag[]\n /**\n * The request's CSP nonce, stamped onto BOTH elements of every pair.\n *\n * A surface that enforces a nonce'd `script-src` refuses an inline script\n * that does not carry it, and the boot half of every pair is inline. Next's\n * `<Script>` stamps a nonce only when one reaches it: the automatic path\n * reads `HeadManagerContext`, and the App Router's client provider carries\n * no nonce at all — so a pair mounted after hydration, which is the only\n * time this component ever mounts one, is unnonced unless the caller hands\n * the value down. An explicit prop wins inside `next/script`\n * (`restProps.nonce || nonce`), which is what makes this the one door.\n *\n * The failure without it is silent and lopsided: the library beside the\n * boot has a `src` the policy allows, so the vendor's code loads and runs\n * against an account nobody configured, and the conversions this surface\n * sends are lost with nothing in the page but a CSP violation.\n *\n * Absent on a surface that sends no `script-src`: nothing is stamped, and\n * nothing is refused.\n */\n readonly nonce?: string\n /**\n * Libraries the HOST PAGE mounts itself, named by the needle a vendor\n * declares in `sharesLibrary` (AGL-2681).\n *\n * {@link sharedLibraryPresent} reads the document at render time, and that\n * is the right instrument for a loader some other party put there. It is\n * the wrong one for a loader the SAME render is about to create. On the\n * tenant the GA pair and this component both wait on `consent.ready`, so\n * the first render that may emit either emits both — GA's `<Script>` is not\n * in the document yet when this component looks, the check honestly says\n * \"absent\", and the visitor downloads `gtag.js` twice: once as ours with\n * `?id=AW-…`, once as gtag's own destination fetch for the `config` that\n * follows. Measured on `aglyn.com/solutions/small-business` with\n * advertising granted: two 147 KiB loaders for one account.\n *\n * A page that KNOWS it renders the library says so here, from the same\n * condition that renders it, and the document check stays as the fallback\n * for loaders it does not know about. Naming the needle rather than passing\n * a boolean keeps the declaration per library: a page that mounts gtag has\n * said nothing about the Meta pixel.\n */\n readonly sharedLibraries?: readonly string[]\n}\n\n/**\n * Is a library matching `needle` already in the document?\n *\n * Read at RENDER time rather than in an effect: the decision is whether to\n * emit a `<Script>` at all, and by the time an effect could answer, Next has\n * already appended it. `document` is guarded because this component renders on\n * the server too, where nothing is mounted and the honest answer is \"no\" — the\n * client render then re-evaluates with the real document.\n *\n * Blind to a loader the current render is creating alongside this one — see\n * `sharedLibraries` on the props for the case that needs the page to say so.\n */\nexport function sharedLibraryPresent(needle: string): boolean {\n if (typeof document === 'undefined') return false\n try {\n return Boolean(document.querySelector(`script[src*=\"${needle}\"]`))\n } catch {\n // A hostile or absent DOM: assume nothing is mounted, which mounts our\n // own copy — the cost is a duplicate fetch, never a missing tag.\n return false\n }\n}\n\nexport default function AdvertisingTagMounts({\n active,\n tags,\n resolve,\n nonce,\n sharedLibraries,\n}: AdvertisingTagMountsProps): ReactElement | null {\n /** Declared by the page, or found in the document: either means \"ride it\". */\n const libraryProvided = (needle: string): boolean =>\n (sharedLibraries?.includes(needle) ?? false) || sharedLibraryPresent(needle)\n /*\n * The resolver is held in a ref rather than listed as an effect dependency.\n *\n * Every caller passes a closure over its own live consent state, so the\n * function identity changes on every render; depending on it would tear the\n * listener down and re-install it each time, and a withdrawal that landed in\n * that window would find no subscriber. The ref makes the subscription's\n * lifetime `active`, which is the only thing that genuinely changes it,\n * while the callback it invokes is always the newest one.\n */\n const resolveRef = useRef(resolve)\n resolveRef.current = resolve\n\n useEffect(() => {\n if (!active) return undefined\n const sync = () => {\n if (resolveRef.current().length === 0) {\n revokeAdvertisingTags()\n } else {\n // Symmetric: a visitor who withdrew and changed their mind inside one\n // pageview would otherwise stay un-tracked until they navigated,\n // because a re-rendered `<Script>` cannot re-execute a library the\n // browser already ran.\n restoreAdvertisingTags()\n }\n }\n window.addEventListener(VISITOR_CONSENT_CHANGED_EVENT, sync)\n return () => window.removeEventListener(VISITOR_CONSENT_CHANGED_EVENT, sync)\n }, [active])\n\n // Nothing is injected before the page has loaded and gone idle (AGL-3581).\n // The withdrawal listener above is NOT behind this: a visitor who refuses\n // before the page idles has nothing to tear down, and one who refuses after\n // needs the listener already there. Every surface reads the same store, so\n // a page that mounts its own loader flips in the same render as this one\n // and `sharedLibraries` still describes it (AGL-2681).\n const pageIsIdle = usePageIdle()\n\n if (!active || tags.length === 0 || !pageIsIdle) return null\n\n return (\n <>\n {tags.map(({ vendor, accountId }) => (\n // A PAIR per vendor, inline boot first and library second — the same\n // shape as the GA `ga-init` / `ga-src` pair, and for the same reason:\n // the boot defines the vendor's queue shim and declares the consent\n // state, so nothing the library later drains was queued under a state\n // nobody chose. Both elements carry the teardown's scope marker; only\n // elements carrying it are ever revoked, removed or cookie-swept.\n <Fragment key={vendor.id}>\n <Script\n id={`ad-tag-${vendor.id}-init`}\n strategy=\"afterInteractive\"\n nonce={nonce}\n {...{ [ADVERTISING_TAG_ATTRIBUTE]: vendor.id }}\n >\n {vendor.bootSnippet ? vendor.bootSnippet(accountId) : ''}\n </Script>\n {/* Skipped when another loader already brought this library in\n (AGL-1152). Google Ads shares `gtag.js` with the GA4 measurement\n id and with a GTM container, so a surface with both configured\n would fetch it twice and define `gtag()` twice — and the boot\n above would be the second voice in a consent conversation the\n first one already had. One library, several `config` calls, is\n how gtag carries several products. The page's own declaration is\n consulted first (AGL-2681): the document cannot yet show a loader\n this same render is creating. */}\n {vendor.sharesLibrary && libraryProvided(vendor.sharesLibrary) ? null : (\n <Script\n id={`ad-tag-${vendor.id}-src`}\n strategy=\"afterInteractive\"\n nonce={nonce}\n {...{ [ADVERTISING_TAG_ATTRIBUTE]: vendor.id }}\n // `scriptSrcFor` where the vendor has one: gtag reads the\n // container out of the loader's query, so the copy we bring\n // ourselves has to name the account. See `scriptSrcFor`.\n src={\n vendor.scriptSrcFor\n ? vendor.scriptSrcFor(accountId)\n : vendor.scriptSrc\n }\n />\n )}\n </Fragment>\n ))}\n </>\n )\n}\n"],"names":["ADVERTISING_TAG_ATTRIBUTE","restoreAdvertisingTags","revokeAdvertisingTags","usePageIdle","VISITOR_CONSENT_CHANGED_EVENT","Script","Fragment","useEffect","useRef","sharedLibraryPresent","needle","document","Boolean","querySelector","AdvertisingTagMounts","active","tags","resolve","nonce","sharedLibraries","libraryProvided","includes","resolveRef","current","undefined","sync","length","window","addEventListener","removeEventListener","pageIsIdle","map","vendor","accountId","id","strategy","bootSnippet","sharesLibrary","src","scriptSrcFor","scriptSrc"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,yEAAyE;AACzE,wEAAwE;AACxE,8EAA8E;AAC9E,sEAAsE;AACtE,gEAAgE;AAChE,EAAE;AACF,2EAA2E;AAC3E,kEAAkE;AAClE,8EAA8E;AAC9E,2BAA2B;AAC3B,SACEA,yBAAyB,EAEzBC,sBAAsB,EACtBC,qBAAqB,QAChB,wBAAoB;AAC3B,SAASC,WAAW,QAAQ,iBAAa;AACzC,SAASC,6BAA6B,QAAQ,uBAAmB;AACjE,OAAOC,YAAY,cAAa;AAChC,SAASC,QAAQ,EAAEC,SAAS,EAAEC,MAAM,QAA2B,QAAO;AAwGtE;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,qBAAqBC,MAAc;IACjD,IAAI,OAAOC,aAAa,aAAa,OAAO;IAC5C,IAAI;QACF,OAAOC,QAAQD,SAASE,aAAa,CAAC,CAAC,aAAa,EAAEH,OAAO,EAAE,CAAC;IAClE,EAAE,eAAM;QACN,uEAAuE;QACvE,iEAAiE;QACjE,OAAO;IACT;AACF;AAEA,eAAe,SAASI,qBAAqB,EAC3CC,MAAM,EACNC,IAAI,EACJC,OAAO,EACPC,KAAK,EACLC,eAAe,EACW;IAC1B,4EAA4E,GAC5E,MAAMC,kBAAkB,CAACV;;eACvB,SAACS,mCAAAA,gBAAiBE,QAAQ,CAACX,0BAAW,UAAUD,qBAAqBC;;IACvE;;;;;;;;;GASC,GACD,MAAMY,aAAad,OAAOS;IAC1BK,WAAWC,OAAO,GAAGN;IAErBV,UAAU;QACR,IAAI,CAACQ,QAAQ,OAAOS;QACpB,MAAMC,OAAO;YACX,IAAIH,WAAWC,OAAO,GAAGG,MAAM,KAAK,GAAG;gBACrCxB;YACF,OAAO;gBACL,sEAAsE;gBACtE,iEAAiE;gBACjE,mEAAmE;gBACnE,uBAAuB;gBACvBD;YACF;QACF;QACA0B,OAAOC,gBAAgB,CAACxB,+BAA+BqB;QACvD,OAAO,IAAME,OAAOE,mBAAmB,CAACzB,+BAA+BqB;IACzE,GAAG;QAACV;KAAO;IAEX,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,2EAA2E;IAC3E,yEAAyE;IACzE,uDAAuD;IACvD,MAAMe,aAAa3B;IAEnB,IAAI,CAACY,UAAUC,KAAKU,MAAM,KAAK,KAAK,CAACI,YAAY,OAAO;IAExD,qBACE;kBACGd,KAAKe,GAAG,CAAC,CAAC,EAAEC,MAAM,EAAEC,SAAS,EAAE,GAC9B,qEAAqE;YACrE,sEAAsE;YACtE,oEAAoE;YACpE,sEAAsE;YACtE,sEAAsE;YACtE,kEAAkE;0BAClE,MAAC3B;;kCACC,KAACD;wBACC6B,IAAI,CAAC,OAAO,EAAEF,OAAOE,EAAE,CAAC,KAAK,CAAC;wBAC9BC,UAAS;wBACTjB,OAAOA;wBACD,CAAClB,0BAA0B,EAAEgC,OAAOE,EAAE;kCAE3CF,OAAOI,WAAW,GAAGJ,OAAOI,WAAW,CAACH,aAAa;;oBAWvDD,OAAOK,aAAa,IAAIjB,gBAAgBY,OAAOK,aAAa,IAAI,qBAC/D,KAAChC;wBACC6B,IAAI,CAAC,OAAO,EAAEF,OAAOE,EAAE,CAAC,IAAI,CAAC;wBAC7BC,UAAS;wBACTjB,OAAOA;wBACD,CAAClB,0BAA0B,EAAEgC,OAAOE,EAAE;wBAC5C,0DAA0D;wBAC1D,4DAA4D;wBAC5D,yDAAyD;wBACzDI,KACEN,OAAOO,YAAY,GACfP,OAAOO,YAAY,CAACN,aACpBD,OAAOQ,SAAS;;;eA9BbR,OAAOE,EAAE;;AAsChC"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/advertising-tag-mounts.tsx"],"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\n// NO `'use client'` here, and it is a lint rule rather than a preference\n// (AGL-52): a directive inside `@aglyn/aglyn` makes the bundler split a\n// duplicate module graph, and the second canvas/emitter singleton renders the\n// tenant site blank. Every component that mounts this one carries the\n// directive itself, which is where the client boundary belongs.\n//\n// Deep app-utils modules, never the `@aglyn/aglyn` barrel (AGL-1550): this\n// file is reached from `site-analytics.tsx`, whose import closure\n// `site-analytics-independence.spec.ts` walks and which must stay independent\n// of the site-plugin gate.\nimport {\n ADVERTISING_EVENTS_ATTRIBUTE,\n ADVERTISING_TAG_ATTRIBUTE,\n type ResolvedAdvertisingTag,\n restoreAdvertisingTags,\n revokeAdvertisingTags,\n} from './advertising-tags'\nimport { usePageIdle } from './page-idle'\nimport { VISITOR_CONSENT_CHANGED_EVENT } from './visitor-consent'\nimport Script from 'next/script'\nimport { Fragment, useEffect, useRef, type ReactElement } from 'react'\n\n/**\n * The MOUNT and the WITHDRAWAL for consent-gated advertising tags, with no\n * opinion about where the verdict came from.\n *\n * ## Why this is a shared component and not a second copy per surface\n *\n * Aglyn runs advertising tags on three first-party surfaces and they resolve\n * consent through three different mechanisms: the tenant runtime reads a host\n * document and a per-host record, the console reads the platform record its\n * own posture machinery wrote, and the docs site reads the registrable-domain\n * mirror of that record. Those are genuinely different questions.\n *\n * What is NOT different is what happens once the answer is known: mount an\n * inline boot and a library per vendor, and — the half that is easy to forget\n * and impossible to retrofit — stop them the moment the answer changes. A\n * second copy of that half is how one surface comes to keep firing after\n * consent is withdrawn on another, because the copy that was not updated\n * still looks exactly like the one that was.\n *\n * So the verdict is a PROP and the machinery is shared. Each surface answers\n * its own question with its own resolver; none of them owns a teardown.\n *\n * ## Why it renders even when the answer is no\n *\n * Because the withdrawal path needs a listener. A visitor who accepts and then\n * turns advertising off must stop being tracked in THAT pageview, and by then\n * the vendor library has executed — React dropping the `<Script>` does not\n * unload it (AGL-1608). So this component stays mounted whenever the surface\n * participates at all, and subscribes to\n * {@link VISITOR_CONSENT_CHANGED_EVENT}; the teardown runs from the event,\n * synchronously with the visitor's click, rather than waiting on a re-render.\n *\n * Both paths run and they agree, which is deliberate: the render gate is what\n * keeps the tag out of a fresh pageview, the listener is what removes one that\n * is already there, and neither can do the other's job.\n */\nexport interface AdvertisingTagMountsProps {\n /**\n * Whether this surface participates in the gate AT ALL.\n *\n * False installs nothing — no listener, no scripts. That is the clause that\n * keeps the tenant runtime's teardown off a customer's site: we did not load\n * their pixel, we do not know what basis it runs on, and reaching into their\n * page to kill it would be its own breach. `revokeAdvertisingTags` is\n * additionally attribute-scoped, so there are two independent scopes.\n */\n readonly active: boolean\n /** The verdict for THIS render: the tags that may exist right now. */\n readonly tags: readonly ResolvedAdvertisingTag[]\n /**\n * Re-read the verdict from live state, for the withdrawal listener.\n *\n * A callback rather than the `tags` prop, because the listener fires from\n * the visitor's own click in the same tick as the record is written — the\n * props for the current render are by definition the state before it.\n */\n readonly resolve: () => readonly ResolvedAdvertisingTag[]\n /**\n * The request's CSP nonce, stamped onto BOTH elements of every pair.\n *\n * A surface that enforces a nonce'd `script-src` refuses an inline script\n * that does not carry it, and the boot half of every pair is inline. Next's\n * `<Script>` stamps a nonce only when one reaches it: the automatic path\n * reads `HeadManagerContext`, and the App Router's client provider carries\n * no nonce at all — so a pair mounted after hydration, which is the only\n * time this component ever mounts one, is unnonced unless the caller hands\n * the value down. An explicit prop wins inside `next/script`\n * (`restProps.nonce || nonce`), which is what makes this the one door.\n *\n * The failure without it is silent and lopsided: the library beside the\n * boot has a `src` the policy allows, so the vendor's code loads and runs\n * against an account nobody configured, and the conversions this surface\n * sends are lost with nothing in the page but a CSP violation.\n *\n * Absent on a surface that sends no `script-src`: nothing is stamped, and\n * nothing is refused.\n */\n readonly nonce?: string\n /**\n * Libraries the HOST PAGE mounts itself, named by the needle a vendor\n * declares in `sharesLibrary` (AGL-2681).\n *\n * {@link sharedLibraryPresent} reads the document at render time, and that\n * is the right instrument for a loader some other party put there. It is\n * the wrong one for a loader the SAME render is about to create. On the\n * tenant the GA pair and this component both wait on `consent.ready`, so\n * the first render that may emit either emits both — GA's `<Script>` is not\n * in the document yet when this component looks, the check honestly says\n * \"absent\", and the visitor downloads `gtag.js` twice: once as ours with\n * `?id=AW-…`, once as gtag's own destination fetch for the `config` that\n * follows. Measured on `aglyn.com/solutions/small-business` with\n * advertising granted: two 147 KiB loaders for one account.\n *\n * A page that KNOWS it renders the library says so here, from the same\n * condition that renders it, and the document check stays as the fallback\n * for loaders it does not know about. Naming the needle rather than passing\n * a boolean keeps the declaration per library: a page that mounts gtag has\n * said nothing about the Meta pixel.\n */\n readonly sharedLibraries?: readonly string[]\n}\n\n/**\n * Is a library matching `needle` already in the document?\n *\n * Read at RENDER time rather than in an effect: the decision is whether to\n * emit a `<Script>` at all, and by the time an effect could answer, Next has\n * already appended it. `document` is guarded because this component renders on\n * the server too, where nothing is mounted and the honest answer is \"no\" — the\n * client render then re-evaluates with the real document.\n *\n * Blind to a loader the current render is creating alongside this one — see\n * `sharedLibraries` on the props for the case that needs the page to say so.\n */\nexport function sharedLibraryPresent(needle: string): boolean {\n if (typeof document === 'undefined') return false\n try {\n return Boolean(document.querySelector(`script[src*=\"${needle}\"]`))\n } catch {\n // A hostile or absent DOM: assume nothing is mounted, which mounts our\n // own copy — the cost is a duplicate fetch, never a missing tag.\n return false\n }\n}\n\nexport default function AdvertisingTagMounts({\n active,\n tags,\n resolve,\n nonce,\n sharedLibraries,\n}: AdvertisingTagMountsProps): ReactElement | null {\n /** Declared by the page, or found in the document: either means \"ride it\". */\n const libraryProvided = (needle: string): boolean =>\n (sharedLibraries?.includes(needle) ?? false) || sharedLibraryPresent(needle)\n /*\n * The resolver is held in a ref rather than listed as an effect dependency.\n *\n * Every caller passes a closure over its own live consent state, so the\n * function identity changes on every render; depending on it would tear the\n * listener down and re-install it each time, and a withdrawal that landed in\n * that window would find no subscriber. The ref makes the subscription's\n * lifetime `active`, which is the only thing that genuinely changes it,\n * while the callback it invokes is always the newest one.\n */\n const resolveRef = useRef(resolve)\n resolveRef.current = resolve\n\n useEffect(() => {\n if (!active) return undefined\n const sync = () => {\n if (resolveRef.current().length === 0) {\n revokeAdvertisingTags()\n } else {\n // Symmetric: a visitor who withdrew and changed their mind inside one\n // pageview would otherwise stay un-tracked until they navigated,\n // because a re-rendered `<Script>` cannot re-execute a library the\n // browser already ran.\n restoreAdvertisingTags()\n }\n }\n window.addEventListener(VISITOR_CONSENT_CHANGED_EVENT, sync)\n return () => window.removeEventListener(VISITOR_CONSENT_CHANGED_EVENT, sync)\n }, [active])\n\n // Nothing is injected before the page has loaded and gone idle (AGL-3581).\n // The withdrawal listener above is NOT behind this: a visitor who refuses\n // before the page idles has nothing to tear down, and one who refuses after\n // needs the listener already there. Every surface reads the same store, so\n // a page that mounts its own loader flips in the same render as this one\n // and `sharedLibraries` still describes it (AGL-2681).\n const pageIsIdle = usePageIdle()\n\n if (!active || tags.length === 0 || !pageIsIdle) return null\n\n return (\n <>\n {tags.map(({ vendor, accountId, siteOwned }) => (\n // A PAIR per vendor, inline boot first and library second — the same\n // shape as the GA `ga-init` / `ga-src` pair, and for the same reason:\n // the boot defines the vendor's queue shim and declares the consent\n // state, so nothing the library later drains was queued under a state\n // nobody chose. Both elements carry the teardown's scope marker; only\n // elements carrying it are ever revoked, removed or cookie-swept.\n <Fragment key={vendor.id}>\n <Script\n id={`ad-tag-${vendor.id}-init`}\n strategy=\"afterInteractive\"\n nonce={nonce}\n {...{ [ADVERTISING_TAG_ATTRIBUTE]: vendor.id }}\n // A site owner's own tag carries the second mark (AGL-3694),\n // which is what lets the site's conversion events reach it.\n {...(siteOwned ? { [ADVERTISING_EVENTS_ATTRIBUTE]: '1' } : {})}\n >\n {vendor.bootSnippet ? vendor.bootSnippet(accountId) : ''}\n </Script>\n {/* Skipped when another loader already brought this library in\n (AGL-1152). Google Ads shares `gtag.js` with the GA4 measurement\n id and with a GTM container, so a surface with both configured\n would fetch it twice and define `gtag()` twice — and the boot\n above would be the second voice in a consent conversation the\n first one already had. One library, several `config` calls, is\n how gtag carries several products. The page's own declaration is\n consulted first (AGL-2681): the document cannot yet show a loader\n this same render is creating. */}\n {vendor.sharesLibrary && libraryProvided(vendor.sharesLibrary) ? null : (\n <Script\n id={`ad-tag-${vendor.id}-src`}\n strategy=\"afterInteractive\"\n nonce={nonce}\n {...{ [ADVERTISING_TAG_ATTRIBUTE]: vendor.id }}\n {...(siteOwned ? { [ADVERTISING_EVENTS_ATTRIBUTE]: '1' } : {})}\n // `scriptSrcFor` where the vendor has one: gtag reads the\n // container out of the loader's query, so the copy we bring\n // ourselves has to name the account. See `scriptSrcFor`.\n src={\n vendor.scriptSrcFor\n ? vendor.scriptSrcFor(accountId)\n : vendor.scriptSrc\n }\n />\n )}\n </Fragment>\n ))}\n </>\n )\n}\n"],"names":["ADVERTISING_EVENTS_ATTRIBUTE","ADVERTISING_TAG_ATTRIBUTE","restoreAdvertisingTags","revokeAdvertisingTags","usePageIdle","VISITOR_CONSENT_CHANGED_EVENT","Script","Fragment","useEffect","useRef","sharedLibraryPresent","needle","document","Boolean","querySelector","AdvertisingTagMounts","active","tags","resolve","nonce","sharedLibraries","libraryProvided","includes","resolveRef","current","undefined","sync","length","window","addEventListener","removeEventListener","pageIsIdle","map","vendor","accountId","siteOwned","id","strategy","bootSnippet","sharesLibrary","src","scriptSrcFor","scriptSrc"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
+
import { ADVERTISING_EVENTS_ATTRIBUTE, ADVERTISING_TAG_ATTRIBUTE } from './advertising-events';
|
|
17
18
|
import { type AnalyticsEnvironment } from './analytics-environment';
|
|
18
19
|
import { type StoredVisitorConsent, type VisitorConsentHost } from './visitor-consent';
|
|
19
20
|
/**
|
|
@@ -24,8 +25,11 @@ import { type StoredVisitorConsent, type VisitorConsentHost } from './visitor-co
|
|
|
24
25
|
* their own site's Custom HTML is never touched by our withdrawal path — we
|
|
25
26
|
* did not load it, we do not know what basis it runs on, and silently killing
|
|
26
27
|
* it would be us configuring a customer's site. Its value is the vendor id.
|
|
28
|
+
*
|
|
29
|
+
* Declared in `advertising-events.ts` (AGL-3694), which finds a mounted tag by
|
|
30
|
+
* it without importing this module.
|
|
27
31
|
*/
|
|
28
|
-
export
|
|
32
|
+
export { ADVERTISING_EVENTS_ATTRIBUTE, ADVERTISING_TAG_ATTRIBUTE };
|
|
29
33
|
/**
|
|
30
34
|
* One advertising vendor, described completely enough to LOAD it and — the
|
|
31
35
|
* half that is easy to forget and impossible to retrofit — to STOP it.
|
|
@@ -224,6 +228,42 @@ export declare const GOOGLE_ADS_VENDOR: AdvertisingVendor;
|
|
|
224
228
|
* half-works.
|
|
225
229
|
*/
|
|
226
230
|
export declare const LINKEDIN_INSIGHT_VENDOR: AdvertisingVendor;
|
|
231
|
+
/**
|
|
232
|
+
* TikTok Pixel (AGL-3694) — a site owner's own, from their TikTok Ads Manager.
|
|
233
|
+
*
|
|
234
|
+
* TikTok's published base code is a loader that inserts `events.js` itself.
|
|
235
|
+
* Here the QUEUE half of it runs as the boot snippet and the library is the
|
|
236
|
+
* marked `<script>` the mount renders, so the teardown can find it — the same
|
|
237
|
+
* split every vendor in this registry has. `ttq.load` is reduced accordingly
|
|
238
|
+
* to the registration it does (`_i`, `_t`, `_o` for the pixel code), without
|
|
239
|
+
* the insertion; `events.js` reads that registration when it arrives.
|
|
240
|
+
*
|
|
241
|
+
* `grantConsent`/`revokeConsent` are TikTok's documented consent controls,
|
|
242
|
+
* and are the `fbq('consent', …)` analogue for the AGL-1608 order: told
|
|
243
|
+
* first, then removed, then swept.
|
|
244
|
+
*
|
|
245
|
+
* Cookies: `_ttp` (the browser id), `_tt_enable_cookie` (the cookie probe) and
|
|
246
|
+
* `ttcsid` / `ttcsid_<pixel>` (the session), all first-party at the
|
|
247
|
+
* registrable domain, which the ladder sweep reaches.
|
|
248
|
+
*/
|
|
249
|
+
export declare const TIKTOK_PIXEL_VENDOR: AdvertisingVendor;
|
|
250
|
+
/**
|
|
251
|
+
* Pinterest Tag (AGL-3694) — a site owner's own, from Pinterest Ads.
|
|
252
|
+
*
|
|
253
|
+
* The published base code is a queue shim plus an inserted `core.js`; as with
|
|
254
|
+
* TikTok the shim is the boot and the library is our marked element.
|
|
255
|
+
*
|
|
256
|
+
* Pinterest publishes no consent call for the tag. On withdrawal the queue
|
|
257
|
+
* function is replaced with one that drops every call, so a late or cached
|
|
258
|
+
* `core.js` finds nothing to report; the element removal and the sweep are
|
|
259
|
+
* the substance, as they are for LinkedIn.
|
|
260
|
+
*
|
|
261
|
+
* Cookies: `_pin_unauth` (the browser id), `_pinterest_ct_ua` /
|
|
262
|
+
* `_pinterest_ct_rt` (conversion tracking) and `_epik` / `_derived_epik` (the
|
|
263
|
+
* click id) — first-party; `_pinterest_sess` lives on pinterest.com and only
|
|
264
|
+
* Pinterest can clear it.
|
|
265
|
+
*/
|
|
266
|
+
export declare const PINTEREST_TAG_VENDOR: AdvertisingVendor;
|
|
227
267
|
/**
|
|
228
268
|
* Every vendor this gate knows how to tear down — which is a SUPERSET of the
|
|
229
269
|
* vendors it knows how to load, now that a sweep-only member exists.
|
|
@@ -232,9 +272,8 @@ export declare const ADVERTISING_VENDORS: readonly AdvertisingVendor[];
|
|
|
232
272
|
/**
|
|
233
273
|
* The host fields this module reads, on top of the consent ones.
|
|
234
274
|
*
|
|
235
|
-
* `adTags` maps a vendor id to that vendor's account id
|
|
236
|
-
*
|
|
237
|
-
* reviewable data change rather than a consequence of merging this.
|
|
275
|
+
* `adTags` maps a vendor id to that vendor's account id: on our marketing
|
|
276
|
+
* host, our own; on a customer's site, the owner's own (AGL-3694).
|
|
238
277
|
*/
|
|
239
278
|
export interface AdvertisingTagHost extends VisitorConsentHost {
|
|
240
279
|
analytics?: {
|
|
@@ -247,6 +286,12 @@ export interface AdvertisingTagHost extends VisitorConsentHost {
|
|
|
247
286
|
export interface ResolvedAdvertisingTag {
|
|
248
287
|
readonly vendor: AdvertisingVendor;
|
|
249
288
|
readonly accountId: string;
|
|
289
|
+
/**
|
|
290
|
+
* The tag is a site owner's own, on their own site (AGL-3694): it is marked
|
|
291
|
+
* {@link ADVERTISING_EVENTS_ATTRIBUTE} and takes the site's conversion
|
|
292
|
+
* events. Absent on our own surfaces' tags.
|
|
293
|
+
*/
|
|
294
|
+
readonly siteOwned?: true;
|
|
250
295
|
}
|
|
251
296
|
/**
|
|
252
297
|
* The whole verdict: which advertising tags may exist in this document, for
|
|
@@ -285,10 +330,11 @@ export declare function residentAdvertisingVendors(): AdvertisingVendor[];
|
|
|
285
330
|
* 3. **Sweep the cookies.** Last, because steps 1 and 2 are what stop them
|
|
286
331
|
* coming straight back (AGL-1608).
|
|
287
332
|
*
|
|
288
|
-
* Acts only on tags carrying {@link ADVERTISING_TAG_ATTRIBUTE}
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* a basis that is not ours to
|
|
333
|
+
* Acts only on tags carrying {@link ADVERTISING_TAG_ATTRIBUTE}: the tags this
|
|
334
|
+
* module mounted, which on a customer's site are the ones its owner configured
|
|
335
|
+
* on Setup → Tracking (AGL-3694). A pixel pasted into Custom HTML carries no
|
|
336
|
+
* mark and is never touched — it runs on a basis that is not ours to
|
|
337
|
+
* withdraw.
|
|
292
338
|
*/
|
|
293
339
|
export declare function revokeAdvertisingTags(hostname?: string | null): string[];
|
|
294
340
|
/**
|