@openreplay/tracker 18.0.14-beta.1 → 18.0.14

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/dist/cjs/entry.js CHANGED
@@ -1827,6 +1827,30 @@ class CanvasRecorder {
1827
1827
  this.MAX_QUEUE_SIZE = 50; // ~500 images max (50 batches × 10 images)
1828
1828
  this.pendingBatches = [];
1829
1829
  this.isProcessingQueue = false;
1830
+ /**
1831
+ * Reacts to a runtime sanitization change on a canvas: stop capturing if it
1832
+ * just became masked, start if it just became visible. (Already-sent frames
1833
+ * can't be retracted — escalation only stops future capture.)
1834
+ */
1835
+ this.resanitizeCanvas = (node, id) => {
1836
+ if (!hasTag(node, 'canvas')) {
1837
+ return;
1838
+ }
1839
+ const isIgnored = this.app.sanitizer.isObscured(id) || this.app.sanitizer.isHidden(id);
1840
+ if (isIgnored) {
1841
+ if (this.snapshots[id] || this.observers.has(id)) {
1842
+ const observer = this.observers.get(id);
1843
+ if (observer) {
1844
+ observer.disconnect();
1845
+ this.observers.delete(id);
1846
+ }
1847
+ this.cleanupCanvas(id);
1848
+ }
1849
+ }
1850
+ else if (!this.snapshots[id] && !this.observers.has(id)) {
1851
+ this.captureCanvas(node);
1852
+ }
1853
+ };
1830
1854
  this.restartTracking = () => {
1831
1855
  this.clear();
1832
1856
  this.app.nodes.scanTree(this.captureCanvas);
@@ -1930,6 +1954,7 @@ class CanvasRecorder {
1930
1954
  setTimeout(() => {
1931
1955
  this.app.nodes.scanTree(this.captureCanvas);
1932
1956
  this.app.nodes.attachNodeCallback(this.captureCanvas);
1957
+ this.app.attachResanitizeCallback(this.resanitizeCanvas);
1933
1958
  }, 125);
1934
1959
  }
1935
1960
  sendSnaps(images, canvasId, createdAt) {
@@ -2814,6 +2839,120 @@ function ConstructedStyleSheets (app) {
2814
2839
  });
2815
2840
  }
2816
2841
 
2842
+ exports.SanitizeLevel = void 0;
2843
+ (function (SanitizeLevel) {
2844
+ SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
2845
+ SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
2846
+ SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
2847
+ })(exports.SanitizeLevel || (exports.SanitizeLevel = {}));
2848
+ const stringWiper = (input) => input
2849
+ .trim()
2850
+ .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
2851
+ class Sanitizer {
2852
+ constructor(params) {
2853
+ // Node id -> level. Plain (0) is never stored; a missing entry means Plain.
2854
+ // A map (not the old grow-only Sets) so levels can be raised and lowered.
2855
+ this.levels = new Map();
2856
+ this.app = params.app;
2857
+ const defaultOptions = {
2858
+ obscureTextEmails: true,
2859
+ obscureTextNumbers: false,
2860
+ privateMode: false,
2861
+ domSanitizer: undefined,
2862
+ };
2863
+ this.privateMode = params.options?.privateMode ?? false;
2864
+ this.options = Object.assign(defaultOptions, params.options);
2865
+ }
2866
+ // Pure recomputation of a node's level from the live DOM + parent level.
2867
+ // Reading current state on every call is what lets resanitize() pick up
2868
+ // runtime attribute/domSanitizer changes.
2869
+ computeLevel(node, parentLevel) {
2870
+ if (this.options.privateMode) {
2871
+ if (isElementNode(node) && !hasOpenreplayAttribute(node, 'unmask')) {
2872
+ return exports.SanitizeLevel.Obscured;
2873
+ }
2874
+ if (isTextNode(node) && !hasOpenreplayAttribute(node.parentNode, 'unmask')) {
2875
+ return exports.SanitizeLevel.Obscured;
2876
+ }
2877
+ }
2878
+ let level = exports.SanitizeLevel.Plain;
2879
+ if (parentLevel >= exports.SanitizeLevel.Obscured ||
2880
+ (isElementNode(node) &&
2881
+ (hasOpenreplayAttribute(node, 'masked') || hasOpenreplayAttribute(node, 'obscured')))) {
2882
+ level = exports.SanitizeLevel.Obscured;
2883
+ }
2884
+ if (parentLevel === exports.SanitizeLevel.Hidden ||
2885
+ (isElementNode(node) &&
2886
+ (hasOpenreplayAttribute(node, 'htmlmasked') || hasOpenreplayAttribute(node, 'hidden')))) {
2887
+ level = exports.SanitizeLevel.Hidden;
2888
+ }
2889
+ if (this.options.domSanitizer !== undefined && isElementNode(node)) {
2890
+ const sanitizeLevel = this.options.domSanitizer(node);
2891
+ if (sanitizeLevel === exports.SanitizeLevel.Obscured && level < exports.SanitizeLevel.Obscured) {
2892
+ level = exports.SanitizeLevel.Obscured;
2893
+ }
2894
+ if (sanitizeLevel === exports.SanitizeLevel.Hidden) {
2895
+ level = exports.SanitizeLevel.Hidden;
2896
+ }
2897
+ }
2898
+ return level;
2899
+ }
2900
+ getLevel(id) {
2901
+ return this.levels.get(id) ?? exports.SanitizeLevel.Plain;
2902
+ }
2903
+ // Sets a node's level (either direction) and returns the previous one.
2904
+ setLevel(id, level) {
2905
+ const prev = this.getLevel(id);
2906
+ if (level === exports.SanitizeLevel.Plain) {
2907
+ this.levels.delete(id);
2908
+ }
2909
+ else {
2910
+ this.levels.set(id, level);
2911
+ }
2912
+ return prev;
2913
+ }
2914
+ handleNode(id, parentID, node) {
2915
+ const level = this.computeLevel(node, this.getLevel(parentID));
2916
+ // Escalate-only: commits never lower a level, only resanitize/setLevel do.
2917
+ if (level > this.getLevel(id)) {
2918
+ this.setLevel(id, level);
2919
+ }
2920
+ }
2921
+ sanitize(id, data) {
2922
+ if (this.getLevel(id) >= exports.SanitizeLevel.Obscured) {
2923
+ // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
2924
+ return stringWiper(data);
2925
+ }
2926
+ if (this.options.obscureTextNumbers) {
2927
+ data = data.replace(/\d/g, '0');
2928
+ }
2929
+ if (this.options.obscureTextEmails) {
2930
+ data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
2931
+ const [name, domain] = email.split('@');
2932
+ const [domainName, host] = domain.split('.');
2933
+ return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
2934
+ });
2935
+ }
2936
+ return data;
2937
+ }
2938
+ isObscured(id) {
2939
+ return this.getLevel(id) >= exports.SanitizeLevel.Obscured;
2940
+ }
2941
+ isHidden(id) {
2942
+ return this.getLevel(id) === exports.SanitizeLevel.Hidden;
2943
+ }
2944
+ getInnerTextSecure(el) {
2945
+ const id = this.app.nodes.getID(el);
2946
+ if (!id) {
2947
+ return '';
2948
+ }
2949
+ return this.sanitize(id, el.innerText);
2950
+ }
2951
+ clear() {
2952
+ this.levels.clear();
2953
+ }
2954
+ }
2955
+
2817
2956
  const iconCache = {};
2818
2957
  const svgUrlCache = {};
2819
2958
  async function parseUseEl(useElement, mode, domParser) {
@@ -3403,6 +3542,100 @@ class Observer {
3403
3542
  beforeCommit(this.app.nodes.getID(node));
3404
3543
  this.commitNodes(true);
3405
3544
  }
3545
+ /**
3546
+ * Re-evaluates sanitization for every tracked node in `root`'s subtree against
3547
+ * the current DOM and re-emits whatever changed. Pass the highest node you
3548
+ * changed (or the document root) so inherited levels propagate correctly.
3549
+ */
3550
+ resanitizeSubtree(root) {
3551
+ if (!isObservable(root)) {
3552
+ return;
3553
+ }
3554
+ const parent = root.parentNode;
3555
+ const parentId = parent !== null ? this.app.nodes.getID(parent) : undefined;
3556
+ const parentLevel = parentId !== undefined ? this.app.sanitizer.getLevel(parentId) : exports.SanitizeLevel.Plain;
3557
+ this.resanitizeNode(root, parentLevel);
3558
+ }
3559
+ resanitizeNode(node, parentLevel) {
3560
+ if (isIgnored(node)) {
3561
+ return;
3562
+ }
3563
+ const id = this.app.nodes.getID(node);
3564
+ if (id === undefined) {
3565
+ // Untracked (new, or under a hidden ancestor): the live observer handles it.
3566
+ return;
3567
+ }
3568
+ const newLevel = this.app.sanitizer.computeLevel(node, parentLevel);
3569
+ const prevLevel = this.app.sanitizer.getLevel(id);
3570
+ const wasHidden = prevLevel === exports.SanitizeLevel.Hidden;
3571
+ const willHidden = newLevel === exports.SanitizeLevel.Hidden;
3572
+ // Crossing the hidden boundary changes the rendered structure (placeholder vs
3573
+ // real subtree), so rebuild rather than re-emit.
3574
+ if (wasHidden !== willHidden) {
3575
+ this.recreateSubtree(node);
3576
+ return;
3577
+ }
3578
+ if (willHidden) {
3579
+ return;
3580
+ }
3581
+ // Plain <-> Obscured: same structure, only leaf content changes.
3582
+ if (prevLevel !== newLevel) {
3583
+ this.app.sanitizer.setLevel(id, newLevel);
3584
+ this.reemitNode(id, node);
3585
+ }
3586
+ for (let child = node.firstChild; child !== null; child = child.nextSibling) {
3587
+ this.resanitizeNode(child, newLevel);
3588
+ }
3589
+ }
3590
+ // Destroys the node player-side and re-emits its subtree from scratch (new ids)
3591
+ // so it materializes at the freshly-computed level.
3592
+ recreateSubtree(node) {
3593
+ const id = this.app.nodes.getID(node);
3594
+ if (id === undefined) {
3595
+ return;
3596
+ }
3597
+ this.app.send(RemoveNode(id));
3598
+ this.clearSubtreeRegistration(node);
3599
+ this.bindTree(node);
3600
+ this.commitNodes();
3601
+ }
3602
+ clearSubtreeRegistration(node) {
3603
+ const clearOne = (n) => {
3604
+ const oldId = this.app.nodes.getID(n);
3605
+ if (oldId !== undefined) {
3606
+ this.app.sanitizer.setLevel(oldId, exports.SanitizeLevel.Plain);
3607
+ }
3608
+ this.app.nodes.unregisterNode(n);
3609
+ };
3610
+ const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
3611
+ acceptNode: (n) => isIgnored(n) || this.app.nodes.getID(n) === undefined
3612
+ ? NodeFilter.FILTER_REJECT
3613
+ : NodeFilter.FILTER_ACCEPT,
3614
+ },
3615
+ // @ts-ignore
3616
+ false);
3617
+ // Collect first, then clear: unregistering mutates the ids the walker reads.
3618
+ const subtree = [];
3619
+ while (walker.nextNode()) {
3620
+ subtree.push(walker.currentNode);
3621
+ }
3622
+ clearOne(node);
3623
+ subtree.forEach(clearOne);
3624
+ }
3625
+ reemitNode(id, node) {
3626
+ if (isTextNode(node)) {
3627
+ const parent = node.parentNode;
3628
+ if (parent !== null && isElementNode(parent)) {
3629
+ // re-runs sanitize() at the level we just set
3630
+ this.sendNodeData(id, parent, node.data);
3631
+ }
3632
+ return;
3633
+ }
3634
+ if (isElementNode(node)) {
3635
+ // inputs/images/canvas re-emit their own payload via registered callbacks
3636
+ this.app.callResanitizeCallbacks(node, id);
3637
+ }
3638
+ }
3406
3639
  disconnect() {
3407
3640
  // THEORY S3: a disconnect may discard MutationRecords still queued by the
3408
3641
  // browser. takeRecords() drains them — they would be discarded by
@@ -3719,9 +3952,19 @@ class TopObserver extends Observer {
3719
3952
  // it has no node_id here
3720
3953
  this.app.nodes.callNodeCallbacks(document, true);
3721
3954
  }, window.document.documentElement);
3722
- // "DOM parsed" signal: observeRoot sent the full initial tree synchronously above.
3723
- // The worker keys on this attribute (BatchWriter) to finalize the visual batch.
3724
- this.app.send(SetNodeAttribute(0, 'orloaded', 'true'));
3955
+ // DEBUG orload
3956
+ const markCandidates = [
3957
+ document.head,
3958
+ document.body,
3959
+ document.body ? document.body.querySelector('div') : null,
3960
+ ];
3961
+ for (const candidate of markCandidates) {
3962
+ const markId = candidate ? this.app.nodes.getID(candidate) : undefined;
3963
+ if (markId !== undefined) {
3964
+ this.app.send(SetNodeAttribute(markId, 'orloaded', 'true'));
3965
+ break;
3966
+ }
3967
+ }
3725
3968
  }
3726
3969
  crossdomainObserve(rootNodeId, frameOder, frameLevel) {
3727
3970
  const observer = this;
@@ -3752,94 +3995,6 @@ class TopObserver extends Observer {
3752
3995
  }
3753
3996
  }
3754
3997
 
3755
- exports.SanitizeLevel = void 0;
3756
- (function (SanitizeLevel) {
3757
- SanitizeLevel[SanitizeLevel["Plain"] = 0] = "Plain";
3758
- SanitizeLevel[SanitizeLevel["Obscured"] = 1] = "Obscured";
3759
- SanitizeLevel[SanitizeLevel["Hidden"] = 2] = "Hidden";
3760
- })(exports.SanitizeLevel || (exports.SanitizeLevel = {}));
3761
- const stringWiper = (input) => input
3762
- .trim()
3763
- .replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff\s]/g, '*');
3764
- class Sanitizer {
3765
- constructor(params) {
3766
- this.obscured = new Set();
3767
- this.hidden = new Set();
3768
- this.app = params.app;
3769
- const defaultOptions = {
3770
- obscureTextEmails: true,
3771
- obscureTextNumbers: false,
3772
- privateMode: false,
3773
- domSanitizer: undefined,
3774
- };
3775
- this.privateMode = params.options?.privateMode ?? false;
3776
- this.options = Object.assign(defaultOptions, params.options);
3777
- }
3778
- handleNode(id, parentID, node) {
3779
- if (this.options.privateMode) {
3780
- if (isElementNode(node) && !hasOpenreplayAttribute(node, 'unmask')) {
3781
- return this.obscured.add(id);
3782
- }
3783
- if (isTextNode(node) && !hasOpenreplayAttribute(node.parentNode, 'unmask')) {
3784
- return this.obscured.add(id);
3785
- }
3786
- }
3787
- if (this.obscured.has(parentID) ||
3788
- (isElementNode(node) &&
3789
- (hasOpenreplayAttribute(node, 'masked') || hasOpenreplayAttribute(node, 'obscured')))) {
3790
- this.obscured.add(id);
3791
- }
3792
- if (this.hidden.has(parentID) ||
3793
- (isElementNode(node) &&
3794
- (hasOpenreplayAttribute(node, 'htmlmasked') || hasOpenreplayAttribute(node, 'hidden')))) {
3795
- this.hidden.add(id);
3796
- }
3797
- if (this.options.domSanitizer !== undefined && isElementNode(node)) {
3798
- const sanitizeLevel = this.options.domSanitizer(node);
3799
- if (sanitizeLevel === exports.SanitizeLevel.Obscured) {
3800
- this.obscured.add(id);
3801
- }
3802
- if (sanitizeLevel === exports.SanitizeLevel.Hidden) {
3803
- this.hidden.add(id);
3804
- }
3805
- }
3806
- }
3807
- sanitize(id, data) {
3808
- if (this.obscured.has(id)) {
3809
- // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
3810
- return stringWiper(data);
3811
- }
3812
- if (this.options.obscureTextNumbers) {
3813
- data = data.replace(/\d/g, '0');
3814
- }
3815
- if (this.options.obscureTextEmails) {
3816
- data = data.replace(/^\w+([+.-]\w+)*@\w+([.-]\w+)*\.\w{2,3}$/g, (email) => {
3817
- const [name, domain] = email.split('@');
3818
- const [domainName, host] = domain.split('.');
3819
- return `${stars(name)}@${stars(domainName)}.${stars(host)}`;
3820
- });
3821
- }
3822
- return data;
3823
- }
3824
- isObscured(id) {
3825
- return this.obscured.has(id);
3826
- }
3827
- isHidden(id) {
3828
- return this.hidden.has(id);
3829
- }
3830
- getInnerTextSecure(el) {
3831
- const id = this.app.nodes.getID(el);
3832
- if (!id) {
3833
- return '';
3834
- }
3835
- return this.sanitize(id, el.innerText);
3836
- }
3837
- clear() {
3838
- this.obscured.clear();
3839
- this.hidden.clear();
3840
- }
3841
- }
3842
-
3843
3998
  const tokenSeparator = '_$_';
3844
3999
  class Session {
3845
4000
  constructor(params) {
@@ -4039,7 +4194,7 @@ class Ticker {
4039
4194
  * this value is injected during build time via rollup
4040
4195
  * */
4041
4196
  // @ts-ignore
4042
- const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.inflightKeepaliveBytes=0,this.ingestURL=t+\"/v1/web/i\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t,s=\"player\",i){if(this.busy||!this.token)this.queue.push({batch:t,dataType:s,split:i});else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t,s,i);else{const e=++this.lastBatchNum;this.sendBatch(t,!1,e,s,i)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t.batch,t.dataType,t.split);else{const s=++this.lastBatchNum;this.sendBatch(t.batch,!1,s,t.dataType,t.split)}else this.busy=!1}retry(t,s,i,e=\"player\",n){if(this.attemptsCount>=this.MAX_ATTEMPTS_COUNT)return void this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`);this.attemptsCount++;const h=new Uint8Array(t);setTimeout((()=>this.sendBatch(h,s,i,e,n)),this.ATTEMPT_TIMEOUT*this.attemptsCount)}sendBatch(t,s,i,e=\"player\",n){var h,r,a;if(0===t.length)return console.error(\"OpenReplay: refusing to send 0-byte batch.\",{batchNum:i,dataType:e,isCompressed:s,batch:t}),this.attemptsCount=0,void this.sendNext();const u=(null!==(h=null==i?void 0:i.toString())&&void 0!==h?h:\"0\").match(/^([^_]+)(?:_([^_]+))?/),o=null!==(r=null==u?void 0:u[1])&&void 0!==r?r:\"0\",l=(null==u?void 0:u[2])?u[2]:\"\";this.busy=!0;const c={Authorization:`Bearer ${this.token}`,DataType:e};if(s&&(c[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`,e,n)}),500);const d=t.length<65536&&this.inflightKeepaliveBytes+t.length<=65536;d&&(this.inflightKeepaliveBytes+=t.length);const p=()=>{d&&(this.inflightKeepaliveBytes-=t.length)},f=t.byteLength;let g=this.ingestURL;g+=`?batch=${null!==(a=this.pageNo)&&void 0!==a?a:0}`,g+=`_${o}`,g+=`_${f}`,g+=\"_\"+(d?\"kyes\":\"kno\"),l&&(g+=`_${l}`),void 0!==n&&(g+=`&split=${n}`),fetch(g,{body:t,method:\"POST\",headers:c,keepalive:d}).then((h=>{var r;if(p(),null===(r=h.body)||void 0===r||r.cancel().catch((()=>{})),401===h.status)return this.busy=!1,void this.onUnauthorised();h.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${h.status}`,e,n):(this.attemptsCount=0,this.sendNext())})).catch((h=>{p(),console.warn(\"OpenReplay:\",h),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${h.message}`,e,n)}))}sendCompressed(t,s=\"player\",i){const e=++this.lastBatchNum;this.sendBatch(t,!0,e,s,i)}sendUncompressed(t,s=\"player\",i){const e=++this.lastBatchNum;this.sendBatch(t,!1,e,s,i)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=new Set([60,61,71,73]),i=new Set([21,22,40,41,44,45,46,47,48,79,83,84,85,87,89,116,120,121,123]),e=new Set([17,23,24,27,28,29,30,42,63,64,78,112,115,124]),n=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class h{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}getCurrentCheckpoint(){return this.checkpointOffset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=n.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}rewind(t,s){t>this.offset||s>this.checkpointOffset||(this.offset=t,this.checkpointOffset=s)}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class r extends h{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class a{constructor(t,s,i){this.bufferSize=t,this.version=s,this.dataType=i,this.sizeBuffer=new Uint8Array(3),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,this.encoder=new r(t)}push(t,s){const i=this.encoder,e=null===this.snap,n=i.getCurrentOffset(),h=i.getCurrentCheckpoint();if(e){const t={pageNo:s.pageNo,firstIndex:s.index,timestamp:s.timestamp,url:s.url,tabId:s.tabId};if(!this.writeHeader(t))return i.rewind(n,h),!1;this.snap=t,this.lastPushedTs=s.timestamp}return 0===t[0]||s.timestamp===this.lastPushedTs||this.writeMessageWithSize([0,s.timestamp])?this.writeMessageWithSize(t)?(this.lastPushedTs=s.timestamp,0!==t[0]&&(this.hasNonTimestamp=!0),!0):(i.rewind(n,h),e&&(this.snap=null),!1):(i.rewind(n,h),!1)}hasContent(){return null!==this.snap&&this.hasNonTimestamp}size(){return null===this.snap?0:this.encoder.getCurrentOffset()}flush(){if(!this.hasContent())return this.reset(),null;const t=this.encoder.flush();return this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,t}reset(){this.encoder.reset(),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0}writeHeader(t){const s=this.encoder,i=[81,this.version,t.pageNo,t.firstIndex,t.timestamp,t.url];return!!s.uint(i[0])&&(!!s.encode(i)&&(!(s.getCurrentOffset()>this.bufferSize)&&(s.checkpoint(),!!this.writeMessageWithSize([0,t.timestamp])&&!!this.writeMessageWithSize([118,t.tabId]))))}writeMessageWithSize(t){const s=this.encoder;if(!s.uint(t[0])||!s.skip(3))return!1;const i=s.getCurrentOffset();if(!s.encode(t))return!1;const e=s.getCurrentOffset(),n=e-i;return n>16777215?(console.warn(\"OpenReplay: max message size overflow.\"),!1):!(e>this.bufferSize)&&(this.writeSizeAt(n,i-3),s.checkpoint(),!0)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}}class u{constructor(t,s,i,e,n,h,r=!1,u){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=e,this.tabId=n,this.onOfflineEnd=h,this.localDebug=r,this.onLocalSave=u,this.nextIndex=0,this.beaconSize=2e5,this.beaconSizeLimit=1e6,this.protocolVersion=1,this.visualSent=!1,this.messagesWritten=!1,this.heldOther=[],this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"),this.assetBuilder=new a(this.beaconSize,3,\"assets\"),this.devtoolsBuilder=new a(this.beaconSize,4,\"devtools\"),this.analyticsBuilder=new a(this.beaconSize,5,\"analytics\")}initActive(){return 2===this.protocolVersion&&!this.visualSent}playerVersion(){return 2===this.protocolVersion?2:1}currentCtx(){return{pageNo:this.pageNo,index:this.nextIndex,timestamp:this.timestamp,url:this.url,tabId:this.tabId}}setBeaconSizeLimit(t){this.beaconSizeLimit=t}setProtocolVersion(t){if(this.protocolVersion!==t){if(this.protocolVersion=t,2===t&&!this.messagesWritten)return this.playerBuilder.reset(),this.assetBuilder.reset(),this.playerBuilder=new a(this.beaconSizeLimit,this.playerVersion(),\"player\"),void(this.assetBuilder=new a(this.beaconSizeLimit,3,\"assets\"));2===t&&(this.visualSent=!0),this.playerBuilder.reset(),this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\")}}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();if(12===t[0]&&\"orloaded\"===t[2])return void(this.initActive()&&this.finalizeVisual());0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]),this.messagesWritten=!0;const s=this.routeMessage(t);this.pushTo(s,t)}routeMessage(t){if(2===this.protocolVersion){const n=t[0];if(s.has(n))return this.assetBuilder;if(i.has(n))return this.devtoolsBuilder;if(e.has(n))return this.analyticsBuilder}return this.playerBuilder}pushTo(t,s){const i=this.currentCtx();if(this.initActive())return void this.pushDuringInit(t,s,i);if(t.push(s,i))return void this.nextIndex++;if(t===this.assetBuilder&&this.flushBuilder(this.playerBuilder),this.flushBuilder(t),t.push(s,i))return void this.nextIndex++;const e=new a(this.beaconSizeLimit,t.version,t.dataType);if(!e.push(s,i))return void console.warn(\"OpenReplay: beacon size overflow. Skipping large message.\",s);this.nextIndex++;const n=e.flush();n&&(t===this.assetBuilder&&this.flushBuilder(this.playerBuilder),this.emitBatch(n,t.dataType,!1))}pushDuringInit(t,s,i){const e=t===this.playerBuilder||t===this.assetBuilder;if(t.push(s,i))return this.nextIndex++,void(e&&this.playerBuilder.size()+this.assetBuilder.size()>=this.beaconSizeLimit&&this.finalizeVisual());if(e)return this.finalizeVisual(),void this.pushTo(this.routeMessage(s),s);if(this.flushBuilderToHeld(t),t.push(s,i))return void this.nextIndex++;const n=new a(this.beaconSizeLimit,t.version,t.dataType);if(!n.push(s,i))return void console.warn(\"OpenReplay: beacon size overflow. Skipping large message.\",s);this.nextIndex++;const h=n.flush();h&&this.heldOther.push({batch:h,dataType:t.dataType})}flushBuilderToHeld(t){const s=t.flush();s&&this.heldOther.push({batch:s,dataType:t.dataType})}finalizeVisual(t=!1){const s=this.playerBuilder.flush(),i=this.assetBuilder.flush();if(this.visualSent=!0,this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"),this.assetBuilder=new a(this.beaconSize,3,\"assets\"),s&&i){const e=new Uint8Array(s.length+i.length);e.set(s,0),e.set(i,s.length),this.emitBatch(e,\"visual\",t,s.length)}else s?this.emitBatch(s,\"visual\",t):i&&this.emitBatch(i,\"assets\",t);for(const s of this.heldOther)this.emitBatch(s.batch,s.dataType,t);this.heldOther.length=0,this.flushBuilder(this.devtoolsBuilder,t),this.flushBuilder(this.analyticsBuilder,t)}flushBuilder(t,s=!1){const i=t.flush();return!!i&&(this.emitBatch(i,t.dataType,s),!0)}emitBatch(t,s,i,e){this.localDebug&&this.onLocalSave&&this.onLocalSave(`${s}-${Date.now()}`,t.slice()),this.onBatch(t,i,s,e)}finaliseBatch(t=!1){this.initActive()?this.finalizeVisual(t):(this.flushBuilder(this.playerBuilder,t),this.flushBuilder(this.assetBuilder,t),this.flushBuilder(this.devtoolsBuilder,t),this.flushBuilder(this.analyticsBuilder,t))}clean(){this.playerBuilder.reset(),this.assetBuilder.reset(),this.devtoolsBuilder.reset(),this.analyticsBuilder.reset(),this.heldOther.length=0,this.visualSent=!1,this.messagesWritten=!1}}var o;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(o||(o={}));let l=null,c=null,d=o.NotActive;function p(t){c&&c.finaliseBatch(t)}function f(){return new Promise((t=>{d=o.Stopping,null!==y&&(clearInterval(y),y=null),c&&(c.clean(),c=null),l&&(l.clean(),setTimeout((()=>{l=null}),20)),setTimeout((()=>{d=o.NotActive,t(null)}),100)}))}function g(){[o.Stopped,o.Stopping].includes(d)||(postMessage(\"a_stop\"),f().then((()=>{postMessage(\"a_start\")})))}let m,y=null;self.onmessage=({data:s})=>{var i;if(\"stop\"===s)return p(),void f().then((()=>{d=o.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!l)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&l.sendCompressed(s.batch,s.dataType,s.split)}if(\"uncompressed\"===s.type){if(!l)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&l.sendUncompressed(s.batch,s.dataType,s.split)}return\"start\"===s.type?(d=o.Starting,l=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),f()}(t)}),s.connAttemptCount,s.connAttemptGap,((t,s,i)=>{postMessage({type:\"compress\",batch:t,dataType:s,split:i},[t.buffer])}),s.pageNo),c=new u(s.pageNo,s.timestamp,s.url,((t,s,i=\"player\",e)=>{l&&(s?l.sendUncompressed(t,i,e):l.push(t,i,e))}),s.tabId,(()=>postMessage({type:\"queue_empty\"})),null!==(i=s.localDebug)&&void 0!==i&&i,((t,s)=>{postMessage({type:\"local_save\",name:t,batch:s},[s.buffer])})),null===y&&(y=setInterval(p,3e4)),d=o.Active):\"auth\"===s.type?l?c?(l.authorise(s.token),s.beaconSizeLimit&&c.setBeaconSizeLimit(s.beaconSizeLimit),void(s.protocolVersion&&c.setProtocolVersion(s.protocolVersion))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(c){const t=c;s.forEach((s=>{55===s[0]&&(s[1]?m=setTimeout((()=>g()),18e5):clearTimeout(m)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else p(!0);else p()}}();\n";
4197
+ const workerBodyFn = "!function(){\"use strict\";class t{constructor(t,s,i,e=10,n=250,h,r){this.onUnauthorised=s,this.onFailure=i,this.MAX_ATTEMPTS_COUNT=e,this.ATTEMPT_TIMEOUT=n,this.onCompress=h,this.pageNo=r,this.attemptsCount=0,this.busy=!1,this.queue=[],this.token=null,this.lastBatchNum=0,this.inflightKeepaliveBytes=0,this.ingestURL=t+\"/v1/web/i\",this.isCompressing=void 0!==h}getQueueStatus(){return 0===this.queue.length&&!this.busy}authorise(t){this.token=t,this.busy||this.sendNext()}push(t,s=\"player\"){if(this.busy||!this.token)this.queue.push({batch:t,dataType:s});else if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t,s);else{const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}}sendNext(){const t=this.queue.shift();if(t)if(this.busy=!0,this.isCompressing&&this.onCompress)this.onCompress(t.batch,t.dataType);else{const s=++this.lastBatchNum;this.sendBatch(t.batch,!1,s,t.dataType)}else this.busy=!1}retry(t,s,i,e=\"player\"){if(this.attemptsCount>=this.MAX_ATTEMPTS_COUNT)return void this.onFailure(`Failed to send batch after ${this.attemptsCount} attempts.`);this.attemptsCount++;const n=new Uint8Array(t);setTimeout((()=>this.sendBatch(n,s,i,e)),this.ATTEMPT_TIMEOUT*this.attemptsCount)}sendBatch(t,s,i,e=\"player\"){var n,h,r;if(0===t.length)return console.error(\"OpenReplay: refusing to send 0-byte batch.\",{batchNum:i,dataType:e,isCompressed:s,batch:t}),this.attemptsCount=0,void this.sendNext();const a=(null!==(n=null==i?void 0:i.toString())&&void 0!==n?n:\"0\").match(/^([^_]+)(?:_([^_]+))?/),u=null!==(h=null==a?void 0:a[1])&&void 0!==h?h:\"0\",o=(null==a?void 0:a[2])?a[2]:\"\";this.busy=!0;const c={Authorization:`Bearer ${this.token}`,DataType:e};if(s&&(c[\"Content-Encoding\"]=\"gzip\"),null===this.token)return void setTimeout((()=>{this.sendBatch(t,s,`${null!=i?i:\"noBatchNum\"}_newToken`,e)}),500);const l=t.length<65536&&this.inflightKeepaliveBytes+t.length<=65536;l&&(this.inflightKeepaliveBytes+=t.length);const d=()=>{l&&(this.inflightKeepaliveBytes-=t.length)},p=t.byteLength;let f=this.ingestURL;f+=`?batch=${null!==(r=this.pageNo)&&void 0!==r?r:0}`,f+=`_${u}`,f+=`_${p}`,f+=\"_\"+(l?\"kyes\":\"kno\"),o&&(f+=`_${o}`),fetch(f,{body:t,method:\"POST\",headers:c,keepalive:l}).then((n=>{var h;if(d(),null===(h=n.body)||void 0===h||h.cancel().catch((()=>{})),401===n.status)return this.busy=!1,void this.onUnauthorised();n.status>=400?this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_network:${n.status}`,e):(this.attemptsCount=0,this.sendNext())})).catch((n=>{d(),console.warn(\"OpenReplay:\",n),this.retry(t,s,`${null!=i?i:\"noBatchNum\"}_reject:${n.message}`,e)}))}sendCompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!0,i,s)}sendUncompressed(t,s=\"player\"){const i=++this.lastBatchNum;this.sendBatch(t,!1,i,s)}clean(){this.sendNext(),setTimeout((()=>{this.token=null,this.queue.length=0}),10)}}const s=new Set([60,61,71,73]),i=new Set([21,22,40,41,44,45,46,47,48,79,83,84,85,87,89,116,120,121,123]),e=new Set([17,23,24,27,28,29,30,42,63,64,78,112,115,124]),n=\"function\"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let e=-1;for(let n=0,h=0,r=0;r!==s;){if(n=t.charCodeAt(r),r+=1,n>=55296&&n<=56319){if(r===s){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;break}if(h=t.charCodeAt(r),!(h>=56320&&h<=57343)){i[e+=1]=239,i[e+=1]=191,i[e+=1]=189;continue}if(n=1024*(n-55296)+h-56320+65536,r+=1,n>65535){i[e+=1]=240|n>>>18,i[e+=1]=128|n>>>12&63,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n;continue}}n<=127?i[e+=1]=0|n:n<=2047?(i[e+=1]=192|n>>>6,i[e+=1]=128|63&n):(i[e+=1]=224|n>>>12,i[e+=1]=128|n>>>6&63,i[e+=1]=128|63&n)}return i.subarray(0,e+1)}};class h{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}getCurrentOffset(){return this.offset}getCurrentCheckpoint(){return this.checkpointOffset}checkpoint(){this.checkpointOffset=this.offset}get isEmpty(){return 0===this.offset}skip(t){return this.offset+=t,this.offset<=this.size}set(t,s){this.data.set(t,s)}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=n.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}rewind(t,s){t>this.offset||s>this.checkpointOffset||(this.offset=t,this.checkpointOffset=s)}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}class r extends h{encode(t){switch(t[0]){case 0:case 11:case 114:case 115:return this.uint(t[1]);case 4:case 44:case 47:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3]);case 5:case 20:case 65:case 70:case 75:case 76:case 77:return this.uint(t[1])&&this.uint(t[2]);case 6:return this.int(t[1])&&this.int(t[2]);case 7:return!0;case 8:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.string(t[4])&&this.boolean(t[5]);case 9:case 10:case 24:case 35:case 51:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3]);case 12:case 52:case 61:case 71:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3]);case 13:case 14:case 17:case 34:case 36:case 50:case 54:return this.uint(t[1])&&this.string(t[2]);case 16:return this.uint(t[1])&&this.int(t[2])&&this.int(t[3]);case 18:return this.uint(t[1])&&this.string(t[2])&&this.int(t[3]);case 19:return this.uint(t[1])&&this.boolean(t[2]);case 21:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8]);case 22:case 27:case 30:case 41:case 45:case 46:case 43:case 63:case 64:case 79:case 124:return this.string(t[1])&&this.string(t[2]);case 23:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 28:case 29:case 42:case 117:case 118:return this.string(t[1]);case 40:return this.string(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 48:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.int(t[5]);case 49:return this.int(t[1])&&this.int(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 53:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8]);case 55:return this.boolean(t[1]);case 57:case 60:return this.uint(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 58:case 120:return this.int(t[1]);case 68:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])&&this.uint(t[6]);case 69:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3])&&this.string(t[4]);case 73:return this.uint(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 78:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4]);case 81:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.int(t[4])&&this.string(t[5]);case 83:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.string(t[5])&&this.uint(t[6])&&this.uint(t[7])&&this.uint(t[8])&&this.uint(t[9]);case 84:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.uint(t[4])&&this.string(t[5])&&this.string(t[6]);case 85:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10])&&this.uint(t[11])&&this.uint(t[12])&&this.uint(t[13])&&this.uint(t[14])&&this.uint(t[15])&&this.uint(t[16])&&this.uint(t[17]);case 87:return this.string(t[1])&&this.int(t[2])&&this.int(t[3]);case 89:return this.string(t[1])&&this.int(t[2])&&this.int(t[3])&&this.int(t[4])&&this.int(t[5])&&this.string(t[6]);case 112:return this.uint(t[1])&&this.string(t[2])&&this.boolean(t[3])&&this.string(t[4])&&this.int(t[5])&&this.int(t[6]);case 113:return this.uint(t[1])&&this.uint(t[2])&&this.string(t[3]);case 116:return this.uint(t[1])&&this.uint(t[2])&&this.uint(t[3])&&this.uint(t[4])&&this.uint(t[5])&&this.uint(t[6])&&this.string(t[7])&&this.string(t[8])&&this.uint(t[9])&&this.boolean(t[10]);case 119:return this.string(t[1])&&this.uint(t[2]);case 121:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.uint(t[4]);case 122:return this.string(t[1])&&this.string(t[2])&&this.uint(t[3])&&this.string(t[4]);case 123:return this.string(t[1])&&this.string(t[2])&&this.string(t[3])&&this.string(t[4])&&this.uint(t[5])}}}class a{constructor(t,s,i){this.bufferSize=t,this.version=s,this.dataType=i,this.sizeBuffer=new Uint8Array(3),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,this.encoder=new r(t)}push(t,s){const i=this.encoder,e=null===this.snap,n=i.getCurrentOffset(),h=i.getCurrentCheckpoint();if(e){const t={pageNo:s.pageNo,firstIndex:s.index,timestamp:s.timestamp,url:s.url,tabId:s.tabId};if(!this.writeHeader(t))return i.rewind(n,h),!1;this.snap=t,this.lastPushedTs=s.timestamp}return 0===t[0]||s.timestamp===this.lastPushedTs||this.writeMessageWithSize([0,s.timestamp])?this.writeMessageWithSize(t)?(this.lastPushedTs=s.timestamp,0!==t[0]&&(this.hasNonTimestamp=!0),!0):(i.rewind(n,h),e&&(this.snap=null),!1):(i.rewind(n,h),!1)}hasContent(){return null!==this.snap&&this.hasNonTimestamp}flush(){if(!this.hasContent())return this.reset(),null;const t=this.encoder.flush();return this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0,t}reset(){this.encoder.reset(),this.snap=null,this.hasNonTimestamp=!1,this.lastPushedTs=0}writeHeader(t){const s=this.encoder,i=[81,this.version,t.pageNo,t.firstIndex,t.timestamp,t.url];return!!s.uint(i[0])&&(!!s.encode(i)&&(!(s.getCurrentOffset()>this.bufferSize)&&(s.checkpoint(),!!this.writeMessageWithSize([0,t.timestamp])&&!!this.writeMessageWithSize([118,t.tabId]))))}writeMessageWithSize(t){const s=this.encoder;if(!s.uint(t[0])||!s.skip(3))return!1;const i=s.getCurrentOffset();if(!s.encode(t))return!1;const e=s.getCurrentOffset(),n=e-i;return n>16777215?(console.warn(\"OpenReplay: max message size overflow.\"),!1):!(e>this.bufferSize)&&(this.writeSizeAt(n,i-3),s.checkpoint(),!0)}writeSizeAt(t,s){for(let s=0;s<3;s++)this.sizeBuffer[s]=t>>8*s;this.encoder.set(this.sizeBuffer,s)}}class u{constructor(t,s,i,e,n,h,r=!1,u){this.pageNo=t,this.timestamp=s,this.url=i,this.onBatch=e,this.tabId=n,this.onOfflineEnd=h,this.localDebug=r,this.onLocalSave=u,this.nextIndex=0,this.beaconSize=2e5,this.beaconSizeLimit=1e6,this.protocolVersion=1,this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"),this.assetBuilder=new a(this.beaconSize,3,\"assets\"),this.devtoolsBuilder=new a(this.beaconSize,4,\"devtools\"),this.analyticsBuilder=new a(this.beaconSize,5,\"analytics\")}playerVersion(){return 2===this.protocolVersion?2:1}currentCtx(){return{pageNo:this.pageNo,index:this.nextIndex,timestamp:this.timestamp,url:this.url,tabId:this.tabId}}setBeaconSizeLimit(t){this.beaconSizeLimit=t}setProtocolVersion(t){this.protocolVersion!==t&&(this.protocolVersion=t,this.playerBuilder.reset(),this.playerBuilder=new a(this.beaconSize,this.playerVersion(),\"player\"))}writeMessage(t){if(-1===t[0])return this.finaliseBatch(),this.onOfflineEnd();0===t[0]&&(this.timestamp=t[1]),122===t[0]&&(this.url=t[1]);const s=this.routeMessage(t);this.pushTo(s,t)}routeMessage(t){if(2===this.protocolVersion){const n=t[0];if(s.has(n))return this.assetBuilder;if(i.has(n))return this.devtoolsBuilder;if(e.has(n))return this.analyticsBuilder}return this.playerBuilder}pushTo(t,s){const i=this.currentCtx();if(t.push(s,i))return void this.nextIndex++;if(t===this.assetBuilder&&this.flushBuilder(this.playerBuilder),this.flushBuilder(t),t.push(s,i))return void this.nextIndex++;const e=new a(this.beaconSizeLimit,t.version,t.dataType);if(!e.push(s,i))return void console.warn(\"OpenReplay: beacon size overflow. Skipping large message.\",s);this.nextIndex++;const n=e.flush();n&&(t===this.assetBuilder&&this.flushBuilder(this.playerBuilder),this.emitBatch(n,t.dataType,!1))}flushBuilder(t,s=!1){const i=t.flush();return!!i&&(this.emitBatch(i,t.dataType,s),!0)}emitBatch(t,s,i){this.localDebug&&this.onLocalSave&&this.onLocalSave(`${s}-${Date.now()}`,t.slice()),this.onBatch(t,i,s)}finaliseBatch(t=!1){this.flushBuilder(this.playerBuilder,t),this.flushBuilder(this.assetBuilder,t),this.flushBuilder(this.devtoolsBuilder,t),this.flushBuilder(this.analyticsBuilder,t)}clean(){this.playerBuilder.reset(),this.assetBuilder.reset(),this.devtoolsBuilder.reset(),this.analyticsBuilder.reset()}}var o;!function(t){t[t.NotActive=0]=\"NotActive\",t[t.Starting=1]=\"Starting\",t[t.Stopping=2]=\"Stopping\",t[t.Active=3]=\"Active\",t[t.Stopped=4]=\"Stopped\"}(o||(o={}));let c=null,l=null,d=o.NotActive;function p(t){l&&l.finaliseBatch(t)}function f(){return new Promise((t=>{d=o.Stopping,null!==y&&(clearInterval(y),y=null),l&&(l.clean(),l=null),c&&(c.clean(),setTimeout((()=>{c=null}),20)),setTimeout((()=>{d=o.NotActive,t(null)}),100)}))}function g(){[o.Stopped,o.Stopping].includes(d)||(postMessage(\"a_stop\"),f().then((()=>{postMessage(\"a_start\")})))}let m,y=null;self.onmessage=({data:s})=>{var i;if(\"stop\"===s)return p(),void f().then((()=>{d=o.Stopped}));if(\"forceFlushBatch\"!==s)if(\"closing\"!==s){if(!Array.isArray(s)){if(\"compressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Compressed batch.\"),void g();s.batch&&c.sendCompressed(s.batch,s.dataType)}if(\"uncompressed\"===s.type){if(!c)return console.debug(\"OR WebWorker: sender not initialised. Uncompressed batch.\"),void g();s.batch&&c.sendUncompressed(s.batch,s.dataType)}return\"start\"===s.type?(d=o.Starting,c=new t(s.ingestPoint,(()=>{g()}),(t=>{!function(t){postMessage({type:\"failure\",reason:t}),f()}(t)}),s.connAttemptCount,s.connAttemptGap,((t,s)=>{postMessage({type:\"compress\",batch:t,dataType:s},[t.buffer])}),s.pageNo),l=new u(s.pageNo,s.timestamp,s.url,((t,s,i=\"player\")=>{c&&(s?c.sendUncompressed(t,i):c.push(t,i))}),s.tabId,(()=>postMessage({type:\"queue_empty\"})),null!==(i=s.localDebug)&&void 0!==i&&i,((t,s)=>{postMessage({type:\"local_save\",name:t,batch:s},[s.buffer])})),null===y&&(y=setInterval(p,3e4)),d=o.Active):\"auth\"===s.type?c?l?(c.authorise(s.token),s.beaconSizeLimit&&l.setBeaconSizeLimit(s.beaconSizeLimit),void(s.protocolVersion&&l.setProtocolVersion(s.protocolVersion))):(console.debug(\"OR WebWorker: writer not initialised. Received auth.\"),void g()):(console.debug(\"OR WebWorker: sender not initialised. Received auth.\"),void g()):void 0}if(l){const t=l;s.forEach((s=>{55===s[0]&&(s[1]?m=setTimeout((()=>g()),18e5):clearTimeout(m)),t.writeMessage(s)}))}else postMessage(\"not_init\"),g()}else p(!0);else p()}}();\n";
4043
4198
  const CANCELED = 'canceled';
4044
4199
  const bufferStorageKey = 'or_buffer_1';
4045
4200
  const UnsuccessfulStart = (reason) => ({ reason, success: false });
@@ -4091,6 +4246,8 @@ class App {
4091
4246
  constructor(projectKey, sessionToken, options, signalError, insideIframe) {
4092
4247
  this.signalError = signalError;
4093
4248
  this.insideIframe = insideIframe;
4249
+ // Registered by input/img/canvas to re-emit a node when its level changes.
4250
+ this.resanitizeCallbacks = [];
4094
4251
  this.messages = [];
4095
4252
  /**
4096
4253
  * we need 2 buffers, so we don't lose anything
@@ -4102,7 +4259,7 @@ class App {
4102
4259
  this.stopCallbacks = [];
4103
4260
  this.commitCallbacks = [];
4104
4261
  this.activityState = ActivityState.NotActive;
4105
- this.version = '18.0.14-beta.1'; // TODO: version compatability check inside each plugin.
4262
+ this.version = '18.0.14'; // TODO: version compatability check inside each plugin.
4106
4263
  this.socketMode = false;
4107
4264
  this.compressionThreshold = 24 * 1000;
4108
4265
  this.bc = null;
@@ -4633,6 +4790,26 @@ class App {
4633
4790
  this.restartCanvasTracking = () => {
4634
4791
  this.canvasRecorder?.restartTracking();
4635
4792
  };
4793
+ this.attachResanitizeCallback = (cb) => {
4794
+ this.resanitizeCallbacks.push(cb);
4795
+ };
4796
+ this.callResanitizeCallbacks = (node, id) => {
4797
+ this.resanitizeCallbacks.forEach((cb) => cb(node, id));
4798
+ };
4799
+ this.resanitize = (el) => {
4800
+ const root = el ?? (IN_BROWSER ? document.documentElement : undefined);
4801
+ if (!root) {
4802
+ return;
4803
+ }
4804
+ this.observer.resanitizeSubtree(root);
4805
+ };
4806
+ this.checkSanitization = (el) => {
4807
+ const id = this.nodes.getID(el);
4808
+ if (id === undefined) {
4809
+ return undefined;
4810
+ }
4811
+ return this.sanitizer.getLevel(id);
4812
+ };
4636
4813
  this.flushBuffer = async (buffer) => {
4637
4814
  return new Promise((res, reject) => {
4638
4815
  if (buffer.length === 0) {
@@ -5035,8 +5212,6 @@ class App {
5035
5212
  else if (data.type === 'compress') {
5036
5213
  const batch = data.batch;
5037
5214
  const dataType = data.dataType;
5038
- // split is a decompressed-byte offset, so it survives gzip unchanged.
5039
- const split = data.split;
5040
5215
  const batchSize = batch.byteLength;
5041
5216
  const hasCompressionAPI = 'CompressionStream' in globalThis;
5042
5217
  if (batchSize > this.compressionThreshold && hasCompressionAPI) {
@@ -5049,16 +5224,15 @@ class App {
5049
5224
  type: 'compressed',
5050
5225
  batch: new Uint8Array(compressedBuffer),
5051
5226
  dataType,
5052
- split,
5053
5227
  });
5054
5228
  })
5055
5229
  .catch((err) => {
5056
5230
  this.debug.error('Openreplay compression error:', err);
5057
- this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType, split });
5231
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
5058
5232
  });
5059
5233
  }
5060
5234
  else {
5061
- this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType, split });
5235
+ this.worker?.postMessage({ type: 'uncompressed', batch: batch, dataType });
5062
5236
  }
5063
5237
  }
5064
5238
  else if (data.type === 'local_save') {
@@ -6328,6 +6502,12 @@ function Img (app) {
6328
6502
  sendImgAttrs(node);
6329
6503
  observer.observe(node, { attributes: true, attributeFilter: ['src', 'srcset'] });
6330
6504
  });
6505
+ // On a runtime level change, re-evaluate placeholder vs real src for this image.
6506
+ app.attachResanitizeCallback((node) => {
6507
+ if (hasTag(node, 'img')) {
6508
+ sendImgAttrs(node);
6509
+ }
6510
+ });
6331
6511
  }
6332
6512
 
6333
6513
  const INPUT_TYPES = [
@@ -6514,6 +6694,13 @@ function Input (app, opts) {
6514
6694
  }
6515
6695
  app.send(InputChange(id, value, mask !== 0, label, hesitationTime, inputTime));
6516
6696
  }
6697
+ // Re-emit a field's value when its sanitization level changes at runtime.
6698
+ // getInputValue() reads the current level, so re-sending applies the new mask.
6699
+ app.attachResanitizeCallback((node, id) => {
6700
+ if (isTextFieldElement(node) || hasTag(node, 'select')) {
6701
+ sendInputValue(id, node);
6702
+ }
6703
+ });
6517
6704
  app.nodes.attachNodeCallback(app.safe((node) => {
6518
6705
  const id = app.nodes.getID(node);
6519
6706
  if (id === undefined) {
@@ -9404,7 +9591,7 @@ class ConstantProperties {
9404
9591
  user_id: this.user_id,
9405
9592
  distinct_id: this.deviceId,
9406
9593
  sdk_edition: 'web',
9407
- sdk_version: '18.0.14-beta.1',
9594
+ sdk_version: '18.0.14',
9408
9595
  timezone: getUTCOffsetString(),
9409
9596
  search_engine: this.searchEngine,
9410
9597
  };
@@ -10106,7 +10293,7 @@ class API {
10106
10293
  this.signalStartIssue = (reason, missingApi) => {
10107
10294
  const doNotTrack = this.checkDoNotTrack();
10108
10295
  console.log("Tracker couldn't start due to:", JSON.stringify({
10109
- trackerVersion: '18.0.14-beta.1',
10296
+ trackerVersion: '18.0.14',
10110
10297
  projectKey: this.options.projectKey,
10111
10298
  doNotTrack,
10112
10299
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,
@@ -10118,6 +10305,31 @@ class API {
10118
10305
  }
10119
10306
  this.app.restartCanvasTracking();
10120
10307
  };
10308
+ /**
10309
+ * Re-evaluates sanitization against the current DOM and re-emits whatever
10310
+ * changed, updating already-recorded nodes mid-session. Call after toggling
10311
+ * `data-openreplay-*` attributes or after changing whatever your `domSanitizer`
10312
+ * keys on (class/id/etc).
10313
+ *
10314
+ * @param el - the highest node you changed; omit to re-scan the whole document;
10315
+ * scanning the entire doc is O(dom size)
10316
+ * */
10317
+ this.resanitize = (el) => {
10318
+ if (this.app === null) {
10319
+ return;
10320
+ }
10321
+ this.app.resanitize(el);
10322
+ };
10323
+ /**
10324
+ * Returns the sanitization level the tracker currently has for a node
10325
+ * (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
10326
+ * */
10327
+ this.checkSanitization = (el) => {
10328
+ if (this.app === null) {
10329
+ return undefined;
10330
+ }
10331
+ return this.app.checkSanitization(el);
10332
+ };
10121
10333
  this.getSessionURL = (options) => {
10122
10334
  if (this.app === null) {
10123
10335
  return undefined;
@@ -10131,7 +10343,10 @@ class API {
10131
10343
  }
10132
10344
  };
10133
10345
  this.identify = this.setUserID;
10134
- this.track = this.analytics?.track;
10346
+ // Delegates at call time: `this.analytics` is assigned in the constructor body,
10347
+ // which runs AFTER field initializers, so binding it here directly would always
10348
+ // capture `undefined`.
10349
+ this.track = (eventName, properties, options) => this.analytics?.track(eventName, properties, options);
10135
10350
  this.userID = (id) => {
10136
10351
  deprecationWarn("'userID' method", "'setUserID' method", '/');
10137
10352
  this.setUserID(id);
@@ -10514,12 +10729,20 @@ class TrackerSingleton {
10514
10729
  constructor() {
10515
10730
  this.instance = null;
10516
10731
  this.isConfigured = false;
10732
+ this.setUserID = (id) => {
10733
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10734
+ return;
10735
+ }
10736
+ this.instance.setUserID(id);
10737
+ };
10517
10738
  this.identify = this.setUserID;
10518
10739
  this.track = (eventName, properties, options) => {
10519
10740
  if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10520
10741
  return;
10521
10742
  }
10522
- this.instance.track?.(eventName, properties);
10743
+ // Route through analytics directly: Tracker.track is bound to analytics?.track
10744
+ // at field-init time (before analytics exists), so it is always undefined.
10745
+ this.instance.analytics?.track(eventName, properties, options);
10523
10746
  };
10524
10747
  }
10525
10748
  /**
@@ -10549,11 +10772,16 @@ class TrackerSingleton {
10549
10772
  if (!IN_BROWSER) {
10550
10773
  return Promise.resolve({ success: false, reason: 'Not in browser environment' });
10551
10774
  }
10552
- if (!this.ensureConfigured()) {
10775
+ if (!this.ensureConfigured() || !this.instance) {
10553
10776
  return Promise.resolve({ success: false, reason: 'Tracker not configured' });
10554
10777
  }
10555
- return (this.instance?.start(startOpts) ||
10556
- Promise.resolve({ success: false, reason: 'Tracker not initialized' }));
10778
+ // Tracker.start() rejects (instead of resolving {success:false}) when the
10779
+ // underlying app failed to initialise (non-https, missing api, doNotTrack,
10780
+ // already initialised...). Normalize so callers always get {success, reason}.
10781
+ return this.instance.start(startOpts).catch((reason) => ({
10782
+ success: false,
10783
+ reason: typeof reason === 'string' ? reason : String(reason),
10784
+ }));
10557
10785
  }
10558
10786
  /**
10559
10787
  * Stop the session and return sessionHash
@@ -10565,21 +10793,9 @@ class TrackerSingleton {
10565
10793
  }
10566
10794
  return this.instance.stop();
10567
10795
  }
10568
- setUserID(id) {
10569
- if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10570
- return;
10571
- }
10572
- this.instance.setUserID(id);
10573
- }
10574
10796
  get analytics() {
10575
- if (this.instance?.analytics) {
10576
- return this.instance.analytics;
10577
- }
10578
- else {
10579
- return null;
10580
- }
10797
+ return this.instance?.analytics ?? null;
10581
10798
  }
10582
- ;
10583
10799
  /**
10584
10800
  * Set metadata for the current session
10585
10801
  *
@@ -10754,6 +10970,51 @@ class TrackerSingleton {
10754
10970
  }
10755
10971
  return this.instance.getTabId();
10756
10972
  }
10973
+ /**
10974
+ * Re-evaluates sanitization against the current DOM and re-emits whatever
10975
+ * changed, updating already-recorded nodes mid-session. Call after toggling
10976
+ * `data-openreplay-*` attributes or after changing whatever your `domSanitizer`
10977
+ * keys on (class/id/etc).
10978
+ *
10979
+ * @param el - the highest node you changed; omit to re-scan the whole document.
10980
+ * */
10981
+ resanitize(el) {
10982
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10983
+ return;
10984
+ }
10985
+ return this.instance.resanitize(el);
10986
+ }
10987
+ /**
10988
+ * Returns the sanitization level the tracker currently has for a node
10989
+ * (0 = Plain, 1 = Obscured, 2 = Hidden), or undefined if it isn't tracked.
10990
+ * */
10991
+ checkSanitization(el) {
10992
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10993
+ return undefined;
10994
+ }
10995
+ return this.instance.checkSanitization(el);
10996
+ }
10997
+ incident(options) {
10998
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
10999
+ return;
11000
+ }
11001
+ this.instance.incident(options);
11002
+ }
11003
+ /**
11004
+ * Use custom token for analytics events without session recording
11005
+ * */
11006
+ setAnalyticsToken(token) {
11007
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
11008
+ return;
11009
+ }
11010
+ this.instance.setAnalyticsToken(token);
11011
+ }
11012
+ getAnalyticsToken() {
11013
+ if (!IN_BROWSER || !this.ensureConfigured() || !this.instance) {
11014
+ return undefined;
11015
+ }
11016
+ return this.instance.getAnalyticsToken();
11017
+ }
10757
11018
  }
10758
11019
  const tracker = new TrackerSingleton();
10759
11020