@coframe/sdk 1.1.33 → 1.1.35

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.
@@ -539,6 +539,30 @@ const getElement = (selector, options) => {
539
539
  }
540
540
  return null;
541
541
  };
542
+ const resolvedPathCache = new Map();
543
+ const selectorKey = (selector) => selector.fingerprint || JSON.stringify(selector);
544
+ const keepsIdentity = (document, element, selector) => {
545
+ if (element.tagName.toLowerCase() !== selector.t.toLowerCase())
546
+ return false;
547
+ const attrs = [
548
+ ['id', selector.id],
549
+ ['data-test-id', selector.dataTestId],
550
+ ['aria-label', selector.ariaLabel],
551
+ ];
552
+ for (const [name, value] of attrs) {
553
+ if (!value || element.getAttribute(name) === value)
554
+ continue;
555
+ try {
556
+ const carriers = document.querySelectorAll(`[${name}="${value.replace(/["\\]/g, '\\$&')}"]`);
557
+ if (carriers.length === 1 && carriers[0] !== element)
558
+ return false;
559
+ }
560
+ catch (_a) {
561
+ return false;
562
+ }
563
+ }
564
+ return true;
565
+ };
542
566
  const getElementUniversal = (document, selector, debug) => {
543
567
  var _a, _b, _c;
544
568
  const debugList = [];
@@ -621,20 +645,34 @@ const getElementUniversal = (document, selector, debug) => {
621
645
  }
622
646
  return element;
623
647
  };
648
+ const key = selectorKey(selector);
649
+ const remembered = resolvedPathCache.get(key);
650
+ if (remembered && !debug) {
651
+ const element = tryPath(remembered);
652
+ if (element && keepsIdentity(document, element, selector))
653
+ return { element };
654
+ resolvedPathCache.delete(key);
655
+ }
656
+ const tryPathAndCache = (pathSegments) => {
657
+ const element = tryPath(pathSegments);
658
+ if (element && !debug)
659
+ resolvedPathCache.set(key, pathSegments);
660
+ return element;
661
+ };
624
662
  const naivePaths = generateNaivePaths(selector);
625
- let element = tryPath(naivePaths[0]);
663
+ let element = tryPathAndCache(naivePaths[0]);
626
664
  if (element && !debug)
627
665
  return { element };
628
666
  const [relativePaths, partialRelativePaths] = generateRelativePaths(selector, 10);
629
667
  for (const relativePath of relativePaths) {
630
- element = tryPath(relativePath);
668
+ element = tryPathAndCache(relativePath);
631
669
  if (element && !debug)
632
670
  return { element };
633
671
  }
634
672
  if (selector.text) {
635
673
  const textPaths = generatePathsByText(selector.text, selector.t);
636
674
  for (const textPath of textPaths) {
637
- element = tryPath(textPath);
675
+ element = tryPathAndCache(textPath);
638
676
  if (element && !debug)
639
677
  return { element };
640
678
  }
@@ -648,7 +686,7 @@ const getElementUniversal = (document, selector, debug) => {
648
686
  const descPath = (_a = generateRelativePaths(descendant.sel, 1)[0]) === null || _a === void 0 ? void 0 : _a[0];
649
687
  if (descPath && descPath.length > 0) {
650
688
  const modifiedPath = appendToLastPathSegment(descPath, ancestorSelectors);
651
- element = tryPath(modifiedPath);
689
+ element = tryPathAndCache(modifiedPath);
652
690
  if (element && !debug)
653
691
  return { element };
654
692
  }
@@ -657,7 +695,7 @@ const getElementUniversal = (document, selector, debug) => {
657
695
  for (const textPath of textPaths) {
658
696
  if (textPath && textPath.length > 0) {
659
697
  const textBasedPath = appendToLastPathSegment(textPath, ancestorSelectors);
660
- element = tryPath(textBasedPath);
698
+ element = tryPathAndCache(textBasedPath);
661
699
  if (element && !debug)
662
700
  return { element };
663
701
  }
@@ -666,12 +704,12 @@ const getElementUniversal = (document, selector, debug) => {
666
704
  }
667
705
  }
668
706
  for (const partialPath of naivePaths.slice(1)) {
669
- element = tryPath(partialPath);
707
+ element = tryPathAndCache(partialPath);
670
708
  if (element && !debug)
671
709
  return { element };
672
710
  }
673
711
  for (const partialPath of partialRelativePaths) {
674
- element = tryPath(partialPath);
712
+ element = tryPathAndCache(partialPath);
675
713
  if (element && !debug)
676
714
  return { element };
677
715
  }
@@ -3133,6 +3171,43 @@ function cacheFrames(url, data) {
3133
3171
 
3134
3172
  let conversionListenerStop = null;
3135
3173
  const activeJSBinders = new Set();
3174
+ const missedUntil = new Map();
3175
+ const FIRST_RETRY_WAIT_MS = 2000;
3176
+ const RETRY_WAIT_FACTOR = 3;
3177
+ const MAX_RETRY_WAIT_MS = 60000;
3178
+ function hasUniversalSelector(selector) {
3179
+ var _a, _b;
3180
+ if (!selector)
3181
+ return false;
3182
+ if (selector.universal)
3183
+ return true;
3184
+ return (((_a = selector.and) !== null && _a !== void 0 ? _a : []).some(hasUniversalSelector) ||
3185
+ ((_b = selector.or) !== null && _b !== void 0 ? _b : []).some(hasUniversalSelector));
3186
+ }
3187
+ function isWaitingAfterMiss(metricId, url, now) {
3188
+ const miss = missedUntil.get(metricId);
3189
+ return !!miss && miss.url === url && now < miss.nextAt;
3190
+ }
3191
+ let retryTimer = null;
3192
+ let retryAt = Infinity;
3193
+ function scheduleRetry(at, now, rebind) {
3194
+ if (retryTimer && at >= retryAt)
3195
+ return;
3196
+ if (retryTimer)
3197
+ clearTimeout(retryTimer);
3198
+ retryAt = at;
3199
+ retryTimer = setTimeout(() => {
3200
+ retryTimer = null;
3201
+ retryAt = Infinity;
3202
+ rebind();
3203
+ }, Math.max(0, at - now));
3204
+ }
3205
+ function recordMiss(metricId, url, now) {
3206
+ const prev = missedUntil.get(metricId);
3207
+ const misses = prev && prev.url === url ? prev.misses + 1 : 1;
3208
+ const wait = Math.min(FIRST_RETRY_WAIT_MS * RETRY_WAIT_FACTOR ** (misses - 1), MAX_RETRY_WAIT_MS);
3209
+ missedUntil.set(metricId, { url, misses, nextAt: now + wait });
3210
+ }
3136
3211
  function rebindMetrics(coframe, logger, metrics, nonce) {
3137
3212
  var _a, _b;
3138
3213
  if (!metrics || metrics.length === 0) {
@@ -3176,6 +3251,9 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
3176
3251
  for (let i = 0; i < elementsToClean.length; i++) {
3177
3252
  elementsToClean[i].removeAttribute('coframe-metric-ids');
3178
3253
  }
3254
+ const currentUrl = getCurrentPageUrl();
3255
+ const now = Date.now();
3256
+ let earliestRetry = Infinity;
3179
3257
  for (let i = 0; i < metrics.length; i++) {
3180
3258
  const metric = metrics[i];
3181
3259
  if (metric.js_binder) {
@@ -3192,11 +3270,23 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
3192
3270
  continue;
3193
3271
  }
3194
3272
  if (metric.selector) {
3273
+ const expensive = hasUniversalSelector(metric.selector);
3274
+ if (expensive && isWaitingAfterMiss(metric.id, currentUrl, now)) {
3275
+ const miss = missedUntil.get(metric.id);
3276
+ if (miss && miss.nextAt < earliestRetry)
3277
+ earliestRetry = miss.nextAt;
3278
+ continue;
3279
+ }
3195
3280
  const elements = (_b = querySelector({
3196
3281
  selector: metric.selector,
3197
- currentUrl: getCurrentPageUrl(),
3282
+ currentUrl,
3198
3283
  isQueryingAll: true,
3199
3284
  })) !== null && _b !== void 0 ? _b : [];
3285
+ if (!expensive) ;
3286
+ else if (elements.length === 0)
3287
+ recordMiss(metric.id, currentUrl, now);
3288
+ else
3289
+ missedUntil.delete(metric.id);
3200
3290
  for (let j = 0; j < elements.length; j++) {
3201
3291
  const e = elements[j];
3202
3292
  const metricIdsStr = e.getAttribute('coframe-metric-ids');
@@ -3211,6 +3301,9 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
3211
3301
  }
3212
3302
  }
3213
3303
  }
3304
+ if (earliestRetry < Infinity) {
3305
+ scheduleRetry(earliestRetry, now, () => rebindMetrics(coframe, logger, metrics, nonce));
3306
+ }
3214
3307
  }
3215
3308
 
3216
3309
  let lastUrl = null;
@@ -537,6 +537,30 @@ const getElement = (selector, options) => {
537
537
  }
538
538
  return null;
539
539
  };
540
+ const resolvedPathCache = new Map();
541
+ const selectorKey = (selector) => selector.fingerprint || JSON.stringify(selector);
542
+ const keepsIdentity = (document, element, selector) => {
543
+ if (element.tagName.toLowerCase() !== selector.t.toLowerCase())
544
+ return false;
545
+ const attrs = [
546
+ ['id', selector.id],
547
+ ['data-test-id', selector.dataTestId],
548
+ ['aria-label', selector.ariaLabel],
549
+ ];
550
+ for (const [name, value] of attrs) {
551
+ if (!value || element.getAttribute(name) === value)
552
+ continue;
553
+ try {
554
+ const carriers = document.querySelectorAll(`[${name}="${value.replace(/["\\]/g, '\\$&')}"]`);
555
+ if (carriers.length === 1 && carriers[0] !== element)
556
+ return false;
557
+ }
558
+ catch (_a) {
559
+ return false;
560
+ }
561
+ }
562
+ return true;
563
+ };
540
564
  const getElementUniversal = (document, selector, debug) => {
541
565
  var _a, _b, _c;
542
566
  const debugList = [];
@@ -619,20 +643,34 @@ const getElementUniversal = (document, selector, debug) => {
619
643
  }
620
644
  return element;
621
645
  };
646
+ const key = selectorKey(selector);
647
+ const remembered = resolvedPathCache.get(key);
648
+ if (remembered && !debug) {
649
+ const element = tryPath(remembered);
650
+ if (element && keepsIdentity(document, element, selector))
651
+ return { element };
652
+ resolvedPathCache.delete(key);
653
+ }
654
+ const tryPathAndCache = (pathSegments) => {
655
+ const element = tryPath(pathSegments);
656
+ if (element && !debug)
657
+ resolvedPathCache.set(key, pathSegments);
658
+ return element;
659
+ };
622
660
  const naivePaths = generateNaivePaths(selector);
623
- let element = tryPath(naivePaths[0]);
661
+ let element = tryPathAndCache(naivePaths[0]);
624
662
  if (element && !debug)
625
663
  return { element };
626
664
  const [relativePaths, partialRelativePaths] = generateRelativePaths(selector, 10);
627
665
  for (const relativePath of relativePaths) {
628
- element = tryPath(relativePath);
666
+ element = tryPathAndCache(relativePath);
629
667
  if (element && !debug)
630
668
  return { element };
631
669
  }
632
670
  if (selector.text) {
633
671
  const textPaths = generatePathsByText(selector.text, selector.t);
634
672
  for (const textPath of textPaths) {
635
- element = tryPath(textPath);
673
+ element = tryPathAndCache(textPath);
636
674
  if (element && !debug)
637
675
  return { element };
638
676
  }
@@ -646,7 +684,7 @@ const getElementUniversal = (document, selector, debug) => {
646
684
  const descPath = (_a = generateRelativePaths(descendant.sel, 1)[0]) === null || _a === void 0 ? void 0 : _a[0];
647
685
  if (descPath && descPath.length > 0) {
648
686
  const modifiedPath = appendToLastPathSegment(descPath, ancestorSelectors);
649
- element = tryPath(modifiedPath);
687
+ element = tryPathAndCache(modifiedPath);
650
688
  if (element && !debug)
651
689
  return { element };
652
690
  }
@@ -655,7 +693,7 @@ const getElementUniversal = (document, selector, debug) => {
655
693
  for (const textPath of textPaths) {
656
694
  if (textPath && textPath.length > 0) {
657
695
  const textBasedPath = appendToLastPathSegment(textPath, ancestorSelectors);
658
- element = tryPath(textBasedPath);
696
+ element = tryPathAndCache(textBasedPath);
659
697
  if (element && !debug)
660
698
  return { element };
661
699
  }
@@ -664,12 +702,12 @@ const getElementUniversal = (document, selector, debug) => {
664
702
  }
665
703
  }
666
704
  for (const partialPath of naivePaths.slice(1)) {
667
- element = tryPath(partialPath);
705
+ element = tryPathAndCache(partialPath);
668
706
  if (element && !debug)
669
707
  return { element };
670
708
  }
671
709
  for (const partialPath of partialRelativePaths) {
672
- element = tryPath(partialPath);
710
+ element = tryPathAndCache(partialPath);
673
711
  if (element && !debug)
674
712
  return { element };
675
713
  }
@@ -3131,6 +3169,43 @@ function cacheFrames(url, data) {
3131
3169
 
3132
3170
  let conversionListenerStop = null;
3133
3171
  const activeJSBinders = new Set();
3172
+ const missedUntil = new Map();
3173
+ const FIRST_RETRY_WAIT_MS = 2000;
3174
+ const RETRY_WAIT_FACTOR = 3;
3175
+ const MAX_RETRY_WAIT_MS = 60000;
3176
+ function hasUniversalSelector(selector) {
3177
+ var _a, _b;
3178
+ if (!selector)
3179
+ return false;
3180
+ if (selector.universal)
3181
+ return true;
3182
+ return (((_a = selector.and) !== null && _a !== void 0 ? _a : []).some(hasUniversalSelector) ||
3183
+ ((_b = selector.or) !== null && _b !== void 0 ? _b : []).some(hasUniversalSelector));
3184
+ }
3185
+ function isWaitingAfterMiss(metricId, url, now) {
3186
+ const miss = missedUntil.get(metricId);
3187
+ return !!miss && miss.url === url && now < miss.nextAt;
3188
+ }
3189
+ let retryTimer = null;
3190
+ let retryAt = Infinity;
3191
+ function scheduleRetry(at, now, rebind) {
3192
+ if (retryTimer && at >= retryAt)
3193
+ return;
3194
+ if (retryTimer)
3195
+ clearTimeout(retryTimer);
3196
+ retryAt = at;
3197
+ retryTimer = setTimeout(() => {
3198
+ retryTimer = null;
3199
+ retryAt = Infinity;
3200
+ rebind();
3201
+ }, Math.max(0, at - now));
3202
+ }
3203
+ function recordMiss(metricId, url, now) {
3204
+ const prev = missedUntil.get(metricId);
3205
+ const misses = prev && prev.url === url ? prev.misses + 1 : 1;
3206
+ const wait = Math.min(FIRST_RETRY_WAIT_MS * RETRY_WAIT_FACTOR ** (misses - 1), MAX_RETRY_WAIT_MS);
3207
+ missedUntil.set(metricId, { url, misses, nextAt: now + wait });
3208
+ }
3134
3209
  function rebindMetrics(coframe, logger, metrics, nonce) {
3135
3210
  var _a, _b;
3136
3211
  if (!metrics || metrics.length === 0) {
@@ -3174,6 +3249,9 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
3174
3249
  for (let i = 0; i < elementsToClean.length; i++) {
3175
3250
  elementsToClean[i].removeAttribute('coframe-metric-ids');
3176
3251
  }
3252
+ const currentUrl = getCurrentPageUrl();
3253
+ const now = Date.now();
3254
+ let earliestRetry = Infinity;
3177
3255
  for (let i = 0; i < metrics.length; i++) {
3178
3256
  const metric = metrics[i];
3179
3257
  if (metric.js_binder) {
@@ -3190,11 +3268,23 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
3190
3268
  continue;
3191
3269
  }
3192
3270
  if (metric.selector) {
3271
+ const expensive = hasUniversalSelector(metric.selector);
3272
+ if (expensive && isWaitingAfterMiss(metric.id, currentUrl, now)) {
3273
+ const miss = missedUntil.get(metric.id);
3274
+ if (miss && miss.nextAt < earliestRetry)
3275
+ earliestRetry = miss.nextAt;
3276
+ continue;
3277
+ }
3193
3278
  const elements = (_b = querySelector({
3194
3279
  selector: metric.selector,
3195
- currentUrl: getCurrentPageUrl(),
3280
+ currentUrl,
3196
3281
  isQueryingAll: true,
3197
3282
  })) !== null && _b !== void 0 ? _b : [];
3283
+ if (!expensive) ;
3284
+ else if (elements.length === 0)
3285
+ recordMiss(metric.id, currentUrl, now);
3286
+ else
3287
+ missedUntil.delete(metric.id);
3198
3288
  for (let j = 0; j < elements.length; j++) {
3199
3289
  const e = elements[j];
3200
3290
  const metricIdsStr = e.getAttribute('coframe-metric-ids');
@@ -3209,6 +3299,9 @@ function rebindMetrics(coframe, logger, metrics, nonce) {
3209
3299
  }
3210
3300
  }
3211
3301
  }
3302
+ if (earliestRetry < Infinity) {
3303
+ scheduleRetry(earliestRetry, now, () => rebindMetrics(coframe, logger, metrics, nonce));
3304
+ }
3212
3305
  }
3213
3306
 
3214
3307
  let lastUrl = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coframe/sdk",
3
- "version": "1.1.33",
3
+ "version": "1.1.35",
4
4
  "description": "Coframe optimizes your website using generative AI to deliver maximum impact to your audience and allow your site to improve itself as you sleep. Learn more at coframe.com.",
5
5
  "main": "dist/pkg/cf.cjs.js",
6
6
  "module": "dist/pkg/cf.esm.mjs",