@openreplay/tracker 3.5.2 → 3.5.5-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.
- package/cjs/app/context.js +37 -14
- package/cjs/app/index.d.ts +4 -5
- package/cjs/app/index.js +11 -24
- package/cjs/app/logger.d.ts +3 -4
- package/cjs/app/logger.js +13 -14
- 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/app/session.d.ts +25 -0
- package/cjs/app/session.js +68 -0
- package/cjs/index.js +3 -5
- package/lib/app/context.js +37 -14
- package/lib/app/index.d.ts +4 -5
- package/lib/app/index.js +12 -25
- package/lib/app/logger.d.ts +3 -4
- package/lib/app/logger.js +13 -14
- 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/app/session.d.ts +25 -0
- package/lib/app/session.js +65 -0
- package/lib/index.js +4 -6
- 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
|
|
15
|
+
while (context !== window) {
|
|
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 || window;
|
|
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.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import Nodes from "./nodes.js";
|
|
|
3
3
|
import Sanitizer from "./sanitizer.js";
|
|
4
4
|
import Ticker from "./ticker.js";
|
|
5
5
|
import Logger from "./logger.js";
|
|
6
|
+
import Session from "./session.js";
|
|
6
7
|
import type { Options as ObserverOptions } from "./observer/top_observer.js";
|
|
7
8
|
import type { Options as SanitizerOptions } from "./sanitizer.js";
|
|
8
9
|
import type { Options as LoggerOptions } from "./logger.js";
|
|
@@ -44,6 +45,7 @@ export default class App {
|
|
|
44
45
|
readonly sanitizer: Sanitizer;
|
|
45
46
|
readonly debug: Logger;
|
|
46
47
|
readonly notify: Logger;
|
|
48
|
+
readonly session: Session;
|
|
47
49
|
private readonly messages;
|
|
48
50
|
private readonly observer;
|
|
49
51
|
private readonly startCallbacks;
|
|
@@ -51,9 +53,6 @@ export default class App {
|
|
|
51
53
|
private readonly commitCallbacks;
|
|
52
54
|
private readonly options;
|
|
53
55
|
private readonly revID;
|
|
54
|
-
private _sessionID;
|
|
55
|
-
private _userID;
|
|
56
|
-
private _metadata;
|
|
57
56
|
private activityState;
|
|
58
57
|
private version;
|
|
59
58
|
private readonly worker?;
|
|
@@ -62,8 +61,8 @@ export default class App {
|
|
|
62
61
|
private readonly preStartMessages;
|
|
63
62
|
send(message: Message, urgent?: boolean): void;
|
|
64
63
|
private commit;
|
|
65
|
-
attachCommitCallback(cb: CommitCallback): void;
|
|
66
64
|
safe<T extends (...args: any[]) => void>(fn: T): T;
|
|
65
|
+
attachCommitCallback(cb: CommitCallback): void;
|
|
67
66
|
attachStartCallback(cb: Callback): void;
|
|
68
67
|
attachStopCallback(cb: Callback): void;
|
|
69
68
|
attachEventListener(target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean): void;
|
|
@@ -75,10 +74,10 @@ export default class App {
|
|
|
75
74
|
revID: string;
|
|
76
75
|
timestamp: number;
|
|
77
76
|
trackerVersion: string;
|
|
78
|
-
userID: string | null;
|
|
79
77
|
isSnippet: boolean;
|
|
80
78
|
sessionID: string | null;
|
|
81
79
|
metadata: Record<string, string>;
|
|
80
|
+
userID: string | null;
|
|
82
81
|
};
|
|
83
82
|
getSessionToken(): string | undefined;
|
|
84
83
|
getSessionID(): string | undefined;
|
package/cjs/app/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const top_observer_js_1 = require("./observer/top_observer.js");
|
|
|
8
8
|
const sanitizer_js_1 = require("./sanitizer.js");
|
|
9
9
|
const ticker_js_1 = require("./ticker.js");
|
|
10
10
|
const logger_js_1 = require("./logger.js");
|
|
11
|
+
const session_js_1 = require("./session.js");
|
|
11
12
|
const performance_js_1 = require("../modules/performance.js");
|
|
12
13
|
var ActivityState;
|
|
13
14
|
(function (ActivityState) {
|
|
@@ -27,11 +28,8 @@ class App {
|
|
|
27
28
|
this.startCallbacks = [];
|
|
28
29
|
this.stopCallbacks = [];
|
|
29
30
|
this.commitCallbacks = [];
|
|
30
|
-
this._sessionID = null;
|
|
31
|
-
this._userID = null;
|
|
32
|
-
this._metadata = {};
|
|
33
31
|
this.activityState = ActivityState.NotActive;
|
|
34
|
-
this.version = '3.5.
|
|
32
|
+
this.version = '3.5.5-beta.0'; // TODO: version compatability check inside each plugin.
|
|
35
33
|
this.preStartMessages = [];
|
|
36
34
|
this.projectKey = projectKey;
|
|
37
35
|
this.options = Object.assign({
|
|
@@ -58,25 +56,20 @@ class App {
|
|
|
58
56
|
this.ticker.attach(() => this.commit());
|
|
59
57
|
this.debug = new logger_js_1.default(this.options.__debug__);
|
|
60
58
|
this.notify = new logger_js_1.default(this.options.verbose ? logger_js_1.LogLevel.Warnings : logger_js_1.LogLevel.Silent);
|
|
59
|
+
this.session = new session_js_1.default(this);
|
|
61
60
|
try {
|
|
62
61
|
this.worker = new Worker(URL.createObjectURL(new Blob([`"use strict";function t(t){function s(...s){return new t(...s)}return s.prototype=t.prototype,s}const s=new Map;const i=t(class{constructor(t,s,i){this.pageNo=t,this.firstIndex=s,this.timestamp=i,this._id=80}encode(t){return t.uint(80)&&t.uint(this.pageNo)&&t.uint(this.firstIndex)&&t.int(this.timestamp)}});s.set(80,i);const n=t(class{constructor(t){this.timestamp=t,this._id=0}encode(t){return t.uint(0)&&t.uint(this.timestamp)}});s.set(0,n);const e=t(class{constructor(t,s,i){this.url=t,this.referrer=s,this.navigationStart=i,this._id=4}encode(t){return t.uint(4)&&t.string(this.url)&&t.string(this.referrer)&&t.uint(this.navigationStart)}});s.set(4,e);const r=t(class{constructor(t,s){this.width=t,this.height=s,this._id=5}encode(t){return t.uint(5)&&t.uint(this.width)&&t.uint(this.height)}});s.set(5,r);const o=t(class{constructor(t,s){this.x=t,this.y=s,this._id=6}encode(t){return t.uint(6)&&t.int(this.x)&&t.int(this.y)}});s.set(6,o);const h=t(class{constructor(){this._id=7}encode(t){return t.uint(7)}});s.set(7,h);const c=t(class{constructor(t,s,i,n,e){this.id=t,this.parentID=s,this.index=i,this.tag=n,this.svg=e,this._id=8}encode(t){return t.uint(8)&&t.uint(this.id)&&t.uint(this.parentID)&&t.uint(this.index)&&t.string(this.tag)&&t.boolean(this.svg)}});s.set(8,c);const u=t(class{constructor(t,s,i){this.id=t,this.parentID=s,this.index=i,this._id=9}encode(t){return t.uint(9)&&t.uint(this.id)&&t.uint(this.parentID)&&t.uint(this.index)}});s.set(9,u);const a=t(class{constructor(t,s,i){this.id=t,this.parentID=s,this.index=i,this._id=10}encode(t){return t.uint(10)&&t.uint(this.id)&&t.uint(this.parentID)&&t.uint(this.index)}});s.set(10,a);const d=t(class{constructor(t){this.id=t,this._id=11}encode(t){return t.uint(11)&&t.uint(this.id)}});s.set(11,d);const l=t(class{constructor(t,s,i){this.id=t,this.name=s,this.value=i,this._id=12}encode(t){return t.uint(12)&&t.uint(this.id)&&t.string(this.name)&&t.string(this.value)}});s.set(12,l);const g=t(class{constructor(t,s){this.id=t,this.name=s,this._id=13}encode(t){return t.uint(13)&&t.uint(this.id)&&t.string(this.name)}});s.set(13,g);const f=t(class{constructor(t,s){this.id=t,this.data=s,this._id=14}encode(t){return t.uint(14)&&t.uint(this.id)&&t.string(this.data)}});s.set(14,f);const p=t(class{constructor(t,s,i){this.id=t,this.x=s,this.y=i,this._id=16}encode(t){return t.uint(16)&&t.uint(this.id)&&t.int(this.x)&&t.int(this.y)}});s.set(16,p);const m=t(class{constructor(t,s){this.id=t,this.label=s,this._id=17}encode(t){return t.uint(17)&&t.uint(this.id)&&t.string(this.label)}});s.set(17,m);const _=t(class{constructor(t,s,i){this.id=t,this.value=s,this.mask=i,this._id=18}encode(t){return t.uint(18)&&t.uint(this.id)&&t.string(this.value)&&t.int(this.mask)}});s.set(18,_);const y=t(class{constructor(t,s){this.id=t,this.checked=s,this._id=19}encode(t){return t.uint(19)&&t.uint(this.id)&&t.boolean(this.checked)}});s.set(19,y);const v=t(class{constructor(t,s){this.x=t,this.y=s,this._id=20}encode(t){return t.uint(20)&&t.uint(this.x)&&t.uint(this.y)}});s.set(20,v);const S=t(class{constructor(t,s){this.level=t,this.value=s,this._id=22}encode(t){return t.uint(22)&&t.string(this.level)&&t.string(this.value)}});s.set(22,S);const b=t(class{constructor(t,s,i,n,e,r,o,h,c){this.requestStart=t,this.responseStart=s,this.responseEnd=i,this.domContentLoadedEventStart=n,this.domContentLoadedEventEnd=e,this.loadEventStart=r,this.loadEventEnd=o,this.firstPaint=h,this.firstContentfulPaint=c,this._id=23}encode(t){return t.uint(23)&&t.uint(this.requestStart)&&t.uint(this.responseStart)&&t.uint(this.responseEnd)&&t.uint(this.domContentLoadedEventStart)&&t.uint(this.domContentLoadedEventEnd)&&t.uint(this.loadEventStart)&&t.uint(this.loadEventEnd)&&t.uint(this.firstPaint)&&t.uint(this.firstContentfulPaint)}});s.set(23,b);const x=t(class{constructor(t,s,i){this.speedIndex=t,this.visuallyComplete=s,this.timeToInteractive=i,this._id=24}encode(t){return t.uint(24)&&t.uint(this.speedIndex)&&t.uint(this.visuallyComplete)&&t.uint(this.timeToInteractive)}});s.set(24,x);const E=t(class{constructor(t,s,i){this.name=t,this.message=s,this.payload=i,this._id=25}encode(t){return t.uint(25)&&t.string(this.name)&&t.string(this.message)&&t.string(this.payload)}});s.set(25,E);const k=t(class{constructor(t,s){this.name=t,this.payload=s,this._id=27}encode(t){return t.uint(27)&&t.string(this.name)&&t.string(this.payload)}});s.set(27,k);const I=t(class{constructor(t){this.id=t,this._id=28}encode(t){return t.uint(28)&&t.string(this.id)}});s.set(28,I);const z=t(class{constructor(t){this.id=t,this._id=29}encode(t){return t.uint(29)&&t.string(this.id)}});s.set(29,z);const w=t(class{constructor(t,s){this.key=t,this.value=s,this._id=30}encode(t){return t.uint(30)&&t.string(this.key)&&t.string(this.value)}});s.set(30,w);const T=t(class{constructor(t,s,i){this.id=t,this.rule=s,this.index=i,this._id=37}encode(t){return t.uint(37)&&t.uint(this.id)&&t.string(this.rule)&&t.uint(this.index)}});s.set(37,T);const L=t(class{constructor(t,s){this.id=t,this.index=s,this._id=38}encode(t){return t.uint(38)&&t.uint(this.id)&&t.uint(this.index)}});s.set(38,L);const A=t(class{constructor(t,s,i,n,e,r,o){this.method=t,this.url=s,this.request=i,this.response=n,this.status=e,this.timestamp=r,this.duration=o,this._id=39}encode(t){return t.uint(39)&&t.string(this.method)&&t.string(this.url)&&t.string(this.request)&&t.string(this.response)&&t.uint(this.status)&&t.uint(this.timestamp)&&t.uint(this.duration)}});s.set(39,A);const C=t(class{constructor(t,s,i,n){this.name=t,this.duration=s,this.args=i,this.result=n,this._id=40}encode(t){return t.uint(40)&&t.string(this.name)&&t.uint(this.duration)&&t.string(this.args)&&t.string(this.result)}});s.set(40,C);const M=t(class{constructor(t,s){this.key=t,this.value=s,this._id=41}encode(t){return t.uint(41)&&t.string(this.key)&&t.string(this.value)}});s.set(41,M);const R=t(class{constructor(t){this.type=t,this._id=42}encode(t){return t.uint(42)&&t.string(this.type)}});s.set(42,R);const N=t(class{constructor(t,s,i){this.action=t,this.state=s,this.duration=i,this._id=44}encode(t){return t.uint(44)&&t.string(this.action)&&t.string(this.state)&&t.uint(this.duration)}});s.set(44,N);const D=t(class{constructor(t,s){this.mutation=t,this.state=s,this._id=45}encode(t){return t.uint(45)&&t.string(this.mutation)&&t.string(this.state)}});s.set(45,D);const U=t(class{constructor(t,s){this.type=t,this.payload=s,this._id=46}encode(t){return t.uint(46)&&t.string(this.type)&&t.string(this.payload)}});s.set(46,U);const O=t(class{constructor(t,s,i){this.action=t,this.state=s,this.duration=i,this._id=47}encode(t){return t.uint(47)&&t.string(this.action)&&t.string(this.state)&&t.uint(this.duration)}});s.set(47,O);const q=t(class{constructor(t,s,i,n){this.operationKind=t,this.operationName=s,this.variables=i,this.response=n,this._id=48}encode(t){return t.uint(48)&&t.string(this.operationKind)&&t.string(this.operationName)&&t.string(this.variables)&&t.string(this.response)}});s.set(48,q);const H=t(class{constructor(t,s,i,n){this.frames=t,this.ticks=s,this.totalJSHeapSize=i,this.usedJSHeapSize=n,this._id=49}encode(t){return t.uint(49)&&t.int(this.frames)&&t.int(this.ticks)&&t.uint(this.totalJSHeapSize)&&t.uint(this.usedJSHeapSize)}});s.set(49,H);const P=t(class{constructor(t,s,i,n,e,r,o,h){this.timestamp=t,this.duration=s,this.ttfb=i,this.headerSize=n,this.encodedBodySize=e,this.decodedBodySize=r,this.url=o,this.initiator=h,this._id=53}encode(t){return t.uint(53)&&t.uint(this.timestamp)&&t.uint(this.duration)&&t.uint(this.ttfb)&&t.uint(this.headerSize)&&t.uint(this.encodedBodySize)&&t.uint(this.decodedBodySize)&&t.string(this.url)&&t.string(this.initiator)}});s.set(53,P);const B=t(class{constructor(t,s){this.downlink=t,this.type=s,this._id=54}encode(t){return t.uint(54)&&t.uint(this.downlink)&&t.string(this.type)}});s.set(54,B);const J=t(class{constructor(t){this.hidden=t,this._id=55}encode(t){return t.uint(55)&&t.boolean(this.hidden)}});s.set(55,J);const j=t(class{constructor(t,s,i,n,e,r,o){this.timestamp=t,this.duration=s,this.context=i,this.containerType=n,this.containerSrc=e,this.containerId=r,this.containerName=o,this._id=59}encode(t){return t.uint(59)&&t.uint(this.timestamp)&&t.uint(this.duration)&&t.uint(this.context)&&t.uint(this.containerType)&&t.string(this.containerSrc)&&t.string(this.containerId)&&t.string(this.containerName)}});s.set(59,j);const G=t(class{constructor(t,s,i,n){this.id=t,this.name=s,this.value=i,this.baseURL=n,this._id=60}encode(t){return t.uint(60)&&t.uint(this.id)&&t.string(this.name)&&t.string(this.value)&&t.string(this.baseURL)}});s.set(60,G);const K=t(class{constructor(t,s,i){this.id=t,this.data=s,this.baseURL=i,this._id=61}encode(t){return t.uint(61)&&t.uint(this.id)&&t.string(this.data)&&t.string(this.baseURL)}});s.set(61,K);const X=t(class{constructor(t,s){this.type=t,this.value=s,this._id=63}encode(t){return t.uint(63)&&t.string(this.type)&&t.string(this.value)}});s.set(63,X);const F=t(class{constructor(t,s){this.name=t,this.payload=s,this._id=64}encode(t){return t.uint(64)&&t.string(this.name)&&t.string(this.payload)}});s.set(64,F);const Q=t(class{constructor(){this._id=65}encode(t){return t.uint(65)}});s.set(65,Q);const V=t(class{constructor(t,s,i,n){this.id=t,this.rule=s,this.index=i,this.baseURL=n,this._id=67}encode(t){return t.uint(67)&&t.uint(this.id)&&t.string(this.rule)&&t.uint(this.index)&&t.string(this.baseURL)}});s.set(67,V);const W=t(class{constructor(t,s,i,n){this.id=t,this.hesitationTime=s,this.label=i,this.selector=n,this._id=69}encode(t){return t.uint(69)&&t.uint(this.id)&&t.uint(this.hesitationTime)&&t.string(this.label)&&t.string(this.selector)}});s.set(69,W);const Y=t(class{constructor(t,s){this.frameID=t,this.id=s,this._id=70}encode(t){return t.uint(70)&&t.uint(this.frameID)&&t.uint(this.id)}});s.set(70,Y);const Z="function"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let n=-1;for(var e=0,r=0,o=0;o!==s;){if(e=t.charCodeAt(o),o+=1,e>=55296&&e<=56319){if(o===s){i[n+=1]=239,i[n+=1]=191,i[n+=1]=189;break}if(!((r=t.charCodeAt(o))>=56320&&r<=57343)){i[n+=1]=239,i[n+=1]=191,i[n+=1]=189;continue}if(o+=1,(e=1024*(e-55296)+r-56320+65536)>65535){i[n+=1]=240|e>>>18,i[n+=1]=128|e>>>12&63,i[n+=1]=128|e>>>6&63,i[n+=1]=128|63&e;continue}}e<=127?i[n+=1]=0|e:e<=2047?(i[n+=1]=192|e>>>6,i[n+=1]=128|63&e):(i[n+=1]=224|e>>>12,i[n+=1]=128|e>>>6&63,i[n+=1]=128|63&e)}return i.subarray(0,n+1)}};class tt{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}checkpoint(){this.checkpointOffset=this.offset}isEmpty(){return 0===this.offset}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=Z.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}let st=1e6,it=2e5,nt=new tt(it),et="",rt="",ot=0,ht=0,ct=0,ut=0,at=!0;function dt(){return new i(ot,ut,ht).encode(nt)}let lt=null;const gt=[];let ft,pt=!1,mt=0,_t=8e3,yt=10;function vt(){if(at||""===rt||""===et)return;const t=nt.flush();pt?gt.push(t):(pt=!0,function t(s){const i=new XMLHttpRequest;i.open("POST",et+"/v1/web/i",!1),i.setRequestHeader("Authorization","Bearer "+rt),i.onreadystatechange=function(){if(4===this.readyState){if(0==this.status)return;if(this.status>=400)return pt=!1,St(),gt.length=0,401===this.status?void self.postMessage("restart"):void self.postMessage(null);const s=gt.shift();s?t(s):pt=!1}},i.onerror=function(i){if(mt>=yt)return St(),void self.postMessage(null);mt++,setTimeout(()=>t(s),_t)},i.send(s.buffer)}(t)),at=!0,dt()}function St(){et="",rt="",null!==lt&&(clearInterval(lt),lt=null),nt.reset()}self.onmessage=({data:t})=>{if(null!==t)return"stop"===t?(vt(),void St()):Array.isArray(t)?void t.forEach(t=>{const i=new(s.get(t._id));if(Object.assign(i,t),i instanceof n?ht=i.timestamp:i instanceof J&&(i.hidden?ft=setTimeout(()=>self.postMessage("restart"),18e5):clearTimeout(ft)),nt.checkpoint(),!i.encode(nt)&&(vt(),!i.encode(nt)))for(;!i.encode(nt);){if(it===st)return console.warn("OpenReplay: beacon size overflow."),nt.reset(),void dt();it=Math.min(2*it,st),nt=new tt(it),dt()}ut++,at=!1}):(et=t.ingestPoint||et,rt=t.token||rt,ot=t.pageNo||ot,ht=t.startTimestamp||ht,ct=t.timeAdjustment||ct,yt=t.connAttemptCount||yt,_t=t.connAttemptGap||_t,st=t.beaconSizeLimit||st,it=Math.min(st,t.beaconSize||it),nt.isEmpty()&&dt(),void(null===lt&&(lt=setInterval(vt,1e4))));vt()};
|
|
63
62
|
`], { type: 'text/javascript' })));
|
|
64
63
|
this.worker.onerror = e => {
|
|
65
64
|
this._debug("webworker_error", e);
|
|
66
65
|
};
|
|
67
|
-
let lastTs = (0, utils_js_1.timestamp)();
|
|
68
|
-
let fileno = 0;
|
|
69
66
|
this.worker.onmessage = ({ data }) => {
|
|
70
67
|
if (data === null) {
|
|
71
68
|
this.stop();
|
|
72
69
|
}
|
|
73
70
|
else if (data === "restart") {
|
|
74
71
|
this.stop();
|
|
75
|
-
this.start({
|
|
76
|
-
forceNew: true,
|
|
77
|
-
userID: this._userID || undefined,
|
|
78
|
-
metadata: this._metadata || undefined,
|
|
79
|
-
});
|
|
72
|
+
this.start({ forceNew: true });
|
|
80
73
|
}
|
|
81
74
|
};
|
|
82
75
|
const alertWorker = () => {
|
|
@@ -130,9 +123,6 @@ class App {
|
|
|
130
123
|
this.messages.length = 0;
|
|
131
124
|
}
|
|
132
125
|
}
|
|
133
|
-
attachCommitCallback(cb) {
|
|
134
|
-
this.commitCallbacks.push(cb);
|
|
135
|
-
}
|
|
136
126
|
safe(fn) {
|
|
137
127
|
const app = this;
|
|
138
128
|
return function (...args) {
|
|
@@ -148,6 +138,9 @@ class App {
|
|
|
148
138
|
}
|
|
149
139
|
}; // TODO: correct typing
|
|
150
140
|
}
|
|
141
|
+
attachCommitCallback(cb) {
|
|
142
|
+
this.commitCallbacks.push(cb);
|
|
143
|
+
}
|
|
151
144
|
attachStartCallback(cb) {
|
|
152
145
|
this.startCallbacks.push(cb);
|
|
153
146
|
}
|
|
@@ -178,12 +171,11 @@ class App {
|
|
|
178
171
|
revID: this.revID,
|
|
179
172
|
timestamp: (0, utils_js_1.timestamp)(),
|
|
180
173
|
trackerVersion: this.version,
|
|
181
|
-
userID: this._userID,
|
|
182
174
|
isSnippet: this.options.__is_snippet,
|
|
183
175
|
};
|
|
184
176
|
}
|
|
185
177
|
getSessionInfo() {
|
|
186
|
-
return Object.assign({
|
|
178
|
+
return Object.assign(Object.assign({}, this.session.getInfo()), this.getStartInfo());
|
|
187
179
|
}
|
|
188
180
|
getSessionToken() {
|
|
189
181
|
const token = sessionStorage.getItem(this.options.session_token_key);
|
|
@@ -192,7 +184,7 @@ class App {
|
|
|
192
184
|
}
|
|
193
185
|
}
|
|
194
186
|
getSessionID() {
|
|
195
|
-
return this.
|
|
187
|
+
return this.session.getInfo().sessionID || undefined;
|
|
196
188
|
}
|
|
197
189
|
getHost() {
|
|
198
190
|
return new URL(this.options.ingestPoint).hostname;
|
|
@@ -249,8 +241,6 @@ class App {
|
|
|
249
241
|
pageNo++;
|
|
250
242
|
}
|
|
251
243
|
sessionStorage.setItem(this.options.session_pageno_key, pageNo.toString());
|
|
252
|
-
this._userID = startOpts.userID || null;
|
|
253
|
-
this._metadata = startOpts.metadata || {}; // TODO: update both dynamically on corresponding messages
|
|
254
244
|
const startInfo = this.getStartInfo();
|
|
255
245
|
const messageData = {
|
|
256
246
|
ingestPoint: this.options.ingestPoint,
|
|
@@ -267,7 +257,7 @@ class App {
|
|
|
267
257
|
headers: {
|
|
268
258
|
'Content-Type': 'application/json',
|
|
269
259
|
},
|
|
270
|
-
body: JSON.stringify(Object.assign(Object.assign({}, startInfo), { token: sessionStorage.getItem(this.options.session_token_key), deviceMemory: performance_js_1.deviceMemory,
|
|
260
|
+
body: JSON.stringify(Object.assign(Object.assign({}, startInfo), { userID: startOpts.userID || this.session.getInfo().userID, token: sessionStorage.getItem(this.options.session_token_key), deviceMemory: performance_js_1.deviceMemory,
|
|
271
261
|
jsHeapSizeLimit: performance_js_1.jsHeapSizeLimit, reset: startOpts.forceNew || sReset !== null })),
|
|
272
262
|
})
|
|
273
263
|
.then(r => {
|
|
@@ -292,15 +282,12 @@ class App {
|
|
|
292
282
|
}
|
|
293
283
|
sessionStorage.setItem(this.options.session_token_key, token);
|
|
294
284
|
localStorage.setItem(this.options.local_uuid_key, userUUID);
|
|
295
|
-
|
|
296
|
-
this._sessionID = sessionID;
|
|
297
|
-
}
|
|
285
|
+
this.session.update(Object.assign({ sessionID }, startOpts));
|
|
298
286
|
this.activityState = ActivityState.Active;
|
|
299
287
|
this.worker.postMessage({ token, beaconSizeLimit });
|
|
300
288
|
this.startCallbacks.forEach((cb) => cb());
|
|
301
289
|
this.observer.observe();
|
|
302
290
|
this.ticker.start();
|
|
303
|
-
Object.entries(this._metadata).forEach(([key, value]) => this.send(new index_js_1.Metadata(key, value)));
|
|
304
291
|
this.notify.log("OpenReplay tracking started.");
|
|
305
292
|
// TODO: get rid of onStart
|
|
306
293
|
const onStartInfo = { sessionToken: token, userUUID, sessionID };
|
package/cjs/app/logger.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export declare const LogLevel: {
|
|
2
|
-
readonly Verbose:
|
|
3
|
-
readonly
|
|
2
|
+
readonly Verbose: 5;
|
|
3
|
+
readonly Log: 4;
|
|
4
4
|
readonly Warnings: 3;
|
|
5
|
-
readonly
|
|
5
|
+
readonly Errors: 2;
|
|
6
6
|
readonly Silent: 0;
|
|
7
7
|
};
|
|
8
8
|
declare type LogLevel = typeof LogLevel[keyof typeof LogLevel];
|
|
@@ -18,7 +18,6 @@ interface _Options {
|
|
|
18
18
|
export declare type Options = true | _Options | LogLevel;
|
|
19
19
|
export default class Logger {
|
|
20
20
|
private readonly options;
|
|
21
|
-
private readonly opts;
|
|
22
21
|
constructor(options?: Options);
|
|
23
22
|
log(...args: any): void;
|
|
24
23
|
warn(...args: any): void;
|
package/cjs/app/logger.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LogLevel = void 0;
|
|
4
4
|
exports.LogLevel = {
|
|
5
|
-
Verbose:
|
|
6
|
-
|
|
5
|
+
Verbose: 5,
|
|
6
|
+
Log: 4,
|
|
7
7
|
Warnings: 3,
|
|
8
|
-
|
|
8
|
+
Errors: 2,
|
|
9
9
|
Silent: 0,
|
|
10
10
|
};
|
|
11
11
|
function IsCustomLevel(l) {
|
|
@@ -13,29 +13,28 @@ function IsCustomLevel(l) {
|
|
|
13
13
|
}
|
|
14
14
|
class Logger {
|
|
15
15
|
constructor(options = exports.LogLevel.Silent) {
|
|
16
|
-
this.options = options
|
|
17
|
-
this.opts = options === true
|
|
16
|
+
this.options = options === true
|
|
18
17
|
? { level: exports.LogLevel.Verbose }
|
|
19
18
|
: typeof options === "number" ? { level: options } : options;
|
|
20
19
|
}
|
|
21
20
|
log(...args) {
|
|
22
|
-
if (IsCustomLevel(this.
|
|
23
|
-
? this.
|
|
24
|
-
: this.
|
|
21
|
+
if (IsCustomLevel(this.options.level)
|
|
22
|
+
? this.options.level.log
|
|
23
|
+
: this.options.level >= exports.LogLevel.Log) {
|
|
25
24
|
console.log(...args);
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
warn(...args) {
|
|
29
|
-
if (IsCustomLevel(this.
|
|
30
|
-
? this.
|
|
31
|
-
: this.
|
|
28
|
+
if (IsCustomLevel(this.options.level)
|
|
29
|
+
? this.options.level.warn
|
|
30
|
+
: this.options.level >= exports.LogLevel.Warnings) {
|
|
32
31
|
console.warn(...args);
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
34
|
error(...args) {
|
|
36
|
-
if (IsCustomLevel(this.
|
|
37
|
-
? this.
|
|
38
|
-
: this.
|
|
35
|
+
if (IsCustomLevel(this.options.level)
|
|
36
|
+
? this.options.level.error
|
|
37
|
+
: this.options.level >= exports.LogLevel.Errors) {
|
|
39
38
|
console.error(...args);
|
|
40
39
|
}
|
|
41
40
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import App from "./index.js";
|
|
2
|
+
interface SessionInfo {
|
|
3
|
+
sessionID: string | null;
|
|
4
|
+
metadata: Record<string, string>;
|
|
5
|
+
userID: string | null;
|
|
6
|
+
}
|
|
7
|
+
declare type OnUpdateCallback = (i: Partial<SessionInfo>) => void;
|
|
8
|
+
export default class Session {
|
|
9
|
+
private app;
|
|
10
|
+
private metadata;
|
|
11
|
+
private userID;
|
|
12
|
+
private sessionID;
|
|
13
|
+
private activityState;
|
|
14
|
+
private callbacks;
|
|
15
|
+
constructor(app: App);
|
|
16
|
+
attachUpdateCallback(cb: OnUpdateCallback): void;
|
|
17
|
+
private handleUpdate;
|
|
18
|
+
update({ userID, metadata, sessionID }: Partial<SessionInfo>): void;
|
|
19
|
+
private _setMetadata;
|
|
20
|
+
private _setUserID;
|
|
21
|
+
setMetadata(key: string, value: string): void;
|
|
22
|
+
setUserID(userID: string): void;
|
|
23
|
+
getInfo(): SessionInfo;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_js_1 = require("../messages/index.js");
|
|
4
|
+
var ActivityState;
|
|
5
|
+
(function (ActivityState) {
|
|
6
|
+
ActivityState[ActivityState["NotActive"] = 0] = "NotActive";
|
|
7
|
+
ActivityState[ActivityState["Starting"] = 1] = "Starting";
|
|
8
|
+
ActivityState[ActivityState["Active"] = 2] = "Active";
|
|
9
|
+
})(ActivityState || (ActivityState = {}));
|
|
10
|
+
class Session {
|
|
11
|
+
constructor(app) {
|
|
12
|
+
this.app = app;
|
|
13
|
+
this.metadata = {};
|
|
14
|
+
this.userID = null;
|
|
15
|
+
this.sessionID = null;
|
|
16
|
+
this.activityState = ActivityState.NotActive;
|
|
17
|
+
this.callbacks = [];
|
|
18
|
+
}
|
|
19
|
+
attachUpdateCallback(cb) {
|
|
20
|
+
this.callbacks.push(cb);
|
|
21
|
+
}
|
|
22
|
+
handleUpdate() {
|
|
23
|
+
const sessInfo = this.getInfo();
|
|
24
|
+
if (sessInfo.userID == null) {
|
|
25
|
+
delete sessInfo.userID;
|
|
26
|
+
}
|
|
27
|
+
if (sessInfo.sessionID == null) {
|
|
28
|
+
delete sessInfo.sessionID;
|
|
29
|
+
}
|
|
30
|
+
this.callbacks.forEach(cb => cb(sessInfo));
|
|
31
|
+
}
|
|
32
|
+
update({ userID, metadata, sessionID }) {
|
|
33
|
+
if (userID != null) { // TODO clear nullable/undefinable types
|
|
34
|
+
this._setUserID(userID);
|
|
35
|
+
}
|
|
36
|
+
if (metadata !== undefined) {
|
|
37
|
+
Object.entries(metadata).forEach(kv => this._setMetadata(...kv));
|
|
38
|
+
}
|
|
39
|
+
if (sessionID !== undefined) {
|
|
40
|
+
this.sessionID = sessionID;
|
|
41
|
+
}
|
|
42
|
+
this.handleUpdate();
|
|
43
|
+
}
|
|
44
|
+
_setMetadata(key, value) {
|
|
45
|
+
this.app.send(new index_js_1.Metadata(key, value));
|
|
46
|
+
this.metadata[key] = value;
|
|
47
|
+
}
|
|
48
|
+
_setUserID(userID) {
|
|
49
|
+
this.app.send(new index_js_1.UserID(userID));
|
|
50
|
+
this.userID = userID;
|
|
51
|
+
}
|
|
52
|
+
setMetadata(key, value) {
|
|
53
|
+
this._setMetadata(key, value);
|
|
54
|
+
this.handleUpdate();
|
|
55
|
+
}
|
|
56
|
+
setUserID(userID) {
|
|
57
|
+
this._setUserID(userID);
|
|
58
|
+
this.handleUpdate();
|
|
59
|
+
}
|
|
60
|
+
getInfo() {
|
|
61
|
+
return {
|
|
62
|
+
sessionID: this.sessionID,
|
|
63
|
+
metadata: this.metadata,
|
|
64
|
+
userID: this.userID,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
exports.default = Session;
|
package/cjs/index.js
CHANGED
|
@@ -17,7 +17,6 @@ const timing_js_1 = require("./modules/timing.js");
|
|
|
17
17
|
const performance_js_1 = require("./modules/performance.js");
|
|
18
18
|
const scroll_js_1 = require("./modules/scroll.js");
|
|
19
19
|
const viewport_js_1 = require("./modules/viewport.js");
|
|
20
|
-
const longtasks_js_1 = require("./modules/longtasks.js");
|
|
21
20
|
const cssrules_js_1 = require("./modules/cssrules.js");
|
|
22
21
|
const utils_js_1 = require("./utils.js");
|
|
23
22
|
const DOCS_SETUP = '/installation/setup-or';
|
|
@@ -104,7 +103,6 @@ class API {
|
|
|
104
103
|
(0, timing_js_1.default)(app, options);
|
|
105
104
|
(0, performance_js_1.default)(app, options);
|
|
106
105
|
(0, scroll_js_1.default)(app);
|
|
107
|
-
(0, longtasks_js_1.default)(app);
|
|
108
106
|
window.__OPENREPLAY__ = this;
|
|
109
107
|
if (options.autoResetOnWindowOpen) {
|
|
110
108
|
const wOpen = window.open;
|
|
@@ -129,7 +127,7 @@ class API {
|
|
|
129
127
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
130
128
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
131
129
|
req.send(JSON.stringify({
|
|
132
|
-
trackerVersion: '3.5.
|
|
130
|
+
trackerVersion: '3.5.5-beta.0',
|
|
133
131
|
projectKey: options.projectKey,
|
|
134
132
|
doNotTrack,
|
|
135
133
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -180,7 +178,7 @@ class API {
|
|
|
180
178
|
}
|
|
181
179
|
setUserID(id) {
|
|
182
180
|
if (typeof id === 'string' && this.app !== null) {
|
|
183
|
-
this.app.
|
|
181
|
+
this.app.session.setUserID(id);
|
|
184
182
|
}
|
|
185
183
|
}
|
|
186
184
|
userID(id) {
|
|
@@ -200,7 +198,7 @@ class API {
|
|
|
200
198
|
if (typeof key === 'string' &&
|
|
201
199
|
typeof value === 'string' &&
|
|
202
200
|
this.app !== null) {
|
|
203
|
-
this.app.
|
|
201
|
+
this.app.session.setMetadata(key, value);
|
|
204
202
|
}
|
|
205
203
|
}
|
|
206
204
|
metadata(key, value) {
|
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
|
|
12
|
+
while (context !== window) {
|
|
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 || window;
|
|
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.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import Nodes from "./nodes.js";
|
|
|
3
3
|
import Sanitizer from "./sanitizer.js";
|
|
4
4
|
import Ticker from "./ticker.js";
|
|
5
5
|
import Logger from "./logger.js";
|
|
6
|
+
import Session from "./session.js";
|
|
6
7
|
import type { Options as ObserverOptions } from "./observer/top_observer.js";
|
|
7
8
|
import type { Options as SanitizerOptions } from "./sanitizer.js";
|
|
8
9
|
import type { Options as LoggerOptions } from "./logger.js";
|
|
@@ -44,6 +45,7 @@ export default class App {
|
|
|
44
45
|
readonly sanitizer: Sanitizer;
|
|
45
46
|
readonly debug: Logger;
|
|
46
47
|
readonly notify: Logger;
|
|
48
|
+
readonly session: Session;
|
|
47
49
|
private readonly messages;
|
|
48
50
|
private readonly observer;
|
|
49
51
|
private readonly startCallbacks;
|
|
@@ -51,9 +53,6 @@ export default class App {
|
|
|
51
53
|
private readonly commitCallbacks;
|
|
52
54
|
private readonly options;
|
|
53
55
|
private readonly revID;
|
|
54
|
-
private _sessionID;
|
|
55
|
-
private _userID;
|
|
56
|
-
private _metadata;
|
|
57
56
|
private activityState;
|
|
58
57
|
private version;
|
|
59
58
|
private readonly worker?;
|
|
@@ -62,8 +61,8 @@ export default class App {
|
|
|
62
61
|
private readonly preStartMessages;
|
|
63
62
|
send(message: Message, urgent?: boolean): void;
|
|
64
63
|
private commit;
|
|
65
|
-
attachCommitCallback(cb: CommitCallback): void;
|
|
66
64
|
safe<T extends (...args: any[]) => void>(fn: T): T;
|
|
65
|
+
attachCommitCallback(cb: CommitCallback): void;
|
|
67
66
|
attachStartCallback(cb: Callback): void;
|
|
68
67
|
attachStopCallback(cb: Callback): void;
|
|
69
68
|
attachEventListener(target: EventTarget, type: string, listener: EventListener, useSafe?: boolean, useCapture?: boolean): void;
|
|
@@ -75,10 +74,10 @@ export default class App {
|
|
|
75
74
|
revID: string;
|
|
76
75
|
timestamp: number;
|
|
77
76
|
trackerVersion: string;
|
|
78
|
-
userID: string | null;
|
|
79
77
|
isSnippet: boolean;
|
|
80
78
|
sessionID: string | null;
|
|
81
79
|
metadata: Record<string, string>;
|
|
80
|
+
userID: string | null;
|
|
82
81
|
};
|
|
83
82
|
getSessionToken(): string | undefined;
|
|
84
83
|
getSessionID(): string | undefined;
|
package/lib/app/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { timestamp } from "../utils.js";
|
|
2
|
-
import { Timestamp
|
|
2
|
+
import { Timestamp } from "../messages/index.js";
|
|
3
3
|
import Nodes from "./nodes.js";
|
|
4
4
|
import Observer from "./observer/top_observer.js";
|
|
5
5
|
import Sanitizer from "./sanitizer.js";
|
|
6
6
|
import Ticker from "./ticker.js";
|
|
7
7
|
import Logger, { LogLevel } from "./logger.js";
|
|
8
|
+
import Session from "./session.js";
|
|
8
9
|
import { deviceMemory, jsHeapSizeLimit } from "../modules/performance.js";
|
|
9
10
|
var ActivityState;
|
|
10
11
|
(function (ActivityState) {
|
|
@@ -24,11 +25,8 @@ export default class App {
|
|
|
24
25
|
this.startCallbacks = [];
|
|
25
26
|
this.stopCallbacks = [];
|
|
26
27
|
this.commitCallbacks = [];
|
|
27
|
-
this._sessionID = null;
|
|
28
|
-
this._userID = null;
|
|
29
|
-
this._metadata = {};
|
|
30
28
|
this.activityState = ActivityState.NotActive;
|
|
31
|
-
this.version = '3.5.
|
|
29
|
+
this.version = '3.5.5-beta.0'; // TODO: version compatability check inside each plugin.
|
|
32
30
|
this.preStartMessages = [];
|
|
33
31
|
this.projectKey = projectKey;
|
|
34
32
|
this.options = Object.assign({
|
|
@@ -55,25 +53,20 @@ export default class App {
|
|
|
55
53
|
this.ticker.attach(() => this.commit());
|
|
56
54
|
this.debug = new Logger(this.options.__debug__);
|
|
57
55
|
this.notify = new Logger(this.options.verbose ? LogLevel.Warnings : LogLevel.Silent);
|
|
56
|
+
this.session = new Session(this);
|
|
58
57
|
try {
|
|
59
58
|
this.worker = new Worker(URL.createObjectURL(new Blob([`"use strict";function t(t){function s(...s){return new t(...s)}return s.prototype=t.prototype,s}const s=new Map;const i=t(class{constructor(t,s,i){this.pageNo=t,this.firstIndex=s,this.timestamp=i,this._id=80}encode(t){return t.uint(80)&&t.uint(this.pageNo)&&t.uint(this.firstIndex)&&t.int(this.timestamp)}});s.set(80,i);const n=t(class{constructor(t){this.timestamp=t,this._id=0}encode(t){return t.uint(0)&&t.uint(this.timestamp)}});s.set(0,n);const e=t(class{constructor(t,s,i){this.url=t,this.referrer=s,this.navigationStart=i,this._id=4}encode(t){return t.uint(4)&&t.string(this.url)&&t.string(this.referrer)&&t.uint(this.navigationStart)}});s.set(4,e);const r=t(class{constructor(t,s){this.width=t,this.height=s,this._id=5}encode(t){return t.uint(5)&&t.uint(this.width)&&t.uint(this.height)}});s.set(5,r);const o=t(class{constructor(t,s){this.x=t,this.y=s,this._id=6}encode(t){return t.uint(6)&&t.int(this.x)&&t.int(this.y)}});s.set(6,o);const h=t(class{constructor(){this._id=7}encode(t){return t.uint(7)}});s.set(7,h);const c=t(class{constructor(t,s,i,n,e){this.id=t,this.parentID=s,this.index=i,this.tag=n,this.svg=e,this._id=8}encode(t){return t.uint(8)&&t.uint(this.id)&&t.uint(this.parentID)&&t.uint(this.index)&&t.string(this.tag)&&t.boolean(this.svg)}});s.set(8,c);const u=t(class{constructor(t,s,i){this.id=t,this.parentID=s,this.index=i,this._id=9}encode(t){return t.uint(9)&&t.uint(this.id)&&t.uint(this.parentID)&&t.uint(this.index)}});s.set(9,u);const a=t(class{constructor(t,s,i){this.id=t,this.parentID=s,this.index=i,this._id=10}encode(t){return t.uint(10)&&t.uint(this.id)&&t.uint(this.parentID)&&t.uint(this.index)}});s.set(10,a);const d=t(class{constructor(t){this.id=t,this._id=11}encode(t){return t.uint(11)&&t.uint(this.id)}});s.set(11,d);const l=t(class{constructor(t,s,i){this.id=t,this.name=s,this.value=i,this._id=12}encode(t){return t.uint(12)&&t.uint(this.id)&&t.string(this.name)&&t.string(this.value)}});s.set(12,l);const g=t(class{constructor(t,s){this.id=t,this.name=s,this._id=13}encode(t){return t.uint(13)&&t.uint(this.id)&&t.string(this.name)}});s.set(13,g);const f=t(class{constructor(t,s){this.id=t,this.data=s,this._id=14}encode(t){return t.uint(14)&&t.uint(this.id)&&t.string(this.data)}});s.set(14,f);const p=t(class{constructor(t,s,i){this.id=t,this.x=s,this.y=i,this._id=16}encode(t){return t.uint(16)&&t.uint(this.id)&&t.int(this.x)&&t.int(this.y)}});s.set(16,p);const m=t(class{constructor(t,s){this.id=t,this.label=s,this._id=17}encode(t){return t.uint(17)&&t.uint(this.id)&&t.string(this.label)}});s.set(17,m);const _=t(class{constructor(t,s,i){this.id=t,this.value=s,this.mask=i,this._id=18}encode(t){return t.uint(18)&&t.uint(this.id)&&t.string(this.value)&&t.int(this.mask)}});s.set(18,_);const y=t(class{constructor(t,s){this.id=t,this.checked=s,this._id=19}encode(t){return t.uint(19)&&t.uint(this.id)&&t.boolean(this.checked)}});s.set(19,y);const v=t(class{constructor(t,s){this.x=t,this.y=s,this._id=20}encode(t){return t.uint(20)&&t.uint(this.x)&&t.uint(this.y)}});s.set(20,v);const S=t(class{constructor(t,s){this.level=t,this.value=s,this._id=22}encode(t){return t.uint(22)&&t.string(this.level)&&t.string(this.value)}});s.set(22,S);const b=t(class{constructor(t,s,i,n,e,r,o,h,c){this.requestStart=t,this.responseStart=s,this.responseEnd=i,this.domContentLoadedEventStart=n,this.domContentLoadedEventEnd=e,this.loadEventStart=r,this.loadEventEnd=o,this.firstPaint=h,this.firstContentfulPaint=c,this._id=23}encode(t){return t.uint(23)&&t.uint(this.requestStart)&&t.uint(this.responseStart)&&t.uint(this.responseEnd)&&t.uint(this.domContentLoadedEventStart)&&t.uint(this.domContentLoadedEventEnd)&&t.uint(this.loadEventStart)&&t.uint(this.loadEventEnd)&&t.uint(this.firstPaint)&&t.uint(this.firstContentfulPaint)}});s.set(23,b);const x=t(class{constructor(t,s,i){this.speedIndex=t,this.visuallyComplete=s,this.timeToInteractive=i,this._id=24}encode(t){return t.uint(24)&&t.uint(this.speedIndex)&&t.uint(this.visuallyComplete)&&t.uint(this.timeToInteractive)}});s.set(24,x);const E=t(class{constructor(t,s,i){this.name=t,this.message=s,this.payload=i,this._id=25}encode(t){return t.uint(25)&&t.string(this.name)&&t.string(this.message)&&t.string(this.payload)}});s.set(25,E);const k=t(class{constructor(t,s){this.name=t,this.payload=s,this._id=27}encode(t){return t.uint(27)&&t.string(this.name)&&t.string(this.payload)}});s.set(27,k);const I=t(class{constructor(t){this.id=t,this._id=28}encode(t){return t.uint(28)&&t.string(this.id)}});s.set(28,I);const z=t(class{constructor(t){this.id=t,this._id=29}encode(t){return t.uint(29)&&t.string(this.id)}});s.set(29,z);const w=t(class{constructor(t,s){this.key=t,this.value=s,this._id=30}encode(t){return t.uint(30)&&t.string(this.key)&&t.string(this.value)}});s.set(30,w);const T=t(class{constructor(t,s,i){this.id=t,this.rule=s,this.index=i,this._id=37}encode(t){return t.uint(37)&&t.uint(this.id)&&t.string(this.rule)&&t.uint(this.index)}});s.set(37,T);const L=t(class{constructor(t,s){this.id=t,this.index=s,this._id=38}encode(t){return t.uint(38)&&t.uint(this.id)&&t.uint(this.index)}});s.set(38,L);const A=t(class{constructor(t,s,i,n,e,r,o){this.method=t,this.url=s,this.request=i,this.response=n,this.status=e,this.timestamp=r,this.duration=o,this._id=39}encode(t){return t.uint(39)&&t.string(this.method)&&t.string(this.url)&&t.string(this.request)&&t.string(this.response)&&t.uint(this.status)&&t.uint(this.timestamp)&&t.uint(this.duration)}});s.set(39,A);const C=t(class{constructor(t,s,i,n){this.name=t,this.duration=s,this.args=i,this.result=n,this._id=40}encode(t){return t.uint(40)&&t.string(this.name)&&t.uint(this.duration)&&t.string(this.args)&&t.string(this.result)}});s.set(40,C);const M=t(class{constructor(t,s){this.key=t,this.value=s,this._id=41}encode(t){return t.uint(41)&&t.string(this.key)&&t.string(this.value)}});s.set(41,M);const R=t(class{constructor(t){this.type=t,this._id=42}encode(t){return t.uint(42)&&t.string(this.type)}});s.set(42,R);const N=t(class{constructor(t,s,i){this.action=t,this.state=s,this.duration=i,this._id=44}encode(t){return t.uint(44)&&t.string(this.action)&&t.string(this.state)&&t.uint(this.duration)}});s.set(44,N);const D=t(class{constructor(t,s){this.mutation=t,this.state=s,this._id=45}encode(t){return t.uint(45)&&t.string(this.mutation)&&t.string(this.state)}});s.set(45,D);const U=t(class{constructor(t,s){this.type=t,this.payload=s,this._id=46}encode(t){return t.uint(46)&&t.string(this.type)&&t.string(this.payload)}});s.set(46,U);const O=t(class{constructor(t,s,i){this.action=t,this.state=s,this.duration=i,this._id=47}encode(t){return t.uint(47)&&t.string(this.action)&&t.string(this.state)&&t.uint(this.duration)}});s.set(47,O);const q=t(class{constructor(t,s,i,n){this.operationKind=t,this.operationName=s,this.variables=i,this.response=n,this._id=48}encode(t){return t.uint(48)&&t.string(this.operationKind)&&t.string(this.operationName)&&t.string(this.variables)&&t.string(this.response)}});s.set(48,q);const H=t(class{constructor(t,s,i,n){this.frames=t,this.ticks=s,this.totalJSHeapSize=i,this.usedJSHeapSize=n,this._id=49}encode(t){return t.uint(49)&&t.int(this.frames)&&t.int(this.ticks)&&t.uint(this.totalJSHeapSize)&&t.uint(this.usedJSHeapSize)}});s.set(49,H);const P=t(class{constructor(t,s,i,n,e,r,o,h){this.timestamp=t,this.duration=s,this.ttfb=i,this.headerSize=n,this.encodedBodySize=e,this.decodedBodySize=r,this.url=o,this.initiator=h,this._id=53}encode(t){return t.uint(53)&&t.uint(this.timestamp)&&t.uint(this.duration)&&t.uint(this.ttfb)&&t.uint(this.headerSize)&&t.uint(this.encodedBodySize)&&t.uint(this.decodedBodySize)&&t.string(this.url)&&t.string(this.initiator)}});s.set(53,P);const B=t(class{constructor(t,s){this.downlink=t,this.type=s,this._id=54}encode(t){return t.uint(54)&&t.uint(this.downlink)&&t.string(this.type)}});s.set(54,B);const J=t(class{constructor(t){this.hidden=t,this._id=55}encode(t){return t.uint(55)&&t.boolean(this.hidden)}});s.set(55,J);const j=t(class{constructor(t,s,i,n,e,r,o){this.timestamp=t,this.duration=s,this.context=i,this.containerType=n,this.containerSrc=e,this.containerId=r,this.containerName=o,this._id=59}encode(t){return t.uint(59)&&t.uint(this.timestamp)&&t.uint(this.duration)&&t.uint(this.context)&&t.uint(this.containerType)&&t.string(this.containerSrc)&&t.string(this.containerId)&&t.string(this.containerName)}});s.set(59,j);const G=t(class{constructor(t,s,i,n){this.id=t,this.name=s,this.value=i,this.baseURL=n,this._id=60}encode(t){return t.uint(60)&&t.uint(this.id)&&t.string(this.name)&&t.string(this.value)&&t.string(this.baseURL)}});s.set(60,G);const K=t(class{constructor(t,s,i){this.id=t,this.data=s,this.baseURL=i,this._id=61}encode(t){return t.uint(61)&&t.uint(this.id)&&t.string(this.data)&&t.string(this.baseURL)}});s.set(61,K);const X=t(class{constructor(t,s){this.type=t,this.value=s,this._id=63}encode(t){return t.uint(63)&&t.string(this.type)&&t.string(this.value)}});s.set(63,X);const F=t(class{constructor(t,s){this.name=t,this.payload=s,this._id=64}encode(t){return t.uint(64)&&t.string(this.name)&&t.string(this.payload)}});s.set(64,F);const Q=t(class{constructor(){this._id=65}encode(t){return t.uint(65)}});s.set(65,Q);const V=t(class{constructor(t,s,i,n){this.id=t,this.rule=s,this.index=i,this.baseURL=n,this._id=67}encode(t){return t.uint(67)&&t.uint(this.id)&&t.string(this.rule)&&t.uint(this.index)&&t.string(this.baseURL)}});s.set(67,V);const W=t(class{constructor(t,s,i,n){this.id=t,this.hesitationTime=s,this.label=i,this.selector=n,this._id=69}encode(t){return t.uint(69)&&t.uint(this.id)&&t.uint(this.hesitationTime)&&t.string(this.label)&&t.string(this.selector)}});s.set(69,W);const Y=t(class{constructor(t,s){this.frameID=t,this.id=s,this._id=70}encode(t){return t.uint(70)&&t.uint(this.frameID)&&t.uint(this.id)}});s.set(70,Y);const Z="function"==typeof TextEncoder?new TextEncoder:{encode(t){const s=t.length,i=new Uint8Array(3*s);let n=-1;for(var e=0,r=0,o=0;o!==s;){if(e=t.charCodeAt(o),o+=1,e>=55296&&e<=56319){if(o===s){i[n+=1]=239,i[n+=1]=191,i[n+=1]=189;break}if(!((r=t.charCodeAt(o))>=56320&&r<=57343)){i[n+=1]=239,i[n+=1]=191,i[n+=1]=189;continue}if(o+=1,(e=1024*(e-55296)+r-56320+65536)>65535){i[n+=1]=240|e>>>18,i[n+=1]=128|e>>>12&63,i[n+=1]=128|e>>>6&63,i[n+=1]=128|63&e;continue}}e<=127?i[n+=1]=0|e:e<=2047?(i[n+=1]=192|e>>>6,i[n+=1]=128|63&e):(i[n+=1]=224|e>>>12,i[n+=1]=128|e>>>6&63,i[n+=1]=128|63&e)}return i.subarray(0,n+1)}};class tt{constructor(t){this.size=t,this.offset=0,this.checkpointOffset=0,this.data=new Uint8Array(t)}checkpoint(){this.checkpointOffset=this.offset}isEmpty(){return 0===this.offset}boolean(t){return this.data[this.offset++]=+t,this.offset<=this.size}uint(t){for((t<0||t>Number.MAX_SAFE_INTEGER)&&(t=0);t>=128;)this.data[this.offset++]=t%256|128,t=Math.floor(t/128);return this.data[this.offset++]=t,this.offset<=this.size}int(t){return t=Math.round(t),this.uint(t>=0?2*t:-2*t-1)}string(t){const s=Z.encode(t),i=s.byteLength;return!(!this.uint(i)||this.offset+i>this.size)&&(this.data.set(s,this.offset),this.offset+=i,!0)}reset(){this.offset=0,this.checkpointOffset=0}flush(){const t=this.data.slice(0,this.checkpointOffset);return this.reset(),t}}let st=1e6,it=2e5,nt=new tt(it),et="",rt="",ot=0,ht=0,ct=0,ut=0,at=!0;function dt(){return new i(ot,ut,ht).encode(nt)}let lt=null;const gt=[];let ft,pt=!1,mt=0,_t=8e3,yt=10;function vt(){if(at||""===rt||""===et)return;const t=nt.flush();pt?gt.push(t):(pt=!0,function t(s){const i=new XMLHttpRequest;i.open("POST",et+"/v1/web/i",!1),i.setRequestHeader("Authorization","Bearer "+rt),i.onreadystatechange=function(){if(4===this.readyState){if(0==this.status)return;if(this.status>=400)return pt=!1,St(),gt.length=0,401===this.status?void self.postMessage("restart"):void self.postMessage(null);const s=gt.shift();s?t(s):pt=!1}},i.onerror=function(i){if(mt>=yt)return St(),void self.postMessage(null);mt++,setTimeout(()=>t(s),_t)},i.send(s.buffer)}(t)),at=!0,dt()}function St(){et="",rt="",null!==lt&&(clearInterval(lt),lt=null),nt.reset()}self.onmessage=({data:t})=>{if(null!==t)return"stop"===t?(vt(),void St()):Array.isArray(t)?void t.forEach(t=>{const i=new(s.get(t._id));if(Object.assign(i,t),i instanceof n?ht=i.timestamp:i instanceof J&&(i.hidden?ft=setTimeout(()=>self.postMessage("restart"),18e5):clearTimeout(ft)),nt.checkpoint(),!i.encode(nt)&&(vt(),!i.encode(nt)))for(;!i.encode(nt);){if(it===st)return console.warn("OpenReplay: beacon size overflow."),nt.reset(),void dt();it=Math.min(2*it,st),nt=new tt(it),dt()}ut++,at=!1}):(et=t.ingestPoint||et,rt=t.token||rt,ot=t.pageNo||ot,ht=t.startTimestamp||ht,ct=t.timeAdjustment||ct,yt=t.connAttemptCount||yt,_t=t.connAttemptGap||_t,st=t.beaconSizeLimit||st,it=Math.min(st,t.beaconSize||it),nt.isEmpty()&&dt(),void(null===lt&&(lt=setInterval(vt,1e4))));vt()};
|
|
60
59
|
`], { type: 'text/javascript' })));
|
|
61
60
|
this.worker.onerror = e => {
|
|
62
61
|
this._debug("webworker_error", e);
|
|
63
62
|
};
|
|
64
|
-
let lastTs = timestamp();
|
|
65
|
-
let fileno = 0;
|
|
66
63
|
this.worker.onmessage = ({ data }) => {
|
|
67
64
|
if (data === null) {
|
|
68
65
|
this.stop();
|
|
69
66
|
}
|
|
70
67
|
else if (data === "restart") {
|
|
71
68
|
this.stop();
|
|
72
|
-
this.start({
|
|
73
|
-
forceNew: true,
|
|
74
|
-
userID: this._userID || undefined,
|
|
75
|
-
metadata: this._metadata || undefined,
|
|
76
|
-
});
|
|
69
|
+
this.start({ forceNew: true });
|
|
77
70
|
}
|
|
78
71
|
};
|
|
79
72
|
const alertWorker = () => {
|
|
@@ -127,9 +120,6 @@ export default class App {
|
|
|
127
120
|
this.messages.length = 0;
|
|
128
121
|
}
|
|
129
122
|
}
|
|
130
|
-
attachCommitCallback(cb) {
|
|
131
|
-
this.commitCallbacks.push(cb);
|
|
132
|
-
}
|
|
133
123
|
safe(fn) {
|
|
134
124
|
const app = this;
|
|
135
125
|
return function (...args) {
|
|
@@ -145,6 +135,9 @@ export default class App {
|
|
|
145
135
|
}
|
|
146
136
|
}; // TODO: correct typing
|
|
147
137
|
}
|
|
138
|
+
attachCommitCallback(cb) {
|
|
139
|
+
this.commitCallbacks.push(cb);
|
|
140
|
+
}
|
|
148
141
|
attachStartCallback(cb) {
|
|
149
142
|
this.startCallbacks.push(cb);
|
|
150
143
|
}
|
|
@@ -175,12 +168,11 @@ export default class App {
|
|
|
175
168
|
revID: this.revID,
|
|
176
169
|
timestamp: timestamp(),
|
|
177
170
|
trackerVersion: this.version,
|
|
178
|
-
userID: this._userID,
|
|
179
171
|
isSnippet: this.options.__is_snippet,
|
|
180
172
|
};
|
|
181
173
|
}
|
|
182
174
|
getSessionInfo() {
|
|
183
|
-
return Object.assign({
|
|
175
|
+
return Object.assign(Object.assign({}, this.session.getInfo()), this.getStartInfo());
|
|
184
176
|
}
|
|
185
177
|
getSessionToken() {
|
|
186
178
|
const token = sessionStorage.getItem(this.options.session_token_key);
|
|
@@ -189,7 +181,7 @@ export default class App {
|
|
|
189
181
|
}
|
|
190
182
|
}
|
|
191
183
|
getSessionID() {
|
|
192
|
-
return this.
|
|
184
|
+
return this.session.getInfo().sessionID || undefined;
|
|
193
185
|
}
|
|
194
186
|
getHost() {
|
|
195
187
|
return new URL(this.options.ingestPoint).hostname;
|
|
@@ -246,8 +238,6 @@ export default class App {
|
|
|
246
238
|
pageNo++;
|
|
247
239
|
}
|
|
248
240
|
sessionStorage.setItem(this.options.session_pageno_key, pageNo.toString());
|
|
249
|
-
this._userID = startOpts.userID || null;
|
|
250
|
-
this._metadata = startOpts.metadata || {}; // TODO: update both dynamically on corresponding messages
|
|
251
241
|
const startInfo = this.getStartInfo();
|
|
252
242
|
const messageData = {
|
|
253
243
|
ingestPoint: this.options.ingestPoint,
|
|
@@ -264,7 +254,7 @@ export default class App {
|
|
|
264
254
|
headers: {
|
|
265
255
|
'Content-Type': 'application/json',
|
|
266
256
|
},
|
|
267
|
-
body: JSON.stringify(Object.assign(Object.assign({}, startInfo), { token: sessionStorage.getItem(this.options.session_token_key), deviceMemory,
|
|
257
|
+
body: JSON.stringify(Object.assign(Object.assign({}, startInfo), { userID: startOpts.userID || this.session.getInfo().userID, token: sessionStorage.getItem(this.options.session_token_key), deviceMemory,
|
|
268
258
|
jsHeapSizeLimit, reset: startOpts.forceNew || sReset !== null })),
|
|
269
259
|
})
|
|
270
260
|
.then(r => {
|
|
@@ -289,15 +279,12 @@ export default class App {
|
|
|
289
279
|
}
|
|
290
280
|
sessionStorage.setItem(this.options.session_token_key, token);
|
|
291
281
|
localStorage.setItem(this.options.local_uuid_key, userUUID);
|
|
292
|
-
|
|
293
|
-
this._sessionID = sessionID;
|
|
294
|
-
}
|
|
282
|
+
this.session.update(Object.assign({ sessionID }, startOpts));
|
|
295
283
|
this.activityState = ActivityState.Active;
|
|
296
284
|
this.worker.postMessage({ token, beaconSizeLimit });
|
|
297
285
|
this.startCallbacks.forEach((cb) => cb());
|
|
298
286
|
this.observer.observe();
|
|
299
287
|
this.ticker.start();
|
|
300
|
-
Object.entries(this._metadata).forEach(([key, value]) => this.send(new Metadata(key, value)));
|
|
301
288
|
this.notify.log("OpenReplay tracking started.");
|
|
302
289
|
// TODO: get rid of onStart
|
|
303
290
|
const onStartInfo = { sessionToken: token, userUUID, sessionID };
|
package/lib/app/logger.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export declare const LogLevel: {
|
|
2
|
-
readonly Verbose:
|
|
3
|
-
readonly
|
|
2
|
+
readonly Verbose: 5;
|
|
3
|
+
readonly Log: 4;
|
|
4
4
|
readonly Warnings: 3;
|
|
5
|
-
readonly
|
|
5
|
+
readonly Errors: 2;
|
|
6
6
|
readonly Silent: 0;
|
|
7
7
|
};
|
|
8
8
|
declare type LogLevel = typeof LogLevel[keyof typeof LogLevel];
|
|
@@ -18,7 +18,6 @@ interface _Options {
|
|
|
18
18
|
export declare type Options = true | _Options | LogLevel;
|
|
19
19
|
export default class Logger {
|
|
20
20
|
private readonly options;
|
|
21
|
-
private readonly opts;
|
|
22
21
|
constructor(options?: Options);
|
|
23
22
|
log(...args: any): void;
|
|
24
23
|
warn(...args: any): void;
|
package/lib/app/logger.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const LogLevel = {
|
|
2
|
-
Verbose:
|
|
3
|
-
|
|
2
|
+
Verbose: 5,
|
|
3
|
+
Log: 4,
|
|
4
4
|
Warnings: 3,
|
|
5
|
-
|
|
5
|
+
Errors: 2,
|
|
6
6
|
Silent: 0,
|
|
7
7
|
};
|
|
8
8
|
function IsCustomLevel(l) {
|
|
@@ -10,29 +10,28 @@ function IsCustomLevel(l) {
|
|
|
10
10
|
}
|
|
11
11
|
export default class Logger {
|
|
12
12
|
constructor(options = LogLevel.Silent) {
|
|
13
|
-
this.options = options
|
|
14
|
-
this.opts = options === true
|
|
13
|
+
this.options = options === true
|
|
15
14
|
? { level: LogLevel.Verbose }
|
|
16
15
|
: typeof options === "number" ? { level: options } : options;
|
|
17
16
|
}
|
|
18
17
|
log(...args) {
|
|
19
|
-
if (IsCustomLevel(this.
|
|
20
|
-
? this.
|
|
21
|
-
: this.
|
|
18
|
+
if (IsCustomLevel(this.options.level)
|
|
19
|
+
? this.options.level.log
|
|
20
|
+
: this.options.level >= LogLevel.Log) {
|
|
22
21
|
console.log(...args);
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
warn(...args) {
|
|
26
|
-
if (IsCustomLevel(this.
|
|
27
|
-
? this.
|
|
28
|
-
: this.
|
|
25
|
+
if (IsCustomLevel(this.options.level)
|
|
26
|
+
? this.options.level.warn
|
|
27
|
+
: this.options.level >= LogLevel.Warnings) {
|
|
29
28
|
console.warn(...args);
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
error(...args) {
|
|
33
|
-
if (IsCustomLevel(this.
|
|
34
|
-
? this.
|
|
35
|
-
: this.
|
|
32
|
+
if (IsCustomLevel(this.options.level)
|
|
33
|
+
? this.options.level.error
|
|
34
|
+
: this.options.level >= LogLevel.Errors) {
|
|
36
35
|
console.error(...args);
|
|
37
36
|
}
|
|
38
37
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import App from "./index.js";
|
|
2
|
+
interface SessionInfo {
|
|
3
|
+
sessionID: string | null;
|
|
4
|
+
metadata: Record<string, string>;
|
|
5
|
+
userID: string | null;
|
|
6
|
+
}
|
|
7
|
+
declare type OnUpdateCallback = (i: Partial<SessionInfo>) => void;
|
|
8
|
+
export default class Session {
|
|
9
|
+
private app;
|
|
10
|
+
private metadata;
|
|
11
|
+
private userID;
|
|
12
|
+
private sessionID;
|
|
13
|
+
private activityState;
|
|
14
|
+
private callbacks;
|
|
15
|
+
constructor(app: App);
|
|
16
|
+
attachUpdateCallback(cb: OnUpdateCallback): void;
|
|
17
|
+
private handleUpdate;
|
|
18
|
+
update({ userID, metadata, sessionID }: Partial<SessionInfo>): void;
|
|
19
|
+
private _setMetadata;
|
|
20
|
+
private _setUserID;
|
|
21
|
+
setMetadata(key: string, value: string): void;
|
|
22
|
+
setUserID(userID: string): void;
|
|
23
|
+
getInfo(): SessionInfo;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { UserID, Metadata } from "../messages/index.js";
|
|
2
|
+
var ActivityState;
|
|
3
|
+
(function (ActivityState) {
|
|
4
|
+
ActivityState[ActivityState["NotActive"] = 0] = "NotActive";
|
|
5
|
+
ActivityState[ActivityState["Starting"] = 1] = "Starting";
|
|
6
|
+
ActivityState[ActivityState["Active"] = 2] = "Active";
|
|
7
|
+
})(ActivityState || (ActivityState = {}));
|
|
8
|
+
export default class Session {
|
|
9
|
+
constructor(app) {
|
|
10
|
+
this.app = app;
|
|
11
|
+
this.metadata = {};
|
|
12
|
+
this.userID = null;
|
|
13
|
+
this.sessionID = null;
|
|
14
|
+
this.activityState = ActivityState.NotActive;
|
|
15
|
+
this.callbacks = [];
|
|
16
|
+
}
|
|
17
|
+
attachUpdateCallback(cb) {
|
|
18
|
+
this.callbacks.push(cb);
|
|
19
|
+
}
|
|
20
|
+
handleUpdate() {
|
|
21
|
+
const sessInfo = this.getInfo();
|
|
22
|
+
if (sessInfo.userID == null) {
|
|
23
|
+
delete sessInfo.userID;
|
|
24
|
+
}
|
|
25
|
+
if (sessInfo.sessionID == null) {
|
|
26
|
+
delete sessInfo.sessionID;
|
|
27
|
+
}
|
|
28
|
+
this.callbacks.forEach(cb => cb(sessInfo));
|
|
29
|
+
}
|
|
30
|
+
update({ userID, metadata, sessionID }) {
|
|
31
|
+
if (userID != null) { // TODO clear nullable/undefinable types
|
|
32
|
+
this._setUserID(userID);
|
|
33
|
+
}
|
|
34
|
+
if (metadata !== undefined) {
|
|
35
|
+
Object.entries(metadata).forEach(kv => this._setMetadata(...kv));
|
|
36
|
+
}
|
|
37
|
+
if (sessionID !== undefined) {
|
|
38
|
+
this.sessionID = sessionID;
|
|
39
|
+
}
|
|
40
|
+
this.handleUpdate();
|
|
41
|
+
}
|
|
42
|
+
_setMetadata(key, value) {
|
|
43
|
+
this.app.send(new Metadata(key, value));
|
|
44
|
+
this.metadata[key] = value;
|
|
45
|
+
}
|
|
46
|
+
_setUserID(userID) {
|
|
47
|
+
this.app.send(new UserID(userID));
|
|
48
|
+
this.userID = userID;
|
|
49
|
+
}
|
|
50
|
+
setMetadata(key, value) {
|
|
51
|
+
this._setMetadata(key, value);
|
|
52
|
+
this.handleUpdate();
|
|
53
|
+
}
|
|
54
|
+
setUserID(userID) {
|
|
55
|
+
this._setUserID(userID);
|
|
56
|
+
this.handleUpdate();
|
|
57
|
+
}
|
|
58
|
+
getInfo() {
|
|
59
|
+
return {
|
|
60
|
+
sessionID: this.sessionID,
|
|
61
|
+
metadata: this.metadata,
|
|
62
|
+
userID: this.userID,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import App, { DEFAULT_INGEST_POINT } from "./app/index.js";
|
|
2
2
|
export { default as App } from './app/index.js';
|
|
3
|
-
import {
|
|
3
|
+
import { UserAnonymousID, RawCustomEvent, CustomIssue } from "./messages/index.js";
|
|
4
4
|
import * as _Messages from "./messages/index.js";
|
|
5
5
|
export const Messages = _Messages;
|
|
6
6
|
import Connection from "./modules/connection.js";
|
|
@@ -13,7 +13,6 @@ import Timing from "./modules/timing.js";
|
|
|
13
13
|
import Performance from "./modules/performance.js";
|
|
14
14
|
import Scroll from "./modules/scroll.js";
|
|
15
15
|
import Viewport from "./modules/viewport.js";
|
|
16
|
-
import Longtasks from "./modules/longtasks.js";
|
|
17
16
|
import CSSRules from "./modules/cssrules.js";
|
|
18
17
|
import { IN_BROWSER, deprecationWarn, DOCS_HOST } from "./utils.js";
|
|
19
18
|
const DOCS_SETUP = '/installation/setup-or';
|
|
@@ -100,7 +99,6 @@ export default class API {
|
|
|
100
99
|
Timing(app, options);
|
|
101
100
|
Performance(app, options);
|
|
102
101
|
Scroll(app);
|
|
103
|
-
Longtasks(app);
|
|
104
102
|
window.__OPENREPLAY__ = this;
|
|
105
103
|
if (options.autoResetOnWindowOpen) {
|
|
106
104
|
const wOpen = window.open;
|
|
@@ -125,7 +123,7 @@ export default class API {
|
|
|
125
123
|
// no-cors issue only with text/plain or not-set Content-Type
|
|
126
124
|
// req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
|
127
125
|
req.send(JSON.stringify({
|
|
128
|
-
trackerVersion: '3.5.
|
|
126
|
+
trackerVersion: '3.5.5-beta.0',
|
|
129
127
|
projectKey: options.projectKey,
|
|
130
128
|
doNotTrack,
|
|
131
129
|
// TODO: add precise reason (an exact API missing)
|
|
@@ -176,7 +174,7 @@ export default class API {
|
|
|
176
174
|
}
|
|
177
175
|
setUserID(id) {
|
|
178
176
|
if (typeof id === 'string' && this.app !== null) {
|
|
179
|
-
this.app.
|
|
177
|
+
this.app.session.setUserID(id);
|
|
180
178
|
}
|
|
181
179
|
}
|
|
182
180
|
userID(id) {
|
|
@@ -196,7 +194,7 @@ export default class API {
|
|
|
196
194
|
if (typeof key === 'string' &&
|
|
197
195
|
typeof value === 'string' &&
|
|
198
196
|
this.app !== null) {
|
|
199
|
-
this.app.
|
|
197
|
+
this.app.session.setMetadata(key, value);
|
|
200
198
|
}
|
|
201
199
|
}
|
|
202
200
|
metadata(key, value) {
|