ci-trap-web
Version:
Lightweight mouse and touch event tracker library for browsers.
314 lines (270 loc) • 7.86 kB
JavaScript
//------------------------------------------------------------------------------
// Copyright (C) 2023- Cursor Insight Ltd.
//
// All rights reserved.
//------------------------------------------------------------------------------
// Metadata handler.
//
// This module manages static, immutable constants for a stream e.g.
// `sessionId` or `streamId`; custom, user-defined metadata; and platform-,
// machine- and DOM-specific metadata as well.
//------------------------------------------------------------------------------
import { v4 as uuidv4 } from 'uuid';
import Cookies from 'js-cookie';
import Platform from 'platform';
import clone from 'rfdc/default';
import simpleAutoBind from './simpleAutoBind';
import eventEmitterMixin from './eventEmitterMixin';
import TimeUtils from './timeUtils';
// Constants
import {
DEFAULT_TRAP_API_KEY_NAME,
DEFAULT_TRAP_API_KEY_VALUE,
DEFAULT_METADATA_SUBMISSION_INTERVAL,
DEFAULT_METADATA_COLLECT_URLS,
METADATA_MESSAGE_TYPE,
PACKAGE_VERSION,
PACKAGE_NAME,
PACKAGE_TYPE,
SCHEMA_VERSION,
} from './constants';
const PACKAGE_COMPONENT = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
type: PACKAGE_TYPE,
};
class Metadata {
constructor() {
simpleAutoBind(this);
// Unique session id -- that represents a unique browser; load or set up a
// new cookie
this._sessionId = (() => {
let sessionId = Cookies.get('sessionId');
if (typeof sessionId !== 'string') {
sessionId = uuidv4();
Cookies.set(
'sessionId',
sessionId,
{ path: '/', sameSite: 'none', secure: true },
);
}
return sessionId;
})();
// Unique stream id to identify browser sessions, i.e. page loads
this.generateNewStreamId();
// Custom metadata store
this._customMetadata = {};
// Key name of the "API key"
this._apiKeyName = DEFAULT_TRAP_API_KEY_NAME;
// API key that is used to distinguish clients
this._apiKeyValue = DEFAULT_TRAP_API_KEY_VALUE;
// Default submission inverval
this._metadataSubmissionInterval = DEFAULT_METADATA_SUBMISSION_INTERVAL;
// Default setting for URL collection
this._collectUrls = DEFAULT_METADATA_COLLECT_URLS;
// Default setting for component list
this._components = [PACKAGE_COMPONENT];
}
// Return current sessionId
get sessionId() {
return this._sessionId;
}
// Return current streamId
get streamId() {
return this._streamId;
}
// Return current API key name
get apiKeyName() {
return this._apiKeyName;
}
// Set current API key name
set apiKeyName(apiKeyName) {
this._apiKeyName = apiKeyName;
}
// Return current API key value
get apiKeyValue() {
return this._apiKeyValue;
}
// Set current API key value
set apiKeyValue(apiKeyValue) {
this._apiKeyValue = apiKeyValue;
}
// Return current submission interval
get metadataSubmissionInterval() {
return this._metadataSubmissionInterval;
}
// Set current submission interval
set metadataSubmissionInterval(metadataSubmissionInterval) {
this._metadataSubmissionInterval = metadataSubmissionInterval;
// Recreate the timer
if (this._submissionTimer !== undefined) {
window.clearInterval(this._submissionTimer);
this._submissionTimer = window.setInterval(
this.submit,
this.metadataSubmissionInterval,
);
}
}
// Return if URL collection is enabled
get collectUrls() {
return this._collectUrls;
}
// Enable/disable URL collection
set collectUrls(collectUrls) {
this._collectUrls = collectUrls;
}
// Return current API key value
set components(components) {
this._components = [
PACKAGE_COMPONENT,
...components,
];
}
get schema() {
return {
version: SCHEMA_VERSION,
components: this._components,
};
}
// Enable periodical metadata submission
enable() {
if (this._submissionTimer === undefined) {
this._submissionTimer = window.setInterval(
this.submit,
this.metadataSubmissionInterval,
);
this.submit();
}
}
// Disable metadata submission
disable() {
if (this._submissionTimer !== undefined) {
window.clearInterval(this._submissionTimer);
this._submissionTimer = undefined;
}
}
submit() {
this.emit('message', this.serializeMetadata());
}
reset() {
this.disable();
this.enable();
}
// Generates a new streamId
generateNewStreamId() {
this._streamId = uuidv4();
}
serializeMetadata() {
return [
METADATA_MESSAGE_TYPE,
TimeUtils.currentTs(),
{
platform: this.platform,
location: this.location,
custom: this.custom,
screen: this.screen,
document: this.document,
},
];
}
// Set custom metadata key/value pair
set(key, value) {
this._customMetadata[key] = value;
this.submit();
}
// Remove custom metadata by key
remove(key) {
delete this._customMetadata[key];
this.submit();
}
// Return custom, user-defined metadata
get custom() {
return clone(this._customMetadata);
}
// Return platform (browser & OS) metadata
// eslint-disable-next-line class-methods-use-this
get platform() {
return {
description: Platform.description,
layout: Platform.layout,
manufacturer: Platform.manufacturer,
name: Platform.name,
prerelease: Platform.prerelease,
product: Platform.product,
ua: Platform.ua,
version: Platform.version,
os: {
architecture: Platform.os.architecture,
family: Platform.os.family,
version: Platform.os.version,
},
};
}
// Return location metadata -- i.e. URL data
get location() {
if (this.collectUrls) {
return {
current: window.location.href,
previous: document.referrer,
};
}
return {};
}
get screen() {
const { screen } = window;
return { // screen metadata
availHeight: screen.availHeight,
availWidth: screen.availWidth,
availLeft: screen.availLeft,
availTop: screen.availTop,
height: screen.height,
width: screen.width,
top: screen.top,
left: screen.left,
colorDepth: screen.colorDepth,
pixelDepth: screen.pixelDepth,
devicePixelRatio: window.devicePixelRatio,
orientation: { ...(this.orientation) },
};
}
// Assemble browser orientation related metadata
// eslint-disable-next-line class-methods-use-this
get orientation() {
let orientationType;
let orientationAngle;
if (window.screen.orientation) {
orientationType = window.screen.orientation.type;
orientationAngle = window.screen.orientation.angle;
} else if (typeof window.orientation === 'number') {
if (Math.abs(window.orientation) === 90) {
orientationType = 'landscape';
} else {
orientationType = 'portrait';
}
orientationAngle = window.orientation;
}
return {
type: orientationType,
angle: orientationAngle,
};
}
// Return document metadata
// eslint-disable-next-line class-methods-use-this
get document() {
const { documentElement } = document;
return {
scrollLeft: documentElement.scrollLeft,
scrollTop: documentElement.scrollTop,
scrollHeight: documentElement.scrollHeight,
scrollWidth: documentElement.scrollWidth,
offsetHeight: documentElement.offsetHeight,
offsetWidth: documentElement.offsetWidth,
clientTop: documentElement.clientTop,
clientLeft: documentElement.clientLeft,
clientHeight: documentElement.clientHeight,
clientWidth: documentElement.clientWidth,
};
}
}
Object.assign(Metadata.prototype, eventEmitterMixin);
export default Metadata;