@blotoutio/edgetag-sdk-js 1.73.0 → 1.74.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.
Files changed (3) hide show
  1. package/index.cjs.js +217 -167
  2. package/index.mjs +217 -167
  3. package/package.json +1 -1
package/index.cjs.js CHANGED
@@ -1497,7 +1497,7 @@ const getStandardPayload = (destination, payload) => {
1497
1497
  referrer: getReferrer(destination),
1498
1498
  search: getSearch(destination),
1499
1499
  locale: getLocale(),
1500
- sdkVersion: "1.73.0" ,
1500
+ sdkVersion: "1.74.0" ,
1501
1501
  ...(payload || {}),
1502
1502
  };
1503
1503
  let storage = {};
@@ -1619,6 +1619,216 @@ const addStub = (destination, stub) => {
1619
1619
  });
1620
1620
  };
1621
1621
 
1622
+ const processData = (destination, data, providers, options) => {
1623
+ saveKV(destination, data);
1624
+ updateUserProperties(destination, data);
1625
+ const providerPackages = getSetting(destination, 'browserPackages');
1626
+ const configuredTags = getSetting(destination, 'channels');
1627
+ const userId = getUserId$1(destination);
1628
+ const requestCountry = getSetting(destination, 'geoCountry') || null;
1629
+ const requestRegion = getSetting(destination, 'geoRegion') || null;
1630
+ const isEURequest = getSetting(destination, 'isEURequest') || false;
1631
+ const consentChannel = getConsent$1(destination);
1632
+ const consentCategory = getConsentCategories(destination);
1633
+ const consentSettings = getSetting(destination, 'consentSetting');
1634
+ const userConsent = { consentChannel, consentCategory, consentSettings };
1635
+ for (const pkg of providerPackages) {
1636
+ if (!pkg || !pkg.user || !pkg.name) {
1637
+ continue;
1638
+ }
1639
+ if (!configuredTags.has(pkg.name)) {
1640
+ logger.log(`Provider ${pkg.name} is not in allow list`);
1641
+ continue;
1642
+ }
1643
+ const variables = getProviderVariables(destination, pkg.name);
1644
+ for (const variable of variables) {
1645
+ if (!isProviderInstanceAllowed(providers, pkg.name, variable.tagName)) {
1646
+ logger.log(`Data not allowed for ${pkg.name} (${variable.tagName})`);
1647
+ continue;
1648
+ }
1649
+ if (!hasUserConsent(userConsent, pkg.name, variable.tagName)) {
1650
+ logger.log(`Consent is missing for ${pkg.name} (${variable.tagName})`);
1651
+ continue;
1652
+ }
1653
+ if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
1654
+ logger.log(`GEO request region does not match list, skipping`);
1655
+ continue;
1656
+ }
1657
+ pkg.user({
1658
+ userId,
1659
+ data,
1660
+ manifestVariables: variable.variableSet,
1661
+ destination,
1662
+ });
1663
+ }
1664
+ }
1665
+ postRequest(getDataURL(destination), { data, providers }, options).catch(logger.error);
1666
+ };
1667
+ const handleData = (data, providers, options) => {
1668
+ if (!data || Object.keys(data).length === 0) {
1669
+ logger.error('Provide data for data API.');
1670
+ return;
1671
+ }
1672
+ if (options === null || options === void 0 ? void 0 : options.destination) {
1673
+ processData(options.destination, data, providers, options);
1674
+ return;
1675
+ }
1676
+ getInstances().forEach((destination) => {
1677
+ processData(destination, data, providers, options);
1678
+ });
1679
+ };
1680
+
1681
+ const cacheKey = `${keyPrefix}Cache`;
1682
+ const identity = (v) => v;
1683
+ const saveDataToEdge = (destination, key, value, provider) => {
1684
+ if (!value) {
1685
+ return;
1686
+ }
1687
+ let updatedValue = value;
1688
+ if (typeof value !== 'string') {
1689
+ try {
1690
+ updatedValue = JSON.stringify(value);
1691
+ }
1692
+ catch {
1693
+ logger.log('Error stringify value.');
1694
+ return;
1695
+ }
1696
+ }
1697
+ handleData({ [`${provider}::${key}`]: updatedValue }, undefined, {
1698
+ destination,
1699
+ });
1700
+ };
1701
+ const handleCaptureQuery = (destination, provider, key, persistType, map) => {
1702
+ try {
1703
+ if (!window) {
1704
+ return;
1705
+ }
1706
+ }
1707
+ catch {
1708
+ return;
1709
+ }
1710
+ const params = new URLSearchParams(getSearch(destination));
1711
+ if (!params || !params.get(key)) {
1712
+ return;
1713
+ }
1714
+ const data = (map !== null && map !== void 0 ? map : identity)(params.get(key));
1715
+ if (!data) {
1716
+ return;
1717
+ }
1718
+ if (persistType === 'edge') {
1719
+ saveDataToEdge(destination, key, data, provider);
1720
+ return;
1721
+ }
1722
+ saveDataPerKey(destination, persistType, provider, data, key);
1723
+ };
1724
+ const getFromCache = (destination, persistType, provider, key) => {
1725
+ var _a;
1726
+ const type = persistType === 'edge' ? 'local' : persistType;
1727
+ const cache = getData$1(destination, type, cacheKey);
1728
+ return (_a = cache[provider]) === null || _a === void 0 ? void 0 : _a[key];
1729
+ };
1730
+ const saveToCache = (destination, persistType, provider, key, value) => {
1731
+ const type = persistType === 'edge' ? 'local' : persistType;
1732
+ const cache = getData$1(destination, type, cacheKey);
1733
+ if (!cache[provider]) {
1734
+ cache[provider] = { [key]: value };
1735
+ }
1736
+ else {
1737
+ cache[provider][key] = value;
1738
+ }
1739
+ saveData(destination, type, cache, cacheKey);
1740
+ };
1741
+ const handleCaptureStorage = (destination, provider, key, persistType, location, map, replay = false) => {
1742
+ let data;
1743
+ try {
1744
+ switch (location) {
1745
+ case 'cookie': {
1746
+ data = (map !== null && map !== void 0 ? map : identity)(getCookieValue(key));
1747
+ break;
1748
+ }
1749
+ case 'local': {
1750
+ data = (map !== null && map !== void 0 ? map : identity)(localStorage.getItem(key));
1751
+ break;
1752
+ }
1753
+ case 'session': {
1754
+ data = (map !== null && map !== void 0 ? map : identity)(sessionStorage.getItem(key));
1755
+ }
1756
+ }
1757
+ }
1758
+ catch {
1759
+ return;
1760
+ }
1761
+ if (!data) {
1762
+ return;
1763
+ }
1764
+ const cachedKey = `${getUserId$1(destination)}/${key}`;
1765
+ const cachedValue = getFromCache(destination, persistType, provider, cachedKey);
1766
+ if (persistType === 'edge' && cachedValue !== data) {
1767
+ saveDataToEdge(destination, key, data, provider);
1768
+ saveToCache(destination, persistType, provider, cachedKey, data);
1769
+ return;
1770
+ }
1771
+ // A cache hit means this rule's value has not moved since it was last
1772
+ // written, so a replay has nothing left to do: saveDataPerKey is a getItem +
1773
+ // JSON.parse + JSON.stringify + setItem, per storage rule, per event, on
1774
+ // every tag in the fleet that has one. The boot path always falls through, so
1775
+ // the value is written at least once per page.
1776
+ if (replay && cachedValue === data) {
1777
+ return;
1778
+ }
1779
+ saveDataPerKey(destination, persistType, provider, data, key);
1780
+ // Cached for every persist type, not just 'edge' — the guard above is what
1781
+ // keeps a non-edge rule (trade-desk's uc_tcf) off the read/parse/write cycle
1782
+ // on every event of the visit.
1783
+ saveToCache(destination, persistType, provider, cachedKey, data);
1784
+ };
1785
+ const registry = new Map();
1786
+ // A manifest pass is the authoritative provider set for the destination, and
1787
+ // handleCapture only replaces the entry for the provider it is called with.
1788
+ // Without this, a channel dropped from a later pass (SPA re-init, manifest
1789
+ // refresh, channel removed) keeps replaying its storage rules for the rest of
1790
+ // the page lifetime. handleManifest calls this once, before its provider loop.
1791
+ const resetCaptures = (destination) => {
1792
+ registry.delete(destination);
1793
+ };
1794
+ const handleCapture = (destination, provider, params, capture) => {
1795
+ var _a;
1796
+ const existing = ((_a = registry.get(destination)) !== null && _a !== void 0 ? _a : []).filter((registration) => registration.provider !== provider);
1797
+ registry.set(destination, [...existing, { provider, params, capture }]);
1798
+ params.forEach((param) => {
1799
+ switch (param.type) {
1800
+ case 'query': {
1801
+ handleCaptureQuery(destination, provider, param.key, param.persist, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
1802
+ break;
1803
+ }
1804
+ case 'storage': {
1805
+ handleCaptureStorage(destination, provider, param.key, param.persist, param.location, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
1806
+ break;
1807
+ }
1808
+ }
1809
+ });
1810
+ };
1811
+ // A storage capture rule reads a slot the provider's own pixel may not have
1812
+ // written yet: handleManifest calls handleCapture before pkg.init(), and
1813
+ // Customers.ai's pixel is injected async, so on a first visit the read always
1814
+ // loses. Re-read per event so a value that appears later is still captured for
1815
+ // that pageview.
1816
+ //
1817
+ // Opt-in per rule (`replay: true`) and storage rules only, so a provider pays
1818
+ // the per-event read only where a late-writing pixel makes the boot read
1819
+ // unreliable.
1820
+ const replayStorageCaptures = (destination) => {
1821
+ var _a;
1822
+ (_a = registry.get(destination)) === null || _a === void 0 ? void 0 : _a.forEach(({ provider, params, capture }) => {
1823
+ params.forEach((param) => {
1824
+ if (param.type !== 'storage' || !param.replay) {
1825
+ return;
1826
+ }
1827
+ handleCaptureStorage(destination, provider, param.key, param.persist, param.location, capture === null || capture === void 0 ? void 0 : capture.bind(null, param), true);
1828
+ });
1829
+ });
1830
+ };
1831
+
1622
1832
  const preparePayloadWithConversion = (payloadData, currencySettings) => {
1623
1833
  const currency = payloadData['currency'];
1624
1834
  if (!currency || !currencySettings) {
@@ -1942,6 +2152,9 @@ const processTag = async (destination, eventName, data = {}, providers, options,
1942
2152
  });
1943
2153
  return;
1944
2154
  }
2155
+ // Capture rules run once at boot, before the providers' own pixels load —
2156
+ // see replayStorageCaptures.
2157
+ replayStorageCaptures(destination);
1945
2158
  let eventId = data['eventId'];
1946
2159
  if (!eventId) {
1947
2160
  eventId = generateEventId(currentEventName);
@@ -2176,65 +2389,6 @@ const handleTag = (eventName, data = {}, providers, options, pluginSource, plugi
2176
2389
  });
2177
2390
  };
2178
2391
 
2179
- const processData = (destination, data, providers, options) => {
2180
- saveKV(destination, data);
2181
- updateUserProperties(destination, data);
2182
- const providerPackages = getSetting(destination, 'browserPackages');
2183
- const configuredTags = getSetting(destination, 'channels');
2184
- const userId = getUserId$1(destination);
2185
- const requestCountry = getSetting(destination, 'geoCountry') || null;
2186
- const requestRegion = getSetting(destination, 'geoRegion') || null;
2187
- const isEURequest = getSetting(destination, 'isEURequest') || false;
2188
- const consentChannel = getConsent$1(destination);
2189
- const consentCategory = getConsentCategories(destination);
2190
- const consentSettings = getSetting(destination, 'consentSetting');
2191
- const userConsent = { consentChannel, consentCategory, consentSettings };
2192
- for (const pkg of providerPackages) {
2193
- if (!pkg || !pkg.user || !pkg.name) {
2194
- continue;
2195
- }
2196
- if (!configuredTags.has(pkg.name)) {
2197
- logger.log(`Provider ${pkg.name} is not in allow list`);
2198
- continue;
2199
- }
2200
- const variables = getProviderVariables(destination, pkg.name);
2201
- for (const variable of variables) {
2202
- if (!isProviderInstanceAllowed(providers, pkg.name, variable.tagName)) {
2203
- logger.log(`Data not allowed for ${pkg.name} (${variable.tagName})`);
2204
- continue;
2205
- }
2206
- if (!hasUserConsent(userConsent, pkg.name, variable.tagName)) {
2207
- logger.log(`Consent is missing for ${pkg.name} (${variable.tagName})`);
2208
- continue;
2209
- }
2210
- if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
2211
- logger.log(`GEO request region does not match list, skipping`);
2212
- continue;
2213
- }
2214
- pkg.user({
2215
- userId,
2216
- data,
2217
- manifestVariables: variable.variableSet,
2218
- destination,
2219
- });
2220
- }
2221
- }
2222
- postRequest(getDataURL(destination), { data, providers }, options).catch(logger.error);
2223
- };
2224
- const handleData = (data, providers, options) => {
2225
- if (!data || Object.keys(data).length === 0) {
2226
- logger.error('Provide data for data API.');
2227
- return;
2228
- }
2229
- if (options === null || options === void 0 ? void 0 : options.destination) {
2230
- processData(options.destination, data, providers, options);
2231
- return;
2232
- }
2233
- getInstances().forEach((destination) => {
2234
- processData(destination, data, providers, options);
2235
- });
2236
- };
2237
-
2238
2392
  const handleConsent = (consent, consentCategories, options) => {
2239
2393
  if (options === null || options === void 0 ? void 0 : options.destination) {
2240
2394
  processConsent(options.destination, consent, consentCategories, options);
@@ -2399,113 +2553,6 @@ const setPreferences = (preferences) => {
2399
2553
  return providersPackages;
2400
2554
  };
2401
2555
 
2402
- const cacheKey = `${keyPrefix}Cache`;
2403
- const identity = (v) => v;
2404
- const saveDataToEdge = (destination, key, value, provider) => {
2405
- if (!value) {
2406
- return;
2407
- }
2408
- let updatedValue = value;
2409
- if (typeof value !== 'string') {
2410
- try {
2411
- updatedValue = JSON.stringify(value);
2412
- }
2413
- catch {
2414
- logger.log('Error stringify value.');
2415
- return;
2416
- }
2417
- }
2418
- handleData({ [`${provider}::${key}`]: updatedValue }, undefined, {
2419
- destination,
2420
- });
2421
- };
2422
- const handleCaptureQuery = (destination, provider, key, persistType, map) => {
2423
- try {
2424
- if (!window) {
2425
- return;
2426
- }
2427
- }
2428
- catch {
2429
- return;
2430
- }
2431
- const params = new URLSearchParams(getSearch(destination));
2432
- if (!params || !params.get(key)) {
2433
- return;
2434
- }
2435
- const data = (map !== null && map !== void 0 ? map : identity)(params.get(key));
2436
- if (!data) {
2437
- return;
2438
- }
2439
- if (persistType === 'edge') {
2440
- saveDataToEdge(destination, key, data, provider);
2441
- return;
2442
- }
2443
- saveDataPerKey(destination, persistType, provider, data, key);
2444
- };
2445
- const getFromCache = (destination, persistType, provider, key) => {
2446
- var _a;
2447
- const type = persistType === 'edge' ? 'local' : persistType;
2448
- const cache = getData$1(destination, type, cacheKey);
2449
- return (_a = cache[provider]) === null || _a === void 0 ? void 0 : _a[key];
2450
- };
2451
- const saveToCache = (destination, persistType, provider, key, value) => {
2452
- const type = persistType === 'edge' ? 'local' : persistType;
2453
- const cache = getData$1(destination, type, cacheKey);
2454
- if (!cache[provider]) {
2455
- cache[provider] = { [key]: value };
2456
- }
2457
- else {
2458
- cache[provider][key] = value;
2459
- }
2460
- saveData(destination, type, cache, cacheKey);
2461
- };
2462
- const handleCaptureStorage = (destination, provider, key, persistType, location, map) => {
2463
- let data;
2464
- try {
2465
- switch (location) {
2466
- case 'cookie': {
2467
- data = (map !== null && map !== void 0 ? map : identity)(getCookieValue(key));
2468
- break;
2469
- }
2470
- case 'local': {
2471
- data = (map !== null && map !== void 0 ? map : identity)(localStorage.getItem(key));
2472
- break;
2473
- }
2474
- case 'session': {
2475
- data = (map !== null && map !== void 0 ? map : identity)(sessionStorage.getItem(key));
2476
- }
2477
- }
2478
- }
2479
- catch {
2480
- return;
2481
- }
2482
- if (!data) {
2483
- return;
2484
- }
2485
- const cachedKey = `${getUserId$1(destination)}/${key}`;
2486
- const cachedValue = getFromCache(destination, persistType, provider, cachedKey);
2487
- if (persistType === 'edge' && cachedValue !== data) {
2488
- saveDataToEdge(destination, key, data, provider);
2489
- saveToCache(destination, persistType, provider, cachedKey, data);
2490
- return;
2491
- }
2492
- saveDataPerKey(destination, persistType, provider, data, key);
2493
- };
2494
- const handleCapture = (destination, provider, params, capture) => {
2495
- params.forEach((param) => {
2496
- switch (param.type) {
2497
- case 'query': {
2498
- handleCaptureQuery(destination, provider, param.key, param.persist, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
2499
- break;
2500
- }
2501
- case 'storage': {
2502
- handleCaptureStorage(destination, provider, param.key, param.persist, param.location, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
2503
- break;
2504
- }
2505
- }
2506
- });
2507
- };
2508
-
2509
2556
  const keyName = `${keyPrefix}Store`;
2510
2557
  const handleManifest = (destination, response) => {
2511
2558
  const providerPackages = getSetting(destination, 'browserPackages');
@@ -2516,6 +2563,9 @@ const handleManifest = (destination, response) => {
2516
2563
  const consentCategory = getConsentCategories(destination);
2517
2564
  const consentSettings = getSetting(destination, 'consentSetting');
2518
2565
  const userConsent = { consentChannel, consentCategory, consentSettings };
2566
+ // This manifest is the full provider set: drop the previous pass's capture
2567
+ // registrations so a channel that is no longer in it stops replaying.
2568
+ resetCaptures(destination);
2519
2569
  manifest.forEach((provider) => {
2520
2570
  addChannel(destination, provider.package, provider.tagName);
2521
2571
  const pkg = providerPackages.find((pkg) => pkg.name === provider.package);
package/index.mjs CHANGED
@@ -1495,7 +1495,7 @@ const getStandardPayload = (destination, payload) => {
1495
1495
  referrer: getReferrer(destination),
1496
1496
  search: getSearch(destination),
1497
1497
  locale: getLocale(),
1498
- sdkVersion: "1.73.0" ,
1498
+ sdkVersion: "1.74.0" ,
1499
1499
  ...(payload || {}),
1500
1500
  };
1501
1501
  let storage = {};
@@ -1617,6 +1617,216 @@ const addStub = (destination, stub) => {
1617
1617
  });
1618
1618
  };
1619
1619
 
1620
+ const processData = (destination, data, providers, options) => {
1621
+ saveKV(destination, data);
1622
+ updateUserProperties(destination, data);
1623
+ const providerPackages = getSetting(destination, 'browserPackages');
1624
+ const configuredTags = getSetting(destination, 'channels');
1625
+ const userId = getUserId$1(destination);
1626
+ const requestCountry = getSetting(destination, 'geoCountry') || null;
1627
+ const requestRegion = getSetting(destination, 'geoRegion') || null;
1628
+ const isEURequest = getSetting(destination, 'isEURequest') || false;
1629
+ const consentChannel = getConsent$1(destination);
1630
+ const consentCategory = getConsentCategories(destination);
1631
+ const consentSettings = getSetting(destination, 'consentSetting');
1632
+ const userConsent = { consentChannel, consentCategory, consentSettings };
1633
+ for (const pkg of providerPackages) {
1634
+ if (!pkg || !pkg.user || !pkg.name) {
1635
+ continue;
1636
+ }
1637
+ if (!configuredTags.has(pkg.name)) {
1638
+ logger.log(`Provider ${pkg.name} is not in allow list`);
1639
+ continue;
1640
+ }
1641
+ const variables = getProviderVariables(destination, pkg.name);
1642
+ for (const variable of variables) {
1643
+ if (!isProviderInstanceAllowed(providers, pkg.name, variable.tagName)) {
1644
+ logger.log(`Data not allowed for ${pkg.name} (${variable.tagName})`);
1645
+ continue;
1646
+ }
1647
+ if (!hasUserConsent(userConsent, pkg.name, variable.tagName)) {
1648
+ logger.log(`Consent is missing for ${pkg.name} (${variable.tagName})`);
1649
+ continue;
1650
+ }
1651
+ if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
1652
+ logger.log(`GEO request region does not match list, skipping`);
1653
+ continue;
1654
+ }
1655
+ pkg.user({
1656
+ userId,
1657
+ data,
1658
+ manifestVariables: variable.variableSet,
1659
+ destination,
1660
+ });
1661
+ }
1662
+ }
1663
+ postRequest(getDataURL(destination), { data, providers }, options).catch(logger.error);
1664
+ };
1665
+ const handleData = (data, providers, options) => {
1666
+ if (!data || Object.keys(data).length === 0) {
1667
+ logger.error('Provide data for data API.');
1668
+ return;
1669
+ }
1670
+ if (options === null || options === void 0 ? void 0 : options.destination) {
1671
+ processData(options.destination, data, providers, options);
1672
+ return;
1673
+ }
1674
+ getInstances().forEach((destination) => {
1675
+ processData(destination, data, providers, options);
1676
+ });
1677
+ };
1678
+
1679
+ const cacheKey = `${keyPrefix}Cache`;
1680
+ const identity = (v) => v;
1681
+ const saveDataToEdge = (destination, key, value, provider) => {
1682
+ if (!value) {
1683
+ return;
1684
+ }
1685
+ let updatedValue = value;
1686
+ if (typeof value !== 'string') {
1687
+ try {
1688
+ updatedValue = JSON.stringify(value);
1689
+ }
1690
+ catch {
1691
+ logger.log('Error stringify value.');
1692
+ return;
1693
+ }
1694
+ }
1695
+ handleData({ [`${provider}::${key}`]: updatedValue }, undefined, {
1696
+ destination,
1697
+ });
1698
+ };
1699
+ const handleCaptureQuery = (destination, provider, key, persistType, map) => {
1700
+ try {
1701
+ if (!window) {
1702
+ return;
1703
+ }
1704
+ }
1705
+ catch {
1706
+ return;
1707
+ }
1708
+ const params = new URLSearchParams(getSearch(destination));
1709
+ if (!params || !params.get(key)) {
1710
+ return;
1711
+ }
1712
+ const data = (map !== null && map !== void 0 ? map : identity)(params.get(key));
1713
+ if (!data) {
1714
+ return;
1715
+ }
1716
+ if (persistType === 'edge') {
1717
+ saveDataToEdge(destination, key, data, provider);
1718
+ return;
1719
+ }
1720
+ saveDataPerKey(destination, persistType, provider, data, key);
1721
+ };
1722
+ const getFromCache = (destination, persistType, provider, key) => {
1723
+ var _a;
1724
+ const type = persistType === 'edge' ? 'local' : persistType;
1725
+ const cache = getData$1(destination, type, cacheKey);
1726
+ return (_a = cache[provider]) === null || _a === void 0 ? void 0 : _a[key];
1727
+ };
1728
+ const saveToCache = (destination, persistType, provider, key, value) => {
1729
+ const type = persistType === 'edge' ? 'local' : persistType;
1730
+ const cache = getData$1(destination, type, cacheKey);
1731
+ if (!cache[provider]) {
1732
+ cache[provider] = { [key]: value };
1733
+ }
1734
+ else {
1735
+ cache[provider][key] = value;
1736
+ }
1737
+ saveData(destination, type, cache, cacheKey);
1738
+ };
1739
+ const handleCaptureStorage = (destination, provider, key, persistType, location, map, replay = false) => {
1740
+ let data;
1741
+ try {
1742
+ switch (location) {
1743
+ case 'cookie': {
1744
+ data = (map !== null && map !== void 0 ? map : identity)(getCookieValue(key));
1745
+ break;
1746
+ }
1747
+ case 'local': {
1748
+ data = (map !== null && map !== void 0 ? map : identity)(localStorage.getItem(key));
1749
+ break;
1750
+ }
1751
+ case 'session': {
1752
+ data = (map !== null && map !== void 0 ? map : identity)(sessionStorage.getItem(key));
1753
+ }
1754
+ }
1755
+ }
1756
+ catch {
1757
+ return;
1758
+ }
1759
+ if (!data) {
1760
+ return;
1761
+ }
1762
+ const cachedKey = `${getUserId$1(destination)}/${key}`;
1763
+ const cachedValue = getFromCache(destination, persistType, provider, cachedKey);
1764
+ if (persistType === 'edge' && cachedValue !== data) {
1765
+ saveDataToEdge(destination, key, data, provider);
1766
+ saveToCache(destination, persistType, provider, cachedKey, data);
1767
+ return;
1768
+ }
1769
+ // A cache hit means this rule's value has not moved since it was last
1770
+ // written, so a replay has nothing left to do: saveDataPerKey is a getItem +
1771
+ // JSON.parse + JSON.stringify + setItem, per storage rule, per event, on
1772
+ // every tag in the fleet that has one. The boot path always falls through, so
1773
+ // the value is written at least once per page.
1774
+ if (replay && cachedValue === data) {
1775
+ return;
1776
+ }
1777
+ saveDataPerKey(destination, persistType, provider, data, key);
1778
+ // Cached for every persist type, not just 'edge' — the guard above is what
1779
+ // keeps a non-edge rule (trade-desk's uc_tcf) off the read/parse/write cycle
1780
+ // on every event of the visit.
1781
+ saveToCache(destination, persistType, provider, cachedKey, data);
1782
+ };
1783
+ const registry = new Map();
1784
+ // A manifest pass is the authoritative provider set for the destination, and
1785
+ // handleCapture only replaces the entry for the provider it is called with.
1786
+ // Without this, a channel dropped from a later pass (SPA re-init, manifest
1787
+ // refresh, channel removed) keeps replaying its storage rules for the rest of
1788
+ // the page lifetime. handleManifest calls this once, before its provider loop.
1789
+ const resetCaptures = (destination) => {
1790
+ registry.delete(destination);
1791
+ };
1792
+ const handleCapture = (destination, provider, params, capture) => {
1793
+ var _a;
1794
+ const existing = ((_a = registry.get(destination)) !== null && _a !== void 0 ? _a : []).filter((registration) => registration.provider !== provider);
1795
+ registry.set(destination, [...existing, { provider, params, capture }]);
1796
+ params.forEach((param) => {
1797
+ switch (param.type) {
1798
+ case 'query': {
1799
+ handleCaptureQuery(destination, provider, param.key, param.persist, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
1800
+ break;
1801
+ }
1802
+ case 'storage': {
1803
+ handleCaptureStorage(destination, provider, param.key, param.persist, param.location, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
1804
+ break;
1805
+ }
1806
+ }
1807
+ });
1808
+ };
1809
+ // A storage capture rule reads a slot the provider's own pixel may not have
1810
+ // written yet: handleManifest calls handleCapture before pkg.init(), and
1811
+ // Customers.ai's pixel is injected async, so on a first visit the read always
1812
+ // loses. Re-read per event so a value that appears later is still captured for
1813
+ // that pageview.
1814
+ //
1815
+ // Opt-in per rule (`replay: true`) and storage rules only, so a provider pays
1816
+ // the per-event read only where a late-writing pixel makes the boot read
1817
+ // unreliable.
1818
+ const replayStorageCaptures = (destination) => {
1819
+ var _a;
1820
+ (_a = registry.get(destination)) === null || _a === void 0 ? void 0 : _a.forEach(({ provider, params, capture }) => {
1821
+ params.forEach((param) => {
1822
+ if (param.type !== 'storage' || !param.replay) {
1823
+ return;
1824
+ }
1825
+ handleCaptureStorage(destination, provider, param.key, param.persist, param.location, capture === null || capture === void 0 ? void 0 : capture.bind(null, param), true);
1826
+ });
1827
+ });
1828
+ };
1829
+
1620
1830
  const preparePayloadWithConversion = (payloadData, currencySettings) => {
1621
1831
  const currency = payloadData['currency'];
1622
1832
  if (!currency || !currencySettings) {
@@ -1940,6 +2150,9 @@ const processTag = async (destination, eventName, data = {}, providers, options,
1940
2150
  });
1941
2151
  return;
1942
2152
  }
2153
+ // Capture rules run once at boot, before the providers' own pixels load —
2154
+ // see replayStorageCaptures.
2155
+ replayStorageCaptures(destination);
1943
2156
  let eventId = data['eventId'];
1944
2157
  if (!eventId) {
1945
2158
  eventId = generateEventId(currentEventName);
@@ -2174,65 +2387,6 @@ const handleTag = (eventName, data = {}, providers, options, pluginSource, plugi
2174
2387
  });
2175
2388
  };
2176
2389
 
2177
- const processData = (destination, data, providers, options) => {
2178
- saveKV(destination, data);
2179
- updateUserProperties(destination, data);
2180
- const providerPackages = getSetting(destination, 'browserPackages');
2181
- const configuredTags = getSetting(destination, 'channels');
2182
- const userId = getUserId$1(destination);
2183
- const requestCountry = getSetting(destination, 'geoCountry') || null;
2184
- const requestRegion = getSetting(destination, 'geoRegion') || null;
2185
- const isEURequest = getSetting(destination, 'isEURequest') || false;
2186
- const consentChannel = getConsent$1(destination);
2187
- const consentCategory = getConsentCategories(destination);
2188
- const consentSettings = getSetting(destination, 'consentSetting');
2189
- const userConsent = { consentChannel, consentCategory, consentSettings };
2190
- for (const pkg of providerPackages) {
2191
- if (!pkg || !pkg.user || !pkg.name) {
2192
- continue;
2193
- }
2194
- if (!configuredTags.has(pkg.name)) {
2195
- logger.log(`Provider ${pkg.name} is not in allow list`);
2196
- continue;
2197
- }
2198
- const variables = getProviderVariables(destination, pkg.name);
2199
- for (const variable of variables) {
2200
- if (!isProviderInstanceAllowed(providers, pkg.name, variable.tagName)) {
2201
- logger.log(`Data not allowed for ${pkg.name} (${variable.tagName})`);
2202
- continue;
2203
- }
2204
- if (!hasUserConsent(userConsent, pkg.name, variable.tagName)) {
2205
- logger.log(`Consent is missing for ${pkg.name} (${variable.tagName})`);
2206
- continue;
2207
- }
2208
- if (!doesGeoRequestMatchList(requestCountry, requestRegion, isEURequest, variable.geoRegions)) {
2209
- logger.log(`GEO request region does not match list, skipping`);
2210
- continue;
2211
- }
2212
- pkg.user({
2213
- userId,
2214
- data,
2215
- manifestVariables: variable.variableSet,
2216
- destination,
2217
- });
2218
- }
2219
- }
2220
- postRequest(getDataURL(destination), { data, providers }, options).catch(logger.error);
2221
- };
2222
- const handleData = (data, providers, options) => {
2223
- if (!data || Object.keys(data).length === 0) {
2224
- logger.error('Provide data for data API.');
2225
- return;
2226
- }
2227
- if (options === null || options === void 0 ? void 0 : options.destination) {
2228
- processData(options.destination, data, providers, options);
2229
- return;
2230
- }
2231
- getInstances().forEach((destination) => {
2232
- processData(destination, data, providers, options);
2233
- });
2234
- };
2235
-
2236
2390
  const handleConsent = (consent, consentCategories, options) => {
2237
2391
  if (options === null || options === void 0 ? void 0 : options.destination) {
2238
2392
  processConsent(options.destination, consent, consentCategories, options);
@@ -2397,113 +2551,6 @@ const setPreferences = (preferences) => {
2397
2551
  return providersPackages;
2398
2552
  };
2399
2553
 
2400
- const cacheKey = `${keyPrefix}Cache`;
2401
- const identity = (v) => v;
2402
- const saveDataToEdge = (destination, key, value, provider) => {
2403
- if (!value) {
2404
- return;
2405
- }
2406
- let updatedValue = value;
2407
- if (typeof value !== 'string') {
2408
- try {
2409
- updatedValue = JSON.stringify(value);
2410
- }
2411
- catch {
2412
- logger.log('Error stringify value.');
2413
- return;
2414
- }
2415
- }
2416
- handleData({ [`${provider}::${key}`]: updatedValue }, undefined, {
2417
- destination,
2418
- });
2419
- };
2420
- const handleCaptureQuery = (destination, provider, key, persistType, map) => {
2421
- try {
2422
- if (!window) {
2423
- return;
2424
- }
2425
- }
2426
- catch {
2427
- return;
2428
- }
2429
- const params = new URLSearchParams(getSearch(destination));
2430
- if (!params || !params.get(key)) {
2431
- return;
2432
- }
2433
- const data = (map !== null && map !== void 0 ? map : identity)(params.get(key));
2434
- if (!data) {
2435
- return;
2436
- }
2437
- if (persistType === 'edge') {
2438
- saveDataToEdge(destination, key, data, provider);
2439
- return;
2440
- }
2441
- saveDataPerKey(destination, persistType, provider, data, key);
2442
- };
2443
- const getFromCache = (destination, persistType, provider, key) => {
2444
- var _a;
2445
- const type = persistType === 'edge' ? 'local' : persistType;
2446
- const cache = getData$1(destination, type, cacheKey);
2447
- return (_a = cache[provider]) === null || _a === void 0 ? void 0 : _a[key];
2448
- };
2449
- const saveToCache = (destination, persistType, provider, key, value) => {
2450
- const type = persistType === 'edge' ? 'local' : persistType;
2451
- const cache = getData$1(destination, type, cacheKey);
2452
- if (!cache[provider]) {
2453
- cache[provider] = { [key]: value };
2454
- }
2455
- else {
2456
- cache[provider][key] = value;
2457
- }
2458
- saveData(destination, type, cache, cacheKey);
2459
- };
2460
- const handleCaptureStorage = (destination, provider, key, persistType, location, map) => {
2461
- let data;
2462
- try {
2463
- switch (location) {
2464
- case 'cookie': {
2465
- data = (map !== null && map !== void 0 ? map : identity)(getCookieValue(key));
2466
- break;
2467
- }
2468
- case 'local': {
2469
- data = (map !== null && map !== void 0 ? map : identity)(localStorage.getItem(key));
2470
- break;
2471
- }
2472
- case 'session': {
2473
- data = (map !== null && map !== void 0 ? map : identity)(sessionStorage.getItem(key));
2474
- }
2475
- }
2476
- }
2477
- catch {
2478
- return;
2479
- }
2480
- if (!data) {
2481
- return;
2482
- }
2483
- const cachedKey = `${getUserId$1(destination)}/${key}`;
2484
- const cachedValue = getFromCache(destination, persistType, provider, cachedKey);
2485
- if (persistType === 'edge' && cachedValue !== data) {
2486
- saveDataToEdge(destination, key, data, provider);
2487
- saveToCache(destination, persistType, provider, cachedKey, data);
2488
- return;
2489
- }
2490
- saveDataPerKey(destination, persistType, provider, data, key);
2491
- };
2492
- const handleCapture = (destination, provider, params, capture) => {
2493
- params.forEach((param) => {
2494
- switch (param.type) {
2495
- case 'query': {
2496
- handleCaptureQuery(destination, provider, param.key, param.persist, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
2497
- break;
2498
- }
2499
- case 'storage': {
2500
- handleCaptureStorage(destination, provider, param.key, param.persist, param.location, capture === null || capture === void 0 ? void 0 : capture.bind(null, param));
2501
- break;
2502
- }
2503
- }
2504
- });
2505
- };
2506
-
2507
2554
  const keyName = `${keyPrefix}Store`;
2508
2555
  const handleManifest = (destination, response) => {
2509
2556
  const providerPackages = getSetting(destination, 'browserPackages');
@@ -2514,6 +2561,9 @@ const handleManifest = (destination, response) => {
2514
2561
  const consentCategory = getConsentCategories(destination);
2515
2562
  const consentSettings = getSetting(destination, 'consentSetting');
2516
2563
  const userConsent = { consentChannel, consentCategory, consentSettings };
2564
+ // This manifest is the full provider set: drop the previous pass's capture
2565
+ // registrations so a channel that is no longer in it stops replaying.
2566
+ resetCaptures(destination);
2517
2567
  manifest.forEach((provider) => {
2518
2568
  addChannel(destination, provider.package, provider.tagName);
2519
2569
  const pkg = providerPackages.find((pkg) => pkg.name === provider.package);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotoutio/edgetag-sdk-js",
3
- "version": "1.73.0",
3
+ "version": "1.74.0",
4
4
  "description": "JS SDK for EdgeTag",
5
5
  "author": "Blotout",
6
6
  "license": "MIT",