@openreplay/tracker 3.5.3 → 3.5.4
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/cjs/app/context.js +37 -14
- package/cjs/app/index.js +1 -1
- package/cjs/app/observer/observer.d.ts +2 -3
- package/cjs/app/observer/observer.js +5 -4
- package/cjs/app/observer/top_observer.js +14 -13
- package/cjs/index.js +1 -1
- package/lib/app/context.js +37 -14
- package/lib/app/index.js +1 -1
- package/lib/app/observer/observer.d.ts +2 -3
- package/lib/app/observer/observer.js +5 -4
- package/lib/app/observer/top_observer.js +15 -14
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/cjs/app/context.js
CHANGED
|
@@ -12,37 +12,60 @@ function isInstance(node, constr) {
|
|
|
12
12
|
// @ts-ignore (for EI, Safary)
|
|
13
13
|
doc.parentWindow ||
|
|
14
14
|
doc.defaultView; // TODO: smart global typing for Window object
|
|
15
|
-
while (context.parent && context.parent !== context) {
|
|
15
|
+
while ((context.parent || context.top) && context.parent !== context) {
|
|
16
16
|
// @ts-ignore
|
|
17
17
|
if (node instanceof context[constr.name]) {
|
|
18
18
|
return true;
|
|
19
19
|
}
|
|
20
20
|
// @ts-ignore
|
|
21
|
-
context = context.parent;
|
|
21
|
+
context = context.parent || context.top;
|
|
22
22
|
}
|
|
23
23
|
// @ts-ignore
|
|
24
24
|
return node instanceof context[constr.name];
|
|
25
25
|
}
|
|
26
26
|
exports.isInstance = isInstance;
|
|
27
|
+
// TODO: ensure 1. it works in every cases (iframes/detached nodes) and 2. the most efficient
|
|
27
28
|
function inDocument(node) {
|
|
28
29
|
const doc = node.ownerDocument;
|
|
29
30
|
if (!doc) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
if (doc.contains(node)) {
|
|
33
31
|
return true;
|
|
34
|
-
}
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
doc.defaultView;
|
|
39
|
-
while (context.parent && context.parent !== context) {
|
|
40
|
-
if (context.document.contains(node)) {
|
|
32
|
+
} // Document
|
|
33
|
+
let current = node;
|
|
34
|
+
while (current) {
|
|
35
|
+
if (current === doc) {
|
|
41
36
|
return true;
|
|
42
37
|
}
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
else if (isInstance(current, ShadowRoot)) {
|
|
39
|
+
current = current.host;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
current = current.parentNode;
|
|
43
|
+
}
|
|
45
44
|
}
|
|
46
45
|
return false;
|
|
47
46
|
}
|
|
48
47
|
exports.inDocument = inDocument;
|
|
48
|
+
// export function inDocument(node: Node): boolean {
|
|
49
|
+
// // @ts-ignore compatability
|
|
50
|
+
// if (node.getRootNode) {
|
|
51
|
+
// let root: Node
|
|
52
|
+
// while ((root = node.getRootNode()) !== node) {
|
|
53
|
+
// ////
|
|
54
|
+
// }
|
|
55
|
+
// }
|
|
56
|
+
// const doc = node.ownerDocument
|
|
57
|
+
// if (!doc) { return false }
|
|
58
|
+
// if (doc.contains(node)) { return true }
|
|
59
|
+
// let context: Window =
|
|
60
|
+
// // @ts-ignore (for EI, Safary)
|
|
61
|
+
// doc.parentWindow ||
|
|
62
|
+
// doc.defaultView;
|
|
63
|
+
// while(context.parent && context.parent !== context) {
|
|
64
|
+
// if (context.document.contains(node)) {
|
|
65
|
+
// return true
|
|
66
|
+
// }
|
|
67
|
+
// // @ts-ignore
|
|
68
|
+
// context = context.parent
|
|
69
|
+
// }
|
|
70
|
+
// return false;
|
|
71
|
+
// }
|
package/cjs/app/index.js
CHANGED
|
@@ -29,7 +29,7 @@ class App {
|
|
|
29
29
|
this.stopCallbacks = [];
|
|
30
30
|
this.commitCallbacks = [];
|
|
31
31
|
this.activityState = ActivityState.NotActive;
|
|
32
|
-
this.version = '3.5.
|
|
32
|
+
this.version = '3.5.4'; // TODO: version compatability check inside each plugin.
|
|
33
33
|
this.preStartMessages = [];
|
|
34
34
|
this.projectKey = projectKey;
|
|
35
35
|
this.options = Object.assign({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import App from "../index.js";
|
|
2
2
|
export default abstract class Observer {
|
|
3
3
|
protected readonly app: App;
|
|
4
|
-
protected readonly
|
|
4
|
+
protected readonly isTopContext: boolean;
|
|
5
5
|
private readonly observer;
|
|
6
6
|
private readonly commited;
|
|
7
7
|
private readonly recents;
|
|
@@ -9,8 +9,7 @@ export default abstract class Observer {
|
|
|
9
9
|
private readonly indexes;
|
|
10
10
|
private readonly attributesList;
|
|
11
11
|
private readonly textSet;
|
|
12
|
-
|
|
13
|
-
constructor(app: App, context?: Window);
|
|
12
|
+
constructor(app: App, isTopContext?: boolean);
|
|
14
13
|
private clear;
|
|
15
14
|
private sendNodeAttribute;
|
|
16
15
|
private sendNodeData;
|
|
@@ -34,16 +34,15 @@ function isObservable(node) {
|
|
|
34
34
|
return !isIgnored(node);
|
|
35
35
|
}
|
|
36
36
|
class Observer {
|
|
37
|
-
constructor(app,
|
|
37
|
+
constructor(app, isTopContext = false) {
|
|
38
38
|
this.app = app;
|
|
39
|
-
this.
|
|
39
|
+
this.isTopContext = isTopContext;
|
|
40
40
|
this.commited = [];
|
|
41
41
|
this.recents = [];
|
|
42
42
|
this.myNodes = [];
|
|
43
43
|
this.indexes = [];
|
|
44
44
|
this.attributesList = [];
|
|
45
45
|
this.textSet = new Set();
|
|
46
|
-
this.inUpperContext = context.parent === context; //TODO: get rid of context here
|
|
47
46
|
this.observer = new MutationObserver(this.app.safe((mutations) => {
|
|
48
47
|
for (const mutation of mutations) {
|
|
49
48
|
const target = mutation.target;
|
|
@@ -186,7 +185,7 @@ class Observer {
|
|
|
186
185
|
// Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
|
|
187
186
|
// TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
|
|
188
187
|
// TODO: Clean the logic (though now it workd fine)
|
|
189
|
-
if (!(0, context_js_1.isInstance)(node, HTMLHtmlElement) || !this.
|
|
188
|
+
if (!(0, context_js_1.isInstance)(node, HTMLHtmlElement) || !this.isTopContext) {
|
|
190
189
|
if (parent === null) {
|
|
191
190
|
this.unbindNode(node);
|
|
192
191
|
return false;
|
|
@@ -274,6 +273,8 @@ class Observer {
|
|
|
274
273
|
for (let id = 0; id < this.recents.length; id++) {
|
|
275
274
|
// TODO: make things/logic nice here.
|
|
276
275
|
// commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
|
|
276
|
+
// Possible solution: separate new node commit (recents) and new attribute/move node commit
|
|
277
|
+
// Otherwise commitNode is called on each node, which might be a lot
|
|
277
278
|
if (!this.myNodes[id]) {
|
|
278
279
|
continue;
|
|
279
280
|
}
|
|
@@ -9,16 +9,17 @@ const utils_js_1 = require("../../utils.js");
|
|
|
9
9
|
const attachShadowNativeFn = utils_js_1.IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
|
|
10
10
|
class TopObserver extends observer_js_1.default {
|
|
11
11
|
constructor(app, options) {
|
|
12
|
-
super(app);
|
|
12
|
+
super(app, true);
|
|
13
13
|
this.iframeObservers = [];
|
|
14
14
|
this.shadowRootObservers = [];
|
|
15
15
|
this.options = Object.assign({
|
|
16
|
-
captureIFrames:
|
|
16
|
+
captureIFrames: true
|
|
17
17
|
}, options);
|
|
18
18
|
// IFrames
|
|
19
19
|
this.app.nodes.attachNodeCallback(node => {
|
|
20
20
|
if ((0, context_js_1.isInstance)(node, HTMLIFrameElement) &&
|
|
21
|
-
(this.options.captureIFrames
|
|
21
|
+
((this.options.captureIFrames && !(0, utils_js_1.hasOpenreplayAttribute)(node, "obscured"))
|
|
22
|
+
|| (0, utils_js_1.hasOpenreplayAttribute)(node, "capture"))) {
|
|
22
23
|
this.handleIframe(node);
|
|
23
24
|
}
|
|
24
25
|
});
|
|
@@ -30,28 +31,28 @@ class TopObserver extends observer_js_1.default {
|
|
|
30
31
|
});
|
|
31
32
|
}
|
|
32
33
|
handleIframe(iframe) {
|
|
33
|
-
let
|
|
34
|
+
let doc = null;
|
|
34
35
|
const handle = this.app.safe(() => {
|
|
35
36
|
const id = this.app.nodes.getID(iframe);
|
|
36
37
|
if (id === undefined) {
|
|
37
38
|
return;
|
|
38
39
|
} //log
|
|
39
|
-
if (iframe.
|
|
40
|
+
if (iframe.contentDocument === doc) {
|
|
40
41
|
return;
|
|
41
|
-
} //
|
|
42
|
-
|
|
43
|
-
if (!
|
|
42
|
+
} // How frequently can it happen?
|
|
43
|
+
doc = iframe.contentDocument;
|
|
44
|
+
if (!doc || !iframe.contentWindow) {
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
|
-
const observer = new iframe_observer_js_1.default(this.app
|
|
47
|
+
const observer = new iframe_observer_js_1.default(this.app);
|
|
47
48
|
this.iframeObservers.push(observer);
|
|
48
49
|
observer.observe(iframe);
|
|
49
50
|
});
|
|
50
|
-
|
|
51
|
+
iframe.addEventListener("load", handle); // why app.attachEventListener not working?
|
|
51
52
|
handle();
|
|
52
53
|
}
|
|
53
54
|
handleShadowRoot(shRoot) {
|
|
54
|
-
const observer = new shadow_root_observer_js_1.default(this.app
|
|
55
|
+
const observer = new shadow_root_observer_js_1.default(this.app);
|
|
55
56
|
this.shadowRootObservers.push(observer);
|
|
56
57
|
observer.observe(shRoot.host);
|
|
57
58
|
}
|
|
@@ -69,9 +70,9 @@ class TopObserver extends observer_js_1.default {
|
|
|
69
70
|
// the change in the re-player behaviour caused by CreateDocument message:
|
|
70
71
|
// the 0-node ("fRoot") will become #document rather than documentElement as it is now.
|
|
71
72
|
// Alternatively - observe(#document) then bindNode(documentElement)
|
|
72
|
-
this.observeRoot(
|
|
73
|
+
this.observeRoot(window.document, () => {
|
|
73
74
|
this.app.send(new index_js_1.CreateDocument());
|
|
74
|
-
},
|
|
75
|
+
}, window.document.documentElement);
|
|
75
76
|
}
|
|
76
77
|
disconnect() {
|
|
77
78
|
Element.prototype.attachShadow = attachShadowNativeFn;
|
package/cjs/index.js
CHANGED
|
@@ -127,7 +127,7 @@ class API {
|
|
|
127
127
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
128
128
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
129
129
|
req.send(JSON.stringify({
|
|
130
|
-
trackerVersion: '3.5.
|
|
130
|
+
trackerVersion: '3.5.4',
|
|
131
131
|
projectKey: options.projectKey,
|
|
132
132
|
doNotTrack,
|
|
133
133
|
// TODO: add precise reason (an exact API missing)
|
package/lib/app/context.js
CHANGED
|
@@ -9,35 +9,58 @@ export function isInstance(node, constr) {
|
|
|
9
9
|
// @ts-ignore (for EI, Safary)
|
|
10
10
|
doc.parentWindow ||
|
|
11
11
|
doc.defaultView; // TODO: smart global typing for Window object
|
|
12
|
-
while (context.parent && context.parent !== context) {
|
|
12
|
+
while ((context.parent || context.top) && context.parent !== context) {
|
|
13
13
|
// @ts-ignore
|
|
14
14
|
if (node instanceof context[constr.name]) {
|
|
15
15
|
return true;
|
|
16
16
|
}
|
|
17
17
|
// @ts-ignore
|
|
18
|
-
context = context.parent;
|
|
18
|
+
context = context.parent || context.top;
|
|
19
19
|
}
|
|
20
20
|
// @ts-ignore
|
|
21
21
|
return node instanceof context[constr.name];
|
|
22
22
|
}
|
|
23
|
+
// TODO: ensure 1. it works in every cases (iframes/detached nodes) and 2. the most efficient
|
|
23
24
|
export function inDocument(node) {
|
|
24
25
|
const doc = node.ownerDocument;
|
|
25
26
|
if (!doc) {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
if (doc.contains(node)) {
|
|
29
27
|
return true;
|
|
30
|
-
}
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
doc.defaultView;
|
|
35
|
-
while (context.parent && context.parent !== context) {
|
|
36
|
-
if (context.document.contains(node)) {
|
|
28
|
+
} // Document
|
|
29
|
+
let current = node;
|
|
30
|
+
while (current) {
|
|
31
|
+
if (current === doc) {
|
|
37
32
|
return true;
|
|
38
33
|
}
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
else if (isInstance(current, ShadowRoot)) {
|
|
35
|
+
current = current.host;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
current = current.parentNode;
|
|
39
|
+
}
|
|
41
40
|
}
|
|
42
41
|
return false;
|
|
43
42
|
}
|
|
43
|
+
// export function inDocument(node: Node): boolean {
|
|
44
|
+
// // @ts-ignore compatability
|
|
45
|
+
// if (node.getRootNode) {
|
|
46
|
+
// let root: Node
|
|
47
|
+
// while ((root = node.getRootNode()) !== node) {
|
|
48
|
+
// ////
|
|
49
|
+
// }
|
|
50
|
+
// }
|
|
51
|
+
// const doc = node.ownerDocument
|
|
52
|
+
// if (!doc) { return false }
|
|
53
|
+
// if (doc.contains(node)) { return true }
|
|
54
|
+
// let context: Window =
|
|
55
|
+
// // @ts-ignore (for EI, Safary)
|
|
56
|
+
// doc.parentWindow ||
|
|
57
|
+
// doc.defaultView;
|
|
58
|
+
// while(context.parent && context.parent !== context) {
|
|
59
|
+
// if (context.document.contains(node)) {
|
|
60
|
+
// return true
|
|
61
|
+
// }
|
|
62
|
+
// // @ts-ignore
|
|
63
|
+
// context = context.parent
|
|
64
|
+
// }
|
|
65
|
+
// return false;
|
|
66
|
+
// }
|
package/lib/app/index.js
CHANGED
|
@@ -26,7 +26,7 @@ export default class App {
|
|
|
26
26
|
this.stopCallbacks = [];
|
|
27
27
|
this.commitCallbacks = [];
|
|
28
28
|
this.activityState = ActivityState.NotActive;
|
|
29
|
-
this.version = '3.5.
|
|
29
|
+
this.version = '3.5.4'; // TODO: version compatability check inside each plugin.
|
|
30
30
|
this.preStartMessages = [];
|
|
31
31
|
this.projectKey = projectKey;
|
|
32
32
|
this.options = Object.assign({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import App from "../index.js";
|
|
2
2
|
export default abstract class Observer {
|
|
3
3
|
protected readonly app: App;
|
|
4
|
-
protected readonly
|
|
4
|
+
protected readonly isTopContext: boolean;
|
|
5
5
|
private readonly observer;
|
|
6
6
|
private readonly commited;
|
|
7
7
|
private readonly recents;
|
|
@@ -9,8 +9,7 @@ export default abstract class Observer {
|
|
|
9
9
|
private readonly indexes;
|
|
10
10
|
private readonly attributesList;
|
|
11
11
|
private readonly textSet;
|
|
12
|
-
|
|
13
|
-
constructor(app: App, context?: Window);
|
|
12
|
+
constructor(app: App, isTopContext?: boolean);
|
|
14
13
|
private clear;
|
|
15
14
|
private sendNodeAttribute;
|
|
16
15
|
private sendNodeData;
|
|
@@ -32,16 +32,15 @@ function isObservable(node) {
|
|
|
32
32
|
return !isIgnored(node);
|
|
33
33
|
}
|
|
34
34
|
export default class Observer {
|
|
35
|
-
constructor(app,
|
|
35
|
+
constructor(app, isTopContext = false) {
|
|
36
36
|
this.app = app;
|
|
37
|
-
this.
|
|
37
|
+
this.isTopContext = isTopContext;
|
|
38
38
|
this.commited = [];
|
|
39
39
|
this.recents = [];
|
|
40
40
|
this.myNodes = [];
|
|
41
41
|
this.indexes = [];
|
|
42
42
|
this.attributesList = [];
|
|
43
43
|
this.textSet = new Set();
|
|
44
|
-
this.inUpperContext = context.parent === context; //TODO: get rid of context here
|
|
45
44
|
this.observer = new MutationObserver(this.app.safe((mutations) => {
|
|
46
45
|
for (const mutation of mutations) {
|
|
47
46
|
const target = mutation.target;
|
|
@@ -184,7 +183,7 @@ export default class Observer {
|
|
|
184
183
|
// Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before)
|
|
185
184
|
// TODO: get rid of "special" cases (there is an issue with CreateDocument altered behaviour though)
|
|
186
185
|
// TODO: Clean the logic (though now it workd fine)
|
|
187
|
-
if (!isInstance(node, HTMLHtmlElement) || !this.
|
|
186
|
+
if (!isInstance(node, HTMLHtmlElement) || !this.isTopContext) {
|
|
188
187
|
if (parent === null) {
|
|
189
188
|
this.unbindNode(node);
|
|
190
189
|
return false;
|
|
@@ -272,6 +271,8 @@ export default class Observer {
|
|
|
272
271
|
for (let id = 0; id < this.recents.length; id++) {
|
|
273
272
|
// TODO: make things/logic nice here.
|
|
274
273
|
// commit required in any case if recents[id] true or false (in case of unbinding) or undefined (in case of attr change).
|
|
274
|
+
// Possible solution: separate new node commit (recents) and new attribute/move node commit
|
|
275
|
+
// Otherwise commitNode is called on each node, which might be a lot
|
|
275
276
|
if (!this.myNodes[id]) {
|
|
276
277
|
continue;
|
|
277
278
|
}
|
|
@@ -3,20 +3,21 @@ import { isInstance } from "../context.js";
|
|
|
3
3
|
import IFrameObserver from "./iframe_observer.js";
|
|
4
4
|
import ShadowRootObserver from "./shadow_root_observer.js";
|
|
5
5
|
import { CreateDocument } from "../../messages/index.js";
|
|
6
|
-
import { IN_BROWSER } from '../../utils.js';
|
|
6
|
+
import { IN_BROWSER, hasOpenreplayAttribute } from '../../utils.js';
|
|
7
7
|
const attachShadowNativeFn = IN_BROWSER ? Element.prototype.attachShadow : () => new ShadowRoot();
|
|
8
8
|
export default class TopObserver extends Observer {
|
|
9
9
|
constructor(app, options) {
|
|
10
|
-
super(app);
|
|
10
|
+
super(app, true);
|
|
11
11
|
this.iframeObservers = [];
|
|
12
12
|
this.shadowRootObservers = [];
|
|
13
13
|
this.options = Object.assign({
|
|
14
|
-
captureIFrames:
|
|
14
|
+
captureIFrames: true
|
|
15
15
|
}, options);
|
|
16
16
|
// IFrames
|
|
17
17
|
this.app.nodes.attachNodeCallback(node => {
|
|
18
18
|
if (isInstance(node, HTMLIFrameElement) &&
|
|
19
|
-
(this.options.captureIFrames
|
|
19
|
+
((this.options.captureIFrames && !hasOpenreplayAttribute(node, "obscured"))
|
|
20
|
+
|| hasOpenreplayAttribute(node, "capture"))) {
|
|
20
21
|
this.handleIframe(node);
|
|
21
22
|
}
|
|
22
23
|
});
|
|
@@ -28,28 +29,28 @@ export default class TopObserver extends Observer {
|
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
handleIframe(iframe) {
|
|
31
|
-
let
|
|
32
|
+
let doc = null;
|
|
32
33
|
const handle = this.app.safe(() => {
|
|
33
34
|
const id = this.app.nodes.getID(iframe);
|
|
34
35
|
if (id === undefined) {
|
|
35
36
|
return;
|
|
36
37
|
} //log
|
|
37
|
-
if (iframe.
|
|
38
|
+
if (iframe.contentDocument === doc) {
|
|
38
39
|
return;
|
|
39
|
-
} //
|
|
40
|
-
|
|
41
|
-
if (!
|
|
40
|
+
} // How frequently can it happen?
|
|
41
|
+
doc = iframe.contentDocument;
|
|
42
|
+
if (!doc || !iframe.contentWindow) {
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
|
-
const observer = new IFrameObserver(this.app
|
|
45
|
+
const observer = new IFrameObserver(this.app);
|
|
45
46
|
this.iframeObservers.push(observer);
|
|
46
47
|
observer.observe(iframe);
|
|
47
48
|
});
|
|
48
|
-
|
|
49
|
+
iframe.addEventListener("load", handle); // why app.attachEventListener not working?
|
|
49
50
|
handle();
|
|
50
51
|
}
|
|
51
52
|
handleShadowRoot(shRoot) {
|
|
52
|
-
const observer = new ShadowRootObserver(this.app
|
|
53
|
+
const observer = new ShadowRootObserver(this.app);
|
|
53
54
|
this.shadowRootObservers.push(observer);
|
|
54
55
|
observer.observe(shRoot.host);
|
|
55
56
|
}
|
|
@@ -67,9 +68,9 @@ export default class TopObserver extends Observer {
|
|
|
67
68
|
// the change in the re-player behaviour caused by CreateDocument message:
|
|
68
69
|
// the 0-node ("fRoot") will become #document rather than documentElement as it is now.
|
|
69
70
|
// Alternatively - observe(#document) then bindNode(documentElement)
|
|
70
|
-
this.observeRoot(
|
|
71
|
+
this.observeRoot(window.document, () => {
|
|
71
72
|
this.app.send(new CreateDocument());
|
|
72
|
-
},
|
|
73
|
+
}, window.document.documentElement);
|
|
73
74
|
}
|
|
74
75
|
disconnect() {
|
|
75
76
|
Element.prototype.attachShadow = attachShadowNativeFn;
|
package/lib/index.js
CHANGED
|
@@ -123,7 +123,7 @@ export default class API {
|
|
|
123
123
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
124
124
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
125
125
|
req.send(JSON.stringify({
|
|
126
|
-
trackerVersion: '3.5.
|
|
126
|
+
trackerVersion: '3.5.4',
|
|
127
127
|
projectKey: options.projectKey,
|
|
128
128
|
doNotTrack,
|
|
129
129
|
// TODO: add precise reason (an exact API missing)
|