UNPKG

@perceptr/web-sdk

Version:

Perceptr Web SDK for recording and monitoring user sessions

239 lines (238 loc) 9.95 kB
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()); }); }; import { NetworkMonitor } from "./NetworkMonitor"; import { SessionRecorder } from "./SessionRecorder"; import { PerformanceMonitor } from "./PerformanceMonitor"; import { EventBuffer } from "./EventBuffer"; import { ApiService } from "./common/services/ApiService"; import { scheduleIdleTask } from "./utils/sessionrecording-utils"; import { ErrorCode, emitError } from "./utils/errors"; import { logger } from "./utils/logger"; export 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(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(this.config.session), networkMonitor: new NetworkMonitor(this.config.network, this.startTime), }; this.performanceMonitor = new PerformanceMonitor((_a = this.config.performance) === null || _a === void 0 ? void 0 : _a.memoryLimit, () => this.handleMemoryLimit()); this.eventBuffer = new EventBuffer((_b = this.config.session) !== null && _b !== void 0 ? _b : {}, (buffer) => this.sendBufferToServer(buffer)); if (this.config.debug) { this.setupDebugListeners(); } this.isInitialized = true; logger.debug("SDK initialized successfully"); } catch (error) { emitError({ code: 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.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) { emitError({ code: 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.error(`error: ${code}: ${message}`, context); }); 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 scheduleIdleTask(() => { this.safelyEnableComponent("networkMonitor"); }); // Start critical components immediately this.safelyEnableComponent("sessionRecorder", "startSession"); this.isEnabled = true; logger.debug("Recording started"); } catch (error) { emitError({ code: 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) { emitError({ code: ErrorCode.API_ERROR, message: `Failed to ${method} ${componentName}`, originalError: error, context: { component: componentName }, }); 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) => { 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.debug("Recording stopped and data exported"); } catch (error) { emitError({ code: ErrorCode.EXPORT_FAILED, message: "Failed to export session data", originalError: error, }); reject(error); } }); }); } catch (error) { emitError({ code: ErrorCode.API_ERROR, message: "Failed to stop recording", originalError: error, }); throw error; } }); } // Handle memory limit exceeded handleMemoryLimit() { emitError({ code: 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(); } } export default Core;