@coframe/sdk 1.1.44 → 1.1.46
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 +1412 -100
- package/dist/pkg/cf.esm.mjs +1412 -100
- 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 {
|
|
@@ -3888,17 +4192,39 @@ function setupListeners(Coframe, executionStartTime) {
|
|
|
3888
4192
|
});
|
|
3889
4193
|
};
|
|
3890
4194
|
document.addEventListener('click', handleClick, true);
|
|
3891
|
-
|
|
4195
|
+
let scheduledEventFlush = null;
|
|
4196
|
+
const flushEvent = (eventType) => {
|
|
3892
4197
|
queueLeaveEvent(Coframe, eventType, eventType === 'tab_show' ? undefined : leaveFields());
|
|
3893
4198
|
flushQueuedEvents(Coframe.projectId);
|
|
3894
4199
|
notifyEventsFlushed();
|
|
3895
|
-
}
|
|
3896
|
-
|
|
3897
|
-
|
|
4200
|
+
};
|
|
4201
|
+
const scheduleEventFlush = (eventType) => {
|
|
4202
|
+
if (scheduledEventFlush !== null) {
|
|
4203
|
+
clearTimeout(scheduledEventFlush);
|
|
4204
|
+
}
|
|
4205
|
+
scheduledEventFlush = setTimeout(() => {
|
|
4206
|
+
scheduledEventFlush = null;
|
|
4207
|
+
flushEvent(eventType);
|
|
4208
|
+
}, 1);
|
|
4209
|
+
};
|
|
4210
|
+
const flushEventImmediately = (eventType) => {
|
|
4211
|
+
if (scheduledEventFlush !== null) {
|
|
4212
|
+
clearTimeout(scheduledEventFlush);
|
|
4213
|
+
scheduledEventFlush = null;
|
|
4214
|
+
}
|
|
4215
|
+
flushEvent(eventType);
|
|
4216
|
+
};
|
|
4217
|
+
window.addEventListener('beforeunload', () => scheduleEventFlush('before_unload'));
|
|
4218
|
+
window.addEventListener('pagehide', () => flushEventImmediately('page_hide'));
|
|
3898
4219
|
window.addEventListener('visibilitychange', () => {
|
|
3899
4220
|
const isPageHidden = document.hidden;
|
|
3900
4221
|
const eventType = isPageHidden ? 'tab_hide' : 'tab_show';
|
|
3901
|
-
|
|
4222
|
+
if (isPageHidden) {
|
|
4223
|
+
flushEventImmediately(eventType);
|
|
4224
|
+
}
|
|
4225
|
+
else {
|
|
4226
|
+
scheduleEventFlush(eventType);
|
|
4227
|
+
}
|
|
3902
4228
|
});
|
|
3903
4229
|
}
|
|
3904
4230
|
function queueLeaveEvent(Coframe, eventType, fields) {
|
|
@@ -3978,26 +4304,32 @@ function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
|
|
|
3978
4304
|
}
|
|
3979
4305
|
return { previewVariantId: null, isSessionVariant: false };
|
|
3980
4306
|
}
|
|
4307
|
+
const isPageHydratedCmd = (cmd) => cmd.pageHydrated === true || cmd.emit === 'pageHydrated';
|
|
3981
4308
|
async function awaitHydration(config, Coframe, project) {
|
|
3982
4309
|
var _a, _b;
|
|
3983
|
-
if (!config
|
|
4310
|
+
if (!shouldSdkWaitForHydration(config))
|
|
3984
4311
|
return true;
|
|
3985
4312
|
if (!((_a = project.frames) === null || _a === void 0 ? void 0 : _a.length) && !((_b = project.tags) === null || _b === void 0 ? void 0 : _b.length))
|
|
3986
4313
|
return true;
|
|
3987
|
-
const cmd = await waitForCmd(
|
|
4314
|
+
const cmd = await waitForCmd(isPageHydratedCmd, Coframe);
|
|
3988
4315
|
if (!cmd) {
|
|
3989
4316
|
Coframe.logger.warn('Failed to wait for page hydrated command', 'wait_for_cmd_timeout');
|
|
3990
4317
|
return false;
|
|
3991
4318
|
}
|
|
3992
4319
|
return true;
|
|
3993
4320
|
}
|
|
3994
|
-
async function ensureBody(Coframe, cdnProject, timeoutMs, nonce) {
|
|
4321
|
+
async function ensureBody(Coframe, cdnProject, timeoutMs, nonce, revealCoordinator, isCurrent) {
|
|
3995
4322
|
const bodyPresent = await waitForBody(timeoutMs).catch(() => false);
|
|
3996
4323
|
if (!bodyPresent) {
|
|
3997
4324
|
Coframe.logger.error('Timeout waiting for document.body, exiting gracefully', 'body_timeout');
|
|
3998
|
-
|
|
4325
|
+
if (isCurrent()) {
|
|
4326
|
+
variantRendered();
|
|
4327
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4328
|
+
}
|
|
3999
4329
|
return false;
|
|
4000
4330
|
}
|
|
4331
|
+
if (!isCurrent())
|
|
4332
|
+
return false;
|
|
4001
4333
|
rebindMetrics(Coframe, Coframe.logger, cdnProject === null || cdnProject === void 0 ? void 0 : cdnProject.metrics, nonce);
|
|
4002
4334
|
return true;
|
|
4003
4335
|
}
|
|
@@ -4010,7 +4342,13 @@ function setupFlushInterval(Coframe) {
|
|
|
4010
4342
|
}
|
|
4011
4343
|
flushInterval = setInterval(() => flushQueuedEvents(Coframe.projectId), FLUSH_EVENTS_TIME);
|
|
4012
4344
|
}
|
|
4013
|
-
|
|
4345
|
+
const hasNoVariants = (Coframe) => !Coframe.variantsAndElements || Coframe.variantsAndElements.length === 0;
|
|
4346
|
+
const logNavigationError = (Coframe, message, error) => {
|
|
4347
|
+
Coframe.logger.error(message, 'spa_navigation_error', {
|
|
4348
|
+
error: describeError(error),
|
|
4349
|
+
});
|
|
4350
|
+
};
|
|
4351
|
+
function setupSPANavigation(Coframe, project, config, previewVariantId, isSessionVariant, runtime, applier, firstViewDeadline, onFirstViewDone) {
|
|
4014
4352
|
var _a;
|
|
4015
4353
|
if (!((_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.mutation_observer_active))
|
|
4016
4354
|
return;
|
|
@@ -4035,16 +4373,20 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4035
4373
|
const observer = new MutationObserver(mutationCallback);
|
|
4036
4374
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4037
4375
|
let navigationAbortController = null;
|
|
4038
|
-
|
|
4376
|
+
const onUrlChange = async (newPageUrl, _oldPageUrl, options = {}) => {
|
|
4039
4377
|
var _a;
|
|
4040
|
-
|
|
4378
|
+
const firstView = !applier.hasCommitted();
|
|
4379
|
+
if (!firstView)
|
|
4380
|
+
sendPageLeave(Coframe);
|
|
4041
4381
|
if (navigationAbortController)
|
|
4042
4382
|
navigationAbortController.abort();
|
|
4043
|
-
|
|
4044
|
-
const
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4383
|
+
runtime === null || runtime === void 0 ? void 0 : runtime.changeRoute(newPageUrl);
|
|
4384
|
+
const controller = createNavigationController();
|
|
4385
|
+
navigationAbortController = controller;
|
|
4386
|
+
const { signal } = controller;
|
|
4387
|
+
await waitForFrame(options.routeAlreadyRendered ? HANDOVER_FRAME_CAP_MS : undefined, NAVIGATION_FRAME_CAP_MS);
|
|
4388
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4389
|
+
return 'skipped';
|
|
4048
4390
|
infiniteLoopProtection = 0;
|
|
4049
4391
|
let pageProject = project;
|
|
4050
4392
|
let pagePersonalizationHeaders = Coframe.personalizationHeaders;
|
|
@@ -4060,9 +4402,9 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4060
4402
|
else {
|
|
4061
4403
|
try {
|
|
4062
4404
|
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;
|
|
4405
|
+
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());
|
|
4406
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4407
|
+
return 'skipped';
|
|
4066
4408
|
if (result) {
|
|
4067
4409
|
pageProject = result.project;
|
|
4068
4410
|
pagePersonalizationHeaders = {
|
|
@@ -4075,10 +4417,37 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4075
4417
|
catch (_b) {
|
|
4076
4418
|
}
|
|
4077
4419
|
}
|
|
4078
|
-
if (signal.aborted)
|
|
4079
|
-
return;
|
|
4080
|
-
|
|
4081
|
-
|
|
4420
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4421
|
+
return 'skipped';
|
|
4422
|
+
let result = 'skipped';
|
|
4423
|
+
let threw = false;
|
|
4424
|
+
try {
|
|
4425
|
+
result = await applier.apply(newPageUrl, pageProject, pagePersonalizationHeaders, signal, firstView ? firstViewDeadline : undefined);
|
|
4426
|
+
}
|
|
4427
|
+
catch (error) {
|
|
4428
|
+
threw = true;
|
|
4429
|
+
if (firstView && !signal.aborted && !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())) {
|
|
4430
|
+
onFirstViewDone === null || onFirstViewDone === void 0 ? void 0 : onFirstViewDone(pageProject);
|
|
4431
|
+
}
|
|
4432
|
+
throw error;
|
|
4433
|
+
}
|
|
4434
|
+
finally {
|
|
4435
|
+
if (shouldSettleRoute(signal.aborted, threw, result)) {
|
|
4436
|
+
runtime === null || runtime === void 0 ? void 0 : runtime.settleRoute();
|
|
4437
|
+
}
|
|
4438
|
+
}
|
|
4439
|
+
if (firstView && result === 'applied' && !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())) {
|
|
4440
|
+
if (hasNoVariants(Coframe))
|
|
4441
|
+
variantRendered();
|
|
4442
|
+
onFirstViewDone === null || onFirstViewDone === void 0 ? void 0 : onFirstViewDone(pageProject);
|
|
4443
|
+
}
|
|
4444
|
+
return result;
|
|
4445
|
+
};
|
|
4446
|
+
observeUrlChanges(false, (newPageUrl, oldPageUrl) => onUrlChange(newPageUrl).catch((error) => {
|
|
4447
|
+
logNavigationError(Coframe, 'SPA navigation failed', error);
|
|
4448
|
+
return 'skipped';
|
|
4449
|
+
}), () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()));
|
|
4450
|
+
return onUrlChange;
|
|
4082
4451
|
}
|
|
4083
4452
|
function readLaneClaims() {
|
|
4084
4453
|
const cookie = getCookie(LANE_COOKIE_KEY);
|
|
@@ -4108,17 +4477,14 @@ function persistLaneClaims(claims, logger) {
|
|
|
4108
4477
|
setCookie(LANE_COOKIE_KEY, encodeURIComponent(serialized), logger);
|
|
4109
4478
|
}
|
|
4110
4479
|
}
|
|
4111
|
-
function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore) {
|
|
4480
|
+
function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore, runtime) {
|
|
4112
4481
|
let isFirstApply = true;
|
|
4113
4482
|
let applicationId = 0;
|
|
4114
|
-
|
|
4115
|
-
var _a;
|
|
4483
|
+
const apply = async (pageUrl, pageProject, personalizationHeaders, signal, deadline) => {
|
|
4484
|
+
var _a, _b;
|
|
4116
4485
|
const currentApplication = ++applicationId;
|
|
4117
4486
|
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;
|
|
4487
|
+
return 'skipped';
|
|
4122
4488
|
const preview = resolvePreview(pageUrl, previewVariantId, isSessionVariant);
|
|
4123
4489
|
const chooseCmabVariants = createCmabVariantBatchChooser({
|
|
4124
4490
|
projectId: Coframe.projectId,
|
|
@@ -4132,8 +4498,22 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4132
4498
|
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
4499
|
if ((signal === null || signal === void 0 ? void 0 : signal.aborted) ||
|
|
4134
4500
|
currentApplication !== applicationId ||
|
|
4501
|
+
(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()) ||
|
|
4135
4502
|
(signal && !urlsMatch(pageUrl, getCurrentPageUrl())))
|
|
4136
|
-
return;
|
|
4503
|
+
return 'skipped';
|
|
4504
|
+
if (!signal &&
|
|
4505
|
+
runtime &&
|
|
4506
|
+
((_b = pageProject === null || pageProject === void 0 ? void 0 : pageProject.settings) === null || _b === void 0 ? void 0 : _b.mutation_observer_active) &&
|
|
4507
|
+
!urlsMatch(pageUrl, getCurrentPageUrl(), true))
|
|
4508
|
+
return 'stale-route';
|
|
4509
|
+
if (!signal && runtime) {
|
|
4510
|
+
const urlAtCommit = getCurrentPageUrl();
|
|
4511
|
+
if (urlsMatch(pageUrl, urlAtCommit, true))
|
|
4512
|
+
runtime.updateUrl(urlAtCommit);
|
|
4513
|
+
}
|
|
4514
|
+
const sessionId = isFirstApply ? Coframe.sessionId : generateUUID();
|
|
4515
|
+
const sessionContext = buildSessionContext(isFirstApply);
|
|
4516
|
+
isFirstApply = false;
|
|
4137
4517
|
Coframe.sessionId = sessionId;
|
|
4138
4518
|
Coframe.personalizationHeaders = personalizationHeaders;
|
|
4139
4519
|
setIsPreviewVariant(!!preview.previewVariantId);
|
|
@@ -4151,7 +4531,9 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4151
4531
|
variantRendered();
|
|
4152
4532
|
}
|
|
4153
4533
|
else {
|
|
4154
|
-
replaceVariants(variantsAndElements, Coframe.orgId, Coframe.logger, config.nonce);
|
|
4534
|
+
await replaceVariants(variantsAndElements, Coframe.orgId, Coframe.logger, config.nonce, runtime);
|
|
4535
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4536
|
+
return 'skipped';
|
|
4155
4537
|
}
|
|
4156
4538
|
const cmabDecisions = chooseCmabVariants.getDecisions(experimentsInfo);
|
|
4157
4539
|
startSession(Coframe, isCookiesEnabled, executionStartTime, personalizationHeaders, {
|
|
@@ -4160,29 +4542,77 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4160
4542
|
}, pageUrl);
|
|
4161
4543
|
analytics.resetBroadcasted();
|
|
4162
4544
|
analytics.sendAnalytics();
|
|
4545
|
+
return 'applied';
|
|
4163
4546
|
};
|
|
4547
|
+
return { apply, hasCommitted: () => !isFirstApply };
|
|
4164
4548
|
}
|
|
4165
|
-
async function modifyPage(Coframe, configResult, projectResult, analytics) {
|
|
4549
|
+
async function modifyPage(Coframe, configResult, projectResult, analytics, lifecycle) {
|
|
4550
|
+
var _a;
|
|
4166
4551
|
const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
|
|
4167
4552
|
const { project: cdnProject, personalizationHeaders } = projectResult;
|
|
4553
|
+
const revealCoordinator = (_a = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.revealCoordinator) !== null && _a !== void 0 ? _a : createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
4554
|
+
const runtime = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.runtime;
|
|
4555
|
+
if (runtime && config.waitForHydration) {
|
|
4556
|
+
if (findLastCmd(isPageHydratedCmd)) {
|
|
4557
|
+
runtime.publishHydrated();
|
|
4558
|
+
}
|
|
4559
|
+
else {
|
|
4560
|
+
let stopHydration = () => { };
|
|
4561
|
+
stopHydration = subscribeToCmd(isPageHydratedCmd, () => {
|
|
4562
|
+
stopHydration();
|
|
4563
|
+
runtime.publishHydrated();
|
|
4564
|
+
});
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4168
4567
|
if (!(await awaitHydration(config, Coframe, cdnProject)))
|
|
4169
4568
|
return;
|
|
4569
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4570
|
+
return;
|
|
4170
4571
|
if (cdnProject.tags) {
|
|
4171
4572
|
executeTags(cdnProject.tags, Coframe.logger, config.nonce);
|
|
4172
4573
|
Coframe.status.tags = cdnProject.tags;
|
|
4173
4574
|
}
|
|
4174
|
-
if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce)))
|
|
4575
|
+
if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce, revealCoordinator, () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))))
|
|
4175
4576
|
return;
|
|
4176
4577
|
const laneStore = { claims: readLaneClaims() };
|
|
4177
|
-
const
|
|
4178
|
-
|
|
4179
|
-
if (
|
|
4180
|
-
|
|
4181
|
-
|
|
4578
|
+
const applier = createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore, runtime);
|
|
4579
|
+
const initialUrl = getCurrentPageUrl();
|
|
4580
|
+
if (runtime) {
|
|
4581
|
+
const runtimeUrl = runtime.getState().currentUrl;
|
|
4582
|
+
if (!urlsMatch(runtimeUrl, initialUrl, true))
|
|
4583
|
+
runtime.changeRoute(initialUrl);
|
|
4584
|
+
}
|
|
4585
|
+
const firstDeadline = (integrationStartTime || executionStartTime).getTime() + timeoutMs;
|
|
4586
|
+
const firstResult = await applier.apply(initialUrl, cdnProject, personalizationHeaders, undefined, firstDeadline);
|
|
4587
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4588
|
+
return;
|
|
4589
|
+
const routeChangedDuringInit = firstResult === 'stale-route';
|
|
4590
|
+
if (!routeChangedDuringInit && hasNoVariants(Coframe)) {
|
|
4591
|
+
revealCoordinator.requestReveal('no-variants');
|
|
4182
4592
|
variantRendered();
|
|
4183
4593
|
}
|
|
4184
|
-
|
|
4185
|
-
|
|
4594
|
+
if (!routeChangedDuringInit)
|
|
4595
|
+
revealCoordinator.flushPending();
|
|
4596
|
+
let timingReported = false;
|
|
4597
|
+
const reportTiming = (appliedProject = cdnProject) => {
|
|
4598
|
+
if (timingReported)
|
|
4599
|
+
return;
|
|
4600
|
+
timingReported = true;
|
|
4601
|
+
reportInitTiming(Coframe, appliedProject, integrationStartTime, executionStartTime);
|
|
4602
|
+
};
|
|
4603
|
+
if (!routeChangedDuringInit)
|
|
4604
|
+
reportTiming();
|
|
4605
|
+
const onUrlChange = setupSPANavigation(Coframe, cdnProject, config, previewVariantId, isSessionVariant, runtime, applier, firstDeadline, routeChangedDuringInit ? reportTiming : undefined);
|
|
4606
|
+
const urlNow = getCurrentPageUrl();
|
|
4607
|
+
if (runtime &&
|
|
4608
|
+
onUrlChange &&
|
|
4609
|
+
(routeChangedDuringInit || !urlsMatch(initialUrl, urlNow, true))) {
|
|
4610
|
+
onUrlChange(urlNow, initialUrl, {
|
|
4611
|
+
routeAlreadyRendered: routeChangedDuringInit,
|
|
4612
|
+
}).catch((error) => {
|
|
4613
|
+
logNavigationError(Coframe, 'Applying the route reached during init failed', error);
|
|
4614
|
+
});
|
|
4615
|
+
}
|
|
4186
4616
|
setupFlushInterval(Coframe);
|
|
4187
4617
|
}
|
|
4188
4618
|
|
|
@@ -4510,11 +4940,12 @@ async function resolveFeatureFlag(Coframe, timeoutMs) {
|
|
|
4510
4940
|
return undefined;
|
|
4511
4941
|
return getFeatureFlagValue(cmd);
|
|
4512
4942
|
}
|
|
4513
|
-
async function resolveConfig(Coframe,
|
|
4943
|
+
async function resolveConfig(Coframe, revealActions) {
|
|
4514
4944
|
const logger = Coframe.logger;
|
|
4515
4945
|
const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
|
|
4516
4946
|
if (!configCmd) {
|
|
4517
4947
|
logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
|
|
4948
|
+
revealActions.forceReveal('config-timeout');
|
|
4518
4949
|
return null;
|
|
4519
4950
|
}
|
|
4520
4951
|
const config = configCmd.config;
|
|
@@ -4530,7 +4961,7 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4530
4961
|
configureReplayScriptUrl(config.replayScriptUrl);
|
|
4531
4962
|
const gracefulExit = (msg) => {
|
|
4532
4963
|
console.warn(`[Coframe] ${msg}`);
|
|
4533
|
-
|
|
4964
|
+
revealActions.forceReveal('graceful-exit');
|
|
4534
4965
|
return null;
|
|
4535
4966
|
};
|
|
4536
4967
|
if (config.waitForFeatureFlag) {
|
|
@@ -4543,8 +4974,9 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4543
4974
|
}
|
|
4544
4975
|
}
|
|
4545
4976
|
const projectId = config === null || config === void 0 ? void 0 : config.projectId;
|
|
4977
|
+
const lifecycleMode = isVariantRuntimeLifecycleEnabled(config);
|
|
4546
4978
|
const warnMissingVariant = () => {
|
|
4547
|
-
if (applyVariantChangesStarted) {
|
|
4979
|
+
if (applyVariantChangesStarted && !lifecycleMode) {
|
|
4548
4980
|
logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
|
|
4549
4981
|
}
|
|
4550
4982
|
};
|
|
@@ -4557,14 +4989,15 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4557
4989
|
}
|
|
4558
4990
|
logger.warn('Variant not rendered', 'variant_not_rendered', metadata);
|
|
4559
4991
|
};
|
|
4560
|
-
waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
}
|
|
4565
|
-
|
|
4566
|
-
.
|
|
4567
|
-
|
|
4992
|
+
waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs, QueueName.CFQ, !lifecycleMode).then((cmd) => {
|
|
4993
|
+
if (cmd) {
|
|
4994
|
+
revealActions.requestReveal('legacy-variant-rendered');
|
|
4995
|
+
return;
|
|
4996
|
+
}
|
|
4997
|
+
warnMissingVariant();
|
|
4998
|
+
if (revealActions.getSnapshot().pageRevealed)
|
|
4999
|
+
return;
|
|
5000
|
+
revealActions.forceReveal('config-timeout');
|
|
4568
5001
|
});
|
|
4569
5002
|
const loggedReasons = [];
|
|
4570
5003
|
listenForCmd((cmd) => cmd.emit === 'variantNotRendered', (cmd) => {
|
|
@@ -4578,6 +5011,7 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4578
5011
|
description: 'No Coframe Page ID',
|
|
4579
5012
|
});
|
|
4580
5013
|
variantRendered();
|
|
5014
|
+
revealActions.forceReveal('graceful-exit');
|
|
4581
5015
|
return null;
|
|
4582
5016
|
}
|
|
4583
5017
|
Coframe.projectId = projectId;
|
|
@@ -4749,6 +5183,824 @@ function injectOptions(options) {
|
|
|
4749
5183
|
}
|
|
4750
5184
|
}
|
|
4751
5185
|
|
|
5186
|
+
const TERMINAL_STATUSES = new Set([
|
|
5187
|
+
'completed',
|
|
5188
|
+
'error',
|
|
5189
|
+
'cleaned',
|
|
5190
|
+
]);
|
|
5191
|
+
const DEFAULT_FADE_DURATION_MS = 150;
|
|
5192
|
+
const MAX_INGEST_PER_SESSION = 10;
|
|
5193
|
+
const TRANSITION_LONGHANDS = [
|
|
5194
|
+
'transition-property',
|
|
5195
|
+
'transition-duration',
|
|
5196
|
+
'transition-timing-function',
|
|
5197
|
+
'transition-delay',
|
|
5198
|
+
];
|
|
5199
|
+
const fadeTargets = new Map();
|
|
5200
|
+
const FADE_SAFETY_MS = 5000;
|
|
5201
|
+
function cancelFadeAnimation(fadeTarget) {
|
|
5202
|
+
if (fadeTarget.animationFrame !== null) {
|
|
5203
|
+
cancelAnimationFrame(fadeTarget.animationFrame);
|
|
5204
|
+
}
|
|
5205
|
+
if (fadeTarget.timer)
|
|
5206
|
+
clearTimeout(fadeTarget.timer);
|
|
5207
|
+
fadeTarget.animationFrame = null;
|
|
5208
|
+
fadeTarget.timer = null;
|
|
5209
|
+
}
|
|
5210
|
+
function finishFade(fadeTarget) {
|
|
5211
|
+
if (fadeTargets.get(fadeTarget.target) !== fadeTarget)
|
|
5212
|
+
return;
|
|
5213
|
+
fadeTargets.delete(fadeTarget.target);
|
|
5214
|
+
cancelFadeAnimation(fadeTarget);
|
|
5215
|
+
fadeTarget.target.removeEventListener('transitionend', onFadeTransitionEnd);
|
|
5216
|
+
for (const record of Array.from(fadeTarget.records))
|
|
5217
|
+
record.stop();
|
|
5218
|
+
fadeTarget.records.clear();
|
|
5219
|
+
const { target } = fadeTarget;
|
|
5220
|
+
const style = target.style;
|
|
5221
|
+
style.setProperty('transition', 'none', 'important');
|
|
5222
|
+
style.setProperty('opacity', ...fadeTarget.opacity);
|
|
5223
|
+
void getComputedStyle(target).opacity;
|
|
5224
|
+
style.removeProperty('transition');
|
|
5225
|
+
if (fadeTarget.transition[0]) {
|
|
5226
|
+
style.setProperty('transition', ...fadeTarget.transition);
|
|
5227
|
+
}
|
|
5228
|
+
for (const [name, value, priority] of fadeTarget.longhands) {
|
|
5229
|
+
if (value)
|
|
5230
|
+
style.setProperty(name, value, priority);
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
function onFadeTransitionEnd(event) {
|
|
5234
|
+
const fadeTarget = fadeTargets.get(event.target);
|
|
5235
|
+
if ((fadeTarget === null || fadeTarget === void 0 ? void 0 : fadeTarget.timer) && event.propertyName === 'opacity') {
|
|
5236
|
+
finishFade(fadeTarget);
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
function maybeStartFade(fadeTarget) {
|
|
5240
|
+
if (fadeTarget.animationFrame !== null || fadeTarget.timer)
|
|
5241
|
+
return;
|
|
5242
|
+
let duration = 0;
|
|
5243
|
+
for (const record of Array.from(fadeTarget.records)) {
|
|
5244
|
+
if (!record.ready)
|
|
5245
|
+
return;
|
|
5246
|
+
duration = Math.max(duration, record.duration);
|
|
5247
|
+
}
|
|
5248
|
+
const { target } = fadeTarget;
|
|
5249
|
+
void getComputedStyle(target).opacity;
|
|
5250
|
+
fadeTarget.animationFrame = requestAnimationFrame(() => {
|
|
5251
|
+
fadeTarget.animationFrame = null;
|
|
5252
|
+
if (fadeTargets.get(target) !== fadeTarget)
|
|
5253
|
+
return;
|
|
5254
|
+
target.style.setProperty('transition', `opacity ${duration}ms ease`, 'important');
|
|
5255
|
+
target.style.setProperty('opacity', '1', 'important');
|
|
5256
|
+
fadeTarget.timer = setTimeout(() => finishFade(fadeTarget), duration + 100);
|
|
5257
|
+
});
|
|
5258
|
+
}
|
|
5259
|
+
function removeFadeRecord(record) {
|
|
5260
|
+
const { fadeTarget } = record;
|
|
5261
|
+
if (!fadeTarget.records.delete(record))
|
|
5262
|
+
return;
|
|
5263
|
+
record.stop();
|
|
5264
|
+
if (fadeTarget.records.size === 0)
|
|
5265
|
+
finishFade(fadeTarget);
|
|
5266
|
+
else
|
|
5267
|
+
maybeStartFade(fadeTarget);
|
|
5268
|
+
}
|
|
5269
|
+
function createVariantSession(options) {
|
|
5270
|
+
const listenerStops = new Set();
|
|
5271
|
+
let ingestCount = 0;
|
|
5272
|
+
let ingestLimitReported = false;
|
|
5273
|
+
let metadataDropReported = false;
|
|
5274
|
+
const fades = new Map();
|
|
5275
|
+
const fadeReleases = new Set();
|
|
5276
|
+
const state = {
|
|
5277
|
+
sessionId: options.sessionId,
|
|
5278
|
+
variantId: options.variantId,
|
|
5279
|
+
batchId: options.batchId,
|
|
5280
|
+
routeVersion: options.routeVersion,
|
|
5281
|
+
active: true,
|
|
5282
|
+
status: 'created',
|
|
5283
|
+
ownsPageAntiflicker: false,
|
|
5284
|
+
pageAntiflickerRemoved: false,
|
|
5285
|
+
};
|
|
5286
|
+
const isMutable = () => state.active && !TERMINAL_STATUSES.has(state.status);
|
|
5287
|
+
const notify = () => options.onStateChange(internal);
|
|
5288
|
+
const releaseVisuals = () => {
|
|
5289
|
+
var _a;
|
|
5290
|
+
for (const record of Array.from(fades.values()))
|
|
5291
|
+
removeFadeRecord(record);
|
|
5292
|
+
(_a = options.releaseVisuals) === null || _a === void 0 ? void 0 : _a.call(options, internal);
|
|
5293
|
+
};
|
|
5294
|
+
const fadeIn = (target, durationMs = DEFAULT_FADE_DURATION_MS) => {
|
|
5295
|
+
if (fades.has(target))
|
|
5296
|
+
return;
|
|
5297
|
+
const duration = typeof durationMs === 'number' && durationMs - durationMs === 0
|
|
5298
|
+
? Math.max(0, durationMs)
|
|
5299
|
+
: DEFAULT_FADE_DURATION_MS;
|
|
5300
|
+
let fadeTarget = fadeTargets.get(target);
|
|
5301
|
+
if (!fadeTarget) {
|
|
5302
|
+
const style = target.style;
|
|
5303
|
+
fadeTarget = {
|
|
5304
|
+
target,
|
|
5305
|
+
opacity: [
|
|
5306
|
+
style.getPropertyValue('opacity'),
|
|
5307
|
+
style.getPropertyPriority('opacity'),
|
|
5308
|
+
],
|
|
5309
|
+
transition: [
|
|
5310
|
+
style.getPropertyValue('transition'),
|
|
5311
|
+
style.getPropertyPriority('transition'),
|
|
5312
|
+
],
|
|
5313
|
+
longhands: TRANSITION_LONGHANDS.map((name) => [
|
|
5314
|
+
name,
|
|
5315
|
+
style.getPropertyValue(name),
|
|
5316
|
+
style.getPropertyPriority(name),
|
|
5317
|
+
]),
|
|
5318
|
+
records: new Set(),
|
|
5319
|
+
animationFrame: null,
|
|
5320
|
+
timer: null,
|
|
5321
|
+
};
|
|
5322
|
+
fadeTargets.set(target, fadeTarget);
|
|
5323
|
+
target.addEventListener('transitionend', onFadeTransitionEnd);
|
|
5324
|
+
}
|
|
5325
|
+
cancelFadeAnimation(fadeTarget);
|
|
5326
|
+
target.style.setProperty('transition', 'none', 'important');
|
|
5327
|
+
target.style.setProperty('opacity', '0', 'important');
|
|
5328
|
+
let pageRevealed = false;
|
|
5329
|
+
let released = false;
|
|
5330
|
+
let stopPageListener = () => { };
|
|
5331
|
+
let safetyTimer = null;
|
|
5332
|
+
const update = () => {
|
|
5333
|
+
record.ready = pageRevealed && released;
|
|
5334
|
+
if (record.ready)
|
|
5335
|
+
maybeStartFade(record.fadeTarget);
|
|
5336
|
+
};
|
|
5337
|
+
const release = () => {
|
|
5338
|
+
released = true;
|
|
5339
|
+
if (safetyTimer)
|
|
5340
|
+
clearTimeout(safetyTimer);
|
|
5341
|
+
safetyTimer = null;
|
|
5342
|
+
update();
|
|
5343
|
+
};
|
|
5344
|
+
const record = {
|
|
5345
|
+
fadeTarget,
|
|
5346
|
+
duration,
|
|
5347
|
+
ready: false,
|
|
5348
|
+
stop: () => {
|
|
5349
|
+
stopPageListener();
|
|
5350
|
+
if (safetyTimer)
|
|
5351
|
+
clearTimeout(safetyTimer);
|
|
5352
|
+
safetyTimer = null;
|
|
5353
|
+
fadeReleases.delete(release);
|
|
5354
|
+
if (fades.get(target) === record)
|
|
5355
|
+
fades.delete(target);
|
|
5356
|
+
},
|
|
5357
|
+
};
|
|
5358
|
+
fadeTarget.records.add(record);
|
|
5359
|
+
fades.set(target, record);
|
|
5360
|
+
fadeReleases.add(release);
|
|
5361
|
+
safetyTimer = setTimeout(release, FADE_SAFETY_MS);
|
|
5362
|
+
stopPageListener = subscribe('pageRevealed', () => {
|
|
5363
|
+
pageRevealed = true;
|
|
5364
|
+
update();
|
|
5365
|
+
}, true);
|
|
5366
|
+
if (options.getPageState().pageRevealed) {
|
|
5367
|
+
pageRevealed = true;
|
|
5368
|
+
update();
|
|
5369
|
+
}
|
|
5370
|
+
};
|
|
5371
|
+
const subscribe = (event, handler, once) => {
|
|
5372
|
+
if (!state.active)
|
|
5373
|
+
return () => { };
|
|
5374
|
+
const stopSubscription = options.subscribePage(event, handler, once);
|
|
5375
|
+
if (!state.active) {
|
|
5376
|
+
stopSubscription();
|
|
5377
|
+
return () => { };
|
|
5378
|
+
}
|
|
5379
|
+
const stop = () => {
|
|
5380
|
+
listenerStops.delete(stop);
|
|
5381
|
+
stopSubscription();
|
|
5382
|
+
};
|
|
5383
|
+
listenerStops.add(stop);
|
|
5384
|
+
return stop;
|
|
5385
|
+
};
|
|
5386
|
+
const facade = {
|
|
5387
|
+
getPageState: () => ({ ...options.getPageState() }),
|
|
5388
|
+
getState: () => ({ ...state }),
|
|
5389
|
+
getVariantId: () => state.variantId,
|
|
5390
|
+
onPage: (event, handler) => subscribe(event, handler, false),
|
|
5391
|
+
oncePage: (event, handler) => subscribe(event, handler, true),
|
|
5392
|
+
ready() {
|
|
5393
|
+
if (!isMutable() || state.status !== 'created')
|
|
5394
|
+
return;
|
|
5395
|
+
state.status = 'ready';
|
|
5396
|
+
notify();
|
|
5397
|
+
},
|
|
5398
|
+
takePageAntiflickerOwnership() {
|
|
5399
|
+
if (!isMutable() || state.ownsPageAntiflicker)
|
|
5400
|
+
return;
|
|
5401
|
+
state.ownsPageAntiflicker = true;
|
|
5402
|
+
state.status = 'rendering';
|
|
5403
|
+
notify();
|
|
5404
|
+
},
|
|
5405
|
+
fadeIn(target, durationMs) {
|
|
5406
|
+
if (!isMutable() || !target)
|
|
5407
|
+
return;
|
|
5408
|
+
if (options.fadeIn)
|
|
5409
|
+
options.fadeIn(internal, target, durationMs);
|
|
5410
|
+
else
|
|
5411
|
+
fadeIn(target, durationMs);
|
|
5412
|
+
},
|
|
5413
|
+
ingestApplied(payload) {
|
|
5414
|
+
var _a, _b, _c;
|
|
5415
|
+
if (!isMutable())
|
|
5416
|
+
return;
|
|
5417
|
+
if (ingestCount >= MAX_INGEST_PER_SESSION) {
|
|
5418
|
+
if (!ingestLimitReported) {
|
|
5419
|
+
ingestLimitReported = true;
|
|
5420
|
+
(_a = options.onIngestLimit) === null || _a === void 0 ? void 0 : _a.call(options, internal);
|
|
5421
|
+
}
|
|
5422
|
+
return;
|
|
5423
|
+
}
|
|
5424
|
+
ingestCount += 1;
|
|
5425
|
+
const metadataDropped = (_b = options.ingestApplied) === null || _b === void 0 ? void 0 : _b.call(options, internal, payload);
|
|
5426
|
+
if (metadataDropped && !metadataDropReported) {
|
|
5427
|
+
metadataDropReported = true;
|
|
5428
|
+
(_c = options.onMetadataDropped) === null || _c === void 0 ? void 0 : _c.call(options, internal);
|
|
5429
|
+
}
|
|
5430
|
+
},
|
|
5431
|
+
complete() {
|
|
5432
|
+
if (!isMutable())
|
|
5433
|
+
return;
|
|
5434
|
+
state.status = 'completed';
|
|
5435
|
+
for (const release of Array.from(fadeReleases))
|
|
5436
|
+
release();
|
|
5437
|
+
fadeReleases.clear();
|
|
5438
|
+
options.emitLegacyVariantRendered();
|
|
5439
|
+
notify();
|
|
5440
|
+
},
|
|
5441
|
+
error(error) {
|
|
5442
|
+
var _a;
|
|
5443
|
+
if (!isMutable())
|
|
5444
|
+
return;
|
|
5445
|
+
state.status = 'error';
|
|
5446
|
+
releaseVisuals();
|
|
5447
|
+
(_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, internal, error);
|
|
5448
|
+
options.emitLegacyVariantRendered();
|
|
5449
|
+
notify();
|
|
5450
|
+
},
|
|
5451
|
+
cleanup() {
|
|
5452
|
+
if (state.status === 'cleaned')
|
|
5453
|
+
return;
|
|
5454
|
+
state.active = false;
|
|
5455
|
+
state.status = 'cleaned';
|
|
5456
|
+
for (const stop of Array.from(listenerStops))
|
|
5457
|
+
stop();
|
|
5458
|
+
releaseVisuals();
|
|
5459
|
+
notify();
|
|
5460
|
+
},
|
|
5461
|
+
};
|
|
5462
|
+
const internal = {
|
|
5463
|
+
facade: Object.freeze(facade),
|
|
5464
|
+
getState: () => ({ ...state }),
|
|
5465
|
+
isSettledForReveal: () => state.status === 'ready' ||
|
|
5466
|
+
state.status === 'rendering' ||
|
|
5467
|
+
TERMINAL_STATUSES.has(state.status),
|
|
5468
|
+
markPageRevealed() {
|
|
5469
|
+
if (state.pageAntiflickerRemoved)
|
|
5470
|
+
return;
|
|
5471
|
+
state.pageAntiflickerRemoved = true;
|
|
5472
|
+
},
|
|
5473
|
+
invalidate() {
|
|
5474
|
+
facade.cleanup();
|
|
5475
|
+
},
|
|
5476
|
+
};
|
|
5477
|
+
return internal;
|
|
5478
|
+
}
|
|
5479
|
+
|
|
5480
|
+
const DEFAULT_BATCH_TIMEOUT_MS = 1000;
|
|
5481
|
+
function createPageRuntime(options) {
|
|
5482
|
+
var _a, _b, _c;
|
|
5483
|
+
const createId = (_a = options.createId) !== null && _a !== void 0 ? _a : generateUUID;
|
|
5484
|
+
const batchTimeoutMs = (_b = options.batchTimeoutMs) !== null && _b !== void 0 ? _b : DEFAULT_BATCH_TIMEOUT_MS;
|
|
5485
|
+
const revealDeadline = Date.now() + batchTimeoutMs;
|
|
5486
|
+
const timeUntilDeadline = () => Math.max(0, revealDeadline - Date.now());
|
|
5487
|
+
const listeners = new Map();
|
|
5488
|
+
const state = {
|
|
5489
|
+
sdkReady: false,
|
|
5490
|
+
hydrated: (_c = options.initialHydrated) !== null && _c !== void 0 ? _c : false,
|
|
5491
|
+
routeVersion: 0,
|
|
5492
|
+
currentUrl: options.currentUrl,
|
|
5493
|
+
pageRevealed: options.revealCoordinator.getSnapshot().pageRevealed,
|
|
5494
|
+
};
|
|
5495
|
+
let activeBatch = null;
|
|
5496
|
+
let handoffBlockerId = null;
|
|
5497
|
+
let handoffTimeout = null;
|
|
5498
|
+
let destroyed = false;
|
|
5499
|
+
const clearHandoff = () => {
|
|
5500
|
+
handoffBlockerId = null;
|
|
5501
|
+
if (handoffTimeout)
|
|
5502
|
+
clearTimeout(handoffTimeout);
|
|
5503
|
+
handoffTimeout = null;
|
|
5504
|
+
};
|
|
5505
|
+
const callListener = (event, handler, payload) => {
|
|
5506
|
+
var _a;
|
|
5507
|
+
try {
|
|
5508
|
+
handler(payload);
|
|
5509
|
+
}
|
|
5510
|
+
catch (error) {
|
|
5511
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime listener failed', {
|
|
5512
|
+
event,
|
|
5513
|
+
error: describeError(error),
|
|
5514
|
+
});
|
|
5515
|
+
}
|
|
5516
|
+
};
|
|
5517
|
+
const emit = (event, payload) => {
|
|
5518
|
+
const handlers = listeners.get(event);
|
|
5519
|
+
if (!handlers)
|
|
5520
|
+
return;
|
|
5521
|
+
for (const handler of Array.from(handlers)) {
|
|
5522
|
+
if (handlers.has(handler))
|
|
5523
|
+
callListener(event, handler, payload);
|
|
5524
|
+
}
|
|
5525
|
+
};
|
|
5526
|
+
const on = (event, handler) => {
|
|
5527
|
+
var _a;
|
|
5528
|
+
const handlers = (_a = listeners.get(event)) !== null && _a !== void 0 ? _a : new Set();
|
|
5529
|
+
const entry = (payload) => handler(payload);
|
|
5530
|
+
handlers.add(entry);
|
|
5531
|
+
listeners.set(event, handlers);
|
|
5532
|
+
return () => {
|
|
5533
|
+
handlers.delete(entry);
|
|
5534
|
+
if (handlers.size === 0 && listeners.get(event) === handlers) {
|
|
5535
|
+
listeners.delete(event);
|
|
5536
|
+
}
|
|
5537
|
+
};
|
|
5538
|
+
};
|
|
5539
|
+
const stickySatisfied = (event) => {
|
|
5540
|
+
if (event === 'sdkReady')
|
|
5541
|
+
return state.sdkReady;
|
|
5542
|
+
if (event === 'hydrated')
|
|
5543
|
+
return state.hydrated;
|
|
5544
|
+
if (event === 'pageRevealed')
|
|
5545
|
+
return state.pageRevealed;
|
|
5546
|
+
return false;
|
|
5547
|
+
};
|
|
5548
|
+
const stickyPayload = (event) => event === 'pageRevealed'
|
|
5549
|
+
? {
|
|
5550
|
+
...state,
|
|
5551
|
+
reason: options.revealCoordinator.getSnapshot().revealedReason,
|
|
5552
|
+
}
|
|
5553
|
+
: { ...state };
|
|
5554
|
+
const once = (event, handler) => {
|
|
5555
|
+
if (stickySatisfied(event)) {
|
|
5556
|
+
callListener(event, handler, stickyPayload(event));
|
|
5557
|
+
return () => { };
|
|
5558
|
+
}
|
|
5559
|
+
let stop = () => { };
|
|
5560
|
+
stop = on(event, (payload) => {
|
|
5561
|
+
stop();
|
|
5562
|
+
handler(payload);
|
|
5563
|
+
});
|
|
5564
|
+
return stop;
|
|
5565
|
+
};
|
|
5566
|
+
const clearBatchTimeout = (batch) => {
|
|
5567
|
+
if (!batch.timeout)
|
|
5568
|
+
return;
|
|
5569
|
+
clearTimeout(batch.timeout);
|
|
5570
|
+
batch.timeout = null;
|
|
5571
|
+
};
|
|
5572
|
+
const revealFromRuntime = (batchId, reveal) => {
|
|
5573
|
+
var _a;
|
|
5574
|
+
try {
|
|
5575
|
+
reveal();
|
|
5576
|
+
}
|
|
5577
|
+
catch (error) {
|
|
5578
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime page reveal failed', {
|
|
5579
|
+
error: describeError(error),
|
|
5580
|
+
...(batchId ? { batchId } : {}),
|
|
5581
|
+
routeVersion: state.routeVersion,
|
|
5582
|
+
});
|
|
5583
|
+
}
|
|
5584
|
+
};
|
|
5585
|
+
const forceRevealOnTimeout = (batchId) => revealFromRuntime(batchId, () => options.revealCoordinator.forceReveal('batch-timeout'));
|
|
5586
|
+
const releaseBatch = (batch) => {
|
|
5587
|
+
if (batch.released)
|
|
5588
|
+
return;
|
|
5589
|
+
batch.released = true;
|
|
5590
|
+
clearBatchTimeout(batch);
|
|
5591
|
+
revealFromRuntime(batch.batchId, () => options.revealCoordinator.release(batch.batchId, 'batch-settled'));
|
|
5592
|
+
};
|
|
5593
|
+
const warnForSession = (session, message) => {
|
|
5594
|
+
var _a;
|
|
5595
|
+
const sessionState = session.getState();
|
|
5596
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, message, {
|
|
5597
|
+
variantId: sessionState.variantId,
|
|
5598
|
+
batchId: sessionState.batchId,
|
|
5599
|
+
});
|
|
5600
|
+
};
|
|
5601
|
+
const maybeReleaseBatch = (batch) => {
|
|
5602
|
+
if (activeBatch !== batch ||
|
|
5603
|
+
batch.released ||
|
|
5604
|
+
!batch.closed ||
|
|
5605
|
+
batch.sessions.size < batch.expectedSessionCount)
|
|
5606
|
+
return;
|
|
5607
|
+
if (Array.from(batch.sessions.values()).every((session) => session.isSettledForReveal())) {
|
|
5608
|
+
releaseBatch(batch);
|
|
5609
|
+
}
|
|
5610
|
+
};
|
|
5611
|
+
const warnUnsettled = (batch, reason) => {
|
|
5612
|
+
var _a;
|
|
5613
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime batch timed out', {
|
|
5614
|
+
batchId: batch.batchId,
|
|
5615
|
+
routeVersion: batch.routeVersion,
|
|
5616
|
+
reason,
|
|
5617
|
+
expectedSessionCount: batch.expectedSessionCount,
|
|
5618
|
+
createdSessionCount: batch.sessions.size,
|
|
5619
|
+
unsettledVariantIds: Array.from(batch.sessions.values())
|
|
5620
|
+
.filter((session) => !session.isSettledForReveal())
|
|
5621
|
+
.map((session) => session.getState().variantId),
|
|
5622
|
+
});
|
|
5623
|
+
};
|
|
5624
|
+
const invalidateBatch = (batchId) => {
|
|
5625
|
+
if (!activeBatch || activeBatch.batchId !== batchId)
|
|
5626
|
+
return;
|
|
5627
|
+
const batch = activeBatch;
|
|
5628
|
+
activeBatch = null;
|
|
5629
|
+
clearBatchTimeout(batch);
|
|
5630
|
+
for (const session of batch.sessions.values())
|
|
5631
|
+
session.invalidate();
|
|
5632
|
+
};
|
|
5633
|
+
const coordinatorStop = options.revealCoordinator.onRevealed((reason) => {
|
|
5634
|
+
if (state.pageRevealed)
|
|
5635
|
+
return;
|
|
5636
|
+
if (activeBatch && !activeBatch.released && reason !== 'batch-settled') {
|
|
5637
|
+
warnUnsettled(activeBatch, reason);
|
|
5638
|
+
}
|
|
5639
|
+
state.pageRevealed = true;
|
|
5640
|
+
clearHandoff();
|
|
5641
|
+
if (activeBatch) {
|
|
5642
|
+
activeBatch.released = true;
|
|
5643
|
+
clearBatchTimeout(activeBatch);
|
|
5644
|
+
for (const session of activeBatch.sessions.values()) {
|
|
5645
|
+
session.markPageRevealed();
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
emit('pageRevealed', { ...state, reason });
|
|
5649
|
+
});
|
|
5650
|
+
const runtime = {
|
|
5651
|
+
getState: () => ({ ...state }),
|
|
5652
|
+
on,
|
|
5653
|
+
once,
|
|
5654
|
+
publishSdkReady() {
|
|
5655
|
+
if (state.sdkReady)
|
|
5656
|
+
return;
|
|
5657
|
+
state.sdkReady = true;
|
|
5658
|
+
emit('sdkReady', { ...state });
|
|
5659
|
+
},
|
|
5660
|
+
publishHydrated() {
|
|
5661
|
+
if (state.hydrated)
|
|
5662
|
+
return;
|
|
5663
|
+
state.hydrated = true;
|
|
5664
|
+
emit('hydrated', { ...state });
|
|
5665
|
+
},
|
|
5666
|
+
settleRoute() {
|
|
5667
|
+
if (destroyed)
|
|
5668
|
+
return;
|
|
5669
|
+
if (handoffBlockerId) {
|
|
5670
|
+
const batchId = handoffBlockerId;
|
|
5671
|
+
clearHandoff();
|
|
5672
|
+
revealFromRuntime(batchId, () => options.revealCoordinator.release(batchId, 'no-variants'));
|
|
5673
|
+
return;
|
|
5674
|
+
}
|
|
5675
|
+
if (!state.pageRevealed && !activeBatch) {
|
|
5676
|
+
revealFromRuntime(null, () => options.revealCoordinator.requestReveal('no-variants'));
|
|
5677
|
+
}
|
|
5678
|
+
},
|
|
5679
|
+
updateUrl(currentUrl) {
|
|
5680
|
+
if (!destroyed)
|
|
5681
|
+
state.currentUrl = currentUrl;
|
|
5682
|
+
},
|
|
5683
|
+
changeRoute(currentUrl) {
|
|
5684
|
+
if (destroyed)
|
|
5685
|
+
return;
|
|
5686
|
+
if (activeBatch) {
|
|
5687
|
+
const { batchId } = activeBatch;
|
|
5688
|
+
invalidateBatch(batchId);
|
|
5689
|
+
if (!state.pageRevealed) {
|
|
5690
|
+
handoffBlockerId = batchId;
|
|
5691
|
+
const abandonedRouteVersion = state.routeVersion;
|
|
5692
|
+
if (!handoffTimeout) {
|
|
5693
|
+
handoffTimeout = setTimeout(() => {
|
|
5694
|
+
var _a;
|
|
5695
|
+
if (!handoffBlockerId)
|
|
5696
|
+
return;
|
|
5697
|
+
const handedOverBatchId = handoffBlockerId;
|
|
5698
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime route handoff timed out', {
|
|
5699
|
+
routeVersion: abandonedRouteVersion,
|
|
5700
|
+
});
|
|
5701
|
+
clearHandoff();
|
|
5702
|
+
forceRevealOnTimeout(handedOverBatchId);
|
|
5703
|
+
}, timeUntilDeadline());
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
}
|
|
5707
|
+
state.routeVersion += 1;
|
|
5708
|
+
state.currentUrl = currentUrl;
|
|
5709
|
+
},
|
|
5710
|
+
beginBatch(input) {
|
|
5711
|
+
if (destroyed || input.expectedSessionCount === 0)
|
|
5712
|
+
return null;
|
|
5713
|
+
if (activeBatch)
|
|
5714
|
+
invalidateBatch(activeBatch.batchId);
|
|
5715
|
+
clearHandoff();
|
|
5716
|
+
const batch = {
|
|
5717
|
+
batchId: createId(),
|
|
5718
|
+
routeVersion: state.routeVersion,
|
|
5719
|
+
expectedSessionCount: input.expectedSessionCount,
|
|
5720
|
+
closed: false,
|
|
5721
|
+
released: state.pageRevealed,
|
|
5722
|
+
sessions: new Map(),
|
|
5723
|
+
timeout: null,
|
|
5724
|
+
};
|
|
5725
|
+
activeBatch = batch;
|
|
5726
|
+
if (!state.pageRevealed &&
|
|
5727
|
+
options.revealCoordinator.block(batch.batchId)) {
|
|
5728
|
+
batch.timeout = setTimeout(() => {
|
|
5729
|
+
if (activeBatch !== batch || batch.released)
|
|
5730
|
+
return;
|
|
5731
|
+
warnUnsettled(batch, 'batch-timeout');
|
|
5732
|
+
batch.released = true;
|
|
5733
|
+
forceRevealOnTimeout(batch.batchId);
|
|
5734
|
+
}, timeUntilDeadline());
|
|
5735
|
+
}
|
|
5736
|
+
return { batchId: batch.batchId, routeVersion: batch.routeVersion };
|
|
5737
|
+
},
|
|
5738
|
+
createVariantSession(input) {
|
|
5739
|
+
var _a;
|
|
5740
|
+
const batch = activeBatch;
|
|
5741
|
+
if (!batch ||
|
|
5742
|
+
batch.batchId !== input.batchId ||
|
|
5743
|
+
batch.routeVersion !== state.routeVersion ||
|
|
5744
|
+
(batch.released && !state.pageRevealed))
|
|
5745
|
+
return null;
|
|
5746
|
+
const session = createVariantSession({
|
|
5747
|
+
sessionId: createId(),
|
|
5748
|
+
variantId: input.variantId,
|
|
5749
|
+
batchId: batch.batchId,
|
|
5750
|
+
routeVersion: batch.routeVersion,
|
|
5751
|
+
getPageState: () => ({ ...state }),
|
|
5752
|
+
subscribePage: (event, handler, onlyOnce) => {
|
|
5753
|
+
if (onlyOnce)
|
|
5754
|
+
return once(event, handler);
|
|
5755
|
+
if (stickySatisfied(event)) {
|
|
5756
|
+
callListener(event, handler, stickyPayload(event));
|
|
5757
|
+
}
|
|
5758
|
+
return on(event, handler);
|
|
5759
|
+
},
|
|
5760
|
+
onStateChange: () => maybeReleaseBatch(batch),
|
|
5761
|
+
emitLegacyVariantRendered: (_a = options.emitLegacyVariantRendered) !== null && _a !== void 0 ? _a : (() => { }),
|
|
5762
|
+
ingestApplied: (currentSession, payload) => {
|
|
5763
|
+
var _a, _b;
|
|
5764
|
+
return (_b = (_a = options.onVariantApplied) === null || _a === void 0 ? void 0 : _a.call(options, { ...state }, currentSession.getState(), payload)) !== null && _b !== void 0 ? _b : false;
|
|
5765
|
+
},
|
|
5766
|
+
onIngestLimit: (currentSession) => warnForSession(currentSession, 'Variant ingestApplied limit reached'),
|
|
5767
|
+
onMetadataDropped: (currentSession) => warnForSession(currentSession, 'Variant ingestApplied metadata dropped'),
|
|
5768
|
+
onError: (currentSession, error) => {
|
|
5769
|
+
var _a;
|
|
5770
|
+
const sessionState = currentSession.getState();
|
|
5771
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime session failed', {
|
|
5772
|
+
error: describeError(error),
|
|
5773
|
+
variantId: sessionState.variantId,
|
|
5774
|
+
sessionId: sessionState.sessionId,
|
|
5775
|
+
batchId: sessionState.batchId,
|
|
5776
|
+
routeVersion: sessionState.routeVersion,
|
|
5777
|
+
currentUrl: state.currentUrl,
|
|
5778
|
+
});
|
|
5779
|
+
},
|
|
5780
|
+
});
|
|
5781
|
+
if (state.pageRevealed)
|
|
5782
|
+
session.markPageRevealed();
|
|
5783
|
+
batch.sessions.set(session.getState().sessionId, session);
|
|
5784
|
+
maybeReleaseBatch(batch);
|
|
5785
|
+
return session;
|
|
5786
|
+
},
|
|
5787
|
+
closeBatch(batchId) {
|
|
5788
|
+
if (!activeBatch || activeBatch.batchId !== batchId)
|
|
5789
|
+
return;
|
|
5790
|
+
activeBatch.closed = true;
|
|
5791
|
+
maybeReleaseBatch(activeBatch);
|
|
5792
|
+
},
|
|
5793
|
+
invalidateBatch,
|
|
5794
|
+
invalidateAllSessions() {
|
|
5795
|
+
if (activeBatch)
|
|
5796
|
+
invalidateBatch(activeBatch.batchId);
|
|
5797
|
+
},
|
|
5798
|
+
getDebugSnapshot() {
|
|
5799
|
+
return {
|
|
5800
|
+
page: { ...state },
|
|
5801
|
+
activeBatch: activeBatch
|
|
5802
|
+
? {
|
|
5803
|
+
batchId: activeBatch.batchId,
|
|
5804
|
+
routeVersion: activeBatch.routeVersion,
|
|
5805
|
+
expectedSessionCount: activeBatch.expectedSessionCount,
|
|
5806
|
+
closed: activeBatch.closed,
|
|
5807
|
+
released: activeBatch.released,
|
|
5808
|
+
sessionStates: Array.from(activeBatch.sessions.values()).map((session) => session.getState()),
|
|
5809
|
+
}
|
|
5810
|
+
: null,
|
|
5811
|
+
};
|
|
5812
|
+
},
|
|
5813
|
+
isDestroyed: () => destroyed,
|
|
5814
|
+
destroy() {
|
|
5815
|
+
destroyed = true;
|
|
5816
|
+
const blockerIds = [handoffBlockerId, activeBatch === null || activeBatch === void 0 ? void 0 : activeBatch.batchId];
|
|
5817
|
+
if (activeBatch)
|
|
5818
|
+
invalidateBatch(activeBatch.batchId);
|
|
5819
|
+
clearHandoff();
|
|
5820
|
+
for (const blockerId of blockerIds) {
|
|
5821
|
+
if (blockerId)
|
|
5822
|
+
options.revealCoordinator.unblock(blockerId);
|
|
5823
|
+
}
|
|
5824
|
+
coordinatorStop();
|
|
5825
|
+
listeners.clear();
|
|
5826
|
+
},
|
|
5827
|
+
};
|
|
5828
|
+
return runtime;
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5831
|
+
const MAX_TARGET_SELECTORS = 20;
|
|
5832
|
+
const MAX_SELECTOR_LENGTH = 500;
|
|
5833
|
+
const MAX_METADATA_JSON_BYTES = 4096;
|
|
5834
|
+
const MAX_METADATA_VISITS = 20000;
|
|
5835
|
+
const MAX_SELECTOR_ENTRIES_SCANNED = 200;
|
|
5836
|
+
function utf8ByteLength(value) {
|
|
5837
|
+
let bytes = 0;
|
|
5838
|
+
for (let i = 0; i < value.length; i++) {
|
|
5839
|
+
const code = value.charCodeAt(i);
|
|
5840
|
+
if (code < 0x80)
|
|
5841
|
+
bytes += 1;
|
|
5842
|
+
else if (code < 0x800)
|
|
5843
|
+
bytes += 2;
|
|
5844
|
+
else if (code >= 0xd800 && code <= 0xdbff) {
|
|
5845
|
+
const next = value.charCodeAt(i + 1);
|
|
5846
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
5847
|
+
bytes += 4;
|
|
5848
|
+
i++;
|
|
5849
|
+
}
|
|
5850
|
+
else {
|
|
5851
|
+
bytes += 3;
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
else
|
|
5855
|
+
bytes += 3;
|
|
5856
|
+
}
|
|
5857
|
+
return bytes;
|
|
5858
|
+
}
|
|
5859
|
+
function isBoxedString(value) {
|
|
5860
|
+
if (!value || typeof value !== 'object')
|
|
5861
|
+
return false;
|
|
5862
|
+
try {
|
|
5863
|
+
String.prototype.valueOf.call(value);
|
|
5864
|
+
return true;
|
|
5865
|
+
}
|
|
5866
|
+
catch (_a) {
|
|
5867
|
+
return false;
|
|
5868
|
+
}
|
|
5869
|
+
}
|
|
5870
|
+
function sanitizeMetadata(payload) {
|
|
5871
|
+
try {
|
|
5872
|
+
const rawMetadata = payload.metadata;
|
|
5873
|
+
if (rawMetadata === undefined || rawMetadata === null)
|
|
5874
|
+
return {};
|
|
5875
|
+
if (typeof rawMetadata !== 'object' || Array.isArray(rawMetadata)) {
|
|
5876
|
+
return null;
|
|
5877
|
+
}
|
|
5878
|
+
let minimumLength = 0;
|
|
5879
|
+
let visits = 0;
|
|
5880
|
+
const json = JSON.stringify(rawMetadata, function (key, value) {
|
|
5881
|
+
visits += 1;
|
|
5882
|
+
if (visits > MAX_METADATA_VISITS) {
|
|
5883
|
+
throw new Error('metadata too large');
|
|
5884
|
+
}
|
|
5885
|
+
const isObjectProperty = visits > 1 && !Array.isArray(this);
|
|
5886
|
+
if (isObjectProperty &&
|
|
5887
|
+
(value === undefined ||
|
|
5888
|
+
typeof value === 'function' ||
|
|
5889
|
+
typeof value === 'symbol')) {
|
|
5890
|
+
return value;
|
|
5891
|
+
}
|
|
5892
|
+
if (isObjectProperty)
|
|
5893
|
+
minimumLength += key.length + 3;
|
|
5894
|
+
const written = isBoxedString(value) ? String(value) : value;
|
|
5895
|
+
minimumLength += typeof written === 'string' ? written.length + 2 : 1;
|
|
5896
|
+
if (minimumLength > MAX_METADATA_JSON_BYTES) {
|
|
5897
|
+
throw new Error('metadata too large');
|
|
5898
|
+
}
|
|
5899
|
+
return written;
|
|
5900
|
+
});
|
|
5901
|
+
if (!json ||
|
|
5902
|
+
json.length > MAX_METADATA_JSON_BYTES ||
|
|
5903
|
+
utf8ByteLength(json) > MAX_METADATA_JSON_BYTES) {
|
|
5904
|
+
return null;
|
|
5905
|
+
}
|
|
5906
|
+
const copy = JSON.parse(json);
|
|
5907
|
+
return copy && typeof copy === 'object' && !Array.isArray(copy)
|
|
5908
|
+
? copy
|
|
5909
|
+
: null;
|
|
5910
|
+
}
|
|
5911
|
+
catch (_a) {
|
|
5912
|
+
return null;
|
|
5913
|
+
}
|
|
5914
|
+
}
|
|
5915
|
+
function sanitizeSelectors(payload) {
|
|
5916
|
+
try {
|
|
5917
|
+
const selectors = payload.targetSelectors;
|
|
5918
|
+
if (!Array.isArray(selectors))
|
|
5919
|
+
return [];
|
|
5920
|
+
const kept = [];
|
|
5921
|
+
const scanned = Math.min(selectors.length, MAX_SELECTOR_ENTRIES_SCANNED);
|
|
5922
|
+
for (let i = 0; i < scanned; i++) {
|
|
5923
|
+
let selector;
|
|
5924
|
+
try {
|
|
5925
|
+
selector = selectors[i];
|
|
5926
|
+
}
|
|
5927
|
+
catch (_a) {
|
|
5928
|
+
continue;
|
|
5929
|
+
}
|
|
5930
|
+
if (typeof selector === 'string' &&
|
|
5931
|
+
selector.length > 0 &&
|
|
5932
|
+
selector.length <= MAX_SELECTOR_LENGTH) {
|
|
5933
|
+
kept.push(selector);
|
|
5934
|
+
if (kept.length === MAX_TARGET_SELECTORS)
|
|
5935
|
+
break;
|
|
5936
|
+
}
|
|
5937
|
+
}
|
|
5938
|
+
return kept;
|
|
5939
|
+
}
|
|
5940
|
+
catch (_b) {
|
|
5941
|
+
return [];
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
function sanitizePayload(payloadInput) {
|
|
5945
|
+
if (!payloadInput ||
|
|
5946
|
+
typeof payloadInput !== 'object' ||
|
|
5947
|
+
Array.isArray(payloadInput)) {
|
|
5948
|
+
const given = payloadInput !== undefined && payloadInput !== null;
|
|
5949
|
+
return { metadata: {}, metadataDropped: given, targetSelectors: [] };
|
|
5950
|
+
}
|
|
5951
|
+
const payload = payloadInput;
|
|
5952
|
+
const metadata = sanitizeMetadata(payload);
|
|
5953
|
+
return {
|
|
5954
|
+
metadata: metadata || {},
|
|
5955
|
+
metadataDropped: metadata === null,
|
|
5956
|
+
targetSelectors: sanitizeSelectors(payload),
|
|
5957
|
+
};
|
|
5958
|
+
}
|
|
5959
|
+
function ingestVariantApplied(coframe, pageState, sessionState, payloadInput) {
|
|
5960
|
+
const { metadata, metadataDropped, targetSelectors } = sanitizePayload(payloadInput);
|
|
5961
|
+
const personalizationHeaders = coframe.personalizationHeaders;
|
|
5962
|
+
const event = {
|
|
5963
|
+
type: 'variant_applied',
|
|
5964
|
+
project_id: coframe.projectId || '',
|
|
5965
|
+
session_id: coframe.sessionId || '',
|
|
5966
|
+
time: new Date(),
|
|
5967
|
+
url: getCurrentPageUrl(),
|
|
5968
|
+
user_token: coframe.userToken || '',
|
|
5969
|
+
org_id: coframe.orgId || '',
|
|
5970
|
+
variant_id: sessionState.variantId,
|
|
5971
|
+
content: {
|
|
5972
|
+
metadata: {
|
|
5973
|
+
batch_id: sessionState.batchId,
|
|
5974
|
+
route_version: pageState.routeVersion,
|
|
5975
|
+
current_url: pageState.currentUrl,
|
|
5976
|
+
...(personalizationHeaders
|
|
5977
|
+
? { personalization_headers: { ...personalizationHeaders } }
|
|
5978
|
+
: {}),
|
|
5979
|
+
...(targetSelectors.length
|
|
5980
|
+
? { target_selectors: targetSelectors }
|
|
5981
|
+
: {}),
|
|
5982
|
+
...(Object.keys(metadata).length ? { custom_data: metadata } : {}),
|
|
5983
|
+
},
|
|
5984
|
+
},
|
|
5985
|
+
};
|
|
5986
|
+
try {
|
|
5987
|
+
document.dispatchEvent(new CustomEvent('coframe:variant-applied', {
|
|
5988
|
+
detail: {
|
|
5989
|
+
...event,
|
|
5990
|
+
time: new Date(event.time.getTime()),
|
|
5991
|
+
content: {
|
|
5992
|
+
metadata: JSON.parse(JSON.stringify(event.content.metadata)),
|
|
5993
|
+
},
|
|
5994
|
+
},
|
|
5995
|
+
}));
|
|
5996
|
+
}
|
|
5997
|
+
catch (_a) {
|
|
5998
|
+
}
|
|
5999
|
+
if (coframe.projectId)
|
|
6000
|
+
queueEventForBatchedSending(coframe, event);
|
|
6001
|
+
return metadataDropped;
|
|
6002
|
+
}
|
|
6003
|
+
|
|
4752
6004
|
function push(cmd) {
|
|
4753
6005
|
if (typeof window === 'undefined')
|
|
4754
6006
|
return;
|
|
@@ -4796,34 +6048,81 @@ const Coframe = {
|
|
|
4796
6048
|
},
|
|
4797
6049
|
};
|
|
4798
6050
|
Coframe.logger = createLogger(() => Coframe);
|
|
4799
|
-
|
|
6051
|
+
const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
6052
|
+
let activeRuntime = null;
|
|
6053
|
+
function clearActiveRuntime() {
|
|
6054
|
+
activeRuntime === null || activeRuntime === void 0 ? void 0 : activeRuntime.destroy();
|
|
6055
|
+
activeRuntime = null;
|
|
6056
|
+
delete Coframe.runtime;
|
|
6057
|
+
}
|
|
6058
|
+
function installRuntime(config) {
|
|
6059
|
+
const batchTimeoutMs = config.timeoutMs
|
|
6060
|
+
? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
|
|
6061
|
+
: undefined;
|
|
6062
|
+
clearActiveRuntime();
|
|
6063
|
+
if (!isVariantRuntimeLifecycleEnabled(config))
|
|
6064
|
+
return undefined;
|
|
6065
|
+
const runtime = createPageRuntime({
|
|
6066
|
+
revealCoordinator,
|
|
6067
|
+
currentUrl: getCurrentPageUrl(),
|
|
6068
|
+
initialHydrated: !config.waitForHydration,
|
|
6069
|
+
batchTimeoutMs,
|
|
6070
|
+
warn: (message, metadata) => Coframe.logger.warn(message, 'variant_runtime_warning', metadata),
|
|
6071
|
+
error: (message, metadata) => Coframe.logger.error(message, 'variant_runtime_error', metadata),
|
|
6072
|
+
emitLegacyVariantRendered: variantRendered,
|
|
6073
|
+
onVariantApplied: (pageState, sessionState, payload) => ingestVariantApplied(Coframe, pageState, sessionState, payload),
|
|
6074
|
+
});
|
|
6075
|
+
activeRuntime = runtime;
|
|
6076
|
+
Coframe.runtime = Object.freeze({
|
|
6077
|
+
getSnapshot: () => runtime.getDebugSnapshot(),
|
|
6078
|
+
});
|
|
6079
|
+
runtime.publishSdkReady();
|
|
6080
|
+
return runtime;
|
|
6081
|
+
}
|
|
6082
|
+
async function initialize(options, context = {}) {
|
|
4800
6083
|
if (typeof window !== 'undefined' &&
|
|
4801
6084
|
/cf_disable/.test(window.location.search)) {
|
|
4802
6085
|
console.warn('[Coframe] Disabled via URL parameter');
|
|
4803
|
-
|
|
6086
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4804
6087
|
return;
|
|
4805
6088
|
}
|
|
4806
6089
|
if (options)
|
|
4807
6090
|
injectOptions(options);
|
|
4808
6091
|
const gracefulExit = (msg) => {
|
|
4809
6092
|
console.warn(`[Coframe] ${msg}`);
|
|
4810
|
-
|
|
6093
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4811
6094
|
return;
|
|
4812
6095
|
};
|
|
4813
|
-
const configResult = await resolveConfig(Coframe,
|
|
6096
|
+
const configResult = await resolveConfig(Coframe, revealCoordinator);
|
|
4814
6097
|
if (!configResult)
|
|
4815
6098
|
return;
|
|
6099
|
+
const runtime = installRuntime(configResult.config);
|
|
6100
|
+
context.runtime = runtime;
|
|
4816
6101
|
loadLauncherIfEnabled(configResult.config.enableDebug, configResult.config.nonce);
|
|
4817
|
-
const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
|
|
4818
|
-
if (
|
|
6102
|
+
const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config, () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()));
|
|
6103
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
6104
|
+
return;
|
|
6105
|
+
if (!projectResult) {
|
|
6106
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4819
6107
|
return;
|
|
6108
|
+
}
|
|
4820
6109
|
Coframe.orgId = projectResult.project.org_id || '';
|
|
4821
6110
|
Coframe.personalizationHeaders = projectResult.personalizationHeaders;
|
|
4822
|
-
const identity = await resolveIdentity(configResult.config, Coframe, configResult.timeoutMs,
|
|
6111
|
+
const identity = await resolveIdentity(configResult.config, Coframe, configResult.timeoutMs, (msg) => {
|
|
6112
|
+
if (!(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
6113
|
+
gracefulExit(msg);
|
|
6114
|
+
}, configResult.previewVariantId);
|
|
4823
6115
|
if (!identity)
|
|
4824
6116
|
return;
|
|
6117
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
6118
|
+
return;
|
|
4825
6119
|
const analytics = setupTracking(Coframe, projectResult, configResult.previewVariantId, configResult.executionStartTime, configResult.config.nonce, configResult.config.waitForCookiesEnabled, identity.onConsentChange);
|
|
4826
|
-
await modifyPage(Coframe, configResult, projectResult, analytics
|
|
6120
|
+
await modifyPage(Coframe, configResult, projectResult, analytics, {
|
|
6121
|
+
revealCoordinator,
|
|
6122
|
+
runtime,
|
|
6123
|
+
});
|
|
6124
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
6125
|
+
return;
|
|
4827
6126
|
try {
|
|
4828
6127
|
startReplayIfEnabled(Coframe, projectResult.project, configResult.config.nonce);
|
|
4829
6128
|
}
|
|
@@ -4831,6 +6130,19 @@ async function init(options) {
|
|
|
4831
6130
|
console.warn('[Coframe] Failed to start session replay', error);
|
|
4832
6131
|
}
|
|
4833
6132
|
}
|
|
6133
|
+
async function init(options) {
|
|
6134
|
+
var _a;
|
|
6135
|
+
const context = {};
|
|
6136
|
+
try {
|
|
6137
|
+
await initialize(options, context);
|
|
6138
|
+
}
|
|
6139
|
+
catch (error) {
|
|
6140
|
+
if (!((_a = context.runtime) === null || _a === void 0 ? void 0 : _a.isDestroyed())) {
|
|
6141
|
+
revealCoordinator.forceReveal('init-error');
|
|
6142
|
+
}
|
|
6143
|
+
throw error;
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
4834
6146
|
Coframe.debug = {
|
|
4835
6147
|
getSelector,
|
|
4836
6148
|
getElement,
|