UNPKG

react-azure-config

Version:

🚀 The Ultimate Multi-App Configuration Library! CRITICAL BUG FIXES: Prefixed environment keys no longer sent to Azure. Complete architectural redesign with bulletproof fallback system. Enterprise-grade Azure integration and monorepo support.

511 lines (505 loc) • 19.2 kB
'use strict'; var React = require('react'); const getEnvVar = (key, defaultValue = '') => { try { return process.env[key] || defaultValue; } catch { return defaultValue; } }; const environment = { isServer: typeof window === 'undefined', isClient: typeof window !== 'undefined', isBrowser: typeof window !== 'undefined' && typeof window.document !== 'undefined', isDevelopment: getEnvVar('NODE_ENV') === 'development', isProduction: getEnvVar('NODE_ENV') === 'production', isTest: getEnvVar('NODE_ENV') === 'test' }; class Logger { constructor(config = {}) { const defaultLevel = this.getDefaultLogLevel(); const defaultPrefix = getEnvVar('LOG_PREFIX', '[ReactAzureConfig]'); this.config = { level: getEnvVar('LOG_LEVEL') || defaultLevel, prefix: defaultPrefix, enableTimestamp: environment.isProduction, enableColors: !environment.isProduction, ...config }; } getDefaultLogLevel() { if (environment.isProduction) return 'warn'; if (environment.isTest) return 'silent'; return 'debug'; } formatMessage(level, message) { let formatted = `${this.config.prefix} ${message}`; if (this.config.enableTimestamp) { const timestamp = new Date().toISOString(); formatted = `[${timestamp}] ${formatted}`; } if (this.config.enableColors && typeof window === 'undefined') { const colors = { debug: '\x1b[36m', info: '\x1b[32m', warn: '\x1b[33m', error: '\x1b[31m', silent: '' }; const reset = '\x1b[0m'; formatted = `${colors[level]}${formatted}${reset}`; } return formatted; } shouldLog(level) { const levels = ['debug', 'info', 'warn', 'error', 'silent']; const currentLevelIndex = levels.indexOf(this.config.level); const targetLevelIndex = levels.indexOf(level); return targetLevelIndex >= currentLevelIndex && this.config.level !== 'silent'; } debug(message, ...args) { if (this.shouldLog('debug')) { console.debug(this.formatMessage('debug', message), ...args); } } info(message, ...args) { if (this.shouldLog('info')) { console.info(this.formatMessage('info', message), ...args); } } warn(message, ...args) { if (this.shouldLog('warn')) { console.warn(this.formatMessage('warn', message), ...args); } } error(message, ...args) { if (this.shouldLog('error')) { console.error(this.formatMessage('error', message), ...args); } } setLevel(level) { this.config.level = level; } } const logger = new Logger(); const AppInsightsContext = React.createContext(null); function AppInsightsProvider({ children, config: providedConfig, connectionString: overrideConnectionString }) { const [isClient, setIsClient] = React.useState(false); React.useEffect(() => { setIsClient(true); }, []); const [appInsights, setAppInsights] = React.useState(null); const [reactPlugin, setReactPlugin] = React.useState(null); const [isInitialized, setIsInitialized] = React.useState(false); const [error, setError] = React.useState(null); const [currentConfig, setCurrentConfig] = React.useState(null); const connectionString = overrideConnectionString || providedConfig?.connectionString; const initializeAppInsights = React.useCallback(async (connString, config) => { if (typeof window === 'undefined') { logger.debug('Skipping Application Insights initialization during SSR'); return; } try { logger.info('Initializing Application Insights', { hasConnectionString: !!connString, enableAutoRouteTracking: config.enableAutoRouteTracking }); const [webModule, reactModule] = await Promise.all([ import('@microsoft/applicationinsights-web').catch(() => null), import('@microsoft/applicationinsights-react-js').catch(() => null) ]); const ApplicationInsights = webModule && ('ApplicationInsights' in webModule) ? webModule.ApplicationInsights : webModule && ('default' in webModule) ? webModule.default?.ApplicationInsights : null; const ReactPlugin = reactModule && ('ReactPlugin' in reactModule) ? reactModule.ReactPlugin : reactModule && ('default' in reactModule) ? reactModule.default?.ReactPlugin : null; if (!ApplicationInsights) { const errorMsg = 'Application Insights packages not installed. Install @microsoft/applicationinsights-web and @microsoft/applicationinsights-react-js'; setError(errorMsg); logger.debug(errorMsg); return; } let plugin = null; if (config.enableReactPlugin !== false && ReactPlugin) { plugin = new ReactPlugin(); setReactPlugin(plugin); } const appInsightsConfig = { connectionString: connString, enableAutoRouteTracking: config.enableAutoRouteTracking, enableCookiesUsage: config.enableCookiesUsage, enableRequestHeaderTracking: config.enableRequestHeaderTracking, enableResponseHeaderTracking: config.enableResponseHeaderTracking, extensions: plugin ? [plugin] : undefined, ...config.additionalConfig }; const appInsightsInstance = new ApplicationInsights({ config: appInsightsConfig }); appInsightsInstance.loadAppInsights(); if (typeof window !== 'undefined') { appInsightsInstance.trackPageView(); } setAppInsights(appInsightsInstance); setIsInitialized(true); setError(null); logger.info('Application Insights initialized successfully'); } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to initialize Application Insights'; setError(errorMessage); setIsInitialized(false); logger.error('Failed to initialize Application Insights', err); } }, []); React.useEffect(() => { if (!isClient) { logger.debug('Skipping Application Insights initialization during SSR'); return; } if (!connectionString) { logger.debug('No Application Insights connection string found, skipping initialization'); return; } const config = { connectionString, autoInitialize: true, enableReactPlugin: true, enableAutoRouteTracking: true, enableCookiesUsage: false, enableRequestHeaderTracking: false, enableResponseHeaderTracking: false, ...providedConfig }; setCurrentConfig(config); if (config.autoInitialize !== false && !isInitialized && !appInsights) { logger.debug('Auto-initializing Application Insights with provided configuration'); initializeAppInsights(connectionString, config); } }, [ isClient, connectionString, providedConfig, initializeAppInsights, isInitialized, appInsights ]); const contextValue = { appInsights, reactPlugin, isInitialized, config: currentConfig, error, connectionString: connectionString || null }; return (React.createElement(AppInsightsContext.Provider, { value: contextValue }, children)); } const useAppInsightsContext = () => { const context = React.useContext(AppInsightsContext); if (!context) { throw new Error('useAppInsightsContext must be used within an AppInsightsProvider'); } return context; }; const useAppInsightsAvailable = () => { const [isAvailable, setIsAvailable] = React.useState(false); React.useEffect(() => { if (typeof window === 'undefined') { return; } const checkAvailability = async () => { try { const [webModule, reactModule] = await Promise.all([ import('@microsoft/applicationinsights-web').catch(() => null), import('@microsoft/applicationinsights-react-js').catch(() => null) ]); const hasWebModule = !!(webModule && (('ApplicationInsights' in webModule && webModule.ApplicationInsights) || ('default' in webModule && webModule.default?.ApplicationInsights))); const hasReactModule = !!(reactModule && (('ReactPlugin' in reactModule && reactModule.ReactPlugin) || ('default' in reactModule && reactModule.default?.ReactPlugin))); setIsAvailable(hasWebModule && hasReactModule); } catch { setIsAvailable(false); logger.debug('Application Insights packages not available - install @microsoft/applicationinsights-web and @microsoft/applicationinsights-react-js to enable telemetry'); } }; checkAvailability(); }, []); return isAvailable; }; const useSafeAppInsightsContext = () => { try { return useAppInsightsContext(); } catch (error) { return { appInsights: null, reactPlugin: null, isInitialized: false, config: null, error: null, connectionString: null }; } }; const useAppInsights = () => { const context = useSafeAppInsightsContext(); const { appInsights, isInitialized, connectionString } = context; const trackEvent = React.useCallback((event) => { if (!appInsights || !isInitialized) { logger.debug('Application Insights not ready, skipping event tracking', { eventName: event.name }); return; } try { appInsights.trackEvent(event, event.properties); logger.debug('Event tracked successfully', { eventName: event.name }); } catch (error) { logger.error('Failed to track event', error); } }, [appInsights, isInitialized]); const trackException = React.useCallback((exceptionData) => { if (!appInsights || !isInitialized) { logger.debug('Application Insights not ready, skipping exception tracking', { error: exceptionData.exception.message }); return; } try { appInsights.trackException({ exception: exceptionData.exception, severityLevel: exceptionData.severityLevel, properties: exceptionData.properties, measurements: exceptionData.measurements }); logger.debug('Exception tracked successfully', { error: exceptionData.exception.message }); } catch (error) { logger.error('Failed to track exception', error); } }, [appInsights, isInitialized]); const trackPageView = React.useCallback((pageViewData) => { if (!appInsights || !isInitialized) { logger.debug('Application Insights not ready, skipping page view tracking'); return; } try { if (pageViewData) { appInsights.trackPageView({ name: pageViewData.name, uri: pageViewData.url, properties: pageViewData.properties, measurements: pageViewData.measurements }); } else { appInsights.trackPageView(); } logger.debug('Page view tracked successfully'); } catch (error) { logger.error('Failed to track page view', error); } }, [appInsights, isInitialized]); const trackMetric = React.useCallback((name, value, properties) => { if (!appInsights || !isInitialized) { logger.debug('Application Insights not ready, skipping metric tracking', { metricName: name }); return; } try { appInsights.trackMetric({ name, average: value }, properties); logger.debug('Metric tracked successfully', { metricName: name, value }); } catch (error) { logger.error('Failed to track metric', error); } }, [appInsights, isInitialized]); const setContext = React.useCallback((telemetryContext) => { if (!appInsights || !isInitialized) { logger.debug('Application Insights not ready, skipping context setup'); return; } try { if (telemetryContext.userId) { appInsights.setAuthenticatedUserContext(telemetryContext.userId); } if (telemetryContext.properties) { Object.entries(telemetryContext.properties).forEach(([key, value]) => { appInsights.addTelemetryInitializer((envelope) => { envelope.data = envelope.data || {}; envelope.data.baseData = envelope.data.baseData || {}; envelope.data.baseData.properties = envelope.data.baseData.properties || {}; envelope.data.baseData.properties[key] = value; return true; }); }); } if (telemetryContext.appVersion) { appInsights.addTelemetryInitializer((envelope) => { envelope.tags = envelope.tags || {}; envelope.tags['ai.application.ver'] = telemetryContext.appVersion; return true; }); } if (telemetryContext.environment) { appInsights.addTelemetryInitializer((envelope) => { envelope.tags = envelope.tags || {}; envelope.tags['ai.cloud.role'] = telemetryContext.environment; return true; }); } logger.debug('Telemetry context set successfully'); } catch (error) { logger.error('Failed to set telemetry context', error); } }, [appInsights, isInitialized]); return { appInsights, trackEvent, trackException, trackPageView, trackMetric, setContext, isReady: isInitialized, connectionString }; }; const useTrackEvent = () => { const { trackEvent, isReady } = useAppInsights(); return React.useCallback((eventName, properties) => { if (!isReady) return; trackEvent({ name: eventName, properties: { timestamp: new Date().toISOString(), ...properties } }); }, [trackEvent, isReady]); }; const useTrackException = () => { const { trackException, isReady } = useAppInsights(); return React.useCallback((error, context) => { if (!isReady) return; trackException({ exception: error, severityLevel: 3, properties: { timestamp: new Date().toISOString(), stack: error.stack, ...context } }); }, [trackException, isReady]); }; const useTrackPageView = () => { const { trackPageView, isReady } = useAppInsights(); React.useEffect(() => { if (isReady) { trackPageView({ name: document.title, url: window.location.href, properties: { timestamp: new Date().toISOString(), userAgent: navigator.userAgent } }); } }, [trackPageView, isReady]); return React.useCallback((pageName, additionalProperties) => { if (!isReady) return; trackPageView({ name: pageName || document.title, url: window.location.href, properties: { timestamp: new Date().toISOString(), ...additionalProperties } }); }, [trackPageView, isReady]); }; const useInsightsConfig = () => { const isAvailable = useAppInsightsAvailable(); const [defaultConfig] = React.useState({ autoInitialize: false, enableReactPlugin: false, enableAutoRouteTracking: false, enableCookiesUsage: false, enableRequestHeaderTracking: false, enableResponseHeaderTracking: false }); const context = useSafeAppInsightsContext(); if (!isAvailable) { return { ...defaultConfig, isConfigured: false, isInitialized: false, error: 'Application Insights packages not installed' }; } const { config, isInitialized, error, connectionString } = context; return { ...(config || defaultConfig), isConfigured: !!connectionString, isInitialized, error }; }; const useTrackPerformance = () => { const { trackMetric, isReady } = useAppInsights(); const trackTiming = React.useCallback((name, duration, properties) => { if (!isReady) return; trackMetric(`${name}_duration_ms`, duration, { timestamp: new Date().toISOString(), ...properties }); }, [trackMetric, isReady]); const createTimer = React.useCallback((name) => { const startTime = Date.now(); return { stop: (properties) => { const duration = Date.now() - startTime; trackTiming(name, duration, properties); return duration; } }; }, [trackTiming]); const trackCounter = React.useCallback((name, value = 1, properties) => { if (!isReady) return; trackMetric(`${name}_count`, value, { timestamp: new Date().toISOString(), ...properties }); }, [trackMetric, isReady]); return { trackTiming, createTimer, trackCounter, isReady }; }; exports.AppInsightsProvider = AppInsightsProvider; exports.useAppInsights = useAppInsights; exports.useAppInsightsAvailable = useAppInsightsAvailable; exports.useInsightsConfig = useInsightsConfig; exports.useTrackEvent = useTrackEvent; exports.useTrackException = useTrackException; exports.useTrackPageView = useTrackPageView; exports.useTrackPerformance = useTrackPerformance;