@openreplay/tracker 16.1.4 → 16.2.1

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/lib/index.js CHANGED
@@ -3698,6 +3698,381 @@ class Nodes {
3698
3698
  }
3699
3699
  }
3700
3700
 
3701
+ let fakeIdHolder = 1000000 * 99;
3702
+ function inlineRemoteCss(node, id, baseHref, getNextID, insertRule, addOwner, forceFetch, sendPlain, onPlain) {
3703
+ const sheetId = sendPlain ? null : getNextID();
3704
+ if (!sendPlain) {
3705
+ addOwner(sheetId, id);
3706
+ }
3707
+ const sheet = node.sheet;
3708
+ if (sheet && !forceFetch) {
3709
+ try {
3710
+ const cssText = stringifyStylesheet(sheet);
3711
+ if (cssText) {
3712
+ processCssText(cssText);
3713
+ return;
3714
+ }
3715
+ }
3716
+ catch (e) {
3717
+ // console.warn("Could not stringify sheet, falling back to fetch:", e);
3718
+ }
3719
+ }
3720
+ // Fall back to fetching if we couldn't get or stringify the sheet
3721
+ if (node.href) {
3722
+ fetch(node.href)
3723
+ .then(response => {
3724
+ if (!response.ok) {
3725
+ throw new Error(`response status ${response.status}`);
3726
+ }
3727
+ return response.text();
3728
+ })
3729
+ .then(cssText => {
3730
+ if (sendPlain && onPlain) {
3731
+ onPlain(cssText, fakeIdHolder++);
3732
+ }
3733
+ else {
3734
+ processCssText(cssText);
3735
+ }
3736
+ })
3737
+ .catch(error => {
3738
+ console.error(`OpenReplay: Failed to fetch CSS from ${node.href}:`, error);
3739
+ });
3740
+ }
3741
+ function processCssText(cssText) {
3742
+ // Remove comments
3743
+ cssText = cssText.replace(/\/\*[\s\S]*?\*\//g, '');
3744
+ // Parse and process the CSS text to extract rules
3745
+ const ruleTexts = parseCSS(cssText);
3746
+ for (let i = 0; i < ruleTexts.length; i++) {
3747
+ insertRule(sheetId, ruleTexts[i], i, baseHref);
3748
+ }
3749
+ }
3750
+ function parseCSS(cssText) {
3751
+ const rules = [];
3752
+ let inComment = false;
3753
+ let inString = false;
3754
+ let stringChar = '';
3755
+ let braceLevel = 0;
3756
+ let currentRule = '';
3757
+ for (let i = 0; i < cssText.length; i++) {
3758
+ const char = cssText[i];
3759
+ const nextChar = cssText[i + 1] || '';
3760
+ // comments
3761
+ if (!inString && char === '/' && nextChar === '*') {
3762
+ inComment = true;
3763
+ i++; // Skip the next character
3764
+ continue;
3765
+ }
3766
+ if (inComment) {
3767
+ if (char === '*' && nextChar === '/') {
3768
+ inComment = false;
3769
+ i++; // Skip the next character
3770
+ }
3771
+ continue;
3772
+ }
3773
+ if (!inString && (char === '"' || char === "'")) {
3774
+ inString = true;
3775
+ stringChar = char;
3776
+ currentRule += char;
3777
+ continue;
3778
+ }
3779
+ if (inString) {
3780
+ currentRule += char;
3781
+ if (char === stringChar && cssText[i - 1] !== '\\') {
3782
+ inString = false;
3783
+ }
3784
+ continue;
3785
+ }
3786
+ currentRule += char;
3787
+ if (char === '{') {
3788
+ braceLevel++;
3789
+ }
3790
+ else if (char === '}') {
3791
+ braceLevel--;
3792
+ if (braceLevel === 0) {
3793
+ // End of a top-level rule
3794
+ rules.push(currentRule.trim());
3795
+ currentRule = '';
3796
+ }
3797
+ }
3798
+ }
3799
+ // Handle any remaining text (should be rare)
3800
+ if (currentRule.trim()) {
3801
+ rules.push(currentRule.trim());
3802
+ }
3803
+ return rules;
3804
+ }
3805
+ function stringifyStylesheet(s) {
3806
+ try {
3807
+ const rules = s.rules || s.cssRules;
3808
+ if (!rules) {
3809
+ return null;
3810
+ }
3811
+ let sheetHref = s.href;
3812
+ if (!sheetHref && s.ownerNode && s.ownerNode.ownerDocument) {
3813
+ // an inline <style> element
3814
+ sheetHref = s.ownerNode.ownerDocument.location.href;
3815
+ }
3816
+ const stringifiedRules = Array.from(rules, (rule) => stringifyRule(rule, sheetHref)).join('');
3817
+ return fixBrowserCompatibilityIssuesInCSS(stringifiedRules);
3818
+ }
3819
+ catch (error) {
3820
+ return null;
3821
+ }
3822
+ }
3823
+ function stringifyRule(rule, sheetHref) {
3824
+ if (isCSSImportRule(rule)) {
3825
+ let importStringified;
3826
+ try {
3827
+ importStringified =
3828
+ // for same-origin stylesheets,
3829
+ // we can access the imported stylesheet rules directly
3830
+ stringifyStylesheet(rule.styleSheet) ||
3831
+ // work around browser issues with the raw string `@import url(...)` statement
3832
+ escapeImportStatement(rule);
3833
+ }
3834
+ catch (error) {
3835
+ importStringified = rule.cssText;
3836
+ }
3837
+ if (rule.styleSheet.href) {
3838
+ // url()s within the imported stylesheet are relative to _that_ sheet's href
3839
+ return absolutifyURLs(importStringified, rule.styleSheet.href);
3840
+ }
3841
+ return importStringified;
3842
+ }
3843
+ else {
3844
+ let ruleStringified = rule.cssText;
3845
+ if (isCSSStyleRule(rule) && rule.selectorText.includes(':')) {
3846
+ // Safari does not escape selectors with : properly
3847
+ ruleStringified = fixSafariColons(ruleStringified);
3848
+ }
3849
+ if (sheetHref) {
3850
+ return absolutifyURLs(ruleStringified, sheetHref);
3851
+ }
3852
+ return ruleStringified;
3853
+ }
3854
+ }
3855
+ function fixBrowserCompatibilityIssuesInCSS(cssText) {
3856
+ // Fix for Chrome's handling of webkit-background-clip
3857
+ if (cssText.includes(' background-clip: text;') &&
3858
+ !cssText.includes(' -webkit-background-clip: text;')) {
3859
+ cssText = cssText.replace(/\sbackground-clip:\s*text;/g, ' -webkit-background-clip: text; background-clip: text;');
3860
+ }
3861
+ return cssText;
3862
+ }
3863
+ function escapeImportStatement(rule) {
3864
+ const { cssText } = rule;
3865
+ if (cssText.split('"').length < 3)
3866
+ return cssText;
3867
+ const statement = ['@import', `url(${JSON.stringify(rule.href)})`];
3868
+ if (rule.layerName === '') {
3869
+ statement.push(`layer`);
3870
+ }
3871
+ else if (rule.layerName) {
3872
+ statement.push(`layer(${rule.layerName})`);
3873
+ }
3874
+ if (rule.supportsText) {
3875
+ statement.push(`supports(${rule.supportsText})`);
3876
+ }
3877
+ if (rule.media.length) {
3878
+ statement.push(rule.media.mediaText);
3879
+ }
3880
+ return statement.join(' ') + ';';
3881
+ }
3882
+ function fixSafariColons(cssStringified) {
3883
+ const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
3884
+ return cssStringified.replace(regex, '$1\\$2');
3885
+ }
3886
+ function isCSSImportRule(rule) {
3887
+ return 'styleSheet' in rule;
3888
+ }
3889
+ function isCSSStyleRule(rule) {
3890
+ return 'selectorText' in rule;
3891
+ }
3892
+ function absolutifyURLs(cssText, href) {
3893
+ if (!cssText)
3894
+ return '';
3895
+ const URL_IN_CSS_REF = /url\((?:(')([^']*)'|(")(.*?)"|([^)]*))\)/gm;
3896
+ const URL_PROTOCOL_MATCH = /^(?:[a-z+]+:)?\/\//i;
3897
+ const URL_WWW_MATCH = /^www\..*/i;
3898
+ const DATA_URI = /^(data:)([^,]*),(.*)/i;
3899
+ return cssText.replace(URL_IN_CSS_REF, (origin, quote1, path1, quote2, path2, path3) => {
3900
+ const filePath = path1 || path2 || path3;
3901
+ const maybeQuote = quote1 || quote2 || '';
3902
+ if (!filePath) {
3903
+ return origin;
3904
+ }
3905
+ if (URL_PROTOCOL_MATCH.test(filePath) || URL_WWW_MATCH.test(filePath)) {
3906
+ return `url(${maybeQuote}${filePath}${maybeQuote})`;
3907
+ }
3908
+ if (DATA_URI.test(filePath)) {
3909
+ return `url(${maybeQuote}${filePath}${maybeQuote})`;
3910
+ }
3911
+ if (filePath[0] === '/') {
3912
+ return `url(${maybeQuote}${extractOrigin(href) + filePath}${maybeQuote})`;
3913
+ }
3914
+ const stack = href.split('/');
3915
+ const parts = filePath.split('/');
3916
+ stack.pop();
3917
+ for (const part of parts) {
3918
+ if (part === '.') {
3919
+ continue;
3920
+ }
3921
+ else if (part === '..') {
3922
+ stack.pop();
3923
+ }
3924
+ else {
3925
+ stack.push(part);
3926
+ }
3927
+ }
3928
+ return `url(${maybeQuote}${stack.join('/')}${maybeQuote})`;
3929
+ });
3930
+ }
3931
+ function extractOrigin(url) {
3932
+ let origin = '';
3933
+ if (url.indexOf('//') > -1) {
3934
+ origin = url.split('/').slice(0, 3).join('/');
3935
+ }
3936
+ else {
3937
+ origin = url.split('/')[0];
3938
+ }
3939
+ origin = origin.split('?')[0];
3940
+ return origin;
3941
+ }
3942
+ }
3943
+
3944
+ function hasAdoptedSS(node) {
3945
+ return (isRootNode(node) &&
3946
+ // @ts-ignore
3947
+ !!node.adoptedStyleSheets);
3948
+ }
3949
+ // TODO: encapsulate to be init-ed on-start and join with cssrules.ts under one folder
3950
+ let _id = 0xf;
3951
+ function nextID() {
3952
+ return _id++;
3953
+ }
3954
+ const styleSheetIDMap = new Map();
3955
+ function ConstructedStyleSheets (app) {
3956
+ if (app === null) {
3957
+ return;
3958
+ }
3959
+ if (!hasAdoptedSS(document)) {
3960
+ return;
3961
+ }
3962
+ const styleSheetIDMap = new Map();
3963
+ const adoptedStyleSheetsOwnings = new Map();
3964
+ const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
3965
+ let nodeID = app.nodes.getID(root);
3966
+ if (root === document) {
3967
+ nodeID = 0; // main document doesn't have nodeID. ID count starts from the documentElement
3968
+ }
3969
+ if (nodeID === undefined) {
3970
+ return;
3971
+ }
3972
+ let pastOwning = adoptedStyleSheetsOwnings.get(nodeID);
3973
+ if (!pastOwning) {
3974
+ pastOwning = [];
3975
+ }
3976
+ const nowOwning = [];
3977
+ const styleSheets = root.adoptedStyleSheets;
3978
+ if (styleSheets && Symbol.iterator in styleSheets) {
3979
+ for (const s of styleSheets) {
3980
+ let sheetID = styleSheetIDMap.get(s);
3981
+ const init = !sheetID;
3982
+ if (!sheetID) {
3983
+ sheetID = nextID();
3984
+ styleSheetIDMap.set(s, sheetID);
3985
+ }
3986
+ if (!pastOwning.includes(sheetID)) {
3987
+ app.send(AdoptedSSAddOwner(sheetID, nodeID));
3988
+ }
3989
+ if (init) {
3990
+ const rules = s.cssRules;
3991
+ for (let i = 0; i < rules.length; i++) {
3992
+ app.send(AdoptedSSInsertRuleURLBased(sheetID, rules[i].cssText, i, app.getBaseHref()));
3993
+ }
3994
+ }
3995
+ nowOwning.push(sheetID);
3996
+ }
3997
+ }
3998
+ if (Symbol.iterator in pastOwning) {
3999
+ for (const sheetID of pastOwning) {
4000
+ if (!nowOwning.includes(sheetID)) {
4001
+ app.send(AdoptedSSRemoveOwner(sheetID, nodeID));
4002
+ }
4003
+ }
4004
+ }
4005
+ adoptedStyleSheetsOwnings.set(nodeID, nowOwning);
4006
+ }, 20); // Mysterious bug:
4007
+ /* On the page https://explore.fast.design/components/fast-accordion
4008
+ the only rule inside the only adoptedStyleSheet of the iframe-s document
4009
+ gets changed during first milliseconds after the load.
4010
+ However, none of the documented methods (replace, insertRule) is triggered.
4011
+ The rule is not substituted (remains the same object), however the text gets changed.
4012
+ */
4013
+ function patchAdoptedStyleSheets(prototype) {
4014
+ const nativeAdoptedStyleSheetsDescriptor = Object.getOwnPropertyDescriptor(prototype, 'adoptedStyleSheets');
4015
+ if (nativeAdoptedStyleSheetsDescriptor) {
4016
+ Object.defineProperty(prototype, 'adoptedStyleSheets', {
4017
+ ...nativeAdoptedStyleSheetsDescriptor,
4018
+ set: function (value) {
4019
+ // @ts-ignore
4020
+ const retVal = nativeAdoptedStyleSheetsDescriptor.set.call(this, value);
4021
+ sendAdoptedStyleSheetsUpdate(this);
4022
+ return retVal;
4023
+ },
4024
+ });
4025
+ }
4026
+ }
4027
+ const patchContext = (context) => {
4028
+ // @ts-ignore
4029
+ if (context.__openreplay_adpss_patched__) {
4030
+ return;
4031
+ }
4032
+ else {
4033
+ // @ts-ignore
4034
+ context.__openreplay_adpss_patched__ = true;
4035
+ }
4036
+ patchAdoptedStyleSheets(context.Document.prototype);
4037
+ patchAdoptedStyleSheets(context.ShadowRoot.prototype);
4038
+ //@ts-ignore TODO: upgrade ts to 4.8+
4039
+ const { replace, replaceSync } = context.CSSStyleSheet.prototype;
4040
+ //@ts-ignore
4041
+ context.CSSStyleSheet.prototype.replace = function (text) {
4042
+ return replace.call(this, text).then((sheet) => {
4043
+ const sheetID = styleSheetIDMap.get(this);
4044
+ if (sheetID) {
4045
+ app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
4046
+ }
4047
+ return sheet;
4048
+ });
4049
+ };
4050
+ //@ts-ignore
4051
+ context.CSSStyleSheet.prototype.replaceSync = function (text) {
4052
+ const sheetID = styleSheetIDMap.get(this);
4053
+ if (sheetID) {
4054
+ app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
4055
+ }
4056
+ return replaceSync.call(this, text);
4057
+ };
4058
+ };
4059
+ patchContext(window);
4060
+ app.observer.attachContextCallback(app.safe(patchContext));
4061
+ app.attachStopCallback(() => {
4062
+ styleSheetIDMap.clear();
4063
+ adoptedStyleSheetsOwnings.clear();
4064
+ });
4065
+ // So far main Document is not triggered with nodeCallbacks
4066
+ app.attachStartCallback(() => {
4067
+ sendAdoptedStyleSheetsUpdate(document);
4068
+ });
4069
+ app.nodes.attachNodeCallback((node) => {
4070
+ if (hasAdoptedSS(node)) {
4071
+ sendAdoptedStyleSheetsUpdate(node);
4072
+ }
4073
+ });
4074
+ }
4075
+
3701
4076
  const iconCache = {};
3702
4077
  const svgUrlCache = {};
3703
4078
  async function parseUseEl(useElement, mode, domParser) {
@@ -3716,7 +4091,7 @@ async function parseUseEl(useElement, mode, domParser) {
3716
4091
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="${symbol.getAttribute('viewBox') || '0 0 24 24'}">
3717
4092
  ${symbol.innerHTML}
3718
4093
  </svg>
3719
- `.trim();
4094
+ `;
3720
4095
  iconCache[symbolId] = inlineSvg;
3721
4096
  return inlineSvg;
3722
4097
  }
@@ -3776,7 +4151,7 @@ async function parseUseEl(useElement, mode, domParser) {
3776
4151
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="${symbol.getAttribute('viewBox') || '0 0 24 24'}">
3777
4152
  ${symbol.innerHTML}
3778
4153
  </svg>
3779
- `.trim();
4154
+ `;
3780
4155
  iconCache[symbolId] = inlineSvg;
3781
4156
  return inlineSvg;
3782
4157
  }
@@ -3823,7 +4198,7 @@ var RecentsType;
3823
4198
  RecentsType[RecentsType["Changed"] = 2] = "Changed";
3824
4199
  })(RecentsType || (RecentsType = {}));
3825
4200
  class Observer {
3826
- constructor(app, isTopContext = false, options = { disableSprites: false }) {
4201
+ constructor(app, isTopContext = false, options = {}) {
3827
4202
  this.app = app;
3828
4203
  this.isTopContext = isTopContext;
3829
4204
  this.commited = [];
@@ -3832,8 +4207,17 @@ class Observer {
3832
4207
  this.attributesMap = new Map();
3833
4208
  this.textSet = new Set();
3834
4209
  this.disableSprites = false;
4210
+ /**
4211
+ * this option means that, instead of using link element with href to load css,
4212
+ * we will try to parse the css text instead and send it as css rules set
4213
+ * can (and will) affect performance
4214
+ * */
4215
+ this.inlineRemoteCss = false;
4216
+ this.inlinerOptions = undefined;
3835
4217
  this.domParser = new DOMParser();
3836
- this.disableSprites = options.disableSprites;
4218
+ this.disableSprites = Boolean(options.disableSprites);
4219
+ this.inlineRemoteCss = Boolean(options.inlineRemoteCss);
4220
+ this.inlinerOptions = options.inlinerOptions;
3837
4221
  this.observer = createMutationObserver(this.app.safe((mutations) => {
3838
4222
  for (const mutation of mutations) {
3839
4223
  // mutations order is sequential
@@ -3907,10 +4291,12 @@ class Observer {
3907
4291
  false);
3908
4292
  let removed = 0;
3909
4293
  const totalBeforeRemove = this.app.nodes.getNodeCount();
4294
+ const contentDocument = iframe.contentDocument;
4295
+ const nodesUnregister = this.app.nodes.unregisterNode.bind(this.app.nodes);
3910
4296
  while (walker.nextNode()) {
3911
- if (!iframe.contentDocument.contains(walker.currentNode)) {
4297
+ if (!contentDocument.contains(walker.currentNode)) {
3912
4298
  removed += 1;
3913
- this.app.nodes.unregisterNode(walker.currentNode);
4299
+ nodesUnregister(walker.currentNode);
3914
4300
  }
3915
4301
  }
3916
4302
  const removedPercent = Math.floor((removed / totalBeforeRemove) * 100);
@@ -3922,7 +4308,7 @@ class Observer {
3922
4308
  }
3923
4309
  sendNodeAttribute(id, node, name, value) {
3924
4310
  if (isSVGElement(node)) {
3925
- if (name.substring(0, 6) === 'xlink:') {
4311
+ if (name.startsWith('xlink:')) {
3926
4312
  name = name.substring(6);
3927
4313
  }
3928
4314
  if (value === null) {
@@ -3971,6 +4357,23 @@ class Observer {
3971
4357
  return;
3972
4358
  }
3973
4359
  if (name === 'style' || (name === 'href' && hasTag(node, 'link'))) {
4360
+ if ('rel' in node && node.rel === 'stylesheet' && this.inlineRemoteCss) {
4361
+ setTimeout(() => {
4362
+ inlineRemoteCss(
4363
+ // @ts-ignore
4364
+ node, id, this.app.getBaseHref(), nextID, (id, cssText, index, baseHref) => {
4365
+ this.app.send(AdoptedSSInsertRuleURLBased(id, cssText, index, baseHref));
4366
+ }, (sheetId, ownerId) => {
4367
+ this.app.send(AdoptedSSAddOwner(sheetId, ownerId));
4368
+ }, this.inlinerOptions?.forceFetch, this.inlinerOptions?.forcePlain, (cssText, fakeTextId) => {
4369
+ this.app.send(CreateTextNode(fakeTextId, id, 0));
4370
+ setTimeout(() => {
4371
+ this.app.send(SetCSSDataURLBased(fakeTextId, cssText, this.app.getBaseHref()));
4372
+ }, 10);
4373
+ });
4374
+ }, 0);
4375
+ return;
4376
+ }
3974
4377
  this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
3975
4378
  return;
3976
4379
  }
@@ -4050,7 +4453,8 @@ class Observer {
4050
4453
  if (isRootNode(node)) {
4051
4454
  return true;
4052
4455
  }
4053
- const parent = node.parentNode;
4456
+ // @ts-ignore SALESFORCE
4457
+ const parent = node.assignedSlot ? node.assignedSlot : node.parentNode;
4054
4458
  let parentID;
4055
4459
  // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
4056
4460
  // TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
@@ -4107,7 +4511,12 @@ class Observer {
4107
4511
  el.style.width = `${width}px`;
4108
4512
  el.style.height = `${height}px`;
4109
4513
  }
4110
- this.app.send(CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)));
4514
+ if ('rel' in el && el.rel === 'stylesheet' && this.inlineRemoteCss) {
4515
+ this.app.send(CreateElementNode(id, parentID, index, 'STYLE', false));
4516
+ }
4517
+ else {
4518
+ this.app.send(CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)));
4519
+ }
4111
4520
  }
4112
4521
  for (let i = 0; i < el.attributes.length; i++) {
4113
4522
  const attr = el.attributes[i];
@@ -4155,12 +4564,12 @@ class Observer {
4155
4564
  }
4156
4565
  commitNodes(isStart = false) {
4157
4566
  let node;
4158
- this.recents.forEach((type, id) => {
4567
+ for (const [id, type] of this.recents.entries()) {
4159
4568
  this.commitNode(id);
4160
4569
  if (type === RecentsType.New && (node = this.app.nodes.getNode(id))) {
4161
4570
  this.app.nodes.callNodeCallbacks(node, isStart);
4162
4571
  }
4163
- });
4572
+ }
4164
4573
  this.clear();
4165
4574
  }
4166
4575
  // ISSSUE (nodeToBinde should be the same as node in all cases. Look at the comment about 0-node at the beginning of the file.)
@@ -4282,14 +4691,67 @@ class IFrameOffsets {
4282
4691
  }
4283
4692
  }
4284
4693
 
4694
+ var InlineCssMode;
4695
+ (function (InlineCssMode) {
4696
+ /** default behavior -- will parse and cache the css file on backend */
4697
+ InlineCssMode[InlineCssMode["Disabled"] = 0] = "Disabled";
4698
+ /** will attempt to record the linked css file as AdoptedStyleSheet object */
4699
+ InlineCssMode[InlineCssMode["Inline"] = 1] = "Inline";
4700
+ /** will fetch the file, then simulated AdoptedStyleSheets behavior programmaticaly for the replay */
4701
+ InlineCssMode[InlineCssMode["InlineFetched"] = 2] = "InlineFetched";
4702
+ /** will fetch the file, then save it as plain css inside <style> node */
4703
+ InlineCssMode[InlineCssMode["PlainFetched"] = 3] = "PlainFetched";
4704
+ })(InlineCssMode || (InlineCssMode = {}));
4705
+ function getInlineOptions(mode) {
4706
+ switch (mode) {
4707
+ case InlineCssMode.Inline:
4708
+ return {
4709
+ inlineRemoteCss: true,
4710
+ inlinerOptions: {
4711
+ forceFetch: false,
4712
+ forcePlain: false,
4713
+ },
4714
+ };
4715
+ case InlineCssMode.InlineFetched:
4716
+ return {
4717
+ inlineRemoteCss: true,
4718
+ inlinerOptions: {
4719
+ forceFetch: true,
4720
+ forcePlain: false,
4721
+ },
4722
+ };
4723
+ case InlineCssMode.PlainFetched:
4724
+ return {
4725
+ inlineRemoteCss: true,
4726
+ inlinerOptions: {
4727
+ forceFetch: true,
4728
+ forcePlain: true,
4729
+ },
4730
+ };
4731
+ case InlineCssMode.Disabled:
4732
+ default:
4733
+ return {
4734
+ inlineRemoteCss: false,
4735
+ inlinerOptions: {
4736
+ forceFetch: false,
4737
+ forcePlain: false,
4738
+ },
4739
+ };
4740
+ }
4741
+ }
4285
4742
  const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
4286
4743
  class TopObserver extends Observer {
4287
4744
  constructor(params) {
4288
4745
  const opts = Object.assign({
4289
4746
  captureIFrames: true,
4290
4747
  disableSprites: false,
4748
+ inlineCss: 0,
4291
4749
  }, params.options);
4292
- super(params.app, true, opts);
4750
+ const observerOptions = {
4751
+ disableSprites: opts.disableSprites,
4752
+ ...getInlineOptions(opts.inlineCss)
4753
+ };
4754
+ super(params.app, true, observerOptions);
4293
4755
  this.iframeOffsets = new IFrameOffsets();
4294
4756
  this.contextCallbacks = [];
4295
4757
  // Attached once per Tracker instance
@@ -4338,7 +4800,7 @@ class TopObserver extends Observer {
4338
4800
  this.app.debug.info('doc already observed for', id);
4339
4801
  return;
4340
4802
  }
4341
- const observer = new IFrameObserver(this.app);
4803
+ const observer = new IFrameObserver(this.app, false, {});
4342
4804
  this.iframeObservers.set(iframe, observer);
4343
4805
  this.docObservers.set(currentDoc, observer);
4344
4806
  this.iframeObserversArr.push(observer);
@@ -4740,7 +5202,7 @@ class App {
4740
5202
  this.stopCallbacks = [];
4741
5203
  this.commitCallbacks = [];
4742
5204
  this.activityState = ActivityState.NotActive;
4743
- this.version = '16.1.4'; // TODO: version compatability check inside each plugin.
5205
+ this.version = '16.2.1'; // TODO: version compatability check inside each plugin.
4744
5206
  this.socketMode = false;
4745
5207
  this.compressionThreshold = 24 * 1000;
4746
5208
  this.bc = null;
@@ -4973,6 +5435,30 @@ class App {
4973
5435
  }
4974
5436
  };
4975
5437
  this.startTimeout = null;
5438
+ this.send = (message, urgent = false) => {
5439
+ if (this.activityState === ActivityState.NotActive) {
5440
+ return;
5441
+ }
5442
+ // ====================================================
5443
+ if (this.activityState === ActivityState.ColdStart) {
5444
+ this.bufferedMessages1.push(message);
5445
+ if (!this.singleBuffer) {
5446
+ this.bufferedMessages2.push(message);
5447
+ }
5448
+ this.conditionsManager?.processMessage(message);
5449
+ }
5450
+ else {
5451
+ this.messages.push(message);
5452
+ }
5453
+ // TODO: commit on start if there were `urgent` sends;
5454
+ // Clarify where urgent can be used for;
5455
+ // Clarify workflow for each type of message in case it was sent before start
5456
+ // (like Fetch before start; maybe add an option "preCapture: boolean" or sth alike)
5457
+ // Careful: `this.delay` is equal to zero before start so all Timestamp-s will have to be updated on start
5458
+ if (this.activityState === ActivityState.Active && urgent) {
5459
+ this.commit();
5460
+ }
5461
+ };
4976
5462
  this.coldStartCommitN = 0;
4977
5463
  this.delay = 0;
4978
5464
  this.attachStartCallback = (cb, useSafe = false) => {
@@ -5039,18 +5525,6 @@ class App {
5039
5525
  this.onUxtCb = [];
5040
5526
  this.contextId = Math.random().toString(36).slice(2);
5041
5527
  this.projectKey = projectKey;
5042
- if (Object.keys(options).findIndex((k) => ['fixedCanvasScaling', 'disableCanvas'].includes(k)) !==
5043
- -1) {
5044
- console.warn('Openreplay: canvas options are moving to separate key "canvas" in next update. Please update your configuration.');
5045
- options = {
5046
- ...options,
5047
- canvas: {
5048
- __save_canvas_locally: options.__save_canvas_locally,
5049
- fixedCanvasScaling: options.fixedCanvasScaling,
5050
- disableCanvas: options.disableCanvas,
5051
- },
5052
- };
5053
- }
5054
5528
  this.networkOptions = options.network;
5055
5529
  const defaultOptions = {
5056
5530
  revID: '',
@@ -5065,18 +5539,14 @@ class App {
5065
5539
  __is_snippet: false,
5066
5540
  __debug_report_edp: null,
5067
5541
  __debug__: LogLevel.Silent,
5068
- __save_canvas_locally: false,
5069
5542
  localStorage: null,
5070
5543
  sessionStorage: null,
5071
- disableStringDict: false,
5072
5544
  forceSingleTab: false,
5073
5545
  assistSocketHost: '',
5074
- fixedCanvasScaling: false,
5075
- disableCanvas: false,
5076
5546
  captureIFrames: true,
5077
- disableSprites: false,
5078
- obscureTextEmails: true,
5547
+ obscureTextEmails: false,
5079
5548
  obscureTextNumbers: false,
5549
+ disableStringDict: false,
5080
5550
  crossdomain: {
5081
5551
  parentDomain: '*',
5082
5552
  },
@@ -5087,6 +5557,8 @@ class App {
5087
5557
  useAnimationFrame: false,
5088
5558
  },
5089
5559
  forceNgOff: false,
5560
+ inlineCss: 0,
5561
+ disableSprites: false,
5090
5562
  };
5091
5563
  this.options = simpleMerge(defaultOptions, options);
5092
5564
  if (!this.insideIframe &&
@@ -5325,30 +5797,6 @@ class App {
5325
5797
  }
5326
5798
  this.debug.error('OpenReplay error: ', context, e);
5327
5799
  }
5328
- send(message, urgent = false) {
5329
- if (this.activityState === ActivityState.NotActive) {
5330
- return;
5331
- }
5332
- // ====================================================
5333
- if (this.activityState === ActivityState.ColdStart) {
5334
- this.bufferedMessages1.push(message);
5335
- if (!this.singleBuffer) {
5336
- this.bufferedMessages2.push(message);
5337
- }
5338
- this.conditionsManager?.processMessage(message);
5339
- }
5340
- else {
5341
- this.messages.push(message);
5342
- }
5343
- // TODO: commit on start if there were `urgent` sends;
5344
- // Clarify where urgent can be used for;
5345
- // Clarify workflow for each type of message in case it was sent before start
5346
- // (like Fetch before start; maybe add an option "preCapture: boolean" or sth alike)
5347
- // Careful: `this.delay` is equal to zero before start so all Timestamp-s will have to be updated on start
5348
- if (this.activityState === ActivityState.Active && urgent) {
5349
- this.commit();
5350
- }
5351
- }
5352
5800
  /**
5353
5801
  * Normal workflow: add timestamp and tab data to batch, then commit it
5354
5802
  * every ~30ms
@@ -7589,138 +8037,6 @@ function Viewport (app) {
7589
8037
  app.ticker.attach(sendSetViewportSize, 5, false);
7590
8038
  }
7591
8039
 
7592
- function hasAdoptedSS(node) {
7593
- return (isRootNode(node) &&
7594
- // @ts-ignore
7595
- !!node.adoptedStyleSheets);
7596
- }
7597
- // TODO: encapsulate to be init-ed on-start and join with cssrules.ts under one folder
7598
- let _id = 0xf;
7599
- function nextID() {
7600
- return _id++;
7601
- }
7602
- const styleSheetIDMap = new Map();
7603
- function ConstructedStyleSheets (app) {
7604
- if (app === null) {
7605
- return;
7606
- }
7607
- if (!hasAdoptedSS(document)) {
7608
- return;
7609
- }
7610
- const styleSheetIDMap = new Map();
7611
- const adoptedStyleSheetsOwnings = new Map();
7612
- const sendAdoptedStyleSheetsUpdate = (root) => setTimeout(() => {
7613
- let nodeID = app.nodes.getID(root);
7614
- if (root === document) {
7615
- nodeID = 0; // main document doesn't have nodeID. ID count starts from the documentElement
7616
- }
7617
- if (nodeID === undefined) {
7618
- return;
7619
- }
7620
- let pastOwning = adoptedStyleSheetsOwnings.get(nodeID);
7621
- if (!pastOwning) {
7622
- pastOwning = [];
7623
- }
7624
- const nowOwning = [];
7625
- const styleSheets = root.adoptedStyleSheets;
7626
- if (styleSheets && Symbol.iterator in styleSheets) {
7627
- for (const s of styleSheets) {
7628
- let sheetID = styleSheetIDMap.get(s);
7629
- const init = !sheetID;
7630
- if (!sheetID) {
7631
- sheetID = nextID();
7632
- styleSheetIDMap.set(s, sheetID);
7633
- }
7634
- if (!pastOwning.includes(sheetID)) {
7635
- app.send(AdoptedSSAddOwner(sheetID, nodeID));
7636
- }
7637
- if (init) {
7638
- const rules = s.cssRules;
7639
- for (let i = 0; i < rules.length; i++) {
7640
- app.send(AdoptedSSInsertRuleURLBased(sheetID, rules[i].cssText, i, app.getBaseHref()));
7641
- }
7642
- }
7643
- nowOwning.push(sheetID);
7644
- }
7645
- }
7646
- if (Symbol.iterator in pastOwning) {
7647
- for (const sheetID of pastOwning) {
7648
- if (!nowOwning.includes(sheetID)) {
7649
- app.send(AdoptedSSRemoveOwner(sheetID, nodeID));
7650
- }
7651
- }
7652
- }
7653
- adoptedStyleSheetsOwnings.set(nodeID, nowOwning);
7654
- }, 20); // Mysterious bug:
7655
- /* On the page https://explore.fast.design/components/fast-accordion
7656
- the only rule inside the only adoptedStyleSheet of the iframe-s document
7657
- gets changed during first milliseconds after the load.
7658
- However, none of the documented methods (replace, insertRule) is triggered.
7659
- The rule is not substituted (remains the same object), however the text gets changed.
7660
- */
7661
- function patchAdoptedStyleSheets(prototype) {
7662
- const nativeAdoptedStyleSheetsDescriptor = Object.getOwnPropertyDescriptor(prototype, 'adoptedStyleSheets');
7663
- if (nativeAdoptedStyleSheetsDescriptor) {
7664
- Object.defineProperty(prototype, 'adoptedStyleSheets', {
7665
- ...nativeAdoptedStyleSheetsDescriptor,
7666
- set: function (value) {
7667
- // @ts-ignore
7668
- const retVal = nativeAdoptedStyleSheetsDescriptor.set.call(this, value);
7669
- sendAdoptedStyleSheetsUpdate(this);
7670
- return retVal;
7671
- },
7672
- });
7673
- }
7674
- }
7675
- const patchContext = (context) => {
7676
- // @ts-ignore
7677
- if (context.__openreplay_adpss_patched__) {
7678
- return;
7679
- }
7680
- else {
7681
- // @ts-ignore
7682
- context.__openreplay_adpss_patched__ = true;
7683
- }
7684
- patchAdoptedStyleSheets(context.Document.prototype);
7685
- patchAdoptedStyleSheets(context.ShadowRoot.prototype);
7686
- //@ts-ignore TODO: upgrade ts to 4.8+
7687
- const { replace, replaceSync } = context.CSSStyleSheet.prototype;
7688
- //@ts-ignore
7689
- context.CSSStyleSheet.prototype.replace = function (text) {
7690
- return replace.call(this, text).then((sheet) => {
7691
- const sheetID = styleSheetIDMap.get(this);
7692
- if (sheetID) {
7693
- app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
7694
- }
7695
- return sheet;
7696
- });
7697
- };
7698
- //@ts-ignore
7699
- context.CSSStyleSheet.prototype.replaceSync = function (text) {
7700
- const sheetID = styleSheetIDMap.get(this);
7701
- if (sheetID) {
7702
- app.send(AdoptedSSReplaceURLBased(sheetID, text, app.getBaseHref()));
7703
- }
7704
- return replaceSync.call(this, text);
7705
- };
7706
- };
7707
- patchContext(window);
7708
- app.observer.attachContextCallback(app.safe(patchContext));
7709
- app.attachStopCallback(() => {
7710
- styleSheetIDMap.clear();
7711
- adoptedStyleSheetsOwnings.clear();
7712
- });
7713
- // So far main Document is not triggered with nodeCallbacks
7714
- app.attachStartCallback(() => {
7715
- sendAdoptedStyleSheetsUpdate(document);
7716
- });
7717
- app.nodes.attachNodeCallback((node) => {
7718
- if (hasAdoptedSS(node)) {
7719
- sendAdoptedStyleSheetsUpdate(node);
7720
- }
7721
- });
7722
- }
7723
-
7724
8040
  function CSSRules (app) {
7725
8041
  if (app === null) {
7726
8042
  return;
@@ -9376,7 +9692,7 @@ class API {
9376
9692
  this.signalStartIssue = (reason, missingApi) => {
9377
9693
  const doNotTrack = this.checkDoNotTrack();
9378
9694
  console.log("Tracker couldn't start due to:", JSON.stringify({
9379
- trackerVersion: '16.1.4',
9695
+ trackerVersion: '16.2.1',
9380
9696
  projectKey: this.options.projectKey,
9381
9697
  doNotTrack,
9382
9698
  reason: missingApi.length ? `missing api: ${missingApi.join(',')}` : reason,