@coframe/sdk 1.1.43 → 1.1.45
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/dist/pkg/cf.cjs.js +1392 -96
- package/dist/pkg/cf.esm.mjs +1392 -96
- package/dist/pkg/types/index.d.ts +1 -0
- package/dist/pkg/types/react.d.ts +1 -0
- package/package.json +1 -1
package/dist/pkg/cf.esm.mjs
CHANGED
|
@@ -35,6 +35,22 @@ if (!Array.prototype.includes) {
|
|
|
35
35
|
enumerable: false,
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
if (!String.prototype.includes) {
|
|
39
|
+
Object.defineProperty(String.prototype, 'includes', {
|
|
40
|
+
value: function (search, position) {
|
|
41
|
+
if (this == null) {
|
|
42
|
+
throw new TypeError('String.prototype.includes called on null or undefined');
|
|
43
|
+
}
|
|
44
|
+
if (search instanceof RegExp) {
|
|
45
|
+
throw new TypeError('First argument to String.prototype.includes must not be a regular expression');
|
|
46
|
+
}
|
|
47
|
+
return String(this).indexOf(search, position) !== -1;
|
|
48
|
+
},
|
|
49
|
+
writable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
enumerable: false,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
38
54
|
if (!Array.prototype.flatMap) {
|
|
39
55
|
Object.defineProperty(Array.prototype, 'flatMap', {
|
|
40
56
|
value: function (callback, thisArg) {
|
|
@@ -1126,7 +1142,7 @@ function warnEventsNotSent() {
|
|
|
1126
1142
|
}
|
|
1127
1143
|
}
|
|
1128
1144
|
|
|
1129
|
-
const SDK_VERSION = '2.
|
|
1145
|
+
const SDK_VERSION = '2.10.0';
|
|
1130
1146
|
const queuedEvents = [];
|
|
1131
1147
|
const queuedMonitoringEvents = [];
|
|
1132
1148
|
const queuedSessions = [];
|
|
@@ -1474,6 +1490,7 @@ const applyUpdateModification = (element, args, logger, logMetadata = {}) => {
|
|
|
1474
1490
|
return true;
|
|
1475
1491
|
};
|
|
1476
1492
|
const executeJs = (jsCode, args = {}, opts = {}) => {
|
|
1493
|
+
var _a, _b, _c;
|
|
1477
1494
|
const key = `__cf_${Math.random().toString(36).slice(2, 12)}`;
|
|
1478
1495
|
const w = window;
|
|
1479
1496
|
w.Coframe = w.Coframe || {};
|
|
@@ -1482,11 +1499,15 @@ const executeJs = (jsCode, args = {}, opts = {}) => {
|
|
|
1482
1499
|
if (opts.elementRef) {
|
|
1483
1500
|
w.Coframe.vars[elKey] = opts.elementRef;
|
|
1484
1501
|
}
|
|
1502
|
+
const refKeys = Object.keys((_a = opts.refs) !== null && _a !== void 0 ? _a : {});
|
|
1503
|
+
for (const refName of refKeys) {
|
|
1504
|
+
w.Coframe.vars[`${key}_${refName}`] = (_b = opts.refs) === null || _b === void 0 ? void 0 : _b[refName];
|
|
1505
|
+
}
|
|
1485
1506
|
const invocation = opts.isExpression
|
|
1486
1507
|
? `(\n${jsCode}\n)(__args)`
|
|
1487
1508
|
: opts.elementRef
|
|
1488
|
-
? `(function(el){\n${jsCode}\n})(__args.el)`
|
|
1489
|
-
: `(function(){\n${jsCode}\n})()`;
|
|
1509
|
+
? `(function(variantSession){return (function(el){\n${jsCode}\n})(__args.el)})(__args.variantSession)`
|
|
1510
|
+
: `(function(variantSession){return (function(){\n${jsCode}\n})()})(__args.variantSession)`;
|
|
1490
1511
|
const ranKey = `${key}_ran`;
|
|
1491
1512
|
const script = document.createElement('script');
|
|
1492
1513
|
script.id = `coframe-script-${key}`;
|
|
@@ -1497,31 +1518,46 @@ const executeJs = (jsCode, args = {}, opts = {}) => {
|
|
|
1497
1518
|
try {
|
|
1498
1519
|
var __args = ${JSON.stringify(args)};
|
|
1499
1520
|
${opts.elementRef ? `__args.el = window.Coframe.vars[${JSON.stringify(elKey)}];` : ''}
|
|
1521
|
+
${refKeys
|
|
1522
|
+
.map((refName) => `__args[${JSON.stringify(refName)}] = window.Coframe.vars[${JSON.stringify(`${key}_${refName}`)}];`)
|
|
1523
|
+
.join('\n ')}
|
|
1500
1524
|
window.Coframe.vars[${JSON.stringify(key)}] = ${invocation};
|
|
1501
1525
|
} catch (e) {
|
|
1502
|
-
|
|
1526
|
+
var __err = 'Unknown error';
|
|
1527
|
+
try { __err = (e && (e.stack || e.message)) ? String(e.stack || e.message) : (String(e) || __err); } catch (_) {}
|
|
1528
|
+
window.Coframe.vars[${JSON.stringify(`${key}_err`)}] = __err;
|
|
1503
1529
|
}
|
|
1504
1530
|
window.Coframe.vars[${JSON.stringify(ranKey)}] = true;
|
|
1505
1531
|
})();
|
|
1506
1532
|
`;
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1533
|
+
let value;
|
|
1534
|
+
let err;
|
|
1535
|
+
let ran;
|
|
1536
|
+
try {
|
|
1537
|
+
document.head.appendChild(script);
|
|
1538
|
+
value = w.Coframe.vars[key];
|
|
1539
|
+
err = w.Coframe.vars[`${key}_err`];
|
|
1540
|
+
ran = w.Coframe.vars[ranKey];
|
|
1541
|
+
}
|
|
1542
|
+
finally {
|
|
1543
|
+
(_c = script.parentNode) === null || _c === void 0 ? void 0 : _c.removeChild(script);
|
|
1544
|
+
delete w.Coframe.vars[key];
|
|
1545
|
+
delete w.Coframe.vars[`${key}_err`];
|
|
1546
|
+
delete w.Coframe.vars[ranKey];
|
|
1547
|
+
if (opts.elementRef)
|
|
1548
|
+
delete w.Coframe.vars[elKey];
|
|
1549
|
+
for (const refName of refKeys) {
|
|
1550
|
+
delete w.Coframe.vars[`${key}_${refName}`];
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1517
1553
|
if (err !== undefined)
|
|
1518
|
-
throw new Error(err);
|
|
1554
|
+
throw new Error(String(err));
|
|
1519
1555
|
if (!ran) {
|
|
1520
1556
|
throw new SyntaxError('Failed to parse JavaScript (likely a syntax error in variant code)');
|
|
1521
1557
|
}
|
|
1522
1558
|
return value;
|
|
1523
1559
|
};
|
|
1524
|
-
const applyJavascriptModification = (element, args, logger, nonce, logMetadata = {}) => {
|
|
1560
|
+
const applyJavascriptModification = (element, args, logger, nonce, logMetadata = {}, refs) => {
|
|
1525
1561
|
var _a, _b;
|
|
1526
1562
|
const javascript = args.js;
|
|
1527
1563
|
try {
|
|
@@ -1537,6 +1573,7 @@ const applyJavascriptModification = (element, args, logger, nonce, logMetadata =
|
|
|
1537
1573
|
executeJs(javascript, {}, {
|
|
1538
1574
|
nonce,
|
|
1539
1575
|
elementRef: isPageElement(element) ? undefined : element,
|
|
1576
|
+
refs,
|
|
1540
1577
|
});
|
|
1541
1578
|
return true;
|
|
1542
1579
|
}
|
|
@@ -1544,9 +1581,7 @@ const applyJavascriptModification = (element, args, logger, nonce, logMetadata =
|
|
|
1544
1581
|
if (logger === null || logger === void 0 ? void 0 : logger.error) {
|
|
1545
1582
|
logger.error('Error executing JavaScript:', 'error_execute_js', {
|
|
1546
1583
|
...logMetadata,
|
|
1547
|
-
error: error instanceof Error
|
|
1548
|
-
? error.stack || error.message
|
|
1549
|
-
: String(error),
|
|
1584
|
+
error: error instanceof Error ? error.stack || error.message : String(error),
|
|
1550
1585
|
javascript,
|
|
1551
1586
|
element: (_b = (_a = element.outerHTML) === null || _a === void 0 ? void 0 : _a.slice(0, 200)) !== null && _b !== void 0 ? _b : element.tagName,
|
|
1552
1587
|
});
|
|
@@ -1836,7 +1871,7 @@ function findLastCmd(predicate, queueName = QueueName.CFQ) {
|
|
|
1836
1871
|
}
|
|
1837
1872
|
return undefined;
|
|
1838
1873
|
}
|
|
1839
|
-
function waitForCmd(predicate, coframe, timeoutMs = 3600000, queueName = QueueName.CFQ) {
|
|
1874
|
+
function waitForCmd(predicate, coframe, timeoutMs = 3600000, queueName = QueueName.CFQ, reportTimeout = true) {
|
|
1840
1875
|
return new Promise((resolve) => {
|
|
1841
1876
|
const existing = findLastCmd(predicate, queueName);
|
|
1842
1877
|
if (existing) {
|
|
@@ -1851,7 +1886,7 @@ function waitForCmd(predicate, coframe, timeoutMs = 3600000, queueName = QueueNa
|
|
|
1851
1886
|
const timer = setTimeout(() => {
|
|
1852
1887
|
stop();
|
|
1853
1888
|
const error = new Error(`Timeout waiting for command after ${timeoutMs}ms for:\n${predicate.toString()}`);
|
|
1854
|
-
if ((coframe === null || coframe === void 0 ? void 0 : coframe.projectId) && (coframe === null || coframe === void 0 ? void 0 : coframe.sessionId)) {
|
|
1889
|
+
if ((coframe === null || coframe === void 0 ? void 0 : coframe.projectId) && (coframe === null || coframe === void 0 ? void 0 : coframe.sessionId) && reportTimeout) {
|
|
1855
1890
|
reportException({
|
|
1856
1891
|
err: error,
|
|
1857
1892
|
orgId: coframe.orgId || '',
|
|
@@ -1876,6 +1911,42 @@ function subscribeToCmd(predicate, callback, queueName = QueueName.CFQ) {
|
|
|
1876
1911
|
return __addListener(predicate, callback, queueName);
|
|
1877
1912
|
}
|
|
1878
1913
|
|
|
1914
|
+
function describeError(error) {
|
|
1915
|
+
if (error && (typeof error === 'object' || typeof error === 'function')) {
|
|
1916
|
+
const stack = readString(() => error.stack);
|
|
1917
|
+
const message = readString(() => error.message);
|
|
1918
|
+
const header = readString(() => String(error));
|
|
1919
|
+
const detail = withHeaderLine(stack, header, message);
|
|
1920
|
+
if (detail)
|
|
1921
|
+
return detail;
|
|
1922
|
+
}
|
|
1923
|
+
try {
|
|
1924
|
+
return String(error);
|
|
1925
|
+
}
|
|
1926
|
+
catch (_a) {
|
|
1927
|
+
return 'Unprintable error';
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
function readString(read) {
|
|
1931
|
+
try {
|
|
1932
|
+
const value = read();
|
|
1933
|
+
return typeof value === 'string' ? value : '';
|
|
1934
|
+
}
|
|
1935
|
+
catch (_a) {
|
|
1936
|
+
return '';
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
function withHeaderLine(stack, header, message) {
|
|
1940
|
+
if (!stack)
|
|
1941
|
+
return header || message;
|
|
1942
|
+
if (!header) {
|
|
1943
|
+
return message && stack.indexOf(message) === -1
|
|
1944
|
+
? `${message}\n${stack}`
|
|
1945
|
+
: stack;
|
|
1946
|
+
}
|
|
1947
|
+
return stack.indexOf(header) === 0 ? stack : `${header}\n${stack}`;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1879
1950
|
function querySelector(config) {
|
|
1880
1951
|
const result = findElementsRecursive(config);
|
|
1881
1952
|
if (result !== null && result.length === 0) {
|
|
@@ -1980,7 +2051,11 @@ function variantRendered() {
|
|
|
1980
2051
|
emit: 'variantRendered',
|
|
1981
2052
|
});
|
|
1982
2053
|
}
|
|
1983
|
-
|
|
2054
|
+
function hasJavascriptModification(variantElements) {
|
|
2055
|
+
const modifications = variantElements.variant.modifications || {};
|
|
2056
|
+
return Object.keys(modifications).some((elementId) => (modifications[elementId] || []).some((modification) => modification.type === 'execute_js'));
|
|
2057
|
+
}
|
|
2058
|
+
const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecycleContext) => {
|
|
1984
2059
|
var _a;
|
|
1985
2060
|
const { variant, elements } = variantElements;
|
|
1986
2061
|
const variantMetadata = {
|
|
@@ -1988,12 +2063,18 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce) => {
|
|
|
1988
2063
|
experiment_id: variant.experiment_id,
|
|
1989
2064
|
};
|
|
1990
2065
|
setApplyVariantChangesStarted(true);
|
|
2066
|
+
const session = lifecycleContext && hasJavascriptModification(variantElements)
|
|
2067
|
+
? lifecycleContext.runtime.createVariantSession({
|
|
2068
|
+
batchId: lifecycleContext.batchId,
|
|
2069
|
+
variantId: variant.id,
|
|
2070
|
+
})
|
|
2071
|
+
: null;
|
|
1991
2072
|
if (!variant.modifications ||
|
|
1992
2073
|
Object.keys(variant.modifications).length === 0) {
|
|
1993
2074
|
variantRendered();
|
|
1994
2075
|
return true;
|
|
1995
2076
|
}
|
|
1996
|
-
for (const
|
|
2077
|
+
for (const elementId of Object.keys(variant.modifications)) {
|
|
1997
2078
|
const elementSelectors = elements[elementId];
|
|
1998
2079
|
let selectedElement = null;
|
|
1999
2080
|
for (const selector of elementSelectors || []) {
|
|
@@ -2008,23 +2089,36 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce) => {
|
|
|
2008
2089
|
variant_id: variant.id,
|
|
2009
2090
|
experiment_id: variant.experiment_id,
|
|
2010
2091
|
});
|
|
2092
|
+
if (session &&
|
|
2093
|
+
(variant.modifications[elementId] || []).some((modification) => modification.type === 'execute_js')) {
|
|
2094
|
+
session.facade.error(new Error(`Element not found for JavaScript modification: ${elementId}`));
|
|
2095
|
+
}
|
|
2011
2096
|
continue;
|
|
2012
2097
|
}
|
|
2013
|
-
const edited = editElement(selectedElement, variant.modifications, elementId, logger, nonce, variantMetadata);
|
|
2014
|
-
if (edited
|
|
2015
|
-
selectedElement.setAttribute('coframe-exp-id', variant.id);
|
|
2016
|
-
}
|
|
2017
|
-
else {
|
|
2098
|
+
const edited = editElement(selectedElement, variant.modifications, elementId, logger, nonce, variantMetadata, session === null || session === void 0 ? void 0 : session.facade);
|
|
2099
|
+
if (!edited) {
|
|
2018
2100
|
logger.error('element not edited', 'element_not_edited', {
|
|
2019
2101
|
...variantMetadata,
|
|
2020
|
-
selectedElement,
|
|
2102
|
+
selectedElement: describeElement(selectedElement),
|
|
2021
2103
|
elementSelectors,
|
|
2022
2104
|
});
|
|
2023
2105
|
}
|
|
2106
|
+
else if (selectedElement instanceof HTMLElement) {
|
|
2107
|
+
selectedElement.setAttribute('coframe-exp-id', variant.id);
|
|
2108
|
+
}
|
|
2024
2109
|
}
|
|
2025
2110
|
return true;
|
|
2026
2111
|
};
|
|
2027
|
-
|
|
2112
|
+
function describeElement(element) {
|
|
2113
|
+
if (!element || typeof element.getAttribute !== 'function') {
|
|
2114
|
+
return String(element);
|
|
2115
|
+
}
|
|
2116
|
+
const target = element;
|
|
2117
|
+
const id = target.getAttribute('id');
|
|
2118
|
+
const className = target.getAttribute('class');
|
|
2119
|
+
return `<${String(target.tagName).toLowerCase()}${id ? ` id=${JSON.stringify(id)}` : ''}${className ? ` class=${JSON.stringify(className)}` : ''}>`.slice(0, 200);
|
|
2120
|
+
}
|
|
2121
|
+
const editElement = (element, modifications, elementId, logger, nonce, logMetadata = {}, variantSession) => {
|
|
2028
2122
|
if (!modifications[elementId]) {
|
|
2029
2123
|
logger.warn('no modifications for element', 'no_modifications_for_element', {
|
|
2030
2124
|
...logMetadata,
|
|
@@ -2036,7 +2130,11 @@ const editElement = (element, modifications, elementId, logger, nonce, logMetada
|
|
|
2036
2130
|
for (const content of modifications[elementId]) {
|
|
2037
2131
|
switch (content.type) {
|
|
2038
2132
|
case 'execute_js':
|
|
2039
|
-
applyJavascriptModification(element, content, logger, nonce, logMetadata)
|
|
2133
|
+
if (!applyJavascriptModification(element, content, logger, nonce, logMetadata, variantSession ? { variantSession } : undefined) &&
|
|
2134
|
+
variantSession) {
|
|
2135
|
+
variantSession.error(new Error('JavaScript variant modification did not run'));
|
|
2136
|
+
return false;
|
|
2137
|
+
}
|
|
2040
2138
|
break;
|
|
2041
2139
|
case 'update':
|
|
2042
2140
|
applyUpdateModification(element, content, logger, logMetadata);
|
|
@@ -2093,13 +2191,16 @@ function revealPage() {
|
|
|
2093
2191
|
document.documentElement.style.setProperty('--cf-show', '1');
|
|
2094
2192
|
document.documentElement.style.setProperty('--cf-visibility', 'visible');
|
|
2095
2193
|
}
|
|
2096
|
-
async function replaceVariants(variants, orgId, logger, nonce) {
|
|
2194
|
+
async function replaceVariants(variants, orgId, logger, nonce, runtime) {
|
|
2097
2195
|
const failedReplacements = [];
|
|
2098
2196
|
let currentVariant;
|
|
2197
|
+
const batch = runtime === null || runtime === void 0 ? void 0 : runtime.beginBatch({
|
|
2198
|
+
expectedSessionCount: variants.filter(hasJavascriptModification).length,
|
|
2199
|
+
});
|
|
2099
2200
|
try {
|
|
2100
2201
|
for (const v of variants) {
|
|
2101
2202
|
currentVariant = v.variant;
|
|
2102
|
-
if (!applyVariantChanges(v, getCurrentPageUrl(), logger, nonce)) {
|
|
2203
|
+
if (!applyVariantChanges(v, getCurrentPageUrl(), logger, nonce, batch && runtime ? { runtime, batchId: batch.batchId } : undefined)) {
|
|
2103
2204
|
failedReplacements.push({
|
|
2104
2205
|
message: 'Original element not found',
|
|
2105
2206
|
metadata: {
|
|
@@ -2113,10 +2214,11 @@ async function replaceVariants(variants, orgId, logger, nonce) {
|
|
|
2113
2214
|
}
|
|
2114
2215
|
}
|
|
2115
2216
|
catch (error) {
|
|
2116
|
-
const errorMessage =
|
|
2217
|
+
const errorMessage = describeError(error);
|
|
2218
|
+
const description = errorMessage.split('\n')[0];
|
|
2117
2219
|
failedReplacements.push({
|
|
2118
2220
|
metadata: {
|
|
2119
|
-
description
|
|
2221
|
+
description,
|
|
2120
2222
|
org_id: orgId,
|
|
2121
2223
|
variant_id: currentVariant === null || currentVariant === void 0 ? void 0 : currentVariant.id,
|
|
2122
2224
|
experiment_id: currentVariant === null || currentVariant === void 0 ? void 0 : currentVariant.experiment_id,
|
|
@@ -2124,6 +2226,10 @@ async function replaceVariants(variants, orgId, logger, nonce) {
|
|
|
2124
2226
|
message: errorMessage,
|
|
2125
2227
|
});
|
|
2126
2228
|
}
|
|
2229
|
+
finally {
|
|
2230
|
+
if (batch && runtime)
|
|
2231
|
+
runtime.closeBatch(batch.batchId);
|
|
2232
|
+
}
|
|
2127
2233
|
for (const event of failedReplacements) {
|
|
2128
2234
|
logger.error(event.message, 'failed_replacement', event.metadata);
|
|
2129
2235
|
}
|
|
@@ -2499,7 +2605,8 @@ function urlConditionMatches(conditionUrl, currentUrl, baseUrlMatch, logger) {
|
|
|
2499
2605
|
}
|
|
2500
2606
|
}
|
|
2501
2607
|
function urlsMatch(conditionUrl, currentUrl, baseUrlMatch) {
|
|
2502
|
-
if (currentUrl.
|
|
2608
|
+
if (currentUrl.indexOf('localhost') !== -1 ||
|
|
2609
|
+
currentUrl.indexOf('0.0.0.0') !== -1) {
|
|
2503
2610
|
const conditionUrlObj = new URL(conditionUrl);
|
|
2504
2611
|
const currentUrlObj = new URL(currentUrl);
|
|
2505
2612
|
const normalizePath = (path) => path.endsWith('/') ? path.slice(0, -1) : path;
|
|
@@ -3318,8 +3425,10 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
|
|
|
3318
3425
|
}
|
|
3319
3426
|
|
|
3320
3427
|
let lastUrl = null;
|
|
3321
|
-
function observeUrlChanges(baseUrlMatch, onUrlChange) {
|
|
3428
|
+
function observeUrlChanges(baseUrlMatch, onUrlChange, isActive = () => true) {
|
|
3322
3429
|
const tryTrack = () => {
|
|
3430
|
+
if (!isActive())
|
|
3431
|
+
return;
|
|
3323
3432
|
const currentUrl = getCurrentPageUrl();
|
|
3324
3433
|
const previousUrl = lastUrl;
|
|
3325
3434
|
if (previousUrl && !urlsMatch(currentUrl, previousUrl, baseUrlMatch)) {
|
|
@@ -3343,6 +3452,200 @@ function observeUrlChanges(baseUrlMatch, onUrlChange) {
|
|
|
3343
3452
|
patchHistoryMethod('replaceState');
|
|
3344
3453
|
}
|
|
3345
3454
|
|
|
3455
|
+
function isVariantRuntimeLifecycleEnabled(config) {
|
|
3456
|
+
return config.deferHydrationWaitToVariant === true;
|
|
3457
|
+
}
|
|
3458
|
+
function shouldSdkWaitForHydration(config) {
|
|
3459
|
+
return (Boolean(config.waitForHydration) &&
|
|
3460
|
+
!isVariantRuntimeLifecycleEnabled(config));
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
function createRevealCoordinator(revealPage, reportError = () => { }) {
|
|
3464
|
+
const notify = (listener, reason) => {
|
|
3465
|
+
try {
|
|
3466
|
+
listener(reason);
|
|
3467
|
+
}
|
|
3468
|
+
catch (error) {
|
|
3469
|
+
try {
|
|
3470
|
+
reportError(error);
|
|
3471
|
+
}
|
|
3472
|
+
catch (_a) {
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3475
|
+
};
|
|
3476
|
+
let pageRevealed = false;
|
|
3477
|
+
let activeBlockerId = null;
|
|
3478
|
+
let revealedReason = null;
|
|
3479
|
+
const pendingReasons = [];
|
|
3480
|
+
const revealedListeners = new Set();
|
|
3481
|
+
const reveal = (reason) => {
|
|
3482
|
+
if (pageRevealed)
|
|
3483
|
+
return;
|
|
3484
|
+
pageRevealed = true;
|
|
3485
|
+
activeBlockerId = null;
|
|
3486
|
+
pendingReasons.length = 0;
|
|
3487
|
+
revealedReason = reason;
|
|
3488
|
+
let revealError = null;
|
|
3489
|
+
let revealFailed = false;
|
|
3490
|
+
try {
|
|
3491
|
+
revealPage();
|
|
3492
|
+
}
|
|
3493
|
+
catch (error) {
|
|
3494
|
+
revealFailed = true;
|
|
3495
|
+
revealError = error;
|
|
3496
|
+
}
|
|
3497
|
+
for (const listener of Array.from(revealedListeners)) {
|
|
3498
|
+
notify(listener, reason);
|
|
3499
|
+
}
|
|
3500
|
+
if (revealFailed)
|
|
3501
|
+
throw revealError;
|
|
3502
|
+
};
|
|
3503
|
+
return {
|
|
3504
|
+
requestReveal(reason) {
|
|
3505
|
+
if (pageRevealed)
|
|
3506
|
+
return;
|
|
3507
|
+
if (activeBlockerId) {
|
|
3508
|
+
if (!pendingReasons.includes(reason))
|
|
3509
|
+
pendingReasons.push(reason);
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
reveal(reason);
|
|
3513
|
+
},
|
|
3514
|
+
forceReveal(reason) {
|
|
3515
|
+
reveal(reason);
|
|
3516
|
+
},
|
|
3517
|
+
block(batchId) {
|
|
3518
|
+
if (pageRevealed)
|
|
3519
|
+
return false;
|
|
3520
|
+
activeBlockerId = batchId;
|
|
3521
|
+
return true;
|
|
3522
|
+
},
|
|
3523
|
+
release(batchId, reason) {
|
|
3524
|
+
if (pageRevealed || activeBlockerId !== batchId)
|
|
3525
|
+
return;
|
|
3526
|
+
activeBlockerId = null;
|
|
3527
|
+
reveal(reason);
|
|
3528
|
+
},
|
|
3529
|
+
unblock(batchId) {
|
|
3530
|
+
if (activeBlockerId === batchId)
|
|
3531
|
+
activeBlockerId = null;
|
|
3532
|
+
},
|
|
3533
|
+
flushPending() {
|
|
3534
|
+
if (pageRevealed || activeBlockerId || pendingReasons.length === 0)
|
|
3535
|
+
return;
|
|
3536
|
+
reveal(pendingReasons[0]);
|
|
3537
|
+
},
|
|
3538
|
+
onRevealed(handler) {
|
|
3539
|
+
revealedListeners.add(handler);
|
|
3540
|
+
return () => revealedListeners.delete(handler);
|
|
3541
|
+
},
|
|
3542
|
+
getSnapshot() {
|
|
3543
|
+
return {
|
|
3544
|
+
pageRevealed,
|
|
3545
|
+
activeBlockerId,
|
|
3546
|
+
pendingReasons: [...pendingReasons],
|
|
3547
|
+
revealedReason,
|
|
3548
|
+
};
|
|
3549
|
+
},
|
|
3550
|
+
};
|
|
3551
|
+
}
|
|
3552
|
+
const logRevealListenerError = (logger) => (error) => logger().error('Page revealed listener failed', 'page_reveal_error', {
|
|
3553
|
+
error: describeError(error),
|
|
3554
|
+
});
|
|
3555
|
+
|
|
3556
|
+
const createNavigationController = () => {
|
|
3557
|
+
if (typeof AbortController !== 'undefined') {
|
|
3558
|
+
try {
|
|
3559
|
+
const controller = new AbortController();
|
|
3560
|
+
return {
|
|
3561
|
+
signal: controller.signal,
|
|
3562
|
+
abort: () => controller.abort(),
|
|
3563
|
+
native: true,
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
catch (_a) {
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
const listeners = [];
|
|
3570
|
+
const signal = {
|
|
3571
|
+
aborted: false,
|
|
3572
|
+
reason: undefined,
|
|
3573
|
+
onabort: null,
|
|
3574
|
+
addEventListener: (type, listener) => {
|
|
3575
|
+
if (type === 'abort')
|
|
3576
|
+
listeners.push(listener);
|
|
3577
|
+
},
|
|
3578
|
+
removeEventListener: (type, listener) => {
|
|
3579
|
+
if (type !== 'abort')
|
|
3580
|
+
return;
|
|
3581
|
+
const index = listeners.indexOf(listener);
|
|
3582
|
+
if (index !== -1)
|
|
3583
|
+
listeners.splice(index, 1);
|
|
3584
|
+
},
|
|
3585
|
+
};
|
|
3586
|
+
return {
|
|
3587
|
+
signal: signal,
|
|
3588
|
+
abort: () => {
|
|
3589
|
+
if (signal.aborted)
|
|
3590
|
+
return;
|
|
3591
|
+
signal.aborted = true;
|
|
3592
|
+
signal.reason = new Error('The navigation was aborted');
|
|
3593
|
+
const event = { type: 'abort', target: signal };
|
|
3594
|
+
const call = (listener) => {
|
|
3595
|
+
try {
|
|
3596
|
+
listener.call(signal, event);
|
|
3597
|
+
}
|
|
3598
|
+
catch (error) {
|
|
3599
|
+
setTimeout(() => {
|
|
3600
|
+
throw error;
|
|
3601
|
+
}, 0);
|
|
3602
|
+
}
|
|
3603
|
+
};
|
|
3604
|
+
if (typeof signal.onabort === 'function')
|
|
3605
|
+
call(signal.onabort);
|
|
3606
|
+
for (const listener of listeners.slice())
|
|
3607
|
+
call(listener);
|
|
3608
|
+
},
|
|
3609
|
+
native: false,
|
|
3610
|
+
};
|
|
3611
|
+
};
|
|
3612
|
+
const waitForFrame = (capMs, hiddenCapMs) => new Promise((resolve) => {
|
|
3613
|
+
let timer;
|
|
3614
|
+
let done = false;
|
|
3615
|
+
const finish = () => {
|
|
3616
|
+
if (done)
|
|
3617
|
+
return;
|
|
3618
|
+
done = true;
|
|
3619
|
+
if (timer !== undefined)
|
|
3620
|
+
clearTimeout(timer);
|
|
3621
|
+
cancelAnimationFrame(frame);
|
|
3622
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
3623
|
+
resolve();
|
|
3624
|
+
};
|
|
3625
|
+
const startTimer = (ms) => {
|
|
3626
|
+
if (timer === undefined)
|
|
3627
|
+
timer = setTimeout(finish, ms);
|
|
3628
|
+
};
|
|
3629
|
+
const onVisibilityChange = () => {
|
|
3630
|
+
if (document.hidden)
|
|
3631
|
+
startTimer(hiddenCapMs);
|
|
3632
|
+
else if (timer !== undefined) {
|
|
3633
|
+
clearTimeout(timer);
|
|
3634
|
+
timer = undefined;
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
const frame = requestAnimationFrame(finish);
|
|
3638
|
+
if (capMs !== undefined)
|
|
3639
|
+
startTimer(capMs);
|
|
3640
|
+
else {
|
|
3641
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
3642
|
+
onVisibilityChange();
|
|
3643
|
+
}
|
|
3644
|
+
});
|
|
3645
|
+
const HANDOVER_FRAME_CAP_MS = 50;
|
|
3646
|
+
const NAVIGATION_FRAME_CAP_MS = 100;
|
|
3647
|
+
const shouldSettleRoute = (aborted, threw, result) => !aborted && (threw || result !== 'skipped');
|
|
3648
|
+
|
|
3346
3649
|
function mergeClientHeaders(client, server) {
|
|
3347
3650
|
if (server)
|
|
3348
3651
|
return { ...client, ...server };
|
|
@@ -3354,7 +3657,7 @@ function getQueuedPersonalizationHeaders() {
|
|
|
3354
3657
|
const nested = (_c = (_b = findLastCmd((c) => !!c.projectData)) === null || _b === void 0 ? void 0 : _b.projectData) === null || _c === void 0 ? void 0 : _c.personalizationHeaders;
|
|
3355
3658
|
return mergeClientHeaders(topLevel, nested);
|
|
3356
3659
|
}
|
|
3357
|
-
async function resolveProject(Coframe, previewVariantId, isSessionVariant, config) {
|
|
3660
|
+
async function resolveProject(Coframe, previewVariantId, isSessionVariant, config, isCurrent = () => true) {
|
|
3358
3661
|
var _a, _b;
|
|
3359
3662
|
const logger = Coframe.logger;
|
|
3360
3663
|
const projectData = (_a = findLastCmd((c) => !!c.projectData)) === null || _a === void 0 ? void 0 : _a.projectData;
|
|
@@ -3370,7 +3673,8 @@ async function resolveProject(Coframe, previewVariantId, isSessionVariant, confi
|
|
|
3370
3673
|
logger.error('No Coframe Project Config', 'no_project_config', {
|
|
3371
3674
|
previewVariantId,
|
|
3372
3675
|
});
|
|
3373
|
-
|
|
3676
|
+
if (isCurrent())
|
|
3677
|
+
variantRendered();
|
|
3374
3678
|
return null;
|
|
3375
3679
|
}
|
|
3376
3680
|
return {
|
|
@@ -3978,26 +4282,32 @@ function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
|
|
|
3978
4282
|
}
|
|
3979
4283
|
return { previewVariantId: null, isSessionVariant: false };
|
|
3980
4284
|
}
|
|
4285
|
+
const isPageHydratedCmd = (cmd) => cmd.pageHydrated === true || cmd.emit === 'pageHydrated';
|
|
3981
4286
|
async function awaitHydration(config, Coframe, project) {
|
|
3982
4287
|
var _a, _b;
|
|
3983
|
-
if (!config
|
|
4288
|
+
if (!shouldSdkWaitForHydration(config))
|
|
3984
4289
|
return true;
|
|
3985
4290
|
if (!((_a = project.frames) === null || _a === void 0 ? void 0 : _a.length) && !((_b = project.tags) === null || _b === void 0 ? void 0 : _b.length))
|
|
3986
4291
|
return true;
|
|
3987
|
-
const cmd = await waitForCmd(
|
|
4292
|
+
const cmd = await waitForCmd(isPageHydratedCmd, Coframe);
|
|
3988
4293
|
if (!cmd) {
|
|
3989
4294
|
Coframe.logger.warn('Failed to wait for page hydrated command', 'wait_for_cmd_timeout');
|
|
3990
4295
|
return false;
|
|
3991
4296
|
}
|
|
3992
4297
|
return true;
|
|
3993
4298
|
}
|
|
3994
|
-
async function ensureBody(Coframe, cdnProject, timeoutMs, nonce) {
|
|
4299
|
+
async function ensureBody(Coframe, cdnProject, timeoutMs, nonce, revealCoordinator, isCurrent) {
|
|
3995
4300
|
const bodyPresent = await waitForBody(timeoutMs).catch(() => false);
|
|
3996
4301
|
if (!bodyPresent) {
|
|
3997
4302
|
Coframe.logger.error('Timeout waiting for document.body, exiting gracefully', 'body_timeout');
|
|
3998
|
-
|
|
4303
|
+
if (isCurrent()) {
|
|
4304
|
+
variantRendered();
|
|
4305
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4306
|
+
}
|
|
3999
4307
|
return false;
|
|
4000
4308
|
}
|
|
4309
|
+
if (!isCurrent())
|
|
4310
|
+
return false;
|
|
4001
4311
|
rebindMetrics(Coframe, Coframe.logger, cdnProject === null || cdnProject === void 0 ? void 0 : cdnProject.metrics, nonce);
|
|
4002
4312
|
return true;
|
|
4003
4313
|
}
|
|
@@ -4010,7 +4320,13 @@ function setupFlushInterval(Coframe) {
|
|
|
4010
4320
|
}
|
|
4011
4321
|
flushInterval = setInterval(() => flushQueuedEvents(Coframe.projectId), FLUSH_EVENTS_TIME);
|
|
4012
4322
|
}
|
|
4013
|
-
|
|
4323
|
+
const hasNoVariants = (Coframe) => !Coframe.variantsAndElements || Coframe.variantsAndElements.length === 0;
|
|
4324
|
+
const logNavigationError = (Coframe, message, error) => {
|
|
4325
|
+
Coframe.logger.error(message, 'spa_navigation_error', {
|
|
4326
|
+
error: describeError(error),
|
|
4327
|
+
});
|
|
4328
|
+
};
|
|
4329
|
+
function setupSPANavigation(Coframe, project, config, previewVariantId, isSessionVariant, runtime, applier, firstViewDeadline, onFirstViewDone) {
|
|
4014
4330
|
var _a;
|
|
4015
4331
|
if (!((_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.mutation_observer_active))
|
|
4016
4332
|
return;
|
|
@@ -4035,16 +4351,20 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4035
4351
|
const observer = new MutationObserver(mutationCallback);
|
|
4036
4352
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4037
4353
|
let navigationAbortController = null;
|
|
4038
|
-
|
|
4354
|
+
const onUrlChange = async (newPageUrl, _oldPageUrl, options = {}) => {
|
|
4039
4355
|
var _a;
|
|
4040
|
-
|
|
4356
|
+
const firstView = !applier.hasCommitted();
|
|
4357
|
+
if (!firstView)
|
|
4358
|
+
sendPageLeave(Coframe);
|
|
4041
4359
|
if (navigationAbortController)
|
|
4042
4360
|
navigationAbortController.abort();
|
|
4043
|
-
|
|
4044
|
-
const
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4361
|
+
runtime === null || runtime === void 0 ? void 0 : runtime.changeRoute(newPageUrl);
|
|
4362
|
+
const controller = createNavigationController();
|
|
4363
|
+
navigationAbortController = controller;
|
|
4364
|
+
const { signal } = controller;
|
|
4365
|
+
await waitForFrame(options.routeAlreadyRendered ? HANDOVER_FRAME_CAP_MS : undefined, NAVIGATION_FRAME_CAP_MS);
|
|
4366
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4367
|
+
return 'skipped';
|
|
4048
4368
|
infiniteLoopProtection = 0;
|
|
4049
4369
|
let pageProject = project;
|
|
4050
4370
|
let pagePersonalizationHeaders = Coframe.personalizationHeaders;
|
|
@@ -4060,9 +4380,9 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4060
4380
|
else {
|
|
4061
4381
|
try {
|
|
4062
4382
|
const preview = resolvePreview(newPageUrl, previewVariantId, isSessionVariant);
|
|
4063
|
-
const result = await fetchProject(config.projectUrl, Coframe.projectId, newPageUrl, signal, config.userContext, preview.previewVariantId || undefined, undefined, (_a = config.referrer) !== null && _a !== void 0 ? _a : getBrowserReferrer());
|
|
4064
|
-
if (signal.aborted)
|
|
4065
|
-
return;
|
|
4383
|
+
const result = await fetchProject(config.projectUrl, Coframe.projectId, newPageUrl, controller.native ? signal : undefined, config.userContext, preview.previewVariantId || undefined, undefined, (_a = config.referrer) !== null && _a !== void 0 ? _a : getBrowserReferrer());
|
|
4384
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4385
|
+
return 'skipped';
|
|
4066
4386
|
if (result) {
|
|
4067
4387
|
pageProject = result.project;
|
|
4068
4388
|
pagePersonalizationHeaders = {
|
|
@@ -4075,10 +4395,37 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4075
4395
|
catch (_b) {
|
|
4076
4396
|
}
|
|
4077
4397
|
}
|
|
4078
|
-
if (signal.aborted)
|
|
4079
|
-
return;
|
|
4080
|
-
|
|
4081
|
-
|
|
4398
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4399
|
+
return 'skipped';
|
|
4400
|
+
let result = 'skipped';
|
|
4401
|
+
let threw = false;
|
|
4402
|
+
try {
|
|
4403
|
+
result = await applier.apply(newPageUrl, pageProject, pagePersonalizationHeaders, signal, firstView ? firstViewDeadline : undefined);
|
|
4404
|
+
}
|
|
4405
|
+
catch (error) {
|
|
4406
|
+
threw = true;
|
|
4407
|
+
if (firstView && !signal.aborted && !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())) {
|
|
4408
|
+
onFirstViewDone === null || onFirstViewDone === void 0 ? void 0 : onFirstViewDone(pageProject);
|
|
4409
|
+
}
|
|
4410
|
+
throw error;
|
|
4411
|
+
}
|
|
4412
|
+
finally {
|
|
4413
|
+
if (shouldSettleRoute(signal.aborted, threw, result)) {
|
|
4414
|
+
runtime === null || runtime === void 0 ? void 0 : runtime.settleRoute();
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
if (firstView && result === 'applied' && !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())) {
|
|
4418
|
+
if (hasNoVariants(Coframe))
|
|
4419
|
+
variantRendered();
|
|
4420
|
+
onFirstViewDone === null || onFirstViewDone === void 0 ? void 0 : onFirstViewDone(pageProject);
|
|
4421
|
+
}
|
|
4422
|
+
return result;
|
|
4423
|
+
};
|
|
4424
|
+
observeUrlChanges(false, (newPageUrl, oldPageUrl) => onUrlChange(newPageUrl).catch((error) => {
|
|
4425
|
+
logNavigationError(Coframe, 'SPA navigation failed', error);
|
|
4426
|
+
return 'skipped';
|
|
4427
|
+
}), () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()));
|
|
4428
|
+
return onUrlChange;
|
|
4082
4429
|
}
|
|
4083
4430
|
function readLaneClaims() {
|
|
4084
4431
|
const cookie = getCookie(LANE_COOKIE_KEY);
|
|
@@ -4108,17 +4455,14 @@ function persistLaneClaims(claims, logger) {
|
|
|
4108
4455
|
setCookie(LANE_COOKIE_KEY, encodeURIComponent(serialized), logger);
|
|
4109
4456
|
}
|
|
4110
4457
|
}
|
|
4111
|
-
function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore) {
|
|
4458
|
+
function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore, runtime) {
|
|
4112
4459
|
let isFirstApply = true;
|
|
4113
4460
|
let applicationId = 0;
|
|
4114
|
-
|
|
4115
|
-
var _a;
|
|
4461
|
+
const apply = async (pageUrl, pageProject, personalizationHeaders, signal, deadline) => {
|
|
4462
|
+
var _a, _b;
|
|
4116
4463
|
const currentApplication = ++applicationId;
|
|
4117
4464
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
4118
|
-
return;
|
|
4119
|
-
const sessionId = isFirstApply ? Coframe.sessionId : generateUUID();
|
|
4120
|
-
const sessionContext = buildSessionContext(isFirstApply);
|
|
4121
|
-
isFirstApply = false;
|
|
4465
|
+
return 'skipped';
|
|
4122
4466
|
const preview = resolvePreview(pageUrl, previewVariantId, isSessionVariant);
|
|
4123
4467
|
const chooseCmabVariants = createCmabVariantBatchChooser({
|
|
4124
4468
|
projectId: Coframe.projectId,
|
|
@@ -4132,8 +4476,22 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4132
4476
|
const { variantsAndElements, experimentsInfo, hasActivatedFrames, laneClaims, } = await getVariantsAndElements(personalizationHeaders, pageProject, preview.previewVariantId, Coframe.userToken, pageUrl, getBrowserLanguages(), getBrowserPlatform(), Coframe.logger, config.nonce, laneStore.claims, preview.isSessionVariant, chooseCmabVariants);
|
|
4133
4477
|
if ((signal === null || signal === void 0 ? void 0 : signal.aborted) ||
|
|
4134
4478
|
currentApplication !== applicationId ||
|
|
4479
|
+
(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()) ||
|
|
4135
4480
|
(signal && !urlsMatch(pageUrl, getCurrentPageUrl())))
|
|
4136
|
-
return;
|
|
4481
|
+
return 'skipped';
|
|
4482
|
+
if (!signal &&
|
|
4483
|
+
runtime &&
|
|
4484
|
+
((_b = pageProject === null || pageProject === void 0 ? void 0 : pageProject.settings) === null || _b === void 0 ? void 0 : _b.mutation_observer_active) &&
|
|
4485
|
+
!urlsMatch(pageUrl, getCurrentPageUrl(), true))
|
|
4486
|
+
return 'stale-route';
|
|
4487
|
+
if (!signal && runtime) {
|
|
4488
|
+
const urlAtCommit = getCurrentPageUrl();
|
|
4489
|
+
if (urlsMatch(pageUrl, urlAtCommit, true))
|
|
4490
|
+
runtime.updateUrl(urlAtCommit);
|
|
4491
|
+
}
|
|
4492
|
+
const sessionId = isFirstApply ? Coframe.sessionId : generateUUID();
|
|
4493
|
+
const sessionContext = buildSessionContext(isFirstApply);
|
|
4494
|
+
isFirstApply = false;
|
|
4137
4495
|
Coframe.sessionId = sessionId;
|
|
4138
4496
|
Coframe.personalizationHeaders = personalizationHeaders;
|
|
4139
4497
|
setIsPreviewVariant(!!preview.previewVariantId);
|
|
@@ -4151,7 +4509,9 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4151
4509
|
variantRendered();
|
|
4152
4510
|
}
|
|
4153
4511
|
else {
|
|
4154
|
-
replaceVariants(variantsAndElements, Coframe.orgId, Coframe.logger, config.nonce);
|
|
4512
|
+
await replaceVariants(variantsAndElements, Coframe.orgId, Coframe.logger, config.nonce, runtime);
|
|
4513
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4514
|
+
return 'skipped';
|
|
4155
4515
|
}
|
|
4156
4516
|
const cmabDecisions = chooseCmabVariants.getDecisions(experimentsInfo);
|
|
4157
4517
|
startSession(Coframe, isCookiesEnabled, executionStartTime, personalizationHeaders, {
|
|
@@ -4160,29 +4520,77 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4160
4520
|
}, pageUrl);
|
|
4161
4521
|
analytics.resetBroadcasted();
|
|
4162
4522
|
analytics.sendAnalytics();
|
|
4523
|
+
return 'applied';
|
|
4163
4524
|
};
|
|
4525
|
+
return { apply, hasCommitted: () => !isFirstApply };
|
|
4164
4526
|
}
|
|
4165
|
-
async function modifyPage(Coframe, configResult, projectResult, analytics) {
|
|
4527
|
+
async function modifyPage(Coframe, configResult, projectResult, analytics, lifecycle) {
|
|
4528
|
+
var _a;
|
|
4166
4529
|
const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
|
|
4167
4530
|
const { project: cdnProject, personalizationHeaders } = projectResult;
|
|
4531
|
+
const revealCoordinator = (_a = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.revealCoordinator) !== null && _a !== void 0 ? _a : createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
4532
|
+
const runtime = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.runtime;
|
|
4533
|
+
if (runtime && config.waitForHydration) {
|
|
4534
|
+
if (findLastCmd(isPageHydratedCmd)) {
|
|
4535
|
+
runtime.publishHydrated();
|
|
4536
|
+
}
|
|
4537
|
+
else {
|
|
4538
|
+
let stopHydration = () => { };
|
|
4539
|
+
stopHydration = subscribeToCmd(isPageHydratedCmd, () => {
|
|
4540
|
+
stopHydration();
|
|
4541
|
+
runtime.publishHydrated();
|
|
4542
|
+
});
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4168
4545
|
if (!(await awaitHydration(config, Coframe, cdnProject)))
|
|
4169
4546
|
return;
|
|
4547
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4548
|
+
return;
|
|
4170
4549
|
if (cdnProject.tags) {
|
|
4171
4550
|
executeTags(cdnProject.tags, Coframe.logger, config.nonce);
|
|
4172
4551
|
Coframe.status.tags = cdnProject.tags;
|
|
4173
4552
|
}
|
|
4174
|
-
if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce)))
|
|
4553
|
+
if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce, revealCoordinator, () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))))
|
|
4175
4554
|
return;
|
|
4176
4555
|
const laneStore = { claims: readLaneClaims() };
|
|
4177
|
-
const
|
|
4178
|
-
|
|
4179
|
-
if (
|
|
4180
|
-
|
|
4181
|
-
|
|
4556
|
+
const applier = createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore, runtime);
|
|
4557
|
+
const initialUrl = getCurrentPageUrl();
|
|
4558
|
+
if (runtime) {
|
|
4559
|
+
const runtimeUrl = runtime.getState().currentUrl;
|
|
4560
|
+
if (!urlsMatch(runtimeUrl, initialUrl, true))
|
|
4561
|
+
runtime.changeRoute(initialUrl);
|
|
4562
|
+
}
|
|
4563
|
+
const firstDeadline = (integrationStartTime || executionStartTime).getTime() + timeoutMs;
|
|
4564
|
+
const firstResult = await applier.apply(initialUrl, cdnProject, personalizationHeaders, undefined, firstDeadline);
|
|
4565
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4566
|
+
return;
|
|
4567
|
+
const routeChangedDuringInit = firstResult === 'stale-route';
|
|
4568
|
+
if (!routeChangedDuringInit && hasNoVariants(Coframe)) {
|
|
4569
|
+
revealCoordinator.requestReveal('no-variants');
|
|
4182
4570
|
variantRendered();
|
|
4183
4571
|
}
|
|
4184
|
-
|
|
4185
|
-
|
|
4572
|
+
if (!routeChangedDuringInit)
|
|
4573
|
+
revealCoordinator.flushPending();
|
|
4574
|
+
let timingReported = false;
|
|
4575
|
+
const reportTiming = (appliedProject = cdnProject) => {
|
|
4576
|
+
if (timingReported)
|
|
4577
|
+
return;
|
|
4578
|
+
timingReported = true;
|
|
4579
|
+
reportInitTiming(Coframe, appliedProject, integrationStartTime, executionStartTime);
|
|
4580
|
+
};
|
|
4581
|
+
if (!routeChangedDuringInit)
|
|
4582
|
+
reportTiming();
|
|
4583
|
+
const onUrlChange = setupSPANavigation(Coframe, cdnProject, config, previewVariantId, isSessionVariant, runtime, applier, firstDeadline, routeChangedDuringInit ? reportTiming : undefined);
|
|
4584
|
+
const urlNow = getCurrentPageUrl();
|
|
4585
|
+
if (runtime &&
|
|
4586
|
+
onUrlChange &&
|
|
4587
|
+
(routeChangedDuringInit || !urlsMatch(initialUrl, urlNow, true))) {
|
|
4588
|
+
onUrlChange(urlNow, initialUrl, {
|
|
4589
|
+
routeAlreadyRendered: routeChangedDuringInit,
|
|
4590
|
+
}).catch((error) => {
|
|
4591
|
+
logNavigationError(Coframe, 'Applying the route reached during init failed', error);
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4186
4594
|
setupFlushInterval(Coframe);
|
|
4187
4595
|
}
|
|
4188
4596
|
|
|
@@ -4420,6 +4828,12 @@ function startReplayIfEnabled(coframe, project, nonce) {
|
|
|
4420
4828
|
var _a;
|
|
4421
4829
|
const consent = readReplayConsent(settings);
|
|
4422
4830
|
if (consent.state === 'allowed') {
|
|
4831
|
+
try {
|
|
4832
|
+
queueMonitoringEvent(coframe, createMonitoringPayload(coframe, 'replay_consent_allowed', {
|
|
4833
|
+
source: consent.source,
|
|
4834
|
+
}));
|
|
4835
|
+
}
|
|
4836
|
+
catch (_b) { }
|
|
4423
4837
|
insertReplayScript(coframe, settings, consent.source, nonce);
|
|
4424
4838
|
}
|
|
4425
4839
|
else if (consent.state === 'denied') {
|
|
@@ -4441,7 +4855,7 @@ function startReplayIfEnabled(coframe, project, nonce) {
|
|
|
4441
4855
|
loadFeatures.call(window.Shopify, [{ name: 'consent-tracking-api', version: '0.1' }], () => { });
|
|
4442
4856
|
}
|
|
4443
4857
|
}
|
|
4444
|
-
catch (
|
|
4858
|
+
catch (_c) { }
|
|
4445
4859
|
}
|
|
4446
4860
|
waited += CONSENT_POLL_MS;
|
|
4447
4861
|
window.setTimeout(decide, CONSENT_POLL_MS);
|
|
@@ -4504,11 +4918,12 @@ async function resolveFeatureFlag(Coframe, timeoutMs) {
|
|
|
4504
4918
|
return undefined;
|
|
4505
4919
|
return getFeatureFlagValue(cmd);
|
|
4506
4920
|
}
|
|
4507
|
-
async function resolveConfig(Coframe,
|
|
4921
|
+
async function resolveConfig(Coframe, revealActions) {
|
|
4508
4922
|
const logger = Coframe.logger;
|
|
4509
4923
|
const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
|
|
4510
4924
|
if (!configCmd) {
|
|
4511
4925
|
logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
|
|
4926
|
+
revealActions.forceReveal('config-timeout');
|
|
4512
4927
|
return null;
|
|
4513
4928
|
}
|
|
4514
4929
|
const config = configCmd.config;
|
|
@@ -4524,7 +4939,7 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4524
4939
|
configureReplayScriptUrl(config.replayScriptUrl);
|
|
4525
4940
|
const gracefulExit = (msg) => {
|
|
4526
4941
|
console.warn(`[Coframe] ${msg}`);
|
|
4527
|
-
|
|
4942
|
+
revealActions.forceReveal('graceful-exit');
|
|
4528
4943
|
return null;
|
|
4529
4944
|
};
|
|
4530
4945
|
if (config.waitForFeatureFlag) {
|
|
@@ -4537,8 +4952,9 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4537
4952
|
}
|
|
4538
4953
|
}
|
|
4539
4954
|
const projectId = config === null || config === void 0 ? void 0 : config.projectId;
|
|
4955
|
+
const lifecycleMode = isVariantRuntimeLifecycleEnabled(config);
|
|
4540
4956
|
const warnMissingVariant = () => {
|
|
4541
|
-
if (applyVariantChangesStarted) {
|
|
4957
|
+
if (applyVariantChangesStarted && !lifecycleMode) {
|
|
4542
4958
|
logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
|
|
4543
4959
|
}
|
|
4544
4960
|
};
|
|
@@ -4551,14 +4967,15 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4551
4967
|
}
|
|
4552
4968
|
logger.warn('Variant not rendered', 'variant_not_rendered', metadata);
|
|
4553
4969
|
};
|
|
4554
|
-
waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
}
|
|
4559
|
-
|
|
4560
|
-
.
|
|
4561
|
-
|
|
4970
|
+
waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs, QueueName.CFQ, !lifecycleMode).then((cmd) => {
|
|
4971
|
+
if (cmd) {
|
|
4972
|
+
revealActions.requestReveal('legacy-variant-rendered');
|
|
4973
|
+
return;
|
|
4974
|
+
}
|
|
4975
|
+
warnMissingVariant();
|
|
4976
|
+
if (revealActions.getSnapshot().pageRevealed)
|
|
4977
|
+
return;
|
|
4978
|
+
revealActions.forceReveal('config-timeout');
|
|
4562
4979
|
});
|
|
4563
4980
|
const loggedReasons = [];
|
|
4564
4981
|
listenForCmd((cmd) => cmd.emit === 'variantNotRendered', (cmd) => {
|
|
@@ -4572,6 +4989,7 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4572
4989
|
description: 'No Coframe Page ID',
|
|
4573
4990
|
});
|
|
4574
4991
|
variantRendered();
|
|
4992
|
+
revealActions.forceReveal('graceful-exit');
|
|
4575
4993
|
return null;
|
|
4576
4994
|
}
|
|
4577
4995
|
Coframe.projectId = projectId;
|
|
@@ -4743,6 +5161,824 @@ function injectOptions(options) {
|
|
|
4743
5161
|
}
|
|
4744
5162
|
}
|
|
4745
5163
|
|
|
5164
|
+
const TERMINAL_STATUSES = new Set([
|
|
5165
|
+
'completed',
|
|
5166
|
+
'error',
|
|
5167
|
+
'cleaned',
|
|
5168
|
+
]);
|
|
5169
|
+
const DEFAULT_FADE_DURATION_MS = 150;
|
|
5170
|
+
const MAX_INGEST_PER_SESSION = 10;
|
|
5171
|
+
const TRANSITION_LONGHANDS = [
|
|
5172
|
+
'transition-property',
|
|
5173
|
+
'transition-duration',
|
|
5174
|
+
'transition-timing-function',
|
|
5175
|
+
'transition-delay',
|
|
5176
|
+
];
|
|
5177
|
+
const fadeTargets = new Map();
|
|
5178
|
+
const FADE_SAFETY_MS = 5000;
|
|
5179
|
+
function cancelFadeAnimation(fadeTarget) {
|
|
5180
|
+
if (fadeTarget.animationFrame !== null) {
|
|
5181
|
+
cancelAnimationFrame(fadeTarget.animationFrame);
|
|
5182
|
+
}
|
|
5183
|
+
if (fadeTarget.timer)
|
|
5184
|
+
clearTimeout(fadeTarget.timer);
|
|
5185
|
+
fadeTarget.animationFrame = null;
|
|
5186
|
+
fadeTarget.timer = null;
|
|
5187
|
+
}
|
|
5188
|
+
function finishFade(fadeTarget) {
|
|
5189
|
+
if (fadeTargets.get(fadeTarget.target) !== fadeTarget)
|
|
5190
|
+
return;
|
|
5191
|
+
fadeTargets.delete(fadeTarget.target);
|
|
5192
|
+
cancelFadeAnimation(fadeTarget);
|
|
5193
|
+
fadeTarget.target.removeEventListener('transitionend', onFadeTransitionEnd);
|
|
5194
|
+
for (const record of Array.from(fadeTarget.records))
|
|
5195
|
+
record.stop();
|
|
5196
|
+
fadeTarget.records.clear();
|
|
5197
|
+
const { target } = fadeTarget;
|
|
5198
|
+
const style = target.style;
|
|
5199
|
+
style.setProperty('transition', 'none', 'important');
|
|
5200
|
+
style.setProperty('opacity', ...fadeTarget.opacity);
|
|
5201
|
+
void getComputedStyle(target).opacity;
|
|
5202
|
+
style.removeProperty('transition');
|
|
5203
|
+
if (fadeTarget.transition[0]) {
|
|
5204
|
+
style.setProperty('transition', ...fadeTarget.transition);
|
|
5205
|
+
}
|
|
5206
|
+
for (const [name, value, priority] of fadeTarget.longhands) {
|
|
5207
|
+
if (value)
|
|
5208
|
+
style.setProperty(name, value, priority);
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
function onFadeTransitionEnd(event) {
|
|
5212
|
+
const fadeTarget = fadeTargets.get(event.target);
|
|
5213
|
+
if ((fadeTarget === null || fadeTarget === void 0 ? void 0 : fadeTarget.timer) && event.propertyName === 'opacity') {
|
|
5214
|
+
finishFade(fadeTarget);
|
|
5215
|
+
}
|
|
5216
|
+
}
|
|
5217
|
+
function maybeStartFade(fadeTarget) {
|
|
5218
|
+
if (fadeTarget.animationFrame !== null || fadeTarget.timer)
|
|
5219
|
+
return;
|
|
5220
|
+
let duration = 0;
|
|
5221
|
+
for (const record of Array.from(fadeTarget.records)) {
|
|
5222
|
+
if (!record.ready)
|
|
5223
|
+
return;
|
|
5224
|
+
duration = Math.max(duration, record.duration);
|
|
5225
|
+
}
|
|
5226
|
+
const { target } = fadeTarget;
|
|
5227
|
+
void getComputedStyle(target).opacity;
|
|
5228
|
+
fadeTarget.animationFrame = requestAnimationFrame(() => {
|
|
5229
|
+
fadeTarget.animationFrame = null;
|
|
5230
|
+
if (fadeTargets.get(target) !== fadeTarget)
|
|
5231
|
+
return;
|
|
5232
|
+
target.style.setProperty('transition', `opacity ${duration}ms ease`, 'important');
|
|
5233
|
+
target.style.setProperty('opacity', '1', 'important');
|
|
5234
|
+
fadeTarget.timer = setTimeout(() => finishFade(fadeTarget), duration + 100);
|
|
5235
|
+
});
|
|
5236
|
+
}
|
|
5237
|
+
function removeFadeRecord(record) {
|
|
5238
|
+
const { fadeTarget } = record;
|
|
5239
|
+
if (!fadeTarget.records.delete(record))
|
|
5240
|
+
return;
|
|
5241
|
+
record.stop();
|
|
5242
|
+
if (fadeTarget.records.size === 0)
|
|
5243
|
+
finishFade(fadeTarget);
|
|
5244
|
+
else
|
|
5245
|
+
maybeStartFade(fadeTarget);
|
|
5246
|
+
}
|
|
5247
|
+
function createVariantSession(options) {
|
|
5248
|
+
const listenerStops = new Set();
|
|
5249
|
+
let ingestCount = 0;
|
|
5250
|
+
let ingestLimitReported = false;
|
|
5251
|
+
let metadataDropReported = false;
|
|
5252
|
+
const fades = new Map();
|
|
5253
|
+
const fadeReleases = new Set();
|
|
5254
|
+
const state = {
|
|
5255
|
+
sessionId: options.sessionId,
|
|
5256
|
+
variantId: options.variantId,
|
|
5257
|
+
batchId: options.batchId,
|
|
5258
|
+
routeVersion: options.routeVersion,
|
|
5259
|
+
active: true,
|
|
5260
|
+
status: 'created',
|
|
5261
|
+
ownsPageAntiflicker: false,
|
|
5262
|
+
pageAntiflickerRemoved: false,
|
|
5263
|
+
};
|
|
5264
|
+
const isMutable = () => state.active && !TERMINAL_STATUSES.has(state.status);
|
|
5265
|
+
const notify = () => options.onStateChange(internal);
|
|
5266
|
+
const releaseVisuals = () => {
|
|
5267
|
+
var _a;
|
|
5268
|
+
for (const record of Array.from(fades.values()))
|
|
5269
|
+
removeFadeRecord(record);
|
|
5270
|
+
(_a = options.releaseVisuals) === null || _a === void 0 ? void 0 : _a.call(options, internal);
|
|
5271
|
+
};
|
|
5272
|
+
const fadeIn = (target, durationMs = DEFAULT_FADE_DURATION_MS) => {
|
|
5273
|
+
if (fades.has(target))
|
|
5274
|
+
return;
|
|
5275
|
+
const duration = typeof durationMs === 'number' && durationMs - durationMs === 0
|
|
5276
|
+
? Math.max(0, durationMs)
|
|
5277
|
+
: DEFAULT_FADE_DURATION_MS;
|
|
5278
|
+
let fadeTarget = fadeTargets.get(target);
|
|
5279
|
+
if (!fadeTarget) {
|
|
5280
|
+
const style = target.style;
|
|
5281
|
+
fadeTarget = {
|
|
5282
|
+
target,
|
|
5283
|
+
opacity: [
|
|
5284
|
+
style.getPropertyValue('opacity'),
|
|
5285
|
+
style.getPropertyPriority('opacity'),
|
|
5286
|
+
],
|
|
5287
|
+
transition: [
|
|
5288
|
+
style.getPropertyValue('transition'),
|
|
5289
|
+
style.getPropertyPriority('transition'),
|
|
5290
|
+
],
|
|
5291
|
+
longhands: TRANSITION_LONGHANDS.map((name) => [
|
|
5292
|
+
name,
|
|
5293
|
+
style.getPropertyValue(name),
|
|
5294
|
+
style.getPropertyPriority(name),
|
|
5295
|
+
]),
|
|
5296
|
+
records: new Set(),
|
|
5297
|
+
animationFrame: null,
|
|
5298
|
+
timer: null,
|
|
5299
|
+
};
|
|
5300
|
+
fadeTargets.set(target, fadeTarget);
|
|
5301
|
+
target.addEventListener('transitionend', onFadeTransitionEnd);
|
|
5302
|
+
}
|
|
5303
|
+
cancelFadeAnimation(fadeTarget);
|
|
5304
|
+
target.style.setProperty('transition', 'none', 'important');
|
|
5305
|
+
target.style.setProperty('opacity', '0', 'important');
|
|
5306
|
+
let pageRevealed = false;
|
|
5307
|
+
let released = false;
|
|
5308
|
+
let stopPageListener = () => { };
|
|
5309
|
+
let safetyTimer = null;
|
|
5310
|
+
const update = () => {
|
|
5311
|
+
record.ready = pageRevealed && released;
|
|
5312
|
+
if (record.ready)
|
|
5313
|
+
maybeStartFade(record.fadeTarget);
|
|
5314
|
+
};
|
|
5315
|
+
const release = () => {
|
|
5316
|
+
released = true;
|
|
5317
|
+
if (safetyTimer)
|
|
5318
|
+
clearTimeout(safetyTimer);
|
|
5319
|
+
safetyTimer = null;
|
|
5320
|
+
update();
|
|
5321
|
+
};
|
|
5322
|
+
const record = {
|
|
5323
|
+
fadeTarget,
|
|
5324
|
+
duration,
|
|
5325
|
+
ready: false,
|
|
5326
|
+
stop: () => {
|
|
5327
|
+
stopPageListener();
|
|
5328
|
+
if (safetyTimer)
|
|
5329
|
+
clearTimeout(safetyTimer);
|
|
5330
|
+
safetyTimer = null;
|
|
5331
|
+
fadeReleases.delete(release);
|
|
5332
|
+
if (fades.get(target) === record)
|
|
5333
|
+
fades.delete(target);
|
|
5334
|
+
},
|
|
5335
|
+
};
|
|
5336
|
+
fadeTarget.records.add(record);
|
|
5337
|
+
fades.set(target, record);
|
|
5338
|
+
fadeReleases.add(release);
|
|
5339
|
+
safetyTimer = setTimeout(release, FADE_SAFETY_MS);
|
|
5340
|
+
stopPageListener = subscribe('pageRevealed', () => {
|
|
5341
|
+
pageRevealed = true;
|
|
5342
|
+
update();
|
|
5343
|
+
}, true);
|
|
5344
|
+
if (options.getPageState().pageRevealed) {
|
|
5345
|
+
pageRevealed = true;
|
|
5346
|
+
update();
|
|
5347
|
+
}
|
|
5348
|
+
};
|
|
5349
|
+
const subscribe = (event, handler, once) => {
|
|
5350
|
+
if (!state.active)
|
|
5351
|
+
return () => { };
|
|
5352
|
+
const stopSubscription = options.subscribePage(event, handler, once);
|
|
5353
|
+
if (!state.active) {
|
|
5354
|
+
stopSubscription();
|
|
5355
|
+
return () => { };
|
|
5356
|
+
}
|
|
5357
|
+
const stop = () => {
|
|
5358
|
+
listenerStops.delete(stop);
|
|
5359
|
+
stopSubscription();
|
|
5360
|
+
};
|
|
5361
|
+
listenerStops.add(stop);
|
|
5362
|
+
return stop;
|
|
5363
|
+
};
|
|
5364
|
+
const facade = {
|
|
5365
|
+
getPageState: () => ({ ...options.getPageState() }),
|
|
5366
|
+
getState: () => ({ ...state }),
|
|
5367
|
+
getVariantId: () => state.variantId,
|
|
5368
|
+
onPage: (event, handler) => subscribe(event, handler, false),
|
|
5369
|
+
oncePage: (event, handler) => subscribe(event, handler, true),
|
|
5370
|
+
ready() {
|
|
5371
|
+
if (!isMutable() || state.status !== 'created')
|
|
5372
|
+
return;
|
|
5373
|
+
state.status = 'ready';
|
|
5374
|
+
notify();
|
|
5375
|
+
},
|
|
5376
|
+
takePageAntiflickerOwnership() {
|
|
5377
|
+
if (!isMutable() || state.ownsPageAntiflicker)
|
|
5378
|
+
return;
|
|
5379
|
+
state.ownsPageAntiflicker = true;
|
|
5380
|
+
state.status = 'rendering';
|
|
5381
|
+
notify();
|
|
5382
|
+
},
|
|
5383
|
+
fadeIn(target, durationMs) {
|
|
5384
|
+
if (!isMutable() || !target)
|
|
5385
|
+
return;
|
|
5386
|
+
if (options.fadeIn)
|
|
5387
|
+
options.fadeIn(internal, target, durationMs);
|
|
5388
|
+
else
|
|
5389
|
+
fadeIn(target, durationMs);
|
|
5390
|
+
},
|
|
5391
|
+
ingestApplied(payload) {
|
|
5392
|
+
var _a, _b, _c;
|
|
5393
|
+
if (!isMutable())
|
|
5394
|
+
return;
|
|
5395
|
+
if (ingestCount >= MAX_INGEST_PER_SESSION) {
|
|
5396
|
+
if (!ingestLimitReported) {
|
|
5397
|
+
ingestLimitReported = true;
|
|
5398
|
+
(_a = options.onIngestLimit) === null || _a === void 0 ? void 0 : _a.call(options, internal);
|
|
5399
|
+
}
|
|
5400
|
+
return;
|
|
5401
|
+
}
|
|
5402
|
+
ingestCount += 1;
|
|
5403
|
+
const metadataDropped = (_b = options.ingestApplied) === null || _b === void 0 ? void 0 : _b.call(options, internal, payload);
|
|
5404
|
+
if (metadataDropped && !metadataDropReported) {
|
|
5405
|
+
metadataDropReported = true;
|
|
5406
|
+
(_c = options.onMetadataDropped) === null || _c === void 0 ? void 0 : _c.call(options, internal);
|
|
5407
|
+
}
|
|
5408
|
+
},
|
|
5409
|
+
complete() {
|
|
5410
|
+
if (!isMutable())
|
|
5411
|
+
return;
|
|
5412
|
+
state.status = 'completed';
|
|
5413
|
+
for (const release of Array.from(fadeReleases))
|
|
5414
|
+
release();
|
|
5415
|
+
fadeReleases.clear();
|
|
5416
|
+
options.emitLegacyVariantRendered();
|
|
5417
|
+
notify();
|
|
5418
|
+
},
|
|
5419
|
+
error(error) {
|
|
5420
|
+
var _a;
|
|
5421
|
+
if (!isMutable())
|
|
5422
|
+
return;
|
|
5423
|
+
state.status = 'error';
|
|
5424
|
+
releaseVisuals();
|
|
5425
|
+
(_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, internal, error);
|
|
5426
|
+
options.emitLegacyVariantRendered();
|
|
5427
|
+
notify();
|
|
5428
|
+
},
|
|
5429
|
+
cleanup() {
|
|
5430
|
+
if (state.status === 'cleaned')
|
|
5431
|
+
return;
|
|
5432
|
+
state.active = false;
|
|
5433
|
+
state.status = 'cleaned';
|
|
5434
|
+
for (const stop of Array.from(listenerStops))
|
|
5435
|
+
stop();
|
|
5436
|
+
releaseVisuals();
|
|
5437
|
+
notify();
|
|
5438
|
+
},
|
|
5439
|
+
};
|
|
5440
|
+
const internal = {
|
|
5441
|
+
facade: Object.freeze(facade),
|
|
5442
|
+
getState: () => ({ ...state }),
|
|
5443
|
+
isSettledForReveal: () => state.status === 'ready' ||
|
|
5444
|
+
state.status === 'rendering' ||
|
|
5445
|
+
TERMINAL_STATUSES.has(state.status),
|
|
5446
|
+
markPageRevealed() {
|
|
5447
|
+
if (state.pageAntiflickerRemoved)
|
|
5448
|
+
return;
|
|
5449
|
+
state.pageAntiflickerRemoved = true;
|
|
5450
|
+
},
|
|
5451
|
+
invalidate() {
|
|
5452
|
+
facade.cleanup();
|
|
5453
|
+
},
|
|
5454
|
+
};
|
|
5455
|
+
return internal;
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
const DEFAULT_BATCH_TIMEOUT_MS = 1000;
|
|
5459
|
+
function createPageRuntime(options) {
|
|
5460
|
+
var _a, _b, _c;
|
|
5461
|
+
const createId = (_a = options.createId) !== null && _a !== void 0 ? _a : generateUUID;
|
|
5462
|
+
const batchTimeoutMs = (_b = options.batchTimeoutMs) !== null && _b !== void 0 ? _b : DEFAULT_BATCH_TIMEOUT_MS;
|
|
5463
|
+
const revealDeadline = Date.now() + batchTimeoutMs;
|
|
5464
|
+
const timeUntilDeadline = () => Math.max(0, revealDeadline - Date.now());
|
|
5465
|
+
const listeners = new Map();
|
|
5466
|
+
const state = {
|
|
5467
|
+
sdkReady: false,
|
|
5468
|
+
hydrated: (_c = options.initialHydrated) !== null && _c !== void 0 ? _c : false,
|
|
5469
|
+
routeVersion: 0,
|
|
5470
|
+
currentUrl: options.currentUrl,
|
|
5471
|
+
pageRevealed: options.revealCoordinator.getSnapshot().pageRevealed,
|
|
5472
|
+
};
|
|
5473
|
+
let activeBatch = null;
|
|
5474
|
+
let handoffBlockerId = null;
|
|
5475
|
+
let handoffTimeout = null;
|
|
5476
|
+
let destroyed = false;
|
|
5477
|
+
const clearHandoff = () => {
|
|
5478
|
+
handoffBlockerId = null;
|
|
5479
|
+
if (handoffTimeout)
|
|
5480
|
+
clearTimeout(handoffTimeout);
|
|
5481
|
+
handoffTimeout = null;
|
|
5482
|
+
};
|
|
5483
|
+
const callListener = (event, handler, payload) => {
|
|
5484
|
+
var _a;
|
|
5485
|
+
try {
|
|
5486
|
+
handler(payload);
|
|
5487
|
+
}
|
|
5488
|
+
catch (error) {
|
|
5489
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime listener failed', {
|
|
5490
|
+
event,
|
|
5491
|
+
error: describeError(error),
|
|
5492
|
+
});
|
|
5493
|
+
}
|
|
5494
|
+
};
|
|
5495
|
+
const emit = (event, payload) => {
|
|
5496
|
+
const handlers = listeners.get(event);
|
|
5497
|
+
if (!handlers)
|
|
5498
|
+
return;
|
|
5499
|
+
for (const handler of Array.from(handlers)) {
|
|
5500
|
+
if (handlers.has(handler))
|
|
5501
|
+
callListener(event, handler, payload);
|
|
5502
|
+
}
|
|
5503
|
+
};
|
|
5504
|
+
const on = (event, handler) => {
|
|
5505
|
+
var _a;
|
|
5506
|
+
const handlers = (_a = listeners.get(event)) !== null && _a !== void 0 ? _a : new Set();
|
|
5507
|
+
const entry = (payload) => handler(payload);
|
|
5508
|
+
handlers.add(entry);
|
|
5509
|
+
listeners.set(event, handlers);
|
|
5510
|
+
return () => {
|
|
5511
|
+
handlers.delete(entry);
|
|
5512
|
+
if (handlers.size === 0 && listeners.get(event) === handlers) {
|
|
5513
|
+
listeners.delete(event);
|
|
5514
|
+
}
|
|
5515
|
+
};
|
|
5516
|
+
};
|
|
5517
|
+
const stickySatisfied = (event) => {
|
|
5518
|
+
if (event === 'sdkReady')
|
|
5519
|
+
return state.sdkReady;
|
|
5520
|
+
if (event === 'hydrated')
|
|
5521
|
+
return state.hydrated;
|
|
5522
|
+
if (event === 'pageRevealed')
|
|
5523
|
+
return state.pageRevealed;
|
|
5524
|
+
return false;
|
|
5525
|
+
};
|
|
5526
|
+
const stickyPayload = (event) => event === 'pageRevealed'
|
|
5527
|
+
? {
|
|
5528
|
+
...state,
|
|
5529
|
+
reason: options.revealCoordinator.getSnapshot().revealedReason,
|
|
5530
|
+
}
|
|
5531
|
+
: { ...state };
|
|
5532
|
+
const once = (event, handler) => {
|
|
5533
|
+
if (stickySatisfied(event)) {
|
|
5534
|
+
callListener(event, handler, stickyPayload(event));
|
|
5535
|
+
return () => { };
|
|
5536
|
+
}
|
|
5537
|
+
let stop = () => { };
|
|
5538
|
+
stop = on(event, (payload) => {
|
|
5539
|
+
stop();
|
|
5540
|
+
handler(payload);
|
|
5541
|
+
});
|
|
5542
|
+
return stop;
|
|
5543
|
+
};
|
|
5544
|
+
const clearBatchTimeout = (batch) => {
|
|
5545
|
+
if (!batch.timeout)
|
|
5546
|
+
return;
|
|
5547
|
+
clearTimeout(batch.timeout);
|
|
5548
|
+
batch.timeout = null;
|
|
5549
|
+
};
|
|
5550
|
+
const revealFromRuntime = (batchId, reveal) => {
|
|
5551
|
+
var _a;
|
|
5552
|
+
try {
|
|
5553
|
+
reveal();
|
|
5554
|
+
}
|
|
5555
|
+
catch (error) {
|
|
5556
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime page reveal failed', {
|
|
5557
|
+
error: describeError(error),
|
|
5558
|
+
...(batchId ? { batchId } : {}),
|
|
5559
|
+
routeVersion: state.routeVersion,
|
|
5560
|
+
});
|
|
5561
|
+
}
|
|
5562
|
+
};
|
|
5563
|
+
const forceRevealOnTimeout = (batchId) => revealFromRuntime(batchId, () => options.revealCoordinator.forceReveal('batch-timeout'));
|
|
5564
|
+
const releaseBatch = (batch) => {
|
|
5565
|
+
if (batch.released)
|
|
5566
|
+
return;
|
|
5567
|
+
batch.released = true;
|
|
5568
|
+
clearBatchTimeout(batch);
|
|
5569
|
+
revealFromRuntime(batch.batchId, () => options.revealCoordinator.release(batch.batchId, 'batch-settled'));
|
|
5570
|
+
};
|
|
5571
|
+
const warnForSession = (session, message) => {
|
|
5572
|
+
var _a;
|
|
5573
|
+
const sessionState = session.getState();
|
|
5574
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, message, {
|
|
5575
|
+
variantId: sessionState.variantId,
|
|
5576
|
+
batchId: sessionState.batchId,
|
|
5577
|
+
});
|
|
5578
|
+
};
|
|
5579
|
+
const maybeReleaseBatch = (batch) => {
|
|
5580
|
+
if (activeBatch !== batch ||
|
|
5581
|
+
batch.released ||
|
|
5582
|
+
!batch.closed ||
|
|
5583
|
+
batch.sessions.size < batch.expectedSessionCount)
|
|
5584
|
+
return;
|
|
5585
|
+
if (Array.from(batch.sessions.values()).every((session) => session.isSettledForReveal())) {
|
|
5586
|
+
releaseBatch(batch);
|
|
5587
|
+
}
|
|
5588
|
+
};
|
|
5589
|
+
const warnUnsettled = (batch, reason) => {
|
|
5590
|
+
var _a;
|
|
5591
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime batch timed out', {
|
|
5592
|
+
batchId: batch.batchId,
|
|
5593
|
+
routeVersion: batch.routeVersion,
|
|
5594
|
+
reason,
|
|
5595
|
+
expectedSessionCount: batch.expectedSessionCount,
|
|
5596
|
+
createdSessionCount: batch.sessions.size,
|
|
5597
|
+
unsettledVariantIds: Array.from(batch.sessions.values())
|
|
5598
|
+
.filter((session) => !session.isSettledForReveal())
|
|
5599
|
+
.map((session) => session.getState().variantId),
|
|
5600
|
+
});
|
|
5601
|
+
};
|
|
5602
|
+
const invalidateBatch = (batchId) => {
|
|
5603
|
+
if (!activeBatch || activeBatch.batchId !== batchId)
|
|
5604
|
+
return;
|
|
5605
|
+
const batch = activeBatch;
|
|
5606
|
+
activeBatch = null;
|
|
5607
|
+
clearBatchTimeout(batch);
|
|
5608
|
+
for (const session of batch.sessions.values())
|
|
5609
|
+
session.invalidate();
|
|
5610
|
+
};
|
|
5611
|
+
const coordinatorStop = options.revealCoordinator.onRevealed((reason) => {
|
|
5612
|
+
if (state.pageRevealed)
|
|
5613
|
+
return;
|
|
5614
|
+
if (activeBatch && !activeBatch.released && reason !== 'batch-settled') {
|
|
5615
|
+
warnUnsettled(activeBatch, reason);
|
|
5616
|
+
}
|
|
5617
|
+
state.pageRevealed = true;
|
|
5618
|
+
clearHandoff();
|
|
5619
|
+
if (activeBatch) {
|
|
5620
|
+
activeBatch.released = true;
|
|
5621
|
+
clearBatchTimeout(activeBatch);
|
|
5622
|
+
for (const session of activeBatch.sessions.values()) {
|
|
5623
|
+
session.markPageRevealed();
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
emit('pageRevealed', { ...state, reason });
|
|
5627
|
+
});
|
|
5628
|
+
const runtime = {
|
|
5629
|
+
getState: () => ({ ...state }),
|
|
5630
|
+
on,
|
|
5631
|
+
once,
|
|
5632
|
+
publishSdkReady() {
|
|
5633
|
+
if (state.sdkReady)
|
|
5634
|
+
return;
|
|
5635
|
+
state.sdkReady = true;
|
|
5636
|
+
emit('sdkReady', { ...state });
|
|
5637
|
+
},
|
|
5638
|
+
publishHydrated() {
|
|
5639
|
+
if (state.hydrated)
|
|
5640
|
+
return;
|
|
5641
|
+
state.hydrated = true;
|
|
5642
|
+
emit('hydrated', { ...state });
|
|
5643
|
+
},
|
|
5644
|
+
settleRoute() {
|
|
5645
|
+
if (destroyed)
|
|
5646
|
+
return;
|
|
5647
|
+
if (handoffBlockerId) {
|
|
5648
|
+
const batchId = handoffBlockerId;
|
|
5649
|
+
clearHandoff();
|
|
5650
|
+
revealFromRuntime(batchId, () => options.revealCoordinator.release(batchId, 'no-variants'));
|
|
5651
|
+
return;
|
|
5652
|
+
}
|
|
5653
|
+
if (!state.pageRevealed && !activeBatch) {
|
|
5654
|
+
revealFromRuntime(null, () => options.revealCoordinator.requestReveal('no-variants'));
|
|
5655
|
+
}
|
|
5656
|
+
},
|
|
5657
|
+
updateUrl(currentUrl) {
|
|
5658
|
+
if (!destroyed)
|
|
5659
|
+
state.currentUrl = currentUrl;
|
|
5660
|
+
},
|
|
5661
|
+
changeRoute(currentUrl) {
|
|
5662
|
+
if (destroyed)
|
|
5663
|
+
return;
|
|
5664
|
+
if (activeBatch) {
|
|
5665
|
+
const { batchId } = activeBatch;
|
|
5666
|
+
invalidateBatch(batchId);
|
|
5667
|
+
if (!state.pageRevealed) {
|
|
5668
|
+
handoffBlockerId = batchId;
|
|
5669
|
+
const abandonedRouteVersion = state.routeVersion;
|
|
5670
|
+
if (!handoffTimeout) {
|
|
5671
|
+
handoffTimeout = setTimeout(() => {
|
|
5672
|
+
var _a;
|
|
5673
|
+
if (!handoffBlockerId)
|
|
5674
|
+
return;
|
|
5675
|
+
const handedOverBatchId = handoffBlockerId;
|
|
5676
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime route handoff timed out', {
|
|
5677
|
+
routeVersion: abandonedRouteVersion,
|
|
5678
|
+
});
|
|
5679
|
+
clearHandoff();
|
|
5680
|
+
forceRevealOnTimeout(handedOverBatchId);
|
|
5681
|
+
}, timeUntilDeadline());
|
|
5682
|
+
}
|
|
5683
|
+
}
|
|
5684
|
+
}
|
|
5685
|
+
state.routeVersion += 1;
|
|
5686
|
+
state.currentUrl = currentUrl;
|
|
5687
|
+
},
|
|
5688
|
+
beginBatch(input) {
|
|
5689
|
+
if (destroyed || input.expectedSessionCount === 0)
|
|
5690
|
+
return null;
|
|
5691
|
+
if (activeBatch)
|
|
5692
|
+
invalidateBatch(activeBatch.batchId);
|
|
5693
|
+
clearHandoff();
|
|
5694
|
+
const batch = {
|
|
5695
|
+
batchId: createId(),
|
|
5696
|
+
routeVersion: state.routeVersion,
|
|
5697
|
+
expectedSessionCount: input.expectedSessionCount,
|
|
5698
|
+
closed: false,
|
|
5699
|
+
released: state.pageRevealed,
|
|
5700
|
+
sessions: new Map(),
|
|
5701
|
+
timeout: null,
|
|
5702
|
+
};
|
|
5703
|
+
activeBatch = batch;
|
|
5704
|
+
if (!state.pageRevealed &&
|
|
5705
|
+
options.revealCoordinator.block(batch.batchId)) {
|
|
5706
|
+
batch.timeout = setTimeout(() => {
|
|
5707
|
+
if (activeBatch !== batch || batch.released)
|
|
5708
|
+
return;
|
|
5709
|
+
warnUnsettled(batch, 'batch-timeout');
|
|
5710
|
+
batch.released = true;
|
|
5711
|
+
forceRevealOnTimeout(batch.batchId);
|
|
5712
|
+
}, timeUntilDeadline());
|
|
5713
|
+
}
|
|
5714
|
+
return { batchId: batch.batchId, routeVersion: batch.routeVersion };
|
|
5715
|
+
},
|
|
5716
|
+
createVariantSession(input) {
|
|
5717
|
+
var _a;
|
|
5718
|
+
const batch = activeBatch;
|
|
5719
|
+
if (!batch ||
|
|
5720
|
+
batch.batchId !== input.batchId ||
|
|
5721
|
+
batch.routeVersion !== state.routeVersion ||
|
|
5722
|
+
(batch.released && !state.pageRevealed))
|
|
5723
|
+
return null;
|
|
5724
|
+
const session = createVariantSession({
|
|
5725
|
+
sessionId: createId(),
|
|
5726
|
+
variantId: input.variantId,
|
|
5727
|
+
batchId: batch.batchId,
|
|
5728
|
+
routeVersion: batch.routeVersion,
|
|
5729
|
+
getPageState: () => ({ ...state }),
|
|
5730
|
+
subscribePage: (event, handler, onlyOnce) => {
|
|
5731
|
+
if (onlyOnce)
|
|
5732
|
+
return once(event, handler);
|
|
5733
|
+
if (stickySatisfied(event)) {
|
|
5734
|
+
callListener(event, handler, stickyPayload(event));
|
|
5735
|
+
}
|
|
5736
|
+
return on(event, handler);
|
|
5737
|
+
},
|
|
5738
|
+
onStateChange: () => maybeReleaseBatch(batch),
|
|
5739
|
+
emitLegacyVariantRendered: (_a = options.emitLegacyVariantRendered) !== null && _a !== void 0 ? _a : (() => { }),
|
|
5740
|
+
ingestApplied: (currentSession, payload) => {
|
|
5741
|
+
var _a, _b;
|
|
5742
|
+
return (_b = (_a = options.onVariantApplied) === null || _a === void 0 ? void 0 : _a.call(options, { ...state }, currentSession.getState(), payload)) !== null && _b !== void 0 ? _b : false;
|
|
5743
|
+
},
|
|
5744
|
+
onIngestLimit: (currentSession) => warnForSession(currentSession, 'Variant ingestApplied limit reached'),
|
|
5745
|
+
onMetadataDropped: (currentSession) => warnForSession(currentSession, 'Variant ingestApplied metadata dropped'),
|
|
5746
|
+
onError: (currentSession, error) => {
|
|
5747
|
+
var _a;
|
|
5748
|
+
const sessionState = currentSession.getState();
|
|
5749
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime session failed', {
|
|
5750
|
+
error: describeError(error),
|
|
5751
|
+
variantId: sessionState.variantId,
|
|
5752
|
+
sessionId: sessionState.sessionId,
|
|
5753
|
+
batchId: sessionState.batchId,
|
|
5754
|
+
routeVersion: sessionState.routeVersion,
|
|
5755
|
+
currentUrl: state.currentUrl,
|
|
5756
|
+
});
|
|
5757
|
+
},
|
|
5758
|
+
});
|
|
5759
|
+
if (state.pageRevealed)
|
|
5760
|
+
session.markPageRevealed();
|
|
5761
|
+
batch.sessions.set(session.getState().sessionId, session);
|
|
5762
|
+
maybeReleaseBatch(batch);
|
|
5763
|
+
return session;
|
|
5764
|
+
},
|
|
5765
|
+
closeBatch(batchId) {
|
|
5766
|
+
if (!activeBatch || activeBatch.batchId !== batchId)
|
|
5767
|
+
return;
|
|
5768
|
+
activeBatch.closed = true;
|
|
5769
|
+
maybeReleaseBatch(activeBatch);
|
|
5770
|
+
},
|
|
5771
|
+
invalidateBatch,
|
|
5772
|
+
invalidateAllSessions() {
|
|
5773
|
+
if (activeBatch)
|
|
5774
|
+
invalidateBatch(activeBatch.batchId);
|
|
5775
|
+
},
|
|
5776
|
+
getDebugSnapshot() {
|
|
5777
|
+
return {
|
|
5778
|
+
page: { ...state },
|
|
5779
|
+
activeBatch: activeBatch
|
|
5780
|
+
? {
|
|
5781
|
+
batchId: activeBatch.batchId,
|
|
5782
|
+
routeVersion: activeBatch.routeVersion,
|
|
5783
|
+
expectedSessionCount: activeBatch.expectedSessionCount,
|
|
5784
|
+
closed: activeBatch.closed,
|
|
5785
|
+
released: activeBatch.released,
|
|
5786
|
+
sessionStates: Array.from(activeBatch.sessions.values()).map((session) => session.getState()),
|
|
5787
|
+
}
|
|
5788
|
+
: null,
|
|
5789
|
+
};
|
|
5790
|
+
},
|
|
5791
|
+
isDestroyed: () => destroyed,
|
|
5792
|
+
destroy() {
|
|
5793
|
+
destroyed = true;
|
|
5794
|
+
const blockerIds = [handoffBlockerId, activeBatch === null || activeBatch === void 0 ? void 0 : activeBatch.batchId];
|
|
5795
|
+
if (activeBatch)
|
|
5796
|
+
invalidateBatch(activeBatch.batchId);
|
|
5797
|
+
clearHandoff();
|
|
5798
|
+
for (const blockerId of blockerIds) {
|
|
5799
|
+
if (blockerId)
|
|
5800
|
+
options.revealCoordinator.unblock(blockerId);
|
|
5801
|
+
}
|
|
5802
|
+
coordinatorStop();
|
|
5803
|
+
listeners.clear();
|
|
5804
|
+
},
|
|
5805
|
+
};
|
|
5806
|
+
return runtime;
|
|
5807
|
+
}
|
|
5808
|
+
|
|
5809
|
+
const MAX_TARGET_SELECTORS = 20;
|
|
5810
|
+
const MAX_SELECTOR_LENGTH = 500;
|
|
5811
|
+
const MAX_METADATA_JSON_BYTES = 4096;
|
|
5812
|
+
const MAX_METADATA_VISITS = 20000;
|
|
5813
|
+
const MAX_SELECTOR_ENTRIES_SCANNED = 200;
|
|
5814
|
+
function utf8ByteLength(value) {
|
|
5815
|
+
let bytes = 0;
|
|
5816
|
+
for (let i = 0; i < value.length; i++) {
|
|
5817
|
+
const code = value.charCodeAt(i);
|
|
5818
|
+
if (code < 0x80)
|
|
5819
|
+
bytes += 1;
|
|
5820
|
+
else if (code < 0x800)
|
|
5821
|
+
bytes += 2;
|
|
5822
|
+
else if (code >= 0xd800 && code <= 0xdbff) {
|
|
5823
|
+
const next = value.charCodeAt(i + 1);
|
|
5824
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
5825
|
+
bytes += 4;
|
|
5826
|
+
i++;
|
|
5827
|
+
}
|
|
5828
|
+
else {
|
|
5829
|
+
bytes += 3;
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
else
|
|
5833
|
+
bytes += 3;
|
|
5834
|
+
}
|
|
5835
|
+
return bytes;
|
|
5836
|
+
}
|
|
5837
|
+
function isBoxedString(value) {
|
|
5838
|
+
if (!value || typeof value !== 'object')
|
|
5839
|
+
return false;
|
|
5840
|
+
try {
|
|
5841
|
+
String.prototype.valueOf.call(value);
|
|
5842
|
+
return true;
|
|
5843
|
+
}
|
|
5844
|
+
catch (_a) {
|
|
5845
|
+
return false;
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
function sanitizeMetadata(payload) {
|
|
5849
|
+
try {
|
|
5850
|
+
const rawMetadata = payload.metadata;
|
|
5851
|
+
if (rawMetadata === undefined || rawMetadata === null)
|
|
5852
|
+
return {};
|
|
5853
|
+
if (typeof rawMetadata !== 'object' || Array.isArray(rawMetadata)) {
|
|
5854
|
+
return null;
|
|
5855
|
+
}
|
|
5856
|
+
let minimumLength = 0;
|
|
5857
|
+
let visits = 0;
|
|
5858
|
+
const json = JSON.stringify(rawMetadata, function (key, value) {
|
|
5859
|
+
visits += 1;
|
|
5860
|
+
if (visits > MAX_METADATA_VISITS) {
|
|
5861
|
+
throw new Error('metadata too large');
|
|
5862
|
+
}
|
|
5863
|
+
const isObjectProperty = visits > 1 && !Array.isArray(this);
|
|
5864
|
+
if (isObjectProperty &&
|
|
5865
|
+
(value === undefined ||
|
|
5866
|
+
typeof value === 'function' ||
|
|
5867
|
+
typeof value === 'symbol')) {
|
|
5868
|
+
return value;
|
|
5869
|
+
}
|
|
5870
|
+
if (isObjectProperty)
|
|
5871
|
+
minimumLength += key.length + 3;
|
|
5872
|
+
const written = isBoxedString(value) ? String(value) : value;
|
|
5873
|
+
minimumLength += typeof written === 'string' ? written.length + 2 : 1;
|
|
5874
|
+
if (minimumLength > MAX_METADATA_JSON_BYTES) {
|
|
5875
|
+
throw new Error('metadata too large');
|
|
5876
|
+
}
|
|
5877
|
+
return written;
|
|
5878
|
+
});
|
|
5879
|
+
if (!json ||
|
|
5880
|
+
json.length > MAX_METADATA_JSON_BYTES ||
|
|
5881
|
+
utf8ByteLength(json) > MAX_METADATA_JSON_BYTES) {
|
|
5882
|
+
return null;
|
|
5883
|
+
}
|
|
5884
|
+
const copy = JSON.parse(json);
|
|
5885
|
+
return copy && typeof copy === 'object' && !Array.isArray(copy)
|
|
5886
|
+
? copy
|
|
5887
|
+
: null;
|
|
5888
|
+
}
|
|
5889
|
+
catch (_a) {
|
|
5890
|
+
return null;
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
function sanitizeSelectors(payload) {
|
|
5894
|
+
try {
|
|
5895
|
+
const selectors = payload.targetSelectors;
|
|
5896
|
+
if (!Array.isArray(selectors))
|
|
5897
|
+
return [];
|
|
5898
|
+
const kept = [];
|
|
5899
|
+
const scanned = Math.min(selectors.length, MAX_SELECTOR_ENTRIES_SCANNED);
|
|
5900
|
+
for (let i = 0; i < scanned; i++) {
|
|
5901
|
+
let selector;
|
|
5902
|
+
try {
|
|
5903
|
+
selector = selectors[i];
|
|
5904
|
+
}
|
|
5905
|
+
catch (_a) {
|
|
5906
|
+
continue;
|
|
5907
|
+
}
|
|
5908
|
+
if (typeof selector === 'string' &&
|
|
5909
|
+
selector.length > 0 &&
|
|
5910
|
+
selector.length <= MAX_SELECTOR_LENGTH) {
|
|
5911
|
+
kept.push(selector);
|
|
5912
|
+
if (kept.length === MAX_TARGET_SELECTORS)
|
|
5913
|
+
break;
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
return kept;
|
|
5917
|
+
}
|
|
5918
|
+
catch (_b) {
|
|
5919
|
+
return [];
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
function sanitizePayload(payloadInput) {
|
|
5923
|
+
if (!payloadInput ||
|
|
5924
|
+
typeof payloadInput !== 'object' ||
|
|
5925
|
+
Array.isArray(payloadInput)) {
|
|
5926
|
+
const given = payloadInput !== undefined && payloadInput !== null;
|
|
5927
|
+
return { metadata: {}, metadataDropped: given, targetSelectors: [] };
|
|
5928
|
+
}
|
|
5929
|
+
const payload = payloadInput;
|
|
5930
|
+
const metadata = sanitizeMetadata(payload);
|
|
5931
|
+
return {
|
|
5932
|
+
metadata: metadata || {},
|
|
5933
|
+
metadataDropped: metadata === null,
|
|
5934
|
+
targetSelectors: sanitizeSelectors(payload),
|
|
5935
|
+
};
|
|
5936
|
+
}
|
|
5937
|
+
function ingestVariantApplied(coframe, pageState, sessionState, payloadInput) {
|
|
5938
|
+
const { metadata, metadataDropped, targetSelectors } = sanitizePayload(payloadInput);
|
|
5939
|
+
const personalizationHeaders = coframe.personalizationHeaders;
|
|
5940
|
+
const event = {
|
|
5941
|
+
type: 'variant_applied',
|
|
5942
|
+
project_id: coframe.projectId || '',
|
|
5943
|
+
session_id: coframe.sessionId || '',
|
|
5944
|
+
time: new Date(),
|
|
5945
|
+
url: getCurrentPageUrl(),
|
|
5946
|
+
user_token: coframe.userToken || '',
|
|
5947
|
+
org_id: coframe.orgId || '',
|
|
5948
|
+
variant_id: sessionState.variantId,
|
|
5949
|
+
content: {
|
|
5950
|
+
metadata: {
|
|
5951
|
+
batch_id: sessionState.batchId,
|
|
5952
|
+
route_version: pageState.routeVersion,
|
|
5953
|
+
current_url: pageState.currentUrl,
|
|
5954
|
+
...(personalizationHeaders
|
|
5955
|
+
? { personalization_headers: { ...personalizationHeaders } }
|
|
5956
|
+
: {}),
|
|
5957
|
+
...(targetSelectors.length
|
|
5958
|
+
? { target_selectors: targetSelectors }
|
|
5959
|
+
: {}),
|
|
5960
|
+
...(Object.keys(metadata).length ? { custom_data: metadata } : {}),
|
|
5961
|
+
},
|
|
5962
|
+
},
|
|
5963
|
+
};
|
|
5964
|
+
try {
|
|
5965
|
+
document.dispatchEvent(new CustomEvent('coframe:variant-applied', {
|
|
5966
|
+
detail: {
|
|
5967
|
+
...event,
|
|
5968
|
+
time: new Date(event.time.getTime()),
|
|
5969
|
+
content: {
|
|
5970
|
+
metadata: JSON.parse(JSON.stringify(event.content.metadata)),
|
|
5971
|
+
},
|
|
5972
|
+
},
|
|
5973
|
+
}));
|
|
5974
|
+
}
|
|
5975
|
+
catch (_a) {
|
|
5976
|
+
}
|
|
5977
|
+
if (coframe.projectId)
|
|
5978
|
+
queueEventForBatchedSending(coframe, event);
|
|
5979
|
+
return metadataDropped;
|
|
5980
|
+
}
|
|
5981
|
+
|
|
4746
5982
|
function push(cmd) {
|
|
4747
5983
|
if (typeof window === 'undefined')
|
|
4748
5984
|
return;
|
|
@@ -4790,34 +6026,81 @@ const Coframe = {
|
|
|
4790
6026
|
},
|
|
4791
6027
|
};
|
|
4792
6028
|
Coframe.logger = createLogger(() => Coframe);
|
|
4793
|
-
|
|
6029
|
+
const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
6030
|
+
let activeRuntime = null;
|
|
6031
|
+
function clearActiveRuntime() {
|
|
6032
|
+
activeRuntime === null || activeRuntime === void 0 ? void 0 : activeRuntime.destroy();
|
|
6033
|
+
activeRuntime = null;
|
|
6034
|
+
delete Coframe.runtime;
|
|
6035
|
+
}
|
|
6036
|
+
function installRuntime(config) {
|
|
6037
|
+
const batchTimeoutMs = config.timeoutMs
|
|
6038
|
+
? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
|
|
6039
|
+
: undefined;
|
|
6040
|
+
clearActiveRuntime();
|
|
6041
|
+
if (!isVariantRuntimeLifecycleEnabled(config))
|
|
6042
|
+
return undefined;
|
|
6043
|
+
const runtime = createPageRuntime({
|
|
6044
|
+
revealCoordinator,
|
|
6045
|
+
currentUrl: getCurrentPageUrl(),
|
|
6046
|
+
initialHydrated: !config.waitForHydration,
|
|
6047
|
+
batchTimeoutMs,
|
|
6048
|
+
warn: (message, metadata) => Coframe.logger.warn(message, 'variant_runtime_warning', metadata),
|
|
6049
|
+
error: (message, metadata) => Coframe.logger.error(message, 'variant_runtime_error', metadata),
|
|
6050
|
+
emitLegacyVariantRendered: variantRendered,
|
|
6051
|
+
onVariantApplied: (pageState, sessionState, payload) => ingestVariantApplied(Coframe, pageState, sessionState, payload),
|
|
6052
|
+
});
|
|
6053
|
+
activeRuntime = runtime;
|
|
6054
|
+
Coframe.runtime = Object.freeze({
|
|
6055
|
+
getSnapshot: () => runtime.getDebugSnapshot(),
|
|
6056
|
+
});
|
|
6057
|
+
runtime.publishSdkReady();
|
|
6058
|
+
return runtime;
|
|
6059
|
+
}
|
|
6060
|
+
async function initialize(options, context = {}) {
|
|
4794
6061
|
if (typeof window !== 'undefined' &&
|
|
4795
6062
|
/cf_disable/.test(window.location.search)) {
|
|
4796
6063
|
console.warn('[Coframe] Disabled via URL parameter');
|
|
4797
|
-
|
|
6064
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4798
6065
|
return;
|
|
4799
6066
|
}
|
|
4800
6067
|
if (options)
|
|
4801
6068
|
injectOptions(options);
|
|
4802
6069
|
const gracefulExit = (msg) => {
|
|
4803
6070
|
console.warn(`[Coframe] ${msg}`);
|
|
4804
|
-
|
|
6071
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4805
6072
|
return;
|
|
4806
6073
|
};
|
|
4807
|
-
const configResult = await resolveConfig(Coframe,
|
|
6074
|
+
const configResult = await resolveConfig(Coframe, revealCoordinator);
|
|
4808
6075
|
if (!configResult)
|
|
4809
6076
|
return;
|
|
6077
|
+
const runtime = installRuntime(configResult.config);
|
|
6078
|
+
context.runtime = runtime;
|
|
4810
6079
|
loadLauncherIfEnabled(configResult.config.enableDebug, configResult.config.nonce);
|
|
4811
|
-
const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
|
|
4812
|
-
if (
|
|
6080
|
+
const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config, () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()));
|
|
6081
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4813
6082
|
return;
|
|
6083
|
+
if (!projectResult) {
|
|
6084
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
6085
|
+
return;
|
|
6086
|
+
}
|
|
4814
6087
|
Coframe.orgId = projectResult.project.org_id || '';
|
|
4815
6088
|
Coframe.personalizationHeaders = projectResult.personalizationHeaders;
|
|
4816
|
-
const identity = await resolveIdentity(configResult.config, Coframe, configResult.timeoutMs,
|
|
6089
|
+
const identity = await resolveIdentity(configResult.config, Coframe, configResult.timeoutMs, (msg) => {
|
|
6090
|
+
if (!(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
6091
|
+
gracefulExit(msg);
|
|
6092
|
+
}, configResult.previewVariantId);
|
|
4817
6093
|
if (!identity)
|
|
4818
6094
|
return;
|
|
6095
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
6096
|
+
return;
|
|
4819
6097
|
const analytics = setupTracking(Coframe, projectResult, configResult.previewVariantId, configResult.executionStartTime, configResult.config.nonce, configResult.config.waitForCookiesEnabled, identity.onConsentChange);
|
|
4820
|
-
await modifyPage(Coframe, configResult, projectResult, analytics
|
|
6098
|
+
await modifyPage(Coframe, configResult, projectResult, analytics, {
|
|
6099
|
+
revealCoordinator,
|
|
6100
|
+
runtime,
|
|
6101
|
+
});
|
|
6102
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
6103
|
+
return;
|
|
4821
6104
|
try {
|
|
4822
6105
|
startReplayIfEnabled(Coframe, projectResult.project, configResult.config.nonce);
|
|
4823
6106
|
}
|
|
@@ -4825,6 +6108,19 @@ async function init(options) {
|
|
|
4825
6108
|
console.warn('[Coframe] Failed to start session replay', error);
|
|
4826
6109
|
}
|
|
4827
6110
|
}
|
|
6111
|
+
async function init(options) {
|
|
6112
|
+
var _a;
|
|
6113
|
+
const context = {};
|
|
6114
|
+
try {
|
|
6115
|
+
await initialize(options, context);
|
|
6116
|
+
}
|
|
6117
|
+
catch (error) {
|
|
6118
|
+
if (!((_a = context.runtime) === null || _a === void 0 ? void 0 : _a.isDestroyed())) {
|
|
6119
|
+
revealCoordinator.forceReveal('init-error');
|
|
6120
|
+
}
|
|
6121
|
+
throw error;
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
4828
6124
|
Coframe.debug = {
|
|
4829
6125
|
getSelector,
|
|
4830
6126
|
getElement,
|