@blotoutio/edgetag-sdk-js 1.58.1 → 1.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs.js +230 -157
- package/index.mjs +230 -157
- package/internal.d.ts +1 -0
- package/package.json +1 -1
package/index.cjs.js
CHANGED
|
@@ -507,77 +507,6 @@ const usStates = new Map([
|
|
|
507
507
|
['US-VI', 'Virgin Islands, U.S.'],
|
|
508
508
|
]);
|
|
509
509
|
new Set([...isoCountries.keys(), ...usStates.keys()]);
|
|
510
|
-
/**
|
|
511
|
-
* ISO 3166-1 alpha-2 codes for EU member states (as of 2024)
|
|
512
|
-
*/
|
|
513
|
-
const EU_COUNTRY_CODES = new Set([
|
|
514
|
-
'AT', // Austria
|
|
515
|
-
'BE', // Belgium
|
|
516
|
-
'BG', // Bulgaria
|
|
517
|
-
'CY', // Cyprus
|
|
518
|
-
'CZ', // Czech Republic
|
|
519
|
-
'DE', // Germany
|
|
520
|
-
'DK', // Denmark
|
|
521
|
-
'EE', // Estonia
|
|
522
|
-
'ES', // Spain
|
|
523
|
-
'FI', // Finland
|
|
524
|
-
'FR', // France
|
|
525
|
-
'GR', // Greece
|
|
526
|
-
'HR', // Croatia
|
|
527
|
-
'HU', // Hungary
|
|
528
|
-
'IE', // Ireland
|
|
529
|
-
'IT', // Italy
|
|
530
|
-
'LT', // Lithuania
|
|
531
|
-
'LU', // Luxembourg
|
|
532
|
-
'LV', // Latvia
|
|
533
|
-
'MT', // Malta
|
|
534
|
-
'NL', // Netherlands
|
|
535
|
-
'PL', // Poland
|
|
536
|
-
'PT', // Portugal
|
|
537
|
-
'RO', // Romania
|
|
538
|
-
'SE', // Sweden
|
|
539
|
-
'SI', // Slovenia
|
|
540
|
-
'SK', // Slovakia
|
|
541
|
-
]);
|
|
542
|
-
/**
|
|
543
|
-
* Returns true if the request originates from the EU or the UK (GB).
|
|
544
|
-
* Cloudflare's isEUCountry flag excludes the UK post-Brexit, so GB is
|
|
545
|
-
* checked explicitly alongside the EU member set.
|
|
546
|
-
*
|
|
547
|
-
* Fails closed: when the country cannot be resolved, the request is treated
|
|
548
|
-
* as EU/UK. This is the safe default for the compliance-sensitive Grey Switch
|
|
549
|
-
* (an unknown visitor may be in the EU/UK, so it must not slip through when
|
|
550
|
-
* the EU/UK toggle is off).
|
|
551
|
-
*/
|
|
552
|
-
const isEuUkRegion = (country, isEURequest) => {
|
|
553
|
-
if (isEURequest) {
|
|
554
|
-
return true;
|
|
555
|
-
}
|
|
556
|
-
if (!country) {
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
const upper = country.toUpperCase();
|
|
560
|
-
return upper === 'GB' || EU_COUNTRY_CODES.has(upper);
|
|
561
|
-
};
|
|
562
|
-
/**
|
|
563
|
-
* Grey Switch fire rule, shared by the SDK and the CDN worker so both stay in
|
|
564
|
-
* sync. When Grey Switch is on, EdgeTag fires server-side events for
|
|
565
|
-
* non-consented users. The EU/UK toggle is off by default, which keeps EU/UK
|
|
566
|
-
* visitors on normal consent unless the merchant explicitly opts those regions
|
|
567
|
-
* in. Returns whether a server-side event should fire for a non-consented user.
|
|
568
|
-
*/
|
|
569
|
-
const greySwitchAllowsServerEvent = ({ greySwitch, greySwitchEuUk, country, isEURequest, isGPC, }) => {
|
|
570
|
-
if (isGPC) {
|
|
571
|
-
return false;
|
|
572
|
-
}
|
|
573
|
-
if (!greySwitch) {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
if (greySwitchEuUk) {
|
|
577
|
-
return true;
|
|
578
|
-
}
|
|
579
|
-
return !isEuUkRegion(country, isEURequest);
|
|
580
|
-
};
|
|
581
510
|
const parseCache = new Map();
|
|
582
511
|
const parseRegions = (regionString) => {
|
|
583
512
|
const include = new Set();
|
|
@@ -1541,7 +1470,7 @@ const getStandardPayload = (destination, payload) => {
|
|
|
1541
1470
|
referrer: getReferrer(destination),
|
|
1542
1471
|
search: getSearch(destination),
|
|
1543
1472
|
locale: getLocale(),
|
|
1544
|
-
sdkVersion: "1.
|
|
1473
|
+
sdkVersion: "1.59.0" ,
|
|
1545
1474
|
...(payload || {}),
|
|
1546
1475
|
};
|
|
1547
1476
|
let storage = {};
|
|
@@ -1729,6 +1658,72 @@ const sendTag = (destination, { eventName, eventId, data, providerData, provider
|
|
|
1729
1658
|
}
|
|
1730
1659
|
postRequest(getTagURL(destination, eventName, options), payload, options).catch(logger.error);
|
|
1731
1660
|
};
|
|
1661
|
+
// Scope (scripts/providers allow-lists) is delivered per plugin via /init, keyed
|
|
1662
|
+
// by the plugin's identity `${providerId}||${name}` (which is carried in the
|
|
1663
|
+
// plugin object's `name`). A missing scope means the plugin is global.
|
|
1664
|
+
//
|
|
1665
|
+
// Mirrors the edge getAllowedPlugins per-item logic: at instance level the
|
|
1666
|
+
// scripts allow-list wins, otherwise fall back to the providers allow-list.
|
|
1667
|
+
const isPluginAllowed = (scope, providerId, instanceName) => {
|
|
1668
|
+
if (!scope) {
|
|
1669
|
+
return true;
|
|
1670
|
+
}
|
|
1671
|
+
if (instanceName && scope.scripts.length > 0) {
|
|
1672
|
+
return scope.scripts.includes(`${providerId}||${instanceName}`);
|
|
1673
|
+
}
|
|
1674
|
+
return scope.providers.length === 0 || scope.providers.includes(providerId);
|
|
1675
|
+
};
|
|
1676
|
+
// Mirrors the edge processPluginsRoot skip scope: a global plugin (no scope)
|
|
1677
|
+
// skips every browser instance (null), a scoped plugin skips only the instances
|
|
1678
|
+
// its scripts/providers allow-lists resolve to.
|
|
1679
|
+
const getScopedPluginSkipKeys = (scope, configuredTags) => {
|
|
1680
|
+
if (!scope || (scope.scripts.length === 0 && scope.providers.length === 0)) {
|
|
1681
|
+
return null;
|
|
1682
|
+
}
|
|
1683
|
+
const result = new Set();
|
|
1684
|
+
for (const script of scope.scripts) {
|
|
1685
|
+
result.add(script);
|
|
1686
|
+
}
|
|
1687
|
+
for (const providerId of scope.providers) {
|
|
1688
|
+
const instances = configuredTags.get(providerId);
|
|
1689
|
+
if (!instances)
|
|
1690
|
+
continue;
|
|
1691
|
+
for (const instanceName of instances) {
|
|
1692
|
+
result.add(`${providerId}||${instanceName}`);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
return result;
|
|
1696
|
+
};
|
|
1697
|
+
// Mirrors the edge getProviderLimits: an additional event emitted by a scoped
|
|
1698
|
+
// plugin is restricted to that plugin's channels/instances; a global plugin
|
|
1699
|
+
// imposes no restriction (undefined).
|
|
1700
|
+
const getPluginProviderLimits = (scope) => {
|
|
1701
|
+
if (!scope || (scope.scripts.length === 0 && scope.providers.length === 0)) {
|
|
1702
|
+
return undefined;
|
|
1703
|
+
}
|
|
1704
|
+
const limited = {};
|
|
1705
|
+
for (const script of scope.scripts) {
|
|
1706
|
+
const separator = script.indexOf('||');
|
|
1707
|
+
if (separator === -1) {
|
|
1708
|
+
continue;
|
|
1709
|
+
}
|
|
1710
|
+
const provider = script.slice(0, separator);
|
|
1711
|
+
const instance = script.slice(separator + 2);
|
|
1712
|
+
const existing = limited[provider];
|
|
1713
|
+
if (existing && existing !== true) {
|
|
1714
|
+
existing[instance] = true;
|
|
1715
|
+
}
|
|
1716
|
+
else {
|
|
1717
|
+
limited[provider] = { [instance]: true };
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
for (const provider of scope.providers) {
|
|
1721
|
+
if (!(provider in limited)) {
|
|
1722
|
+
limited[provider] = true;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return limited;
|
|
1726
|
+
};
|
|
1732
1727
|
const getPlugins = (destination) => {
|
|
1733
1728
|
var _a, _b, _c;
|
|
1734
1729
|
try {
|
|
@@ -1739,7 +1734,19 @@ const getPlugins = (destination) => {
|
|
|
1739
1734
|
return [];
|
|
1740
1735
|
}
|
|
1741
1736
|
};
|
|
1742
|
-
|
|
1737
|
+
// Dispatches a plugin's additional events. Mirrors the edge: each event is
|
|
1738
|
+
// stamped with the emitting plugin (so it cannot reprocess itself) and limited
|
|
1739
|
+
// to that plugin's scope.
|
|
1740
|
+
const dispatchAdditionalEvents = (plugin, pluginScopes, events) => {
|
|
1741
|
+
const providerLimits = getPluginProviderLimits(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name]);
|
|
1742
|
+
for (const evt of events) {
|
|
1743
|
+
handleTag(evt.eventName, evt.data, providerLimits, undefined, plugin.name);
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
// Runs a channel/instance hook chain. The emitting plugin is excluded by the
|
|
1747
|
+
// caller's allow-list filter; additional events fire before any skip is applied
|
|
1748
|
+
// (matching the edge), and a skip is reported without mutating the final tag.
|
|
1749
|
+
const runPluginHook = async (plugins, hookName, pluginScopes, baseParams) => {
|
|
1743
1750
|
var _a, _b;
|
|
1744
1751
|
const payload = baseParams['payload'];
|
|
1745
1752
|
let currentEventName = payload['eventName'];
|
|
@@ -1760,6 +1767,9 @@ const runPluginHook = async (plugins, hookName, baseParams) => {
|
|
|
1760
1767
|
},
|
|
1761
1768
|
variables: plugin.variables || {},
|
|
1762
1769
|
});
|
|
1770
|
+
if (result === null || result === void 0 ? void 0 : result.additionalEvents) {
|
|
1771
|
+
dispatchAdditionalEvents(plugin, pluginScopes, result.additionalEvents);
|
|
1772
|
+
}
|
|
1763
1773
|
if (result === null || result === void 0 ? void 0 : result.skipEvent) {
|
|
1764
1774
|
skip = true;
|
|
1765
1775
|
break;
|
|
@@ -1773,24 +1783,87 @@ const runPluginHook = async (plugins, hookName, baseParams) => {
|
|
|
1773
1783
|
if ((result === null || result === void 0 ? void 0 : result.providers) !== undefined) {
|
|
1774
1784
|
currentProviders = result.providers;
|
|
1775
1785
|
}
|
|
1786
|
+
}
|
|
1787
|
+
catch (e) {
|
|
1788
|
+
logger.error(`Plugin ${plugin.name} ${hookName} error: ${e}`);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return {
|
|
1792
|
+
eventName: currentEventName,
|
|
1793
|
+
data: currentData,
|
|
1794
|
+
providers: currentProviders,
|
|
1795
|
+
skip,
|
|
1796
|
+
};
|
|
1797
|
+
};
|
|
1798
|
+
// Root hooks mirror the edge processPluginsRoot: every plugin runs (except the
|
|
1799
|
+
// one that emitted this event), additional events are dispatched before any
|
|
1800
|
+
// skip is applied, and skips are scoped — a global plugin marks every browser
|
|
1801
|
+
// instance, a scoped one only its resolved instances. The skip is a marker:
|
|
1802
|
+
// channel/instance hooks still run and only the final tag is suppressed.
|
|
1803
|
+
const runRootPluginHooks = async (plugins, baseParams, pluginScopes, configuredTags, pluginSource) => {
|
|
1804
|
+
var _a, _b;
|
|
1805
|
+
const payload = baseParams['payload'];
|
|
1806
|
+
let currentEventName = payload['eventName'];
|
|
1807
|
+
let currentData = payload['data'];
|
|
1808
|
+
let currentProviders = baseParams['providers'];
|
|
1809
|
+
const skippedInstances = new Set();
|
|
1810
|
+
for (const plugin of plugins) {
|
|
1811
|
+
if (plugin.name === pluginSource)
|
|
1812
|
+
continue;
|
|
1813
|
+
const hook = plugin.rules.tagRoot;
|
|
1814
|
+
if (!hook)
|
|
1815
|
+
continue;
|
|
1816
|
+
try {
|
|
1817
|
+
const result = await hook({
|
|
1818
|
+
...baseParams,
|
|
1819
|
+
payload: {
|
|
1820
|
+
...payload,
|
|
1821
|
+
eventName: currentEventName,
|
|
1822
|
+
data: jsonClone(currentData),
|
|
1823
|
+
},
|
|
1824
|
+
variables: plugin.variables || {},
|
|
1825
|
+
});
|
|
1776
1826
|
if (result === null || result === void 0 ? void 0 : result.additionalEvents) {
|
|
1777
|
-
|
|
1778
|
-
|
|
1827
|
+
dispatchAdditionalEvents(plugin, pluginScopes, result.additionalEvents);
|
|
1828
|
+
}
|
|
1829
|
+
if (result === null || result === void 0 ? void 0 : result.skipEvent) {
|
|
1830
|
+
const scopeSkipKeys = getScopedPluginSkipKeys(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name], configuredTags);
|
|
1831
|
+
if (scopeSkipKeys === null) {
|
|
1832
|
+
// Global plugin: skip every configured browser instance.
|
|
1833
|
+
for (const [provider, instances] of configuredTags) {
|
|
1834
|
+
for (const instanceName of instances) {
|
|
1835
|
+
skippedInstances.add(`${provider}||${instanceName}`);
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1779
1838
|
}
|
|
1839
|
+
else {
|
|
1840
|
+
for (const key of scopeSkipKeys) {
|
|
1841
|
+
skippedInstances.add(key);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
if ((_a = result === null || result === void 0 ? void 0 : result.payload) === null || _a === void 0 ? void 0 : _a.eventName) {
|
|
1846
|
+
currentEventName = result.payload.eventName;
|
|
1847
|
+
}
|
|
1848
|
+
if ((_b = result === null || result === void 0 ? void 0 : result.payload) === null || _b === void 0 ? void 0 : _b.data) {
|
|
1849
|
+
currentData = result.payload.data;
|
|
1850
|
+
}
|
|
1851
|
+
if ((result === null || result === void 0 ? void 0 : result.providers) !== undefined) {
|
|
1852
|
+
currentProviders = result.providers;
|
|
1780
1853
|
}
|
|
1781
1854
|
}
|
|
1782
1855
|
catch (e) {
|
|
1783
|
-
logger.error(`Plugin ${plugin.name}
|
|
1856
|
+
logger.error(`Plugin ${plugin.name} tagRoot error: ${e}`);
|
|
1784
1857
|
}
|
|
1785
1858
|
}
|
|
1786
1859
|
return {
|
|
1787
1860
|
eventName: currentEventName,
|
|
1788
1861
|
data: currentData,
|
|
1789
1862
|
providers: currentProviders,
|
|
1790
|
-
|
|
1863
|
+
skippedInstances,
|
|
1791
1864
|
};
|
|
1792
1865
|
};
|
|
1793
|
-
const processTag = async (destination, eventName, data = {}, providers, options) => {
|
|
1866
|
+
const processTag = async (destination, eventName, data = {}, providers, options, pluginSource) => {
|
|
1794
1867
|
var _a, _b;
|
|
1795
1868
|
let currentEventName = eventName;
|
|
1796
1869
|
if (!getSetting(destination, 'initialized')) {
|
|
@@ -1817,15 +1890,6 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1817
1890
|
const consentCategory = getConsentCategories(destination);
|
|
1818
1891
|
const consentSettings = getSetting(destination, 'consentSetting');
|
|
1819
1892
|
const userConsent = { consentChannel, consentCategory, consentSettings };
|
|
1820
|
-
// Grey Switch: when enabled, still send the event to the server for
|
|
1821
|
-
// non-consented users. Browser pixels remain gated by hasUserConsent below,
|
|
1822
|
-
// so nothing fires client-side.
|
|
1823
|
-
const greySwitchAllows = greySwitchAllowsServerEvent({
|
|
1824
|
-
greySwitch: getSetting(destination, 'greySwitch') || false,
|
|
1825
|
-
greySwitchEuUk: getSetting(destination, 'greySwitchEuUk') || false,
|
|
1826
|
-
country: requestCountry,
|
|
1827
|
-
isEURequest,
|
|
1828
|
-
});
|
|
1829
1893
|
const ip = getSetting(destination, 'ip') || null;
|
|
1830
1894
|
const userProperties = getSetting(destination, 'userProperties');
|
|
1831
1895
|
if (skipZeroPurchaseEvent && isZeroPurchaseEvent({ eventName, data })) {
|
|
@@ -1846,6 +1910,7 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1846
1910
|
providers = rulesResult.updatedProviders;
|
|
1847
1911
|
}
|
|
1848
1912
|
const plugins = getPlugins(destination);
|
|
1913
|
+
const pluginScopes = getSetting(destination, 'pluginScopes');
|
|
1849
1914
|
const pluginSettings = {
|
|
1850
1915
|
userId,
|
|
1851
1916
|
sessionId,
|
|
@@ -1861,26 +1926,15 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1861
1926
|
const pluginUtilities = {
|
|
1862
1927
|
getIsNewCustomerFlag: () => getIsNewCustomerFlag(processGetData.bind(null, destination)),
|
|
1863
1928
|
};
|
|
1929
|
+
let rootSkippedInstances = new Set();
|
|
1864
1930
|
if (plugins.length > 0) {
|
|
1865
|
-
const rootResult = await
|
|
1931
|
+
const rootResult = await runRootPluginHooks(plugins, {
|
|
1866
1932
|
payload: { eventName: currentEventName, data, eventId },
|
|
1867
1933
|
context: pluginContext,
|
|
1868
1934
|
providers,
|
|
1869
1935
|
settings: pluginSettings,
|
|
1870
1936
|
utilities: pluginUtilities,
|
|
1871
|
-
});
|
|
1872
|
-
if (rootResult.skip) {
|
|
1873
|
-
sendTag(destination, {
|
|
1874
|
-
configuratorProcessed: true,
|
|
1875
|
-
eventName: currentEventName,
|
|
1876
|
-
eventId,
|
|
1877
|
-
data,
|
|
1878
|
-
providerData: {},
|
|
1879
|
-
providers,
|
|
1880
|
-
options,
|
|
1881
|
-
});
|
|
1882
|
-
return;
|
|
1883
|
-
}
|
|
1937
|
+
}, pluginScopes, configuredTags, pluginSource);
|
|
1884
1938
|
if (rootResult.providers !== undefined) {
|
|
1885
1939
|
// eslint-disable-next-line no-param-reassign
|
|
1886
1940
|
providers = rootResult.providers;
|
|
@@ -1888,6 +1942,7 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1888
1942
|
currentEventName = rootResult.eventName;
|
|
1889
1943
|
// eslint-disable-next-line no-param-reassign
|
|
1890
1944
|
data = rootResult.data;
|
|
1945
|
+
rootSkippedInstances = rootResult.skippedInstances;
|
|
1891
1946
|
}
|
|
1892
1947
|
if (!rulesResult.skipBrowserEvent) {
|
|
1893
1948
|
const currencySettings = getSetting(destination, 'currency');
|
|
@@ -1899,69 +1954,96 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1899
1954
|
logger.log(`Provider ${pkg.name} is not in allow list`);
|
|
1900
1955
|
continue;
|
|
1901
1956
|
}
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
providerId: pkg.name,
|
|
1914
|
-
utilities: pluginUtilities,
|
|
1957
|
+
const providerId = pkg.name;
|
|
1958
|
+
const variables = getProviderVariables(destination, pkg.name);
|
|
1959
|
+
// Per-instance state, mirroring the edge PluginData array: each instance
|
|
1960
|
+
// carries its own payload and skip marker (seeded from the root skip
|
|
1961
|
+
// scope). A skip never short-circuits the hooks — only the final tag.
|
|
1962
|
+
const instanceStates = new Map();
|
|
1963
|
+
for (const variable of variables) {
|
|
1964
|
+
instanceStates.set(variable.tagName, {
|
|
1965
|
+
eventName: currentEventName,
|
|
1966
|
+
data,
|
|
1967
|
+
skipped: rootSkippedInstances.has(`${providerId}||${variable.tagName}`),
|
|
1915
1968
|
});
|
|
1916
|
-
if (channelResult.skip)
|
|
1917
|
-
continue;
|
|
1918
|
-
providerEventName = channelResult.eventName;
|
|
1919
|
-
channelData = channelResult.data;
|
|
1920
1969
|
}
|
|
1921
|
-
|
|
1970
|
+
// Channel phase (processPluginsChannel): tagChannel runs once per
|
|
1971
|
+
// instance, regardless of skip/consent/geo, so its side effects and
|
|
1972
|
+
// additional events still fire on skipped instances.
|
|
1973
|
+
const channelPlugins = plugins.filter((plugin) => plugin.name !== pluginSource &&
|
|
1974
|
+
isPluginAllowed(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name], providerId, null));
|
|
1975
|
+
if (channelPlugins.length > 0) {
|
|
1976
|
+
for (const variable of variables) {
|
|
1977
|
+
const state = instanceStates.get(variable.tagName);
|
|
1978
|
+
const channelResult = await runPluginHook(channelPlugins, 'tagChannel', pluginScopes, {
|
|
1979
|
+
payload: {
|
|
1980
|
+
eventName: state.eventName,
|
|
1981
|
+
data: state.data,
|
|
1982
|
+
eventId,
|
|
1983
|
+
},
|
|
1984
|
+
context: pluginContext,
|
|
1985
|
+
settings: pluginSettings,
|
|
1986
|
+
providerId,
|
|
1987
|
+
utilities: pluginUtilities,
|
|
1988
|
+
});
|
|
1989
|
+
if (channelResult.skip) {
|
|
1990
|
+
state.skipped = true;
|
|
1991
|
+
}
|
|
1992
|
+
state.eventName = channelResult.eventName;
|
|
1993
|
+
state.data = channelResult.data;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
// Instance phase (processPluginsInstance + tag send): consent/geo gate,
|
|
1997
|
+
// run tagInstance, then suppress only the final tag when skipped.
|
|
1922
1998
|
const result = {};
|
|
1923
1999
|
const executionContext = new Map();
|
|
1924
2000
|
for (const variable of variables) {
|
|
1925
|
-
|
|
1926
|
-
|
|
2001
|
+
const state = instanceStates.get(variable.tagName);
|
|
2002
|
+
if (!isProviderInstanceAllowed(providers, providerId, variable.tagName)) {
|
|
2003
|
+
logger.log(`Provider instance is not allowed (${providerId}: ${variable.tagName})`);
|
|
1927
2004
|
continue;
|
|
1928
2005
|
}
|
|
1929
|
-
if (!hasUserConsent(userConsent,
|
|
1930
|
-
logger.log(`Consent is missing (${
|
|
2006
|
+
if (!hasUserConsent(userConsent, providerId, variable.tagName)) {
|
|
2007
|
+
logger.log(`Consent is missing (${providerId}: ${variable.tagName})`);
|
|
1931
2008
|
continue;
|
|
1932
2009
|
}
|
|
1933
2010
|
if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
|
|
1934
2011
|
logger.log('GEO request region does not match the filter, skipping');
|
|
1935
2012
|
continue;
|
|
1936
2013
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
if (
|
|
1940
|
-
const instanceResult = await runPluginHook(
|
|
2014
|
+
const instancePlugins = plugins.filter((plugin) => plugin.name !== pluginSource &&
|
|
2015
|
+
isPluginAllowed(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name], providerId, variable.tagName));
|
|
2016
|
+
if (instancePlugins.length > 0) {
|
|
2017
|
+
const instanceResult = await runPluginHook(instancePlugins, 'tagInstance', pluginScopes, {
|
|
1941
2018
|
payload: {
|
|
1942
|
-
eventName:
|
|
1943
|
-
data:
|
|
2019
|
+
eventName: state.eventName,
|
|
2020
|
+
data: state.data,
|
|
1944
2021
|
eventId,
|
|
1945
2022
|
},
|
|
1946
2023
|
context: pluginContext,
|
|
1947
2024
|
settings: pluginSettings,
|
|
1948
|
-
providerId
|
|
2025
|
+
providerId,
|
|
1949
2026
|
utilities: pluginUtilities,
|
|
1950
2027
|
});
|
|
1951
|
-
if (instanceResult.skip)
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
2028
|
+
if (instanceResult.skip) {
|
|
2029
|
+
state.skipped = true;
|
|
2030
|
+
}
|
|
2031
|
+
state.eventName = instanceResult.eventName;
|
|
2032
|
+
state.data = instanceResult.data;
|
|
2033
|
+
}
|
|
2034
|
+
if (state.skipped) {
|
|
2035
|
+
logger.log(`Skipping event due to plugin condition (${providerId}: ${variable.tagName})`);
|
|
2036
|
+
continue;
|
|
1955
2037
|
}
|
|
1956
|
-
const conversion = preparePayloadWithConversion(jsonClone(
|
|
2038
|
+
const conversion = preparePayloadWithConversion(jsonClone(state.data), currencySettings);
|
|
1957
2039
|
const payload = ((_a = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _a === void 0 ? void 0 : _a.length) === 0 ||
|
|
1958
|
-
((_b = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _b === void 0 ? void 0 : _b.includes(
|
|
2040
|
+
((_b = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _b === void 0 ? void 0 : _b.includes(providerId))
|
|
1959
2041
|
? conversion.payload
|
|
1960
|
-
:
|
|
2042
|
+
: state.data;
|
|
1961
2043
|
result[variable.tagName] = pkg.tag({
|
|
1962
2044
|
userId,
|
|
1963
2045
|
sessionId,
|
|
1964
|
-
eventName:
|
|
2046
|
+
eventName: state.eventName,
|
|
1965
2047
|
eventId,
|
|
1966
2048
|
data: jsonClone(payload),
|
|
1967
2049
|
sendTag: sendTag.bind(null, destination),
|
|
@@ -1976,16 +2058,17 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1976
2058
|
pageUrl: getPageUrl(destination),
|
|
1977
2059
|
});
|
|
1978
2060
|
}
|
|
1979
|
-
|
|
2061
|
+
if (Object.keys(result).length > 0) {
|
|
2062
|
+
providerData[providerId] = result;
|
|
2063
|
+
}
|
|
1980
2064
|
}
|
|
1981
2065
|
}
|
|
1982
2066
|
else {
|
|
1983
2067
|
logger.log('Browser events skipped by event rules, sending tag to server only');
|
|
1984
2068
|
}
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
}
|
|
2069
|
+
// The server event is always forwarded to EdgeTag regardless of consent or
|
|
2070
|
+
// provider config; the CDN re-runs this logic server-side. Browser tags are
|
|
2071
|
+
// still individually gated by consent/geo/provider above.
|
|
1989
2072
|
sendTag(destination, {
|
|
1990
2073
|
configuratorProcessed: true,
|
|
1991
2074
|
eventName: currentEventName,
|
|
@@ -2004,26 +2087,15 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
2004
2087
|
handleTag(additionalEventName, jsonClone(originalData || {}), originalProviders, options);
|
|
2005
2088
|
}
|
|
2006
2089
|
};
|
|
2007
|
-
const handleTag = (eventName, data = {}, providers, options) => {
|
|
2090
|
+
const handleTag = (eventName, data = {}, providers, options, pluginSource) => {
|
|
2008
2091
|
if (options === null || options === void 0 ? void 0 : options.destination) {
|
|
2009
|
-
processTag(options.destination, eventName, data, providers, options).catch(logger.error);
|
|
2092
|
+
processTag(options.destination, eventName, data, providers, options, pluginSource).catch(logger.error);
|
|
2010
2093
|
return;
|
|
2011
2094
|
}
|
|
2012
2095
|
getInstances().forEach((instance) => {
|
|
2013
|
-
processTag(instance, eventName, data, providers, options).catch(logger.error);
|
|
2096
|
+
processTag(instance, eventName, data, providers, options, pluginSource).catch(logger.error);
|
|
2014
2097
|
});
|
|
2015
2098
|
};
|
|
2016
|
-
const hasAllowedManifestTags = (tags, consent, providersConfig) => {
|
|
2017
|
-
for (const [pkg, tagNames] of tags) {
|
|
2018
|
-
for (const tagName of tagNames) {
|
|
2019
|
-
if (hasUserConsent(consent, pkg, tagName) &&
|
|
2020
|
-
isProviderInstanceAllowed(providersConfig, pkg, tagName)) {
|
|
2021
|
-
return true;
|
|
2022
|
-
}
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
return false;
|
|
2026
|
-
};
|
|
2027
2099
|
|
|
2028
2100
|
const processData = (destination, data, providers, options) => {
|
|
2029
2101
|
saveKV(destination, data);
|
|
@@ -2563,6 +2635,7 @@ const handleInit = (preferences) => {
|
|
|
2563
2635
|
configuratorSetting: result.configuratorSetting,
|
|
2564
2636
|
userProperties: result.userProperties,
|
|
2565
2637
|
ip: result.ip,
|
|
2638
|
+
pluginScopes: result.pluginScopes,
|
|
2566
2639
|
});
|
|
2567
2640
|
if (result.storageId != null) {
|
|
2568
2641
|
savePerKey(preferences.edgeURL, 'local', tagStorage, result.storageId, storageIdKey);
|
package/index.mjs
CHANGED
|
@@ -505,77 +505,6 @@ const usStates = new Map([
|
|
|
505
505
|
['US-VI', 'Virgin Islands, U.S.'],
|
|
506
506
|
]);
|
|
507
507
|
new Set([...isoCountries.keys(), ...usStates.keys()]);
|
|
508
|
-
/**
|
|
509
|
-
* ISO 3166-1 alpha-2 codes for EU member states (as of 2024)
|
|
510
|
-
*/
|
|
511
|
-
const EU_COUNTRY_CODES = new Set([
|
|
512
|
-
'AT', // Austria
|
|
513
|
-
'BE', // Belgium
|
|
514
|
-
'BG', // Bulgaria
|
|
515
|
-
'CY', // Cyprus
|
|
516
|
-
'CZ', // Czech Republic
|
|
517
|
-
'DE', // Germany
|
|
518
|
-
'DK', // Denmark
|
|
519
|
-
'EE', // Estonia
|
|
520
|
-
'ES', // Spain
|
|
521
|
-
'FI', // Finland
|
|
522
|
-
'FR', // France
|
|
523
|
-
'GR', // Greece
|
|
524
|
-
'HR', // Croatia
|
|
525
|
-
'HU', // Hungary
|
|
526
|
-
'IE', // Ireland
|
|
527
|
-
'IT', // Italy
|
|
528
|
-
'LT', // Lithuania
|
|
529
|
-
'LU', // Luxembourg
|
|
530
|
-
'LV', // Latvia
|
|
531
|
-
'MT', // Malta
|
|
532
|
-
'NL', // Netherlands
|
|
533
|
-
'PL', // Poland
|
|
534
|
-
'PT', // Portugal
|
|
535
|
-
'RO', // Romania
|
|
536
|
-
'SE', // Sweden
|
|
537
|
-
'SI', // Slovenia
|
|
538
|
-
'SK', // Slovakia
|
|
539
|
-
]);
|
|
540
|
-
/**
|
|
541
|
-
* Returns true if the request originates from the EU or the UK (GB).
|
|
542
|
-
* Cloudflare's isEUCountry flag excludes the UK post-Brexit, so GB is
|
|
543
|
-
* checked explicitly alongside the EU member set.
|
|
544
|
-
*
|
|
545
|
-
* Fails closed: when the country cannot be resolved, the request is treated
|
|
546
|
-
* as EU/UK. This is the safe default for the compliance-sensitive Grey Switch
|
|
547
|
-
* (an unknown visitor may be in the EU/UK, so it must not slip through when
|
|
548
|
-
* the EU/UK toggle is off).
|
|
549
|
-
*/
|
|
550
|
-
const isEuUkRegion = (country, isEURequest) => {
|
|
551
|
-
if (isEURequest) {
|
|
552
|
-
return true;
|
|
553
|
-
}
|
|
554
|
-
if (!country) {
|
|
555
|
-
return true;
|
|
556
|
-
}
|
|
557
|
-
const upper = country.toUpperCase();
|
|
558
|
-
return upper === 'GB' || EU_COUNTRY_CODES.has(upper);
|
|
559
|
-
};
|
|
560
|
-
/**
|
|
561
|
-
* Grey Switch fire rule, shared by the SDK and the CDN worker so both stay in
|
|
562
|
-
* sync. When Grey Switch is on, EdgeTag fires server-side events for
|
|
563
|
-
* non-consented users. The EU/UK toggle is off by default, which keeps EU/UK
|
|
564
|
-
* visitors on normal consent unless the merchant explicitly opts those regions
|
|
565
|
-
* in. Returns whether a server-side event should fire for a non-consented user.
|
|
566
|
-
*/
|
|
567
|
-
const greySwitchAllowsServerEvent = ({ greySwitch, greySwitchEuUk, country, isEURequest, isGPC, }) => {
|
|
568
|
-
if (isGPC) {
|
|
569
|
-
return false;
|
|
570
|
-
}
|
|
571
|
-
if (!greySwitch) {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
if (greySwitchEuUk) {
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
return !isEuUkRegion(country, isEURequest);
|
|
578
|
-
};
|
|
579
508
|
const parseCache = new Map();
|
|
580
509
|
const parseRegions = (regionString) => {
|
|
581
510
|
const include = new Set();
|
|
@@ -1539,7 +1468,7 @@ const getStandardPayload = (destination, payload) => {
|
|
|
1539
1468
|
referrer: getReferrer(destination),
|
|
1540
1469
|
search: getSearch(destination),
|
|
1541
1470
|
locale: getLocale(),
|
|
1542
|
-
sdkVersion: "1.
|
|
1471
|
+
sdkVersion: "1.59.0" ,
|
|
1543
1472
|
...(payload || {}),
|
|
1544
1473
|
};
|
|
1545
1474
|
let storage = {};
|
|
@@ -1727,6 +1656,72 @@ const sendTag = (destination, { eventName, eventId, data, providerData, provider
|
|
|
1727
1656
|
}
|
|
1728
1657
|
postRequest(getTagURL(destination, eventName, options), payload, options).catch(logger.error);
|
|
1729
1658
|
};
|
|
1659
|
+
// Scope (scripts/providers allow-lists) is delivered per plugin via /init, keyed
|
|
1660
|
+
// by the plugin's identity `${providerId}||${name}` (which is carried in the
|
|
1661
|
+
// plugin object's `name`). A missing scope means the plugin is global.
|
|
1662
|
+
//
|
|
1663
|
+
// Mirrors the edge getAllowedPlugins per-item logic: at instance level the
|
|
1664
|
+
// scripts allow-list wins, otherwise fall back to the providers allow-list.
|
|
1665
|
+
const isPluginAllowed = (scope, providerId, instanceName) => {
|
|
1666
|
+
if (!scope) {
|
|
1667
|
+
return true;
|
|
1668
|
+
}
|
|
1669
|
+
if (instanceName && scope.scripts.length > 0) {
|
|
1670
|
+
return scope.scripts.includes(`${providerId}||${instanceName}`);
|
|
1671
|
+
}
|
|
1672
|
+
return scope.providers.length === 0 || scope.providers.includes(providerId);
|
|
1673
|
+
};
|
|
1674
|
+
// Mirrors the edge processPluginsRoot skip scope: a global plugin (no scope)
|
|
1675
|
+
// skips every browser instance (null), a scoped plugin skips only the instances
|
|
1676
|
+
// its scripts/providers allow-lists resolve to.
|
|
1677
|
+
const getScopedPluginSkipKeys = (scope, configuredTags) => {
|
|
1678
|
+
if (!scope || (scope.scripts.length === 0 && scope.providers.length === 0)) {
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
const result = new Set();
|
|
1682
|
+
for (const script of scope.scripts) {
|
|
1683
|
+
result.add(script);
|
|
1684
|
+
}
|
|
1685
|
+
for (const providerId of scope.providers) {
|
|
1686
|
+
const instances = configuredTags.get(providerId);
|
|
1687
|
+
if (!instances)
|
|
1688
|
+
continue;
|
|
1689
|
+
for (const instanceName of instances) {
|
|
1690
|
+
result.add(`${providerId}||${instanceName}`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
return result;
|
|
1694
|
+
};
|
|
1695
|
+
// Mirrors the edge getProviderLimits: an additional event emitted by a scoped
|
|
1696
|
+
// plugin is restricted to that plugin's channels/instances; a global plugin
|
|
1697
|
+
// imposes no restriction (undefined).
|
|
1698
|
+
const getPluginProviderLimits = (scope) => {
|
|
1699
|
+
if (!scope || (scope.scripts.length === 0 && scope.providers.length === 0)) {
|
|
1700
|
+
return undefined;
|
|
1701
|
+
}
|
|
1702
|
+
const limited = {};
|
|
1703
|
+
for (const script of scope.scripts) {
|
|
1704
|
+
const separator = script.indexOf('||');
|
|
1705
|
+
if (separator === -1) {
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
const provider = script.slice(0, separator);
|
|
1709
|
+
const instance = script.slice(separator + 2);
|
|
1710
|
+
const existing = limited[provider];
|
|
1711
|
+
if (existing && existing !== true) {
|
|
1712
|
+
existing[instance] = true;
|
|
1713
|
+
}
|
|
1714
|
+
else {
|
|
1715
|
+
limited[provider] = { [instance]: true };
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
for (const provider of scope.providers) {
|
|
1719
|
+
if (!(provider in limited)) {
|
|
1720
|
+
limited[provider] = true;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return limited;
|
|
1724
|
+
};
|
|
1730
1725
|
const getPlugins = (destination) => {
|
|
1731
1726
|
var _a, _b, _c;
|
|
1732
1727
|
try {
|
|
@@ -1737,7 +1732,19 @@ const getPlugins = (destination) => {
|
|
|
1737
1732
|
return [];
|
|
1738
1733
|
}
|
|
1739
1734
|
};
|
|
1740
|
-
|
|
1735
|
+
// Dispatches a plugin's additional events. Mirrors the edge: each event is
|
|
1736
|
+
// stamped with the emitting plugin (so it cannot reprocess itself) and limited
|
|
1737
|
+
// to that plugin's scope.
|
|
1738
|
+
const dispatchAdditionalEvents = (plugin, pluginScopes, events) => {
|
|
1739
|
+
const providerLimits = getPluginProviderLimits(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name]);
|
|
1740
|
+
for (const evt of events) {
|
|
1741
|
+
handleTag(evt.eventName, evt.data, providerLimits, undefined, plugin.name);
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
// Runs a channel/instance hook chain. The emitting plugin is excluded by the
|
|
1745
|
+
// caller's allow-list filter; additional events fire before any skip is applied
|
|
1746
|
+
// (matching the edge), and a skip is reported without mutating the final tag.
|
|
1747
|
+
const runPluginHook = async (plugins, hookName, pluginScopes, baseParams) => {
|
|
1741
1748
|
var _a, _b;
|
|
1742
1749
|
const payload = baseParams['payload'];
|
|
1743
1750
|
let currentEventName = payload['eventName'];
|
|
@@ -1758,6 +1765,9 @@ const runPluginHook = async (plugins, hookName, baseParams) => {
|
|
|
1758
1765
|
},
|
|
1759
1766
|
variables: plugin.variables || {},
|
|
1760
1767
|
});
|
|
1768
|
+
if (result === null || result === void 0 ? void 0 : result.additionalEvents) {
|
|
1769
|
+
dispatchAdditionalEvents(plugin, pluginScopes, result.additionalEvents);
|
|
1770
|
+
}
|
|
1761
1771
|
if (result === null || result === void 0 ? void 0 : result.skipEvent) {
|
|
1762
1772
|
skip = true;
|
|
1763
1773
|
break;
|
|
@@ -1771,24 +1781,87 @@ const runPluginHook = async (plugins, hookName, baseParams) => {
|
|
|
1771
1781
|
if ((result === null || result === void 0 ? void 0 : result.providers) !== undefined) {
|
|
1772
1782
|
currentProviders = result.providers;
|
|
1773
1783
|
}
|
|
1784
|
+
}
|
|
1785
|
+
catch (e) {
|
|
1786
|
+
logger.error(`Plugin ${plugin.name} ${hookName} error: ${e}`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
eventName: currentEventName,
|
|
1791
|
+
data: currentData,
|
|
1792
|
+
providers: currentProviders,
|
|
1793
|
+
skip,
|
|
1794
|
+
};
|
|
1795
|
+
};
|
|
1796
|
+
// Root hooks mirror the edge processPluginsRoot: every plugin runs (except the
|
|
1797
|
+
// one that emitted this event), additional events are dispatched before any
|
|
1798
|
+
// skip is applied, and skips are scoped — a global plugin marks every browser
|
|
1799
|
+
// instance, a scoped one only its resolved instances. The skip is a marker:
|
|
1800
|
+
// channel/instance hooks still run and only the final tag is suppressed.
|
|
1801
|
+
const runRootPluginHooks = async (plugins, baseParams, pluginScopes, configuredTags, pluginSource) => {
|
|
1802
|
+
var _a, _b;
|
|
1803
|
+
const payload = baseParams['payload'];
|
|
1804
|
+
let currentEventName = payload['eventName'];
|
|
1805
|
+
let currentData = payload['data'];
|
|
1806
|
+
let currentProviders = baseParams['providers'];
|
|
1807
|
+
const skippedInstances = new Set();
|
|
1808
|
+
for (const plugin of plugins) {
|
|
1809
|
+
if (plugin.name === pluginSource)
|
|
1810
|
+
continue;
|
|
1811
|
+
const hook = plugin.rules.tagRoot;
|
|
1812
|
+
if (!hook)
|
|
1813
|
+
continue;
|
|
1814
|
+
try {
|
|
1815
|
+
const result = await hook({
|
|
1816
|
+
...baseParams,
|
|
1817
|
+
payload: {
|
|
1818
|
+
...payload,
|
|
1819
|
+
eventName: currentEventName,
|
|
1820
|
+
data: jsonClone(currentData),
|
|
1821
|
+
},
|
|
1822
|
+
variables: plugin.variables || {},
|
|
1823
|
+
});
|
|
1774
1824
|
if (result === null || result === void 0 ? void 0 : result.additionalEvents) {
|
|
1775
|
-
|
|
1776
|
-
|
|
1825
|
+
dispatchAdditionalEvents(plugin, pluginScopes, result.additionalEvents);
|
|
1826
|
+
}
|
|
1827
|
+
if (result === null || result === void 0 ? void 0 : result.skipEvent) {
|
|
1828
|
+
const scopeSkipKeys = getScopedPluginSkipKeys(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name], configuredTags);
|
|
1829
|
+
if (scopeSkipKeys === null) {
|
|
1830
|
+
// Global plugin: skip every configured browser instance.
|
|
1831
|
+
for (const [provider, instances] of configuredTags) {
|
|
1832
|
+
for (const instanceName of instances) {
|
|
1833
|
+
skippedInstances.add(`${provider}||${instanceName}`);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1777
1836
|
}
|
|
1837
|
+
else {
|
|
1838
|
+
for (const key of scopeSkipKeys) {
|
|
1839
|
+
skippedInstances.add(key);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if ((_a = result === null || result === void 0 ? void 0 : result.payload) === null || _a === void 0 ? void 0 : _a.eventName) {
|
|
1844
|
+
currentEventName = result.payload.eventName;
|
|
1845
|
+
}
|
|
1846
|
+
if ((_b = result === null || result === void 0 ? void 0 : result.payload) === null || _b === void 0 ? void 0 : _b.data) {
|
|
1847
|
+
currentData = result.payload.data;
|
|
1848
|
+
}
|
|
1849
|
+
if ((result === null || result === void 0 ? void 0 : result.providers) !== undefined) {
|
|
1850
|
+
currentProviders = result.providers;
|
|
1778
1851
|
}
|
|
1779
1852
|
}
|
|
1780
1853
|
catch (e) {
|
|
1781
|
-
logger.error(`Plugin ${plugin.name}
|
|
1854
|
+
logger.error(`Plugin ${plugin.name} tagRoot error: ${e}`);
|
|
1782
1855
|
}
|
|
1783
1856
|
}
|
|
1784
1857
|
return {
|
|
1785
1858
|
eventName: currentEventName,
|
|
1786
1859
|
data: currentData,
|
|
1787
1860
|
providers: currentProviders,
|
|
1788
|
-
|
|
1861
|
+
skippedInstances,
|
|
1789
1862
|
};
|
|
1790
1863
|
};
|
|
1791
|
-
const processTag = async (destination, eventName, data = {}, providers, options) => {
|
|
1864
|
+
const processTag = async (destination, eventName, data = {}, providers, options, pluginSource) => {
|
|
1792
1865
|
var _a, _b;
|
|
1793
1866
|
let currentEventName = eventName;
|
|
1794
1867
|
if (!getSetting(destination, 'initialized')) {
|
|
@@ -1815,15 +1888,6 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1815
1888
|
const consentCategory = getConsentCategories(destination);
|
|
1816
1889
|
const consentSettings = getSetting(destination, 'consentSetting');
|
|
1817
1890
|
const userConsent = { consentChannel, consentCategory, consentSettings };
|
|
1818
|
-
// Grey Switch: when enabled, still send the event to the server for
|
|
1819
|
-
// non-consented users. Browser pixels remain gated by hasUserConsent below,
|
|
1820
|
-
// so nothing fires client-side.
|
|
1821
|
-
const greySwitchAllows = greySwitchAllowsServerEvent({
|
|
1822
|
-
greySwitch: getSetting(destination, 'greySwitch') || false,
|
|
1823
|
-
greySwitchEuUk: getSetting(destination, 'greySwitchEuUk') || false,
|
|
1824
|
-
country: requestCountry,
|
|
1825
|
-
isEURequest,
|
|
1826
|
-
});
|
|
1827
1891
|
const ip = getSetting(destination, 'ip') || null;
|
|
1828
1892
|
const userProperties = getSetting(destination, 'userProperties');
|
|
1829
1893
|
if (skipZeroPurchaseEvent && isZeroPurchaseEvent({ eventName, data })) {
|
|
@@ -1844,6 +1908,7 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1844
1908
|
providers = rulesResult.updatedProviders;
|
|
1845
1909
|
}
|
|
1846
1910
|
const plugins = getPlugins(destination);
|
|
1911
|
+
const pluginScopes = getSetting(destination, 'pluginScopes');
|
|
1847
1912
|
const pluginSettings = {
|
|
1848
1913
|
userId,
|
|
1849
1914
|
sessionId,
|
|
@@ -1859,26 +1924,15 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1859
1924
|
const pluginUtilities = {
|
|
1860
1925
|
getIsNewCustomerFlag: () => getIsNewCustomerFlag(processGetData.bind(null, destination)),
|
|
1861
1926
|
};
|
|
1927
|
+
let rootSkippedInstances = new Set();
|
|
1862
1928
|
if (plugins.length > 0) {
|
|
1863
|
-
const rootResult = await
|
|
1929
|
+
const rootResult = await runRootPluginHooks(plugins, {
|
|
1864
1930
|
payload: { eventName: currentEventName, data, eventId },
|
|
1865
1931
|
context: pluginContext,
|
|
1866
1932
|
providers,
|
|
1867
1933
|
settings: pluginSettings,
|
|
1868
1934
|
utilities: pluginUtilities,
|
|
1869
|
-
});
|
|
1870
|
-
if (rootResult.skip) {
|
|
1871
|
-
sendTag(destination, {
|
|
1872
|
-
configuratorProcessed: true,
|
|
1873
|
-
eventName: currentEventName,
|
|
1874
|
-
eventId,
|
|
1875
|
-
data,
|
|
1876
|
-
providerData: {},
|
|
1877
|
-
providers,
|
|
1878
|
-
options,
|
|
1879
|
-
});
|
|
1880
|
-
return;
|
|
1881
|
-
}
|
|
1935
|
+
}, pluginScopes, configuredTags, pluginSource);
|
|
1882
1936
|
if (rootResult.providers !== undefined) {
|
|
1883
1937
|
// eslint-disable-next-line no-param-reassign
|
|
1884
1938
|
providers = rootResult.providers;
|
|
@@ -1886,6 +1940,7 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1886
1940
|
currentEventName = rootResult.eventName;
|
|
1887
1941
|
// eslint-disable-next-line no-param-reassign
|
|
1888
1942
|
data = rootResult.data;
|
|
1943
|
+
rootSkippedInstances = rootResult.skippedInstances;
|
|
1889
1944
|
}
|
|
1890
1945
|
if (!rulesResult.skipBrowserEvent) {
|
|
1891
1946
|
const currencySettings = getSetting(destination, 'currency');
|
|
@@ -1897,69 +1952,96 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1897
1952
|
logger.log(`Provider ${pkg.name} is not in allow list`);
|
|
1898
1953
|
continue;
|
|
1899
1954
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
providerId: pkg.name,
|
|
1912
|
-
utilities: pluginUtilities,
|
|
1955
|
+
const providerId = pkg.name;
|
|
1956
|
+
const variables = getProviderVariables(destination, pkg.name);
|
|
1957
|
+
// Per-instance state, mirroring the edge PluginData array: each instance
|
|
1958
|
+
// carries its own payload and skip marker (seeded from the root skip
|
|
1959
|
+
// scope). A skip never short-circuits the hooks — only the final tag.
|
|
1960
|
+
const instanceStates = new Map();
|
|
1961
|
+
for (const variable of variables) {
|
|
1962
|
+
instanceStates.set(variable.tagName, {
|
|
1963
|
+
eventName: currentEventName,
|
|
1964
|
+
data,
|
|
1965
|
+
skipped: rootSkippedInstances.has(`${providerId}||${variable.tagName}`),
|
|
1913
1966
|
});
|
|
1914
|
-
if (channelResult.skip)
|
|
1915
|
-
continue;
|
|
1916
|
-
providerEventName = channelResult.eventName;
|
|
1917
|
-
channelData = channelResult.data;
|
|
1918
1967
|
}
|
|
1919
|
-
|
|
1968
|
+
// Channel phase (processPluginsChannel): tagChannel runs once per
|
|
1969
|
+
// instance, regardless of skip/consent/geo, so its side effects and
|
|
1970
|
+
// additional events still fire on skipped instances.
|
|
1971
|
+
const channelPlugins = plugins.filter((plugin) => plugin.name !== pluginSource &&
|
|
1972
|
+
isPluginAllowed(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name], providerId, null));
|
|
1973
|
+
if (channelPlugins.length > 0) {
|
|
1974
|
+
for (const variable of variables) {
|
|
1975
|
+
const state = instanceStates.get(variable.tagName);
|
|
1976
|
+
const channelResult = await runPluginHook(channelPlugins, 'tagChannel', pluginScopes, {
|
|
1977
|
+
payload: {
|
|
1978
|
+
eventName: state.eventName,
|
|
1979
|
+
data: state.data,
|
|
1980
|
+
eventId,
|
|
1981
|
+
},
|
|
1982
|
+
context: pluginContext,
|
|
1983
|
+
settings: pluginSettings,
|
|
1984
|
+
providerId,
|
|
1985
|
+
utilities: pluginUtilities,
|
|
1986
|
+
});
|
|
1987
|
+
if (channelResult.skip) {
|
|
1988
|
+
state.skipped = true;
|
|
1989
|
+
}
|
|
1990
|
+
state.eventName = channelResult.eventName;
|
|
1991
|
+
state.data = channelResult.data;
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
// Instance phase (processPluginsInstance + tag send): consent/geo gate,
|
|
1995
|
+
// run tagInstance, then suppress only the final tag when skipped.
|
|
1920
1996
|
const result = {};
|
|
1921
1997
|
const executionContext = new Map();
|
|
1922
1998
|
for (const variable of variables) {
|
|
1923
|
-
|
|
1924
|
-
|
|
1999
|
+
const state = instanceStates.get(variable.tagName);
|
|
2000
|
+
if (!isProviderInstanceAllowed(providers, providerId, variable.tagName)) {
|
|
2001
|
+
logger.log(`Provider instance is not allowed (${providerId}: ${variable.tagName})`);
|
|
1925
2002
|
continue;
|
|
1926
2003
|
}
|
|
1927
|
-
if (!hasUserConsent(userConsent,
|
|
1928
|
-
logger.log(`Consent is missing (${
|
|
2004
|
+
if (!hasUserConsent(userConsent, providerId, variable.tagName)) {
|
|
2005
|
+
logger.log(`Consent is missing (${providerId}: ${variable.tagName})`);
|
|
1929
2006
|
continue;
|
|
1930
2007
|
}
|
|
1931
2008
|
if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
|
|
1932
2009
|
logger.log('GEO request region does not match the filter, skipping');
|
|
1933
2010
|
continue;
|
|
1934
2011
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
if (
|
|
1938
|
-
const instanceResult = await runPluginHook(
|
|
2012
|
+
const instancePlugins = plugins.filter((plugin) => plugin.name !== pluginSource &&
|
|
2013
|
+
isPluginAllowed(pluginScopes === null || pluginScopes === void 0 ? void 0 : pluginScopes[plugin.name], providerId, variable.tagName));
|
|
2014
|
+
if (instancePlugins.length > 0) {
|
|
2015
|
+
const instanceResult = await runPluginHook(instancePlugins, 'tagInstance', pluginScopes, {
|
|
1939
2016
|
payload: {
|
|
1940
|
-
eventName:
|
|
1941
|
-
data:
|
|
2017
|
+
eventName: state.eventName,
|
|
2018
|
+
data: state.data,
|
|
1942
2019
|
eventId,
|
|
1943
2020
|
},
|
|
1944
2021
|
context: pluginContext,
|
|
1945
2022
|
settings: pluginSettings,
|
|
1946
|
-
providerId
|
|
2023
|
+
providerId,
|
|
1947
2024
|
utilities: pluginUtilities,
|
|
1948
2025
|
});
|
|
1949
|
-
if (instanceResult.skip)
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2026
|
+
if (instanceResult.skip) {
|
|
2027
|
+
state.skipped = true;
|
|
2028
|
+
}
|
|
2029
|
+
state.eventName = instanceResult.eventName;
|
|
2030
|
+
state.data = instanceResult.data;
|
|
2031
|
+
}
|
|
2032
|
+
if (state.skipped) {
|
|
2033
|
+
logger.log(`Skipping event due to plugin condition (${providerId}: ${variable.tagName})`);
|
|
2034
|
+
continue;
|
|
1953
2035
|
}
|
|
1954
|
-
const conversion = preparePayloadWithConversion(jsonClone(
|
|
2036
|
+
const conversion = preparePayloadWithConversion(jsonClone(state.data), currencySettings);
|
|
1955
2037
|
const payload = ((_a = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _a === void 0 ? void 0 : _a.length) === 0 ||
|
|
1956
|
-
((_b = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _b === void 0 ? void 0 : _b.includes(
|
|
2038
|
+
((_b = conversion === null || conversion === void 0 ? void 0 : conversion.providers) === null || _b === void 0 ? void 0 : _b.includes(providerId))
|
|
1957
2039
|
? conversion.payload
|
|
1958
|
-
:
|
|
2040
|
+
: state.data;
|
|
1959
2041
|
result[variable.tagName] = pkg.tag({
|
|
1960
2042
|
userId,
|
|
1961
2043
|
sessionId,
|
|
1962
|
-
eventName:
|
|
2044
|
+
eventName: state.eventName,
|
|
1963
2045
|
eventId,
|
|
1964
2046
|
data: jsonClone(payload),
|
|
1965
2047
|
sendTag: sendTag.bind(null, destination),
|
|
@@ -1974,16 +2056,17 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
1974
2056
|
pageUrl: getPageUrl(destination),
|
|
1975
2057
|
});
|
|
1976
2058
|
}
|
|
1977
|
-
|
|
2059
|
+
if (Object.keys(result).length > 0) {
|
|
2060
|
+
providerData[providerId] = result;
|
|
2061
|
+
}
|
|
1978
2062
|
}
|
|
1979
2063
|
}
|
|
1980
2064
|
else {
|
|
1981
2065
|
logger.log('Browser events skipped by event rules, sending tag to server only');
|
|
1982
2066
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
}
|
|
2067
|
+
// The server event is always forwarded to EdgeTag regardless of consent or
|
|
2068
|
+
// provider config; the CDN re-runs this logic server-side. Browser tags are
|
|
2069
|
+
// still individually gated by consent/geo/provider above.
|
|
1987
2070
|
sendTag(destination, {
|
|
1988
2071
|
configuratorProcessed: true,
|
|
1989
2072
|
eventName: currentEventName,
|
|
@@ -2002,26 +2085,15 @@ const processTag = async (destination, eventName, data = {}, providers, options)
|
|
|
2002
2085
|
handleTag(additionalEventName, jsonClone(originalData || {}), originalProviders, options);
|
|
2003
2086
|
}
|
|
2004
2087
|
};
|
|
2005
|
-
const handleTag = (eventName, data = {}, providers, options) => {
|
|
2088
|
+
const handleTag = (eventName, data = {}, providers, options, pluginSource) => {
|
|
2006
2089
|
if (options === null || options === void 0 ? void 0 : options.destination) {
|
|
2007
|
-
processTag(options.destination, eventName, data, providers, options).catch(logger.error);
|
|
2090
|
+
processTag(options.destination, eventName, data, providers, options, pluginSource).catch(logger.error);
|
|
2008
2091
|
return;
|
|
2009
2092
|
}
|
|
2010
2093
|
getInstances().forEach((instance) => {
|
|
2011
|
-
processTag(instance, eventName, data, providers, options).catch(logger.error);
|
|
2094
|
+
processTag(instance, eventName, data, providers, options, pluginSource).catch(logger.error);
|
|
2012
2095
|
});
|
|
2013
2096
|
};
|
|
2014
|
-
const hasAllowedManifestTags = (tags, consent, providersConfig) => {
|
|
2015
|
-
for (const [pkg, tagNames] of tags) {
|
|
2016
|
-
for (const tagName of tagNames) {
|
|
2017
|
-
if (hasUserConsent(consent, pkg, tagName) &&
|
|
2018
|
-
isProviderInstanceAllowed(providersConfig, pkg, tagName)) {
|
|
2019
|
-
return true;
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
return false;
|
|
2024
|
-
};
|
|
2025
2097
|
|
|
2026
2098
|
const processData = (destination, data, providers, options) => {
|
|
2027
2099
|
saveKV(destination, data);
|
|
@@ -2561,6 +2633,7 @@ const handleInit = (preferences) => {
|
|
|
2561
2633
|
configuratorSetting: result.configuratorSetting,
|
|
2562
2634
|
userProperties: result.userProperties,
|
|
2563
2635
|
ip: result.ip,
|
|
2636
|
+
pluginScopes: result.pluginScopes,
|
|
2564
2637
|
});
|
|
2565
2638
|
if (result.storageId != null) {
|
|
2566
2639
|
savePerKey(preferences.edgeURL, 'local', tagStorage, result.storageId, storageIdKey);
|
package/internal.d.ts
CHANGED