@coframe/sdk 1.1.44 → 1.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pkg/cf.cjs.js +1385 -95
- package/dist/pkg/cf.esm.mjs +1385 -95
- 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 {
|
|
@@ -3980,26 +4284,32 @@ function resolvePreview(pageUrl, previewVariantId, isSessionVariant) {
|
|
|
3980
4284
|
}
|
|
3981
4285
|
return { previewVariantId: null, isSessionVariant: false };
|
|
3982
4286
|
}
|
|
4287
|
+
const isPageHydratedCmd = (cmd) => cmd.pageHydrated === true || cmd.emit === 'pageHydrated';
|
|
3983
4288
|
async function awaitHydration(config, Coframe, project) {
|
|
3984
4289
|
var _a, _b;
|
|
3985
|
-
if (!config
|
|
4290
|
+
if (!shouldSdkWaitForHydration(config))
|
|
3986
4291
|
return true;
|
|
3987
4292
|
if (!((_a = project.frames) === null || _a === void 0 ? void 0 : _a.length) && !((_b = project.tags) === null || _b === void 0 ? void 0 : _b.length))
|
|
3988
4293
|
return true;
|
|
3989
|
-
const cmd = await waitForCmd(
|
|
4294
|
+
const cmd = await waitForCmd(isPageHydratedCmd, Coframe);
|
|
3990
4295
|
if (!cmd) {
|
|
3991
4296
|
Coframe.logger.warn('Failed to wait for page hydrated command', 'wait_for_cmd_timeout');
|
|
3992
4297
|
return false;
|
|
3993
4298
|
}
|
|
3994
4299
|
return true;
|
|
3995
4300
|
}
|
|
3996
|
-
async function ensureBody(Coframe, cdnProject, timeoutMs, nonce) {
|
|
4301
|
+
async function ensureBody(Coframe, cdnProject, timeoutMs, nonce, revealCoordinator, isCurrent) {
|
|
3997
4302
|
const bodyPresent = await waitForBody(timeoutMs).catch(() => false);
|
|
3998
4303
|
if (!bodyPresent) {
|
|
3999
4304
|
Coframe.logger.error('Timeout waiting for document.body, exiting gracefully', 'body_timeout');
|
|
4000
|
-
|
|
4305
|
+
if (isCurrent()) {
|
|
4306
|
+
variantRendered();
|
|
4307
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4308
|
+
}
|
|
4001
4309
|
return false;
|
|
4002
4310
|
}
|
|
4311
|
+
if (!isCurrent())
|
|
4312
|
+
return false;
|
|
4003
4313
|
rebindMetrics(Coframe, Coframe.logger, cdnProject === null || cdnProject === void 0 ? void 0 : cdnProject.metrics, nonce);
|
|
4004
4314
|
return true;
|
|
4005
4315
|
}
|
|
@@ -4012,7 +4322,13 @@ function setupFlushInterval(Coframe) {
|
|
|
4012
4322
|
}
|
|
4013
4323
|
flushInterval = setInterval(() => flushQueuedEvents(Coframe.projectId), FLUSH_EVENTS_TIME);
|
|
4014
4324
|
}
|
|
4015
|
-
|
|
4325
|
+
const hasNoVariants = (Coframe) => !Coframe.variantsAndElements || Coframe.variantsAndElements.length === 0;
|
|
4326
|
+
const logNavigationError = (Coframe, message, error) => {
|
|
4327
|
+
Coframe.logger.error(message, 'spa_navigation_error', {
|
|
4328
|
+
error: describeError(error),
|
|
4329
|
+
});
|
|
4330
|
+
};
|
|
4331
|
+
function setupSPANavigation(Coframe, project, config, previewVariantId, isSessionVariant, runtime, applier, firstViewDeadline, onFirstViewDone) {
|
|
4016
4332
|
var _a;
|
|
4017
4333
|
if (!((_a = project === null || project === void 0 ? void 0 : project.settings) === null || _a === void 0 ? void 0 : _a.mutation_observer_active))
|
|
4018
4334
|
return;
|
|
@@ -4037,16 +4353,20 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4037
4353
|
const observer = new MutationObserver(mutationCallback);
|
|
4038
4354
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
4039
4355
|
let navigationAbortController = null;
|
|
4040
|
-
|
|
4356
|
+
const onUrlChange = async (newPageUrl, _oldPageUrl, options = {}) => {
|
|
4041
4357
|
var _a;
|
|
4042
|
-
|
|
4358
|
+
const firstView = !applier.hasCommitted();
|
|
4359
|
+
if (!firstView)
|
|
4360
|
+
sendPageLeave(Coframe);
|
|
4043
4361
|
if (navigationAbortController)
|
|
4044
4362
|
navigationAbortController.abort();
|
|
4045
|
-
|
|
4046
|
-
const
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4363
|
+
runtime === null || runtime === void 0 ? void 0 : runtime.changeRoute(newPageUrl);
|
|
4364
|
+
const controller = createNavigationController();
|
|
4365
|
+
navigationAbortController = controller;
|
|
4366
|
+
const { signal } = controller;
|
|
4367
|
+
await waitForFrame(options.routeAlreadyRendered ? HANDOVER_FRAME_CAP_MS : undefined, NAVIGATION_FRAME_CAP_MS);
|
|
4368
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4369
|
+
return 'skipped';
|
|
4050
4370
|
infiniteLoopProtection = 0;
|
|
4051
4371
|
let pageProject = project;
|
|
4052
4372
|
let pagePersonalizationHeaders = Coframe.personalizationHeaders;
|
|
@@ -4062,9 +4382,9 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4062
4382
|
else {
|
|
4063
4383
|
try {
|
|
4064
4384
|
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;
|
|
4385
|
+
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());
|
|
4386
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4387
|
+
return 'skipped';
|
|
4068
4388
|
if (result) {
|
|
4069
4389
|
pageProject = result.project;
|
|
4070
4390
|
pagePersonalizationHeaders = {
|
|
@@ -4077,10 +4397,37 @@ function setupSPANavigation(Coframe, project, config, previewVariantId, isSessio
|
|
|
4077
4397
|
catch (_b) {
|
|
4078
4398
|
}
|
|
4079
4399
|
}
|
|
4080
|
-
if (signal.aborted)
|
|
4081
|
-
return;
|
|
4082
|
-
|
|
4083
|
-
|
|
4400
|
+
if (signal.aborted || (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
4401
|
+
return 'skipped';
|
|
4402
|
+
let result = 'skipped';
|
|
4403
|
+
let threw = false;
|
|
4404
|
+
try {
|
|
4405
|
+
result = await applier.apply(newPageUrl, pageProject, pagePersonalizationHeaders, signal, firstView ? firstViewDeadline : undefined);
|
|
4406
|
+
}
|
|
4407
|
+
catch (error) {
|
|
4408
|
+
threw = true;
|
|
4409
|
+
if (firstView && !signal.aborted && !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())) {
|
|
4410
|
+
onFirstViewDone === null || onFirstViewDone === void 0 ? void 0 : onFirstViewDone(pageProject);
|
|
4411
|
+
}
|
|
4412
|
+
throw error;
|
|
4413
|
+
}
|
|
4414
|
+
finally {
|
|
4415
|
+
if (shouldSettleRoute(signal.aborted, threw, result)) {
|
|
4416
|
+
runtime === null || runtime === void 0 ? void 0 : runtime.settleRoute();
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
if (firstView && result === 'applied' && !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())) {
|
|
4420
|
+
if (hasNoVariants(Coframe))
|
|
4421
|
+
variantRendered();
|
|
4422
|
+
onFirstViewDone === null || onFirstViewDone === void 0 ? void 0 : onFirstViewDone(pageProject);
|
|
4423
|
+
}
|
|
4424
|
+
return result;
|
|
4425
|
+
};
|
|
4426
|
+
observeUrlChanges(false, (newPageUrl, oldPageUrl) => onUrlChange(newPageUrl).catch((error) => {
|
|
4427
|
+
logNavigationError(Coframe, 'SPA navigation failed', error);
|
|
4428
|
+
return 'skipped';
|
|
4429
|
+
}), () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()));
|
|
4430
|
+
return onUrlChange;
|
|
4084
4431
|
}
|
|
4085
4432
|
function readLaneClaims() {
|
|
4086
4433
|
const cookie = getCookie(LANE_COOKIE_KEY);
|
|
@@ -4110,17 +4457,14 @@ function persistLaneClaims(claims, logger) {
|
|
|
4110
4457
|
setCookie(LANE_COOKIE_KEY, encodeURIComponent(serialized), logger);
|
|
4111
4458
|
}
|
|
4112
4459
|
}
|
|
4113
|
-
function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore) {
|
|
4460
|
+
function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore, runtime) {
|
|
4114
4461
|
let isFirstApply = true;
|
|
4115
4462
|
let applicationId = 0;
|
|
4116
|
-
|
|
4117
|
-
var _a;
|
|
4463
|
+
const apply = async (pageUrl, pageProject, personalizationHeaders, signal, deadline) => {
|
|
4464
|
+
var _a, _b;
|
|
4118
4465
|
const currentApplication = ++applicationId;
|
|
4119
4466
|
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;
|
|
4467
|
+
return 'skipped';
|
|
4124
4468
|
const preview = resolvePreview(pageUrl, previewVariantId, isSessionVariant);
|
|
4125
4469
|
const chooseCmabVariants = createCmabVariantBatchChooser({
|
|
4126
4470
|
projectId: Coframe.projectId,
|
|
@@ -4134,8 +4478,22 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4134
4478
|
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
4479
|
if ((signal === null || signal === void 0 ? void 0 : signal.aborted) ||
|
|
4136
4480
|
currentApplication !== applicationId ||
|
|
4481
|
+
(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()) ||
|
|
4137
4482
|
(signal && !urlsMatch(pageUrl, getCurrentPageUrl())))
|
|
4138
|
-
return;
|
|
4483
|
+
return 'skipped';
|
|
4484
|
+
if (!signal &&
|
|
4485
|
+
runtime &&
|
|
4486
|
+
((_b = pageProject === null || pageProject === void 0 ? void 0 : pageProject.settings) === null || _b === void 0 ? void 0 : _b.mutation_observer_active) &&
|
|
4487
|
+
!urlsMatch(pageUrl, getCurrentPageUrl(), true))
|
|
4488
|
+
return 'stale-route';
|
|
4489
|
+
if (!signal && runtime) {
|
|
4490
|
+
const urlAtCommit = getCurrentPageUrl();
|
|
4491
|
+
if (urlsMatch(pageUrl, urlAtCommit, true))
|
|
4492
|
+
runtime.updateUrl(urlAtCommit);
|
|
4493
|
+
}
|
|
4494
|
+
const sessionId = isFirstApply ? Coframe.sessionId : generateUUID();
|
|
4495
|
+
const sessionContext = buildSessionContext(isFirstApply);
|
|
4496
|
+
isFirstApply = false;
|
|
4139
4497
|
Coframe.sessionId = sessionId;
|
|
4140
4498
|
Coframe.personalizationHeaders = personalizationHeaders;
|
|
4141
4499
|
setIsPreviewVariant(!!preview.previewVariantId);
|
|
@@ -4153,7 +4511,9 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4153
4511
|
variantRendered();
|
|
4154
4512
|
}
|
|
4155
4513
|
else {
|
|
4156
|
-
replaceVariants(variantsAndElements, Coframe.orgId, Coframe.logger, config.nonce);
|
|
4514
|
+
await replaceVariants(variantsAndElements, Coframe.orgId, Coframe.logger, config.nonce, runtime);
|
|
4515
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4516
|
+
return 'skipped';
|
|
4157
4517
|
}
|
|
4158
4518
|
const cmabDecisions = chooseCmabVariants.getDecisions(experimentsInfo);
|
|
4159
4519
|
startSession(Coframe, isCookiesEnabled, executionStartTime, personalizationHeaders, {
|
|
@@ -4162,29 +4522,77 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4162
4522
|
}, pageUrl);
|
|
4163
4523
|
analytics.resetBroadcasted();
|
|
4164
4524
|
analytics.sendAnalytics();
|
|
4525
|
+
return 'applied';
|
|
4165
4526
|
};
|
|
4527
|
+
return { apply, hasCommitted: () => !isFirstApply };
|
|
4166
4528
|
}
|
|
4167
|
-
async function modifyPage(Coframe, configResult, projectResult, analytics) {
|
|
4529
|
+
async function modifyPage(Coframe, configResult, projectResult, analytics, lifecycle) {
|
|
4530
|
+
var _a;
|
|
4168
4531
|
const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
|
|
4169
4532
|
const { project: cdnProject, personalizationHeaders } = projectResult;
|
|
4533
|
+
const revealCoordinator = (_a = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.revealCoordinator) !== null && _a !== void 0 ? _a : createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
4534
|
+
const runtime = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.runtime;
|
|
4535
|
+
if (runtime && config.waitForHydration) {
|
|
4536
|
+
if (findLastCmd(isPageHydratedCmd)) {
|
|
4537
|
+
runtime.publishHydrated();
|
|
4538
|
+
}
|
|
4539
|
+
else {
|
|
4540
|
+
let stopHydration = () => { };
|
|
4541
|
+
stopHydration = subscribeToCmd(isPageHydratedCmd, () => {
|
|
4542
|
+
stopHydration();
|
|
4543
|
+
runtime.publishHydrated();
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4170
4547
|
if (!(await awaitHydration(config, Coframe, cdnProject)))
|
|
4171
4548
|
return;
|
|
4549
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4550
|
+
return;
|
|
4172
4551
|
if (cdnProject.tags) {
|
|
4173
4552
|
executeTags(cdnProject.tags, Coframe.logger, config.nonce);
|
|
4174
4553
|
Coframe.status.tags = cdnProject.tags;
|
|
4175
4554
|
}
|
|
4176
|
-
if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce)))
|
|
4555
|
+
if (!(await ensureBody(Coframe, cdnProject, timeoutMs, config.nonce, revealCoordinator, () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))))
|
|
4177
4556
|
return;
|
|
4178
4557
|
const laneStore = { claims: readLaneClaims() };
|
|
4179
|
-
const
|
|
4180
|
-
|
|
4181
|
-
if (
|
|
4182
|
-
|
|
4183
|
-
|
|
4558
|
+
const applier = createApplyVariants(Coframe, config, previewVariantId, isSessionVariant, executionStartTime, analytics, laneStore, runtime);
|
|
4559
|
+
const initialUrl = getCurrentPageUrl();
|
|
4560
|
+
if (runtime) {
|
|
4561
|
+
const runtimeUrl = runtime.getState().currentUrl;
|
|
4562
|
+
if (!urlsMatch(runtimeUrl, initialUrl, true))
|
|
4563
|
+
runtime.changeRoute(initialUrl);
|
|
4564
|
+
}
|
|
4565
|
+
const firstDeadline = (integrationStartTime || executionStartTime).getTime() + timeoutMs;
|
|
4566
|
+
const firstResult = await applier.apply(initialUrl, cdnProject, personalizationHeaders, undefined, firstDeadline);
|
|
4567
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4568
|
+
return;
|
|
4569
|
+
const routeChangedDuringInit = firstResult === 'stale-route';
|
|
4570
|
+
if (!routeChangedDuringInit && hasNoVariants(Coframe)) {
|
|
4571
|
+
revealCoordinator.requestReveal('no-variants');
|
|
4184
4572
|
variantRendered();
|
|
4185
4573
|
}
|
|
4186
|
-
|
|
4187
|
-
|
|
4574
|
+
if (!routeChangedDuringInit)
|
|
4575
|
+
revealCoordinator.flushPending();
|
|
4576
|
+
let timingReported = false;
|
|
4577
|
+
const reportTiming = (appliedProject = cdnProject) => {
|
|
4578
|
+
if (timingReported)
|
|
4579
|
+
return;
|
|
4580
|
+
timingReported = true;
|
|
4581
|
+
reportInitTiming(Coframe, appliedProject, integrationStartTime, executionStartTime);
|
|
4582
|
+
};
|
|
4583
|
+
if (!routeChangedDuringInit)
|
|
4584
|
+
reportTiming();
|
|
4585
|
+
const onUrlChange = setupSPANavigation(Coframe, cdnProject, config, previewVariantId, isSessionVariant, runtime, applier, firstDeadline, routeChangedDuringInit ? reportTiming : undefined);
|
|
4586
|
+
const urlNow = getCurrentPageUrl();
|
|
4587
|
+
if (runtime &&
|
|
4588
|
+
onUrlChange &&
|
|
4589
|
+
(routeChangedDuringInit || !urlsMatch(initialUrl, urlNow, true))) {
|
|
4590
|
+
onUrlChange(urlNow, initialUrl, {
|
|
4591
|
+
routeAlreadyRendered: routeChangedDuringInit,
|
|
4592
|
+
}).catch((error) => {
|
|
4593
|
+
logNavigationError(Coframe, 'Applying the route reached during init failed', error);
|
|
4594
|
+
});
|
|
4595
|
+
}
|
|
4188
4596
|
setupFlushInterval(Coframe);
|
|
4189
4597
|
}
|
|
4190
4598
|
|
|
@@ -4512,11 +4920,12 @@ async function resolveFeatureFlag(Coframe, timeoutMs) {
|
|
|
4512
4920
|
return undefined;
|
|
4513
4921
|
return getFeatureFlagValue(cmd);
|
|
4514
4922
|
}
|
|
4515
|
-
async function resolveConfig(Coframe,
|
|
4923
|
+
async function resolveConfig(Coframe, revealActions) {
|
|
4516
4924
|
const logger = Coframe.logger;
|
|
4517
4925
|
const configCmd = await waitForCmd((cmd) => !!cmd.config, Coframe);
|
|
4518
4926
|
if (!configCmd) {
|
|
4519
4927
|
logger.error('Failed to wait for config cmd', 'wait_for_cmd_timeout');
|
|
4928
|
+
revealActions.forceReveal('config-timeout');
|
|
4520
4929
|
return null;
|
|
4521
4930
|
}
|
|
4522
4931
|
const config = configCmd.config;
|
|
@@ -4532,7 +4941,7 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4532
4941
|
configureReplayScriptUrl(config.replayScriptUrl);
|
|
4533
4942
|
const gracefulExit = (msg) => {
|
|
4534
4943
|
console.warn(`[Coframe] ${msg}`);
|
|
4535
|
-
|
|
4944
|
+
revealActions.forceReveal('graceful-exit');
|
|
4536
4945
|
return null;
|
|
4537
4946
|
};
|
|
4538
4947
|
if (config.waitForFeatureFlag) {
|
|
@@ -4545,8 +4954,9 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4545
4954
|
}
|
|
4546
4955
|
}
|
|
4547
4956
|
const projectId = config === null || config === void 0 ? void 0 : config.projectId;
|
|
4957
|
+
const lifecycleMode = isVariantRuntimeLifecycleEnabled(config);
|
|
4548
4958
|
const warnMissingVariant = () => {
|
|
4549
|
-
if (applyVariantChangesStarted) {
|
|
4959
|
+
if (applyVariantChangesStarted && !lifecycleMode) {
|
|
4550
4960
|
logger.warn('Failed to wait for variant rendered command', 'missing_variant_rendered');
|
|
4551
4961
|
}
|
|
4552
4962
|
};
|
|
@@ -4559,14 +4969,15 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4559
4969
|
}
|
|
4560
4970
|
logger.warn('Variant not rendered', 'variant_not_rendered', metadata);
|
|
4561
4971
|
};
|
|
4562
|
-
waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs)
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
}
|
|
4567
|
-
|
|
4568
|
-
.
|
|
4569
|
-
|
|
4972
|
+
waitForCmd((cmd) => cmd.variantRendered === true || cmd.emit === 'variantRendered', Coframe, timeoutMs, QueueName.CFQ, !lifecycleMode).then((cmd) => {
|
|
4973
|
+
if (cmd) {
|
|
4974
|
+
revealActions.requestReveal('legacy-variant-rendered');
|
|
4975
|
+
return;
|
|
4976
|
+
}
|
|
4977
|
+
warnMissingVariant();
|
|
4978
|
+
if (revealActions.getSnapshot().pageRevealed)
|
|
4979
|
+
return;
|
|
4980
|
+
revealActions.forceReveal('config-timeout');
|
|
4570
4981
|
});
|
|
4571
4982
|
const loggedReasons = [];
|
|
4572
4983
|
listenForCmd((cmd) => cmd.emit === 'variantNotRendered', (cmd) => {
|
|
@@ -4580,6 +4991,7 @@ async function resolveConfig(Coframe, revealPage) {
|
|
|
4580
4991
|
description: 'No Coframe Page ID',
|
|
4581
4992
|
});
|
|
4582
4993
|
variantRendered();
|
|
4994
|
+
revealActions.forceReveal('graceful-exit');
|
|
4583
4995
|
return null;
|
|
4584
4996
|
}
|
|
4585
4997
|
Coframe.projectId = projectId;
|
|
@@ -4751,6 +5163,824 @@ function injectOptions(options) {
|
|
|
4751
5163
|
}
|
|
4752
5164
|
}
|
|
4753
5165
|
|
|
5166
|
+
const TERMINAL_STATUSES = new Set([
|
|
5167
|
+
'completed',
|
|
5168
|
+
'error',
|
|
5169
|
+
'cleaned',
|
|
5170
|
+
]);
|
|
5171
|
+
const DEFAULT_FADE_DURATION_MS = 150;
|
|
5172
|
+
const MAX_INGEST_PER_SESSION = 10;
|
|
5173
|
+
const TRANSITION_LONGHANDS = [
|
|
5174
|
+
'transition-property',
|
|
5175
|
+
'transition-duration',
|
|
5176
|
+
'transition-timing-function',
|
|
5177
|
+
'transition-delay',
|
|
5178
|
+
];
|
|
5179
|
+
const fadeTargets = new Map();
|
|
5180
|
+
const FADE_SAFETY_MS = 5000;
|
|
5181
|
+
function cancelFadeAnimation(fadeTarget) {
|
|
5182
|
+
if (fadeTarget.animationFrame !== null) {
|
|
5183
|
+
cancelAnimationFrame(fadeTarget.animationFrame);
|
|
5184
|
+
}
|
|
5185
|
+
if (fadeTarget.timer)
|
|
5186
|
+
clearTimeout(fadeTarget.timer);
|
|
5187
|
+
fadeTarget.animationFrame = null;
|
|
5188
|
+
fadeTarget.timer = null;
|
|
5189
|
+
}
|
|
5190
|
+
function finishFade(fadeTarget) {
|
|
5191
|
+
if (fadeTargets.get(fadeTarget.target) !== fadeTarget)
|
|
5192
|
+
return;
|
|
5193
|
+
fadeTargets.delete(fadeTarget.target);
|
|
5194
|
+
cancelFadeAnimation(fadeTarget);
|
|
5195
|
+
fadeTarget.target.removeEventListener('transitionend', onFadeTransitionEnd);
|
|
5196
|
+
for (const record of Array.from(fadeTarget.records))
|
|
5197
|
+
record.stop();
|
|
5198
|
+
fadeTarget.records.clear();
|
|
5199
|
+
const { target } = fadeTarget;
|
|
5200
|
+
const style = target.style;
|
|
5201
|
+
style.setProperty('transition', 'none', 'important');
|
|
5202
|
+
style.setProperty('opacity', ...fadeTarget.opacity);
|
|
5203
|
+
void getComputedStyle(target).opacity;
|
|
5204
|
+
style.removeProperty('transition');
|
|
5205
|
+
if (fadeTarget.transition[0]) {
|
|
5206
|
+
style.setProperty('transition', ...fadeTarget.transition);
|
|
5207
|
+
}
|
|
5208
|
+
for (const [name, value, priority] of fadeTarget.longhands) {
|
|
5209
|
+
if (value)
|
|
5210
|
+
style.setProperty(name, value, priority);
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
function onFadeTransitionEnd(event) {
|
|
5214
|
+
const fadeTarget = fadeTargets.get(event.target);
|
|
5215
|
+
if ((fadeTarget === null || fadeTarget === void 0 ? void 0 : fadeTarget.timer) && event.propertyName === 'opacity') {
|
|
5216
|
+
finishFade(fadeTarget);
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
function maybeStartFade(fadeTarget) {
|
|
5220
|
+
if (fadeTarget.animationFrame !== null || fadeTarget.timer)
|
|
5221
|
+
return;
|
|
5222
|
+
let duration = 0;
|
|
5223
|
+
for (const record of Array.from(fadeTarget.records)) {
|
|
5224
|
+
if (!record.ready)
|
|
5225
|
+
return;
|
|
5226
|
+
duration = Math.max(duration, record.duration);
|
|
5227
|
+
}
|
|
5228
|
+
const { target } = fadeTarget;
|
|
5229
|
+
void getComputedStyle(target).opacity;
|
|
5230
|
+
fadeTarget.animationFrame = requestAnimationFrame(() => {
|
|
5231
|
+
fadeTarget.animationFrame = null;
|
|
5232
|
+
if (fadeTargets.get(target) !== fadeTarget)
|
|
5233
|
+
return;
|
|
5234
|
+
target.style.setProperty('transition', `opacity ${duration}ms ease`, 'important');
|
|
5235
|
+
target.style.setProperty('opacity', '1', 'important');
|
|
5236
|
+
fadeTarget.timer = setTimeout(() => finishFade(fadeTarget), duration + 100);
|
|
5237
|
+
});
|
|
5238
|
+
}
|
|
5239
|
+
function removeFadeRecord(record) {
|
|
5240
|
+
const { fadeTarget } = record;
|
|
5241
|
+
if (!fadeTarget.records.delete(record))
|
|
5242
|
+
return;
|
|
5243
|
+
record.stop();
|
|
5244
|
+
if (fadeTarget.records.size === 0)
|
|
5245
|
+
finishFade(fadeTarget);
|
|
5246
|
+
else
|
|
5247
|
+
maybeStartFade(fadeTarget);
|
|
5248
|
+
}
|
|
5249
|
+
function createVariantSession(options) {
|
|
5250
|
+
const listenerStops = new Set();
|
|
5251
|
+
let ingestCount = 0;
|
|
5252
|
+
let ingestLimitReported = false;
|
|
5253
|
+
let metadataDropReported = false;
|
|
5254
|
+
const fades = new Map();
|
|
5255
|
+
const fadeReleases = new Set();
|
|
5256
|
+
const state = {
|
|
5257
|
+
sessionId: options.sessionId,
|
|
5258
|
+
variantId: options.variantId,
|
|
5259
|
+
batchId: options.batchId,
|
|
5260
|
+
routeVersion: options.routeVersion,
|
|
5261
|
+
active: true,
|
|
5262
|
+
status: 'created',
|
|
5263
|
+
ownsPageAntiflicker: false,
|
|
5264
|
+
pageAntiflickerRemoved: false,
|
|
5265
|
+
};
|
|
5266
|
+
const isMutable = () => state.active && !TERMINAL_STATUSES.has(state.status);
|
|
5267
|
+
const notify = () => options.onStateChange(internal);
|
|
5268
|
+
const releaseVisuals = () => {
|
|
5269
|
+
var _a;
|
|
5270
|
+
for (const record of Array.from(fades.values()))
|
|
5271
|
+
removeFadeRecord(record);
|
|
5272
|
+
(_a = options.releaseVisuals) === null || _a === void 0 ? void 0 : _a.call(options, internal);
|
|
5273
|
+
};
|
|
5274
|
+
const fadeIn = (target, durationMs = DEFAULT_FADE_DURATION_MS) => {
|
|
5275
|
+
if (fades.has(target))
|
|
5276
|
+
return;
|
|
5277
|
+
const duration = typeof durationMs === 'number' && durationMs - durationMs === 0
|
|
5278
|
+
? Math.max(0, durationMs)
|
|
5279
|
+
: DEFAULT_FADE_DURATION_MS;
|
|
5280
|
+
let fadeTarget = fadeTargets.get(target);
|
|
5281
|
+
if (!fadeTarget) {
|
|
5282
|
+
const style = target.style;
|
|
5283
|
+
fadeTarget = {
|
|
5284
|
+
target,
|
|
5285
|
+
opacity: [
|
|
5286
|
+
style.getPropertyValue('opacity'),
|
|
5287
|
+
style.getPropertyPriority('opacity'),
|
|
5288
|
+
],
|
|
5289
|
+
transition: [
|
|
5290
|
+
style.getPropertyValue('transition'),
|
|
5291
|
+
style.getPropertyPriority('transition'),
|
|
5292
|
+
],
|
|
5293
|
+
longhands: TRANSITION_LONGHANDS.map((name) => [
|
|
5294
|
+
name,
|
|
5295
|
+
style.getPropertyValue(name),
|
|
5296
|
+
style.getPropertyPriority(name),
|
|
5297
|
+
]),
|
|
5298
|
+
records: new Set(),
|
|
5299
|
+
animationFrame: null,
|
|
5300
|
+
timer: null,
|
|
5301
|
+
};
|
|
5302
|
+
fadeTargets.set(target, fadeTarget);
|
|
5303
|
+
target.addEventListener('transitionend', onFadeTransitionEnd);
|
|
5304
|
+
}
|
|
5305
|
+
cancelFadeAnimation(fadeTarget);
|
|
5306
|
+
target.style.setProperty('transition', 'none', 'important');
|
|
5307
|
+
target.style.setProperty('opacity', '0', 'important');
|
|
5308
|
+
let pageRevealed = false;
|
|
5309
|
+
let released = false;
|
|
5310
|
+
let stopPageListener = () => { };
|
|
5311
|
+
let safetyTimer = null;
|
|
5312
|
+
const update = () => {
|
|
5313
|
+
record.ready = pageRevealed && released;
|
|
5314
|
+
if (record.ready)
|
|
5315
|
+
maybeStartFade(record.fadeTarget);
|
|
5316
|
+
};
|
|
5317
|
+
const release = () => {
|
|
5318
|
+
released = true;
|
|
5319
|
+
if (safetyTimer)
|
|
5320
|
+
clearTimeout(safetyTimer);
|
|
5321
|
+
safetyTimer = null;
|
|
5322
|
+
update();
|
|
5323
|
+
};
|
|
5324
|
+
const record = {
|
|
5325
|
+
fadeTarget,
|
|
5326
|
+
duration,
|
|
5327
|
+
ready: false,
|
|
5328
|
+
stop: () => {
|
|
5329
|
+
stopPageListener();
|
|
5330
|
+
if (safetyTimer)
|
|
5331
|
+
clearTimeout(safetyTimer);
|
|
5332
|
+
safetyTimer = null;
|
|
5333
|
+
fadeReleases.delete(release);
|
|
5334
|
+
if (fades.get(target) === record)
|
|
5335
|
+
fades.delete(target);
|
|
5336
|
+
},
|
|
5337
|
+
};
|
|
5338
|
+
fadeTarget.records.add(record);
|
|
5339
|
+
fades.set(target, record);
|
|
5340
|
+
fadeReleases.add(release);
|
|
5341
|
+
safetyTimer = setTimeout(release, FADE_SAFETY_MS);
|
|
5342
|
+
stopPageListener = subscribe('pageRevealed', () => {
|
|
5343
|
+
pageRevealed = true;
|
|
5344
|
+
update();
|
|
5345
|
+
}, true);
|
|
5346
|
+
if (options.getPageState().pageRevealed) {
|
|
5347
|
+
pageRevealed = true;
|
|
5348
|
+
update();
|
|
5349
|
+
}
|
|
5350
|
+
};
|
|
5351
|
+
const subscribe = (event, handler, once) => {
|
|
5352
|
+
if (!state.active)
|
|
5353
|
+
return () => { };
|
|
5354
|
+
const stopSubscription = options.subscribePage(event, handler, once);
|
|
5355
|
+
if (!state.active) {
|
|
5356
|
+
stopSubscription();
|
|
5357
|
+
return () => { };
|
|
5358
|
+
}
|
|
5359
|
+
const stop = () => {
|
|
5360
|
+
listenerStops.delete(stop);
|
|
5361
|
+
stopSubscription();
|
|
5362
|
+
};
|
|
5363
|
+
listenerStops.add(stop);
|
|
5364
|
+
return stop;
|
|
5365
|
+
};
|
|
5366
|
+
const facade = {
|
|
5367
|
+
getPageState: () => ({ ...options.getPageState() }),
|
|
5368
|
+
getState: () => ({ ...state }),
|
|
5369
|
+
getVariantId: () => state.variantId,
|
|
5370
|
+
onPage: (event, handler) => subscribe(event, handler, false),
|
|
5371
|
+
oncePage: (event, handler) => subscribe(event, handler, true),
|
|
5372
|
+
ready() {
|
|
5373
|
+
if (!isMutable() || state.status !== 'created')
|
|
5374
|
+
return;
|
|
5375
|
+
state.status = 'ready';
|
|
5376
|
+
notify();
|
|
5377
|
+
},
|
|
5378
|
+
takePageAntiflickerOwnership() {
|
|
5379
|
+
if (!isMutable() || state.ownsPageAntiflicker)
|
|
5380
|
+
return;
|
|
5381
|
+
state.ownsPageAntiflicker = true;
|
|
5382
|
+
state.status = 'rendering';
|
|
5383
|
+
notify();
|
|
5384
|
+
},
|
|
5385
|
+
fadeIn(target, durationMs) {
|
|
5386
|
+
if (!isMutable() || !target)
|
|
5387
|
+
return;
|
|
5388
|
+
if (options.fadeIn)
|
|
5389
|
+
options.fadeIn(internal, target, durationMs);
|
|
5390
|
+
else
|
|
5391
|
+
fadeIn(target, durationMs);
|
|
5392
|
+
},
|
|
5393
|
+
ingestApplied(payload) {
|
|
5394
|
+
var _a, _b, _c;
|
|
5395
|
+
if (!isMutable())
|
|
5396
|
+
return;
|
|
5397
|
+
if (ingestCount >= MAX_INGEST_PER_SESSION) {
|
|
5398
|
+
if (!ingestLimitReported) {
|
|
5399
|
+
ingestLimitReported = true;
|
|
5400
|
+
(_a = options.onIngestLimit) === null || _a === void 0 ? void 0 : _a.call(options, internal);
|
|
5401
|
+
}
|
|
5402
|
+
return;
|
|
5403
|
+
}
|
|
5404
|
+
ingestCount += 1;
|
|
5405
|
+
const metadataDropped = (_b = options.ingestApplied) === null || _b === void 0 ? void 0 : _b.call(options, internal, payload);
|
|
5406
|
+
if (metadataDropped && !metadataDropReported) {
|
|
5407
|
+
metadataDropReported = true;
|
|
5408
|
+
(_c = options.onMetadataDropped) === null || _c === void 0 ? void 0 : _c.call(options, internal);
|
|
5409
|
+
}
|
|
5410
|
+
},
|
|
5411
|
+
complete() {
|
|
5412
|
+
if (!isMutable())
|
|
5413
|
+
return;
|
|
5414
|
+
state.status = 'completed';
|
|
5415
|
+
for (const release of Array.from(fadeReleases))
|
|
5416
|
+
release();
|
|
5417
|
+
fadeReleases.clear();
|
|
5418
|
+
options.emitLegacyVariantRendered();
|
|
5419
|
+
notify();
|
|
5420
|
+
},
|
|
5421
|
+
error(error) {
|
|
5422
|
+
var _a;
|
|
5423
|
+
if (!isMutable())
|
|
5424
|
+
return;
|
|
5425
|
+
state.status = 'error';
|
|
5426
|
+
releaseVisuals();
|
|
5427
|
+
(_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, internal, error);
|
|
5428
|
+
options.emitLegacyVariantRendered();
|
|
5429
|
+
notify();
|
|
5430
|
+
},
|
|
5431
|
+
cleanup() {
|
|
5432
|
+
if (state.status === 'cleaned')
|
|
5433
|
+
return;
|
|
5434
|
+
state.active = false;
|
|
5435
|
+
state.status = 'cleaned';
|
|
5436
|
+
for (const stop of Array.from(listenerStops))
|
|
5437
|
+
stop();
|
|
5438
|
+
releaseVisuals();
|
|
5439
|
+
notify();
|
|
5440
|
+
},
|
|
5441
|
+
};
|
|
5442
|
+
const internal = {
|
|
5443
|
+
facade: Object.freeze(facade),
|
|
5444
|
+
getState: () => ({ ...state }),
|
|
5445
|
+
isSettledForReveal: () => state.status === 'ready' ||
|
|
5446
|
+
state.status === 'rendering' ||
|
|
5447
|
+
TERMINAL_STATUSES.has(state.status),
|
|
5448
|
+
markPageRevealed() {
|
|
5449
|
+
if (state.pageAntiflickerRemoved)
|
|
5450
|
+
return;
|
|
5451
|
+
state.pageAntiflickerRemoved = true;
|
|
5452
|
+
},
|
|
5453
|
+
invalidate() {
|
|
5454
|
+
facade.cleanup();
|
|
5455
|
+
},
|
|
5456
|
+
};
|
|
5457
|
+
return internal;
|
|
5458
|
+
}
|
|
5459
|
+
|
|
5460
|
+
const DEFAULT_BATCH_TIMEOUT_MS = 1000;
|
|
5461
|
+
function createPageRuntime(options) {
|
|
5462
|
+
var _a, _b, _c;
|
|
5463
|
+
const createId = (_a = options.createId) !== null && _a !== void 0 ? _a : generateUUID;
|
|
5464
|
+
const batchTimeoutMs = (_b = options.batchTimeoutMs) !== null && _b !== void 0 ? _b : DEFAULT_BATCH_TIMEOUT_MS;
|
|
5465
|
+
const revealDeadline = Date.now() + batchTimeoutMs;
|
|
5466
|
+
const timeUntilDeadline = () => Math.max(0, revealDeadline - Date.now());
|
|
5467
|
+
const listeners = new Map();
|
|
5468
|
+
const state = {
|
|
5469
|
+
sdkReady: false,
|
|
5470
|
+
hydrated: (_c = options.initialHydrated) !== null && _c !== void 0 ? _c : false,
|
|
5471
|
+
routeVersion: 0,
|
|
5472
|
+
currentUrl: options.currentUrl,
|
|
5473
|
+
pageRevealed: options.revealCoordinator.getSnapshot().pageRevealed,
|
|
5474
|
+
};
|
|
5475
|
+
let activeBatch = null;
|
|
5476
|
+
let handoffBlockerId = null;
|
|
5477
|
+
let handoffTimeout = null;
|
|
5478
|
+
let destroyed = false;
|
|
5479
|
+
const clearHandoff = () => {
|
|
5480
|
+
handoffBlockerId = null;
|
|
5481
|
+
if (handoffTimeout)
|
|
5482
|
+
clearTimeout(handoffTimeout);
|
|
5483
|
+
handoffTimeout = null;
|
|
5484
|
+
};
|
|
5485
|
+
const callListener = (event, handler, payload) => {
|
|
5486
|
+
var _a;
|
|
5487
|
+
try {
|
|
5488
|
+
handler(payload);
|
|
5489
|
+
}
|
|
5490
|
+
catch (error) {
|
|
5491
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime listener failed', {
|
|
5492
|
+
event,
|
|
5493
|
+
error: describeError(error),
|
|
5494
|
+
});
|
|
5495
|
+
}
|
|
5496
|
+
};
|
|
5497
|
+
const emit = (event, payload) => {
|
|
5498
|
+
const handlers = listeners.get(event);
|
|
5499
|
+
if (!handlers)
|
|
5500
|
+
return;
|
|
5501
|
+
for (const handler of Array.from(handlers)) {
|
|
5502
|
+
if (handlers.has(handler))
|
|
5503
|
+
callListener(event, handler, payload);
|
|
5504
|
+
}
|
|
5505
|
+
};
|
|
5506
|
+
const on = (event, handler) => {
|
|
5507
|
+
var _a;
|
|
5508
|
+
const handlers = (_a = listeners.get(event)) !== null && _a !== void 0 ? _a : new Set();
|
|
5509
|
+
const entry = (payload) => handler(payload);
|
|
5510
|
+
handlers.add(entry);
|
|
5511
|
+
listeners.set(event, handlers);
|
|
5512
|
+
return () => {
|
|
5513
|
+
handlers.delete(entry);
|
|
5514
|
+
if (handlers.size === 0 && listeners.get(event) === handlers) {
|
|
5515
|
+
listeners.delete(event);
|
|
5516
|
+
}
|
|
5517
|
+
};
|
|
5518
|
+
};
|
|
5519
|
+
const stickySatisfied = (event) => {
|
|
5520
|
+
if (event === 'sdkReady')
|
|
5521
|
+
return state.sdkReady;
|
|
5522
|
+
if (event === 'hydrated')
|
|
5523
|
+
return state.hydrated;
|
|
5524
|
+
if (event === 'pageRevealed')
|
|
5525
|
+
return state.pageRevealed;
|
|
5526
|
+
return false;
|
|
5527
|
+
};
|
|
5528
|
+
const stickyPayload = (event) => event === 'pageRevealed'
|
|
5529
|
+
? {
|
|
5530
|
+
...state,
|
|
5531
|
+
reason: options.revealCoordinator.getSnapshot().revealedReason,
|
|
5532
|
+
}
|
|
5533
|
+
: { ...state };
|
|
5534
|
+
const once = (event, handler) => {
|
|
5535
|
+
if (stickySatisfied(event)) {
|
|
5536
|
+
callListener(event, handler, stickyPayload(event));
|
|
5537
|
+
return () => { };
|
|
5538
|
+
}
|
|
5539
|
+
let stop = () => { };
|
|
5540
|
+
stop = on(event, (payload) => {
|
|
5541
|
+
stop();
|
|
5542
|
+
handler(payload);
|
|
5543
|
+
});
|
|
5544
|
+
return stop;
|
|
5545
|
+
};
|
|
5546
|
+
const clearBatchTimeout = (batch) => {
|
|
5547
|
+
if (!batch.timeout)
|
|
5548
|
+
return;
|
|
5549
|
+
clearTimeout(batch.timeout);
|
|
5550
|
+
batch.timeout = null;
|
|
5551
|
+
};
|
|
5552
|
+
const revealFromRuntime = (batchId, reveal) => {
|
|
5553
|
+
var _a;
|
|
5554
|
+
try {
|
|
5555
|
+
reveal();
|
|
5556
|
+
}
|
|
5557
|
+
catch (error) {
|
|
5558
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime page reveal failed', {
|
|
5559
|
+
error: describeError(error),
|
|
5560
|
+
...(batchId ? { batchId } : {}),
|
|
5561
|
+
routeVersion: state.routeVersion,
|
|
5562
|
+
});
|
|
5563
|
+
}
|
|
5564
|
+
};
|
|
5565
|
+
const forceRevealOnTimeout = (batchId) => revealFromRuntime(batchId, () => options.revealCoordinator.forceReveal('batch-timeout'));
|
|
5566
|
+
const releaseBatch = (batch) => {
|
|
5567
|
+
if (batch.released)
|
|
5568
|
+
return;
|
|
5569
|
+
batch.released = true;
|
|
5570
|
+
clearBatchTimeout(batch);
|
|
5571
|
+
revealFromRuntime(batch.batchId, () => options.revealCoordinator.release(batch.batchId, 'batch-settled'));
|
|
5572
|
+
};
|
|
5573
|
+
const warnForSession = (session, message) => {
|
|
5574
|
+
var _a;
|
|
5575
|
+
const sessionState = session.getState();
|
|
5576
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, message, {
|
|
5577
|
+
variantId: sessionState.variantId,
|
|
5578
|
+
batchId: sessionState.batchId,
|
|
5579
|
+
});
|
|
5580
|
+
};
|
|
5581
|
+
const maybeReleaseBatch = (batch) => {
|
|
5582
|
+
if (activeBatch !== batch ||
|
|
5583
|
+
batch.released ||
|
|
5584
|
+
!batch.closed ||
|
|
5585
|
+
batch.sessions.size < batch.expectedSessionCount)
|
|
5586
|
+
return;
|
|
5587
|
+
if (Array.from(batch.sessions.values()).every((session) => session.isSettledForReveal())) {
|
|
5588
|
+
releaseBatch(batch);
|
|
5589
|
+
}
|
|
5590
|
+
};
|
|
5591
|
+
const warnUnsettled = (batch, reason) => {
|
|
5592
|
+
var _a;
|
|
5593
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime batch timed out', {
|
|
5594
|
+
batchId: batch.batchId,
|
|
5595
|
+
routeVersion: batch.routeVersion,
|
|
5596
|
+
reason,
|
|
5597
|
+
expectedSessionCount: batch.expectedSessionCount,
|
|
5598
|
+
createdSessionCount: batch.sessions.size,
|
|
5599
|
+
unsettledVariantIds: Array.from(batch.sessions.values())
|
|
5600
|
+
.filter((session) => !session.isSettledForReveal())
|
|
5601
|
+
.map((session) => session.getState().variantId),
|
|
5602
|
+
});
|
|
5603
|
+
};
|
|
5604
|
+
const invalidateBatch = (batchId) => {
|
|
5605
|
+
if (!activeBatch || activeBatch.batchId !== batchId)
|
|
5606
|
+
return;
|
|
5607
|
+
const batch = activeBatch;
|
|
5608
|
+
activeBatch = null;
|
|
5609
|
+
clearBatchTimeout(batch);
|
|
5610
|
+
for (const session of batch.sessions.values())
|
|
5611
|
+
session.invalidate();
|
|
5612
|
+
};
|
|
5613
|
+
const coordinatorStop = options.revealCoordinator.onRevealed((reason) => {
|
|
5614
|
+
if (state.pageRevealed)
|
|
5615
|
+
return;
|
|
5616
|
+
if (activeBatch && !activeBatch.released && reason !== 'batch-settled') {
|
|
5617
|
+
warnUnsettled(activeBatch, reason);
|
|
5618
|
+
}
|
|
5619
|
+
state.pageRevealed = true;
|
|
5620
|
+
clearHandoff();
|
|
5621
|
+
if (activeBatch) {
|
|
5622
|
+
activeBatch.released = true;
|
|
5623
|
+
clearBatchTimeout(activeBatch);
|
|
5624
|
+
for (const session of activeBatch.sessions.values()) {
|
|
5625
|
+
session.markPageRevealed();
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
emit('pageRevealed', { ...state, reason });
|
|
5629
|
+
});
|
|
5630
|
+
const runtime = {
|
|
5631
|
+
getState: () => ({ ...state }),
|
|
5632
|
+
on,
|
|
5633
|
+
once,
|
|
5634
|
+
publishSdkReady() {
|
|
5635
|
+
if (state.sdkReady)
|
|
5636
|
+
return;
|
|
5637
|
+
state.sdkReady = true;
|
|
5638
|
+
emit('sdkReady', { ...state });
|
|
5639
|
+
},
|
|
5640
|
+
publishHydrated() {
|
|
5641
|
+
if (state.hydrated)
|
|
5642
|
+
return;
|
|
5643
|
+
state.hydrated = true;
|
|
5644
|
+
emit('hydrated', { ...state });
|
|
5645
|
+
},
|
|
5646
|
+
settleRoute() {
|
|
5647
|
+
if (destroyed)
|
|
5648
|
+
return;
|
|
5649
|
+
if (handoffBlockerId) {
|
|
5650
|
+
const batchId = handoffBlockerId;
|
|
5651
|
+
clearHandoff();
|
|
5652
|
+
revealFromRuntime(batchId, () => options.revealCoordinator.release(batchId, 'no-variants'));
|
|
5653
|
+
return;
|
|
5654
|
+
}
|
|
5655
|
+
if (!state.pageRevealed && !activeBatch) {
|
|
5656
|
+
revealFromRuntime(null, () => options.revealCoordinator.requestReveal('no-variants'));
|
|
5657
|
+
}
|
|
5658
|
+
},
|
|
5659
|
+
updateUrl(currentUrl) {
|
|
5660
|
+
if (!destroyed)
|
|
5661
|
+
state.currentUrl = currentUrl;
|
|
5662
|
+
},
|
|
5663
|
+
changeRoute(currentUrl) {
|
|
5664
|
+
if (destroyed)
|
|
5665
|
+
return;
|
|
5666
|
+
if (activeBatch) {
|
|
5667
|
+
const { batchId } = activeBatch;
|
|
5668
|
+
invalidateBatch(batchId);
|
|
5669
|
+
if (!state.pageRevealed) {
|
|
5670
|
+
handoffBlockerId = batchId;
|
|
5671
|
+
const abandonedRouteVersion = state.routeVersion;
|
|
5672
|
+
if (!handoffTimeout) {
|
|
5673
|
+
handoffTimeout = setTimeout(() => {
|
|
5674
|
+
var _a;
|
|
5675
|
+
if (!handoffBlockerId)
|
|
5676
|
+
return;
|
|
5677
|
+
const handedOverBatchId = handoffBlockerId;
|
|
5678
|
+
(_a = options.warn) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime route handoff timed out', {
|
|
5679
|
+
routeVersion: abandonedRouteVersion,
|
|
5680
|
+
});
|
|
5681
|
+
clearHandoff();
|
|
5682
|
+
forceRevealOnTimeout(handedOverBatchId);
|
|
5683
|
+
}, timeUntilDeadline());
|
|
5684
|
+
}
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
state.routeVersion += 1;
|
|
5688
|
+
state.currentUrl = currentUrl;
|
|
5689
|
+
},
|
|
5690
|
+
beginBatch(input) {
|
|
5691
|
+
if (destroyed || input.expectedSessionCount === 0)
|
|
5692
|
+
return null;
|
|
5693
|
+
if (activeBatch)
|
|
5694
|
+
invalidateBatch(activeBatch.batchId);
|
|
5695
|
+
clearHandoff();
|
|
5696
|
+
const batch = {
|
|
5697
|
+
batchId: createId(),
|
|
5698
|
+
routeVersion: state.routeVersion,
|
|
5699
|
+
expectedSessionCount: input.expectedSessionCount,
|
|
5700
|
+
closed: false,
|
|
5701
|
+
released: state.pageRevealed,
|
|
5702
|
+
sessions: new Map(),
|
|
5703
|
+
timeout: null,
|
|
5704
|
+
};
|
|
5705
|
+
activeBatch = batch;
|
|
5706
|
+
if (!state.pageRevealed &&
|
|
5707
|
+
options.revealCoordinator.block(batch.batchId)) {
|
|
5708
|
+
batch.timeout = setTimeout(() => {
|
|
5709
|
+
if (activeBatch !== batch || batch.released)
|
|
5710
|
+
return;
|
|
5711
|
+
warnUnsettled(batch, 'batch-timeout');
|
|
5712
|
+
batch.released = true;
|
|
5713
|
+
forceRevealOnTimeout(batch.batchId);
|
|
5714
|
+
}, timeUntilDeadline());
|
|
5715
|
+
}
|
|
5716
|
+
return { batchId: batch.batchId, routeVersion: batch.routeVersion };
|
|
5717
|
+
},
|
|
5718
|
+
createVariantSession(input) {
|
|
5719
|
+
var _a;
|
|
5720
|
+
const batch = activeBatch;
|
|
5721
|
+
if (!batch ||
|
|
5722
|
+
batch.batchId !== input.batchId ||
|
|
5723
|
+
batch.routeVersion !== state.routeVersion ||
|
|
5724
|
+
(batch.released && !state.pageRevealed))
|
|
5725
|
+
return null;
|
|
5726
|
+
const session = createVariantSession({
|
|
5727
|
+
sessionId: createId(),
|
|
5728
|
+
variantId: input.variantId,
|
|
5729
|
+
batchId: batch.batchId,
|
|
5730
|
+
routeVersion: batch.routeVersion,
|
|
5731
|
+
getPageState: () => ({ ...state }),
|
|
5732
|
+
subscribePage: (event, handler, onlyOnce) => {
|
|
5733
|
+
if (onlyOnce)
|
|
5734
|
+
return once(event, handler);
|
|
5735
|
+
if (stickySatisfied(event)) {
|
|
5736
|
+
callListener(event, handler, stickyPayload(event));
|
|
5737
|
+
}
|
|
5738
|
+
return on(event, handler);
|
|
5739
|
+
},
|
|
5740
|
+
onStateChange: () => maybeReleaseBatch(batch),
|
|
5741
|
+
emitLegacyVariantRendered: (_a = options.emitLegacyVariantRendered) !== null && _a !== void 0 ? _a : (() => { }),
|
|
5742
|
+
ingestApplied: (currentSession, payload) => {
|
|
5743
|
+
var _a, _b;
|
|
5744
|
+
return (_b = (_a = options.onVariantApplied) === null || _a === void 0 ? void 0 : _a.call(options, { ...state }, currentSession.getState(), payload)) !== null && _b !== void 0 ? _b : false;
|
|
5745
|
+
},
|
|
5746
|
+
onIngestLimit: (currentSession) => warnForSession(currentSession, 'Variant ingestApplied limit reached'),
|
|
5747
|
+
onMetadataDropped: (currentSession) => warnForSession(currentSession, 'Variant ingestApplied metadata dropped'),
|
|
5748
|
+
onError: (currentSession, error) => {
|
|
5749
|
+
var _a;
|
|
5750
|
+
const sessionState = currentSession.getState();
|
|
5751
|
+
(_a = options.error) === null || _a === void 0 ? void 0 : _a.call(options, 'Variant runtime session failed', {
|
|
5752
|
+
error: describeError(error),
|
|
5753
|
+
variantId: sessionState.variantId,
|
|
5754
|
+
sessionId: sessionState.sessionId,
|
|
5755
|
+
batchId: sessionState.batchId,
|
|
5756
|
+
routeVersion: sessionState.routeVersion,
|
|
5757
|
+
currentUrl: state.currentUrl,
|
|
5758
|
+
});
|
|
5759
|
+
},
|
|
5760
|
+
});
|
|
5761
|
+
if (state.pageRevealed)
|
|
5762
|
+
session.markPageRevealed();
|
|
5763
|
+
batch.sessions.set(session.getState().sessionId, session);
|
|
5764
|
+
maybeReleaseBatch(batch);
|
|
5765
|
+
return session;
|
|
5766
|
+
},
|
|
5767
|
+
closeBatch(batchId) {
|
|
5768
|
+
if (!activeBatch || activeBatch.batchId !== batchId)
|
|
5769
|
+
return;
|
|
5770
|
+
activeBatch.closed = true;
|
|
5771
|
+
maybeReleaseBatch(activeBatch);
|
|
5772
|
+
},
|
|
5773
|
+
invalidateBatch,
|
|
5774
|
+
invalidateAllSessions() {
|
|
5775
|
+
if (activeBatch)
|
|
5776
|
+
invalidateBatch(activeBatch.batchId);
|
|
5777
|
+
},
|
|
5778
|
+
getDebugSnapshot() {
|
|
5779
|
+
return {
|
|
5780
|
+
page: { ...state },
|
|
5781
|
+
activeBatch: activeBatch
|
|
5782
|
+
? {
|
|
5783
|
+
batchId: activeBatch.batchId,
|
|
5784
|
+
routeVersion: activeBatch.routeVersion,
|
|
5785
|
+
expectedSessionCount: activeBatch.expectedSessionCount,
|
|
5786
|
+
closed: activeBatch.closed,
|
|
5787
|
+
released: activeBatch.released,
|
|
5788
|
+
sessionStates: Array.from(activeBatch.sessions.values()).map((session) => session.getState()),
|
|
5789
|
+
}
|
|
5790
|
+
: null,
|
|
5791
|
+
};
|
|
5792
|
+
},
|
|
5793
|
+
isDestroyed: () => destroyed,
|
|
5794
|
+
destroy() {
|
|
5795
|
+
destroyed = true;
|
|
5796
|
+
const blockerIds = [handoffBlockerId, activeBatch === null || activeBatch === void 0 ? void 0 : activeBatch.batchId];
|
|
5797
|
+
if (activeBatch)
|
|
5798
|
+
invalidateBatch(activeBatch.batchId);
|
|
5799
|
+
clearHandoff();
|
|
5800
|
+
for (const blockerId of blockerIds) {
|
|
5801
|
+
if (blockerId)
|
|
5802
|
+
options.revealCoordinator.unblock(blockerId);
|
|
5803
|
+
}
|
|
5804
|
+
coordinatorStop();
|
|
5805
|
+
listeners.clear();
|
|
5806
|
+
},
|
|
5807
|
+
};
|
|
5808
|
+
return runtime;
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
const MAX_TARGET_SELECTORS = 20;
|
|
5812
|
+
const MAX_SELECTOR_LENGTH = 500;
|
|
5813
|
+
const MAX_METADATA_JSON_BYTES = 4096;
|
|
5814
|
+
const MAX_METADATA_VISITS = 20000;
|
|
5815
|
+
const MAX_SELECTOR_ENTRIES_SCANNED = 200;
|
|
5816
|
+
function utf8ByteLength(value) {
|
|
5817
|
+
let bytes = 0;
|
|
5818
|
+
for (let i = 0; i < value.length; i++) {
|
|
5819
|
+
const code = value.charCodeAt(i);
|
|
5820
|
+
if (code < 0x80)
|
|
5821
|
+
bytes += 1;
|
|
5822
|
+
else if (code < 0x800)
|
|
5823
|
+
bytes += 2;
|
|
5824
|
+
else if (code >= 0xd800 && code <= 0xdbff) {
|
|
5825
|
+
const next = value.charCodeAt(i + 1);
|
|
5826
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
5827
|
+
bytes += 4;
|
|
5828
|
+
i++;
|
|
5829
|
+
}
|
|
5830
|
+
else {
|
|
5831
|
+
bytes += 3;
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
else
|
|
5835
|
+
bytes += 3;
|
|
5836
|
+
}
|
|
5837
|
+
return bytes;
|
|
5838
|
+
}
|
|
5839
|
+
function isBoxedString(value) {
|
|
5840
|
+
if (!value || typeof value !== 'object')
|
|
5841
|
+
return false;
|
|
5842
|
+
try {
|
|
5843
|
+
String.prototype.valueOf.call(value);
|
|
5844
|
+
return true;
|
|
5845
|
+
}
|
|
5846
|
+
catch (_a) {
|
|
5847
|
+
return false;
|
|
5848
|
+
}
|
|
5849
|
+
}
|
|
5850
|
+
function sanitizeMetadata(payload) {
|
|
5851
|
+
try {
|
|
5852
|
+
const rawMetadata = payload.metadata;
|
|
5853
|
+
if (rawMetadata === undefined || rawMetadata === null)
|
|
5854
|
+
return {};
|
|
5855
|
+
if (typeof rawMetadata !== 'object' || Array.isArray(rawMetadata)) {
|
|
5856
|
+
return null;
|
|
5857
|
+
}
|
|
5858
|
+
let minimumLength = 0;
|
|
5859
|
+
let visits = 0;
|
|
5860
|
+
const json = JSON.stringify(rawMetadata, function (key, value) {
|
|
5861
|
+
visits += 1;
|
|
5862
|
+
if (visits > MAX_METADATA_VISITS) {
|
|
5863
|
+
throw new Error('metadata too large');
|
|
5864
|
+
}
|
|
5865
|
+
const isObjectProperty = visits > 1 && !Array.isArray(this);
|
|
5866
|
+
if (isObjectProperty &&
|
|
5867
|
+
(value === undefined ||
|
|
5868
|
+
typeof value === 'function' ||
|
|
5869
|
+
typeof value === 'symbol')) {
|
|
5870
|
+
return value;
|
|
5871
|
+
}
|
|
5872
|
+
if (isObjectProperty)
|
|
5873
|
+
minimumLength += key.length + 3;
|
|
5874
|
+
const written = isBoxedString(value) ? String(value) : value;
|
|
5875
|
+
minimumLength += typeof written === 'string' ? written.length + 2 : 1;
|
|
5876
|
+
if (minimumLength > MAX_METADATA_JSON_BYTES) {
|
|
5877
|
+
throw new Error('metadata too large');
|
|
5878
|
+
}
|
|
5879
|
+
return written;
|
|
5880
|
+
});
|
|
5881
|
+
if (!json ||
|
|
5882
|
+
json.length > MAX_METADATA_JSON_BYTES ||
|
|
5883
|
+
utf8ByteLength(json) > MAX_METADATA_JSON_BYTES) {
|
|
5884
|
+
return null;
|
|
5885
|
+
}
|
|
5886
|
+
const copy = JSON.parse(json);
|
|
5887
|
+
return copy && typeof copy === 'object' && !Array.isArray(copy)
|
|
5888
|
+
? copy
|
|
5889
|
+
: null;
|
|
5890
|
+
}
|
|
5891
|
+
catch (_a) {
|
|
5892
|
+
return null;
|
|
5893
|
+
}
|
|
5894
|
+
}
|
|
5895
|
+
function sanitizeSelectors(payload) {
|
|
5896
|
+
try {
|
|
5897
|
+
const selectors = payload.targetSelectors;
|
|
5898
|
+
if (!Array.isArray(selectors))
|
|
5899
|
+
return [];
|
|
5900
|
+
const kept = [];
|
|
5901
|
+
const scanned = Math.min(selectors.length, MAX_SELECTOR_ENTRIES_SCANNED);
|
|
5902
|
+
for (let i = 0; i < scanned; i++) {
|
|
5903
|
+
let selector;
|
|
5904
|
+
try {
|
|
5905
|
+
selector = selectors[i];
|
|
5906
|
+
}
|
|
5907
|
+
catch (_a) {
|
|
5908
|
+
continue;
|
|
5909
|
+
}
|
|
5910
|
+
if (typeof selector === 'string' &&
|
|
5911
|
+
selector.length > 0 &&
|
|
5912
|
+
selector.length <= MAX_SELECTOR_LENGTH) {
|
|
5913
|
+
kept.push(selector);
|
|
5914
|
+
if (kept.length === MAX_TARGET_SELECTORS)
|
|
5915
|
+
break;
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
return kept;
|
|
5919
|
+
}
|
|
5920
|
+
catch (_b) {
|
|
5921
|
+
return [];
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
function sanitizePayload(payloadInput) {
|
|
5925
|
+
if (!payloadInput ||
|
|
5926
|
+
typeof payloadInput !== 'object' ||
|
|
5927
|
+
Array.isArray(payloadInput)) {
|
|
5928
|
+
const given = payloadInput !== undefined && payloadInput !== null;
|
|
5929
|
+
return { metadata: {}, metadataDropped: given, targetSelectors: [] };
|
|
5930
|
+
}
|
|
5931
|
+
const payload = payloadInput;
|
|
5932
|
+
const metadata = sanitizeMetadata(payload);
|
|
5933
|
+
return {
|
|
5934
|
+
metadata: metadata || {},
|
|
5935
|
+
metadataDropped: metadata === null,
|
|
5936
|
+
targetSelectors: sanitizeSelectors(payload),
|
|
5937
|
+
};
|
|
5938
|
+
}
|
|
5939
|
+
function ingestVariantApplied(coframe, pageState, sessionState, payloadInput) {
|
|
5940
|
+
const { metadata, metadataDropped, targetSelectors } = sanitizePayload(payloadInput);
|
|
5941
|
+
const personalizationHeaders = coframe.personalizationHeaders;
|
|
5942
|
+
const event = {
|
|
5943
|
+
type: 'variant_applied',
|
|
5944
|
+
project_id: coframe.projectId || '',
|
|
5945
|
+
session_id: coframe.sessionId || '',
|
|
5946
|
+
time: new Date(),
|
|
5947
|
+
url: getCurrentPageUrl(),
|
|
5948
|
+
user_token: coframe.userToken || '',
|
|
5949
|
+
org_id: coframe.orgId || '',
|
|
5950
|
+
variant_id: sessionState.variantId,
|
|
5951
|
+
content: {
|
|
5952
|
+
metadata: {
|
|
5953
|
+
batch_id: sessionState.batchId,
|
|
5954
|
+
route_version: pageState.routeVersion,
|
|
5955
|
+
current_url: pageState.currentUrl,
|
|
5956
|
+
...(personalizationHeaders
|
|
5957
|
+
? { personalization_headers: { ...personalizationHeaders } }
|
|
5958
|
+
: {}),
|
|
5959
|
+
...(targetSelectors.length
|
|
5960
|
+
? { target_selectors: targetSelectors }
|
|
5961
|
+
: {}),
|
|
5962
|
+
...(Object.keys(metadata).length ? { custom_data: metadata } : {}),
|
|
5963
|
+
},
|
|
5964
|
+
},
|
|
5965
|
+
};
|
|
5966
|
+
try {
|
|
5967
|
+
document.dispatchEvent(new CustomEvent('coframe:variant-applied', {
|
|
5968
|
+
detail: {
|
|
5969
|
+
...event,
|
|
5970
|
+
time: new Date(event.time.getTime()),
|
|
5971
|
+
content: {
|
|
5972
|
+
metadata: JSON.parse(JSON.stringify(event.content.metadata)),
|
|
5973
|
+
},
|
|
5974
|
+
},
|
|
5975
|
+
}));
|
|
5976
|
+
}
|
|
5977
|
+
catch (_a) {
|
|
5978
|
+
}
|
|
5979
|
+
if (coframe.projectId)
|
|
5980
|
+
queueEventForBatchedSending(coframe, event);
|
|
5981
|
+
return metadataDropped;
|
|
5982
|
+
}
|
|
5983
|
+
|
|
4754
5984
|
function push(cmd) {
|
|
4755
5985
|
if (typeof window === 'undefined')
|
|
4756
5986
|
return;
|
|
@@ -4798,34 +6028,81 @@ const Coframe = {
|
|
|
4798
6028
|
},
|
|
4799
6029
|
};
|
|
4800
6030
|
Coframe.logger = createLogger(() => Coframe);
|
|
4801
|
-
|
|
6031
|
+
const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
6032
|
+
let activeRuntime = null;
|
|
6033
|
+
function clearActiveRuntime() {
|
|
6034
|
+
activeRuntime === null || activeRuntime === void 0 ? void 0 : activeRuntime.destroy();
|
|
6035
|
+
activeRuntime = null;
|
|
6036
|
+
delete Coframe.runtime;
|
|
6037
|
+
}
|
|
6038
|
+
function installRuntime(config) {
|
|
6039
|
+
const batchTimeoutMs = config.timeoutMs
|
|
6040
|
+
? Math.min(config.timeoutMs, TIMEOUT_MAX_MS)
|
|
6041
|
+
: undefined;
|
|
6042
|
+
clearActiveRuntime();
|
|
6043
|
+
if (!isVariantRuntimeLifecycleEnabled(config))
|
|
6044
|
+
return undefined;
|
|
6045
|
+
const runtime = createPageRuntime({
|
|
6046
|
+
revealCoordinator,
|
|
6047
|
+
currentUrl: getCurrentPageUrl(),
|
|
6048
|
+
initialHydrated: !config.waitForHydration,
|
|
6049
|
+
batchTimeoutMs,
|
|
6050
|
+
warn: (message, metadata) => Coframe.logger.warn(message, 'variant_runtime_warning', metadata),
|
|
6051
|
+
error: (message, metadata) => Coframe.logger.error(message, 'variant_runtime_error', metadata),
|
|
6052
|
+
emitLegacyVariantRendered: variantRendered,
|
|
6053
|
+
onVariantApplied: (pageState, sessionState, payload) => ingestVariantApplied(Coframe, pageState, sessionState, payload),
|
|
6054
|
+
});
|
|
6055
|
+
activeRuntime = runtime;
|
|
6056
|
+
Coframe.runtime = Object.freeze({
|
|
6057
|
+
getSnapshot: () => runtime.getDebugSnapshot(),
|
|
6058
|
+
});
|
|
6059
|
+
runtime.publishSdkReady();
|
|
6060
|
+
return runtime;
|
|
6061
|
+
}
|
|
6062
|
+
async function initialize(options, context = {}) {
|
|
4802
6063
|
if (typeof window !== 'undefined' &&
|
|
4803
6064
|
/cf_disable/.test(window.location.search)) {
|
|
4804
6065
|
console.warn('[Coframe] Disabled via URL parameter');
|
|
4805
|
-
|
|
6066
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4806
6067
|
return;
|
|
4807
6068
|
}
|
|
4808
6069
|
if (options)
|
|
4809
6070
|
injectOptions(options);
|
|
4810
6071
|
const gracefulExit = (msg) => {
|
|
4811
6072
|
console.warn(`[Coframe] ${msg}`);
|
|
4812
|
-
|
|
6073
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
4813
6074
|
return;
|
|
4814
6075
|
};
|
|
4815
|
-
const configResult = await resolveConfig(Coframe,
|
|
6076
|
+
const configResult = await resolveConfig(Coframe, revealCoordinator);
|
|
4816
6077
|
if (!configResult)
|
|
4817
6078
|
return;
|
|
6079
|
+
const runtime = installRuntime(configResult.config);
|
|
6080
|
+
context.runtime = runtime;
|
|
4818
6081
|
loadLauncherIfEnabled(configResult.config.enableDebug, configResult.config.nonce);
|
|
4819
|
-
const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config);
|
|
4820
|
-
if (
|
|
6082
|
+
const projectResult = await resolveProject(Coframe, configResult.previewVariantId, configResult.isSessionVariant, configResult.config, () => !(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()));
|
|
6083
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
4821
6084
|
return;
|
|
6085
|
+
if (!projectResult) {
|
|
6086
|
+
revealCoordinator.forceReveal('graceful-exit');
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
4822
6089
|
Coframe.orgId = projectResult.project.org_id || '';
|
|
4823
6090
|
Coframe.personalizationHeaders = projectResult.personalizationHeaders;
|
|
4824
|
-
const identity = await resolveIdentity(configResult.config, Coframe, configResult.timeoutMs,
|
|
6091
|
+
const identity = await resolveIdentity(configResult.config, Coframe, configResult.timeoutMs, (msg) => {
|
|
6092
|
+
if (!(runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed()))
|
|
6093
|
+
gracefulExit(msg);
|
|
6094
|
+
}, configResult.previewVariantId);
|
|
4825
6095
|
if (!identity)
|
|
4826
6096
|
return;
|
|
6097
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
6098
|
+
return;
|
|
4827
6099
|
const analytics = setupTracking(Coframe, projectResult, configResult.previewVariantId, configResult.executionStartTime, configResult.config.nonce, configResult.config.waitForCookiesEnabled, identity.onConsentChange);
|
|
4828
|
-
await modifyPage(Coframe, configResult, projectResult, analytics
|
|
6100
|
+
await modifyPage(Coframe, configResult, projectResult, analytics, {
|
|
6101
|
+
revealCoordinator,
|
|
6102
|
+
runtime,
|
|
6103
|
+
});
|
|
6104
|
+
if (runtime === null || runtime === void 0 ? void 0 : runtime.isDestroyed())
|
|
6105
|
+
return;
|
|
4829
6106
|
try {
|
|
4830
6107
|
startReplayIfEnabled(Coframe, projectResult.project, configResult.config.nonce);
|
|
4831
6108
|
}
|
|
@@ -4833,6 +6110,19 @@ async function init(options) {
|
|
|
4833
6110
|
console.warn('[Coframe] Failed to start session replay', error);
|
|
4834
6111
|
}
|
|
4835
6112
|
}
|
|
6113
|
+
async function init(options) {
|
|
6114
|
+
var _a;
|
|
6115
|
+
const context = {};
|
|
6116
|
+
try {
|
|
6117
|
+
await initialize(options, context);
|
|
6118
|
+
}
|
|
6119
|
+
catch (error) {
|
|
6120
|
+
if (!((_a = context.runtime) === null || _a === void 0 ? void 0 : _a.isDestroyed())) {
|
|
6121
|
+
revealCoordinator.forceReveal('init-error');
|
|
6122
|
+
}
|
|
6123
|
+
throw error;
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
4836
6126
|
Coframe.debug = {
|
|
4837
6127
|
getSelector,
|
|
4838
6128
|
getElement,
|