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