@duffcloudservices/telemetry 0.2.0 → 0.3.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 +49 -5
- package/dist/index.d.ts +65 -1
- package/dist/index.js +190 -65
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/composable.test.ts +91 -16
- package/src/composable.ts +393 -80
- package/src/index.ts +7 -2
- package/src/lazySdk.test.ts +698 -0
- package/src/syntheticCapture.test.ts +74 -0
- package/src/syntheticCapture.ts +50 -0
package/README.md
CHANGED
|
@@ -29,6 +29,38 @@ pnpm add @duffcloudservices/telemetry
|
|
|
29
29
|
pnpm add @microsoft/applicationinsights-web vue
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
## The SDK loads lazily — adoption does not move it onto your entry chunk
|
|
33
|
+
|
|
34
|
+
Importing `useTelemetry` costs your **entry chunk** nothing but this package's own
|
|
35
|
+
code (~6–7KB raw / ~2KB gzip minified). The ~192KB raw / ~78KB gzip App Insights
|
|
36
|
+
web SDK is reached through a dynamic `import()` inside `initialize()`, so your
|
|
37
|
+
bundler emits it as its **own async chunk**, fetched only when `initialize()` runs.
|
|
38
|
+
|
|
39
|
+
This is not a micro-optimisation, it is the reason the package is adoptable. Before
|
|
40
|
+
`0.3.0` the composable imported the SDK statically. Measured on a live customer site
|
|
41
|
+
(bryans-handyman-solutions, 2026-08-11), adopting `0.2.1` in place of the site's
|
|
42
|
+
hand-rolled lazy composable moved the whole SDK onto the entry chunk:
|
|
43
|
+
|
|
44
|
+
| | entry chunk raw | entry chunk gzip | SDK on critical path? |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| site's own lazy composable | 213,172 B | 76,398 B | no — separate async chunk |
|
|
47
|
+
| adopting `0.2.1` (static SDK) | 405,817 B **(+90.4%)** | 153,493 B **(+101.2%)** | **yes** |
|
|
48
|
+
| adopting `0.3.0` (lazy SDK) | 219,842 B **(+3.1%)** | 78,582 B **(+2.9%)** | no — separate async chunk, byte-identical to the site's own |
|
|
49
|
+
|
|
50
|
+
Total transferred JS barely moved in the middle row — the SDK simply relocated onto
|
|
51
|
+
the critical path and defeated the site's deliberate `requestIdleCallback` deferral.
|
|
52
|
+
|
|
53
|
+
Laziness costs you no telemetry:
|
|
54
|
+
|
|
55
|
+
- **Calls made during the load are buffered**, with their call-time properties, and
|
|
56
|
+
replayed in order once the SDK lands (bounded at 100 calls). A landing pageView or
|
|
57
|
+
a boot-time `trackException` is not lost.
|
|
58
|
+
- **`initialize()` is still synchronous** and still returns `boolean` — "telemetry is
|
|
59
|
+
enabled and the SDK load has started". Nothing about the call site changes.
|
|
60
|
+
- **Concurrent `initialize()` callers share one load** and one SDK instance.
|
|
61
|
+
- `whenReady(): Promise<boolean>` is available for the rare caller that must
|
|
62
|
+
sequence work after the SDK exists. You should not need it to avoid losing events.
|
|
63
|
+
|
|
32
64
|
## Usage — composable (SPA / eager)
|
|
33
65
|
|
|
34
66
|
The connection string, cloud role, environment and app version are supplied by
|
|
@@ -48,15 +80,27 @@ const telemetry = useTelemetry({
|
|
|
48
80
|
})
|
|
49
81
|
|
|
50
82
|
telemetry.initialize() // synchronous; no-op (with a loud one-shot warn) when unconfigured
|
|
51
|
-
telemetry.trackCtaClick('hero-cta', '/contact')
|
|
83
|
+
telemetry.trackCtaClick('hero-cta', '/contact') // buffered if the SDK is still loading
|
|
84
|
+
|
|
85
|
+
// Only when you must sequence work after the SDK itself exists:
|
|
86
|
+
void telemetry.whenReady().then(() => telemetry.trackPageView())
|
|
52
87
|
```
|
|
53
88
|
|
|
54
|
-
`initialize()`
|
|
89
|
+
`initialize()` returns `boolean` **synchronously** — the SDK loads in the background
|
|
90
|
+
(see above). Events fired before it lands are buffered, not dropped, so the common
|
|
91
|
+
case needs no `await` at all.
|
|
92
|
+
|
|
93
|
+
> **Migrating from a site-local composable?** The site versions were
|
|
94
|
+
> `async initialize(): Promise<boolean>`, so `void initialize().then(…)` must become
|
|
95
|
+
> `initialize(); void whenReady().then(…)`. TypeScript flags the old form
|
|
96
|
+
> (`Property 'then' does not exist on type 'boolean'`), so this cannot slip through a
|
|
97
|
+
> site's `type-check`.
|
|
55
98
|
|
|
56
99
|
## Usage — config only (lazy / inert loaders)
|
|
57
100
|
|
|
58
|
-
|
|
59
|
-
|
|
101
|
+
`useTelemetry` already loads the SDK lazily, so most sites no longer need this. It
|
|
102
|
+
remains for consumers that construct the SDK themselves — a bespoke boot, or a
|
|
103
|
+
non-Vue surface — and want the shared config without the composable:
|
|
60
104
|
|
|
61
105
|
```ts
|
|
62
106
|
import { createTelemetryConfig } from '@duffcloudservices/telemetry/config'
|
|
@@ -103,7 +147,7 @@ omit the field entirely rather than block a submission.
|
|
|
103
147
|
|
|
104
148
|
| Export | Description |
|
|
105
149
|
| --- | --- |
|
|
106
|
-
| `useTelemetry(options?)` | Full composable: `initialize`, `trackEvent`, `trackPageView`, `trackCtaClick`, `trackException`, `trackMetric`, `trackDependency`, `flush`, `startTrackPage`, `stopTrackPage`, plus `isEnabled` / `isInitialized` / `initializationError` refs. |
|
|
150
|
+
| `useTelemetry(options?)` | Full composable: `initialize`, `whenReady`, `trackEvent`, `trackPageView`, `trackCtaClick`, `trackException`, `trackMetric`, `trackDependency`, `flush`, `startTrackPage`, `stopTrackPage`, plus `isEnabled` / `isInitialized` / `initializationError` refs. Loads the App Insights SDK lazily. |
|
|
107
151
|
| `createTelemetryConfig(options?)` | Builds the App Insights config with DCS defaults + the `disablePageUnloadEvents: ['unload']` fix. Pure, no SDK import. |
|
|
108
152
|
| `getJourneyContext()` | Visitor-journey join key for a form submission. Never throws. Also at `@duffcloudservices/telemetry/journey` (no SDK import). |
|
|
109
153
|
| `toJourneyTelemetryPayload(context?)` | Flattens a journey context into the DCS `SubmissionTelemetry` wire shape, or `undefined` when empty. |
|
package/dist/index.d.ts
CHANGED
|
@@ -12,12 +12,36 @@ import '@microsoft/applicationinsights-web';
|
|
|
12
12
|
* pass plain values in) so this package stays portable and build-tool agnostic.
|
|
13
13
|
* The shared App Insights config — including the `disablePageUnloadEvents:
|
|
14
14
|
* ['unload']` bfcache/Lighthouse fix — comes from {@link createTelemetryConfig}.
|
|
15
|
+
*
|
|
16
|
+
* ## The SDK loads LAZILY (C-601)
|
|
17
|
+
*
|
|
18
|
+
* `initialize()` still returns `boolean` synchronously — "telemetry is enabled and
|
|
19
|
+
* the SDK load has started" — but the App Insights web SDK itself arrives through
|
|
20
|
+
* a dynamic `import()`, so a bundler emits it as its own async chunk instead of
|
|
21
|
+
* folding ~192KB raw / ~78KB gzip into the consumer's entry chunk. Adopting this
|
|
22
|
+
* package therefore no longer moves the SDK onto the critical path.
|
|
23
|
+
*
|
|
24
|
+
* Three things make that safe:
|
|
25
|
+
*
|
|
26
|
+
* 1. **Buffered, not dropped.** track* calls made between `initialize()` and the
|
|
27
|
+
* chunk landing are queued with their call-time properties and replayed in
|
|
28
|
+
* order (bounded at {@link PENDING_CALL_LIMIT}). The site-local composables this
|
|
29
|
+
* package replaces dropped them.
|
|
30
|
+
* 2. **One init.** Concurrent `initialize()` callers share one `import()` and one
|
|
31
|
+
* `new ApplicationInsights(...)` via {@link initPromise}.
|
|
32
|
+
* 3. **Guards stay synchronous.** `enableAutoRouteTracking` is resolved before the
|
|
33
|
+
* `await`, so the C-288 double-count guard is armed for calls made during the
|
|
34
|
+
* load, not only after it.
|
|
35
|
+
*
|
|
36
|
+
* `whenReady()` is there for the rare caller that must sequence work after the SDK
|
|
37
|
+
* exists. You should not need it to avoid losing events.
|
|
15
38
|
*/
|
|
16
39
|
declare function useTelemetry(options?: TelemetryOptions): {
|
|
17
40
|
isEnabled: vue.ComputedRef<boolean>;
|
|
18
41
|
isInitialized: vue.Ref<boolean, boolean>;
|
|
19
42
|
initializationError: vue.Ref<string | null, string | null>;
|
|
20
43
|
initialize: () => boolean;
|
|
44
|
+
whenReady: () => Promise<boolean>;
|
|
21
45
|
flush: () => void;
|
|
22
46
|
startTrackPage: (name?: string) => void;
|
|
23
47
|
stopTrackPage: (name?: string, url?: string, properties?: Record<string, string>, measurements?: Record<string, number>) => void;
|
|
@@ -31,4 +55,44 @@ declare function useTelemetry(options?: TelemetryOptions): {
|
|
|
31
55
|
/** The full public API returned by {@link useTelemetry}. */
|
|
32
56
|
type TelemetryApi = ReturnType<typeof useTelemetry>;
|
|
33
57
|
|
|
34
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Synthetic-capture detection (C-614).
|
|
60
|
+
*
|
|
61
|
+
* The post-deploy "Capture page snapshots" CI step drives real Chromium against
|
|
62
|
+
* the LIVE production URL of the site it just deployed, so the site's telemetry
|
|
63
|
+
* boots exactly as it would for a visitor and every page the capture walked is
|
|
64
|
+
* recorded as a production pageView — during our own deploy run. Measured on
|
|
65
|
+
* coron8 over the 8 days to 2026-08-11: 49 synthetic pageViews against 14
|
|
66
|
+
* organic, 78% of the entire store, all 49 on 08-11 across 5 deploys versus 3
|
|
67
|
+
* real visits that day.
|
|
68
|
+
*
|
|
69
|
+
* The capture tooling marks every URL it loads (`packages/cli/src/commands/
|
|
70
|
+
* capture-snapshots.ts` and each site's own `scripts/capture-snapshots.*`).
|
|
71
|
+
* `initialize()` reads that mark and declines, so nothing is emitted at all.
|
|
72
|
+
* Suppressing here rather than tagging and filtering downstream keeps the
|
|
73
|
+
* denominator honest in every read path at once instead of obliging each of
|
|
74
|
+
* them to remember an exclusion — the C-430 preview/localhost blend is the
|
|
75
|
+
* recorded UNSOLVED residual that shape produces.
|
|
76
|
+
*
|
|
77
|
+
* DELIBERATE DUPLICATION. `@duffcloudservices/cms` carries the same predicate in
|
|
78
|
+
* its own `syntheticCapture.ts`. These two packages are intentionally decoupled
|
|
79
|
+
* — this one is the low-level telemetry package (peer deps: the App Insights SDK
|
|
80
|
+
* and vue, nothing else), cms is the site-content package — and a dependency
|
|
81
|
+
* edge between them to share ten lines of pure string matching would cost more
|
|
82
|
+
* than the duplication does. The parameter list is the contract; keep the two
|
|
83
|
+
* lists identical, and note that the CI drift guard in the canonical
|
|
84
|
+
* site-deploy workflow greps for these same names.
|
|
85
|
+
*/
|
|
86
|
+
/** Query parameters that mark a document as an automated capture, not a visit. */
|
|
87
|
+
declare const SYNTHETIC_CAPTURE_PARAMS: readonly ["dcs-hide-ribbon", "dcs-no-telemetry"];
|
|
88
|
+
/**
|
|
89
|
+
* Whether this document is an automated capture rather than a visit worth counting.
|
|
90
|
+
*
|
|
91
|
+
* Reads `window.location.search` on every call rather than caching, so a
|
|
92
|
+
* client-side route change is judged on the CURRENT URL. Returns `false` on the
|
|
93
|
+
* server, and `false` when the query string cannot be read: over-suppression
|
|
94
|
+
* deletes real visits and leaves no trace to notice it by, so this fails OPEN.
|
|
95
|
+
*/
|
|
96
|
+
declare function syntheticCaptureDetected(): boolean;
|
|
97
|
+
|
|
98
|
+
export { SYNTHETIC_CAPTURE_PARAMS, type TelemetryApi, TelemetryDependency, TelemetryEvent, TelemetryException, TelemetryMetric, TelemetryOptions, TelemetryPageView, syntheticCaptureDetected, useTelemetry };
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,67 @@ 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
3
|
import { captureJourneyFirstTouch, setJourneyIdentityResolver } from './chunk-DKX4AC2U.js';
|
|
4
4
|
export { JOURNEY_FIELD_MAX_LENGTH, JOURNEY_FIRST_TOUCH_KEY, captureJourneyFirstTouch, getJourneyContext, setJourneyIdentityResolver, toJourneyTelemetryPayload } from './chunk-DKX4AC2U.js';
|
|
5
|
-
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
|
|
6
5
|
import { ref, computed } from 'vue';
|
|
7
6
|
|
|
7
|
+
// src/syntheticCapture.ts
|
|
8
|
+
var SYNTHETIC_CAPTURE_PARAMS = ["dcs-hide-ribbon", "dcs-no-telemetry"];
|
|
9
|
+
function syntheticCaptureDetected() {
|
|
10
|
+
if (typeof window === "undefined") return false;
|
|
11
|
+
try {
|
|
12
|
+
const params = new URLSearchParams(window.location.search);
|
|
13
|
+
return SYNTHETIC_CAPTURE_PARAMS.some((param) => params.has(param));
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/composable.ts
|
|
8
20
|
var appInsights = null;
|
|
9
21
|
var isInitialized = ref(false);
|
|
10
22
|
var initializationError = ref(null);
|
|
23
|
+
var initPromise = null;
|
|
24
|
+
var pendingSdkCalls = [];
|
|
25
|
+
var PENDING_CALL_LIMIT = 100;
|
|
26
|
+
var warnedBufferFull = false;
|
|
27
|
+
function bufferWhileLoading(call) {
|
|
28
|
+
if (!initPromise) return false;
|
|
29
|
+
if (pendingSdkCalls.length >= PENDING_CALL_LIMIT) {
|
|
30
|
+
if (!warnedBufferFull) {
|
|
31
|
+
warnedBufferFull = true;
|
|
32
|
+
console.warn(
|
|
33
|
+
`[telemetry] pre-init buffer full (${PENDING_CALL_LIMIT} calls) \u2014 dropping telemetry until the Application Insights SDK chunk finishes loading. If you see this, the SDK chunk is failing to load, not merely slow.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
pendingSdkCalls.push(call);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
function drainPendingSdkCalls(instance) {
|
|
42
|
+
const calls = pendingSdkCalls.splice(0, pendingSdkCalls.length);
|
|
43
|
+
for (const call of calls) {
|
|
44
|
+
try {
|
|
45
|
+
call(instance);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("Failed to replay buffered telemetry:", error);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function discardPendingSdkCalls() {
|
|
52
|
+
pendingSdkCalls.length = 0;
|
|
53
|
+
}
|
|
54
|
+
async function loadSdk() {
|
|
55
|
+
const module = await import('@microsoft/applicationinsights-web');
|
|
56
|
+
return module.ApplicationInsights;
|
|
57
|
+
}
|
|
58
|
+
function nowMs() {
|
|
59
|
+
return typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
|
60
|
+
}
|
|
61
|
+
var preInitPageTimings = /* @__PURE__ */ new Map();
|
|
62
|
+
function resolvePageName(name) {
|
|
63
|
+
if (typeof name === "string") return name;
|
|
64
|
+
return typeof document !== "undefined" ? document.title : "";
|
|
65
|
+
}
|
|
11
66
|
var warnedDisabled = false;
|
|
12
67
|
var warnedNotInitialized = false;
|
|
13
68
|
var warnedDoubleCount = false;
|
|
@@ -91,9 +146,15 @@ function useTelemetry(options = {}) {
|
|
|
91
146
|
const initialize = () => {
|
|
92
147
|
try {
|
|
93
148
|
captureJourneyFirstTouch();
|
|
149
|
+
if (syntheticCaptureDetected()) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
94
152
|
if (isInitialized.value && appInsights) {
|
|
95
153
|
return true;
|
|
96
154
|
}
|
|
155
|
+
if (initPromise) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
97
158
|
if (!isEnabled.value) {
|
|
98
159
|
if (!warnedDisabled) {
|
|
99
160
|
warnedDisabled = true;
|
|
@@ -112,21 +173,37 @@ function useTelemetry(options = {}) {
|
|
|
112
173
|
overrides: options.overrides
|
|
113
174
|
});
|
|
114
175
|
autoRouteTrackingActive = resolvedConfig.enableAutoRouteTracking !== false;
|
|
115
|
-
|
|
116
|
-
appInsights.loadAppInsights();
|
|
117
|
-
appInsights.addTelemetryInitializer((envelope) => {
|
|
176
|
+
initPromise = (async () => {
|
|
118
177
|
try {
|
|
119
|
-
|
|
178
|
+
const ApplicationInsightsCtor = await loadSdk();
|
|
179
|
+
const instance = new ApplicationInsightsCtor({ config: resolvedConfig });
|
|
180
|
+
instance.loadAppInsights();
|
|
181
|
+
instance.addTelemetryInitializer((envelope) => {
|
|
182
|
+
try {
|
|
183
|
+
enrichTelemetryEnvelope(envelope);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.warn("Failed to add web telemetry context:", error);
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
});
|
|
189
|
+
appInsights = instance;
|
|
190
|
+
setJourneyIdentityResolver(() => readSdkIdentity(appInsights));
|
|
191
|
+
isInitialized.value = true;
|
|
192
|
+
initializationError.value = null;
|
|
193
|
+
console.log("Application Insights initialized successfully");
|
|
194
|
+
drainPendingSdkCalls(instance);
|
|
195
|
+
attachConversionCapture(trackEvent);
|
|
196
|
+
return true;
|
|
120
197
|
} catch (error) {
|
|
121
|
-
|
|
198
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown initialization error";
|
|
199
|
+
initializationError.value = errorMessage;
|
|
200
|
+
console.error("Failed to initialize Application Insights:", error);
|
|
201
|
+
discardPendingSdkCalls();
|
|
202
|
+
preInitPageTimings.clear();
|
|
203
|
+
initPromise = null;
|
|
204
|
+
return false;
|
|
122
205
|
}
|
|
123
|
-
|
|
124
|
-
});
|
|
125
|
-
setJourneyIdentityResolver(() => readSdkIdentity(appInsights));
|
|
126
|
-
isInitialized.value = true;
|
|
127
|
-
initializationError.value = null;
|
|
128
|
-
console.log("Application Insights initialized successfully");
|
|
129
|
-
attachConversionCapture(trackEvent);
|
|
206
|
+
})();
|
|
130
207
|
return true;
|
|
131
208
|
} catch (error) {
|
|
132
209
|
const errorMessage = error instanceof Error ? error.message : "Unknown initialization error";
|
|
@@ -135,8 +212,18 @@ function useTelemetry(options = {}) {
|
|
|
135
212
|
return false;
|
|
136
213
|
}
|
|
137
214
|
};
|
|
215
|
+
const whenReady = () => {
|
|
216
|
+
if (isInitialized.value && appInsights) return Promise.resolve(true);
|
|
217
|
+
return initPromise ?? Promise.resolve(false);
|
|
218
|
+
};
|
|
138
219
|
const trackEvent = (event) => {
|
|
139
220
|
if (!appInsights || !isInitialized.value) {
|
|
221
|
+
const payload = {
|
|
222
|
+
name: event.name,
|
|
223
|
+
properties: buildEventProperties(event.properties),
|
|
224
|
+
measurements: event.measurements
|
|
225
|
+
};
|
|
226
|
+
if (bufferWhileLoading((instance) => instance.trackEvent(payload))) return;
|
|
140
227
|
if (!warnedNotInitialized) {
|
|
141
228
|
warnedNotInitialized = true;
|
|
142
229
|
console.warn(
|
|
@@ -156,10 +243,6 @@ function useTelemetry(options = {}) {
|
|
|
156
243
|
}
|
|
157
244
|
};
|
|
158
245
|
const trackPageView = (pageView) => {
|
|
159
|
-
if (!appInsights || !isInitialized.value) {
|
|
160
|
-
console.warn("Application Insights not initialized, skipping page view tracking");
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
246
|
if (autoRouteTrackingActive && !pageView?.force) {
|
|
164
247
|
if (!warnedDoubleCount) {
|
|
165
248
|
warnedDoubleCount = true;
|
|
@@ -169,17 +252,23 @@ function useTelemetry(options = {}) {
|
|
|
169
252
|
}
|
|
170
253
|
return;
|
|
171
254
|
}
|
|
255
|
+
const referrer = typeof document !== "undefined" ? document.referrer ?? "" : "";
|
|
256
|
+
const payload = {
|
|
257
|
+
name: pageView?.name || (typeof document !== "undefined" ? document.title : void 0),
|
|
258
|
+
uri: pageView?.uri || (typeof window !== "undefined" ? window.location.href : void 0),
|
|
259
|
+
properties: buildEventProperties({
|
|
260
|
+
referrer,
|
|
261
|
+
...pageView?.properties
|
|
262
|
+
}),
|
|
263
|
+
measurements: pageView?.measurements
|
|
264
|
+
};
|
|
265
|
+
if (!appInsights || !isInitialized.value) {
|
|
266
|
+
if (bufferWhileLoading((instance) => instance.trackPageView(payload))) return;
|
|
267
|
+
console.warn("Application Insights not initialized, skipping page view tracking");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
172
270
|
try {
|
|
173
|
-
|
|
174
|
-
appInsights.trackPageView({
|
|
175
|
-
name: pageView?.name || (typeof document !== "undefined" ? document.title : void 0),
|
|
176
|
-
uri: pageView?.uri || (typeof window !== "undefined" ? window.location.href : void 0),
|
|
177
|
-
properties: buildEventProperties({
|
|
178
|
-
referrer,
|
|
179
|
-
...pageView?.properties
|
|
180
|
-
}),
|
|
181
|
-
measurements: pageView?.measurements
|
|
182
|
-
});
|
|
271
|
+
appInsights.trackPageView(payload);
|
|
183
272
|
} catch (error) {
|
|
184
273
|
console.error("Failed to track page view:", error);
|
|
185
274
|
}
|
|
@@ -194,58 +283,64 @@ function useTelemetry(options = {}) {
|
|
|
194
283
|
});
|
|
195
284
|
};
|
|
196
285
|
const trackException = (exception) => {
|
|
286
|
+
const payload = {
|
|
287
|
+
exception: exception.exception,
|
|
288
|
+
properties: buildEventProperties({
|
|
289
|
+
url: typeof window !== "undefined" ? window.location.href : "",
|
|
290
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
291
|
+
...exception.properties
|
|
292
|
+
}),
|
|
293
|
+
measurements: exception.measurements
|
|
294
|
+
};
|
|
197
295
|
if (!appInsights || !isInitialized.value) {
|
|
296
|
+
if (bufferWhileLoading((instance) => instance.trackException(payload))) return;
|
|
198
297
|
console.warn("Application Insights not initialized, skipping exception tracking");
|
|
199
298
|
return;
|
|
200
299
|
}
|
|
201
300
|
try {
|
|
202
|
-
appInsights.trackException(
|
|
203
|
-
exception: exception.exception,
|
|
204
|
-
properties: buildEventProperties({
|
|
205
|
-
url: typeof window !== "undefined" ? window.location.href : "",
|
|
206
|
-
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
|
|
207
|
-
...exception.properties
|
|
208
|
-
}),
|
|
209
|
-
measurements: exception.measurements
|
|
210
|
-
});
|
|
301
|
+
appInsights.trackException(payload);
|
|
211
302
|
} catch (error) {
|
|
212
303
|
console.error("Failed to track exception:", error);
|
|
213
304
|
}
|
|
214
305
|
};
|
|
215
306
|
const trackDependency = (dependency) => {
|
|
307
|
+
const payload = {
|
|
308
|
+
id: `dep-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
309
|
+
name: dependency.name,
|
|
310
|
+
data: dependency.data,
|
|
311
|
+
duration: dependency.duration,
|
|
312
|
+
success: dependency.success,
|
|
313
|
+
responseCode: dependency.resultCode || 0,
|
|
314
|
+
properties: buildEventProperties(dependency.properties),
|
|
315
|
+
measurements: dependency.measurements
|
|
316
|
+
};
|
|
216
317
|
if (!appInsights || !isInitialized.value) {
|
|
318
|
+
if (bufferWhileLoading((instance) => instance.trackDependencyData(payload))) return;
|
|
217
319
|
console.warn("Application Insights not initialized, skipping dependency tracking");
|
|
218
320
|
return;
|
|
219
321
|
}
|
|
220
322
|
try {
|
|
221
|
-
appInsights.trackDependencyData(
|
|
222
|
-
id: `dep-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
|
223
|
-
name: dependency.name,
|
|
224
|
-
data: dependency.data,
|
|
225
|
-
duration: dependency.duration,
|
|
226
|
-
success: dependency.success,
|
|
227
|
-
responseCode: dependency.resultCode || 0,
|
|
228
|
-
properties: buildEventProperties(dependency.properties),
|
|
229
|
-
measurements: dependency.measurements
|
|
230
|
-
});
|
|
323
|
+
appInsights.trackDependencyData(payload);
|
|
231
324
|
} catch (error) {
|
|
232
325
|
console.error("Failed to track dependency:", error);
|
|
233
326
|
}
|
|
234
327
|
};
|
|
235
328
|
const trackMetric = (metric) => {
|
|
329
|
+
const payload = {
|
|
330
|
+
name: metric.name,
|
|
331
|
+
average: metric.average,
|
|
332
|
+
sampleCount: metric.sampleCount,
|
|
333
|
+
min: metric.min,
|
|
334
|
+
max: metric.max,
|
|
335
|
+
properties: buildEventProperties(metric.properties)
|
|
336
|
+
};
|
|
236
337
|
if (!appInsights || !isInitialized.value) {
|
|
338
|
+
if (bufferWhileLoading((instance) => instance.trackMetric(payload))) return;
|
|
237
339
|
console.warn("Application Insights not initialized, skipping metric tracking");
|
|
238
340
|
return;
|
|
239
341
|
}
|
|
240
342
|
try {
|
|
241
|
-
appInsights.trackMetric(
|
|
242
|
-
name: metric.name,
|
|
243
|
-
average: metric.average,
|
|
244
|
-
sampleCount: metric.sampleCount,
|
|
245
|
-
min: metric.min,
|
|
246
|
-
max: metric.max,
|
|
247
|
-
properties: buildEventProperties(metric.properties)
|
|
248
|
-
});
|
|
343
|
+
appInsights.trackMetric(payload);
|
|
249
344
|
} catch (error) {
|
|
250
345
|
console.error("Failed to track metric:", error);
|
|
251
346
|
}
|
|
@@ -269,28 +364,58 @@ function useTelemetry(options = {}) {
|
|
|
269
364
|
return true;
|
|
270
365
|
};
|
|
271
366
|
const startTrackPage = (name) => {
|
|
272
|
-
if (!appInsights || !isInitialized.value) return;
|
|
273
367
|
if (manualPageTrackingIsRedundant()) return;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
368
|
+
if (appInsights && isInitialized.value) {
|
|
369
|
+
try {
|
|
370
|
+
appInsights.startTrackPage(name);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error("Failed to start track page:", error);
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (initPromise) {
|
|
377
|
+
preInitPageTimings.set(resolvePageName(name), nowMs());
|
|
278
378
|
}
|
|
279
379
|
};
|
|
280
380
|
const stopTrackPage = (name, url, properties, measurements) => {
|
|
281
|
-
if (!appInsights || !isInitialized.value) return;
|
|
282
381
|
if (manualPageTrackingIsRedundant()) return;
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
382
|
+
if (appInsights && isInitialized.value) {
|
|
383
|
+
try {
|
|
384
|
+
appInsights.stopTrackPage(name, url, buildEventProperties(properties), measurements);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error("Failed to stop track page:", error);
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
287
389
|
}
|
|
390
|
+
if (!initPromise) return;
|
|
391
|
+
const key = resolvePageName(name);
|
|
392
|
+
const startedAt = preInitPageTimings.get(key);
|
|
393
|
+
if (startedAt === void 0) return;
|
|
394
|
+
preInitPageTimings.delete(key);
|
|
395
|
+
const duration = Math.max(0, Math.round(nowMs() - startedAt));
|
|
396
|
+
const payload = {
|
|
397
|
+
name: key,
|
|
398
|
+
uri: url ?? (typeof window !== "undefined" ? window.location.href : void 0),
|
|
399
|
+
// `duration` as a STRING alongside the custom properties is the SDK's OWN
|
|
400
|
+
// encoding for a timed page view — `_pageTracking.action` does
|
|
401
|
+
// `properties.duration = duration.toString()` — and it is what
|
|
402
|
+
// `sendPageViewInternal` reads to back-date `startTime`.
|
|
403
|
+
//
|
|
404
|
+
// The cast is deliberate: `IPageViewTelemetry` declares `properties.duration`
|
|
405
|
+
// as `number`, which its own `stopTrackPage` path does not honour. Matching
|
|
406
|
+
// the runtime the collector actually parses beats matching a type that the
|
|
407
|
+
// emitting SDK contradicts.
|
|
408
|
+
properties: { ...buildEventProperties(properties), duration: String(duration) },
|
|
409
|
+
measurements
|
|
410
|
+
};
|
|
411
|
+
bufferWhileLoading((instance) => instance.trackPageView(payload));
|
|
288
412
|
};
|
|
289
413
|
return {
|
|
290
414
|
isEnabled,
|
|
291
415
|
isInitialized,
|
|
292
416
|
initializationError,
|
|
293
417
|
initialize,
|
|
418
|
+
whenReady,
|
|
294
419
|
flush,
|
|
295
420
|
startTrackPage,
|
|
296
421
|
stopTrackPage,
|
|
@@ -303,6 +428,6 @@ function useTelemetry(options = {}) {
|
|
|
303
428
|
};
|
|
304
429
|
}
|
|
305
430
|
|
|
306
|
-
export { useTelemetry };
|
|
431
|
+
export { SYNTHETIC_CAPTURE_PARAMS, syntheticCaptureDetected, useTelemetry };
|
|
307
432
|
//# sourceMappingURL=index.js.map
|
|
308
433
|
//# sourceMappingURL=index.js.map
|