@perceptr/web-sdk
Version:
Perceptr Web SDK for recording and monitoring user sessions
243 lines (242 loc) • 10.5 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Core = void 0;
const NetworkMonitor_1 = require("./NetworkMonitor");
const SessionRecorder_1 = require("./SessionRecorder");
const PerformanceMonitor_1 = require("./PerformanceMonitor");
const EventBuffer_1 = require("./EventBuffer");
const ApiService_1 = require("./common/services/ApiService");
const sessionrecording_utils_1 = require("./utils/sessionrecording-utils");
const errors_1 = require("./utils/errors");
const logger_1 = require("./utils/logger");
class Core {
constructor(config) {
this.isEnabled = false;
this.eventListeners = [];
this.isInitialized = false;
this.config = config;
this.initPromise = this.init();
}
init() {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
try {
this.apiService = new ApiService_1.ApiService(this.config);
const valid = yield this.apiService.checkValidProjectId();
if (!valid) {
throw new Error(`Invalid project ID: ${this.config.projectId}`);
}
this.startTime = Date.now();
this.eventListeners = [];
this.userIdentity = this.config.userIdentity;
this.components = {
sessionRecorder: new SessionRecorder_1.SessionRecorder(this.config.session),
networkMonitor: new NetworkMonitor_1.NetworkMonitor(this.config.network, this.startTime),
};
this.performanceMonitor = new PerformanceMonitor_1.PerformanceMonitor((_a = this.config.performance) === null || _a === void 0 ? void 0 : _a.memoryLimit, () => this.handleMemoryLimit());
this.eventBuffer = new EventBuffer_1.EventBuffer((_b = this.config.session) !== null && _b !== void 0 ? _b : {}, (buffer) => this.sendBufferToServer(buffer));
if (this.config.debug) {
this.setupDebugListeners();
}
this.isInitialized = true;
logger_1.logger.debug("SDK initialized successfully");
}
catch (error) {
(0, errors_1.emitError)({
code: errors_1.ErrorCode.API_ERROR,
message: "Failed to initialize SDK",
originalError: error,
});
throw error;
}
});
}
/**
* Identify the current user
* @param distinctId - Unique identifier for the user
* @param traits - Additional user properties
*/
identify(distinctId_1) {
return __awaiter(this, arguments, void 0, function* (distinctId, traits = {}) {
try {
yield this.initPromise;
if (!this.isInitialized) {
throw new Error("[SDK] Not properly initialized");
}
this.userIdentity = Object.assign({ distinctId }, traits);
logger_1.logger.debug(`User identified: ${distinctId}`, traits);
// If we have an active buffer, update it with the user identity
this.eventBuffer.setUserIdentity(this.userIdentity);
// Send an identify event to the session recorder
if (this.isEnabled && this.components.sessionRecorder) {
this.components.sessionRecorder.addCustomEvent("$identify", Object.assign({ distinctId }, traits));
}
}
catch (error) {
(0, errors_1.emitError)({
code: errors_1.ErrorCode.API_ERROR,
message: "Failed to identify user",
originalError: error,
});
}
});
}
sendBufferToServer(buffer) {
return __awaiter(this, void 0, void 0, function* () {
// Add user identity to the buffer before sending
if (this.userIdentity) {
buffer.userIdentity = this.userIdentity;
}
yield this.apiService.sendEvents(buffer);
});
}
setupDebugListeners() {
window.addEventListener("sdk-error", (event) => {
const { code, message, context } = event.detail;
logger_1.logger.error(`error: ${code}: ${message}`, context);
});
logger_1.logger.debug("Initialized with config:", this.config);
}
start() {
return __awaiter(this, void 0, void 0, function* () {
if (this.isEnabled)
return;
try {
// Wait for initialization to complete
yield this.initPromise;
if (!this.isInitialized) {
throw new Error("[SDK] Not properly initialized");
}
// Set up event listeners for buffer
this.setupEventListeners();
// Start performance monitor
this.performanceMonitor.start();
// Start components using idle scheduling
(0, sessionrecording_utils_1.scheduleIdleTask)(() => {
this.safelyEnableComponent("networkMonitor");
});
// Start critical components immediately
this.safelyEnableComponent("sessionRecorder", "startSession");
this.isEnabled = true;
logger_1.logger.debug("Recording started");
}
catch (error) {
(0, errors_1.emitError)({
code: errors_1.ErrorCode.RECORDING_FAILED,
message: "Failed to start recording",
originalError: error,
});
throw error;
}
});
}
setupEventListeners() {
// Subscribe to events from each component
this.eventListeners.push(this.components.sessionRecorder.onEvent((event) => {
this.eventBuffer.addEvent(event);
}));
this.eventListeners.push(this.components.networkMonitor.onRequest((request) => {
this.eventBuffer.addEvent(request);
}));
}
safelyEnableComponent(componentName, method = "enable") {
try {
const component = this.components[componentName];
if (method === "startSession" && componentName === "sessionRecorder") {
component.startSession();
}
else if (method === "enable") {
component.enable();
}
}
catch (error) {
(0, errors_1.emitError)({
code: errors_1.ErrorCode.API_ERROR,
message: `Failed to ${method} ${componentName}`,
originalError: error,
context: { component: componentName },
});
logger_1.logger.warn(`Failed to ${method} ${componentName}:`, error);
}
}
stop() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isEnabled) {
throw new Error("[SDK] is not enabled");
}
try {
// Force flush to ensure data is sent
yield this.eventBuffer.flush(true);
// Aggregate and export data
return new Promise((resolve, reject) => {
(0, sessionrecording_utils_1.scheduleIdleTask)(() => {
try {
this.isEnabled = false;
// Stop all components
this.eventListeners.forEach((listener) => listener());
this.eventBuffer.destroy();
this.performanceMonitor.stop();
this.components.sessionRecorder.stopSession();
this.components.networkMonitor.disable();
// Export the session data
resolve();
logger_1.logger.debug("Recording stopped and data exported");
}
catch (error) {
(0, errors_1.emitError)({
code: errors_1.ErrorCode.EXPORT_FAILED,
message: "Failed to export session data",
originalError: error,
});
reject(error);
}
});
});
}
catch (error) {
(0, errors_1.emitError)({
code: errors_1.ErrorCode.API_ERROR,
message: "Failed to stop recording",
originalError: error,
});
throw error;
}
});
}
// Handle memory limit exceeded
handleMemoryLimit() {
(0, errors_1.emitError)({
code: errors_1.ErrorCode.MEMORY_LIMIT_EXCEEDED,
message: "Memory limit exceeded, pausing session recording",
context: { limit: this.performanceMonitor.getMemoryLimit() },
});
this.pause();
}
pause() {
if (!this.isEnabled)
return;
this.isEnabled = false;
this.components.sessionRecorder.pause();
this.components.networkMonitor.disable();
this.performanceMonitor.stop();
}
resume() {
if (this.isEnabled)
return;
this.isEnabled = true;
this.components.sessionRecorder.resume();
this.components.networkMonitor.enable();
this.performanceMonitor.start();
}
}
exports.Core = Core;
exports.default = Core;