UNPKG

@thomkjel/logger

Version:

Security-focused event logging library for Next.js applications (Work in Progress)

291 lines (290 loc) 10.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SecurityLoggerPresets = void 0; exports.SecurityLogger = SecurityLogger; const react_1 = __importStar(require("react")); const logger_1 = require("./logger"); const logger = logger_1.Logger.getInstance(); function SecurityLogger({ children, config = {}, apiKey, disabled = false }) { const defaultConfig = { apiLogging: true, authLogging: true, errorLogging: true, navigationLogging: false, captureUserAgent: true, captureIPAddress: false, excludeRoutes: ['/api/health', '/_next'], batchEvents: false, flushInterval: 5000, ...config }; (0, react_1.useEffect)(() => { if (disabled) return; // Configure logger with API key if provided if (apiKey) { logger.configure({ apiKey }); } const cleanup = []; // 1. Auto-track API calls if (defaultConfig.apiLogging) { const apiTracker = setupAPITracking(defaultConfig); cleanup.push(apiTracker); } // 2. Auto-track authentication events if (defaultConfig.authLogging) { const authTracker = setupAuthTracking(defaultConfig); cleanup.push(authTracker); } // 3. Auto-track errors if (defaultConfig.errorLogging) { const errorTracker = setupErrorTracking(defaultConfig); cleanup.push(errorTracker); } // 4. Auto-track navigation (optional) if (defaultConfig.navigationLogging) { const navTracker = setupNavigationTracking(defaultConfig); cleanup.push(navTracker); } // Log that security logging is active logger.info('security_logger_initialized', { config: { apiLogging: defaultConfig.apiLogging, authLogging: defaultConfig.authLogging, errorLogging: defaultConfig.errorLogging }, timestamp: new Date().toISOString() }); // Cleanup on unmount return () => { cleanup.forEach(fn => fn()); logger.info('security_logger_disabled', { timestamp: new Date().toISOString() }); }; }, [disabled, apiKey, defaultConfig]); return react_1.default.createElement(react_1.default.Fragment, null, children); } // API Call Tracking function setupAPITracking(config) { if (typeof window === 'undefined') return () => { }; const originalFetch = window.fetch; window.fetch = async function (url, options) { const urlString = url.toString(); // Skip excluded routes if (config.excludeRoutes?.some(route => urlString.includes(route))) { return originalFetch(url, options); } // Include only specified routes if configured if (config.includeOnlyRoutes && !config.includeOnlyRoutes.some(route => urlString.includes(route))) { return originalFetch(url, options); } const startTime = Date.now(); const metadata = { url: urlString, method: options?.method || 'GET', timestamp: new Date().toISOString() }; // Add user agent if enabled if (config.captureUserAgent) { metadata.userAgent = navigator.userAgent; } try { logger.info('api_request_start', metadata); const response = await originalFetch(url, options); const duration = Date.now() - startTime; const responseMetadata = { ...metadata, status: response.status, success: response.ok, duration: `${duration}ms` }; if (response.ok) { logger.info('api_request_success', responseMetadata); } else { logger.warn('api_request_failure', { ...responseMetadata, statusText: response.statusText }); } return response; } catch (error) { const duration = Date.now() - startTime; logger.error('api_request_error', { ...metadata, error: error instanceof Error ? error.message : 'Unknown error', duration: `${duration}ms` }); throw error; } }; // Return cleanup function return () => { window.fetch = originalFetch; }; } // Authentication Event Tracking function setupAuthTracking(config) { if (typeof window === 'undefined') return () => { }; // Track localStorage changes for auth tokens const originalSetItem = localStorage.setItem; localStorage.setItem = function (key, value) { if (key.includes('token') || key.includes('auth') || key.includes('session')) { logger.info('session_token_set', { tokenType: key, timestamp: new Date().toISOString() }); } return originalSetItem.call(this, key, value); }; const originalRemoveItem = localStorage.removeItem; localStorage.removeItem = function (key) { if (key.includes('token') || key.includes('auth') || key.includes('session')) { logger.warn('session_token_removed', { tokenType: key, reason: 'explicit_removal', timestamp: new Date().toISOString() }); } return originalRemoveItem.call(this, key); }; // Return cleanup function return () => { localStorage.setItem = originalSetItem; localStorage.removeItem = originalRemoveItem; }; } // Error Tracking function setupErrorTracking(config) { if (typeof window === 'undefined') return () => { }; // Global error handler const handleError = (event) => { logger.error('javascript_error', { error: event.error?.message || event.message, filename: event.filename, line: event.lineno, column: event.colno, stack: event.error?.stack, timestamp: new Date().toISOString() }); }; // Unhandled promise rejections const handleRejection = (event) => { logger.error('unhandled_promise_rejection', { reason: event.reason, timestamp: new Date().toISOString() }); }; window.addEventListener('error', handleError); window.addEventListener('unhandledrejection', handleRejection); // Return cleanup function return () => { window.removeEventListener('error', handleError); window.removeEventListener('unhandledrejection', handleRejection); }; } // Navigation Tracking (optional) function setupNavigationTracking(config) { if (typeof window === 'undefined') return () => { }; // Track page changes (for SPAs) let currentPath = window.location.pathname; const trackNavigation = () => { const newPath = window.location.pathname; if (newPath !== currentPath) { logger.info('page_navigation', { from: currentPath, to: newPath, timestamp: new Date().toISOString() }); currentPath = newPath; } }; // Listen for navigation events window.addEventListener('popstate', trackNavigation); // Override pushState and replaceState for SPA navigation const originalPushState = history.pushState; history.pushState = function (...args) { const result = originalPushState.apply(this, args); trackNavigation(); return result; }; const originalReplaceState = history.replaceState; history.replaceState = function (...args) { const result = originalReplaceState.apply(this, args); trackNavigation(); return result; }; // Return cleanup function return () => { window.removeEventListener('popstate', trackNavigation); history.pushState = originalPushState; history.replaceState = originalReplaceState; }; } // Export convenience configurations exports.SecurityLoggerPresets = { minimal: { apiLogging: true, authLogging: true, errorLogging: true, navigationLogging: false }, standard: { apiLogging: true, authLogging: true, errorLogging: true, navigationLogging: true, captureUserAgent: true }, comprehensive: { apiLogging: true, authLogging: true, errorLogging: true, navigationLogging: true, captureUserAgent: true, captureIPAddress: false, // Usually handled server-side batchEvents: true, flushInterval: 3000 } }; exports.default = SecurityLogger;