UNPKG

@bugspotter/sdk

Version:

Professional bug reporting SDK with screenshots, session replay, and automatic error capture for web applications

246 lines (245 loc) 8.29 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.compressData = compressData; exports.decompressData = decompressData; exports.compressImage = compressImage; exports.getCompressionRatio = getCompressionRatio; exports.estimateSize = estimateSize; exports.resetCompressionCache = resetCompressionCache; const pako_1 = __importDefault(require("pako")); const logger_1 = require("../utils/logger"); /** * Compression utilities for BugSpotter SDK * Handles payload and image compression using browser-native APIs */ const logger = (0, logger_1.getLogger)(); // Configuration constants const COMPRESSION_DEFAULTS = { GZIP_LEVEL: 6, // Balanced speed/size ratio (0-9) IMAGE_MAX_WIDTH: 1920, IMAGE_MAX_HEIGHT: 1080, IMAGE_WEBP_QUALITY: 0.8, IMAGE_JPEG_QUALITY: 0.85, IMAGE_LOAD_TIMEOUT: 3000, // milliseconds }; // Singleton instances for performance let textEncoder = null; let textDecoder = null; let webpSupportCache = null; /** * Get or create TextEncoder instance */ function getTextEncoder() { if (!textEncoder) { textEncoder = new TextEncoder(); } return textEncoder; } /** * Get or create TextDecoder instance */ function getTextDecoder() { if (!textDecoder) { textDecoder = new TextDecoder(); } return textDecoder; } /** * Convert data to string representation */ function dataToString(data) { return typeof data === 'string' ? data : JSON.stringify(data); } /** * Compress JSON or string data using gzip * @param data - Data to compress (will be JSON stringified if object) * @param config - Optional compression configuration * @returns Compressed data as Uint8Array */ async function compressData(data, config) { var _a; try { const jsonString = dataToString(data); const encoder = getTextEncoder(); const uint8Data = encoder.encode(jsonString); const gzipLevel = (_a = config === null || config === void 0 ? void 0 : config.gzipLevel) !== null && _a !== void 0 ? _a : COMPRESSION_DEFAULTS.GZIP_LEVEL; // pako.gzip already returns Uint8Array, no need to wrap it const compressed = pako_1.default.gzip(uint8Data, { level: gzipLevel }); return compressed; } catch (error) { logger.error('Compression failed:', error); throw error; } } /** * Try to parse string as JSON, return string if not valid JSON */ function tryParseJSON(jsonString) { try { return JSON.parse(jsonString); } catch (_a) { return jsonString; } } /** * Decompress gzipped data back to original format * Useful for testing and verification * @param compressed - Compressed Uint8Array data * @param config - Optional configuration * @returns Decompressed and parsed data (or string if input was string) */ function decompressData(compressed, config) { try { const decompressed = pako_1.default.ungzip(compressed); const decoder = getTextDecoder(); const jsonString = decoder.decode(decompressed); return tryParseJSON(jsonString); } catch (error) { if ((config === null || config === void 0 ? void 0 : config.verbose) !== false) { (0, logger_1.getLogger)().error('Decompression failed:', error); } throw new Error(`Failed to decompress data: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Check if code is running in browser environment */ function isBrowserEnvironment() { return (typeof document !== 'undefined' && typeof Image !== 'undefined' && typeof HTMLCanvasElement !== 'undefined'); } /** * Check if browser supports WebP format (cached result) */ function supportsWebP() { if (webpSupportCache !== null) { return webpSupportCache; } if (!isBrowserEnvironment()) { webpSupportCache = false; return false; } try { const canvas = document.createElement('canvas'); webpSupportCache = canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0; } catch (_a) { webpSupportCache = false; } return webpSupportCache; } /** * Load image from base64 string with timeout */ function loadImage(base64, timeout) { return new Promise((resolve, reject) => { const img = new Image(); const timer = setTimeout(() => { reject(new Error(`Image load timeout after ${timeout}ms`)); }, timeout); img.onload = () => { clearTimeout(timer); resolve(img); }; img.onerror = () => { clearTimeout(timer); reject(new Error('Failed to load image')); }; img.src = base64; }); } /** * Calculate resized dimensions maintaining aspect ratio */ function calculateResizedDimensions(width, height, maxWidth, maxHeight) { let newWidth = width; let newHeight = height; if (newWidth > maxWidth) { newHeight = (newHeight * maxWidth) / newWidth; newWidth = maxWidth; } if (newHeight > maxHeight) { newWidth = (newWidth * maxHeight) / newHeight; newHeight = maxHeight; } return { width: newWidth, height: newHeight }; } /** * Optimize and compress screenshot image * Converts to WebP if supported, resizes if too large, then compresses * @param base64 - Base64 encoded image (PNG or other format) * @param config - Optional compression configuration * @returns Optimized base64 image string */ async function compressImage(base64, config) { var _a, _b, _c, _d; try { if (!isBrowserEnvironment()) { return base64; } const maxWidth = (_a = config === null || config === void 0 ? void 0 : config.imageMaxWidth) !== null && _a !== void 0 ? _a : COMPRESSION_DEFAULTS.IMAGE_MAX_WIDTH; const maxHeight = (_b = config === null || config === void 0 ? void 0 : config.imageMaxHeight) !== null && _b !== void 0 ? _b : COMPRESSION_DEFAULTS.IMAGE_MAX_HEIGHT; const webpQuality = (_c = config === null || config === void 0 ? void 0 : config.webpQuality) !== null && _c !== void 0 ? _c : COMPRESSION_DEFAULTS.IMAGE_WEBP_QUALITY; const jpegQuality = (_d = config === null || config === void 0 ? void 0 : config.jpegQuality) !== null && _d !== void 0 ? _d : COMPRESSION_DEFAULTS.IMAGE_JPEG_QUALITY; const timeout = COMPRESSION_DEFAULTS.IMAGE_LOAD_TIMEOUT; const img = await loadImage(base64, timeout); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) { throw new Error('Failed to get 2D canvas context'); } const { width, height } = calculateResizedDimensions(img.width, img.height, maxWidth, maxHeight); canvas.width = width; canvas.height = height; ctx.drawImage(img, 0, 0, width, height); if (supportsWebP()) { return canvas.toDataURL('image/webp', webpQuality); } else { return canvas.toDataURL('image/jpeg', jpegQuality); } } catch (error) { if ((config === null || config === void 0 ? void 0 : config.verbose) !== false) { (0, logger_1.getLogger)().error('Image compression failed:', error); } return base64; } } /** * Calculate compression ratio for analytics * @param originalSize - Original data size in bytes * @param compressedSize - Compressed data size in bytes * @returns Compression ratio as percentage (0-100) */ function getCompressionRatio(originalSize, compressedSize) { if (originalSize <= 0) { return 0; } return Math.round((1 - compressedSize / originalSize) * 100); } /** * Estimate payload size before compression * @param data - Data to estimate * @returns Estimated size in bytes */ function estimateSize(data) { const jsonString = dataToString(data); return getTextEncoder().encode(jsonString).length; } /** * Reset cached instances (useful for testing) * @internal */ function resetCompressionCache() { textEncoder = null; textDecoder = null; webpSupportCache = null; }