@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.
@@ -1661,6 +1661,60 @@ const executeTags = (tags, logger, nonce) => {
1661
1661
  }
1662
1662
  };
1663
1663
 
1664
+ function isRecord(value) {
1665
+ return !!value && typeof value === 'object' && !Array.isArray(value);
1666
+ }
1667
+
1668
+ const SCOPED = new Set(['update', 'delete', 'insert_into', 'insert_by']);
1669
+ const ADDED = new Set(['add_modal', 'add_banner']);
1670
+ function safeSelector(css) {
1671
+ return css.trim() !== '' && !/[<{}]/.test(css) && !css.includes('/*');
1672
+ }
1673
+ function modificationScope(modifications, elements) {
1674
+ const selectors = [];
1675
+ if (!isRecord(modifications) || !isRecord(elements))
1676
+ return { kind: 'page', reason: 'invalid_variant' };
1677
+ for (const [id, changes] of Object.entries(modifications)) {
1678
+ if (!Array.isArray(changes))
1679
+ return { kind: 'page', reason: 'invalid_variant' };
1680
+ let scoped = false;
1681
+ for (const change of changes) {
1682
+ const type = isRecord(change) ? change.type : undefined;
1683
+ if (typeof type === 'string' && SCOPED.has(type))
1684
+ scoped = true;
1685
+ else if (type === 'execute_js' &&
1686
+ isRecord(change) &&
1687
+ change.css !== undefined) {
1688
+ const { css: list } = change;
1689
+ const declared = Array.isArray(list) ? list : [list];
1690
+ if (declared.length === 0)
1691
+ return { kind: 'page', reason: `unscoped_change:${type}` };
1692
+ for (const css of declared) {
1693
+ if (typeof css !== 'string' || !safeSelector(css))
1694
+ return { kind: 'page', reason: `unscoped_selector:${id}` };
1695
+ selectors.push(css.trim());
1696
+ }
1697
+ }
1698
+ else if (typeof type !== 'string' || !ADDED.has(type))
1699
+ return { kind: 'page', reason: `unscoped_change:${String(type)}` };
1700
+ }
1701
+ if (!scoped)
1702
+ continue;
1703
+ const conditions = elements[id];
1704
+ if (!Array.isArray(conditions) || conditions.length === 0)
1705
+ return { kind: 'page', reason: 'invalid_variant' };
1706
+ const css = [];
1707
+ for (const condition of conditions) {
1708
+ const value = isRecord(condition) ? condition.css : undefined;
1709
+ if (typeof value !== 'string' || !safeSelector(value))
1710
+ return { kind: 'page', reason: `unscoped_selector:${id}` };
1711
+ css.push(value.trim());
1712
+ }
1713
+ selectors.push(css.join(', '));
1714
+ }
1715
+ return selectors.length ? { kind: 'elements', selectors } : { kind: 'none' };
1716
+ }
1717
+
1664
1718
  function getLogCallback() {
1665
1719
  const queue = window.CFQ;
1666
1720
  if (!queue) {
@@ -1972,6 +2026,75 @@ function subscribeToCmd(predicate, callback, queueName = QueueName.CFQ) {
1972
2026
  return __addListener(predicate, callback, queueName);
1973
2027
  }
1974
2028
 
2029
+ function scopeOwners(variants, selectors) {
2030
+ const matches = new Map();
2031
+ const select = (css) => {
2032
+ if (!matches.has(css)) {
2033
+ try {
2034
+ matches.set(css, document.querySelectorAll(css));
2035
+ }
2036
+ catch (_a) {
2037
+ matches.set(css, null);
2038
+ }
2039
+ }
2040
+ return matches.get(css);
2041
+ };
2042
+ const overlaps = (hidden, changed) => {
2043
+ if (hidden === changed)
2044
+ return true;
2045
+ const left = select(hidden);
2046
+ const right = select(changed);
2047
+ if (!(left === null || left === void 0 ? void 0 : left.length) || !(right === null || right === void 0 ? void 0 : right.length))
2048
+ return true;
2049
+ for (let i = 0; i < left.length; i++) {
2050
+ for (let j = 0; j < right.length; j++) {
2051
+ if (left[i].contains(right[j]) || right[j].contains(left[i]))
2052
+ return true;
2053
+ }
2054
+ }
2055
+ return false;
2056
+ };
2057
+ const scopes = variants.map(({ variant, elements }) => modificationScope(variant.modifications, elements));
2058
+ return selectors.map((selector) => {
2059
+ const owners = [];
2060
+ scopes.forEach((scope, index) => {
2061
+ if (scope.kind === 'page' ||
2062
+ (scope.kind === 'elements' &&
2063
+ scope.selectors.some((changed) => overlaps(selector, changed)))) {
2064
+ owners.push(index);
2065
+ }
2066
+ });
2067
+ return owners;
2068
+ });
2069
+ }
2070
+ function beginProxyBatch(variants) {
2071
+ const request = findLastCmd((cmd) => cmd.proxyReveal === true);
2072
+ if (!request)
2073
+ return undefined;
2074
+ const queue = ensureQueueExists();
2075
+ const batchId = queue.length + 1;
2076
+ const keys = variants.map(({ variant }, index) => JSON.stringify([variant.experiment_id, variant.id, index]));
2077
+ const owners = request.proxySelectors
2078
+ ? scopeOwners(variants, request.proxySelectors)
2079
+ : undefined;
2080
+ queue.push({ proxyBatch: { batchId, keys, ...(owners ? { owners } : {}) } });
2081
+ return (index) => {
2082
+ let completed = false;
2083
+ const complete = (() => {
2084
+ if (completed)
2085
+ return;
2086
+ completed = true;
2087
+ queue.push({ proxyVariantRendered: { batchId, key: keys[index] } });
2088
+ });
2089
+ complete.silent = () => {
2090
+ if (completed)
2091
+ return;
2092
+ queue.push({ proxyVariantSilent: { batchId, key: keys[index] } });
2093
+ };
2094
+ return complete;
2095
+ };
2096
+ }
2097
+
1975
2098
  function describeError(error) {
1976
2099
  if (error && (typeof error === 'object' || typeof error === 'function')) {
1977
2100
  const stack = readString(() => error.stack);
@@ -2110,13 +2233,15 @@ function variantRendered() {
2110
2233
  const queue = ensureQueueExists();
2111
2234
  queue.push({
2112
2235
  emit: 'variantRendered',
2236
+ sdkVariantRendered: true,
2113
2237
  });
2114
2238
  }
2239
+ const SIGNALS_COMPLETION = /variantRendered|variantSession/;
2115
2240
  function hasJavascriptModification(variantElements) {
2116
2241
  const modifications = variantElements.variant.modifications || {};
2117
2242
  return Object.keys(modifications).some((elementId) => (modifications[elementId] || []).some((modification) => modification.type === 'execute_js'));
2118
2243
  }
2119
- const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecycleContext) => {
2244
+ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecycleContext, proxyComplete) => {
2120
2245
  var _a;
2121
2246
  const { variant, elements } = variantElements;
2122
2247
  const variantMetadata = {
@@ -2124,6 +2249,35 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2124
2249
  experiment_id: variant.experiment_id,
2125
2250
  };
2126
2251
  setApplyVariantChangesStarted(true);
2252
+ const javascriptPending = [];
2253
+ const javascriptSilent = [];
2254
+ let applying = true;
2255
+ const maybeComplete = () => {
2256
+ if (applying)
2257
+ return;
2258
+ if (javascriptPending.every((pending) => !pending))
2259
+ proxyComplete === null || proxyComplete === void 0 ? void 0 : proxyComplete();
2260
+ else if (javascriptPending.every((pending, i) => !pending || javascriptSilent[i]))
2261
+ proxyComplete === null || proxyComplete === void 0 ? void 0 : proxyComplete.silent();
2262
+ };
2263
+ const scriptCompletion = () => {
2264
+ const index = javascriptPending.push(true) - 1;
2265
+ javascriptSilent.push(false);
2266
+ const complete = (() => {
2267
+ var _a;
2268
+ if (!javascriptPending[index])
2269
+ return;
2270
+ javascriptPending[index] = false;
2271
+ (_a = complete.settled) === null || _a === void 0 ? void 0 : _a.call(complete);
2272
+ complete.settled = undefined;
2273
+ maybeComplete();
2274
+ });
2275
+ complete.silent = () => {
2276
+ if (javascriptPending[index])
2277
+ javascriptSilent[index] = true;
2278
+ };
2279
+ return complete;
2280
+ };
2127
2281
  const session = lifecycleContext && hasJavascriptModification(variantElements)
2128
2282
  ? lifecycleContext.runtime.createVariantSession({
2129
2283
  batchId: lifecycleContext.batchId,
@@ -2134,6 +2288,7 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2134
2288
  if (!variant.modifications ||
2135
2289
  Object.keys(variant.modifications).length === 0) {
2136
2290
  variantRendered();
2291
+ proxyComplete === null || proxyComplete === void 0 ? void 0 : proxyComplete();
2137
2292
  return true;
2138
2293
  }
2139
2294
  for (const elementId of Object.keys(variant.modifications)) {
@@ -2157,7 +2312,7 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2157
2312
  }
2158
2313
  continue;
2159
2314
  }
2160
- const edited = editElement(selectedElement, variant.modifications, elementId, logger, nonce, variantMetadata, session === null || session === void 0 ? void 0 : session.facade);
2315
+ const edited = editElement(selectedElement, variant.modifications, elementId, logger, nonce, variantMetadata, session === null || session === void 0 ? void 0 : session.facade, proxyComplete ? scriptCompletion : undefined);
2161
2316
  if (!edited) {
2162
2317
  logger.error('element not edited', 'element_not_edited', {
2163
2318
  ...variantMetadata,
@@ -2175,6 +2330,8 @@ const applyVariantChanges = (variantElements, currentUrl, logger, nonce, lifecyc
2175
2330
  }
2176
2331
  }
2177
2332
  }
2333
+ applying = false;
2334
+ maybeComplete();
2178
2335
  return true;
2179
2336
  };
2180
2337
  function describeElement(element) {
@@ -2186,7 +2343,7 @@ function describeElement(element) {
2186
2343
  const className = target.getAttribute('class');
2187
2344
  return `<${String(target.tagName).toLowerCase()}${id ? ` id=${JSON.stringify(id)}` : ''}${className ? ` class=${JSON.stringify(className)}` : ''}>`.slice(0, 200);
2188
2345
  }
2189
- const editElement = (element, modifications, elementId, logger, nonce, logMetadata = {}, variantSession) => {
2346
+ const editElement = (element, modifications, elementId, logger, nonce, logMetadata = {}, variantSession, scriptCompletion) => {
2190
2347
  if (!modifications[elementId]) {
2191
2348
  logger.warn('no modifications for element', 'no_modifications_for_element', {
2192
2349
  ...logMetadata,
@@ -2197,13 +2354,37 @@ const editElement = (element, modifications, elementId, logger, nonce, logMetada
2197
2354
  }
2198
2355
  for (const content of modifications[elementId]) {
2199
2356
  switch (content.type) {
2200
- case 'execute_js':
2201
- if (!applyJavascriptModification(element, content, logger, nonce, logMetadata, variantSession ? { variantSession } : undefined) &&
2202
- variantSession) {
2357
+ case 'execute_js': {
2358
+ const complete = scriptCompletion === null || scriptCompletion === void 0 ? void 0 : scriptCompletion();
2359
+ const session = variantSession && complete
2360
+ ? {
2361
+ ...variantSession,
2362
+ complete() {
2363
+ variantSession.complete();
2364
+ complete();
2365
+ },
2366
+ error(error) {
2367
+ variantSession.error(error);
2368
+ complete();
2369
+ },
2370
+ }
2371
+ : variantSession;
2372
+ const stop = complete &&
2373
+ subscribeToCmd((cmd) => !cmd.sdkVariantRendered &&
2374
+ (cmd.variantRendered === true || cmd.emit === 'variantRendered'), () => complete());
2375
+ if (complete && stop)
2376
+ complete.settled = stop;
2377
+ const applied = applyJavascriptModification(element, content, logger, nonce, logMetadata, session ? { variantSession: session } : undefined);
2378
+ if (!applied)
2379
+ complete === null || complete === void 0 ? void 0 : complete();
2380
+ if (applied && complete && !SIGNALS_COMPLETION.test(content.js))
2381
+ complete.silent();
2382
+ if (!applied && variantSession) {
2203
2383
  variantSession.error(new Error('JavaScript variant modification did not run'));
2204
2384
  return false;
2205
2385
  }
2206
2386
  break;
2387
+ }
2207
2388
  case 'update':
2208
2389
  applyUpdateModification(element, content, logger, logMetadata);
2209
2390
  variantRendered();
@@ -2262,13 +2443,14 @@ function revealPage() {
2262
2443
  async function replaceVariants(variants, orgId, logger, nonce, runtime) {
2263
2444
  const failedReplacements = [];
2264
2445
  let currentVariant;
2446
+ const proxyCompletion = beginProxyBatch(variants);
2265
2447
  const batch = runtime === null || runtime === void 0 ? void 0 : runtime.beginBatch({
2266
2448
  expectedSessionCount: variants.filter(hasJavascriptModification).length,
2267
2449
  });
2268
2450
  try {
2269
- for (const v of variants) {
2451
+ for (const [index, v] of variants.entries()) {
2270
2452
  currentVariant = v.variant;
2271
- if (!applyVariantChanges(v, getCurrentPageUrl(), logger, nonce, batch && runtime ? { runtime, batchId: batch.batchId } : undefined)) {
2453
+ if (!applyVariantChanges(v, getCurrentPageUrl(), logger, nonce, batch && runtime ? { runtime, batchId: batch.batchId } : undefined, proxyCompletion === null || proxyCompletion === void 0 ? void 0 : proxyCompletion(index))) {
2272
2454
  failedReplacements.push({
2273
2455
  message: 'Original element not found',
2274
2456
  metadata: {
@@ -2821,8 +3003,7 @@ function checkAllAudiences(metadata, ctx) {
2821
3003
  }
2822
3004
  return result;
2823
3005
  }
2824
- function audiencesPass(metadata, ctx) {
2825
- const result = checkAllAudiences(metadata, ctx);
3006
+ function audiencesPass(result, ctx) {
2826
3007
  return ctx.unknownIsInactive ? result === true : result !== false;
2827
3008
  }
2828
3009
  async function getVariantsAndElements(personalizationHeaders, project, previewVariantId, userToken, currentPageUrl, languages, browserPlatform, logger, nonce, laneClaims = {}, silentFallback = false, chooseCmabVariants, userVisitCountOverride, runtime = {}) {
@@ -2904,57 +3085,89 @@ async function getVariantsAndElements(personalizationHeaders, project, previewVa
2904
3085
  experimentsInfo: [...experimentsInfo, ...assignment.experimentsInfo],
2905
3086
  });
2906
3087
  }
3088
+ function frameActivation(frame, ctx) {
3089
+ return checkActivationCondition(frame.activation_condition, ctx.personalizationHeaders, ctx.currentPageUrl, ctx.languages, ctx.browserPlatform, ctx.userToken, ctx.userVisitCount, ctx.runJsFunction, ctx.logger);
3090
+ }
2907
3091
  function activateFrames(ctx) {
2908
3092
  return ctx.project.frames.filter((frame) => {
2909
3093
  if (!frame.experiments)
2910
3094
  return false;
2911
- const frameActivated = checkActivationCondition(frame.activation_condition, ctx.personalizationHeaders, ctx.currentPageUrl, ctx.languages, ctx.browserPlatform, ctx.userToken, ctx.userVisitCount, ctx.runJsFunction, ctx.logger);
3095
+ const frameActivated = frameActivation(frame, ctx);
2912
3096
  return ctx.unknownIsInactive ? frameActivated === true : !!frameActivated;
2913
3097
  });
2914
3098
  }
3099
+ function undecidedFrames(ctx) {
3100
+ if (!ctx.unknownIsInactive)
3101
+ return [];
3102
+ return ctx.project.frames.filter((frame) => {
3103
+ if (!frame.experiments)
3104
+ return false;
3105
+ const activated = frameActivation(frame, ctx);
3106
+ return activated !== true && activated !== false;
3107
+ });
3108
+ }
2915
3109
  async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVariants) {
2916
- var _a, _b, _c;
3110
+ var _a, _b, _c, _d;
2917
3111
  const { project, userToken, logger } = ctx;
2918
3112
  const experimentsInfo = [];
2919
3113
  const variantsAndElements = [];
2920
- const audiencePass = new Map();
3114
+ const audienceResult = new Map();
2921
3115
  const reachableByLane = {};
3116
+ const undecidableLanes = new Set();
2922
3117
  for (const frame of activeFrames) {
2923
3118
  for (const experiment of (_a = frame.experiments) !== null && _a !== void 0 ? _a : []) {
2924
3119
  const metadata = experiment.metadata;
2925
3120
  const laneIds = experimentLaneIds(metadata);
2926
3121
  if (laneIds.length === 0)
2927
3122
  continue;
2928
- const passes = audiencesPass(metadata, ctx);
2929
- audiencePass.set(metadata.experiment_id, passes);
2930
- if (passes) {
3123
+ const result = checkAllAudiences(metadata, ctx);
3124
+ audienceResult.set(metadata.experiment_id, result);
3125
+ if (audiencesPass(result, ctx)) {
2931
3126
  for (const laneId of laneIds) {
2932
3127
  if (!reachableByLane[laneId])
2933
3128
  reachableByLane[laneId] = [];
2934
3129
  reachableByLane[laneId].push(metadata.experiment_id);
2935
3130
  }
2936
3131
  }
3132
+ else if (ctx.unknownIsInactive && result !== false) {
3133
+ for (const laneId of laneIds)
3134
+ if (!validatedClaims[laneId])
3135
+ undecidableLanes.add(laneId);
3136
+ }
2937
3137
  }
2938
3138
  }
3139
+ for (const frame of undecidedFrames(ctx))
3140
+ for (const experiment of (_b = frame.experiments) !== null && _b !== void 0 ? _b : [])
3141
+ for (const laneId of experimentLaneIds(experiment.metadata))
3142
+ if (!validatedClaims[laneId])
3143
+ undecidableLanes.add(laneId);
2939
3144
  const excludedExperimentIds = resolveLaneWinners(validatedClaims, reachableByLane, project.lanes, userToken);
2940
3145
  const updatedClaims = { ...validatedClaims };
2941
3146
  const assignmentEntries = [];
2942
3147
  let hasActivatedFrames = false;
2943
3148
  for (const frame of activeFrames) {
2944
3149
  let atLeastOneExperimentWasActivated = false;
2945
- for (const experiment of (_b = frame.experiments) !== null && _b !== void 0 ? _b : []) {
3150
+ for (const experiment of (_c = frame.experiments) !== null && _c !== void 0 ? _c : []) {
2946
3151
  if (atLeastOneExperimentWasActivated)
2947
3152
  break;
2948
3153
  const metadata = experiment.metadata;
2949
3154
  const variants = experiment.variants;
2950
3155
  const elements = frame.elements;
3156
+ if (ctx.unknownIsInactive &&
3157
+ experimentLaneIds(metadata).some((id) => undecidableLanes.has(id))) {
3158
+ logger.warn('Leaving the frame to the browser: a lane member needs the page', 'undecidable_lane', { experiment_id: metadata.experiment_id });
3159
+ break;
3160
+ }
2951
3161
  if (excludedExperimentIds.has(metadata.experiment_id))
2952
3162
  continue;
2953
- let activated = audiencePass.get(metadata.experiment_id);
2954
- if (activated === undefined) {
2955
- activated = audiencesPass(metadata, ctx);
3163
+ let result = audienceResult.get(metadata.experiment_id);
3164
+ if (result === undefined)
3165
+ result = checkAllAudiences(metadata, ctx);
3166
+ if (result !== true && result !== false && ctx.unknownIsInactive) {
3167
+ logger.warn('Leaving the frame to the browser: an audience needs the page', 'undecidable_audience', { experiment_id: metadata.experiment_id });
3168
+ break;
2956
3169
  }
2957
- if (!activated)
3170
+ if (!audiencesPass(result, ctx))
2958
3171
  continue;
2959
3172
  atLeastOneExperimentWasActivated = true;
2960
3173
  hasActivatedFrames = true;
@@ -3008,7 +3221,7 @@ async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVarian
3008
3221
  try {
3009
3222
  mlVariantIds = await chooseCmabVariants(Array.from(cmabMetadataById.values()), userToken);
3010
3223
  }
3011
- catch (_d) {
3224
+ catch (_e) {
3012
3225
  mlVariantIds = new Map();
3013
3226
  }
3014
3227
  }
@@ -3019,7 +3232,7 @@ async function assignFrames(activeFrames, validatedClaims, ctx, chooseCmabVarian
3019
3232
  }
3020
3233
  const { metadata, variants, elements } = entry;
3021
3234
  let mlVariantId = metadata.type === 'CMAB'
3022
- ? ((_c = mlVariantIds.get(metadata.experiment_id)) !== null && _c !== void 0 ? _c : null)
3235
+ ? ((_d = mlVariantIds.get(metadata.experiment_id)) !== null && _d !== void 0 ? _d : null)
3023
3236
  : null;
3024
3237
  if (mlVariantId && !variants.some((v) => v.id === mlVariantId)) {
3025
3238
  logger.warn(`CMAB variant ${mlVariantId} not in experiment ${metadata.experiment_id}`, 'cmab_variant_not_found', { experiment_id: metadata.experiment_id });
@@ -3562,7 +3775,7 @@ function shouldSdkWaitForHydration(config) {
3562
3775
  !isVariantRuntimeLifecycleEnabled(config));
3563
3776
  }
3564
3777
 
3565
- function createRevealCoordinator(revealPage, reportError = () => { }) {
3778
+ function createRevealCoordinator(revealPage, reportError = () => { }, onForced = () => { }) {
3566
3779
  const notify = (listener, reason) => {
3567
3780
  try {
3568
3781
  listener(reason);
@@ -3614,6 +3827,18 @@ function createRevealCoordinator(revealPage, reportError = () => { }) {
3614
3827
  reveal(reason);
3615
3828
  },
3616
3829
  forceReveal(reason) {
3830
+ if (pageRevealed)
3831
+ return;
3832
+ try {
3833
+ onForced(reason);
3834
+ }
3835
+ catch (error) {
3836
+ try {
3837
+ reportError(error);
3838
+ }
3839
+ catch (_a) {
3840
+ }
3841
+ }
3617
3842
  reveal(reason);
3618
3843
  },
3619
3844
  block(batchId) {
@@ -3801,10 +4026,6 @@ function notifyEventsFlushed() {
3801
4026
  }
3802
4027
  }
3803
4028
 
3804
- function isRecord(value) {
3805
- return !!value && typeof value === 'object' && !Array.isArray(value);
3806
- }
3807
-
3808
4029
  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 ' +
3809
4030
  'AX GF GP MF MQ RE YT ' +
3810
4031
  'IS LI NO ' +
@@ -5008,6 +5229,7 @@ function createApplyVariants(Coframe, config, previewVariantId, isSessionVariant
5008
5229
  Coframe.debug.laneClaims = laneClaims;
5009
5230
  }
5010
5231
  if (config.trackingOnly) {
5232
+ beginProxyBatch([]);
5011
5233
  variantRendered();
5012
5234
  }
5013
5235
  else {
@@ -5030,7 +5252,7 @@ async function modifyPage(Coframe, configResult, projectResult, analytics, lifec
5030
5252
  var _a;
5031
5253
  const { config, timeoutMs, previewVariantId, isSessionVariant, integrationStartTime, executionStartTime, } = configResult;
5032
5254
  const { project: cdnProject, personalizationHeaders, assignmentContext, } = projectResult;
5033
- const revealCoordinator = (_a = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.revealCoordinator) !== null && _a !== void 0 ? _a : createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
5255
+ const revealCoordinator = (_a = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.revealCoordinator) !== null && _a !== void 0 ? _a : createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger), () => beginProxyBatch([]));
5034
5256
  const runtime = lifecycle === null || lifecycle === void 0 ? void 0 : lifecycle.runtime;
5035
5257
  if (runtime && config.waitForHydration) {
5036
5258
  if (findLastCmd(isPageHydratedCmd)) {
@@ -5397,6 +5619,7 @@ async function resolveConfig(Coframe, revealActions) {
5397
5619
  }
5398
5620
 
5399
5621
  async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit, previewVariantId) {
5622
+ var _a;
5400
5623
  const logger = Coframe.logger;
5401
5624
  let userToken;
5402
5625
  if (config.waitForUserToken) {
@@ -5414,7 +5637,8 @@ async function resolveIdentity(config, Coframe, timeoutMs, gracefulExit, preview
5414
5637
  }
5415
5638
  }
5416
5639
  else {
5417
- userToken = getOrGenerateUserToken();
5640
+ const proxied = (_a = findLastCmd((cmd) => typeof cmd.proxyUserToken === 'string' && !!cmd.proxyUserToken)) === null || _a === void 0 ? void 0 : _a.proxyUserToken;
5641
+ userToken = proxied || getOrGenerateUserToken();
5418
5642
  }
5419
5643
  Coframe.userToken = userToken;
5420
5644
  const persistCookies = () => {
@@ -6407,7 +6631,7 @@ const Coframe = {
6407
6631
  },
6408
6632
  };
6409
6633
  Coframe.logger = createLogger(() => Coframe);
6410
- const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger));
6634
+ const revealCoordinator = createRevealCoordinator(revealPage, logRevealListenerError(() => Coframe.logger), () => beginProxyBatch([]));
6411
6635
  let activeRuntime = null;
6412
6636
  function clearActiveRuntime() {
6413
6637
  activeRuntime === null || activeRuntime === void 0 ? void 0 : activeRuntime.destroy();
@@ -236,6 +236,8 @@ const ID_FIELDS = ['projectId', 'experimentId', 'userToken'];
236
236
  function invalidAssignmentIdField(body) {
237
237
  for (const field of ID_FIELDS) {
238
238
  const value = body[field];
239
+ if (field === 'experimentId' && value === undefined)
240
+ continue;
239
241
  if (typeof value !== 'string' || !value) {
240
242
  return { field, message: 'is required' };
241
243
  }
@@ -515,6 +517,25 @@ async function flushFeatureFlagEvents() {
515
517
  }
516
518
  }
517
519
 
520
+ const IDLE_MS = 60000;
521
+ let dispatcher;
522
+ function runsOnNode() {
523
+ var _a;
524
+ return (typeof process !== 'undefined' &&
525
+ typeof ((_a = process.versions) === null || _a === void 0 ? void 0 : _a.node) === 'string' &&
526
+ !('EdgeRuntime' in globalThis));
527
+ }
528
+ function edgeDispatcher() {
529
+ if (!runsOnNode())
530
+ return Promise.resolve(undefined);
531
+ dispatcher !== null && dispatcher !== void 0 ? dispatcher : (dispatcher = import('undici').then(({ Agent }) => new Agent({ keepAliveTimeout: IDLE_MS, keepAliveMaxTimeout: IDLE_MS }), () => undefined));
532
+ return dispatcher;
533
+ }
534
+ async function edgeFetch(url, init = {}) {
535
+ const agent = await edgeDispatcher();
536
+ return fetch(url, agent ? { ...init, dispatcher: agent } : init);
537
+ }
538
+
518
539
  const DEFAULT_ASSIGNMENT_TIMEOUT_MS = 1500;
519
540
  function normalizeRequestHeaders(headers) {
520
541
  const normalized = new Headers();
@@ -601,6 +622,13 @@ function effectiveVisitCount(headers) {
601
622
  const next = nextVisitCount(existing, lastVisitTime, Date.now());
602
623
  return Number.isSafeInteger(next) ? next : existing;
603
624
  }
625
+ function isAssignedVariant(value) {
626
+ return (isRecord(value) &&
627
+ typeof value.id === 'string' &&
628
+ typeof value.slug === 'string' &&
629
+ typeof value.experimentId === 'string' &&
630
+ typeof value.iterationId === 'string');
631
+ }
604
632
  function isExperimentAssignmentResult(value, experimentId) {
605
633
  if (!isRecord(value) || !isRecord(value.projectData))
606
634
  return false;
@@ -617,22 +645,32 @@ function isExperimentAssignmentResult(value, experimentId) {
617
645
  context.userVisitCount < 0) {
618
646
  return false;
619
647
  }
648
+ if (value.variants !== undefined && !Array.isArray(value.variants))
649
+ return false;
650
+ if (Array.isArray(value.variants) && !value.variants.every(isAssignedVariant))
651
+ return false;
652
+ if (experimentId === undefined) {
653
+ return value.variant === null && Array.isArray(value.variants);
654
+ }
620
655
  if (value.variant === null) {
621
656
  return typeof value.reason === 'string';
622
657
  }
623
- if (!isRecord(value.variant))
624
- return false;
625
- return (typeof value.variant.id === 'string' &&
626
- typeof value.variant.slug === 'string' &&
627
- value.variant.experimentId === experimentId &&
628
- typeof value.variant.iterationId === 'string');
658
+ return (isAssignedVariant(value.variant) &&
659
+ value.variant.experimentId === experimentId);
629
660
  }
630
661
  function generateUserToken() {
631
662
  return generateUUID();
632
663
  }
633
- async function getExperimentAssignment(options) {
664
+ function getExperimentAssignment(options) {
665
+ var _a;
666
+ return fetchAssignment(options, (_a = options.experimentId) !== null && _a !== void 0 ? _a : '');
667
+ }
668
+ function getAssignments(options) {
669
+ return fetchAssignment(options, undefined);
670
+ }
671
+ async function fetchAssignment(options, experimentId) {
634
672
  var _a, _b, _c, _d, _e, _f;
635
- const idProblem = invalidAssignmentIdField(options);
673
+ const idProblem = invalidAssignmentIdField({ ...options, experimentId });
636
674
  if (idProblem)
637
675
  throw new Error(`${idProblem.field} ${idProblem.message}`);
638
676
  if (options.timeoutMs !== undefined &&
@@ -658,7 +696,7 @@ async function getExperimentAssignment(options) {
658
696
  : clampHeader(incoming.get('referer'), ASSIGNMENT_FIELD_LIMITS.referrer);
659
697
  const requestBody = {
660
698
  projectId: options.projectId,
661
- experimentId: options.experimentId,
699
+ ...(experimentId !== undefined ? { experimentId } : {}),
662
700
  userToken: options.userToken,
663
701
  context: {
664
702
  currentUrl: options.currentUrl,
@@ -672,7 +710,7 @@ async function getExperimentAssignment(options) {
672
710
  userVisitCount,
673
711
  },
674
712
  };
675
- const response = await fetch(`${edgeBaseUrl(options.edgeUrl)}/api/v1/assignment`, {
713
+ const response = await edgeFetch(`${edgeBaseUrl(options.edgeUrl)}/api/v1/assignment`, {
676
714
  method: 'POST',
677
715
  headers: createAssignmentHeaders(incoming),
678
716
  signal: controller.signal,
@@ -681,7 +719,7 @@ async function getExperimentAssignment(options) {
681
719
  if (!response.ok)
682
720
  return undefined;
683
721
  const result = await response.json();
684
- return isExperimentAssignmentResult(result, options.experimentId)
722
+ return isExperimentAssignmentResult(result, experimentId)
685
723
  ? result
686
724
  : undefined;
687
725
  }
@@ -697,7 +735,7 @@ async function requestFeatureFlags(options) {
697
735
  const controller = new AbortController();
698
736
  const timeout = setTimeout(() => controller.abort(), (_a = options.requestTimeoutMs) !== null && _a !== void 0 ? _a : 3000);
699
737
  try {
700
- const response = await fetch(`${edgeBaseUrl(options.edgeUrl)}/api/v1/flags?v=1`, {
738
+ const response = await edgeFetch(`${edgeBaseUrl(options.edgeUrl)}/api/v1/flags?v=1`, {
701
739
  method: 'POST',
702
740
  headers: {
703
741
  'Content-Type': 'application/json',
@@ -784,7 +822,7 @@ async function fetchAntiflickerFromEdge(projectId, currentUrl, referrer, userCon
784
822
  const config = encodeURIComponent(JSON.stringify(payload));
785
823
  const edgeUrl = new URL(`${"https://edge.cofra.me"}/cf.css`);
786
824
  edgeUrl.searchParams.set('config', config);
787
- const response = await fetch(edgeUrl, {
825
+ const response = await edgeFetch(edgeUrl, {
788
826
  headers,
789
827
  });
790
828
  if (!response.ok)
@@ -836,6 +874,7 @@ async function getProjectData(options) {
836
874
  exports.evaluateFlags = evaluateFlags;
837
875
  exports.flushFeatureFlagEvents = flushFeatureFlagEvents;
838
876
  exports.generateUserToken = generateUserToken;
877
+ exports.getAssignments = getAssignments;
839
878
  exports.getExperimentAssignment = getExperimentAssignment;
840
879
  exports.getFeatureFlag = getFeatureFlag;
841
880
  exports.getFeatureFlagPayload = getFeatureFlagPayload;