feat (tracker): 3.1.0 - updated app interface for the assist plugin & enable url-based cssRule

This commit is contained in:
ShiKhu 2021-07-02 17:29:03 +02:00
parent 9d16b1feae
commit 6d8d46fcd5
5 changed files with 66 additions and 25 deletions

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker",
"description": "The OpenReplay tracker main package",
"version": "3.0.5",
"version": "3.1.0",
"keywords": [
"logging",
"replay"

View file

@ -9,7 +9,7 @@ import { deviceMemory, jsHeapSizeLimit } from '../modules/performance';
import type { Options as ObserverOptions } from './observer';
import type { Options as WebworkerOptions, MessageData } from '../../webworker/types';
import type { Options as WebworkerOptions, WorkerMessageData } from '../../messages/webworker';
export type Options = {
revID: string;
@ -23,19 +23,22 @@ export type Options = {
} & ObserverOptions & WebworkerOptions;
type Callback = () => void;
type CommitCallback = (messages: Array<Message>) => void;
export const DEFAULT_INGEST_POINT = 'https://ingest.openreplay.com';
export default class App {
readonly nodes: Nodes;
readonly ticker: Ticker;
readonly projectKey: string;
private readonly messages: Array<Message> = [];
private readonly observer: Observer;
private readonly startCallbacks: Array<Callback>;
private readonly stopCallbacks: Array<Callback>;
private readonly startCallbacks: Array<Callback> = [];
private readonly stopCallbacks: Array<Callback> = [];
private readonly commitCallbacks: Array<CommitCallback> = [];
private readonly options: Options;
private readonly projectKey: string;
private readonly revID: string;
private _sessionID: string | null = null;
private isActive = false;
private version = 'TRACKER_VERSION';
private readonly worker?: Worker;
@ -67,8 +70,6 @@ export default class App {
this.observer = new Observer(this, this.options);
this.ticker = new Ticker(this);
this.ticker.attach(() => this.commit());
this.startCallbacks = [];
this.stopCallbacks = [];
try {
this.worker = new Worker(
URL.createObjectURL(
@ -94,8 +95,10 @@ export default class App {
this.worker.postMessage(null);
}
}
// TODO: keep better tactics, discard others (look https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon)
this.attachEventListener(window, 'beforeunload', alertWorker, false);
this.attachEventListener(document, 'mouseleave', alertWorker, false, false);
this.attachEventListener(document, 'visibilitychange', alertWorker, false);
} catch (e) { /* TODO: send report */}
}
send(message: Message, urgent = false): void {
@ -111,10 +114,16 @@ export default class App {
if (this.worker && this.messages.length) {
this.messages.unshift(new Timestamp(timestamp()));
this.worker.postMessage(this.messages);
this.commitCallbacks.forEach(cb => cb(this.messages));
this.messages.length = 0;
}
}
addCommitCallback(cb: CommitCallback): void {
this.commitCallbacks.push(cb)
}
safe<T extends (...args: any[]) => void>(fn: T): T {
const app = this;
return function (this: any, ...args: any) {
@ -161,9 +170,11 @@ export default class App {
return token;
}
}
// @Depricated; for the old fetch-plugin versions
sessionID(): string | undefined {
return this.getSessionToken();
getSessionID(): string | undefined {
return this._sessionID || undefined;
}
getHost(): string {
return new URL(this.options.ingestPoint).host;
}
isServiceURL(url: string): boolean {
@ -189,7 +200,7 @@ export default class App {
sessionStorage.setItem(this.options.session_pageno_key, pageNo.toString());
const startTimestamp = timestamp();
const messageData: MessageData = {
const messageData: WorkerMessageData = {
ingestPoint: this.options.ingestPoint,
pageNo,
startTimestamp,
@ -197,10 +208,6 @@ export default class App {
connAttemptGap: this.options.connAttemptGap,
}
this.worker.postMessage(messageData); // brings delay of 10th ms?
this.observer.observe();
this.startCallbacks.forEach((cb) => cb());
this.ticker.start();
window.fetch(this.options.ingestPoint + '/v1/web/start', {
method: 'POST',
headers: {
@ -227,21 +234,27 @@ export default class App {
}
})
.then(r => {
const { token, userUUID } = r;
const { token, userUUID, sessionID } = r;
if (typeof token !== 'string' ||
typeof userUUID !== 'string') {
throw new Error("Incorrect server responce");
}
sessionStorage.setItem(this.options.session_token_key, token);
localStorage.setItem(this.options.local_uuid_key, userUUID);
if (typeof sessionID === 'string') {
this._sessionID = sessionID;
}
if (!this.worker) {
throw new Error("Stranger things: no worker found after start request");
}
this.worker.postMessage({ token });
this.observer.observe();
this.startCallbacks.forEach((cb) => cb());
this.ticker.start();
log("OpenReplay tracking started.");
if (typeof this.options.onStart === 'function') {
this.options.onStart({ sessionToken: token, userUUID, sessionID: token /* back compat (depricated) */ });
this.options.onStart({ sessionToken: token, userUUID, sessionID });
}
})
.catch(e => {

View file

@ -64,12 +64,12 @@ function processOptions(obj: any): obj is Options {
export default class API {
private readonly app: App | null = null;
constructor(options: Options) {
constructor(private readonly options: Options) {
if (!IN_BROWSER || !processOptions(options)) {
return;
}
if (!options.__DISABLE_SECURE_MODE && location.protocol !== 'https:') {
console.error("OpenReplay: Your website must be publicly accessible and running on SSL in order for OpenReplay to properly capture and replay the user session.")
console.error("OpenReplay: Your website must be publicly accessible and running on SSL in order for OpenReplay to properly capture and replay the user session. You can disable this check by setting `__DISABLE_SECURE_MODE` option to `true` if you are testing in localhost. Keep in mind, that asset files on a local machine are not available to the outside world. This might affect tracking if you use css files.")
return;
}
const doNotTrack = options.respectDoNotTrack && (navigator.doNotTrack == '1' || window.doNotTrack == '1');
@ -114,8 +114,8 @@ export default class API {
}
}
use<T>(fn: (app: App | null) => T): T {
return fn(this.app);
use<T>(fn: (app: App | null, options?: Options) => T): T {
return fn(this.app, this.options);
}
isActive(): boolean {
@ -152,9 +152,15 @@ export default class API {
}
return this.app.getSessionToken();
}
getSessionID(): string | null | undefined {
if (this.app === null) {
return null;
}
return this.app.getSessionID();
}
sessionID(): string | null | undefined {
depricationWarn("'sessionID' method", "'getSessionToken' method", "/")
return this.getSessionToken();
depricationWarn("'sessionID' method", "'getSessionID' method", "/");
return this.getSessionID();
}
setUserID(id: string): void {

View file

@ -1,5 +1,6 @@
import App from '../app';
import { CSSInsertRule, CSSDeleteRule, TechnicalInfo } from '../../messages';
import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from '../../messages';
import { getBaseURI } from '../utils';
export default function(app: App | null) {
if (app === null) {
@ -13,7 +14,7 @@ export default function(app: App | null) {
const processOperation = app.safe(
(stylesheet: CSSStyleSheet, index: number, rule?: string) => {
const sendMessage = typeof rule === 'string'
? (nodeID: number) => app.send(new CSSInsertRule(nodeID, rule, index))
? (nodeID: number) => app.send(new CSSInsertRuleURLBased(nodeID, rule, index, getBaseURI()))
: (nodeID: number) => app.send(new CSSDeleteRule(nodeID, index));
// TODO: Extend messages to maintain nested rules (CSSGroupingRule prototype, as well as CSSKeyframesRule)
if (stylesheet.ownerNode == null) {

View file

@ -0,0 +1,21 @@
import Message from '../messages/message';
class MessageTransformer {
private urlRewriter?: URLRewriter
constructor() {
}
transform(m: Message): Message {
if (m instanceof SetNodeAttribute) {
if (m.name == "src" || m.name == "href") {
sendAssetForCache
}
}
}
}