@duffcloudservices/telemetry 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,15 +68,51 @@ const ai = new ApplicationInsights({
68
68
  ai.loadAppInsights()
69
69
  ```
70
70
 
71
+ ## Usage — visitor journey on form submissions
72
+
73
+ A stored form submission and the site's telemetry describe the same visit,
74
+ but nothing joins them. `getJourneyContext()` returns the join key: the
75
+ App Insights visitor/session ids, the submit-time page, and the session's
76
+ **first-touch** referrer / landing path / `utm_*`.
77
+
78
+ First-touch is snapshotted into `sessionStorage` by `initialize()`, not
79
+ read at submit time — `document.referrer` is destroyed the moment a
80
+ client-side router takes over, so capture-at-submit is structurally empty
81
+ for exactly the multi-page journeys worth attributing.
82
+
83
+ The module imports nothing, so a consumer with its own telemetry boot can
84
+ use it SDK-free:
85
+
86
+ ```ts
87
+ import {
88
+ captureJourneyFirstTouch,
89
+ toJourneyTelemetryPayload,
90
+ } from '@duffcloudservices/telemetry/journey'
91
+
92
+ captureJourneyFirstTouch() // once, at app boot, before any navigation
93
+
94
+ const telemetry = toJourneyTelemetryPayload() // undefined when nothing to report
95
+ if (telemetry) payload.telemetry = telemetry // omit the field, never send {}
96
+ ```
97
+
98
+ **A telemetry failure must never cost a lead.** Every entry point swallows
99
+ its own errors and degrades to an empty result; senders wrap the call and
100
+ omit the field entirely rather than block a submission.
101
+
71
102
  ## API
72
103
 
73
104
  | Export | Description |
74
105
  | --- | --- |
75
106
  | `useTelemetry(options?)` | Full composable: `initialize`, `trackEvent`, `trackPageView`, `trackCtaClick`, `trackException`, `trackMetric`, `trackDependency`, `flush`, `startTrackPage`, `stopTrackPage`, plus `isEnabled` / `isInitialized` / `initializationError` refs. |
76
107
  | `createTelemetryConfig(options?)` | Builds the App Insights config with DCS defaults + the `disablePageUnloadEvents: ['unload']` fix. Pure, no SDK import. |
108
+ | `getJourneyContext()` | Visitor-journey join key for a form submission. Never throws. Also at `@duffcloudservices/telemetry/journey` (no SDK import). |
109
+ | `toJourneyTelemetryPayload(context?)` | Flattens a journey context into the DCS `SubmissionTelemetry` wire shape, or `undefined` when empty. |
110
+ | `captureJourneyFirstTouch()` | Snapshots this session's referrer / landing path / `utm_*`. Idempotent; called by `initialize()`. |
111
+ | `setJourneyIdentityResolver(fn)` | Registers a live-SDK source for visitor/session ids; falls back to the `ai_user` / `ai_session` cookies. |
112
+ | `JOURNEY_FIELD_MAX_LENGTH` | `512` — mirrors the server-side per-field clamp. |
77
113
  | `DEFAULT_DISABLE_PAGE_UNLOAD_EVENTS` | `['unload']` — the disabled-events default. |
78
114
  | `DEFAULT_CLOUD_ROLE` | `'dcs-site'`. |
79
- | types | `TelemetryOptions`, `TelemetryConfigOptions`, `ApplicationInsightsConfig`, `TelemetryEvent`, `TelemetryPageView`, `TelemetryException`, `TelemetryDependency`, `TelemetryMetric`. |
115
+ | types | `TelemetryOptions`, `TelemetryConfigOptions`, `ApplicationInsightsConfig`, `TelemetryEvent`, `TelemetryPageView`, `TelemetryException`, `TelemetryDependency`, `TelemetryMetric`, `JourneyContext`, `JourneyTelemetryPayload`, `JourneyUtm`, `JourneyIdentityResolver`. |
80
116
 
81
117
  ### `TelemetryOptions`
82
118
 
@@ -0,0 +1,131 @@
1
+ // src/journey.ts
2
+ var JOURNEY_FIRST_TOUCH_KEY = "dcs:journey:first-touch";
3
+ var JOURNEY_FIELD_MAX_LENGTH = 512;
4
+ var identityResolver = null;
5
+ function setJourneyIdentityResolver(resolver) {
6
+ identityResolver = resolver;
7
+ }
8
+ function clamp(value) {
9
+ if (typeof value !== "string") return void 0;
10
+ const trimmed = value.trim();
11
+ if (!trimmed) return void 0;
12
+ return trimmed.length > JOURNEY_FIELD_MAX_LENGTH ? trimmed.slice(0, JOURNEY_FIELD_MAX_LENGTH) : trimmed;
13
+ }
14
+ function readCookie(name) {
15
+ if (typeof document === "undefined" || typeof document.cookie !== "string") {
16
+ return void 0;
17
+ }
18
+ const prefix = `${name}=`;
19
+ for (const part of document.cookie.split(";")) {
20
+ const entry = part.trim();
21
+ if (!entry.startsWith(prefix)) continue;
22
+ const raw = entry.slice(prefix.length);
23
+ try {
24
+ return decodeURIComponent(raw);
25
+ } catch {
26
+ return raw;
27
+ }
28
+ }
29
+ return void 0;
30
+ }
31
+ function firstCookieSegment(name) {
32
+ const value = readCookie(name);
33
+ if (!value) return void 0;
34
+ return clamp(value.split("|")[0]);
35
+ }
36
+ function parseUtm(params) {
37
+ const utm = {};
38
+ const source = clamp(params.get("utm_source"));
39
+ const medium = clamp(params.get("utm_medium"));
40
+ const campaign = clamp(params.get("utm_campaign"));
41
+ const term = clamp(params.get("utm_term"));
42
+ const content = clamp(params.get("utm_content"));
43
+ if (source) utm.source = source;
44
+ if (medium) utm.medium = medium;
45
+ if (campaign) utm.campaign = campaign;
46
+ if (term) utm.term = term;
47
+ if (content) utm.content = content;
48
+ return Object.keys(utm).length > 0 ? utm : void 0;
49
+ }
50
+ function readFirstTouch() {
51
+ if (typeof sessionStorage === "undefined") return void 0;
52
+ try {
53
+ const raw = sessionStorage.getItem(JOURNEY_FIRST_TOUCH_KEY);
54
+ if (!raw) return void 0;
55
+ const parsed = JSON.parse(raw);
56
+ return parsed && typeof parsed === "object" ? parsed : void 0;
57
+ } catch {
58
+ return void 0;
59
+ }
60
+ }
61
+ function captureJourneyFirstTouch() {
62
+ try {
63
+ if (typeof window === "undefined" || typeof sessionStorage === "undefined") {
64
+ return;
65
+ }
66
+ if (sessionStorage.getItem(JOURNEY_FIRST_TOUCH_KEY)) return;
67
+ const snapshot = {};
68
+ const referrer = typeof document !== "undefined" ? clamp(document.referrer) : void 0;
69
+ if (referrer) snapshot.referrer = referrer;
70
+ const landingPath = clamp(
71
+ `${window.location.pathname}${window.location.search}`
72
+ );
73
+ if (landingPath) snapshot.landingPath = landingPath;
74
+ const utm = parseUtm(new URLSearchParams(window.location.search));
75
+ if (utm) snapshot.utm = utm;
76
+ sessionStorage.setItem(JOURNEY_FIRST_TOUCH_KEY, JSON.stringify(snapshot));
77
+ } catch {
78
+ }
79
+ }
80
+ function getJourneyContext() {
81
+ try {
82
+ if (typeof window === "undefined") return {};
83
+ captureJourneyFirstTouch();
84
+ const context = {};
85
+ const identity = (() => {
86
+ try {
87
+ return identityResolver?.();
88
+ } catch {
89
+ return void 0;
90
+ }
91
+ })();
92
+ const visitorId = clamp(identity?.visitorId) ?? firstCookieSegment("ai_user");
93
+ const sessionId = clamp(identity?.sessionId) ?? firstCookieSegment("ai_session");
94
+ if (visitorId) context.visitorId = visitorId;
95
+ if (sessionId) context.sessionId = sessionId;
96
+ const pagePath = clamp(window.location.pathname);
97
+ const pageUrl = clamp(window.location.href);
98
+ if (pagePath) context.pagePath = pagePath;
99
+ if (pageUrl) context.pageUrl = pageUrl;
100
+ const firstTouch = readFirstTouch();
101
+ const referrer = clamp(firstTouch?.referrer);
102
+ const landingPath = clamp(firstTouch?.landingPath);
103
+ if (referrer) context.referrer = referrer;
104
+ if (landingPath) context.landingPath = landingPath;
105
+ if (firstTouch?.utm && Object.keys(firstTouch.utm).length > 0) {
106
+ context.utm = firstTouch.utm;
107
+ }
108
+ return context;
109
+ } catch {
110
+ return {};
111
+ }
112
+ }
113
+ function toJourneyTelemetryPayload(context = getJourneyContext()) {
114
+ const payload = {};
115
+ if (context.visitorId) payload.visitorId = context.visitorId;
116
+ if (context.sessionId) payload.sessionId = context.sessionId;
117
+ if (context.pagePath) payload.pagePath = context.pagePath;
118
+ if (context.pageUrl) payload.pageUrl = context.pageUrl;
119
+ if (context.referrer) payload.referrer = context.referrer;
120
+ if (context.landingPath) payload.landingPath = context.landingPath;
121
+ if (context.utm?.source) payload.utmSource = context.utm.source;
122
+ if (context.utm?.medium) payload.utmMedium = context.utm.medium;
123
+ if (context.utm?.campaign) payload.utmCampaign = context.utm.campaign;
124
+ if (context.utm?.term) payload.utmTerm = context.utm.term;
125
+ if (context.utm?.content) payload.utmContent = context.utm.content;
126
+ return Object.keys(payload).length > 0 ? payload : void 0;
127
+ }
128
+
129
+ export { JOURNEY_FIELD_MAX_LENGTH, JOURNEY_FIRST_TOUCH_KEY, captureJourneyFirstTouch, getJourneyContext, setJourneyIdentityResolver, toJourneyTelemetryPayload };
130
+ //# sourceMappingURL=chunk-DKX4AC2U.js.map
131
+ //# sourceMappingURL=chunk-DKX4AC2U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/journey.ts"],"names":[],"mappings":"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","file":"chunk-DKX4AC2U.js","sourcesContent":["/**\n * Visitor-journey context for form submissions.\n *\n * A submitted form row and the site's Application Insights telemetry describe\n * the same visit, but nothing joins them: the row knows the message, the\n * telemetry knows the path that produced it. `getJourneyContext()` returns the\n * join key — the ids the SDK is already stamping on every event, plus the\n * first-touch attribution the SPA throws away.\n *\n * WHY FIRST-TOUCH IS CAPTURED AT SESSION START, NOT AT SUBMIT. `document.referrer`\n * is only populated for the document the browser actually loaded. The moment a\n * client-side router takes over, it is gone — so a visitor who lands from Google\n * on a comparison page and clicks through to /contact submits with an empty\n * referrer. Capture-at-submit is not \"less accurate\" here; it is structurally\n * always empty for the multi-page journeys worth attributing. So\n * `captureJourneyFirstTouch()` snapshots the referrer, landing path, and utm_*\n * parameters once per session into `sessionStorage`, and every later read serves\n * that snapshot.\n *\n * This module deliberately imports nothing. It must stay usable by a consumer\n * that has no App Insights SDK loaded (the portal runs its own hand-rolled\n * telemetry boot), and it must never be the reason a form submission fails —\n * every entry point swallows its own errors and degrades to an empty result.\n */\n\n/** First-touch UTM parameters parsed from the landing URL. */\nexport interface JourneyUtm {\n source?: string\n medium?: string\n campaign?: string\n term?: string\n content?: string\n}\n\n/** The join key attached to an outbound form submission. */\nexport interface JourneyContext {\n /** App Insights user id — the visitor, across sessions. */\n visitorId?: string\n /** App Insights session id — this visit. */\n sessionId?: string\n /** Route path the form was submitted from. */\n pagePath?: string\n /** Full URL the form was submitted from. */\n pageUrl?: string\n /** First-touch `document.referrer` for this session. */\n referrer?: string\n /** First-touch landing path (pathname + query) for this session. */\n landingPath?: string\n /** First-touch UTM parameters, when the landing URL carried any. */\n utm?: JourneyUtm\n}\n\n/** Resolves visitor/session ids from a live telemetry SDK instance. */\nexport type JourneyIdentityResolver = () =>\n | { visitorId?: string; sessionId?: string }\n | undefined\n\n/** sessionStorage key holding this session's first-touch snapshot. */\nexport const JOURNEY_FIRST_TOUCH_KEY = 'dcs:journey:first-touch'\n\n/**\n * Per-field clamp. Mirrors the server's `journey.FieldMaxLength` so a value that\n * survives here is never silently truncated on the way into storage.\n */\nexport const JOURNEY_FIELD_MAX_LENGTH = 512\n\ninterface FirstTouchSnapshot {\n referrer?: string\n landingPath?: string\n utm?: JourneyUtm\n}\n\nlet identityResolver: JourneyIdentityResolver | null = null\n\n/**\n * Register the live-SDK source for visitor/session ids.\n *\n * The composable calls this after `loadAppInsights()`. Without it — or when the\n * resolver returns nothing — {@link getJourneyContext} falls back to parsing the\n * `ai_user` / `ai_session` cookies the SDK writes, which is why the portal (with\n * its own SDK boot and no call to this function) still reports usable ids.\n */\nexport function setJourneyIdentityResolver(\n resolver: JourneyIdentityResolver | null,\n): void {\n identityResolver = resolver\n}\n\nfunction clamp(value: string | undefined | null): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (!trimmed) return undefined\n return trimmed.length > JOURNEY_FIELD_MAX_LENGTH\n ? trimmed.slice(0, JOURNEY_FIELD_MAX_LENGTH)\n : trimmed\n}\n\n/**\n * Read a cookie value, URL-decoded. Returns undefined when absent.\n */\nfunction readCookie(name: string): string | undefined {\n if (typeof document === 'undefined' || typeof document.cookie !== 'string') {\n return undefined\n }\n const prefix = `${name}=`\n for (const part of document.cookie.split(';')) {\n const entry = part.trim()\n if (!entry.startsWith(prefix)) continue\n const raw = entry.slice(prefix.length)\n try {\n return decodeURIComponent(raw)\n } catch {\n return raw\n }\n }\n return undefined\n}\n\n/**\n * The App Insights cookies are pipe-delimited composites:\n * `ai_user=<userId>|<acquisitionDate>` and\n * `ai_session=<sessionId>|<acquisitionDate>|<renewalDate>`. Only the leading\n * segment is the id.\n */\nfunction firstCookieSegment(name: string): string | undefined {\n const value = readCookie(name)\n if (!value) return undefined\n return clamp(value.split('|')[0])\n}\n\nfunction parseUtm(params: URLSearchParams): JourneyUtm | undefined {\n const utm: JourneyUtm = {}\n const source = clamp(params.get('utm_source'))\n const medium = clamp(params.get('utm_medium'))\n const campaign = clamp(params.get('utm_campaign'))\n const term = clamp(params.get('utm_term'))\n const content = clamp(params.get('utm_content'))\n if (source) utm.source = source\n if (medium) utm.medium = medium\n if (campaign) utm.campaign = campaign\n if (term) utm.term = term\n if (content) utm.content = content\n return Object.keys(utm).length > 0 ? utm : undefined\n}\n\nfunction readFirstTouch(): FirstTouchSnapshot | undefined {\n if (typeof sessionStorage === 'undefined') return undefined\n try {\n const raw = sessionStorage.getItem(JOURNEY_FIRST_TOUCH_KEY)\n if (!raw) return undefined\n const parsed = JSON.parse(raw) as FirstTouchSnapshot\n return parsed && typeof parsed === 'object' ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Snapshot this session's first-touch context, once.\n *\n * Idempotent and safe to call from anywhere: a snapshot already in\n * sessionStorage always wins, so a later call on a deep page can never overwrite\n * the real landing page with the current one. Called by the composable's\n * `initialize()` (as early in the app's life as telemetry exists) and lazily by\n * {@link getJourneyContext} so a consumer that never initializes the shared SDK\n * still records *something* rather than nothing.\n */\nexport function captureJourneyFirstTouch(): void {\n try {\n if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') {\n return\n }\n if (sessionStorage.getItem(JOURNEY_FIRST_TOUCH_KEY)) return\n\n const snapshot: FirstTouchSnapshot = {}\n const referrer =\n typeof document !== 'undefined' ? clamp(document.referrer) : undefined\n if (referrer) snapshot.referrer = referrer\n\n const landingPath = clamp(\n `${window.location.pathname}${window.location.search}`,\n )\n if (landingPath) snapshot.landingPath = landingPath\n\n const utm = parseUtm(new URLSearchParams(window.location.search))\n if (utm) snapshot.utm = utm\n\n sessionStorage.setItem(JOURNEY_FIRST_TOUCH_KEY, JSON.stringify(snapshot))\n } catch {\n // sessionStorage can throw under blocked-storage policies. Journey capture\n // is an enrichment, never a requirement — stay silent and degrade.\n }\n}\n\n/**\n * Build the journey context to attach to an outbound form submission.\n *\n * NEVER THROWS. Returns `{}` when there is nothing to report (SSR, blocked\n * storage, telemetry-dark site), and callers omit the field entirely in that\n * case — a telemetry failure must not cost a lead.\n */\nexport function getJourneyContext(): JourneyContext {\n try {\n if (typeof window === 'undefined') return {}\n\n captureJourneyFirstTouch()\n\n const context: JourneyContext = {}\n\n const identity = (() => {\n try {\n return identityResolver?.()\n } catch {\n return undefined\n }\n })()\n\n const visitorId = clamp(identity?.visitorId) ?? firstCookieSegment('ai_user')\n const sessionId =\n clamp(identity?.sessionId) ?? firstCookieSegment('ai_session')\n if (visitorId) context.visitorId = visitorId\n if (sessionId) context.sessionId = sessionId\n\n const pagePath = clamp(window.location.pathname)\n const pageUrl = clamp(window.location.href)\n if (pagePath) context.pagePath = pagePath\n if (pageUrl) context.pageUrl = pageUrl\n\n const firstTouch = readFirstTouch()\n const referrer = clamp(firstTouch?.referrer)\n const landingPath = clamp(firstTouch?.landingPath)\n if (referrer) context.referrer = referrer\n if (landingPath) context.landingPath = landingPath\n if (firstTouch?.utm && Object.keys(firstTouch.utm).length > 0) {\n context.utm = firstTouch.utm\n }\n\n return context\n } catch {\n return {}\n }\n}\n\n/** The wire shape the DCS submission contracts accept (`SubmissionTelemetry`). */\nexport interface JourneyTelemetryPayload {\n visitorId?: string\n sessionId?: string\n pagePath?: string\n pageUrl?: string\n referrer?: string\n landingPath?: string\n utmSource?: string\n utmMedium?: string\n utmCampaign?: string\n utmTerm?: string\n utmContent?: string\n}\n\n/**\n * Flatten a {@link JourneyContext} into the contract's `telemetry` object, or\n * return `undefined` when there is nothing worth sending so the caller can omit\n * the field rather than post an empty object.\n */\nexport function toJourneyTelemetryPayload(\n context: JourneyContext = getJourneyContext(),\n): JourneyTelemetryPayload | undefined {\n const payload: JourneyTelemetryPayload = {}\n if (context.visitorId) payload.visitorId = context.visitorId\n if (context.sessionId) payload.sessionId = context.sessionId\n if (context.pagePath) payload.pagePath = context.pagePath\n if (context.pageUrl) payload.pageUrl = context.pageUrl\n if (context.referrer) payload.referrer = context.referrer\n if (context.landingPath) payload.landingPath = context.landingPath\n if (context.utm?.source) payload.utmSource = context.utm.source\n if (context.utm?.medium) payload.utmMedium = context.utm.medium\n if (context.utm?.campaign) payload.utmCampaign = context.utm.campaign\n if (context.utm?.term) payload.utmTerm = context.utm.term\n if (context.utm?.content) payload.utmContent = context.utm.content\n return Object.keys(payload).length > 0 ? payload : undefined\n}\n"]}
@@ -16,6 +16,17 @@ interface TelemetryPageView {
16
16
  uri?: string;
17
17
  properties?: Record<string, string>;
18
18
  measurements?: Record<string, number>;
19
+ /**
20
+ * Send this page view even though the SDK's `enableAutoRouteTracking` is active.
21
+ *
22
+ * Off by default, and it should stay off: with auto-route tracking on, the SDK
23
+ * already emits one pageView per navigation, so a manual one double-counts the
24
+ * page. C-288 measured that exact configuration producing 52% duplicate page views
25
+ * on a live customer site, which the portal then reported to the owner. Set this
26
+ * only when you have verified auto-route tracking does not fire for your routing
27
+ * setup; prefer `useTelemetry({ enableAutoRouteTracking: false })` instead.
28
+ */
29
+ force?: boolean;
19
30
  }
20
31
  interface TelemetryException {
21
32
  exception: Error;
package/dist/config.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { D as DEFAULT_CLOUD_ROLE, g as DEFAULT_DISABLE_PAGE_UNLOAD_EVENTS, f as createTelemetryConfig } from './config-BPmCpvmt.js';
1
+ export { D as DEFAULT_CLOUD_ROLE, g as DEFAULT_DISABLE_PAGE_UNLOAD_EVENTS, f as createTelemetryConfig } from './config-F0cscN3Z.js';
2
2
  import '@microsoft/applicationinsights-web';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as vue from 'vue';
2
- import { T as TelemetryOptions, a as TelemetryDependency, b as TelemetryEvent, c as TelemetryException, d as TelemetryMetric, e as TelemetryPageView } from './config-BPmCpvmt.js';
3
- export { A as ApplicationInsightsConfig, D as DEFAULT_CLOUD_ROLE, g as DEFAULT_DISABLE_PAGE_UNLOAD_EVENTS, h as TelemetryConfigOptions, f as createTelemetryConfig } from './config-BPmCpvmt.js';
2
+ import { T as TelemetryOptions, a as TelemetryDependency, b as TelemetryEvent, c as TelemetryException, d as TelemetryMetric, e as TelemetryPageView } from './config-F0cscN3Z.js';
3
+ export { A as ApplicationInsightsConfig, D as DEFAULT_CLOUD_ROLE, g as DEFAULT_DISABLE_PAGE_UNLOAD_EVENTS, h as TelemetryConfigOptions, f as createTelemetryConfig } from './config-F0cscN3Z.js';
4
+ export { JOURNEY_FIELD_MAX_LENGTH, JOURNEY_FIRST_TOUCH_KEY, JourneyContext, JourneyIdentityResolver, JourneyTelemetryPayload, JourneyUtm, captureJourneyFirstTouch, getJourneyContext, setJourneyIdentityResolver, toJourneyTelemetryPayload } from './journey.js';
4
5
  import '@microsoft/applicationinsights-web';
5
6
 
6
7
  /**
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { DEFAULT_CLOUD_ROLE, createTelemetryConfig } from './chunk-MX6WFWG4.js';
2
2
  export { DEFAULT_CLOUD_ROLE, DEFAULT_DISABLE_PAGE_UNLOAD_EVENTS, createTelemetryConfig } from './chunk-MX6WFWG4.js';
3
+ import { captureJourneyFirstTouch, setJourneyIdentityResolver } from './chunk-DKX4AC2U.js';
4
+ export { JOURNEY_FIELD_MAX_LENGTH, JOURNEY_FIRST_TOUCH_KEY, captureJourneyFirstTouch, getJourneyContext, setJourneyIdentityResolver, toJourneyTelemetryPayload } from './chunk-DKX4AC2U.js';
3
5
  import { ApplicationInsights } from '@microsoft/applicationinsights-web';
4
6
  import { ref, computed } from 'vue';
5
7
 
@@ -8,6 +10,35 @@ var isInitialized = ref(false);
8
10
  var initializationError = ref(null);
9
11
  var warnedDisabled = false;
10
12
  var warnedNotInitialized = false;
13
+ var warnedDoubleCount = false;
14
+ var autoRouteTrackingActive = false;
15
+ function attachConversionCapture(sink) {
16
+ try {
17
+ const w = typeof window !== "undefined" ? window : void 0;
18
+ w?.__dcsConversionAttach?.(sink);
19
+ } catch (error) {
20
+ console.warn("Failed to attach conversion capture to App Insights:", error);
21
+ }
22
+ }
23
+ function readSdkIdentity(instance) {
24
+ if (!instance) return void 0;
25
+ try {
26
+ const context = instance.context;
27
+ if (!context) return void 0;
28
+ const visitorId = typeof context.user?.id === "string" ? context.user.id : void 0;
29
+ let sessionId;
30
+ const fromGetter = typeof context.getSessionId === "function" ? context.getSessionId() : void 0;
31
+ if (typeof fromGetter === "string") {
32
+ sessionId = fromGetter;
33
+ } else if (typeof context.sessionManager?.automaticSession?.id === "string") {
34
+ sessionId = context.sessionManager.automaticSession.id;
35
+ }
36
+ if (!visitorId && !sessionId) return void 0;
37
+ return { visitorId, sessionId };
38
+ } catch {
39
+ return void 0;
40
+ }
41
+ }
11
42
  function resolveAppVersion(explicit) {
12
43
  if (explicit) return explicit;
13
44
  const w = typeof window !== "undefined" ? window : void 0;
@@ -59,6 +90,7 @@ function useTelemetry(options = {}) {
59
90
  }
60
91
  const initialize = () => {
61
92
  try {
93
+ captureJourneyFirstTouch();
62
94
  if (isInitialized.value && appInsights) {
63
95
  return true;
64
96
  }
@@ -71,16 +103,16 @@ function useTelemetry(options = {}) {
71
103
  }
72
104
  return false;
73
105
  }
74
- appInsights = new ApplicationInsights({
75
- config: createTelemetryConfig({
76
- connectionString: options.connectionString,
77
- instrumentationKey: options.instrumentationKey,
78
- enableAutoRouteTracking: options.enableAutoRouteTracking,
79
- dev: options.dev,
80
- disablePageUnloadEvents: options.disablePageUnloadEvents,
81
- overrides: options.overrides
82
- })
106
+ const resolvedConfig = createTelemetryConfig({
107
+ connectionString: options.connectionString,
108
+ instrumentationKey: options.instrumentationKey,
109
+ enableAutoRouteTracking: options.enableAutoRouteTracking,
110
+ dev: options.dev,
111
+ disablePageUnloadEvents: options.disablePageUnloadEvents,
112
+ overrides: options.overrides
83
113
  });
114
+ autoRouteTrackingActive = resolvedConfig.enableAutoRouteTracking !== false;
115
+ appInsights = new ApplicationInsights({ config: resolvedConfig });
84
116
  appInsights.loadAppInsights();
85
117
  appInsights.addTelemetryInitializer((envelope) => {
86
118
  try {
@@ -90,9 +122,11 @@ function useTelemetry(options = {}) {
90
122
  }
91
123
  return true;
92
124
  });
125
+ setJourneyIdentityResolver(() => readSdkIdentity(appInsights));
93
126
  isInitialized.value = true;
94
127
  initializationError.value = null;
95
128
  console.log("Application Insights initialized successfully");
129
+ attachConversionCapture(trackEvent);
96
130
  return true;
97
131
  } catch (error) {
98
132
  const errorMessage = error instanceof Error ? error.message : "Unknown initialization error";
@@ -126,6 +160,15 @@ function useTelemetry(options = {}) {
126
160
  console.warn("Application Insights not initialized, skipping page view tracking");
127
161
  return;
128
162
  }
163
+ if (autoRouteTrackingActive && !pageView?.force) {
164
+ if (!warnedDoubleCount) {
165
+ warnedDoubleCount = true;
166
+ console.warn(
167
+ "[telemetry] trackPageView() SKIPPED to prevent double-counting: this instance was initialized with enableAutoRouteTracking, so the App Insights SDK already reports one page view per navigation. Pass { enableAutoRouteTracking: false } to useTelemetry() if you want to report page views manually instead."
168
+ );
169
+ }
170
+ return;
171
+ }
129
172
  try {
130
173
  const referrer = typeof document !== "undefined" ? document.referrer ?? "" : "";
131
174
  appInsights.trackPageView({
@@ -215,8 +258,19 @@ function useTelemetry(options = {}) {
215
258
  console.error("Failed to flush telemetry:", error);
216
259
  }
217
260
  };
261
+ const manualPageTrackingIsRedundant = () => {
262
+ if (!autoRouteTrackingActive) return false;
263
+ if (!warnedDoubleCount) {
264
+ warnedDoubleCount = true;
265
+ console.warn(
266
+ "[telemetry] startTrackPage()/stopTrackPage() SKIPPED to prevent double-counting: this instance was initialized with enableAutoRouteTracking, so the App Insights SDK already reports a page view per navigation. Pass { enableAutoRouteTracking: false } to useTelemetry() if you want to time and report page views manually."
267
+ );
268
+ }
269
+ return true;
270
+ };
218
271
  const startTrackPage = (name) => {
219
272
  if (!appInsights || !isInitialized.value) return;
273
+ if (manualPageTrackingIsRedundant()) return;
220
274
  try {
221
275
  appInsights.startTrackPage(name);
222
276
  } catch (error) {
@@ -225,6 +279,7 @@ function useTelemetry(options = {}) {
225
279
  };
226
280
  const stopTrackPage = (name, url, properties, measurements) => {
227
281
  if (!appInsights || !isInitialized.value) return;
282
+ if (manualPageTrackingIsRedundant()) return;
228
283
  try {
229
284
  appInsights.stopTrackPage(name, url, buildEventProperties(properties), measurements);
230
285
  } catch (error) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/composable.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { ApplicationInsights } from '@microsoft/applicationinsights-web'\nimport { computed, ref } from 'vue'\n\nimport { DEFAULT_CLOUD_ROLE, createTelemetryConfig } from './config'\nimport type {\n TelemetryDependency,\n TelemetryEvent,\n TelemetryException,\n TelemetryMetric,\n TelemetryOptions,\n TelemetryPageView,\n} from './types'\n\ninterface TelemetryEnvelope {\n data?: {\n baseData?: {\n properties?: Record<string, string>\n }\n }\n tags?: Record<string, string>\n}\n\n// Module-level singleton state: one App Insights instance per app bundle,\n// shared across every `useTelemetry()` call (matches the original per-app\n// composable). The reactive refs let views observe init state.\nlet appInsights: ApplicationInsights | null = null\nconst isInitialized = ref(false)\nconst initializationError = ref<string | null>(null)\n\n// One-shot guards so we surface \"App Insights is not configured\" loudly in\n// DevTools the FIRST time the app tries to use it, but don't spam the console\n// for every subsequent trackEvent / trackPageView call. See web/ site-audit F2\n// — `customEvents = 0` for 7d was a missing connection string silently\n// no-op'ing every telemetry call; the loud warning makes that mode visible.\nlet warnedDisabled = false\nlet warnedNotInitialized = false\n\nfunction resolveAppVersion(explicit?: string): string {\n if (explicit) return explicit\n const w =\n typeof window !== 'undefined'\n ? (window as Window & { __APP_VERSION__?: string })\n : undefined\n return w?.__APP_VERSION__ ?? 'unknown'\n}\n\n/**\n * Azure Application Insights composable for DCS Vue apps.\n *\n * The connection string, cloud role, environment and app version are supplied\n * by the consumer (they read their own `import.meta.env` / build defines and\n * pass plain values in) so this package stays portable and build-tool agnostic.\n * The shared App Insights config — including the `disablePageUnloadEvents:\n * ['unload']` bfcache/Lighthouse fix — comes from {@link createTelemetryConfig}.\n */\nexport function useTelemetry(options: TelemetryOptions = {}) {\n const cloudRole = options.cloudRole ?? DEFAULT_CLOUD_ROLE\n const environment = options.environment ?? 'unknown'\n\n const isEnabled = computed(() => {\n return !!(options.connectionString || options.instrumentationKey)\n })\n\n function buildSharedProperties(): Record<string, string> {\n const appVersion = resolveAppVersion(options.appVersion)\n const referrerHost =\n typeof document !== 'undefined' && document.referrer\n ? (() => {\n try {\n return new URL(document.referrer).hostname\n } catch {\n return 'unknown'\n }\n })()\n : 'direct'\n\n return {\n app_version: appVersion,\n app_name: cloudRole,\n app_host: typeof window !== 'undefined' ? window.location.hostname : '',\n page_path: typeof window !== 'undefined' ? window.location.pathname : '',\n referrer_host: referrerHost,\n }\n }\n\n function buildEventProperties(\n additional?: Record<string, string>,\n ): Record<string, string> {\n return {\n timestamp: new Date().toISOString(),\n environment,\n ...buildSharedProperties(),\n ...additional,\n }\n }\n\n function enrichTelemetryEnvelope(envelope: TelemetryEnvelope) {\n if (!envelope.data) {\n envelope.data = {}\n }\n\n envelope.data.baseData = envelope.data.baseData || {}\n envelope.data.baseData.properties = envelope.data.baseData.properties || {}\n Object.assign(envelope.data.baseData.properties, buildSharedProperties())\n\n const appVersion = resolveAppVersion(options.appVersion)\n envelope.tags = envelope.tags || {}\n envelope.tags['ai.application.ver'] = appVersion\n envelope.tags['ai.cloud.role'] = cloudRole\n envelope.tags['ai.cloud.roleInstance'] =\n typeof window !== 'undefined' ? window.location.hostname : ''\n }\n\n const initialize = (): boolean => {\n try {\n if (isInitialized.value && appInsights) {\n return true\n }\n\n if (!isEnabled.value) {\n if (!warnedDisabled) {\n warnedDisabled = true\n // Loud, one-shot warning so this is obvious in DevTools. If you see\n // this in production, the build pipeline is not injecting the App\n // Insights connection string — every track* call below silently\n // no-ops. See web/ site-audit F2.\n console.warn(\n '[telemetry] Application Insights DISABLED: no connection string ' +\n 'or instrumentation key was provided at build time. ' +\n 'All telemetry calls will silently no-op.',\n )\n }\n return false\n }\n\n appInsights = new ApplicationInsights({\n config: createTelemetryConfig({\n connectionString: options.connectionString,\n instrumentationKey: options.instrumentationKey,\n enableAutoRouteTracking: options.enableAutoRouteTracking,\n dev: options.dev,\n disablePageUnloadEvents: options.disablePageUnloadEvents,\n overrides: options.overrides,\n }),\n })\n\n appInsights.loadAppInsights()\n appInsights.addTelemetryInitializer((envelope) => {\n try {\n enrichTelemetryEnvelope(envelope as TelemetryEnvelope)\n } catch (error) {\n console.warn('Failed to add web telemetry context:', error)\n }\n\n return true\n })\n\n isInitialized.value = true\n initializationError.value = null\n console.log('Application Insights initialized successfully')\n return true\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown initialization error'\n initializationError.value = errorMessage\n console.error('Failed to initialize Application Insights:', error)\n return false\n }\n }\n\n const trackEvent = (event: TelemetryEvent) => {\n if (!appInsights || !isInitialized.value) {\n if (!warnedNotInitialized) {\n warnedNotInitialized = true\n console.warn(\n `[telemetry] Application Insights not initialized — dropping event \"${event.name}\" ` +\n '(and silencing further \"not initialized\" warnings for this session).',\n )\n }\n return\n }\n\n try {\n appInsights.trackEvent({\n name: event.name,\n properties: buildEventProperties(event.properties),\n measurements: event.measurements,\n })\n } catch (error) {\n console.error('Failed to track event:', error)\n }\n }\n\n const trackPageView = (pageView?: TelemetryPageView) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping page view tracking')\n return\n }\n\n try {\n // Attach the full document.referrer (not just hostname) so we can\n // attribute traffic sources in App Insights pageViews.\n const referrer =\n typeof document !== 'undefined' ? (document.referrer ?? '') : ''\n appInsights.trackPageView({\n name:\n pageView?.name ||\n (typeof document !== 'undefined' ? document.title : undefined),\n uri:\n pageView?.uri ||\n (typeof window !== 'undefined' ? window.location.href : undefined),\n properties: buildEventProperties({\n referrer,\n ...pageView?.properties,\n }),\n measurements: pageView?.measurements,\n })\n } catch (error) {\n console.error('Failed to track page view:', error)\n }\n }\n\n /**\n * Convenience helper for primary CTA conversion measurement. Fires a\n * `cta_click` custom event so conversion rate per landing page can be\n * computed in App Insights / KQL.\n */\n const trackCtaClick = (label: string, page?: string) => {\n trackEvent({\n name: 'cta_click',\n properties: {\n label,\n page:\n page ??\n (typeof window !== 'undefined' ? window.location.pathname : ''),\n },\n })\n }\n\n const trackException = (exception: TelemetryException) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping exception tracking')\n return\n }\n\n try {\n appInsights.trackException({\n exception: exception.exception,\n properties: buildEventProperties({\n url: typeof window !== 'undefined' ? window.location.href : '',\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n ...exception.properties,\n }),\n measurements: exception.measurements,\n })\n } catch (error) {\n console.error('Failed to track exception:', error)\n }\n }\n\n const trackDependency = (dependency: TelemetryDependency) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping dependency tracking')\n return\n }\n\n try {\n appInsights.trackDependencyData({\n id: `dep-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,\n name: dependency.name,\n data: dependency.data,\n duration: dependency.duration,\n success: dependency.success,\n responseCode: dependency.resultCode || 0,\n properties: buildEventProperties(dependency.properties),\n measurements: dependency.measurements,\n })\n } catch (error) {\n console.error('Failed to track dependency:', error)\n }\n }\n\n const trackMetric = (metric: TelemetryMetric) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping metric tracking')\n return\n }\n\n try {\n appInsights.trackMetric({\n name: metric.name,\n average: metric.average,\n sampleCount: metric.sampleCount,\n min: metric.min,\n max: metric.max,\n properties: buildEventProperties(metric.properties),\n })\n } catch (error) {\n console.error('Failed to track metric:', error)\n }\n }\n\n const flush = () => {\n if (!appInsights || !isInitialized.value) return\n\n try {\n appInsights.flush()\n } catch (error) {\n console.error('Failed to flush telemetry:', error)\n }\n }\n\n const startTrackPage = (name?: string) => {\n if (!appInsights || !isInitialized.value) return\n\n try {\n appInsights.startTrackPage(name)\n } catch (error) {\n console.error('Failed to start track page:', error)\n }\n }\n\n const stopTrackPage = (\n name?: string,\n url?: string,\n properties?: Record<string, string>,\n measurements?: Record<string, number>,\n ) => {\n if (!appInsights || !isInitialized.value) return\n\n try {\n appInsights.stopTrackPage(name, url, buildEventProperties(properties), measurements)\n } catch (error) {\n console.error('Failed to stop track page:', error)\n }\n }\n\n return {\n isEnabled,\n isInitialized,\n initializationError,\n initialize,\n flush,\n startTrackPage,\n stopTrackPage,\n trackDependency,\n trackEvent,\n trackException,\n trackMetric,\n trackPageView,\n trackCtaClick,\n }\n}\n\n/** The full public API returned by {@link useTelemetry}. */\nexport type TelemetryApi = ReturnType<typeof useTelemetry>\n"]}
1
+ {"version":3,"sources":["../src/composable.ts"],"names":[],"mappings":";;;;;;;AA0BA,IAAI,WAAA,GAA0C,IAAA;AAC9C,IAAM,aAAA,GAAgB,IAAI,KAAK,CAAA;AAC/B,IAAM,mBAAA,GAAsB,IAAmB,IAAI,CAAA;AAOnD,IAAI,cAAA,GAAiB,KAAA;AACrB,IAAI,oBAAA,GAAuB,KAAA;AAC3B,IAAI,iBAAA,GAAoB,KAAA;AAWxB,IAAI,uBAAA,GAA0B,KAAA;AAwB9B,SAAS,wBAAwB,IAAA,EAA6C;AAC5E,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GACJ,OAAO,MAAA,KAAW,WAAA,GACb,MAAA,GAGD,KAAA,CAAA;AACN,IAAA,CAAA,EAAG,wBAAwB,IAAI,CAAA;AAAA,EACjC,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA,CAAK,wDAAwD,KAAK,CAAA;AAAA,EAC5E;AACF;AAUA,SAAS,gBACP,QAAA,EACwD;AACxD,EAAA,IAAI,CAAC,UAAU,OAAO,MAAA;AACtB,EAAA,IAAI;AACF,IAAA,MAAM,UACJ,QAAA,CAOA,OAAA;AACF,IAAA,IAAI,CAAC,SAAS,OAAO,KAAA,CAAA;AAErB,IAAA,MAAM,SAAA,GACJ,OAAO,OAAA,CAAQ,IAAA,EAAM,OAAO,QAAA,GAAW,OAAA,CAAQ,KAAK,EAAA,GAAK,KAAA,CAAA;AAE3D,IAAA,IAAI,SAAA;AACJ,IAAA,MAAM,aACJ,OAAO,OAAA,CAAQ,iBAAiB,UAAA,GAAa,OAAA,CAAQ,cAAa,GAAI,KAAA,CAAA;AACxE,IAAA,IAAI,OAAO,eAAe,QAAA,EAAU;AAClC,MAAA,SAAA,GAAY,UAAA;AAAA,IACd,WAAW,OAAO,OAAA,CAAQ,cAAA,EAAgB,gBAAA,EAAkB,OAAO,QAAA,EAAU;AAC3E,MAAA,SAAA,GAAY,OAAA,CAAQ,eAAe,gBAAA,CAAiB,EAAA;AAAA,IACtD;AAEA,IAAA,IAAI,CAAC,SAAA,IAAa,CAAC,SAAA,EAAW,OAAO,KAAA,CAAA;AACrC,IAAA,OAAO,EAAE,WAAW,SAAA,EAAU;AAAA,EAChC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEA,SAAS,kBAAkB,QAAA,EAA2B;AACpD,EAAA,IAAI,UAAU,OAAO,QAAA;AACrB,EAAA,MAAM,CAAA,GACJ,OAAO,MAAA,KAAW,WAAA,GACb,MAAA,GACD,MAAA;AACN,EAAA,OAAO,GAAG,eAAA,IAAmB,SAAA;AAC/B;AAWO,SAAS,YAAA,CAAa,OAAA,GAA4B,EAAC,EAAG;AAC3D,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,kBAAA;AACvC,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,SAAA;AAE3C,EAAA,MAAM,SAAA,GAAY,SAAS,MAAM;AAC/B,IAAA,OAAO,CAAC,EAAE,OAAA,CAAQ,gBAAA,IAAoB,OAAA,CAAQ,kBAAA,CAAA;AAAA,EAChD,CAAC,CAAA;AAED,EAAA,SAAS,qBAAA,GAAgD;AACvD,IAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,OAAA,CAAQ,UAAU,CAAA;AACvD,IAAA,MAAM,eACJ,OAAO,QAAA,KAAa,WAAA,IAAe,QAAA,CAAS,YACvC,MAAM;AACL,MAAA,IAAI;AACF,QAAA,OAAO,IAAI,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAA,CAAE,QAAA;AAAA,MACpC,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,SAAA;AAAA,MACT;AAAA,IACF,IAAG,GACH,QAAA;AAEN,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,UAAA;AAAA,MACb,QAAA,EAAU,SAAA;AAAA,MACV,UAAU,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,EAAA;AAAA,MACrE,WAAW,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,EAAA;AAAA,MACtE,aAAA,EAAe;AAAA,KACjB;AAAA,EACF;AAEA,EAAA,SAAS,qBACP,UAAA,EACwB;AACxB,IAAA,OAAO;AAAA,MACL,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MAClC,WAAA;AAAA,MACA,GAAG,qBAAA,EAAsB;AAAA,MACzB,GAAG;AAAA,KACL;AAAA,EACF;AAEA,EAAA,SAAS,wBAAwB,QAAA,EAA6B;AAC5D,IAAA,IAAI,CAAC,SAAS,IAAA,EAAM;AAClB,MAAA,QAAA,CAAS,OAAO,EAAC;AAAA,IACnB;AAEA,IAAA,QAAA,CAAS,IAAA,CAAK,QAAA,GAAW,QAAA,CAAS,IAAA,CAAK,YAAY,EAAC;AACpD,IAAA,QAAA,CAAS,KAAK,QAAA,CAAS,UAAA,GAAa,SAAS,IAAA,CAAK,QAAA,CAAS,cAAc,EAAC;AAC1E,IAAA,MAAA,CAAO,OAAO,QAAA,CAAS,IAAA,CAAK,QAAA,CAAS,UAAA,EAAY,uBAAuB,CAAA;AAExE,IAAA,MAAM,UAAA,GAAa,iBAAA,CAAkB,OAAA,CAAQ,UAAU,CAAA;AACvD,IAAA,QAAA,CAAS,IAAA,GAAO,QAAA,CAAS,IAAA,IAAQ,EAAC;AAClC,IAAA,QAAA,CAAS,IAAA,CAAK,oBAAoB,CAAA,GAAI,UAAA;AACtC,IAAA,QAAA,CAAS,IAAA,CAAK,eAAe,CAAA,GAAI,SAAA;AACjC,IAAA,QAAA,CAAS,IAAA,CAAK,uBAAuB,CAAA,GACnC,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,EAAA;AAAA,EAC/D;AAEA,EAAA,MAAM,aAAa,MAAe;AAChC,IAAA,IAAI;AAOF,MAAA,wBAAA,EAAyB;AAEzB,MAAA,IAAI,aAAA,CAAc,SAAS,WAAA,EAAa;AACtC,QAAA,OAAO,IAAA;AAAA,MACT;AAEA,MAAA,IAAI,CAAC,UAAU,KAAA,EAAO;AACpB,QAAA,IAAI,CAAC,cAAA,EAAgB;AACnB,UAAA,cAAA,GAAiB,IAAA;AAKjB,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN;AAAA,WAGF;AAAA,QACF;AACA,QAAA,OAAO,KAAA;AAAA,MACT;AAEA,MAAA,MAAM,iBAAiB,qBAAA,CAAsB;AAAA,QAC3C,kBAAkB,OAAA,CAAQ,gBAAA;AAAA,QAC1B,oBAAoB,OAAA,CAAQ,kBAAA;AAAA,QAC5B,yBAAyB,OAAA,CAAQ,uBAAA;AAAA,QACjC,KAAK,OAAA,CAAQ,GAAA;AAAA,QACb,yBAAyB,OAAA,CAAQ,uBAAA;AAAA,QACjC,WAAW,OAAA,CAAQ;AAAA,OACpB,CAAA;AAGD,MAAA,uBAAA,GAA0B,eAAe,uBAAA,KAA4B,KAAA;AAErE,MAAA,WAAA,GAAc,IAAI,mBAAA,CAAoB,EAAE,MAAA,EAAQ,gBAAgB,CAAA;AAEhE,MAAA,WAAA,CAAY,eAAA,EAAgB;AAC5B,MAAA,WAAA,CAAY,uBAAA,CAAwB,CAAC,QAAA,KAAa;AAChD,QAAA,IAAI;AACF,UAAA,uBAAA,CAAwB,QAA6B,CAAA;AAAA,QACvD,SAAS,KAAA,EAAO;AACd,UAAA,OAAA,CAAQ,IAAA,CAAK,wCAAwC,KAAK,CAAA;AAAA,QAC5D;AAEA,QAAA,OAAO,IAAA;AAAA,MACT,CAAC,CAAA;AAKD,MAAA,0BAAA,CAA2B,MAAM,eAAA,CAAgB,WAAW,CAAC,CAAA;AAE7D,MAAA,aAAA,CAAc,KAAA,GAAQ,IAAA;AACtB,MAAA,mBAAA,CAAoB,KAAA,GAAQ,IAAA;AAC5B,MAAA,OAAA,CAAQ,IAAI,+CAA+C,CAAA;AAI3D,MAAA,uBAAA,CAAwB,UAAU,CAAA;AAElC,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,MAAM,YAAA,GACJ,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,8BAAA;AAC3C,MAAA,mBAAA,CAAoB,KAAA,GAAQ,YAAA;AAC5B,MAAA,OAAA,CAAQ,KAAA,CAAM,8CAA8C,KAAK,CAAA;AACjE,MAAA,OAAO,KAAA;AAAA,IACT;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,CAAC,KAAA,KAA0B;AAC5C,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AACxC,MAAA,IAAI,CAAC,oBAAA,EAAsB;AACzB,QAAA,oBAAA,GAAuB,IAAA;AACvB,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN,CAAA,wEAAA,EAAsE,MAAM,IAAI,CAAA,sEAAA;AAAA,SAElF;AAAA,MACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,WAAA,CAAY,UAAA,CAAW;AAAA,QACrB,MAAM,KAAA,CAAM,IAAA;AAAA,QACZ,UAAA,EAAY,oBAAA,CAAqB,KAAA,CAAM,UAAU,CAAA;AAAA,QACjD,cAAc,KAAA,CAAM;AAAA,OACrB,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,0BAA0B,KAAK,CAAA;AAAA,IAC/C;AAAA,EACF,CAAA;AAeA,EAAA,MAAM,aAAA,GAAgB,CAAC,QAAA,KAAiC;AACtD,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AACxC,MAAA,OAAA,CAAQ,KAAK,mEAAmE,CAAA;AAChF,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,uBAAA,IAA2B,CAAC,QAAA,EAAU,KAAA,EAAO;AAC/C,MAAA,IAAI,CAAC,iBAAA,EAAmB;AACtB,QAAA,iBAAA,GAAoB,IAAA;AACpB,QAAA,OAAA,CAAQ,IAAA;AAAA,UACN;AAAA,SAKF;AAAA,MACF;AACA,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AAGF,MAAA,MAAM,WACJ,OAAO,QAAA,KAAa,WAAA,GAAe,QAAA,CAAS,YAAY,EAAA,GAAM,EAAA;AAChE,MAAA,WAAA,CAAY,aAAA,CAAc;AAAA,QACxB,MACE,QAAA,EAAU,IAAA,KACT,OAAO,QAAA,KAAa,WAAA,GAAc,SAAS,KAAA,GAAQ,KAAA,CAAA,CAAA;AAAA,QACtD,GAAA,EACE,UAAU,GAAA,KACT,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,SAAS,IAAA,GAAO,KAAA,CAAA,CAAA;AAAA,QAC1D,YAAY,oBAAA,CAAqB;AAAA,UAC/B,QAAA;AAAA,UACA,GAAG,QAAA,EAAU;AAAA,SACd,CAAA;AAAA,QACD,cAAc,QAAA,EAAU;AAAA,OACzB,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AAAA,IACnD;AAAA,EACF,CAAA;AAOA,EAAA,MAAM,aAAA,GAAgB,CAAC,KAAA,EAAe,IAAA,KAAkB;AACtD,IAAA,UAAA,CAAW;AAAA,MACT,IAAA,EAAM,WAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACV,KAAA;AAAA,QACA,MACE,IAAA,KACC,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,EAAA;AAAA;AAChE,KACD,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,CAAC,SAAA,KAAkC;AACxD,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AACxC,MAAA,OAAA,CAAQ,KAAK,mEAAmE,CAAA;AAChF,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,WAAA,CAAY,cAAA,CAAe;AAAA,QACzB,WAAW,SAAA,CAAU,SAAA;AAAA,QACrB,YAAY,oBAAA,CAAqB;AAAA,UAC/B,KAAK,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,IAAA,GAAO,EAAA;AAAA,UAC5D,SAAA,EAAW,OAAO,SAAA,KAAc,WAAA,GAAc,UAAU,SAAA,GAAY,EAAA;AAAA,UACpE,GAAG,SAAA,CAAU;AAAA,SACd,CAAA;AAAA,QACD,cAAc,SAAA,CAAU;AAAA,OACzB,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AAAA,IACnD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,eAAA,GAAkB,CAAC,UAAA,KAAoC;AAC3D,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AACxC,MAAA,OAAA,CAAQ,KAAK,oEAAoE,CAAA;AACjF,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,WAAA,CAAY,mBAAA,CAAoB;AAAA,QAC9B,EAAA,EAAI,CAAA,IAAA,EAAO,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA,CAAA;AAAA,QAChE,MAAM,UAAA,CAAW,IAAA;AAAA,QACjB,MAAM,UAAA,CAAW,IAAA;AAAA,QACjB,UAAU,UAAA,CAAW,QAAA;AAAA,QACrB,SAAS,UAAA,CAAW,OAAA;AAAA,QACpB,YAAA,EAAc,WAAW,UAAA,IAAc,CAAA;AAAA,QACvC,UAAA,EAAY,oBAAA,CAAqB,UAAA,CAAW,UAAU,CAAA;AAAA,QACtD,cAAc,UAAA,CAAW;AAAA,OAC1B,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,+BAA+B,KAAK,CAAA;AAAA,IACpD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,MAAA,KAA4B;AAC/C,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AACxC,MAAA,OAAA,CAAQ,KAAK,gEAAgE,CAAA;AAC7E,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AACF,MAAA,WAAA,CAAY,WAAA,CAAY;AAAA,QACtB,MAAM,MAAA,CAAO,IAAA;AAAA,QACb,SAAS,MAAA,CAAO,OAAA;AAAA,QAChB,aAAa,MAAA,CAAO,WAAA;AAAA,QACpB,KAAK,MAAA,CAAO,GAAA;AAAA,QACZ,KAAK,MAAA,CAAO,GAAA;AAAA,QACZ,UAAA,EAAY,oBAAA,CAAqB,MAAA,CAAO,UAAU;AAAA,OACnD,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,2BAA2B,KAAK,CAAA;AAAA,IAChD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,QAAQ,MAAM;AAClB,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AAE1C,IAAA,IAAI;AACF,MAAA,WAAA,CAAY,KAAA,EAAM;AAAA,IACpB,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AAAA,IACnD;AAAA,EACF,CAAA;AAcA,EAAA,MAAM,gCAAgC,MAAe;AACnD,IAAA,IAAI,CAAC,yBAAyB,OAAO,KAAA;AACrC,IAAA,IAAI,CAAC,iBAAA,EAAmB;AACtB,MAAA,iBAAA,GAAoB,IAAA;AACpB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN;AAAA,OAKF;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,cAAA,GAAiB,CAAC,IAAA,KAAkB;AACxC,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AAC1C,IAAA,IAAI,+BAA8B,EAAG;AAErC,IAAA,IAAI;AACF,MAAA,WAAA,CAAY,eAAe,IAAI,CAAA;AAAA,IACjC,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,+BAA+B,KAAK,CAAA;AAAA,IACpD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,aAAA,GAAgB,CACpB,IAAA,EACA,GAAA,EACA,YACA,YAAA,KACG;AACH,IAAA,IAAI,CAAC,WAAA,IAAe,CAAC,aAAA,CAAc,KAAA,EAAO;AAC1C,IAAA,IAAI,+BAA8B,EAAG;AAErC,IAAA,IAAI;AACF,MAAA,WAAA,CAAY,cAAc,IAAA,EAAM,GAAA,EAAK,oBAAA,CAAqB,UAAU,GAAG,YAAY,CAAA;AAAA,IACrF,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,KAAK,CAAA;AAAA,IACnD;AAAA,EACF,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,aAAA;AAAA,IACA,mBAAA;AAAA,IACA,UAAA;AAAA,IACA,KAAA;AAAA,IACA,cAAA;AAAA,IACA,aAAA;AAAA,IACA,eAAA;AAAA,IACA,UAAA;AAAA,IACA,cAAA;AAAA,IACA,WAAA;AAAA,IACA,aAAA;AAAA,IACA;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import { ApplicationInsights } from '@microsoft/applicationinsights-web'\nimport { computed, ref } from 'vue'\n\nimport { DEFAULT_CLOUD_ROLE, createTelemetryConfig } from './config'\nimport { captureJourneyFirstTouch, setJourneyIdentityResolver } from './journey'\nimport type {\n TelemetryDependency,\n TelemetryEvent,\n TelemetryException,\n TelemetryMetric,\n TelemetryOptions,\n TelemetryPageView,\n} from './types'\n\ninterface TelemetryEnvelope {\n data?: {\n baseData?: {\n properties?: Record<string, string>\n }\n }\n tags?: Record<string, string>\n}\n\n// Module-level singleton state: one App Insights instance per app bundle,\n// shared across every `useTelemetry()` call (matches the original per-app\n// composable). The reactive refs let views observe init state.\nlet appInsights: ApplicationInsights | null = null\nconst isInitialized = ref(false)\nconst initializationError = ref<string | null>(null)\n\n// One-shot guards so we surface \"App Insights is not configured\" loudly in\n// DevTools the FIRST time the app tries to use it, but don't spam the console\n// for every subsequent trackEvent / trackPageView call. See web/ site-audit F2\n// — `customEvents = 0` for 7d was a missing connection string silently\n// no-op'ing every telemetry call; the loud warning makes that mode visible.\nlet warnedDisabled = false\nlet warnedNotInitialized = false\nlet warnedDoubleCount = false\n\n// Whether the live instance was created with the SDK's own SPA route tracking on.\n// When it is, the SDK already emits one pageView per navigation, so a consumer that\n// ALSO calls trackPageView() on route change counts every page twice.\n//\n// This is not hypothetical (C-288, 2026-07-25): the Just Posh site ran both at once\n// and 509 of its 971 human page views last month — 52% — were duplicates. It was the\n// largest single correction in either owner report, and the portal dashboard was\n// showing the uncorrected number to the customer. See\n// .docs/analysis/owner-reports/2026-07-justposh.md §7.\nlet autoRouteTrackingActive = false\n\n/**\n * Hand this App Insights instance to the cms conversion tracker, if one is capturing.\n *\n * THE CONTRACT, AND WHY IT IS A GLOBAL. `@duffcloudservices/cms` auto-installs a delegated\n * conversion listener (C-326) that starts capturing `tel:`/`sms:`/`mailto:`/booking/form\n * interactions before any telemetry SDK exists, buffering them until a transport shows up.\n * This package is a transport. Importing cms from here to say so would couple two packages\n * that are versioned and published independently — and would break the one property the\n * capture layer exists to have, which is that it never depends on a telemetry SDK.\n *\n * So the handshake is a single global function that cms publishes and anyone may call:\n *\n * ```js\n * window.__dcsConversionAttach?.((event) => appInsights.trackEvent(event))\n * ```\n *\n * A site with a bespoke telemetry boot (Bryan's is a hand-rolled copy of this composable)\n * adds exactly that line. Consumers of this package get it for free, here.\n *\n * Failing silently is correct: on a site without cms, or an older cms, the global is simply\n * absent and there is nothing to drain.\n */\nfunction attachConversionCapture(sink: (event: TelemetryEvent) => void): void {\n try {\n const w =\n typeof window !== 'undefined'\n ? (window as Window & {\n __dcsConversionAttach?: (s: (event: TelemetryEvent) => void) => number\n })\n : undefined\n w?.__dcsConversionAttach?.(sink)\n } catch (error) {\n console.warn('Failed to attach conversion capture to App Insights:', error)\n }\n}\n\n/**\n * Read the visitor/session ids off a live App Insights instance.\n *\n * The SDK's `context` shape is internal-ish and has moved between major\n * versions, so this reads it structurally and returns undefined on any\n * mismatch — {@link getJourneyContext} then falls back to the `ai_user` /\n * `ai_session` cookies. Never throws.\n */\nfunction readSdkIdentity(\n instance: ApplicationInsights | null,\n): { visitorId?: string; sessionId?: string } | undefined {\n if (!instance) return undefined\n try {\n const context = (\n instance as unknown as {\n context?: {\n user?: { id?: unknown }\n getSessionId?: () => unknown\n sessionManager?: { automaticSession?: { id?: unknown } }\n }\n }\n ).context\n if (!context) return undefined\n\n const visitorId =\n typeof context.user?.id === 'string' ? context.user.id : undefined\n\n let sessionId: string | undefined\n const fromGetter =\n typeof context.getSessionId === 'function' ? context.getSessionId() : undefined\n if (typeof fromGetter === 'string') {\n sessionId = fromGetter\n } else if (typeof context.sessionManager?.automaticSession?.id === 'string') {\n sessionId = context.sessionManager.automaticSession.id\n }\n\n if (!visitorId && !sessionId) return undefined\n return { visitorId, sessionId }\n } catch {\n return undefined\n }\n}\n\nfunction resolveAppVersion(explicit?: string): string {\n if (explicit) return explicit\n const w =\n typeof window !== 'undefined'\n ? (window as Window & { __APP_VERSION__?: string })\n : undefined\n return w?.__APP_VERSION__ ?? 'unknown'\n}\n\n/**\n * Azure Application Insights composable for DCS Vue apps.\n *\n * The connection string, cloud role, environment and app version are supplied\n * by the consumer (they read their own `import.meta.env` / build defines and\n * pass plain values in) so this package stays portable and build-tool agnostic.\n * The shared App Insights config — including the `disablePageUnloadEvents:\n * ['unload']` bfcache/Lighthouse fix — comes from {@link createTelemetryConfig}.\n */\nexport function useTelemetry(options: TelemetryOptions = {}) {\n const cloudRole = options.cloudRole ?? DEFAULT_CLOUD_ROLE\n const environment = options.environment ?? 'unknown'\n\n const isEnabled = computed(() => {\n return !!(options.connectionString || options.instrumentationKey)\n })\n\n function buildSharedProperties(): Record<string, string> {\n const appVersion = resolveAppVersion(options.appVersion)\n const referrerHost =\n typeof document !== 'undefined' && document.referrer\n ? (() => {\n try {\n return new URL(document.referrer).hostname\n } catch {\n return 'unknown'\n }\n })()\n : 'direct'\n\n return {\n app_version: appVersion,\n app_name: cloudRole,\n app_host: typeof window !== 'undefined' ? window.location.hostname : '',\n page_path: typeof window !== 'undefined' ? window.location.pathname : '',\n referrer_host: referrerHost,\n }\n }\n\n function buildEventProperties(\n additional?: Record<string, string>,\n ): Record<string, string> {\n return {\n timestamp: new Date().toISOString(),\n environment,\n ...buildSharedProperties(),\n ...additional,\n }\n }\n\n function enrichTelemetryEnvelope(envelope: TelemetryEnvelope) {\n if (!envelope.data) {\n envelope.data = {}\n }\n\n envelope.data.baseData = envelope.data.baseData || {}\n envelope.data.baseData.properties = envelope.data.baseData.properties || {}\n Object.assign(envelope.data.baseData.properties, buildSharedProperties())\n\n const appVersion = resolveAppVersion(options.appVersion)\n envelope.tags = envelope.tags || {}\n envelope.tags['ai.application.ver'] = appVersion\n envelope.tags['ai.cloud.role'] = cloudRole\n envelope.tags['ai.cloud.roleInstance'] =\n typeof window !== 'undefined' ? window.location.hostname : ''\n }\n\n const initialize = (): boolean => {\n try {\n // BEFORE the enabled check, and before anything can navigate. The SPA\n // router destroys document.referrer on the first route change, so the\n // landing referrer/utm has to be snapshotted here or it is gone. It is\n // deliberately not conditional on telemetry being configured: a site with\n // no connection string still benefits from first-touch attribution on its\n // stored form submissions.\n captureJourneyFirstTouch()\n\n if (isInitialized.value && appInsights) {\n return true\n }\n\n if (!isEnabled.value) {\n if (!warnedDisabled) {\n warnedDisabled = true\n // Loud, one-shot warning so this is obvious in DevTools. If you see\n // this in production, the build pipeline is not injecting the App\n // Insights connection string — every track* call below silently\n // no-ops. See web/ site-audit F2.\n console.warn(\n '[telemetry] Application Insights DISABLED: no connection string ' +\n 'or instrumentation key was provided at build time. ' +\n 'All telemetry calls will silently no-op.',\n )\n }\n return false\n }\n\n const resolvedConfig = createTelemetryConfig({\n connectionString: options.connectionString,\n instrumentationKey: options.instrumentationKey,\n enableAutoRouteTracking: options.enableAutoRouteTracking,\n dev: options.dev,\n disablePageUnloadEvents: options.disablePageUnloadEvents,\n overrides: options.overrides,\n })\n // Remember whether the SDK is counting page views for us, so trackPageView()\n // can refuse to count them a second time.\n autoRouteTrackingActive = resolvedConfig.enableAutoRouteTracking !== false\n\n appInsights = new ApplicationInsights({ config: resolvedConfig })\n\n appInsights.loadAppInsights()\n appInsights.addTelemetryInitializer((envelope) => {\n try {\n enrichTelemetryEnvelope(envelope as TelemetryEnvelope)\n } catch (error) {\n console.warn('Failed to add web telemetry context:', error)\n }\n\n return true\n })\n\n // Prefer the live SDK context over cookie parsing for the journey join\n // key: the instance is authoritative, and it stays correct even if the\n // cookies are blocked or renamed by a future SDK version.\n setJourneyIdentityResolver(() => readSdkIdentity(appInsights))\n\n isInitialized.value = true\n initializationError.value = null\n console.log('Application Insights initialized successfully')\n\n // Drain any conversion clicks captured before this transport existed, and stay\n // attached for the rest of the session. See attachConversionCapture().\n attachConversionCapture(trackEvent)\n\n return true\n } catch (error) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown initialization error'\n initializationError.value = errorMessage\n console.error('Failed to initialize Application Insights:', error)\n return false\n }\n }\n\n const trackEvent = (event: TelemetryEvent) => {\n if (!appInsights || !isInitialized.value) {\n if (!warnedNotInitialized) {\n warnedNotInitialized = true\n console.warn(\n `[telemetry] Application Insights not initialized — dropping event \"${event.name}\" ` +\n '(and silencing further \"not initialized\" warnings for this session).',\n )\n }\n return\n }\n\n try {\n appInsights.trackEvent({\n name: event.name,\n properties: buildEventProperties(event.properties),\n measurements: event.measurements,\n })\n } catch (error) {\n console.error('Failed to track event:', error)\n }\n }\n\n /**\n * Send a page view.\n *\n * REFUSES TO SEND when the SDK's own `enableAutoRouteTracking` is active, because\n * the SDK has already emitted a pageView for this navigation and sending another\n * double-counts the page. Pass `{ force: true }` only when you have verified that\n * auto-route tracking genuinely does not fire for your routing setup — and expect\n * to justify it, because a page-view count that is silently 2x is the defect this\n * guard exists to prevent (C-288: 52% of one site's page views were duplicates).\n *\n * If you want manual page views, turn the SDK's tracking off:\n * `useTelemetry({ enableAutoRouteTracking: false })`. One tracker, one count.\n */\n const trackPageView = (pageView?: TelemetryPageView) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping page view tracking')\n return\n }\n\n if (autoRouteTrackingActive && !pageView?.force) {\n if (!warnedDoubleCount) {\n warnedDoubleCount = true\n console.warn(\n '[telemetry] trackPageView() SKIPPED to prevent double-counting: this ' +\n 'instance was initialized with enableAutoRouteTracking, so the App ' +\n 'Insights SDK already reports one page view per navigation. Pass ' +\n '{ enableAutoRouteTracking: false } to useTelemetry() if you want to ' +\n 'report page views manually instead.',\n )\n }\n return\n }\n\n try {\n // Attach the full document.referrer (not just hostname) so we can\n // attribute traffic sources in App Insights pageViews.\n const referrer =\n typeof document !== 'undefined' ? (document.referrer ?? '') : ''\n appInsights.trackPageView({\n name:\n pageView?.name ||\n (typeof document !== 'undefined' ? document.title : undefined),\n uri:\n pageView?.uri ||\n (typeof window !== 'undefined' ? window.location.href : undefined),\n properties: buildEventProperties({\n referrer,\n ...pageView?.properties,\n }),\n measurements: pageView?.measurements,\n })\n } catch (error) {\n console.error('Failed to track page view:', error)\n }\n }\n\n /**\n * Convenience helper for primary CTA conversion measurement. Fires a\n * `cta_click` custom event so conversion rate per landing page can be\n * computed in App Insights / KQL.\n */\n const trackCtaClick = (label: string, page?: string) => {\n trackEvent({\n name: 'cta_click',\n properties: {\n label,\n page:\n page ??\n (typeof window !== 'undefined' ? window.location.pathname : ''),\n },\n })\n }\n\n const trackException = (exception: TelemetryException) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping exception tracking')\n return\n }\n\n try {\n appInsights.trackException({\n exception: exception.exception,\n properties: buildEventProperties({\n url: typeof window !== 'undefined' ? window.location.href : '',\n userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',\n ...exception.properties,\n }),\n measurements: exception.measurements,\n })\n } catch (error) {\n console.error('Failed to track exception:', error)\n }\n }\n\n const trackDependency = (dependency: TelemetryDependency) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping dependency tracking')\n return\n }\n\n try {\n appInsights.trackDependencyData({\n id: `dep-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,\n name: dependency.name,\n data: dependency.data,\n duration: dependency.duration,\n success: dependency.success,\n responseCode: dependency.resultCode || 0,\n properties: buildEventProperties(dependency.properties),\n measurements: dependency.measurements,\n })\n } catch (error) {\n console.error('Failed to track dependency:', error)\n }\n }\n\n const trackMetric = (metric: TelemetryMetric) => {\n if (!appInsights || !isInitialized.value) {\n console.warn('Application Insights not initialized, skipping metric tracking')\n return\n }\n\n try {\n appInsights.trackMetric({\n name: metric.name,\n average: metric.average,\n sampleCount: metric.sampleCount,\n min: metric.min,\n max: metric.max,\n properties: buildEventProperties(metric.properties),\n })\n } catch (error) {\n console.error('Failed to track metric:', error)\n }\n }\n\n const flush = () => {\n if (!appInsights || !isInitialized.value) return\n\n try {\n appInsights.flush()\n } catch (error) {\n console.error('Failed to flush telemetry:', error)\n }\n }\n\n /**\n * The same guard `trackPageView` applies, on the other door into the page-view\n * pipeline. `stopTrackPage` emits a page view just as surely as `trackPageView`\n * does, so leaving it unguarded left the C-288 double-count fully reachable —\n * and `web/` walked straight through it: the marketing site called the manual\n * pair from its router while `enableAutoRouteTracking` kept its `true` default,\n * and live KQL (2026-07-31) caught one /contact navigation writing THREE\n * AppPageViews rows in 700ms. A guard on one entry point is not a guard.\n *\n * Both halves of the pair check this so they stay balanced: skipping only the\n * stop would leave an unterminated page-timing entry inside the SDK.\n */\n const manualPageTrackingIsRedundant = (): boolean => {\n if (!autoRouteTrackingActive) return false\n if (!warnedDoubleCount) {\n warnedDoubleCount = true\n console.warn(\n '[telemetry] startTrackPage()/stopTrackPage() SKIPPED to prevent ' +\n 'double-counting: this instance was initialized with ' +\n 'enableAutoRouteTracking, so the App Insights SDK already reports a ' +\n 'page view per navigation. Pass { enableAutoRouteTracking: false } to ' +\n 'useTelemetry() if you want to time and report page views manually.',\n )\n }\n return true\n }\n\n const startTrackPage = (name?: string) => {\n if (!appInsights || !isInitialized.value) return\n if (manualPageTrackingIsRedundant()) return\n\n try {\n appInsights.startTrackPage(name)\n } catch (error) {\n console.error('Failed to start track page:', error)\n }\n }\n\n const stopTrackPage = (\n name?: string,\n url?: string,\n properties?: Record<string, string>,\n measurements?: Record<string, number>,\n ) => {\n if (!appInsights || !isInitialized.value) return\n if (manualPageTrackingIsRedundant()) return\n\n try {\n appInsights.stopTrackPage(name, url, buildEventProperties(properties), measurements)\n } catch (error) {\n console.error('Failed to stop track page:', error)\n }\n }\n\n return {\n isEnabled,\n isInitialized,\n initializationError,\n initialize,\n flush,\n startTrackPage,\n stopTrackPage,\n trackDependency,\n trackEvent,\n trackException,\n trackMetric,\n trackPageView,\n trackCtaClick,\n }\n}\n\n/** The full public API returned by {@link useTelemetry}. */\nexport type TelemetryApi = ReturnType<typeof useTelemetry>\n"]}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Visitor-journey context for form submissions.
3
+ *
4
+ * A submitted form row and the site's Application Insights telemetry describe
5
+ * the same visit, but nothing joins them: the row knows the message, the
6
+ * telemetry knows the path that produced it. `getJourneyContext()` returns the
7
+ * join key — the ids the SDK is already stamping on every event, plus the
8
+ * first-touch attribution the SPA throws away.
9
+ *
10
+ * WHY FIRST-TOUCH IS CAPTURED AT SESSION START, NOT AT SUBMIT. `document.referrer`
11
+ * is only populated for the document the browser actually loaded. The moment a
12
+ * client-side router takes over, it is gone — so a visitor who lands from Google
13
+ * on a comparison page and clicks through to /contact submits with an empty
14
+ * referrer. Capture-at-submit is not "less accurate" here; it is structurally
15
+ * always empty for the multi-page journeys worth attributing. So
16
+ * `captureJourneyFirstTouch()` snapshots the referrer, landing path, and utm_*
17
+ * parameters once per session into `sessionStorage`, and every later read serves
18
+ * that snapshot.
19
+ *
20
+ * This module deliberately imports nothing. It must stay usable by a consumer
21
+ * that has no App Insights SDK loaded (the portal runs its own hand-rolled
22
+ * telemetry boot), and it must never be the reason a form submission fails —
23
+ * every entry point swallows its own errors and degrades to an empty result.
24
+ */
25
+ /** First-touch UTM parameters parsed from the landing URL. */
26
+ interface JourneyUtm {
27
+ source?: string;
28
+ medium?: string;
29
+ campaign?: string;
30
+ term?: string;
31
+ content?: string;
32
+ }
33
+ /** The join key attached to an outbound form submission. */
34
+ interface JourneyContext {
35
+ /** App Insights user id — the visitor, across sessions. */
36
+ visitorId?: string;
37
+ /** App Insights session id — this visit. */
38
+ sessionId?: string;
39
+ /** Route path the form was submitted from. */
40
+ pagePath?: string;
41
+ /** Full URL the form was submitted from. */
42
+ pageUrl?: string;
43
+ /** First-touch `document.referrer` for this session. */
44
+ referrer?: string;
45
+ /** First-touch landing path (pathname + query) for this session. */
46
+ landingPath?: string;
47
+ /** First-touch UTM parameters, when the landing URL carried any. */
48
+ utm?: JourneyUtm;
49
+ }
50
+ /** Resolves visitor/session ids from a live telemetry SDK instance. */
51
+ type JourneyIdentityResolver = () => {
52
+ visitorId?: string;
53
+ sessionId?: string;
54
+ } | undefined;
55
+ /** sessionStorage key holding this session's first-touch snapshot. */
56
+ declare const JOURNEY_FIRST_TOUCH_KEY = "dcs:journey:first-touch";
57
+ /**
58
+ * Per-field clamp. Mirrors the server's `journey.FieldMaxLength` so a value that
59
+ * survives here is never silently truncated on the way into storage.
60
+ */
61
+ declare const JOURNEY_FIELD_MAX_LENGTH = 512;
62
+ /**
63
+ * Register the live-SDK source for visitor/session ids.
64
+ *
65
+ * The composable calls this after `loadAppInsights()`. Without it — or when the
66
+ * resolver returns nothing — {@link getJourneyContext} falls back to parsing the
67
+ * `ai_user` / `ai_session` cookies the SDK writes, which is why the portal (with
68
+ * its own SDK boot and no call to this function) still reports usable ids.
69
+ */
70
+ declare function setJourneyIdentityResolver(resolver: JourneyIdentityResolver | null): void;
71
+ /**
72
+ * Snapshot this session's first-touch context, once.
73
+ *
74
+ * Idempotent and safe to call from anywhere: a snapshot already in
75
+ * sessionStorage always wins, so a later call on a deep page can never overwrite
76
+ * the real landing page with the current one. Called by the composable's
77
+ * `initialize()` (as early in the app's life as telemetry exists) and lazily by
78
+ * {@link getJourneyContext} so a consumer that never initializes the shared SDK
79
+ * still records *something* rather than nothing.
80
+ */
81
+ declare function captureJourneyFirstTouch(): void;
82
+ /**
83
+ * Build the journey context to attach to an outbound form submission.
84
+ *
85
+ * NEVER THROWS. Returns `{}` when there is nothing to report (SSR, blocked
86
+ * storage, telemetry-dark site), and callers omit the field entirely in that
87
+ * case — a telemetry failure must not cost a lead.
88
+ */
89
+ declare function getJourneyContext(): JourneyContext;
90
+ /** The wire shape the DCS submission contracts accept (`SubmissionTelemetry`). */
91
+ interface JourneyTelemetryPayload {
92
+ visitorId?: string;
93
+ sessionId?: string;
94
+ pagePath?: string;
95
+ pageUrl?: string;
96
+ referrer?: string;
97
+ landingPath?: string;
98
+ utmSource?: string;
99
+ utmMedium?: string;
100
+ utmCampaign?: string;
101
+ utmTerm?: string;
102
+ utmContent?: string;
103
+ }
104
+ /**
105
+ * Flatten a {@link JourneyContext} into the contract's `telemetry` object, or
106
+ * return `undefined` when there is nothing worth sending so the caller can omit
107
+ * the field rather than post an empty object.
108
+ */
109
+ declare function toJourneyTelemetryPayload(context?: JourneyContext): JourneyTelemetryPayload | undefined;
110
+
111
+ export { JOURNEY_FIELD_MAX_LENGTH, JOURNEY_FIRST_TOUCH_KEY, type JourneyContext, type JourneyIdentityResolver, type JourneyTelemetryPayload, type JourneyUtm, captureJourneyFirstTouch, getJourneyContext, setJourneyIdentityResolver, toJourneyTelemetryPayload };
@@ -0,0 +1,3 @@
1
+ export { JOURNEY_FIELD_MAX_LENGTH, JOURNEY_FIRST_TOUCH_KEY, captureJourneyFirstTouch, getJourneyContext, setJourneyIdentityResolver, toJourneyTelemetryPayload } from './chunk-DKX4AC2U.js';
2
+ //# sourceMappingURL=journey.js.map
3
+ //# sourceMappingURL=journey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"journey.js"}