@openreplay/tracker 3.4.13 → 3.4.17-beta.0

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.
Files changed (45) hide show
  1. package/cjs/app/context.d.ts +18 -0
  2. package/cjs/app/context.js +48 -0
  3. package/cjs/app/index.d.ts +11 -6
  4. package/cjs/app/index.js +33 -22
  5. package/cjs/app/observer/iframe_observer.d.ts +4 -0
  6. package/cjs/app/observer/iframe_observer.js +22 -0
  7. package/cjs/app/observer/observer.d.ts +25 -0
  8. package/cjs/app/{observer.js → observer/observer.js} +82 -170
  9. package/cjs/app/observer/shadow_root_observer.d.ts +4 -0
  10. package/cjs/app/observer/shadow_root_observer.js +21 -0
  11. package/cjs/app/observer/top_observer.d.ts +15 -0
  12. package/cjs/app/observer/top_observer.js +84 -0
  13. package/cjs/app/sanitizer.d.ts +16 -0
  14. package/cjs/app/sanitizer.js +46 -0
  15. package/cjs/index.d.ts +1 -0
  16. package/cjs/index.js +7 -1
  17. package/cjs/modules/exception.js +8 -1
  18. package/cjs/modules/img.js +15 -1
  19. package/cjs/modules/input.d.ts +3 -1
  20. package/cjs/modules/input.js +6 -3
  21. package/cjs/modules/mouse.js +1 -1
  22. package/lib/app/context.d.ts +18 -0
  23. package/lib/app/context.js +43 -0
  24. package/lib/app/index.d.ts +11 -6
  25. package/lib/app/index.js +33 -22
  26. package/lib/app/observer/iframe_observer.d.ts +4 -0
  27. package/lib/app/observer/iframe_observer.js +19 -0
  28. package/lib/app/observer/observer.d.ts +25 -0
  29. package/lib/app/{observer.js → observer/observer.js} +82 -170
  30. package/lib/app/observer/shadow_root_observer.d.ts +4 -0
  31. package/lib/app/observer/shadow_root_observer.js +18 -0
  32. package/lib/app/observer/top_observer.d.ts +15 -0
  33. package/lib/app/observer/top_observer.js +81 -0
  34. package/lib/app/sanitizer.d.ts +16 -0
  35. package/lib/app/sanitizer.js +44 -1
  36. package/lib/index.d.ts +1 -0
  37. package/lib/index.js +7 -1
  38. package/lib/modules/exception.js +8 -1
  39. package/lib/modules/img.js +16 -2
  40. package/lib/modules/input.d.ts +3 -1
  41. package/lib/modules/input.js +6 -3
  42. package/lib/modules/mouse.js +1 -1
  43. package/package.json +1 -1
  44. package/cjs/app/observer.d.ts +0 -47
  45. package/lib/app/observer.d.ts +0 -47
@@ -1,42 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const utils_js_1 = require("../utils.js");
4
- const index_js_1 = require("../messages/index.js");
3
+ const index_js_1 = require("../../messages/index.js");
4
+ const context_js_1 = require("../context.js");
5
5
  function isSVGElement(node) {
6
6
  return node.namespaceURI === 'http://www.w3.org/2000/svg';
7
7
  }
8
+ function isIgnored(node) {
9
+ if ((0, context_js_1.isInstance)(node, Text)) {
10
+ return false;
11
+ }
12
+ if (!(0, context_js_1.isInstance)(node, Element)) {
13
+ return true;
14
+ }
15
+ const tag = node.tagName.toUpperCase();
16
+ if (tag === 'LINK') {
17
+ const rel = node.getAttribute('rel');
18
+ const as = node.getAttribute('as');
19
+ return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
20
+ }
21
+ return (tag === 'SCRIPT' ||
22
+ tag === 'NOSCRIPT' ||
23
+ tag === 'META' ||
24
+ tag === 'TITLE' ||
25
+ tag === 'BASE');
26
+ }
27
+ function isRootNode(node) {
28
+ return (0, context_js_1.isInstance)(node, Document) || (0, context_js_1.isInstance)(node, ShadowRoot);
29
+ }
30
+ function isObservable(node) {
31
+ if (isRootNode(node)) {
32
+ return true;
33
+ }
34
+ return !isIgnored(node);
35
+ }
8
36
  class Observer {
9
- constructor(app, options, context = window) {
37
+ constructor(app, context = window) {
10
38
  this.app = app;
11
- this.options = options;
12
39
  this.context = context;
13
- this.iframeObservers = [];
40
+ this.commited = [];
41
+ this.recents = [];
42
+ this.myNodes = [];
43
+ this.indexes = [];
44
+ this.attributesList = [];
45
+ this.textSet = new Set();
46
+ this.inUpperContext = context.parent === context; //TODO: get rid of context here
14
47
  this.observer = new MutationObserver(this.app.safe((mutations) => {
15
- var _a;
16
48
  for (const mutation of mutations) {
17
49
  const target = mutation.target;
18
50
  const type = mutation.type;
19
- // Special case
20
- // Document 'childList' might happen in case of iframe.
21
- // TODO: generalize as much as possible
22
- if (this.isInstance(target, Document)
23
- && type === 'childList'
24
- //&& new Array(mutation.addedNodes).some(node => this.isInstance(node, HTMLHtmlElement))
25
- ) {
26
- const parentFrame = (_a = target.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement;
27
- if (!parentFrame) {
28
- continue;
29
- }
30
- this.bindTree(target.documentElement);
31
- const frameID = this.app.nodes.getID(parentFrame);
32
- const docID = this.app.nodes.getID(target.documentElement);
33
- if (frameID === undefined || docID === undefined) {
34
- continue;
35
- }
36
- this.app.send((0, index_js_1.CreateIFrameDocument)(frameID, docID));
37
- continue;
38
- }
39
- if (this.isIgnored(target) || !context.document.contains(target)) {
51
+ if (!isObservable(target) || !(0, context_js_1.inDocument)(target)) {
40
52
  continue;
41
53
  }
42
54
  if (type === 'childList') {
@@ -52,7 +64,7 @@ class Observer {
52
64
  if (id === undefined) {
53
65
  continue;
54
66
  }
55
- if (id >= this.recents.length) {
67
+ if (id >= this.recents.length) { // TODO: something more convinient
56
68
  this.recents[id] = undefined;
57
69
  }
58
70
  if (type === 'attributes') {
@@ -74,12 +86,6 @@ class Observer {
74
86
  }
75
87
  this.commitNodes();
76
88
  }));
77
- this.commited = [];
78
- this.recents = [];
79
- this.indexes = [0];
80
- this.attributesList = [];
81
- this.textSet = new Set();
82
- this.textMasked = new Set();
83
89
  }
84
90
  clear() {
85
91
  this.commited.length = 0;
@@ -87,40 +93,6 @@ class Observer {
87
93
  this.indexes.length = 1;
88
94
  this.attributesList.length = 0;
89
95
  this.textSet.clear();
90
- this.textMasked.clear();
91
- }
92
- // TODO: we need a type expert here so we won't have to ignore the lines
93
- isInstance(node, constr) {
94
- let context = this.context;
95
- while (context.parent && context.parent !== context) {
96
- // @ts-ignore
97
- if (node instanceof context[constr.name]) {
98
- return true;
99
- }
100
- // @ts-ignore
101
- context = context.parent;
102
- }
103
- // @ts-ignore
104
- return node instanceof context[constr.name];
105
- }
106
- isIgnored(node) {
107
- if (this.isInstance(node, Text)) {
108
- return false;
109
- }
110
- if (!this.isInstance(node, Element)) {
111
- return true;
112
- }
113
- const tag = node.tagName.toUpperCase();
114
- if (tag === 'LINK') {
115
- const rel = node.getAttribute('rel');
116
- const as = node.getAttribute('as');
117
- return !((rel === null || rel === void 0 ? void 0 : rel.includes('stylesheet')) || as === "style" || as === "font");
118
- }
119
- return (tag === 'SCRIPT' ||
120
- tag === 'NOSCRIPT' ||
121
- tag === 'META' ||
122
- tag === 'TITLE' ||
123
- tag === 'BASE');
124
96
  }
125
97
  sendNodeAttribute(id, node, name, value) {
126
98
  if (isSVGElement(node)) {
@@ -150,7 +122,7 @@ class Observer {
150
122
  return;
151
123
  }
152
124
  if (name === 'value' &&
153
- this.isInstance(node, HTMLInputElement) &&
125
+ (0, context_js_1.isInstance)(node, HTMLInputElement) &&
154
126
  node.type !== 'button' &&
155
127
  node.type !== 'reset' &&
156
128
  node.type !== 'submit') {
@@ -160,7 +132,7 @@ class Observer {
160
132
  this.app.send(new index_js_1.RemoveNodeAttribute(id, name));
161
133
  return;
162
134
  }
163
- if (name === 'style' || name === 'href' && this.isInstance(node, HTMLLinkElement)) {
135
+ if (name === 'style' || name === 'href' && (0, context_js_1.isInstance)(node, HTMLLinkElement)) {
164
136
  this.app.send(new index_js_1.SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
165
137
  return;
166
138
  }
@@ -169,47 +141,27 @@ class Observer {
169
141
  }
170
142
  this.app.send(new index_js_1.SetNodeAttribute(id, name, value));
171
143
  }
172
- /* TODO: abstract sanitation */
173
- getInnerTextSecure(el) {
174
- const id = this.app.nodes.getID(el);
175
- if (!id) {
176
- return '';
177
- }
178
- return this.checkObscure(id, el.innerText);
179
- }
180
- checkObscure(id, data) {
181
- if (this.textMasked.has(id)) {
182
- return data.replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
183
- }
184
- if (this.options.obscureTextNumbers) {
185
- data = data.replace(/\d/g, '0');
186
- }
187
- if (this.options.obscureTextEmails) {
188
- data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => (0, utils_js_1.stars)(f[1]) + '@' + (0, utils_js_1.stars)(f[2]) + '.' + (0, utils_js_1.stars)(f[3]));
189
- }
190
- return data;
191
- }
192
144
  sendNodeData(id, parentElement, data) {
193
- if (this.isInstance(parentElement, HTMLStyleElement) || this.isInstance(parentElement, SVGStyleElement)) {
145
+ if ((0, context_js_1.isInstance)(parentElement, HTMLStyleElement) || (0, context_js_1.isInstance)(parentElement, SVGStyleElement)) {
194
146
  this.app.send(new index_js_1.SetCSSDataURLBased(id, data, this.app.getBaseHref()));
195
147
  return;
196
148
  }
197
- data = this.checkObscure(id, data);
149
+ data = this.app.sanitizer.sanitize(id, data);
198
150
  this.app.send(new index_js_1.SetNodeData(id, data));
199
151
  }
200
- /* end TODO: abstract sanitation */
201
152
  bindNode(node) {
202
153
  const r = this.app.nodes.registerNode(node);
203
154
  const id = r[0];
204
155
  this.recents[id] = r[1] || this.recents[id] || false;
156
+ this.myNodes[id] = true;
205
157
  }
206
158
  bindTree(node) {
207
- if (this.isIgnored(node)) {
159
+ if (!isObservable(node)) {
208
160
  return;
209
161
  }
210
162
  this.bindNode(node);
211
163
  const walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_TEXT, {
212
- acceptNode: (node) => this.isIgnored(node) || this.app.nodes.getID(node) !== undefined
164
+ acceptNode: (node) => isIgnored(node) || this.app.nodes.getID(node) !== undefined
213
165
  ? NodeFilter.FILTER_REJECT
214
166
  : NodeFilter.FILTER_ACCEPT,
215
167
  },
@@ -226,12 +178,15 @@ class Observer {
226
178
  }
227
179
  }
228
180
  _commitNode(id, node) {
181
+ if (isRootNode(node)) {
182
+ return true;
183
+ }
229
184
  const parent = node.parentNode;
230
185
  let parentID;
231
- if (this.isInstance(node, HTMLHtmlElement)) {
232
- this.indexes[id] = 0;
233
- }
234
- else {
186
+ // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
187
+ // TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
188
+ // TODO: Clean the logic (though now it workd fine)
189
+ if (!(0, context_js_1.isInstance)(node, HTMLHtmlElement) || !this.inUpperContext) {
235
190
  if (parent === null) {
236
191
  this.unbindNode(node);
237
192
  return false;
@@ -245,23 +200,20 @@ class Observer {
245
200
  this.unbindNode(node);
246
201
  return false;
247
202
  }
248
- if (this.textMasked.has(parentID) ||
249
- (this.isInstance(node, Element) && (0, utils_js_1.hasOpenreplayAttribute)(node, 'masked'))) {
250
- this.textMasked.add(id);
251
- }
252
- let sibling = node.previousSibling;
253
- while (sibling !== null) {
254
- const siblingID = this.app.nodes.getID(sibling);
255
- if (siblingID !== undefined) {
256
- this.commitNode(siblingID);
257
- this.indexes[id] = this.indexes[siblingID] + 1;
258
- break;
259
- }
260
- sibling = sibling.previousSibling;
261
- }
262
- if (sibling === null) {
263
- this.indexes[id] = 0;
203
+ this.app.sanitizer.handleNode(id, parentID, node);
204
+ }
205
+ let sibling = node.previousSibling;
206
+ while (sibling !== null) {
207
+ const siblingID = this.app.nodes.getID(sibling);
208
+ if (siblingID !== undefined) {
209
+ this.commitNode(siblingID);
210
+ this.indexes[id] = this.indexes[siblingID] + 1;
211
+ break;
264
212
  }
213
+ sibling = sibling.previousSibling;
214
+ }
215
+ if (sibling === null) {
216
+ this.indexes[id] = 0; //
265
217
  }
266
218
  const isNew = this.recents[id];
267
219
  const index = this.indexes[id];
@@ -269,7 +221,7 @@ class Observer {
269
221
  throw 'commitNode: missing node index';
270
222
  }
271
223
  if (isNew === true) {
272
- if (this.isInstance(node, Element)) {
224
+ if ((0, context_js_1.isInstance)(node, Element)) {
273
225
  if (parentID !== undefined) {
274
226
  this.app.send(new index_js_1.CreateElementNode(id, parentID, index, node.tagName, isSVGElement(node)));
275
227
  }
@@ -277,12 +229,8 @@ class Observer {
277
229
  const attr = node.attributes[i];
278
230
  this.sendNodeAttribute(id, node, attr.nodeName, attr.value);
279
231
  }
280
- if (this.isInstance(node, HTMLIFrameElement) &&
281
- (this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
282
- this.handleIframe(node);
283
- }
284
232
  }
285
- else if (this.isInstance(node, Text)) {
233
+ else if ((0, context_js_1.isInstance)(node, Text)) {
286
234
  // for text node id != 0, hence parentID !== undefined and parent is Element
287
235
  this.app.send(new index_js_1.CreateTextNode(id, parentID, index));
288
236
  this.sendNodeData(id, parent, node.data);
@@ -294,7 +242,7 @@ class Observer {
294
242
  }
295
243
  const attr = this.attributesList[id];
296
244
  if (attr !== undefined) {
297
- if (!this.isInstance(node, Element)) {
245
+ if (!(0, context_js_1.isInstance)(node, Element)) {
298
246
  throw 'commitNode: node is not an element';
299
247
  }
300
248
  for (const name of attr) {
@@ -302,7 +250,7 @@ class Observer {
302
250
  }
303
251
  }
304
252
  if (this.textSet.has(id)) {
305
- if (!this.isInstance(node, Text)) {
253
+ if (!(0, context_js_1.isInstance)(node, Text)) {
306
254
  throw 'commitNode: node is not a text';
307
255
  }
308
256
  // for text node id != 0, hence parent is Element
@@ -324,6 +272,11 @@ class Observer {
324
272
  commitNodes() {
325
273
  let node;
326
274
  for (let id = 0; id < this.recents.length; id++) {
275
+ // TODO: make things/logic nice here.
276
+ // commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
277
+ if (!this.myNodes[id]) {
278
+ continue;
279
+ }
327
280
  this.commitNode(id);
328
281
  if (this.recents[id] === true && (node = this.app.nodes.getNode(id))) {
329
282
  this.app.nodes.callNodeCallbacks(node);
@@ -331,49 +284,9 @@ class Observer {
331
284
  }
332
285
  this.clear();
333
286
  }
334
- handleIframe(iframe) {
335
- let context = null;
336
- const handle = this.app.safe(() => {
337
- const id = this.app.nodes.getID(iframe);
338
- if (id === undefined) {
339
- return;
340
- }
341
- if (iframe.contentWindow === context) {
342
- return;
343
- }
344
- context = iframe.contentWindow;
345
- if (!context) {
346
- return;
347
- }
348
- const observer = new Observer(this.app, this.options, context);
349
- this.iframeObservers.push(observer);
350
- observer.observeIframe(id, context);
351
- });
352
- this.app.attachEventListener(iframe, "load", handle);
353
- handle();
354
- }
355
- // TODO: abstract common functionality, separate FrameObserver
356
- observeIframe(id, context) {
357
- const doc = context.document;
358
- this.observer.observe(doc, {
359
- childList: true,
360
- attributes: true,
361
- characterData: true,
362
- subtree: true,
363
- attributeOldValue: false,
364
- characterDataOldValue: false,
365
- });
366
- this.bindTree(doc.documentElement);
367
- const docID = this.app.nodes.getID(doc.documentElement);
368
- if (docID === undefined) {
369
- console.log("Wrong");
370
- return;
371
- }
372
- this.app.send((0, index_js_1.CreateIFrameDocument)(id, docID));
373
- this.commitNodes();
374
- }
375
- observe() {
376
- this.observer.observe(this.context.document, {
287
+ // ISSSUE
288
+ observeRoot(node, beforeCommit, nodeToBind = node) {
289
+ this.observer.observe(node, {
377
290
  childList: true,
378
291
  attributes: true,
379
292
  characterData: true,
@@ -381,15 +294,14 @@ class Observer {
381
294
  attributeOldValue: false,
382
295
  characterDataOldValue: false,
383
296
  });
384
- this.app.send(new index_js_1.CreateDocument());
385
- this.bindTree(this.context.document.documentElement);
297
+ this.bindTree(nodeToBind);
298
+ beforeCommit(this.app.nodes.getID(node));
386
299
  this.commitNodes();
387
300
  }
388
301
  disconnect() {
389
- this.iframeObservers.forEach(o => o.disconnect());
390
- this.iframeObservers = [];
391
302
  this.observer.disconnect();
392
303
  this.clear();
304
+ this.myNodes.length = 0;
393
305
  }
394
306
  }
395
307
  exports.default = Observer;
@@ -0,0 +1,4 @@
1
+ import Observer from "./observer.js";
2
+ export default class ShadowRootObserver extends Observer {
3
+ observe(el: Element): void;
4
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const observer_js_1 = require("./observer.js");
4
+ const index_js_1 = require("../../messages/index.js");
5
+ class ShadowRootObserver extends observer_js_1.default {
6
+ observe(el) {
7
+ const shRoot = el.shadowRoot;
8
+ const hostID = this.app.nodes.getID(el);
9
+ if (!shRoot || hostID === undefined) {
10
+ return;
11
+ } // log
12
+ this.observeRoot(shRoot, (rootID) => {
13
+ if (rootID === undefined) {
14
+ console.log("OpenReplay: Shadow Root was not bound");
15
+ return;
16
+ }
17
+ this.app.send((0, index_js_1.CreateIFrameDocument)(hostID, rootID));
18
+ });
19
+ }
20
+ }
21
+ exports.default = ShadowRootObserver;
@@ -0,0 +1,15 @@
1
+ import Observer from "./observer.js";
2
+ import App from "../index.js";
3
+ export interface Options {
4
+ captureIFrames: boolean;
5
+ }
6
+ export default class TopObserver extends Observer {
7
+ private readonly options;
8
+ constructor(app: App, options: Partial<Options>);
9
+ private iframeObservers;
10
+ private handleIframe;
11
+ private shadowRootObservers;
12
+ private handleShadowRoot;
13
+ observe(): void;
14
+ disconnect(): void;
15
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const observer_js_1 = require("./observer.js");
4
+ const context_js_1 = require("../context.js");
5
+ const iframe_observer_js_1 = require("./iframe_observer.js");
6
+ const shadow_root_observer_js_1 = require("./shadow_root_observer.js");
7
+ const index_js_1 = require("../../messages/index.js");
8
+ const attachShadowNativeFn = Element.prototype.attachShadow;
9
+ class TopObserver extends observer_js_1.default {
10
+ constructor(app, options) {
11
+ super(app);
12
+ this.iframeObservers = [];
13
+ this.shadowRootObservers = [];
14
+ this.options = Object.assign({
15
+ captureIFrames: false
16
+ }, options);
17
+ // IFrames
18
+ this.app.nodes.attachNodeCallback(node => {
19
+ if ((0, context_js_1.isInstance)(node, HTMLIFrameElement) &&
20
+ (this.options.captureIFrames || node.getAttribute("data-openreplay-capture"))) {
21
+ this.handleIframe(node);
22
+ }
23
+ });
24
+ // ShadowDOM
25
+ this.app.nodes.attachNodeCallback(node => {
26
+ if ((0, context_js_1.isInstance)(node, Element) && node.shadowRoot !== null) {
27
+ this.handleShadowRoot(node.shadowRoot);
28
+ }
29
+ });
30
+ }
31
+ handleIframe(iframe) {
32
+ let context = null;
33
+ const handle = this.app.safe(() => {
34
+ const id = this.app.nodes.getID(iframe);
35
+ if (id === undefined) {
36
+ return;
37
+ } //log
38
+ if (iframe.contentWindow === context) {
39
+ return;
40
+ } //Does this happen frequently?
41
+ context = iframe.contentWindow;
42
+ if (!context) {
43
+ return;
44
+ }
45
+ const observer = new iframe_observer_js_1.default(this.app, context);
46
+ this.iframeObservers.push(observer);
47
+ observer.observe(iframe);
48
+ });
49
+ this.app.attachEventListener(iframe, "load", handle);
50
+ handle();
51
+ }
52
+ handleShadowRoot(shRoot) {
53
+ const observer = new shadow_root_observer_js_1.default(this.app, this.context);
54
+ this.shadowRootObservers.push(observer);
55
+ observer.observe(shRoot.host);
56
+ }
57
+ observe() {
58
+ // Protection from several subsequent calls?
59
+ const observer = this;
60
+ Element.prototype.attachShadow = function () {
61
+ const shadow = attachShadowNativeFn.apply(this, arguments);
62
+ observer.handleShadowRoot(shadow);
63
+ return shadow;
64
+ };
65
+ // Can observe documentElement (<html>) here, because it is not supposed to be changing.
66
+ // However, it is possible in some exotic cases and may cause an ignorance of the newly created <html>
67
+ // In this case context.document have to be observed, but this will cause
68
+ // the change in the re-player behaviour caused by CreateDocument message:
69
+ // the 0-node ("fRoot") will become #document rather than documentElement as it is now.
70
+ // Alternatively - observe(#document) then bindNode(documentElement)
71
+ this.observeRoot(this.context.document, () => {
72
+ this.app.send(new index_js_1.CreateDocument());
73
+ }, this.context.document.documentElement);
74
+ }
75
+ disconnect() {
76
+ Element.prototype.attachShadow = attachShadowNativeFn;
77
+ this.iframeObservers.forEach(o => o.disconnect());
78
+ this.iframeObservers = [];
79
+ this.shadowRootObservers.forEach(o => o.disconnect());
80
+ this.shadowRootObservers = [];
81
+ super.disconnect();
82
+ }
83
+ }
84
+ exports.default = TopObserver;
@@ -0,0 +1,16 @@
1
+ import App from "./index.js";
2
+ export interface Options {
3
+ obscureTextEmails: boolean;
4
+ obscureTextNumbers: boolean;
5
+ }
6
+ export default class Sanitizer {
7
+ private readonly app;
8
+ private readonly masked;
9
+ private readonly options;
10
+ constructor(app: App, options: Partial<Options>);
11
+ handleNode(id: number, parentID: number, node: Node): void;
12
+ sanitize(id: number, data: string): string;
13
+ isMasked(id: number): boolean;
14
+ getInnerTextSecure(el: HTMLElement): string;
15
+ clear(): void;
16
+ }
@@ -1 +1,47 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_js_1 = require("../utils.js");
4
+ const context_js_1 = require("./context.js");
5
+ class Sanitizer {
6
+ constructor(app, options) {
7
+ this.app = app;
8
+ this.masked = new Set();
9
+ this.options = Object.assign({
10
+ obscureTextEmails: true,
11
+ obscureTextNumbers: false,
12
+ }, options);
13
+ }
14
+ handleNode(id, parentID, node) {
15
+ if (this.masked.has(parentID) ||
16
+ ((0, context_js_1.isInstance)(node, Element) && (0, utils_js_1.hasOpenreplayAttribute)(node, 'masked'))) {
17
+ this.masked.add(id);
18
+ }
19
+ }
20
+ sanitize(id, data) {
21
+ if (this.masked.has(id)) {
22
+ // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
23
+ return data.trim().replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
24
+ }
25
+ if (this.options.obscureTextNumbers) {
26
+ data = data.replace(/\d/g, '0');
27
+ }
28
+ if (this.options.obscureTextEmails) {
29
+ data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => (0, utils_js_1.stars)(f[1]) + '@' + (0, utils_js_1.stars)(f[2]) + '.' + (0, utils_js_1.stars)(f[3]));
30
+ }
31
+ return data;
32
+ }
33
+ isMasked(id) {
34
+ return this.masked.has(id);
35
+ }
36
+ getInnerTextSecure(el) {
37
+ const id = this.app.nodes.getID(el);
38
+ if (!id) {
39
+ return '';
40
+ }
41
+ return this.sanitize(id, el.innerText);
42
+ }
43
+ clear() {
44
+ this.masked.clear();
45
+ }
46
+ }
47
+ exports.default = Sanitizer;
package/cjs/index.d.ts CHANGED
@@ -37,4 +37,5 @@ export default class API {
37
37
  event(key: string, payload: any, issue?: boolean): void;
38
38
  issue(key: string, payload: any): void;
39
39
  handleError: (e: Error | ErrorEvent | PromiseRejectionEvent) => void;
40
+ resetNextPageSession(flag: boolean): void;
40
41
  }
package/cjs/index.js CHANGED
@@ -115,7 +115,7 @@ class API {
115
115
  // no-cors issue only with text/plain or not-set Content-Type
116
116
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
117
117
  req.send(JSON.stringify({
118
- trackerVersion: '3.4.13',
118
+ trackerVersion: '3.4.17-beta.0',
119
119
  projectKey: options.projectKey,
120
120
  doNotTrack,
121
121
  // TODO: add precise reason (an exact API missing)
@@ -223,5 +223,11 @@ class API {
223
223
  this.app.send(new index_js_3.CustomIssue(key, payload));
224
224
  }
225
225
  }
226
+ resetNextPageSession(flag) {
227
+ if (typeof flag !== 'boolean' || !this.app) {
228
+ return;
229
+ }
230
+ this.app.resetNextPageSession(flag);
231
+ }
226
232
  }
227
233
  exports.default = API;
@@ -41,7 +41,14 @@ function getExceptionMessageFromEvent(e) {
41
41
  return getExceptionMessage(e.reason, []);
42
42
  }
43
43
  else {
44
- return new index_js_1.JSException('Unhandled Promise Rejection', String(e.reason), '[]');
44
+ let message;
45
+ try {
46
+ message = JSON.stringify(e.reason);
47
+ }
48
+ catch (_) {
49
+ message = String(e.reason);
50
+ }
51
+ return new index_js_1.JSException('Unhandled Promise Rejection', message, '[]');
45
52
  }
46
53
  }
47
54
  return null;
@@ -2,7 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const utils_js_1 = require("../utils.js");
4
4
  const index_js_1 = require("../messages/index.js");
5
+ const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
5
6
  function default_1(app) {
7
+ function sendPlaceholder(id, node) {
8
+ app.send(new index_js_1.SetNodeAttribute(id, "src", PLACEHOLDER_SRC));
9
+ const { width, height } = node.getBoundingClientRect();
10
+ if (!node.hasAttribute("width")) {
11
+ app.send(new index_js_1.SetNodeAttribute(id, "width", String(width)));
12
+ }
13
+ if (!node.hasAttribute("height")) {
14
+ app.send(new index_js_1.SetNodeAttribute(id, "height", String(height)));
15
+ }
16
+ }
6
17
  const sendImgSrc = app.safe(function () {
7
18
  const id = app.nodes.getID(this);
8
19
  if (id === undefined) {
@@ -17,7 +28,10 @@ function default_1(app) {
17
28
  app.send(new index_js_1.ResourceTiming((0, utils_js_1.timestamp)(), 0, 0, 0, 0, 0, src, 'img'));
18
29
  }
19
30
  }
20
- else if (src.length < 1e5) {
31
+ else if (src.length >= 1e5 || app.sanitizer.isMasked(id)) {
32
+ sendPlaceholder(id, this);
33
+ }
34
+ else {
21
35
  app.send(new index_js_1.SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
22
36
  }
23
37
  });
@@ -1,5 +1,6 @@
1
1
  import App from "../app/index.js";
2
- export declare function getInputLabel(node: HTMLInputElement): string;
2
+ declare type TextEditableElement = HTMLInputElement | HTMLTextAreaElement;
3
+ export declare function getInputLabel(node: TextEditableElement): string;
3
4
  export declare const enum InputMode {
4
5
  Plain = 0,
5
6
  Obscured = 1,
@@ -11,3 +12,4 @@ export interface Options {
11
12
  defaultInputMode: InputMode;
12
13
  }
13
14
  export default function (app: App, opts: Partial<Options>): void;
15
+ export {};