feat(tracker): update message schema with BatchMetadata; separate message-related responsibilities; add message size
This commit is contained in:
parent
820994b55f
commit
64d481aa4c
31 changed files with 1104 additions and 1050 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Special one for Batch Meta. Message id could define the version
|
||||
# Depricated since tracker 3.6.0
|
||||
message 80, 'BatchMeta', :replayer => false do
|
||||
# Depricated since tracker 3.6.0 in favor of BatchMetadata
|
||||
message 80, 'BatchMeta', :js => false, :replayer => false do
|
||||
uint 'PageNo'
|
||||
uint 'FirstIndex'
|
||||
int 'Timestamp'
|
||||
|
|
@ -98,7 +98,6 @@ message 14, 'SetNodeData' do
|
|||
uint 'ID'
|
||||
string 'Data'
|
||||
end
|
||||
# Depricated starting from 5.5.11 in favor of SetStyleData
|
||||
message 15, 'SetCSSData', :js => false do
|
||||
uint 'ID'
|
||||
string 'Data'
|
||||
|
|
@ -400,10 +399,6 @@ message 64, 'CustomIssue', :replayer => false do
|
|||
string 'Name'
|
||||
string 'Payload'
|
||||
end
|
||||
# Since 5.6.6; only for websocket (might be probably replaced with ws.close())
|
||||
# Depricated
|
||||
message 65, 'PageClose', :replayer => false do
|
||||
end
|
||||
message 66, 'AssetCache', :replayer => false, :js => false do
|
||||
string 'URL'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,31 +1,15 @@
|
|||
// Auto-generated, do not edit
|
||||
import type { Writer, Message }from "./types.js";
|
||||
export default Message
|
||||
|
||||
function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
|
||||
Class: C & { new(...args: A): T }
|
||||
): C & ((...args: A) => T) {
|
||||
function _Class(...args: A) {
|
||||
return new Class(...args);
|
||||
}
|
||||
_Class.prototype = Class.prototype;
|
||||
return <C & ((...args: A) => T)>_Class;
|
||||
export enum Type {
|
||||
<%= $messages.select { |msg| msg.js }.map { |msg| "#{ msg.name } = #{ msg.id }," }.join "\n " %>
|
||||
}
|
||||
|
||||
export const classes: Map<number, Function> = new Map();
|
||||
|
||||
<% $messages.select { |msg| msg.js }.each do |msg| %>
|
||||
class _<%= msg.name %> implements Message {
|
||||
readonly _id: number = <%= msg.id %>;
|
||||
constructor(
|
||||
<%= msg.attributes.map { |attr| "public #{attr.name.first_lower}: #{attr.type_js}" }.join ",\n " %>
|
||||
) {}
|
||||
encode(writer: Writer): boolean {
|
||||
return writer.uint(<%= msg.id %>)<%= " &&" if msg.attributes.length() > 0 %>
|
||||
<%= msg.attributes.map { |attr| "writer.#{attr.type}(this.#{attr.name.first_lower})" }.join " &&\n " %>;
|
||||
}
|
||||
}
|
||||
export const <%= msg.name %> = bindNew(_<%= msg.name %>);
|
||||
classes.set(<%= msg.id %>, <%= msg.name %>);
|
||||
|
||||
export type <%= msg.name %> = [
|
||||
type: Type.<%= msg.name %>,
|
||||
<%= msg.attributes.map { |attr| "#{attr.name.first_lower}: #{attr.type_js}," }.join "\n " %>
|
||||
]
|
||||
<% end %>
|
||||
|
||||
type Message = <%= $messages.select { |msg| msg.js }.map { |msg| "#{msg.name}" }.join " | " %>
|
||||
export default Message
|
||||
|
|
|
|||
15
mobs/templates/tracker~tracker~src~main~app~messages.ts.erb
Normal file
15
mobs/templates/tracker~tracker~src~main~app~messages.ts.erb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Auto-generated, do not edit
|
||||
|
||||
import * as Messages from '../../common/messages.js'
|
||||
export { default } from '../../common/messages.js'
|
||||
|
||||
<% $messages.select { |msg| msg.js }.each do |msg| %>
|
||||
export function <%= msg.name %>(
|
||||
<%= msg.attributes.map { |attr| "#{attr.name.first_lower}: #{attr.type_js}," }.join "\n " %>
|
||||
): Messages.<%= msg.name %> {
|
||||
return [
|
||||
Messages.Type.<%= msg.name %>,
|
||||
<%= msg.attributes.map { |attr| "#{attr.name.first_lower}," }.join "\n " %>
|
||||
]
|
||||
}
|
||||
<% end %>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// Auto-generated, do not edit
|
||||
|
||||
import * as Messages from '../common/messages.js'
|
||||
import Message from '../common/messages.js'
|
||||
import PrimitiveEncoder from './PrimitiveEncoder.js'
|
||||
|
||||
|
||||
export default class MessageEncoder extends PrimitiveEncoder {
|
||||
encode(msg: Message): boolean {
|
||||
switch(msg[0]) {
|
||||
<% $messages.select { |msg| msg.js }.each do |msg| %>
|
||||
case Messages.Type.<%= msg.name %>:
|
||||
return <% if msg.attributes.size == 0 %> true <% else %> <%= msg.attributes.map.with_index { |attr, index| "this.#{attr.type}(msg[#{index+1}])" }.join " && " %> <% end %>
|
||||
break
|
||||
<% end %>
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import Message from './messages.js';
|
||||
|
||||
export interface Options {
|
||||
connAttemptCount?: number;
|
||||
connAttemptGap?: number;
|
||||
|
|
@ -8,6 +10,7 @@ type Start = {
|
|||
ingestPoint: string;
|
||||
pageNo: number;
|
||||
timestamp: number;
|
||||
url: string;
|
||||
} & Options;
|
||||
|
||||
type Auth = {
|
||||
|
|
@ -16,4 +19,4 @@ type Auth = {
|
|||
beaconSizeLimit?: number;
|
||||
};
|
||||
|
||||
export type WorkerMessageData = null | 'stop' | Start | Auth | Array<{ _id: number }>;
|
||||
export type WorkerMessageData = null | 'stop' | Start | Auth | Array<Message>;
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,10 +0,0 @@
|
|||
export interface Writer {
|
||||
uint(n: number): boolean
|
||||
int(n: number): boolean
|
||||
string(s: string): boolean
|
||||
boolean(b: boolean): boolean
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
encode(w: Writer): boolean;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type Message from '../../common/messages.js';
|
||||
import { Timestamp, Metadata, UserID } from '../../common/messages.js';
|
||||
import type Message from './messages.js';
|
||||
import { Timestamp, Metadata, UserID } from './messages.js';
|
||||
import { timestamp, deprecationWarn } from '../utils.js';
|
||||
import Nodes from './nodes.js';
|
||||
import Observer from './observer/top_observer.js';
|
||||
|
|
@ -13,7 +13,7 @@ import { deviceMemory, jsHeapSizeLimit } from '../modules/performance.js';
|
|||
import type { Options as ObserverOptions } from './observer/top_observer.js';
|
||||
import type { Options as SanitizerOptions } from './sanitizer.js';
|
||||
import type { Options as LoggerOptions } from './logger.js';
|
||||
import type { Options as WebworkerOptions, WorkerMessageData } from '../../common/webworker.js';
|
||||
import type { Options as WebworkerOptions, WorkerMessageData } from '../../common/interaction.js';
|
||||
|
||||
// TODO: Unify and clearly describe options logic
|
||||
export interface StartOptions {
|
||||
|
|
@ -133,10 +133,10 @@ export default class App {
|
|||
this.session.attachUpdateCallback(({ userID, metadata }) => {
|
||||
if (userID != null) {
|
||||
// TODO: nullable userID
|
||||
this.send(new UserID(userID));
|
||||
this.send(UserID(userID));
|
||||
}
|
||||
if (metadata != null) {
|
||||
Object.entries(metadata).forEach(([key, value]) => this.send(new Metadata(key, value)));
|
||||
Object.entries(metadata).forEach(([key, value]) => this.send(Metadata(key, value)));
|
||||
}
|
||||
});
|
||||
this.localStorage = this.options.localStorage;
|
||||
|
|
@ -206,7 +206,7 @@ export default class App {
|
|||
}
|
||||
private commit(): void {
|
||||
if (this.worker && this.messages.length) {
|
||||
this.messages.unshift(new Timestamp(timestamp()));
|
||||
this.messages.unshift(Timestamp(timestamp()));
|
||||
this.worker.postMessage(this.messages);
|
||||
this.commitCallbacks.forEach((cb) => cb(this.messages));
|
||||
this.messages.length = 0;
|
||||
|
|
@ -358,6 +358,7 @@ export default class App {
|
|||
pageNo,
|
||||
ingestPoint: this.options.ingestPoint,
|
||||
timestamp: startInfo.timestamp,
|
||||
url: document.URL,
|
||||
connAttemptCount: this.options.connAttemptCount,
|
||||
connAttemptGap: this.options.connAttemptGap,
|
||||
};
|
||||
|
|
|
|||
334
tracker/tracker/src/main/app/messages.ts
Normal file
334
tracker/tracker/src/main/app/messages.ts
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
// Auto-generated, do not edit
|
||||
|
||||
import * as Messages from '../../common/messages.js';
|
||||
export { default } from '../../common/messages.js';
|
||||
|
||||
export function BatchMetadata(
|
||||
version: number,
|
||||
pageNo: number,
|
||||
firstIndex: number,
|
||||
timestamp: number,
|
||||
location: string,
|
||||
): Messages.BatchMetadata {
|
||||
return [Messages.Type.BatchMetadata, version, pageNo, firstIndex, timestamp, location];
|
||||
}
|
||||
|
||||
export function PartitionedMessage(partNo: number, partTotal: number): Messages.PartitionedMessage {
|
||||
return [Messages.Type.PartitionedMessage, partNo, partTotal];
|
||||
}
|
||||
|
||||
export function Timestamp(timestamp: number): Messages.Timestamp {
|
||||
return [Messages.Type.Timestamp, timestamp];
|
||||
}
|
||||
|
||||
export function SetPageLocation(
|
||||
url: string,
|
||||
referrer: string,
|
||||
navigationStart: number,
|
||||
): Messages.SetPageLocation {
|
||||
return [Messages.Type.SetPageLocation, url, referrer, navigationStart];
|
||||
}
|
||||
|
||||
export function SetViewportSize(width: number, height: number): Messages.SetViewportSize {
|
||||
return [Messages.Type.SetViewportSize, width, height];
|
||||
}
|
||||
|
||||
export function SetViewportScroll(x: number, y: number): Messages.SetViewportScroll {
|
||||
return [Messages.Type.SetViewportScroll, x, y];
|
||||
}
|
||||
|
||||
export function CreateDocument(): Messages.CreateDocument {
|
||||
return [Messages.Type.CreateDocument];
|
||||
}
|
||||
|
||||
export function CreateElementNode(
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
tag: string,
|
||||
svg: boolean,
|
||||
): Messages.CreateElementNode {
|
||||
return [Messages.Type.CreateElementNode, id, parentID, index, tag, svg];
|
||||
}
|
||||
|
||||
export function CreateTextNode(
|
||||
id: number,
|
||||
parentID: number,
|
||||
index: number,
|
||||
): Messages.CreateTextNode {
|
||||
return [Messages.Type.CreateTextNode, id, parentID, index];
|
||||
}
|
||||
|
||||
export function MoveNode(id: number, parentID: number, index: number): Messages.MoveNode {
|
||||
return [Messages.Type.MoveNode, id, parentID, index];
|
||||
}
|
||||
|
||||
export function RemoveNode(id: number): Messages.RemoveNode {
|
||||
return [Messages.Type.RemoveNode, id];
|
||||
}
|
||||
|
||||
export function SetNodeAttribute(
|
||||
id: number,
|
||||
name: string,
|
||||
value: string,
|
||||
): Messages.SetNodeAttribute {
|
||||
return [Messages.Type.SetNodeAttribute, id, name, value];
|
||||
}
|
||||
|
||||
export function RemoveNodeAttribute(id: number, name: string): Messages.RemoveNodeAttribute {
|
||||
return [Messages.Type.RemoveNodeAttribute, id, name];
|
||||
}
|
||||
|
||||
export function SetNodeData(id: number, data: string): Messages.SetNodeData {
|
||||
return [Messages.Type.SetNodeData, id, data];
|
||||
}
|
||||
|
||||
export function SetNodeScroll(id: number, x: number, y: number): Messages.SetNodeScroll {
|
||||
return [Messages.Type.SetNodeScroll, id, x, y];
|
||||
}
|
||||
|
||||
export function SetInputTarget(id: number, label: string): Messages.SetInputTarget {
|
||||
return [Messages.Type.SetInputTarget, id, label];
|
||||
}
|
||||
|
||||
export function SetInputValue(id: number, value: string, mask: number): Messages.SetInputValue {
|
||||
return [Messages.Type.SetInputValue, id, value, mask];
|
||||
}
|
||||
|
||||
export function SetInputChecked(id: number, checked: boolean): Messages.SetInputChecked {
|
||||
return [Messages.Type.SetInputChecked, id, checked];
|
||||
}
|
||||
|
||||
export function MouseMove(x: number, y: number): Messages.MouseMove {
|
||||
return [Messages.Type.MouseMove, x, y];
|
||||
}
|
||||
|
||||
export function ConsoleLog(level: string, value: string): Messages.ConsoleLog {
|
||||
return [Messages.Type.ConsoleLog, level, value];
|
||||
}
|
||||
|
||||
export function PageLoadTiming(
|
||||
requestStart: number,
|
||||
responseStart: number,
|
||||
responseEnd: number,
|
||||
domContentLoadedEventStart: number,
|
||||
domContentLoadedEventEnd: number,
|
||||
loadEventStart: number,
|
||||
loadEventEnd: number,
|
||||
firstPaint: number,
|
||||
firstContentfulPaint: number,
|
||||
): Messages.PageLoadTiming {
|
||||
return [
|
||||
Messages.Type.PageLoadTiming,
|
||||
requestStart,
|
||||
responseStart,
|
||||
responseEnd,
|
||||
domContentLoadedEventStart,
|
||||
domContentLoadedEventEnd,
|
||||
loadEventStart,
|
||||
loadEventEnd,
|
||||
firstPaint,
|
||||
firstContentfulPaint,
|
||||
];
|
||||
}
|
||||
|
||||
export function PageRenderTiming(
|
||||
speedIndex: number,
|
||||
visuallyComplete: number,
|
||||
timeToInteractive: number,
|
||||
): Messages.PageRenderTiming {
|
||||
return [Messages.Type.PageRenderTiming, speedIndex, visuallyComplete, timeToInteractive];
|
||||
}
|
||||
|
||||
export function JSException(name: string, message: string, payload: string): Messages.JSException {
|
||||
return [Messages.Type.JSException, name, message, payload];
|
||||
}
|
||||
|
||||
export function RawCustomEvent(name: string, payload: string): Messages.RawCustomEvent {
|
||||
return [Messages.Type.RawCustomEvent, name, payload];
|
||||
}
|
||||
|
||||
export function UserID(id: string): Messages.UserID {
|
||||
return [Messages.Type.UserID, id];
|
||||
}
|
||||
|
||||
export function UserAnonymousID(id: string): Messages.UserAnonymousID {
|
||||
return [Messages.Type.UserAnonymousID, id];
|
||||
}
|
||||
|
||||
export function Metadata(key: string, value: string): Messages.Metadata {
|
||||
return [Messages.Type.Metadata, key, value];
|
||||
}
|
||||
|
||||
export function CSSInsertRule(id: number, rule: string, index: number): Messages.CSSInsertRule {
|
||||
return [Messages.Type.CSSInsertRule, id, rule, index];
|
||||
}
|
||||
|
||||
export function CSSDeleteRule(id: number, index: number): Messages.CSSDeleteRule {
|
||||
return [Messages.Type.CSSDeleteRule, id, index];
|
||||
}
|
||||
|
||||
export function Fetch(
|
||||
method: string,
|
||||
url: string,
|
||||
request: string,
|
||||
response: string,
|
||||
status: number,
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
): Messages.Fetch {
|
||||
return [Messages.Type.Fetch, method, url, request, response, status, timestamp, duration];
|
||||
}
|
||||
|
||||
export function Profiler(
|
||||
name: string,
|
||||
duration: number,
|
||||
args: string,
|
||||
result: string,
|
||||
): Messages.Profiler {
|
||||
return [Messages.Type.Profiler, name, duration, args, result];
|
||||
}
|
||||
|
||||
export function OTable(key: string, value: string): Messages.OTable {
|
||||
return [Messages.Type.OTable, key, value];
|
||||
}
|
||||
|
||||
export function StateAction(type: string): Messages.StateAction {
|
||||
return [Messages.Type.StateAction, type];
|
||||
}
|
||||
|
||||
export function Redux(action: string, state: string, duration: number): Messages.Redux {
|
||||
return [Messages.Type.Redux, action, state, duration];
|
||||
}
|
||||
|
||||
export function Vuex(mutation: string, state: string): Messages.Vuex {
|
||||
return [Messages.Type.Vuex, mutation, state];
|
||||
}
|
||||
|
||||
export function MobX(type: string, payload: string): Messages.MobX {
|
||||
return [Messages.Type.MobX, type, payload];
|
||||
}
|
||||
|
||||
export function NgRx(action: string, state: string, duration: number): Messages.NgRx {
|
||||
return [Messages.Type.NgRx, action, state, duration];
|
||||
}
|
||||
|
||||
export function GraphQL(
|
||||
operationKind: string,
|
||||
operationName: string,
|
||||
variables: string,
|
||||
response: string,
|
||||
): Messages.GraphQL {
|
||||
return [Messages.Type.GraphQL, operationKind, operationName, variables, response];
|
||||
}
|
||||
|
||||
export function PerformanceTrack(
|
||||
frames: number,
|
||||
ticks: number,
|
||||
totalJSHeapSize: number,
|
||||
usedJSHeapSize: number,
|
||||
): Messages.PerformanceTrack {
|
||||
return [Messages.Type.PerformanceTrack, frames, ticks, totalJSHeapSize, usedJSHeapSize];
|
||||
}
|
||||
|
||||
export function ResourceTiming(
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
ttfb: number,
|
||||
headerSize: number,
|
||||
encodedBodySize: number,
|
||||
decodedBodySize: number,
|
||||
url: string,
|
||||
initiator: string,
|
||||
): Messages.ResourceTiming {
|
||||
return [
|
||||
Messages.Type.ResourceTiming,
|
||||
timestamp,
|
||||
duration,
|
||||
ttfb,
|
||||
headerSize,
|
||||
encodedBodySize,
|
||||
decodedBodySize,
|
||||
url,
|
||||
initiator,
|
||||
];
|
||||
}
|
||||
|
||||
export function ConnectionInformation(
|
||||
downlink: number,
|
||||
type: string,
|
||||
): Messages.ConnectionInformation {
|
||||
return [Messages.Type.ConnectionInformation, downlink, type];
|
||||
}
|
||||
|
||||
export function SetPageVisibility(hidden: boolean): Messages.SetPageVisibility {
|
||||
return [Messages.Type.SetPageVisibility, hidden];
|
||||
}
|
||||
|
||||
export function LongTask(
|
||||
timestamp: number,
|
||||
duration: number,
|
||||
context: number,
|
||||
containerType: number,
|
||||
containerSrc: string,
|
||||
containerId: string,
|
||||
containerName: string,
|
||||
): Messages.LongTask {
|
||||
return [
|
||||
Messages.Type.LongTask,
|
||||
timestamp,
|
||||
duration,
|
||||
context,
|
||||
containerType,
|
||||
containerSrc,
|
||||
containerId,
|
||||
containerName,
|
||||
];
|
||||
}
|
||||
|
||||
export function SetNodeAttributeURLBased(
|
||||
id: number,
|
||||
name: string,
|
||||
value: string,
|
||||
baseURL: string,
|
||||
): Messages.SetNodeAttributeURLBased {
|
||||
return [Messages.Type.SetNodeAttributeURLBased, id, name, value, baseURL];
|
||||
}
|
||||
|
||||
export function SetCSSDataURLBased(
|
||||
id: number,
|
||||
data: string,
|
||||
baseURL: string,
|
||||
): Messages.SetCSSDataURLBased {
|
||||
return [Messages.Type.SetCSSDataURLBased, id, data, baseURL];
|
||||
}
|
||||
|
||||
export function TechnicalInfo(type: string, value: string): Messages.TechnicalInfo {
|
||||
return [Messages.Type.TechnicalInfo, type, value];
|
||||
}
|
||||
|
||||
export function CustomIssue(name: string, payload: string): Messages.CustomIssue {
|
||||
return [Messages.Type.CustomIssue, name, payload];
|
||||
}
|
||||
|
||||
export function CSSInsertRuleURLBased(
|
||||
id: number,
|
||||
rule: string,
|
||||
index: number,
|
||||
baseURL: string,
|
||||
): Messages.CSSInsertRuleURLBased {
|
||||
return [Messages.Type.CSSInsertRuleURLBased, id, rule, index, baseURL];
|
||||
}
|
||||
|
||||
export function MouseClick(
|
||||
id: number,
|
||||
hesitationTime: number,
|
||||
label: string,
|
||||
selector: string,
|
||||
): Messages.MouseClick {
|
||||
return [Messages.Type.MouseClick, id, hesitationTime, label, selector];
|
||||
}
|
||||
|
||||
export function CreateIFrameDocument(frameID: number, id: number): Messages.CreateIFrameDocument {
|
||||
return [Messages.Type.CreateIFrameDocument, frameID, id];
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import Observer from './observer.js';
|
||||
import { CreateIFrameDocument } from '../../../common/messages.js';
|
||||
import { CreateIFrameDocument } from '../messages.js';
|
||||
|
||||
export default class IFrameObserver extends Observer {
|
||||
observe(iframe: HTMLIFrameElement) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
CreateElementNode,
|
||||
MoveNode,
|
||||
RemoveNode,
|
||||
} from '../../../common/messages.js';
|
||||
} from '../messages.js';
|
||||
import App from '../index.js';
|
||||
import { isRootNode, isTextNode, isElementNode, isSVGElement, hasTag } from '../guards.js';
|
||||
|
||||
|
|
@ -125,14 +125,14 @@ export default abstract class Observer {
|
|||
name = name.substr(6);
|
||||
}
|
||||
if (value === null) {
|
||||
this.app.send(new RemoveNodeAttribute(id, name));
|
||||
this.app.send(RemoveNodeAttribute(id, name));
|
||||
} else if (name === 'href') {
|
||||
if (value.length > 1e5) {
|
||||
value = '';
|
||||
}
|
||||
this.app.send(new SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
||||
this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
||||
} else {
|
||||
this.app.send(new SetNodeAttribute(id, name, value));
|
||||
this.app.send(SetNodeAttribute(id, name, value));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
@ -156,26 +156,26 @@ export default abstract class Observer {
|
|||
return;
|
||||
}
|
||||
if (value === null) {
|
||||
this.app.send(new RemoveNodeAttribute(id, name));
|
||||
this.app.send(RemoveNodeAttribute(id, name));
|
||||
return;
|
||||
}
|
||||
if (name === 'style' || (name === 'href' && hasTag(node, 'LINK'))) {
|
||||
this.app.send(new SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
||||
this.app.send(SetNodeAttributeURLBased(id, name, value, this.app.getBaseHref()));
|
||||
return;
|
||||
}
|
||||
if (name === 'href' || value.length > 1e5) {
|
||||
value = '';
|
||||
}
|
||||
this.app.send(new SetNodeAttribute(id, name, value));
|
||||
this.app.send(SetNodeAttribute(id, name, value));
|
||||
}
|
||||
|
||||
private sendNodeData(id: number, parentElement: Element, data: string): void {
|
||||
if (hasTag(parentElement, 'STYLE') || hasTag(parentElement, 'style')) {
|
||||
this.app.send(new SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
||||
this.app.send(SetCSSDataURLBased(id, data, this.app.getBaseHref()));
|
||||
return;
|
||||
}
|
||||
data = this.app.sanitizer.sanitize(id, data);
|
||||
this.app.send(new SetNodeData(id, data));
|
||||
this.app.send(SetNodeData(id, data));
|
||||
}
|
||||
|
||||
private bindNode(node: Node): void {
|
||||
|
|
@ -221,7 +221,7 @@ export default abstract class Observer {
|
|||
private unbindNode(node: Node) {
|
||||
const id = this.app.nodes.unregisterNode(node);
|
||||
if (id !== undefined && this.recents.get(id) === RecentsType.Removed) {
|
||||
this.app.send(new RemoveNode(id));
|
||||
this.app.send(RemoveNode(id));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -289,7 +289,7 @@ export default abstract class Observer {
|
|||
(el as HTMLElement | SVGElement).style.height = height + 'px';
|
||||
}
|
||||
|
||||
this.app.send(new CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)));
|
||||
this.app.send(CreateElementNode(id, parentID, index, el.tagName, isSVGElement(node)));
|
||||
}
|
||||
for (let i = 0; i < el.attributes.length; i++) {
|
||||
const attr = el.attributes[i];
|
||||
|
|
@ -297,13 +297,13 @@ export default abstract class Observer {
|
|||
}
|
||||
} else if (isTextNode(node)) {
|
||||
// for text node id != 0, hence parentID !== undefined and parent is Element
|
||||
this.app.send(new CreateTextNode(id, parentID as number, index));
|
||||
this.app.send(CreateTextNode(id, parentID as number, index));
|
||||
this.sendNodeData(id, parent as Element, node.data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (recentsType === RecentsType.Removed && parentID !== undefined) {
|
||||
this.app.send(new MoveNode(id, parentID, index));
|
||||
this.app.send(MoveNode(id, parentID, index));
|
||||
}
|
||||
const attr = this.attributesMap.get(id);
|
||||
if (attr !== undefined) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Observer from './observer.js';
|
||||
import { CreateIFrameDocument } from '../../../common/messages.js';
|
||||
import { CreateIFrameDocument } from '../messages.js';
|
||||
|
||||
export default class ShadowRootObserver extends Observer {
|
||||
observe(el: Element) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { isElementNode, hasTag } from '../guards.js';
|
|||
import IFrameObserver from './iframe_observer.js';
|
||||
import ShadowRootObserver from './shadow_root_observer.js';
|
||||
|
||||
import { CreateDocument } from '../../../common/messages.js';
|
||||
import { CreateDocument } from '../messages.js';
|
||||
import App from '../index.js';
|
||||
import { IN_BROWSER, hasOpenreplayAttribute } from '../../utils.js';
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ export default class TopObserver extends Observer {
|
|||
this.observeRoot(
|
||||
window.document,
|
||||
() => {
|
||||
this.app.send(new CreateDocument());
|
||||
this.app.send(CreateDocument());
|
||||
},
|
||||
window.document.documentElement,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { UserID, UserAnonymousID, Metadata } from '../../common/messages.js';
|
||||
|
||||
interface SessionInfo {
|
||||
sessionID: string | null;
|
||||
metadata: Record<string, string>;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
import App, { DEFAULT_INGEST_POINT } from './app/index.js';
|
||||
export { default as App } from './app/index.js';
|
||||
|
||||
import {
|
||||
UserID,
|
||||
UserAnonymousID,
|
||||
Metadata,
|
||||
RawCustomEvent,
|
||||
CustomIssue,
|
||||
} from '../common/messages.js';
|
||||
import * as _Messages from '../common/messages.js';
|
||||
import { UserID, UserAnonymousID, RawCustomEvent, CustomIssue } from './app/messages.js';
|
||||
import * as _Messages from './app/messages.js';
|
||||
export const Messages = _Messages;
|
||||
|
||||
import Connection from './modules/connection.js';
|
||||
|
|
@ -224,7 +218,7 @@ export default class API {
|
|||
|
||||
setUserAnonymousID(id: string): void {
|
||||
if (typeof id === 'string' && this.app !== null) {
|
||||
this.app.send(new UserAnonymousID(id));
|
||||
this.app.send(UserAnonymousID(id));
|
||||
}
|
||||
}
|
||||
userAnonymousID(id: string): void {
|
||||
|
|
@ -252,7 +246,7 @@ export default class API {
|
|||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
this.app.send(new RawCustomEvent(key, payload));
|
||||
this.app.send(RawCustomEvent(key, payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -264,7 +258,7 @@ export default class API {
|
|||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
this.app.send(new CustomIssue(key, payload));
|
||||
this.app.send(CustomIssue(key, payload));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import App from '../app/index.js';
|
||||
import { ConnectionInformation } from '../../common/messages.js';
|
||||
import { ConnectionInformation } from '../app/messages.js';
|
||||
|
||||
export default function (app: App): void {
|
||||
const connection:
|
||||
|
|
@ -18,10 +18,7 @@ export default function (app: App): void {
|
|||
|
||||
const sendConnectionInformation = (): void =>
|
||||
app.send(
|
||||
new ConnectionInformation(
|
||||
Math.round(connection.downlink * 1000),
|
||||
connection.type || 'unknown',
|
||||
),
|
||||
ConnectionInformation(Math.round(connection.downlink * 1000), connection.type || 'unknown'),
|
||||
);
|
||||
sendConnectionInformation();
|
||||
connection.addEventListener('change', sendConnectionInformation);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type App from '../app/index.js';
|
||||
import { hasTag } from '../app/guards.js';
|
||||
import { IN_BROWSER } from '../utils.js';
|
||||
import { ConsoleLog } from '../../common/messages.js';
|
||||
import { ConsoleLog } from '../app/messages.js';
|
||||
|
||||
const printError: (e: Error) => string =
|
||||
IN_BROWSER && 'InstallTrigger' in window // detect Firefox
|
||||
|
|
@ -109,7 +109,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
}
|
||||
|
||||
const sendConsoleLog = app.safe((level: string, args: unknown[]): void =>
|
||||
app.send(new ConsoleLog(level, printf(args))),
|
||||
app.send(ConsoleLog(level, printf(args))),
|
||||
);
|
||||
|
||||
let n: number;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type App from '../app/index.js';
|
||||
import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from '../../common/messages.js';
|
||||
import { CSSInsertRuleURLBased, CSSDeleteRule, TechnicalInfo } from '../app/messages.js';
|
||||
import { hasTag } from '../app/guards.js';
|
||||
|
||||
export default function (app: App | null) {
|
||||
|
|
@ -7,7 +7,7 @@ export default function (app: App | null) {
|
|||
return;
|
||||
}
|
||||
if (!window.CSSStyleSheet) {
|
||||
app.send(new TechnicalInfo('no_stylesheet_prototype_in_window', ''));
|
||||
app.send(TechnicalInfo('no_stylesheet_prototype_in_window', ''));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -15,8 +15,8 @@ export default function (app: App | null) {
|
|||
const sendMessage =
|
||||
typeof rule === 'string'
|
||||
? (nodeID: number) =>
|
||||
app.send(new CSSInsertRuleURLBased(nodeID, rule, index, app.getBaseHref()))
|
||||
: (nodeID: number) => app.send(new CSSDeleteRule(nodeID, index));
|
||||
app.send(CSSInsertRuleURLBased(nodeID, rule, index, app.getBaseHref()))
|
||||
: (nodeID: number) => app.send(CSSDeleteRule(nodeID, index));
|
||||
// TODO: Extend messages to maintain nested rules (CSSGroupingRule prototype, as well as CSSKeyframesRule)
|
||||
if (stylesheet.ownerNode == null) {
|
||||
throw new Error('Owner Node not found');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type App from '../app/index.js';
|
||||
import type Message from '../../common/messages.js';
|
||||
import { JSException } from '../../common/messages.js';
|
||||
import type Message from '../app/messages.js';
|
||||
import { JSException } from '../app/messages.js';
|
||||
import ErrorStackParser from 'error-stack-parser';
|
||||
|
||||
export interface Options {
|
||||
|
|
@ -32,7 +32,7 @@ export function getExceptionMessage(error: Error, fallbackStack: Array<StackFram
|
|||
try {
|
||||
stack = ErrorStackParser.parse(error);
|
||||
} catch (e) {}
|
||||
return new JSException(error.name, error.message, JSON.stringify(stack));
|
||||
return JSException(error.name, error.message, JSON.stringify(stack));
|
||||
}
|
||||
|
||||
export function getExceptionMessageFromEvent(
|
||||
|
|
@ -47,7 +47,7 @@ export function getExceptionMessageFromEvent(
|
|||
name = 'Error';
|
||||
message = e.message;
|
||||
}
|
||||
return new JSException(name, message, JSON.stringify(getDefaultStack(e)));
|
||||
return JSException(name, message, JSON.stringify(getDefaultStack(e)));
|
||||
}
|
||||
} else if ('PromiseRejectionEvent' in window && e instanceof PromiseRejectionEvent) {
|
||||
if (e.reason instanceof Error) {
|
||||
|
|
@ -59,7 +59,7 @@ export function getExceptionMessageFromEvent(
|
|||
} catch (_) {
|
||||
message = String(e.reason);
|
||||
}
|
||||
return new JSException('Unhandled Promise Rejection', message, '[]');
|
||||
return JSException('Unhandled Promise Rejection', message, '[]');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import type App from '../app/index.js';
|
||||
import { timestamp, isURL } from '../utils.js';
|
||||
import {
|
||||
ResourceTiming,
|
||||
SetNodeAttributeURLBased,
|
||||
SetNodeAttribute,
|
||||
} from '../../common/messages.js';
|
||||
import { ResourceTiming, SetNodeAttributeURLBased, SetNodeAttribute } from '../app/messages.js';
|
||||
import { hasTag } from '../app/guards.js';
|
||||
|
||||
function resolveURL(url: string, location: Location = document.location) {
|
||||
|
|
@ -26,13 +22,13 @@ const PLACEHOLDER_SRC = 'https://static.openreplay.com/tracker/placeholder.jpeg'
|
|||
|
||||
export default function (app: App): void {
|
||||
function sendPlaceholder(id: number, node: HTMLImageElement): void {
|
||||
app.send(new SetNodeAttribute(id, 'src', PLACEHOLDER_SRC));
|
||||
app.send(SetNodeAttribute(id, 'src', PLACEHOLDER_SRC));
|
||||
const { width, height } = node.getBoundingClientRect();
|
||||
if (!node.hasAttribute('width')) {
|
||||
app.send(new SetNodeAttribute(id, 'width', String(width)));
|
||||
app.send(SetNodeAttribute(id, 'width', String(width)));
|
||||
}
|
||||
if (!node.hasAttribute('height')) {
|
||||
app.send(new SetNodeAttribute(id, 'height', String(height)));
|
||||
app.send(SetNodeAttribute(id, 'height', String(height)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,18 +44,18 @@ export default function (app: App): void {
|
|||
const resolvedSrc = resolveURL(src || ''); // Src type is null sometimes. - is it true?
|
||||
if (naturalWidth === 0 && naturalHeight === 0) {
|
||||
if (isURL(resolvedSrc)) {
|
||||
app.send(new ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
|
||||
app.send(ResourceTiming(timestamp(), 0, 0, 0, 0, 0, resolvedSrc, 'img'));
|
||||
}
|
||||
} else if (resolvedSrc.length >= 1e5 || app.sanitizer.isMasked(id)) {
|
||||
sendPlaceholder(id, this);
|
||||
} else {
|
||||
app.send(new SetNodeAttribute(id, 'src', resolvedSrc));
|
||||
app.send(SetNodeAttribute(id, 'src', resolvedSrc));
|
||||
if (srcset) {
|
||||
const resolvedSrcset = srcset
|
||||
.split(',')
|
||||
.map((str) => resolveURL(str))
|
||||
.join(',');
|
||||
app.send(new SetNodeAttribute(id, 'srcset', resolvedSrcset));
|
||||
app.send(SetNodeAttribute(id, 'srcset', resolvedSrcset));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -74,11 +70,11 @@ export default function (app: App): void {
|
|||
}
|
||||
if (mutation.attributeName === 'src') {
|
||||
const src = target.src;
|
||||
app.send(new SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
|
||||
app.send(SetNodeAttributeURLBased(id, 'src', src, app.getBaseHref()));
|
||||
}
|
||||
if (mutation.attributeName === 'srcset') {
|
||||
const srcset = target.srcset;
|
||||
app.send(new SetNodeAttribute(id, 'srcset', srcset));
|
||||
app.send(SetNodeAttribute(id, 'srcset', srcset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type App from '../app/index.js';
|
||||
import { normSpaces, IN_BROWSER, getLabelAttribute, hasOpenreplayAttribute } from '../utils.js';
|
||||
import { hasTag } from '../app/guards.js';
|
||||
import { SetInputTarget, SetInputValue, SetInputChecked } from '../../common/messages.js';
|
||||
import { SetInputTarget, SetInputValue, SetInputChecked } from '../app/messages.js';
|
||||
|
||||
const INPUT_TYPES = ['text', 'password', 'email', 'search', 'number', 'range', 'date'];
|
||||
|
||||
|
|
@ -97,7 +97,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
function sendInputTarget(id: number, node: TextEditableElement): void {
|
||||
const label = getInputLabel(node);
|
||||
if (label !== '') {
|
||||
app.send(new SetInputTarget(id, label));
|
||||
app.send(SetInputTarget(id, label));
|
||||
}
|
||||
}
|
||||
function sendInputValue(id: number, node: TextEditableElement | HTMLSelectElement): void {
|
||||
|
|
@ -126,7 +126,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
break;
|
||||
}
|
||||
|
||||
app.send(new SetInputValue(id, value, mask));
|
||||
app.send(SetInputValue(id, value, mask));
|
||||
}
|
||||
|
||||
const inputValues: Map<number, string> = new Map();
|
||||
|
|
@ -165,7 +165,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
}
|
||||
if (checked !== node.checked) {
|
||||
checkableValues.set(id, node.checked);
|
||||
app.send(new SetInputChecked(id, node.checked));
|
||||
app.send(SetInputChecked(id, node.checked));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -191,7 +191,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
}
|
||||
if (isCheckable(node)) {
|
||||
checkableValues.set(id, node.checked);
|
||||
app.send(new SetInputChecked(id, node.checked));
|
||||
app.send(SetInputChecked(id, node.checked));
|
||||
return;
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type App from '../app/index.js';
|
||||
import { LongTask } from '../../common/messages.js';
|
||||
import { LongTask } from '../app/messages.js';
|
||||
|
||||
// https://w3c.github.io/performance-timeline/#the-performanceentry-interface
|
||||
interface TaskAttributionTiming extends PerformanceEntry {
|
||||
|
|
@ -45,7 +45,7 @@ export default function (app: App): void {
|
|||
}
|
||||
|
||||
app.send(
|
||||
new LongTask(
|
||||
LongTask(
|
||||
entry.startTime + performance.timing.navigationStart,
|
||||
entry.duration,
|
||||
Math.max(contexts.indexOf(entry.name), 0),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type App from '../app/index.js';
|
||||
import { hasTag, isSVGElement } from '../app/guards.js';
|
||||
import { normSpaces, hasOpenreplayAttribute, getLabelAttribute } from '../utils.js';
|
||||
import { MouseMove, MouseClick } from '../../common/messages.js';
|
||||
import { MouseMove, MouseClick } from '../app/messages.js';
|
||||
import { getInputLabel } from './input.js';
|
||||
|
||||
function _getSelector(target: Element): string {
|
||||
|
|
@ -115,7 +115,7 @@ export default function (app: App): void {
|
|||
|
||||
const sendMouseMove = (): void => {
|
||||
if (mousePositionChanged) {
|
||||
app.send(new MouseMove(mousePositionX, mousePositionY));
|
||||
app.send(MouseMove(mousePositionX, mousePositionY));
|
||||
mousePositionChanged = false;
|
||||
}
|
||||
};
|
||||
|
|
@ -151,7 +151,7 @@ export default function (app: App): void {
|
|||
if (id !== undefined) {
|
||||
sendMouseMove();
|
||||
app.send(
|
||||
new MouseClick(
|
||||
MouseClick(
|
||||
id,
|
||||
mouseTarget === target ? Math.round(performance.now() - mouseTargetTime) : 0,
|
||||
getTargetLabel(target),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type App from '../app/index.js';
|
||||
import { IN_BROWSER } from '../utils.js';
|
||||
import { PerformanceTrack } from '../../common/messages.js';
|
||||
import { PerformanceTrack } from '../app/messages.js';
|
||||
|
||||
type Perf = {
|
||||
memory: {
|
||||
|
|
@ -60,7 +60,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
return;
|
||||
}
|
||||
app.send(
|
||||
new PerformanceTrack(
|
||||
PerformanceTrack(
|
||||
frames,
|
||||
ticks,
|
||||
perf.memory.totalJSHeapSize || 0,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type App from '../app/index.js';
|
||||
import { SetViewportScroll, SetNodeScroll } from '../../common/messages.js';
|
||||
import { SetViewportScroll, SetNodeScroll } from '../app/messages.js';
|
||||
import { isElementNode } from '../app/guards.js';
|
||||
|
||||
export default function (app: App): void {
|
||||
|
|
@ -8,7 +8,7 @@ export default function (app: App): void {
|
|||
|
||||
const sendSetViewportScroll = app.safe((): void =>
|
||||
app.send(
|
||||
new SetViewportScroll(
|
||||
SetViewportScroll(
|
||||
window.pageXOffset ||
|
||||
(document.documentElement && document.documentElement.scrollLeft) ||
|
||||
(document.body && document.body.scrollLeft) ||
|
||||
|
|
@ -24,7 +24,7 @@ export default function (app: App): void {
|
|||
const sendSetNodeScroll = app.safe((s: [number, number], node: Node): void => {
|
||||
const id = app.nodes.getID(node);
|
||||
if (id !== undefined) {
|
||||
app.send(new SetNodeScroll(id, s[0], s[1]));
|
||||
app.send(SetNodeScroll(id, s[0], s[1]));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type App from '../app/index.js';
|
||||
import { hasTag } from '../app/guards.js';
|
||||
import { isURL } from '../utils.js';
|
||||
import { ResourceTiming, PageLoadTiming, PageRenderTiming } from '../../common/messages.js';
|
||||
import { ResourceTiming, PageLoadTiming, PageRenderTiming } from '../app/messages.js';
|
||||
|
||||
// Inspired by https://github.com/WPO-Foundation/RUM-SpeedIndex/blob/master/src/rum-speedindex.js
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
resources[entry.name] = entry.startTime + entry.duration;
|
||||
}
|
||||
app.send(
|
||||
new ResourceTiming(
|
||||
ResourceTiming(
|
||||
entry.startTime + performance.timing.navigationStart,
|
||||
entry.duration,
|
||||
entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0,
|
||||
|
|
@ -175,7 +175,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
loadEventEnd,
|
||||
} = performance.timing;
|
||||
app.send(
|
||||
new PageLoadTiming(
|
||||
PageLoadTiming(
|
||||
requestStart - navigationStart || 0,
|
||||
responseStart - navigationStart || 0,
|
||||
responseEnd - navigationStart || 0,
|
||||
|
|
@ -236,7 +236,7 @@ export default function (app: App, opts: Partial<Options>): void {
|
|||
)
|
||||
: 0;
|
||||
app.send(
|
||||
new PageRenderTiming(
|
||||
PageRenderTiming(
|
||||
speedIndex,
|
||||
firstContentfulPaint > visuallyComplete ? firstContentfulPaint : visuallyComplete,
|
||||
timeToInteractive,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type App from '../app/index.js';
|
||||
import { SetPageLocation, SetViewportSize, SetPageVisibility } from '../../common/messages.js';
|
||||
import { SetPageLocation, SetViewportSize, SetPageVisibility } from '../app/messages.js';
|
||||
|
||||
export default function (app: App): void {
|
||||
let url: string, width: number, height: number;
|
||||
|
|
@ -9,7 +9,7 @@ export default function (app: App): void {
|
|||
const { URL } = document;
|
||||
if (URL !== url) {
|
||||
url = URL;
|
||||
app.send(new SetPageLocation(url, document.referrer, navigationStart));
|
||||
app.send(SetPageLocation(url, document.referrer, navigationStart));
|
||||
navigationStart = 0;
|
||||
}
|
||||
});
|
||||
|
|
@ -19,14 +19,14 @@ export default function (app: App): void {
|
|||
if (innerWidth !== width || innerHeight !== height) {
|
||||
width = innerWidth;
|
||||
height = innerHeight;
|
||||
app.send(new SetViewportSize(width, height));
|
||||
app.send(SetViewportSize(width, height));
|
||||
}
|
||||
});
|
||||
|
||||
const sendSetPageVisibility =
|
||||
document.hidden === undefined
|
||||
? Function.prototype
|
||||
: app.safe(() => app.send(new SetPageVisibility(document.hidden)));
|
||||
: app.safe(() => app.send(SetPageVisibility(document.hidden)));
|
||||
|
||||
app.attachStartCallback(() => {
|
||||
url = '';
|
||||
|
|
|
|||
|
|
@ -1,33 +1,62 @@
|
|||
import type Message from '../common/messages.js';
|
||||
import PrimitiveWriter from './PrimitiveWriter.js';
|
||||
import { BatchMeta, Timestamp } from '../common/messages.js';
|
||||
import * as Messages from '../common/messages.js';
|
||||
import MessageEncoder from './MessageEncoder.js';
|
||||
import PrimitiveEncoder from './PrimitiveEncoder.js';
|
||||
|
||||
const SIZE_RESERVED = 2;
|
||||
const MAX_M_SIZE = (1 << (SIZE_RESERVED * 8)) - 1;
|
||||
|
||||
export default class BatchWriter {
|
||||
private nextIndex = 0;
|
||||
private beaconSize = 2 * 1e5; // Default 200kB
|
||||
private writer = new PrimitiveWriter(this.beaconSize);
|
||||
private encoder = new MessageEncoder(this.beaconSize);
|
||||
private readonly sizeEncoder = new PrimitiveEncoder(SIZE_RESERVED);
|
||||
private isEmpty = true;
|
||||
|
||||
constructor(
|
||||
private readonly pageNo: number,
|
||||
private timestamp: number,
|
||||
private url: string,
|
||||
private readonly onBatch: (batch: Uint8Array) => void,
|
||||
) {
|
||||
this.prepare();
|
||||
}
|
||||
|
||||
private prepare(): void {
|
||||
if (!this.writer.isEmpty()) {
|
||||
if (!this.encoder.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
new BatchMeta(this.pageNo, this.nextIndex, this.timestamp).encode(this.writer);
|
||||
// MBTODO: move service-messages creation to webworker
|
||||
const batchMetadata: Messages.BatchMetadata = [
|
||||
Messages.Type.BatchMetadata,
|
||||
1,
|
||||
this.pageNo,
|
||||
this.nextIndex,
|
||||
this.timestamp,
|
||||
this.url,
|
||||
];
|
||||
this.encoder.encode(batchMetadata);
|
||||
}
|
||||
|
||||
private write(message: Message): boolean {
|
||||
const wasWritten = message.encode(this.writer);
|
||||
const e = this.encoder;
|
||||
if (!e.uint(message[0]) || !e.skip(SIZE_RESERVED)) {
|
||||
return false;
|
||||
}
|
||||
const startOffset = e.getCurrentOffset();
|
||||
const wasWritten = e.encode(message);
|
||||
if (wasWritten) {
|
||||
const endOffset = e.getCurrentOffset();
|
||||
const size = endOffset - startOffset;
|
||||
if (size > MAX_M_SIZE || !this.sizeEncoder.uint(size)) {
|
||||
console.warn('OpenReplay: max message size overflow.');
|
||||
return false;
|
||||
}
|
||||
this.sizeEncoder.checkpoint(); // TODO: separate checkpoint logic to an Encoder-inherit class
|
||||
e.set(this.sizeEncoder.flush(), startOffset - SIZE_RESERVED);
|
||||
|
||||
e.checkpoint();
|
||||
this.isEmpty = false;
|
||||
this.writer.checkpoint();
|
||||
this.nextIndex++;
|
||||
}
|
||||
return wasWritten;
|
||||
|
|
@ -39,21 +68,24 @@ export default class BatchWriter {
|
|||
}
|
||||
|
||||
writeMessage(message: Message) {
|
||||
if (message instanceof Timestamp) {
|
||||
this.timestamp = (<any>message).timestamp;
|
||||
if (message[0] === Messages.Type.Timestamp) {
|
||||
this.timestamp = message[1]; // .timestamp
|
||||
}
|
||||
if (message[0] === Messages.Type.SetPageLocation) {
|
||||
this.url = message[1]; // .url
|
||||
}
|
||||
while (!this.write(message)) {
|
||||
this.finaliseBatch();
|
||||
if (this.beaconSize === this.beaconSizeLimit) {
|
||||
console.warn('OpenReplay: beacon size overflow. Skipping large message.');
|
||||
this.writer.reset();
|
||||
this.encoder.reset();
|
||||
this.prepare();
|
||||
this.isEmpty = true;
|
||||
return;
|
||||
}
|
||||
// MBTODO: tempWriter for one message?
|
||||
this.beaconSize = Math.min(this.beaconSize * 2, this.beaconSizeLimit);
|
||||
this.writer = new PrimitiveWriter(this.beaconSize);
|
||||
this.encoder = new MessageEncoder(this.beaconSize);
|
||||
this.prepare();
|
||||
this.isEmpty = true;
|
||||
}
|
||||
|
|
@ -63,12 +95,12 @@ export default class BatchWriter {
|
|||
if (this.isEmpty) {
|
||||
return;
|
||||
}
|
||||
this.onBatch(this.writer.flush());
|
||||
this.onBatch(this.encoder.flush());
|
||||
this.prepare();
|
||||
this.isEmpty = true;
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.writer.reset();
|
||||
this.encoder.reset();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
264
tracker/tracker/src/webworker/MessageEncoder.ts
Normal file
264
tracker/tracker/src/webworker/MessageEncoder.ts
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
// Auto-generated, do not edit
|
||||
|
||||
import * as Messages from '../common/messages.js';
|
||||
import Message from '../common/messages.js';
|
||||
import PrimitiveEncoder from './PrimitiveEncoder.js';
|
||||
|
||||
export default class MessageEncoder extends PrimitiveEncoder {
|
||||
encode(msg: Message): boolean {
|
||||
switch (msg[0]) {
|
||||
case Messages.Type.BatchMetadata:
|
||||
return (
|
||||
this.uint(msg[1]) &&
|
||||
this.uint(msg[2]) &&
|
||||
this.uint(msg[3]) &&
|
||||
this.int(msg[4]) &&
|
||||
this.string(msg[5])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.PartitionedMessage:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.Timestamp:
|
||||
return this.uint(msg[1]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetPageLocation:
|
||||
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetViewportSize:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetViewportScroll:
|
||||
return this.int(msg[1]) && this.int(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.CreateDocument:
|
||||
return true;
|
||||
break;
|
||||
|
||||
case Messages.Type.CreateElementNode:
|
||||
return (
|
||||
this.uint(msg[1]) &&
|
||||
this.uint(msg[2]) &&
|
||||
this.uint(msg[3]) &&
|
||||
this.string(msg[4]) &&
|
||||
this.boolean(msg[5])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.CreateTextNode:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.MoveNode:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.RemoveNode:
|
||||
return this.uint(msg[1]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetNodeAttribute:
|
||||
return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.RemoveNodeAttribute:
|
||||
return this.uint(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetNodeData:
|
||||
return this.uint(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetNodeScroll:
|
||||
return this.uint(msg[1]) && this.int(msg[2]) && this.int(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetInputTarget:
|
||||
return this.uint(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetInputValue:
|
||||
return this.uint(msg[1]) && this.string(msg[2]) && this.int(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetInputChecked:
|
||||
return this.uint(msg[1]) && this.boolean(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.MouseMove:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.ConsoleLog:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.PageLoadTiming:
|
||||
return (
|
||||
this.uint(msg[1]) &&
|
||||
this.uint(msg[2]) &&
|
||||
this.uint(msg[3]) &&
|
||||
this.uint(msg[4]) &&
|
||||
this.uint(msg[5]) &&
|
||||
this.uint(msg[6]) &&
|
||||
this.uint(msg[7]) &&
|
||||
this.uint(msg[8]) &&
|
||||
this.uint(msg[9])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.PageRenderTiming:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]) && this.uint(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.JSException:
|
||||
return this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.RawCustomEvent:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.UserID:
|
||||
return this.string(msg[1]);
|
||||
break;
|
||||
|
||||
case Messages.Type.UserAnonymousID:
|
||||
return this.string(msg[1]);
|
||||
break;
|
||||
|
||||
case Messages.Type.Metadata:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.CSSInsertRule:
|
||||
return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.CSSDeleteRule:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.Fetch:
|
||||
return (
|
||||
this.string(msg[1]) &&
|
||||
this.string(msg[2]) &&
|
||||
this.string(msg[3]) &&
|
||||
this.string(msg[4]) &&
|
||||
this.uint(msg[5]) &&
|
||||
this.uint(msg[6]) &&
|
||||
this.uint(msg[7])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.Profiler:
|
||||
return (
|
||||
this.string(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.OTable:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.StateAction:
|
||||
return this.string(msg[1]);
|
||||
break;
|
||||
|
||||
case Messages.Type.Redux:
|
||||
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.Vuex:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.MobX:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.NgRx:
|
||||
return this.string(msg[1]) && this.string(msg[2]) && this.uint(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.GraphQL:
|
||||
return (
|
||||
this.string(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.PerformanceTrack:
|
||||
return this.int(msg[1]) && this.int(msg[2]) && this.uint(msg[3]) && this.uint(msg[4]);
|
||||
break;
|
||||
|
||||
case Messages.Type.ResourceTiming:
|
||||
return (
|
||||
this.uint(msg[1]) &&
|
||||
this.uint(msg[2]) &&
|
||||
this.uint(msg[3]) &&
|
||||
this.uint(msg[4]) &&
|
||||
this.uint(msg[5]) &&
|
||||
this.uint(msg[6]) &&
|
||||
this.string(msg[7]) &&
|
||||
this.string(msg[8])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.ConnectionInformation:
|
||||
return this.uint(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetPageVisibility:
|
||||
return this.boolean(msg[1]);
|
||||
break;
|
||||
|
||||
case Messages.Type.LongTask:
|
||||
return (
|
||||
this.uint(msg[1]) &&
|
||||
this.uint(msg[2]) &&
|
||||
this.uint(msg[3]) &&
|
||||
this.uint(msg[4]) &&
|
||||
this.string(msg[5]) &&
|
||||
this.string(msg[6]) &&
|
||||
this.string(msg[7])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetNodeAttributeURLBased:
|
||||
return (
|
||||
this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]) && this.string(msg[4])
|
||||
);
|
||||
break;
|
||||
|
||||
case Messages.Type.SetCSSDataURLBased:
|
||||
return this.uint(msg[1]) && this.string(msg[2]) && this.string(msg[3]);
|
||||
break;
|
||||
|
||||
case Messages.Type.TechnicalInfo:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.CustomIssue:
|
||||
return this.string(msg[1]) && this.string(msg[2]);
|
||||
break;
|
||||
|
||||
case Messages.Type.CSSInsertRuleURLBased:
|
||||
return this.uint(msg[1]) && this.string(msg[2]) && this.uint(msg[3]) && this.string(msg[4]);
|
||||
break;
|
||||
|
||||
case Messages.Type.MouseClick:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]) && this.string(msg[3]) && this.string(msg[4]);
|
||||
break;
|
||||
|
||||
case Messages.Type.CreateIFrameDocument:
|
||||
return this.uint(msg[1]) && this.uint(msg[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,19 +53,29 @@ const textEncoder: { encode(str: string): Uint8Array } =
|
|||
},
|
||||
};
|
||||
|
||||
export default class PrimitiveWriter {
|
||||
export default class PrimitiveEncoder {
|
||||
private offset = 0;
|
||||
private checkpointOffset = 0;
|
||||
private readonly data: Uint8Array;
|
||||
constructor(private readonly size: number) {
|
||||
this.data = new Uint8Array(size);
|
||||
}
|
||||
getCurrentOffset(): number {
|
||||
return this.offset;
|
||||
}
|
||||
checkpoint() {
|
||||
this.checkpointOffset = this.offset;
|
||||
}
|
||||
isEmpty(): boolean {
|
||||
return this.offset === 0;
|
||||
}
|
||||
skip(n: number): boolean {
|
||||
this.offset += n;
|
||||
return this.offset <= this.size;
|
||||
}
|
||||
set(bytes: Uint8Array, offset: number) {
|
||||
this.data.set(bytes, offset);
|
||||
}
|
||||
boolean(value: boolean): boolean {
|
||||
this.data[this.offset++] = +value;
|
||||
return this.offset <= this.size;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type Message from '../common/messages.js';
|
||||
import { WorkerMessageData } from '../common/webworker.js';
|
||||
import { Type as MType } from '../common/messages.js';
|
||||
import { WorkerMessageData } from '../common/interaction.js';
|
||||
|
||||
import { classes, SetPageVisibility } from '../common/messages.js';
|
||||
import QueueSender from './QueueSender.js';
|
||||
import BatchWriter from './BatchWriter.js';
|
||||
|
||||
|
|
@ -66,13 +66,11 @@ self.onmessage = ({ data }: MessageEvent<WorkerMessageData>): any => {
|
|||
}
|
||||
const w = writer;
|
||||
// Message[]
|
||||
data.forEach((data) => {
|
||||
// @ts-ignore
|
||||
const message: Message = new (classes.get(data._id))();
|
||||
data.forEach((message) => {
|
||||
Object.assign(message, data);
|
||||
if (message instanceof SetPageVisibility) {
|
||||
// @ts-ignore
|
||||
if ((<any>message).hidden) {
|
||||
if (message[0] === MType.SetPageVisibility) {
|
||||
if (message[1]) {
|
||||
// .hidden
|
||||
restartTimeoutID = setTimeout(() => self.postMessage('restart'), 30 * 60 * 1000);
|
||||
} else {
|
||||
clearTimeout(restartTimeoutID);
|
||||
|
|
@ -102,6 +100,7 @@ self.onmessage = ({ data }: MessageEvent<WorkerMessageData>): any => {
|
|||
writer = new BatchWriter(
|
||||
data.pageNo,
|
||||
data.timestamp,
|
||||
data.url,
|
||||
// onBatch
|
||||
(batch) => sender && sender.push(batch),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue