@openreplay/tracker 3.4.14 → 3.4.17-beta.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.
@@ -1,15 +1,18 @@
1
- import Observer, { isInstance } from "./observer.js";
1
+ import Observer from "./observer.js";
2
+ import { isInstance } from "../context.js";
2
3
  import IFrameObserver from "./iframe_observer.js";
3
4
  import ShadowRootObserver from "./shadow_root_observer.js";
4
5
  import { CreateDocument } from "../../messages/index.js";
5
- const attachShadowNativeFn = Element.prototype.attachShadow;
6
+ import { IN_BROWSER } from '../../utils.js';
7
+ const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
6
8
  export default class TopObserver extends Observer {
7
9
  constructor(app, options) {
8
- super(app, Object.assign({
9
- captureIFrames: false
10
- }, options));
10
+ super(app);
11
11
  this.iframeObservers = [];
12
12
  this.shadowRootObservers = [];
13
+ this.options = Object.assign({
14
+ captureIFrames: false
15
+ }, options);
13
16
  // IFrames
14
17
  this.app.nodes.attachNodeCallback(node => {
15
18
  if (isInstance(node, HTMLIFrameElement) &&
@@ -38,11 +41,7 @@ export default class TopObserver extends Observer {
38
41
  if (!context) {
39
42
  return;
40
43
  }
41
- const observer = new IFrameObserver(this.app, this.options, context);
42
- // @ts-ignore
43
- observer.commited = this.commited;
44
- // @ts-ignore
45
- //observers.recents = this.recents
44
+ const observer = new IFrameObserver(this.app, context);
46
45
  this.iframeObservers.push(observer);
47
46
  observer.observe(iframe);
48
47
  });
@@ -50,7 +49,7 @@ export default class TopObserver extends Observer {
50
49
  handle();
51
50
  }
52
51
  handleShadowRoot(shRoot) {
53
- const observer = new ShadowRootObserver(this.app, this.options, this.context);
52
+ const observer = new ShadowRootObserver(this.app, this.context);
54
53
  this.shadowRootObservers.push(observer);
55
54
  observer.observe(shRoot.host);
56
55
  }
@@ -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,44 @@
1
- "use strict";
1
+ import { stars, hasOpenreplayAttribute } from "../utils.js";
2
+ import { isInstance } from "./context.js";
3
+ export default class Sanitizer {
4
+ constructor(app, options) {
5
+ this.app = app;
6
+ this.masked = new Set();
7
+ this.options = Object.assign({
8
+ obscureTextEmails: true,
9
+ obscureTextNumbers: false,
10
+ }, options);
11
+ }
12
+ handleNode(id, parentID, node) {
13
+ if (this.masked.has(parentID) ||
14
+ (isInstance(node, Element) && hasOpenreplayAttribute(node, 'masked'))) {
15
+ this.masked.add(id);
16
+ }
17
+ }
18
+ sanitize(id, data) {
19
+ if (this.masked.has(id)) {
20
+ // TODO: is it the best place to put trim() ? Might trimmed spaces be considered in layout in certain cases?
21
+ return data.trim().replace(/[^\f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]/g, '█');
22
+ }
23
+ if (this.options.obscureTextNumbers) {
24
+ data = data.replace(/\d/g, '0');
25
+ }
26
+ if (this.options.obscureTextEmails) {
27
+ data = data.replace(/([^\s]+)@([^\s]+)\.([^\s]+)/g, (...f) => stars(f[1]) + '@' + stars(f[2]) + '.' + stars(f[3]));
28
+ }
29
+ return data;
30
+ }
31
+ isMasked(id) {
32
+ return this.masked.has(id);
33
+ }
34
+ getInnerTextSecure(el) {
35
+ const id = this.app.nodes.getID(el);
36
+ if (!id) {
37
+ return '';
38
+ }
39
+ return this.sanitize(id, el.innerText);
40
+ }
41
+ clear() {
42
+ this.masked.clear();
43
+ }
44
+ }
package/lib/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/lib/index.js CHANGED
@@ -111,7 +111,7 @@ export default class API {
111
111
  // no-cors issue only with text/plain or not-set Content-Type
112
112
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
113
113
  req.send(JSON.stringify({
114
- trackerVersion: '3.4.14',
114
+ trackerVersion: '3.4.17-beta.1',
115
115
  projectKey: options.projectKey,
116
116
  doNotTrack,
117
117
  // TODO: add precise reason (an exact API missing)
@@ -219,4 +219,10 @@ export default class API {
219
219
  this.app.send(new CustomIssue(key, payload));
220
220
  }
221
221
  }
222
+ resetNextPageSession(flag) {
223
+ if (typeof flag !== 'boolean' || !this.app) {
224
+ return;
225
+ }
226
+ this.app.resetNextPageSession(flag);
227
+ }
222
228
  }
@@ -37,7 +37,14 @@ export function getExceptionMessageFromEvent(e) {
37
37
  return getExceptionMessage(e.reason, []);
38
38
  }
39
39
  else {
40
- return new JSException('Unhandled Promise Rejection', String(e.reason), '[]');
40
+ let message;
41
+ try {
42
+ message = JSON.stringify(e.reason);
43
+ }
44
+ catch (_) {
45
+ message = String(e.reason);
46
+ }
47
+ return new JSException('Unhandled Promise Rejection', message, '[]');
41
48
  }
42
49
  }
43
50
  return null;
@@ -1,6 +1,17 @@
1
1
  import { timestamp, isURL } from "../utils.js";
2
- import { ResourceTiming, SetNodeAttributeURLBased } from "../messages/index.js";
2
+ import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from "../messages/index.js";
3
+ const PLACEHOLDER_SRC = "https://static.openreplay.com/tracker/placeholder.jpeg";
3
4
  export default function (app) {
5
+ function sendPlaceholder(id, node) {
6
+ app.send(new SetNodeAttribute(id, "src", PLACEHOLDER_SRC));
7
+ const { width, height } = node.getBoundingClientRect();
8
+ if (!node.hasAttribute("width")) {
9
+ app.send(new SetNodeAttribute(id, "width", String(width)));
10
+ }
11
+ if (!node.hasAttribute("height")) {
12
+ app.send(new SetNodeAttribute(id, "height", String(height)));
13
+ }
14
+ }
4
15
  const sendImgSrc = app.safe(function () {
5
16
  const id = app.nodes.getID(this);
6
17
  if (id === undefined) {
@@ -15,7 +26,10 @@ export default function (app) {
15
26
  app.send(new ResourceTiming(timestamp(), 0, 0, 0, 0, 0, src, 'img'));
16
27
  }
17
28
  }
18
- else if (src.length < 1e5) {
29
+ else if (src.length >= 1e5 || app.sanitizer.isMasked(id)) {
30
+ sendPlaceholder(id, this);
31
+ }
32
+ else {
19
33
  app.send(new SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
20
34
  }
21
35
  });
@@ -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 {};
@@ -1,6 +1,9 @@
1
1
  import { normSpaces, IN_BROWSER, getLabelAttribute, hasOpenreplayAttribute } from "../utils.js";
2
2
  import { SetInputTarget, SetInputValue, SetInputChecked } from "../messages/index.js";
3
- function isInput(node) {
3
+ function isTextEditable(node) {
4
+ if (node instanceof HTMLTextAreaElement) {
5
+ return true;
6
+ }
4
7
  if (!(node instanceof HTMLInputElement)) {
5
8
  return false;
6
9
  }
@@ -107,7 +110,7 @@ export default function (app, opts) {
107
110
  app.ticker.attach(() => {
108
111
  inputValues.forEach((value, id) => {
109
112
  const node = app.nodes.getNode(id);
110
- if (!isInput(node)) {
113
+ if (!isTextEditable(node)) {
111
114
  inputValues.delete(id);
112
115
  return;
113
116
  }
@@ -138,7 +141,7 @@ export default function (app, opts) {
138
141
  if (id === undefined) {
139
142
  return;
140
143
  }
141
- if (isInput(node)) {
144
+ if (isTextEditable(node)) {
142
145
  inputValues.set(id, node.value);
143
146
  sendInputValue(id, node);
144
147
  return;
@@ -83,7 +83,7 @@ export default function (app) {
83
83
  tag === 'LI' ||
84
84
  target.onclick != null ||
85
85
  target.getAttribute('role') === 'button') {
86
- const label = app.observer.getInnerTextSecure(target);
86
+ const label = app.sanitizer.getInnerTextSecure(target);
87
87
  return normSpaces(label).slice(0, 100);
88
88
  }
89
89
  return '';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openreplay/tracker",
3
3
  "description": "The OpenReplay tracker main package",
4
- "version": "3.4.14",
4
+ "version": "3.4.17-beta.1",
5
5
  "keywords": [
6
6
  "logging",
7
7
  "replay"