UNPKG

@perceptr/web-sdk

Version:

Perceptr Web SDK for recording and monitoring user sessions

243 lines (242 loc) 10.5 kB
"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;