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