@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 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 skips it and assigns the target, while the browser
277
- can match it and skip the target. Keep JavaScript-targeted experiments out
278
- of the target experiment's frame, or place them after the target.
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. Keep JavaScript-targeted experiments out of the target's
283
- lane.
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)`
@@ -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.getAttribute('id') || undefined,
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
- if (!applyJavascriptModification(element, content, logger, nonce, logMetadata, variantSession ? { variantSession } : undefined) &&
2179
- variantSession) {
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
- const isMobileDevice = personalizationHeaders['x-is-mobile-viewer'] === 'true';
2617
- if (condition.device.includes('mobile') && !isMobileDevice) {
2618
- return false;
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('desktop') && isMobileDevice) {
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(metadata, ctx) {
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 = checkActivationCondition(frame.activation_condition, ctx.personalizationHeaders, ctx.currentPageUrl, ctx.languages, ctx.browserPlatform, ctx.userToken, ctx.userVisitCount, ctx.runJsFunction, ctx.logger);
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 audiencePass = new Map();
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 passes = audiencesPass(metadata, ctx);
2904
- audiencePass.set(metadata.experiment_id, passes);
2905
- if (passes) {
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 (_b = frame.experiments) !== null && _b !== void 0 ? _b : []) {
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 activated = audiencePass.get(metadata.experiment_id);
2929
- if (activated === undefined) {
2930
- activated = audiencesPass(metadata, ctx);
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 (!activated)
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 (_d) {
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
- ? ((_c = mlVariantIds.get(metadata.experiment_id)) !== null && _c !== void 0 ? _c : null)
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
- userToken = getOrGenerateUserToken();
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();