diff --git a/EssentialCSharp.Web/wwwroot/js/appinsights-manager.js b/EssentialCSharp.Web/wwwroot/js/appinsights-manager.js index 6b9846b8..f92501a2 100644 --- a/EssentialCSharp.Web/wwwroot/js/appinsights-manager.js +++ b/EssentialCSharp.Web/wwwroot/js/appinsights-manager.js @@ -5,6 +5,11 @@ (function () { const SDK_URL = "https://js.monitor.azure.com/scripts/b/ai.3.gbl.min.js"; const CONSENT_EVENT = "ecs:consent-changed"; + const DistributedTracingModes = { + AI: 0, + AI_AND_W3C: 1, + W3C: 2 + }; let appInsights = null; let sdkLoadPromise = null; @@ -15,6 +20,25 @@ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; } + function getTryDotNetOrigin() { + const value = window.TRYDOTNET_ORIGIN; + return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; + } + + function getCorrelationHeaderDomains() { + const domains = [window.location.hostname]; + const tryDotNetOrigin = getTryDotNetOrigin(); + if (tryDotNetOrigin) { + try { + domains.push(new URL(tryDotNetOrigin).hostname); + } catch (error) { + console.warn("Ignoring invalid TryDotNet origin for App Insights correlation:", error); + } + } + + return Array.from(new Set(domains.filter(Boolean))); + } + function hasAnalyticsConsent() { if (window.consentManager && typeof window.consentManager.hasAnalyticsConsent === "function") { return window.consentManager.hasAnalyticsConsent(); @@ -113,7 +137,11 @@ const instance = new window.Microsoft.ApplicationInsights.ApplicationInsights({ config: { connectionString, - disableAjaxTracking: true, // avoid duplicate/debatable dependency telemetry from browser fetch/XHR + disableAjaxTracking: false, + disableFetchTracking: false, + distributedTracingMode: DistributedTracingModes.W3C, + correlationHeaderDomains: getCorrelationHeaderDomains(), + enableCorsCorrelation: true, disableTelemetry: false } }); diff --git a/EssentialCSharp.Web/wwwroot/js/chat-module.js b/EssentialCSharp.Web/wwwroot/js/chat-module.js index fae834df..a5d05086 100644 --- a/EssentialCSharp.Web/wwwroot/js/chat-module.js +++ b/EssentialCSharp.Web/wwwroot/js/chat-module.js @@ -12,6 +12,17 @@ const errorIconClassByType = { 'connection-error': 'fas fa-plug' }; +function getTraceHeaders() { + const headers = {}; + if (typeof window.ecsGetCorrelationContext === 'function') { + const traceparent = window.ecsGetCorrelationContext(); + if (typeof traceparent === 'string' && traceparent.length > 0) { + headers.traceparent = traceparent; + } + } + return headers; +} + // hCaptcha integration — invisible widget renders once and is reused across messages. // When HCAPTCHA_SITE_KEY is null (dev / captcha not configured), all captcha calls are no-ops. const captchaSiteKey = window.HCAPTCHA_SITE_KEY || null; @@ -338,6 +349,7 @@ export function useChatWidget() { method: 'POST', headers: { 'Content-Type': 'application/json', + ...getTraceHeaders() }, body: JSON.stringify(requestBody) }); diff --git a/EssentialCSharp.Web/wwwroot/js/trydotnet-module.js b/EssentialCSharp.Web/wwwroot/js/trydotnet-module.js index ddff070e..d99a4b4e 100644 --- a/EssentialCSharp.Web/wwwroot/js/trydotnet-module.js +++ b/EssentialCSharp.Web/wwwroot/js/trydotnet-module.js @@ -31,6 +31,15 @@ function getCorrelationContext() { return null; } +function getTraceHeaders() { + const headers = {}; + const correlationContext = getCorrelationContext(); + if (typeof correlationContext === 'string' && correlationContext.length > 0) { + headers.traceparent = correlationContext; + } + return headers; +} + function trackTryEvent(name, properties = {}, measurements = {}) { const appInsights = getAppInsights(); if (!appInsights || typeof appInsights.trackEvent !== 'function') { @@ -241,7 +250,7 @@ export function useTryDotNet() { try { // Check the actual script endpoint rather than the bare origin, // which may not have a handler and would return 404. - const res = await fetch(`${origin}/api/trydotnet.min.js`, { + await fetch(`${origin}/api/trydotnet.min.js`, { method: 'HEAD', mode: 'no-cors', signal: controller.signal, @@ -489,7 +498,9 @@ export function useTryDotNet() { * @returns {Promise} The listing source code (extracted snippet) */ async function fetchListingCode(chapter, listing) { - const response = await fetch(`/api/ListingSourceCode/chapter/${chapter}/listing/${listing}`); + const response = await fetch(`/api/ListingSourceCode/chapter/${chapter}/listing/${listing}`, { + headers: getTraceHeaders() + }); if (!response.ok) { throw new Error(ERROR_MESSAGES.fetchFailed); }