UNPKG

avi-analytics-sdk

Version:

An analytics SDK for capturing user interactions

177 lines (176 loc) 7.64 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 axios from "axios"; import * as pako from "pako"; import { EVENT_SEND_INTERVAL, EVENTS_ENDPOINT, OBSERVABILITY_ENDPOINT, RECORDING_ENDPOINT, } from "../config/constants"; let captureEventCallback; const eventQueue = []; let initialized = false; // Helper to batch and send events function flushEvents(sessionId) { if (eventQueue.length === 0) return; // Prepare the batch payload const batchPayload = eventQueue.map((event) => ({ session_id: sessionId, event_type: event.type, method: event.method || null, url: event.url || null, status: event.status || null, headers: event.headers ? JSON.stringify(event.headers) : null, body: event.body ? JSON.stringify(event.body) : null, timestamp: event.timestamp, })); // Clear the queue before sending eventQueue.length = 0; // Compress the payload const compressedPayload = pako.deflate(JSON.stringify(batchPayload)); // Send the batch axios .post(OBSERVABILITY_ENDPOINT, compressedPayload, { headers: { "Content-Encoding": "deflate", "Content-Type": "application/octet-stream", }, }) .catch((err) => { console.error("Failed to send observability events:", err); }); } function setupNetworkInterception(excludedUrls) { // Intercept fetch const originalFetch = window.fetch; window.fetch = (...args) => __awaiter(this, void 0, void 0, function* () { var _a; const [url, options] = args; // Exclude calls made to excluded URLs if (excludedUrls.some((excludedUrl) => url.toString().includes(excludedUrl))) { return originalFetch(...args); } // Log request captureEventCallback === null || captureEventCallback === void 0 ? void 0 : captureEventCallback({ type: "request", method: (_a = options === null || options === void 0 ? void 0 : options.method) !== null && _a !== void 0 ? _a : "GET", url: url.toString(), headers: (options === null || options === void 0 ? void 0 : options.headers) || {}, payload: options === null || options === void 0 ? void 0 : options.body, timestamp: new Date().toISOString(), }); try { const response = yield originalFetch(...args); const clonedResponse = response.clone(); let responseBody; try { responseBody = yield clonedResponse.text(); } catch (e) { responseBody = null; } // Log response const headerEntries = [...response.headers.entries()].reduce((acc, [key, value]) => { acc[key] = value; return acc; }, {}); captureEventCallback === null || captureEventCallback === void 0 ? void 0 : captureEventCallback({ type: "response", url: url.toString(), status: response.status, headers: JSON.stringify(headerEntries), body: responseBody, timestamp: new Date().toISOString(), }); return response; } catch (error) { // Log error captureEventCallback === null || captureEventCallback === void 0 ? void 0 : captureEventCallback({ type: "error", url: url.toString(), message: error instanceof Error ? error.message : "Unknown error", timestamp: new Date().toISOString(), }); throw error; } }); // Intercept XMLHttpRequest const originalXHR = window.XMLHttpRequest; class CustomXHR extends XMLHttpRequest { constructor() { super(); this.requestMethod = ""; this.requestUrl = ""; this.setupEventListeners(); } setupEventListeners() { this.addEventListener("loadstart", () => { if (!excludedUrls.some((excludedUrl) => this.requestUrl.includes(excludedUrl))) { captureEventCallback === null || captureEventCallback === void 0 ? void 0 : captureEventCallback({ type: "request", method: this.requestMethod || "GET", url: this.requestUrl, payload: this._requestData, timestamp: new Date().toISOString(), }); } }); this.addEventListener("loadend", () => { if (!excludedUrls.some((excludedUrl) => this.requestUrl.includes(excludedUrl))) { captureEventCallback === null || captureEventCallback === void 0 ? void 0 : captureEventCallback({ type: "response", url: this.requestUrl, status: this.status, headers: this.getAllResponseHeaders(), body: this.responseText, timestamp: new Date().toISOString(), }); } }); this.addEventListener("error", () => { if (!excludedUrls.some((excludedUrl) => this.requestUrl.includes(excludedUrl))) { captureEventCallback === null || captureEventCallback === void 0 ? void 0 : captureEventCallback({ type: "error", url: this.requestUrl, timestamp: new Date().toISOString(), }); } }); } open(method, url, async = true, username, password) { this.requestMethod = method; this.requestUrl = url.toString(); super.open(method, url, async, username || null, password || null); } send(data) { this._requestData = data; super.send(data); } } window.XMLHttpRequest = CustomXHR; } export function initializeObservability({ apiKey, sessionId, excludedUrls = [OBSERVABILITY_ENDPOINT, EVENTS_ENDPOINT, RECORDING_ENDPOINT], // Default to exclude OBSERVABILITY_ENDPOINT itself }) { if (!apiKey || !sessionId) { throw new Error("API key, session ID, and API endpoint are required to initialize observability."); } if (initialized) { console.warn("Observability is already initialized."); return; } initialized = true; // Set up event capturing callback captureEventCallback = (event) => { eventQueue.push(event); }; // Start the interval to flush events every 5 seconds setInterval(() => flushEvents(sessionId), EVENT_SEND_INTERVAL); // Setup network interception setupNetworkInterception(excludedUrls); console.log("Observability initialized with API Key:", apiKey); }