@coframe/sdk 1.1.56 → 1.1.60
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/README.md +34 -6
- package/dist/pkg/cf.cjs.js +290 -39
- package/dist/pkg/cf.esm.mjs +290 -39
- package/dist/pkg/server.cjs.js +52 -13
- package/dist/pkg/server.esm.mjs +52 -14
- package/dist/pkg/types/index.d.ts +1 -0
- package/dist/pkg/types/react.d.ts +1 -0
- package/dist/pkg/types/server.d.ts +8 -3
- package/package.json +12 -14
package/README.md
CHANGED
|
@@ -145,7 +145,11 @@ Pass `projectData` into `<CoframeProvider>` to avoid the extra client-side proje
|
|
|
145
145
|
|
|
146
146
|
Use `getExperimentAssignment()` when the server must know one experiment's
|
|
147
147
|
variant before rendering or calling an upstream service. Edge performs the full
|
|
148
|
-
assignment; the package never falls back to local bucketing.
|
|
148
|
+
assignment; the package never falls back to local bucketing. Use
|
|
149
|
+
`getAssignments()` with the same options minus `experimentId` to get every
|
|
150
|
+
experiment the visitor is assigned on `currentUrl` in one call; the result's
|
|
151
|
+
`variants` lists them in the order the browser applies them and `variant` is
|
|
152
|
+
`null` with reason `no_target_experiment`.
|
|
149
153
|
|
|
150
154
|
```ts
|
|
151
155
|
import {
|
|
@@ -273,14 +277,21 @@ kinds of input only exist in the page, so Edge cannot evaluate them:
|
|
|
273
277
|
on experiments you resolve on the server.
|
|
274
278
|
- Experiments in the same frame are evaluated in order and the first match
|
|
275
279
|
wins. If a JavaScript-targeted experiment sits **ahead of the target in
|
|
276
|
-
the same frame**, Edge
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
the same frame**, Edge cannot tell whether the browser would match it and
|
|
281
|
+
skip the target, so it leaves the whole frame to the browser: the result is
|
|
282
|
+
`variant: null` with reason `not_assigned` (logged on Edge as
|
|
283
|
+
`undecidable_audience`) and the browser renders the frame's pick. Keep
|
|
284
|
+
JavaScript-targeted experiments out of the target experiment's frame, or
|
|
285
|
+
place them after the target.
|
|
279
286
|
- The same applies to a JavaScript-targeted experiment that shares a
|
|
280
287
|
**lane** with the target, wherever it sits: lane winners are drawn from
|
|
281
288
|
the experiments the visitor can reach, and Edge cannot tell whether that
|
|
282
|
-
one is reachable
|
|
283
|
-
|
|
289
|
+
one is reachable, so it leaves every frame holding a member of that lane
|
|
290
|
+
to the browser (`variant: null`, reason `not_assigned`, logged as
|
|
291
|
+
`undecidable_lane`) unless an earlier visit already claimed the lane; a
|
|
292
|
+
lane member in a frame whose own activation needs the page counts the
|
|
293
|
+
same way. Keep
|
|
294
|
+
JavaScript-targeted experiments out of the target's lane.
|
|
284
295
|
- **CMAB experiments.** A target CMAB experiment returns `variant: null` with
|
|
285
296
|
reason `unsupported_experiment_type`.
|
|
286
297
|
- **URL fragments.** A server never sees the `#fragment` of the page URL, and
|
|
@@ -601,6 +612,22 @@ If your app uses an older TypeScript module resolution mode, prefer moving to on
|
|
|
601
612
|
|
|
602
613
|
Modern resolution modes handle package subpath exports more reliably.
|
|
603
614
|
|
|
615
|
+
## SDK development
|
|
616
|
+
|
|
617
|
+
When building this SDK from source, use the Node.js version pinned in `.nvmrc`.
|
|
618
|
+
SDK CI reads the same `.nvmrc` file through `actions/setup-node`.
|
|
619
|
+
|
|
620
|
+
From `sdk/sdk-js`, run:
|
|
621
|
+
|
|
622
|
+
```sh
|
|
623
|
+
nvm install
|
|
624
|
+
nvm use
|
|
625
|
+
yarn install --frozen-lockfile
|
|
626
|
+
yarn build:stage
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
SDK Tools use their own `sdk/tools/.nvmrc`.
|
|
630
|
+
|
|
604
631
|
## API overview
|
|
605
632
|
|
|
606
633
|
### `@coframe/sdk`
|
|
@@ -627,6 +654,7 @@ Modern resolution modes handle package subpath exports more reliably.
|
|
|
627
654
|
|
|
628
655
|
- `getProjectData(options)`
|
|
629
656
|
- `getExperimentAssignment(options)`
|
|
657
|
+
- `getAssignments(options)`
|
|
630
658
|
- `generateUserToken()`
|
|
631
659
|
- `evaluateFlags(options)`
|
|
632
660
|
- `getFeatureFlag(key, options)` / `isFeatureEnabled(key, options)`
|
package/dist/pkg/cf.cjs.js
CHANGED
|
@@ -480,6 +480,10 @@ const getSelectorWithoutCache = (element, options) => {
|
|
|
480
480
|
}
|
|
481
481
|
return null;
|
|
482
482
|
}
|
|
483
|
+
const ownId = (el) => {
|
|
484
|
+
const id = el.getAttribute('id');
|
|
485
|
+
return id && !isGeneratedIdentifier(id) ? id : undefined;
|
|
486
|
+
};
|
|
483
487
|
const getParentNode = (element) => {
|
|
484
488
|
return element.parentElement || getShadowHost(element);
|
|
485
489
|
};
|
|
@@ -487,7 +491,7 @@ const getSelectorWithoutCache = (element, options) => {
|
|
|
487
491
|
var _a;
|
|
488
492
|
const selector = {
|
|
489
493
|
t: el.tagName.toLowerCase(),
|
|
490
|
-
id: el
|
|
494
|
+
id: ownId(el),
|
|
491
495
|
text: getInnerText(el) || undefined,
|
|
492
496
|
href: el.getAttribute('href') || undefined,
|
|
493
497
|
dataTestId: el.getAttribute('data-test-id') || undefined,
|
|
@@ -572,12 +576,33 @@ const getElement = (selector, options) => {
|
|
|
572
576
|
return null;
|
|
573
577
|
};
|
|
574
578
|
const resolvedPathCache = new Map();
|
|
579
|
+
const isGeneratedIdentifier = (value) => {
|
|
580
|
+
const token = value.trim();
|
|
581
|
+
if (!token)
|
|
582
|
+
return false;
|
|
583
|
+
const frameworkId = /^(?::r[0-9a-z]+:|«r[0-9a-z]+»|radix-:?r?[0-9a-z]+:?.*|headlessui-[a-z-]+-:?r?[0-9a-z]+:?|react-aria\d+.*|mui-\d+|ember\d+)$/;
|
|
584
|
+
const cssInJsClass = /^(?:css-[0-9a-z]{5,}(?:-[A-Za-z0-9]+)?|sc-[A-Za-z]{5,}|jsx-\d{6,}|[A-Za-z][A-Za-z0-9]*_[A-Za-z0-9]+__[A-Za-z0-9_-]{5})$/;
|
|
585
|
+
if (frameworkId.test(token) || cssInJsClass.test(token))
|
|
586
|
+
return true;
|
|
587
|
+
return token.split(/[-_:.]+/).some((segment) => {
|
|
588
|
+
var _a, _b;
|
|
589
|
+
if (/^\d{8,}$/.test(segment))
|
|
590
|
+
return true;
|
|
591
|
+
if (/^[0-9a-f]{6,}$/.test(segment) &&
|
|
592
|
+
/\d/.test(segment) &&
|
|
593
|
+
/[a-f]/.test(segment))
|
|
594
|
+
return true;
|
|
595
|
+
return (/^[0-9a-zA-Z]{8,}$/.test(segment) &&
|
|
596
|
+
((_b = (_a = segment.match(/\d+|[a-zA-Z]+/g)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) >= 5);
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
const stableId = (id) => id && !isGeneratedIdentifier(id) ? id : undefined;
|
|
575
600
|
const selectorKey = (selector) => selector.fingerprint || JSON.stringify(selector);
|
|
576
601
|
const keepsIdentity = (document, element, selector) => {
|
|
577
602
|
if (element.tagName.toLowerCase() !== selector.t.toLowerCase())
|
|
578
603
|
return false;
|
|
579
604
|
const attrs = [
|
|
580
|
-
['id', selector.id],
|
|
605
|
+
['id', stableId(selector.id)],
|
|
581
606
|
['data-test-id', selector.dataTestId],
|
|
582
607
|
['aria-label', selector.ariaLabel],
|
|
583
608
|
];
|
|
@@ -843,7 +868,7 @@ const generateRelativePaths = (sel, max) => {
|
|
|
843
868
|
const uniqueQueries = [];
|
|
844
869
|
const nonUniqQueries = [];
|
|
845
870
|
const newWipPaths = [];
|
|
846
|
-
if (currentSel.id) {
|
|
871
|
+
if (currentSel.id && !isGeneratedIdentifier(currentSel.id)) {
|
|
847
872
|
uniqueQueries.push({
|
|
848
873
|
xpath: `[@id="${currentSel.id}"]`,
|
|
849
874
|
css: `#${currentSel.id}`,
|
|
@@ -1638,6 +1663,60 @@ const executeTags = (tags, logger, nonce) => {
|
|
|
1638
1663
|
}
|
|
1639
1664
|
};
|
|
1640
1665
|
|
|
1666
|
+
function isRecord(value) {
|
|
1667
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
const SCOPED = new Set(['update', 'delete', 'insert_into', 'insert_by']);
|
|
1671
|
+
const ADDED = new Set(['add_modal', 'add_banner']);
|
|
1672
|
+
function safeSelector(css) {
|
|
1673
|
+
return css.trim() !== '' && !/[<{}]/.test(css) && !css.includes('/*');
|
|
1674
|
+
}
|
|
1675
|
+
function modificationScope(modifications, elements) {
|
|
1676
|
+
const selectors = [];
|
|
1677
|
+
if (!isRecord(modifications) || !isRecord(elements))
|
|
1678
|
+
return { kind: 'page', reason: 'invalid_variant' };
|
|
1679
|
+
for (const [id, changes] of Object.entries(modifications)) {
|
|
1680
|
+
if (!Array.isArray(changes))
|
|
1681
|
+
return { kind: 'page', reason: 'invalid_variant' };
|
|
1682
|
+
let scoped = false;
|
|
1683
|
+
for (const change of changes) {
|
|
1684
|
+
const type = isRecord(change) ? change.type : undefined;
|
|
1685
|
+
if (typeof type === 'string' && SCOPED.has(type))
|
|
1686
|
+
scoped = true;
|
|
1687
|
+
else if (type === 'execute_js' &&
|
|
1688
|
+
isRecord(change) &&
|
|
1689
|
+
change.css !== undefined) {
|
|
1690
|
+
const { css: list } = change;
|
|
1691
|
+
const declared = Array.isArray(list) ? list : [list];
|
|
1692
|
+
if (declared.length === 0)
|
|
1693
|
+
return { kind: 'page', reason: `unscoped_change:${type}` };
|
|
1694
|
+
for (const css of declared) {
|
|
1695
|
+
if (typeof css !== 'string' || !safeSelector(css))
|
|
1696
|
+
return { kind: 'page', reason: `unscoped_selector:${id}` };
|
|
1697
|
+
selectors.push(css.trim());
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
else if (typeof type !== 'string' || !ADDED.has(type))
|
|
1701
|
+
return { kind: 'page', reason: `unscoped_change:${String(type)}` };
|
|
1702
|
+
}
|
|
1703
|
+
if (!scoped)
|
|
1704
|
+
continue;
|
|
1705
|
+
const conditions = elements[id];
|
|
1706
|
+
if (!Array.isArray(conditions) || conditions.length === 0)
|
|
1707
|
+
return { kind: 'page', reason: 'invalid_variant' };
|
|
1708
|
+
const css = [];
|
|
1709
|
+
for (const condition of conditions) {
|
|
1710
|
+
const value = isRecord(condition) ? condition.css : undefined;
|
|
1711
|
+
if (typeof value !== 'string' || !safeSelector(value))
|
|
1712
|
+
return { kind: 'page', reason: `unscoped_selector:${id}` };
|
|
1713
|
+
css.push(value.trim());
|
|
1714
|
+
}
|
|
1715
|
+
selectors.push(css.join(', '));
|
|
1716
|
+
}
|
|
1717
|
+
return selectors.length ? { kind: 'elements', selectors } : { kind: 'none' };
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1641
1720
|
function getLogCallback() {
|
|
1642
1721
|
const queue = window.CFQ;
|
|
1643
1722
|
if (!queue) {
|
|
@@ -1949,6 +2028,75 @@ function subscribeToCmd(predicate, callback, queueName = QueueName.CFQ) {
|
|
|
1949
2028
|
return __addListener(predicate, callback, queueName);
|
|
1950
2029
|
}
|
|
1951
2030
|
|
|
2031
|
+
function scopeOwners(variants, selectors) {
|
|
2032
|
+
const matches = new Map();
|
|
2033
|
+
const select = (css) => {
|
|
2034
|
+
if (!matches.has(css)) {
|
|
2035
|
+
try {
|
|
2036
|
+
matches.set(css, document.querySelectorAll(css));
|
|
2037
|
+
}
|
|
2038
|
+
catch (_a) {
|
|
2039
|
+
matches.set(css, null);
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
return matches.get(css);
|
|
2043
|
+
};
|
|
2044
|
+
const overlaps = (hidden, changed) => {
|
|
2045
|
+
if (hidden === changed)
|
|
2046
|
+
return true;
|
|
2047
|
+
const left = select(hidden);
|
|
2048
|
+
const right = select(changed);
|
|
2049
|
+
if (!(left === null || left === void 0 ? void 0 : left.length) || !(right === null || right === void 0 ? void 0 : right.length))
|
|
2050
|
+
return true;
|
|
2051
|
+
for (let i = 0; i < left.length; i++) {
|
|
2052
|
+
for (let j = 0; j < right.length; j++) {
|
|
2053
|
+
if (left[i].contains(right[j]) || right[j].contains(left[i]))
|
|
2054
|
+
return true;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return false;
|
|
2058
|
+
};
|
|
2059
|
+
const scopes = variants.map(({ variant, elements }) => modificationScope(variant.modifications, elements));
|
|
2060
|
+
return selectors.map((selector) => {
|
|
2061
|
+
const owners = [];
|
|
2062
|
+
scopes.forEach((scope, index) => {
|
|
2063
|
+
if (scope.kind === 'page' ||
|
|
2064
|
+
(scope.kind === 'elements' &&
|
|
2065
|
+
scope.selectors.some((changed) => overlaps(selector, changed)))) {
|
|
2066
|
+
owners.push(index);
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
return owners;
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
function beginProxyBatch(variants) {
|
|
2073
|
+
const request = findLastCmd((cmd) => cmd.proxyReveal === true);
|
|
2074
|
+
if (!request)
|
|
2075
|
+
return undefined;
|
|
2076
|
+
const queue = ensureQueueExists();
|
|
2077
|
+
const batchId = queue.length + 1;
|
|
2078
|
+
const keys = variants.map(({ variant }, index) => JSON.stringify([variant.experiment_id, variant.id, index]));
|
|
2079
|
+
const owners = request.proxySelectors
|
|
2080
|
+
? scopeOwners(variants, request.proxySelectors)
|
|
2081
|
+
: undefined;
|
|
2082
|
+
queue.push({ proxyBatch: { batchId, keys, ...(owners ? { owners } : {}) } });
|
|
2083
|
+
return (index) => {
|
|
2084
|
+
let completed = false;
|
|
2085
|
+
const complete = (() => {
|
|
2086
|
+
if (completed)
|
|
2087
|
+
return;
|
|
2088
|
+
completed = true;
|
|
2089
|
+
queue.push({ proxyVariantRendered: { batchId, key: keys[index] } });
|
|
2090
|
+
});
|
|
2091
|
+
complete.silent = () => {
|
|
2092
|
+
if (completed)
|
|
2093
|
+
return;
|
|
2094
|
+
queue.push({ proxyVariantSilent: { batchId, key: keys[index] } });
|
|
2095
|
+
};
|
|
2096
|
+
return complete;
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
|
|
1952
2100
|
function describeError(error) {
|
|
1953
2101
|
if (error && (typeof error === 'object' || typeof error === 'function')) {
|
|
1954
2102
|
const stack = readString(() => error.stack);
|
|
@@ -2087,13 +2235,15 @@ function variantRendered() {
|
|
|
2087
2235
|
const queue = ensureQueueExists();
|
|
2088
2236
|
queue.push({
|
|
2089
2237
|
emit: 'variantRendered',
|
|
2238
|
+
sdkVariantRendered: true,
|
|
2090
2239
|
});
|
|
2091
2240
|
}
|
|
2241
|
+
const SIGNALS_COMPLETION = /variantRendered|variantSession/;
|
|
2092
2242
|
function hasJavascriptModification(variantElements) {
|
|
2093
2243
|
const modifications = variantElements.variant.modifications || {};
|
|
2094
2244
|
return Object.keys(modifications).some((elementId) => (modifications[elementId] || []).some((modification) => modification.type === 'execute_js'));
|
|
2095
2245
|
}
|
|
2096
|
-
const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecycleContext) => {
|
|
2246
|
+
const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecycleContext, proxyComplete) => {
|
|
2097
2247
|
var _a;
|
|
2098
2248
|
const { variant, elements } = variantElements;
|
|
2099
2249
|
const variantMetadata = {
|
|
@@ -2101,6 +2251,35 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
|
|
|
2101
2251
|
experiment_id: variant.experiment_id,
|
|
2102
2252
|
};
|
|
2103
2253
|
setApplyVariantChangesStarted(true);
|
|
2254
|
+
const javascriptPending = [];
|
|
2255
|
+
const javascriptSilent = [];
|
|
2256
|
+
let applying = true;
|
|
2257
|
+
const maybeComplete = () => {
|
|
2258
|
+
if (applying)
|
|
2259
|
+
return;
|
|
2260
|
+
if (javascriptPending.every((pending) => !pending))
|
|
2261
|
+
proxyComplete === null || proxyComplete === void 0 ? void 0 : proxyComplete();
|
|
2262
|
+
else if (javascriptPending.every((pending, i) => !pending || javascriptSilent[i]))
|
|
2263
|
+
proxyComplete === null || proxyComplete === void 0 ? void 0 : proxyComplete.silent();
|
|
2264
|
+
};
|
|
2265
|
+
const scriptCompletion = () => {
|
|
2266
|
+
const index = javascriptPending.push(true) - 1;
|
|
2267
|
+
javascriptSilent.push(false);
|
|
2268
|
+
const complete = (() => {
|
|
2269
|
+
var _a;
|
|
2270
|
+
if (!javascriptPending[index])
|
|
2271
|
+
return;
|
|
2272
|
+
javascriptPending[index] = false;
|
|
2273
|
+
(_a = complete.settled) === null || _a === void 0 ? void 0 : _a.call(complete);
|
|
2274
|
+
complete.settled = undefined;
|
|
2275
|
+
maybeComplete();
|
|
2276
|
+
});
|
|
2277
|
+
complete.silent = () => {
|
|
2278
|
+
if (javascriptPending[index])
|
|
2279
|
+
javascriptSilent[index] = true;
|
|
2280
|
+
};
|
|
2281
|
+
return complete;
|
|
2282
|
+
};
|
|
2104
2283
|
const session = lifecycleContext && hasJavascriptModification(variantElements)
|
|
2105
2284
|
? lifecycleContext.runtime.createVariantSession({
|
|
2106
2285
|
batchId: lifecycleContext.batchId,
|
|
@@ -2111,6 +2290,7 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
|
|
|
2111
2290
|
if (!variant.modifications ||
|
|
2112
2291
|
Object.keys(variant.modifications).length === 0) {
|
|
2113
2292
|
variantRendered();
|
|
2293
|
+
proxyComplete === null || proxyComplete === void 0 ? void 0 : proxyComplete();
|
|
2114
2294
|
return true;
|
|
2115
2295
|
}
|
|
2116
2296
|
for (const elementId of Object.keys(variant.modifications)) {
|
|
@@ -2134,7 +2314,7 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
|
|
|
2134
2314
|
}
|
|
2135
2315
|
continue;
|
|
2136
2316
|
}
|
|
2137
|
-
const edited = editElement(selectedElement, variant.modifications, elementId, logger, nonce, variantMetadata, session === null || session === void 0 ? void 0 : session.facade);
|
|
2317
|
+
const edited = editElement(selectedElement, variant.modifications, elementId, logger, nonce, variantMetadata, session === null || session === void 0 ? void 0 : session.facade, proxyComplete ? scriptCompletion : undefined);
|
|
2138
2318
|
if (!edited) {
|
|
2139
2319
|
logger.error('element not edited', 'element_not_edited', {
|
|
2140
2320
|
...variantMetadata,
|
|
@@ -2152,6 +2332,8 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
|
|
|
2152
2332
|
}
|
|
2153
2333
|
}
|
|
2154
2334
|
}
|
|
2335
|
+
applying = false;
|
|
2336
|
+
maybeComplete();
|
|
2155
2337
|
return true;
|
|
2156
2338
|
};
|
|
2157
2339
|
function describeElement(element) {
|
|
@@ -2163,7 +2345,7 @@ function describeElement(element) {
|
|
|
2163
2345
|
const className = target.getAttribute('class');
|
|
2164
2346
|
return `<${String(target.tagName).toLowerCase()}${id ? ` id=${JSON.stringify(id)}` : ''}${className ? ` class=${JSON.stringify(className)}` : ''}>`.slice(0, 200);
|
|
2165
2347
|
}
|
|
2166
|
-
const editElement = (element, modifications, elementId, logger, nonce, logMetadata = {}, variantSession) => {
|
|
2348
|
+
const editElement = (element, modifications, elementId, logger, nonce, logMetadata = {}, variantSession, scriptCompletion) => {
|
|
2167
2349
|
if (!modifications[elementId]) {
|
|
2168
2350
|
logger.warn('no modifications for element', 'no_modifications_for_element', {
|
|
2169
2351
|
...logMetadata,
|
|
@@ -2174,13 +2356,37 @@ const editElement = (element, modifications, elementId, logger, nonce, logMetada
|
|
|
2174
2356
|
}
|
|
2175
2357
|
for (const content of modifications[elementId]) {
|
|
2176
2358
|
switch (content.type) {
|
|
2177
|
-
case 'execute_js':
|
|
2178
|
-
|
|
2179
|
-
|
|
2359
|
+
case 'execute_js': {
|
|
2360
|
+
const complete = scriptCompletion === null || scriptCompletion === void 0 ? void 0 : scriptCompletion();
|
|
2361
|
+
const session = variantSession && complete
|
|
2362
|
+
? {
|
|
2363
|
+
...variantSession,
|
|
2364
|
+
complete() {
|
|
2365
|
+
variantSession.complete();
|
|
2366
|
+
complete();
|
|
2367
|
+
},
|
|
2368
|
+
error(error) {
|
|
2369
|
+
variantSession.error(error);
|
|
2370
|
+
complete();
|
|
2371
|
+
},
|
|
2372
|
+
}
|
|
2373
|
+
: variantSession;
|
|
2374
|
+
const stop = complete &&
|
|
2375
|
+
subscribeToCmd((cmd) => !cmd.sdkVariantRendered &&
|
|
2376
|
+
(cmd.variantRendered === true || cmd.emit === 'variantRendered'), () => complete());
|
|
2377
|
+
if (complete && stop)
|
|
2378
|
+
complete.settled = stop;
|
|
2379
|
+
const applied = applyJavascriptModification(element, content, logger, nonce, logMetadata, session ? { variantSession: session } : undefined);
|
|
2380
|
+
if (!applied)
|
|
2381
|
+
complete === null || complete === void 0 ? void 0 : complete();
|
|
2382
|
+
if (applied && complete && !SIGNALS_COMPLETION.test(content.js))
|
|
2383
|
+
complete.silent();
|
|
2384
|
+
if (!applied && variantSession) {
|
|
2180
2385
|
variantSession.error(new Error('JavaScript variant modification did not run'));
|
|
2181
2386
|
return false;
|
|
2182
2387
|
}
|
|
2183
2388
|
break;
|
|
2389
|
+
}
|
|
2184
2390
|
case 'update':
|
|
2185
2391
|
applyUpdateModification(element, content, logger, logMetadata);
|
|
2186
2392
|
variantRendered();
|
|
@@ -2239,13 +2445,14 @@ function revealPage() {
|
|
|
2239
2445
|
async function replaceVariants(variants, orgId, logger, nonce, runtime) {
|
|
2240
2446
|
const failedReplacements = [];
|
|
2241
2447
|
let currentVariant;
|
|
2448
|
+
const proxyCompletion = beginProxyBatch(variants);
|
|
2242
2449
|
const batch = runtime === null || runtime === void 0 ? void 0 : runtime.beginBatch({
|
|
2243
2450
|
expectedSessionCount: variants.filter(hasJavascriptModification).length,
|
|
2244
2451
|
});
|
|
2245
2452
|
try {
|
|
2246
|
-
for (const v of variants) {
|
|
2453
|
+
for (const [index, v] of variants.entries()) {
|
|
2247
2454
|
currentVariant = v.variant;
|
|
2248
|
-
if (!applyVariantChanges(v, getCurrentPageUrl(), logger, nonce, batch && runtime ? { runtime, batchId: batch.batchId } : undefined)) {
|
|
2455
|
+
if (!applyVariantChanges(v, getCurrentPageUrl(), logger, nonce, batch && runtime ? { runtime, batchId: batch.batchId } : undefined, proxyCompletion === null || proxyCompletion === void 0 ? void 0 : proxyCompletion(index))) {
|
|
2249
2456
|
failedReplacements.push({
|
|
2250
2457
|
message: 'Original element not found',
|
|
2251
2458
|
metadata: {
|
|
@@ -2506,6 +2713,7 @@ function isElementVisible(el) {
|
|
|
2506
2713
|
}
|
|
2507
2714
|
const VALUE_UNKNOWN_YET = 'n/a';
|
|
2508
2715
|
function checkActivationCondition(condition, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger, options) {
|
|
2716
|
+
var _a;
|
|
2509
2717
|
if (condition.and) {
|
|
2510
2718
|
const results = condition.and.map((c) => checkActivationCondition(c, personalizationHeaders, currentUrl, languages, browserPlatform, userToken, userVisitCount, runJsFunction, logger));
|
|
2511
2719
|
const hasUnknown = results.some((r) => r === VALUE_UNKNOWN_YET);
|
|
@@ -2612,12 +2820,13 @@ function checkActivationCondition(condition, personalizationHeaders, currentUrl,
|
|
|
2612
2820
|
}
|
|
2613
2821
|
}
|
|
2614
2822
|
}
|
|
2615
|
-
if (condition.device) {
|
|
2616
|
-
|
|
2617
|
-
if (
|
|
2618
|
-
|
|
2823
|
+
if ((_a = condition.device) === null || _a === void 0 ? void 0 : _a.length) {
|
|
2824
|
+
let device = personalizationHeaders['x-device-type'];
|
|
2825
|
+
if (!['desktop', 'mobile', 'tablet'].includes(device)) {
|
|
2826
|
+
device =
|
|
2827
|
+
personalizationHeaders['x-is-mobile-viewer'] === 'true' ? 'mobile' : '';
|
|
2619
2828
|
}
|
|
2620
|
-
if (condition.device.includes(
|
|
2829
|
+
if (!condition.device.includes(device)) {
|
|
2621
2830
|
return false;
|
|
2622
2831
|
}
|
|
2623
2832
|
}
|
|
@@ -2796,8 +3005,7 @@ function checkAllAudiences(metadata, ctx) {
|
|
|
2796
3005
|
}
|
|
2797
3006
|
return result;
|
|
2798
3007
|
}
|
|
2799
|
-
function audiencesPass(
|
|
2800
|
-
const result = checkAllAudiences(metadata, ctx);
|
|
3008
|
+
function audiencesPass(result, ctx) {
|
|
2801
3009
|
return ctx.unknownIsInactive ? result === true : result !== false;
|
|
2802
3010
|
}
|
|
2803
3011
|
async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}, silentFallback = false, chooseCmabVariants, userVisitCountOverride, runtime = {}) {
|
|
@@ -2879,57 +3087,89 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
|
|
|
2879
3087
|
experimentsInfo: [...experimentsInfo, ...assignment.experimentsInfo],
|
|
2880
3088
|
});
|
|
2881
3089
|
}
|
|
3090
|
+
function frameActivation(frame, ctx) {
|
|
3091
|
+
return checkActivationCondition(frame.activation_condition, ctx.personalizationHeaders, ctx.currentPageUrl, ctx.languages, ctx.browserPlatform, ctx.userToken, ctx.userVisitCount, ctx.runJsFunction, ctx.logger);
|
|
3092
|
+
}
|
|
2882
3093
|
function activateFrames(ctx) {
|
|
2883
3094
|
return ctx.project.frames.filter((frame) => {
|
|
2884
3095
|
if (!frame.experiments)
|
|
2885
3096
|
return false;
|
|
2886
|
-
const frameActivated =
|
|
3097
|
+
const frameActivated = frameActivation(frame, ctx);
|
|
2887
3098
|
return ctx.unknownIsInactive ? frameActivated === true : !!frameActivated;
|
|
2888
3099
|
});
|
|
2889
3100
|
}
|
|
3101
|
+
function undecidedFrames(ctx) {
|
|
3102
|
+
if (!ctx.unknownIsInactive)
|
|
3103
|
+
return [];
|
|
3104
|
+
return ctx.project.frames.filter((frame) => {
|
|
3105
|
+
if (!frame.experiments)
|
|
3106
|
+
return false;
|
|
3107
|
+
const activated = frameActivation(frame, ctx);
|
|
3108
|
+
return activated !== true && activated !== false;
|
|
3109
|
+
});
|
|
3110
|
+
}
|
|
2890
3111
|
async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVariants) {
|
|
2891
|
-
var _a, _b, _c;
|
|
3112
|
+
var _a, _b, _c, _d;
|
|
2892
3113
|
const { project, userToken, logger } = ctx;
|
|
2893
3114
|
const experimentsInfo = [];
|
|
2894
3115
|
const variantsAndElements = [];
|
|
2895
|
-
const
|
|
3116
|
+
const audienceResult = new Map();
|
|
2896
3117
|
const reachableByLane = {};
|
|
3118
|
+
const undecidableLanes = new Set();
|
|
2897
3119
|
for (const frame of activeFrames) {
|
|
2898
3120
|
for (const experiment of (_a = frame.experiments) !== null && _a !== void 0 ? _a : []) {
|
|
2899
3121
|
const metadata = experiment.metadata;
|
|
2900
3122
|
const laneIds = experimentLaneIds(metadata);
|
|
2901
3123
|
if (laneIds.length === 0)
|
|
2902
3124
|
continue;
|
|
2903
|
-
const
|
|
2904
|
-
|
|
2905
|
-
if (
|
|
3125
|
+
const result = checkAllAudiences(metadata, ctx);
|
|
3126
|
+
audienceResult.set(metadata.experiment_id, result);
|
|
3127
|
+
if (audiencesPass(result, ctx)) {
|
|
2906
3128
|
for (const laneId of laneIds) {
|
|
2907
3129
|
if (!reachableByLane[laneId])
|
|
2908
3130
|
reachableByLane[laneId] = [];
|
|
2909
3131
|
reachableByLane[laneId].push(metadata.experiment_id);
|
|
2910
3132
|
}
|
|
2911
3133
|
}
|
|
3134
|
+
else if (ctx.unknownIsInactive && result !== false) {
|
|
3135
|
+
for (const laneId of laneIds)
|
|
3136
|
+
if (!validatedClaims[laneId])
|
|
3137
|
+
undecidableLanes.add(laneId);
|
|
3138
|
+
}
|
|
2912
3139
|
}
|
|
2913
3140
|
}
|
|
3141
|
+
for (const frame of undecidedFrames(ctx))
|
|
3142
|
+
for (const experiment of (_b = frame.experiments) !== null && _b !== void 0 ? _b : [])
|
|
3143
|
+
for (const laneId of experimentLaneIds(experiment.metadata))
|
|
3144
|
+
if (!validatedClaims[laneId])
|
|
3145
|
+
undecidableLanes.add(laneId);
|
|
2914
3146
|
const excludedExperimentIds = resolveLaneWinners(validatedClaims, reachableByLane, project.lanes, userToken);
|
|
2915
3147
|
const updatedClaims = { ...validatedClaims };
|
|
2916
3148
|
const assignmentEntries = [];
|
|
2917
3149
|
let hasActivatedFrames = false;
|
|
2918
3150
|
for (const frame of activeFrames) {
|
|
2919
3151
|
let atLeastOneExperimentWasActivated = false;
|
|
2920
|
-
for (const experiment of (
|
|
3152
|
+
for (const experiment of (_c = frame.experiments) !== null && _c !== void 0 ? _c : []) {
|
|
2921
3153
|
if (atLeastOneExperimentWasActivated)
|
|
2922
3154
|
break;
|
|
2923
3155
|
const metadata = experiment.metadata;
|
|
2924
3156
|
const variants = experiment.variants;
|
|
2925
3157
|
const elements = frame.elements;
|
|
3158
|
+
if (ctx.unknownIsInactive &&
|
|
3159
|
+
experimentLaneIds(metadata).some((id) => undecidableLanes.has(id))) {
|
|
3160
|
+
logger.warn('Leaving the frame to the browser: a lane member needs the page', 'undecidable_lane', { experiment_id: metadata.experiment_id });
|
|
3161
|
+
break;
|
|
3162
|
+
}
|
|
2926
3163
|
if (excludedExperimentIds.has(metadata.experiment_id))
|
|
2927
3164
|
continue;
|
|
2928
|
-
let
|
|
2929
|
-
if (
|
|
2930
|
-
|
|
3165
|
+
let result = audienceResult.get(metadata.experiment_id);
|
|
3166
|
+
if (result === undefined)
|
|
3167
|
+
result = checkAllAudiences(metadata, ctx);
|
|
3168
|
+
if (result !== true && result !== false && ctx.unknownIsInactive) {
|
|
3169
|
+
logger.warn('Leaving the frame to the browser: an audience needs the page', 'undecidable_audience', { experiment_id: metadata.experiment_id });
|
|
3170
|
+
break;
|
|
2931
3171
|
}
|
|
2932
|
-
if (!
|
|
3172
|
+
if (!audiencesPass(result, ctx))
|
|
2933
3173
|
continue;
|
|
2934
3174
|
atLeastOneExperimentWasActivated = true;
|
|
2935
3175
|
hasActivatedFrames = true;
|
|
@@ -2983,7 +3223,7 @@ async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVarian
|
|
|
2983
3223
|
try {
|
|
2984
3224
|
mlVariantIds = await chooseCmabVariants(Array.from(cmabMetadataById.values()), userToken);
|
|
2985
3225
|
}
|
|
2986
|
-
catch (
|
|
3226
|
+
catch (_e) {
|
|
2987
3227
|
mlVariantIds = new Map();
|
|
2988
3228
|
}
|
|
2989
3229
|
}
|
|
@@ -2994,7 +3234,7 @@ async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVarian
|
|
|
2994
3234
|
}
|
|
2995
3235
|
const { metadata, variants, elements } = entry;
|
|
2996
3236
|
let mlVariantId = metadata.type === 'CMAB'
|
|
2997
|
-
? ((
|
|
3237
|
+
? ((_d = mlVariantIds.get(metadata.experiment_id)) !== null && _d !== void 0 ? _d : null)
|
|
2998
3238
|
: null;
|
|
2999
3239
|
if (mlVariantId && !variants.some((v) => v.id === mlVariantId)) {
|
|
3000
3240
|
logger.warn(`CMAB variant ${mlVariantId} not in experiment ${metadata.experiment_id}`, 'cmab_variant_not_found', { experiment_id: metadata.experiment_id });
|
|
@@ -3537,7 +3777,7 @@ function shouldSdkWaitForHydration(config) {
|
|
|
3537
3777
|
!isVariantRuntimeLifecycleEnabled(config));
|
|
3538
3778
|
}
|
|
3539
3779
|
|
|
3540
|
-
function createRevealCoordinator(revealPage, reportError = () => { }) {
|
|
3780
|
+
function createRevealCoordinator(revealPage, reportError = () => { }, onForced = () => { }) {
|
|
3541
3781
|
const notify = (listener, reason) => {
|
|
3542
3782
|
try {
|
|
3543
3783
|
listener(reason);
|
|
@@ -3589,6 +3829,18 @@ function createRevealCoordinator(revealPage, reportError = () => { }) {
|
|
|
3589
3829
|
reveal(reason);
|
|
3590
3830
|
},
|
|
3591
3831
|
forceReveal(reason) {
|
|
3832
|
+
if (pageRevealed)
|
|
3833
|
+
return;
|
|
3834
|
+
try {
|
|
3835
|
+
onForced(reason);
|
|
3836
|
+
}
|
|
3837
|
+
catch (error) {
|
|
3838
|
+
try {
|
|
3839
|
+
reportError(error);
|
|
3840
|
+
}
|
|
3841
|
+
catch (_a) {
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3592
3844
|
reveal(reason);
|
|
3593
3845
|
},
|
|
3594
3846
|
block(batchId) {
|
|
@@ -3776,10 +4028,6 @@ function notifyEventsFlushed() {
|
|
|
3776
4028
|
}
|
|
3777
4029
|
}
|
|
3778
4030
|
|
|
3779
|
-
function isRecord(value) {
|
|
3780
|
-
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3781
|
-
}
|
|
3782
|
-
|
|
3783
4031
|
const OPT_IN_COUNTRIES = new Set(('AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE ' +
|
|
3784
4032
|
'AX GF GP MF MQ RE YT ' +
|
|
3785
4033
|
'IS LI NO ' +
|
|
@@ -4983,6 +5231,7 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
|
|
|
4983
5231
|
Coframe.debug.laneClaims = laneClaims;
|
|
4984
5232
|
}
|
|
4985
5233
|
if (config.trackingOnly) {
|
|
5234
|
+
beginProxyBatch([]);
|
|
4986
5235
|
variantRendered();
|
|
4987
5236
|
}
|
|
4988
5237
|
else {
|
|
@@ -5005,7 +5254,7 @@ async function modifyPage(Coframe, configResult, projectResult, analytics, lifec
|
|
|
5005
5254
|
var _a;
|
|
5006
5255
|
const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
|
|
5007
5256
|
const { project: cdnProject, personalizationHeaders, assignmentContext, } = projectResult;
|
|
5008
|
-
const revealCoordinator = (_a = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.revealCoordinator) !== null && _a !== void 0 ? _a : createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
5257
|
+
const revealCoordinator = (_a = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.revealCoordinator) !== null && _a !== void 0 ? _a : createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger), () => beginProxyBatch([]));
|
|
5009
5258
|
const runtime = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.runtime;
|
|
5010
5259
|
if (runtime && config.waitForHydration) {
|
|
5011
5260
|
if (findLastCmd(isPageHydratedCmd)) {
|
|
@@ -5372,6 +5621,7 @@ async function resolveConfig(Coframe, revealActions) {
|
|
|
5372
5621
|
}
|
|
5373
5622
|
|
|
5374
5623
|
async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit, previewVariantId) {
|
|
5624
|
+
var _a;
|
|
5375
5625
|
const logger = Coframe.logger;
|
|
5376
5626
|
let userToken;
|
|
5377
5627
|
if (config.waitForUserToken) {
|
|
@@ -5389,7 +5639,8 @@ async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit, preview
|
|
|
5389
5639
|
}
|
|
5390
5640
|
}
|
|
5391
5641
|
else {
|
|
5392
|
-
|
|
5642
|
+
const proxied = (_a = findLastCmd((cmd) => typeof cmd.proxyUserToken === 'string' && !!cmd.proxyUserToken)) === null || _a === void 0 ? void 0 : _a.proxyUserToken;
|
|
5643
|
+
userToken = proxied || getOrGenerateUserToken();
|
|
5393
5644
|
}
|
|
5394
5645
|
Coframe.userToken = userToken;
|
|
5395
5646
|
const persistCookies = () => {
|
|
@@ -6382,7 +6633,7 @@ const Coframe = {
|
|
|
6382
6633
|
},
|
|
6383
6634
|
};
|
|
6384
6635
|
Coframe.logger = createLogger(() => Coframe);
|
|
6385
|
-
const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
|
|
6636
|
+
const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger), () => beginProxyBatch([]));
|
|
6386
6637
|
let activeRuntime = null;
|
|
6387
6638
|
function clearActiveRuntime() {
|
|
6388
6639
|
activeRuntime === null || activeRuntime === void 0 ? void 0 : activeRuntime.destroy();
|