@coframe/sdk 1.1.59 → 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)`
@@ -1663,6 +1663,60 @@ const executeTags = (tags, logger, nonce) => {
1663
1663
  }
1664
1664
  };
1665
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
+
1666
1720
  function getLogCallback() {
1667
1721
  const queue = window.CFQ;
1668
1722
  if (!queue) {
@@ -1974,6 +2028,75 @@ function subscribeToCmd(predicate, callback, queueName = QueueName.CFQ) {
1974
2028
  return __addListener(predicate, callback, queueName);
1975
2029
  }
1976
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
+
1977
2100
  function describeError(error) {
1978
2101
  if (error && (typeof error === 'object' || typeof error === 'function')) {
1979
2102
  const stack = readString(() => error.stack);
@@ -2112,13 +2235,15 @@ function variantRendered() {
2112
2235
  const queue = ensureQueueExists();
2113
2236
  queue.push({
2114
2237
  emit: 'variantRendered',
2238
+ sdkVariantRendered: true,
2115
2239
  });
2116
2240
  }
2241
+ const SIGNALS_COMPLETION = /variantRendered|variantSession/;
2117
2242
  function hasJavascriptModification(variantElements) {
2118
2243
  const modifications = variantElements.variant.modifications || {};
2119
2244
  return Object.keys(modifications).some((elementId) => (modifications[elementId] || []).some((modification) => modification.type === 'execute_js'));
2120
2245
  }
2121
- const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecycleContext) => {
2246
+ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecycleContext, proxyComplete) => {
2122
2247
  var _a;
2123
2248
  const { variant, elements } = variantElements;
2124
2249
  const variantMetadata = {
@@ -2126,6 +2251,35 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2126
2251
  experiment_id: variant.experiment_id,
2127
2252
  };
2128
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
+ };
2129
2283
  const session = lifecycleContext && hasJavascriptModification(variantElements)
2130
2284
  ? lifecycleContext.runtime.createVariantSession({
2131
2285
  batchId: lifecycleContext.batchId,
@@ -2136,6 +2290,7 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2136
2290
  if (!variant.modifications ||
2137
2291
  Object.keys(variant.modifications).length === 0) {
2138
2292
  variantRendered();
2293
+ proxyComplete === null || proxyComplete === void 0 ? void 0 : proxyComplete();
2139
2294
  return true;
2140
2295
  }
2141
2296
  for (const elementId of Object.keys(variant.modifications)) {
@@ -2159,7 +2314,7 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2159
2314
  }
2160
2315
  continue;
2161
2316
  }
2162
- 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);
2163
2318
  if (!edited) {
2164
2319
  logger.error('element not edited', 'element_not_edited', {
2165
2320
  ...variantMetadata,
@@ -2177,6 +2332,8 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2177
2332
  }
2178
2333
  }
2179
2334
  }
2335
+ applying = false;
2336
+ maybeComplete();
2180
2337
  return true;
2181
2338
  };
2182
2339
  function describeElement(element) {
@@ -2188,7 +2345,7 @@ function describeElement(element) {
2188
2345
  const className = target.getAttribute('class');
2189
2346
  return `<${String(target.tagName).toLowerCase()}${id ? ` id=${JSON.stringify(id)}` : ''}${className ? ` class=${JSON.stringify(className)}` : ''}>`.slice(0, 200);
2190
2347
  }
2191
- const editElement = (element, modifications, elementId, logger, nonce, logMetadata = {}, variantSession) => {
2348
+ const editElement = (element, modifications, elementId, logger, nonce, logMetadata = {}, variantSession, scriptCompletion) => {
2192
2349
  if (!modifications[elementId]) {
2193
2350
  logger.warn('no modifications for element', 'no_modifications_for_element', {
2194
2351
  ...logMetadata,
@@ -2199,13 +2356,37 @@ const editElement = (element, modifications, elementId, logger, nonce, logMetada
2199
2356
  }
2200
2357
  for (const content of modifications[elementId]) {
2201
2358
  switch (content.type) {
2202
- case 'execute_js':
2203
- if (!applyJavascriptModification(element, content, logger, nonce, logMetadata, variantSession ? { variantSession } : undefined) &&
2204
- 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) {
2205
2385
  variantSession.error(new Error('JavaScript variant modification did not run'));
2206
2386
  return false;
2207
2387
  }
2208
2388
  break;
2389
+ }
2209
2390
  case 'update':
2210
2391
  applyUpdateModification(element, content, logger, logMetadata);
2211
2392
  variantRendered();
@@ -2264,13 +2445,14 @@ function revealPage() {
2264
2445
  async function replaceVariants(variants, orgId, logger, nonce, runtime) {
2265
2446
  const failedReplacements = [];
2266
2447
  let currentVariant;
2448
+ const proxyCompletion = beginProxyBatch(variants);
2267
2449
  const batch = runtime === null || runtime === void 0 ? void 0 : runtime.beginBatch({
2268
2450
  expectedSessionCount: variants.filter(hasJavascriptModification).length,
2269
2451
  });
2270
2452
  try {
2271
- for (const v of variants) {
2453
+ for (const [index, v] of variants.entries()) {
2272
2454
  currentVariant = v.variant;
2273
- 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))) {
2274
2456
  failedReplacements.push({
2275
2457
  message: 'Original element not found',
2276
2458
  metadata: {
@@ -2823,8 +3005,7 @@ function checkAllAudiences(metadata, ctx) {
2823
3005
  }
2824
3006
  return result;
2825
3007
  }
2826
- function audiencesPass(metadata, ctx) {
2827
- const result = checkAllAudiences(metadata, ctx);
3008
+ function audiencesPass(result, ctx) {
2828
3009
  return ctx.unknownIsInactive ? result === true : result !== false;
2829
3010
  }
2830
3011
  async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}, silentFallback = false, chooseCmabVariants, userVisitCountOverride, runtime = {}) {
@@ -2906,57 +3087,89 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2906
3087
  experimentsInfo: [...experimentsInfo, ...assignment.experimentsInfo],
2907
3088
  });
2908
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
+ }
2909
3093
  function activateFrames(ctx) {
2910
3094
  return ctx.project.frames.filter((frame) => {
2911
3095
  if (!frame.experiments)
2912
3096
  return false;
2913
- 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);
2914
3098
  return ctx.unknownIsInactive ? frameActivated === true : !!frameActivated;
2915
3099
  });
2916
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
+ }
2917
3111
  async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVariants) {
2918
- var _a, _b, _c;
3112
+ var _a, _b, _c, _d;
2919
3113
  const { project, userToken, logger } = ctx;
2920
3114
  const experimentsInfo = [];
2921
3115
  const variantsAndElements = [];
2922
- const audiencePass = new Map();
3116
+ const audienceResult = new Map();
2923
3117
  const reachableByLane = {};
3118
+ const undecidableLanes = new Set();
2924
3119
  for (const frame of activeFrames) {
2925
3120
  for (const experiment of (_a = frame.experiments) !== null && _a !== void 0 ? _a : []) {
2926
3121
  const metadata = experiment.metadata;
2927
3122
  const laneIds = experimentLaneIds(metadata);
2928
3123
  if (laneIds.length === 0)
2929
3124
  continue;
2930
- const passes = audiencesPass(metadata, ctx);
2931
- audiencePass.set(metadata.experiment_id, passes);
2932
- if (passes) {
3125
+ const result = checkAllAudiences(metadata, ctx);
3126
+ audienceResult.set(metadata.experiment_id, result);
3127
+ if (audiencesPass(result, ctx)) {
2933
3128
  for (const laneId of laneIds) {
2934
3129
  if (!reachableByLane[laneId])
2935
3130
  reachableByLane[laneId] = [];
2936
3131
  reachableByLane[laneId].push(metadata.experiment_id);
2937
3132
  }
2938
3133
  }
3134
+ else if (ctx.unknownIsInactive && result !== false) {
3135
+ for (const laneId of laneIds)
3136
+ if (!validatedClaims[laneId])
3137
+ undecidableLanes.add(laneId);
3138
+ }
2939
3139
  }
2940
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);
2941
3146
  const excludedExperimentIds = resolveLaneWinners(validatedClaims, reachableByLane, project.lanes, userToken);
2942
3147
  const updatedClaims = { ...validatedClaims };
2943
3148
  const assignmentEntries = [];
2944
3149
  let hasActivatedFrames = false;
2945
3150
  for (const frame of activeFrames) {
2946
3151
  let atLeastOneExperimentWasActivated = false;
2947
- 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 : []) {
2948
3153
  if (atLeastOneExperimentWasActivated)
2949
3154
  break;
2950
3155
  const metadata = experiment.metadata;
2951
3156
  const variants = experiment.variants;
2952
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
+ }
2953
3163
  if (excludedExperimentIds.has(metadata.experiment_id))
2954
3164
  continue;
2955
- let activated = audiencePass.get(metadata.experiment_id);
2956
- if (activated === undefined) {
2957
- 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;
2958
3171
  }
2959
- if (!activated)
3172
+ if (!audiencesPass(result, ctx))
2960
3173
  continue;
2961
3174
  atLeastOneExperimentWasActivated = true;
2962
3175
  hasActivatedFrames = true;
@@ -3010,7 +3223,7 @@ async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVarian
3010
3223
  try {
3011
3224
  mlVariantIds = await chooseCmabVariants(Array.from(cmabMetadataById.values()), userToken);
3012
3225
  }
3013
- catch (_d) {
3226
+ catch (_e) {
3014
3227
  mlVariantIds = new Map();
3015
3228
  }
3016
3229
  }
@@ -3021,7 +3234,7 @@ async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVarian
3021
3234
  }
3022
3235
  const { metadata, variants, elements } = entry;
3023
3236
  let mlVariantId = metadata.type === 'CMAB'
3024
- ? ((_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)
3025
3238
  : null;
3026
3239
  if (mlVariantId && !variants.some((v) => v.id === mlVariantId)) {
3027
3240
  logger.warn(`CMAB variant ${mlVariantId} not in experiment ${metadata.experiment_id}`, 'cmab_variant_not_found', { experiment_id: metadata.experiment_id });
@@ -3564,7 +3777,7 @@ function shouldSdkWaitForHydration(config) {
3564
3777
  !isVariantRuntimeLifecycleEnabled(config));
3565
3778
  }
3566
3779
 
3567
- function createRevealCoordinator(revealPage, reportError = () => { }) {
3780
+ function createRevealCoordinator(revealPage, reportError = () => { }, onForced = () => { }) {
3568
3781
  const notify = (listener, reason) => {
3569
3782
  try {
3570
3783
  listener(reason);
@@ -3616,6 +3829,18 @@ function createRevealCoordinator(revealPage, reportError = () => { }) {
3616
3829
  reveal(reason);
3617
3830
  },
3618
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
+ }
3619
3844
  reveal(reason);
3620
3845
  },
3621
3846
  block(batchId) {
@@ -3803,10 +4028,6 @@ function notifyEventsFlushed() {
3803
4028
  }
3804
4029
  }
3805
4030
 
3806
- function isRecord(value) {
3807
- return !!value && typeof value === 'object' && !Array.isArray(value);
3808
- }
3809
-
3810
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 ' +
3811
4032
  'AX GF GP MF MQ RE YT ' +
3812
4033
  'IS LI NO ' +
@@ -5010,6 +5231,7 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
5010
5231
  Coframe.debug.laneClaims = laneClaims;
5011
5232
  }
5012
5233
  if (config.trackingOnly) {
5234
+ beginProxyBatch([]);
5013
5235
  variantRendered();
5014
5236
  }
5015
5237
  else {
@@ -5032,7 +5254,7 @@ async function modifyPage(Coframe, configResult, projectResult, analytics, lifec
5032
5254
  var _a;
5033
5255
  const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
5034
5256
  const { project: cdnProject, personalizationHeaders, assignmentContext, } = projectResult;
5035
- 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([]));
5036
5258
  const runtime = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.runtime;
5037
5259
  if (runtime && config.waitForHydration) {
5038
5260
  if (findLastCmd(isPageHydratedCmd)) {
@@ -5399,6 +5621,7 @@ async function resolveConfig(Coframe, revealActions) {
5399
5621
  }
5400
5622
 
5401
5623
  async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit, previewVariantId) {
5624
+ var _a;
5402
5625
  const logger = Coframe.logger;
5403
5626
  let userToken;
5404
5627
  if (config.waitForUserToken) {
@@ -5416,7 +5639,8 @@ async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit, preview
5416
5639
  }
5417
5640
  }
5418
5641
  else {
5419
- 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();
5420
5644
  }
5421
5645
  Coframe.userToken = userToken;
5422
5646
  const persistCookies = () => {
@@ -6409,7 +6633,7 @@ const Coframe = {
6409
6633
  },
6410
6634
  };
6411
6635
  Coframe.logger = createLogger(() => Coframe);
6412
- const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
6636
+ const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger), () => beginProxyBatch([]));
6413
6637
  let activeRuntime = null;
6414
6638
  function clearActiveRuntime() {
6415
6639
  activeRuntime === null || activeRuntime === void 0 ? void 0 : activeRuntime.destroy();