@servicetitan/journey 2.1.0 → 3.0.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.
Files changed (163) hide show
  1. package/README.md +1 -1
  2. package/dist/__test-utils__/test-runtime.d.ts.map +1 -1
  3. package/dist/__test-utils__/test-runtime.js +9 -2
  4. package/dist/__test-utils__/test-runtime.js.map +1 -1
  5. package/dist/__tests__/core/sanitize.test.d.ts +2 -0
  6. package/dist/__tests__/core/sanitize.test.d.ts.map +1 -0
  7. package/dist/__tests__/hardening.test.d.ts +2 -0
  8. package/dist/__tests__/hardening.test.d.ts.map +1 -0
  9. package/dist/__tests__/integrations/host-mfe.test.d.ts +2 -0
  10. package/dist/__tests__/integrations/host-mfe.test.d.ts.map +1 -0
  11. package/dist/__tests__/integrations/use-journey-step.test.d.ts +2 -0
  12. package/dist/__tests__/integrations/use-journey-step.test.d.ts.map +1 -0
  13. package/dist/config.d.ts +57 -13
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +87 -24
  16. package/dist/config.js.map +1 -1
  17. package/dist/core/abort-error.d.ts +4 -0
  18. package/dist/core/abort-error.d.ts.map +1 -0
  19. package/dist/core/abort-error.js +22 -0
  20. package/dist/core/abort-error.js.map +1 -0
  21. package/dist/core/api.d.ts +22 -9
  22. package/dist/core/api.d.ts.map +1 -1
  23. package/dist/core/api.js +77 -30
  24. package/dist/core/api.js.map +1 -1
  25. package/dist/core/endpoint-policy.d.ts +14 -4
  26. package/dist/core/endpoint-policy.d.ts.map +1 -1
  27. package/dist/core/endpoint-policy.js +32 -1
  28. package/dist/core/endpoint-policy.js.map +1 -1
  29. package/dist/core/index.d.ts +4 -3
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/core/index.js +3 -3
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/limits.d.ts +29 -0
  34. package/dist/core/limits.d.ts.map +1 -0
  35. package/dist/core/limits.js +63 -0
  36. package/dist/core/limits.js.map +1 -0
  37. package/dist/core/request-policy.d.ts +39 -0
  38. package/dist/core/request-policy.d.ts.map +1 -0
  39. package/dist/core/request-policy.js +18 -0
  40. package/dist/core/request-policy.js.map +1 -0
  41. package/dist/core/runtime.d.ts +65 -15
  42. package/dist/core/runtime.d.ts.map +1 -1
  43. package/dist/core/runtime.js +403 -126
  44. package/dist/core/runtime.js.map +1 -1
  45. package/dist/core/sanitize.d.ts +23 -0
  46. package/dist/core/sanitize.d.ts.map +1 -0
  47. package/dist/core/sanitize.js +143 -0
  48. package/dist/core/sanitize.js.map +1 -0
  49. package/dist/core/step-tag.d.ts +25 -6
  50. package/dist/core/step-tag.d.ts.map +1 -1
  51. package/dist/core/step-tag.js +56 -10
  52. package/dist/core/step-tag.js.map +1 -1
  53. package/dist/core/types.d.ts +73 -25
  54. package/dist/core/types.d.ts.map +1 -1
  55. package/dist/core/types.js +4 -1
  56. package/dist/core/types.js.map +1 -1
  57. package/dist/global.d.ts +91 -13
  58. package/dist/global.d.ts.map +1 -1
  59. package/dist/global.js +235 -25
  60. package/dist/global.js.map +1 -1
  61. package/dist/index.d.ts +7 -8
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +4 -6
  64. package/dist/index.js.map +1 -1
  65. package/dist/integrations/axios.d.ts +5 -1
  66. package/dist/integrations/axios.d.ts.map +1 -1
  67. package/dist/integrations/axios.js +146 -34
  68. package/dist/integrations/axios.js.map +1 -1
  69. package/dist/integrations/fetch.d.ts +3 -2
  70. package/dist/integrations/fetch.d.ts.map +1 -1
  71. package/dist/integrations/fetch.js +105 -26
  72. package/dist/integrations/fetch.js.map +1 -1
  73. package/dist/integrations/http-report.d.ts +27 -0
  74. package/dist/integrations/http-report.d.ts.map +1 -0
  75. package/dist/integrations/http-report.js +123 -0
  76. package/dist/integrations/http-report.js.map +1 -0
  77. package/dist/integrations/jquery/index.d.ts +8 -4
  78. package/dist/integrations/jquery/index.d.ts.map +1 -1
  79. package/dist/integrations/jquery/index.js +26 -8
  80. package/dist/integrations/jquery/index.js.map +1 -1
  81. package/dist/integrations/jquery/instrument.d.ts +14 -5
  82. package/dist/integrations/jquery/instrument.d.ts.map +1 -1
  83. package/dist/integrations/jquery/instrument.js +111 -29
  84. package/dist/integrations/jquery/instrument.js.map +1 -1
  85. package/dist/integrations/react/context.d.ts +4 -1
  86. package/dist/integrations/react/context.d.ts.map +1 -1
  87. package/dist/integrations/react/context.js +4 -1
  88. package/dist/integrations/react/context.js.map +1 -1
  89. package/dist/integrations/react/scope.d.ts +14 -3
  90. package/dist/integrations/react/scope.d.ts.map +1 -1
  91. package/dist/integrations/react/scope.js +46 -8
  92. package/dist/integrations/react/scope.js.map +1 -1
  93. package/dist/integrations/react/stable-config-keys.d.ts +6 -0
  94. package/dist/integrations/react/stable-config-keys.d.ts.map +1 -0
  95. package/dist/integrations/react/stable-config-keys.js +21 -0
  96. package/dist/integrations/react/stable-config-keys.js.map +1 -0
  97. package/dist/integrations/react/use-journey-step.d.ts +11 -0
  98. package/dist/integrations/react/use-journey-step.d.ts.map +1 -0
  99. package/dist/integrations/react/use-journey-step.js +19 -0
  100. package/dist/integrations/react/use-journey-step.js.map +1 -0
  101. package/dist/integrations/request-key.d.ts +8 -3
  102. package/dist/integrations/request-key.d.ts.map +1 -1
  103. package/dist/integrations/request-key.js +10 -9
  104. package/dist/integrations/request-key.js.map +1 -1
  105. package/dist/integrations/soft-skip.d.ts +7 -0
  106. package/dist/integrations/soft-skip.d.ts.map +1 -0
  107. package/dist/integrations/soft-skip.js +30 -0
  108. package/dist/integrations/soft-skip.js.map +1 -0
  109. package/dist/sinks/console.d.ts.map +1 -1
  110. package/dist/sinks/console.js +2 -1
  111. package/dist/sinks/console.js.map +1 -1
  112. package/dist/sinks/datadog.d.ts +1 -1
  113. package/dist/sinks/datadog.d.ts.map +1 -1
  114. package/dist/sinks/datadog.js +16 -20
  115. package/dist/sinks/datadog.js.map +1 -1
  116. package/package.json +2 -2
  117. package/src/__test-utils__/test-runtime.ts +9 -2
  118. package/src/__tests__/config.test.ts +150 -31
  119. package/src/__tests__/core/ambient.test.ts +16 -10
  120. package/src/__tests__/core/api.test.ts +7 -7
  121. package/src/__tests__/core/define-journey.test.ts +28 -11
  122. package/src/__tests__/core/requests.test.ts +49 -31
  123. package/src/__tests__/core/runtime.test.ts +67 -20
  124. package/src/__tests__/core/sanitize.test.ts +69 -0
  125. package/src/__tests__/core/step.test.ts +132 -36
  126. package/src/__tests__/core/timeouts.test.ts +69 -4
  127. package/src/__tests__/global.test.ts +44 -3
  128. package/src/__tests__/hardening.test.ts +613 -0
  129. package/src/__tests__/integrations/axios.test.ts +247 -27
  130. package/src/__tests__/integrations/expose-app-journey.test.ts +9 -3
  131. package/src/__tests__/integrations/fetch.test.ts +153 -19
  132. package/src/__tests__/integrations/host-mfe.test.ts +548 -0
  133. package/src/__tests__/integrations/jquery.test.ts +209 -34
  134. package/src/__tests__/integrations/scope.test.tsx +160 -2
  135. package/src/__tests__/integrations/use-journey-step.test.tsx +43 -0
  136. package/src/__tests__/sinks/console.test.ts +9 -4
  137. package/src/__tests__/sinks/datadog.test.ts +47 -0
  138. package/src/config.ts +162 -38
  139. package/src/core/abort-error.ts +24 -0
  140. package/src/core/api.ts +83 -33
  141. package/src/core/endpoint-policy.ts +46 -4
  142. package/src/core/index.ts +12 -4
  143. package/src/core/limits.ts +73 -0
  144. package/src/core/request-policy.ts +66 -0
  145. package/src/core/runtime.ts +507 -133
  146. package/src/core/sanitize.ts +177 -0
  147. package/src/core/step-tag.ts +82 -12
  148. package/src/core/types.ts +79 -25
  149. package/src/global.ts +312 -42
  150. package/src/index.ts +17 -17
  151. package/src/integrations/axios.ts +183 -45
  152. package/src/integrations/fetch.ts +125 -39
  153. package/src/integrations/http-report.ts +184 -0
  154. package/src/integrations/jquery/index.ts +34 -10
  155. package/src/integrations/jquery/instrument.ts +126 -42
  156. package/src/integrations/react/context.ts +4 -1
  157. package/src/integrations/react/scope.tsx +59 -9
  158. package/src/integrations/react/stable-config-keys.ts +28 -0
  159. package/src/integrations/react/use-journey-step.ts +31 -0
  160. package/src/integrations/request-key.ts +18 -10
  161. package/src/integrations/soft-skip.ts +35 -0
  162. package/src/sinks/console.ts +2 -1
  163. package/src/sinks/datadog.ts +23 -29
package/src/global.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { SlowRequestConfig } from './core/endpoint-policy';
2
+ import { type RequestPolicyConfig } from './core/request-policy';
1
3
  import type { JourneyRuntime, JourneyRuntimeDebugSnapshot } from './core/runtime';
2
4
 
3
5
  /** Force-enable journey host wiring outside go* hosts (`__stJourney.enable()`). */
@@ -9,21 +11,45 @@ export const JOURNEY_DEBUG_STORAGE_KEY = 'st:journey:debug';
9
11
  /** @internal Shared cross-bundle bag on globalThis (`rum` fallback + DevTools helpers). */
10
12
  export const ST_JOURNEY_GLOBAL_KEY = '__stJourney';
11
13
 
12
- export interface JourneyDebugRuntimeEntry extends JourneyRuntimeDebugSnapshot {
13
- id: string;
14
- }
14
+ /** Bump when shared bag shape / policy semantics change incompatibly. */
15
+ export const ST_JOURNEY_SCHEMA_VERSION = 2;
15
16
 
16
17
  export interface JourneyDebugState {
17
- /** Force-enable flag (`st:journey:enabled`). */
18
+ /** Host default or force-enable (`isJourneyEnabled()`). */
18
19
  enabled: boolean;
19
20
  /** Console-logging flag (`st:journey:debug`). */
20
21
  debug: boolean;
21
22
  hasRum: boolean;
22
- runtimes: JourneyDebugRuntimeEntry[];
23
+ runtimes: JourneyRuntimeDebugSnapshot[];
24
+ }
25
+
26
+ /** Shared host policies visible to every package copy on the page. */
27
+ export interface JourneySharedPolicies {
28
+ slowRequests?: SlowRequestConfig;
29
+ /** Whether shared slowRequests were explicitly set via configureJourney. */
30
+ slowRequestsConfigured: boolean;
31
+ httpAbortedRequests: RequestPolicyConfig;
32
+ httpClientErrorRequests: RequestPolicyConfig;
33
+ autoAttributeRequests: boolean;
34
+ /** True after the first configureJourney that set any shared policy knob. */
35
+ locked: boolean;
36
+ }
37
+
38
+ /** DevTools registration — `{ id, getSnapshot }` so older package copies can still list runtimes. */
39
+ export interface RuntimeRegistration {
40
+ id: string;
41
+ getSnapshot: () => JourneyRuntimeDebugSnapshot;
42
+ teardown?: () => void;
43
+ }
44
+
45
+ /** @internal Restore/dispose record for HTTP instrumentation. */
46
+ export interface InstrumentRecord {
47
+ disposed: boolean;
48
+ restore?: () => void;
23
49
  }
24
50
 
25
51
  /**
26
- * @internal Cross-bundle surface on `globalThis.__stJourney`.
52
+ * Cross-bundle surface on `globalThis.__stJourney`.
27
53
  * Prefer importing `isJourneyEnabled` / `isJourneyDebugEnabled` from the package in app code.
28
54
  */
29
55
  export interface StJourneyGlobal {
@@ -32,6 +58,24 @@ export interface StJourneyGlobal {
32
58
  * Prefer `DD_RUM` (ST host: `datadogGuard`).
33
59
  */
34
60
  rum?: unknown;
61
+ /** Shared bag schema; mismatched copies warn but continue. */
62
+ schemaVersion?: number;
63
+ /** Host default enable (e.g. RUM active). Set via `setJourneyDefaultEnabled`. */
64
+ defaultEnabled?: boolean;
65
+ /** Shared configureJourney policies. */
66
+ policies: JourneySharedPolicies;
67
+ /**
68
+ * Bundle runtimes registered for DevTools `getDebugState()`.
69
+ * `{ id, getSnapshot }` so mixed package copies can list each other.
70
+ */
71
+ runtimeRegistrations: RuntimeRegistration[];
72
+ /** @internal Cross-bundle instrumented axios/fetch/jquery targets. */
73
+ instrumentedAxios: WeakSet<object>;
74
+ instrumentedFetch: WeakSet<object>;
75
+ instrumentedJquery: WeakSet<object>;
76
+ /** @internal Active instrument records for teardown. */
77
+ instrumentRecords: InstrumentRecord[];
78
+ fetchRestore?: () => void;
35
79
  enable(): void;
36
80
  disable(): void;
37
81
  isEnabled(): boolean;
@@ -45,21 +89,17 @@ export interface StJourneyGlobal {
45
89
  getDebugState(): JourneyDebugState;
46
90
  }
47
91
 
48
- interface RuntimeRegistration {
49
- id: string;
50
- getSnapshot: () => JourneyRuntimeDebugSnapshot;
51
- }
92
+ let warnedMissingSlowRequests = false;
93
+ let debugEnabledCache: boolean | undefined;
94
+ const MAX_INSTRUMENT_RECORDS = 32;
52
95
 
53
- interface StJourneyGlobalInternal extends StJourneyGlobal {
54
- /** @internal */
55
- runtimeRegistrations: RuntimeRegistration[];
96
+ function storageFor(key: string): Storage | undefined {
97
+ return key === JOURNEY_DEBUG_STORAGE_KEY ? globalThis.sessionStorage : globalThis.localStorage;
56
98
  }
57
99
 
58
- let runtimeSeq = 0;
59
-
60
100
  function readFlag(key: string): boolean {
61
101
  try {
62
- return globalThis.localStorage?.getItem(key) === 'true';
102
+ return storageFor(key)?.getItem(key) === 'true';
63
103
  } catch {
64
104
  return false;
65
105
  }
@@ -67,19 +107,31 @@ function readFlag(key: string): boolean {
67
107
 
68
108
  function writeFlag(key: string, on: boolean): void {
69
109
  try {
110
+ const storage = storageFor(key);
70
111
  if (on) {
71
- globalThis.localStorage?.setItem(key, 'true');
112
+ storage?.setItem(key, 'true');
72
113
  } else {
73
- globalThis.localStorage?.removeItem(key);
114
+ storage?.removeItem(key);
74
115
  }
75
116
  } catch {
76
117
  // ignore storage failures (privacy mode, non-browser)
77
118
  }
78
119
  }
79
120
 
80
- /** True when `localStorage['st:journey:enabled'] === 'true'` (force-on outside go*). */
121
+ /**
122
+ * Host default for journey wiring (e.g. RUM initialized). Stored on `__stJourney` so
123
+ * MFEs see the same value without importing Datadog. Call once after RUM init.
124
+ */
125
+ export function setJourneyDefaultEnabled(enabled: boolean): void {
126
+ ensureGlobal().defaultEnabled = enabled === true;
127
+ }
128
+
129
+ /**
130
+ * True when the host default is on or force-enable is set
131
+ * (`st:journey:enabled` / `__stJourney.enable()`).
132
+ */
81
133
  export function isJourneyEnabled(): boolean {
82
- return readFlag(JOURNEY_ENABLED_STORAGE_KEY);
134
+ return ensureGlobal().defaultEnabled === true || readFlag(JOURNEY_ENABLED_STORAGE_KEY);
83
135
  }
84
136
 
85
137
  /** Set the force-enable flag. Reload after calling from DevTools. */
@@ -92,19 +144,25 @@ export function disableJourney(): void {
92
144
  writeFlag(JOURNEY_ENABLED_STORAGE_KEY, false);
93
145
  }
94
146
 
95
- /** True when `localStorage['st:journey:debug'] === 'true'`. */
147
+ /** True when `sessionStorage['st:journey:debug'] === 'true'`. */
96
148
  export function isJourneyDebugEnabled(): boolean {
97
- return readFlag(JOURNEY_DEBUG_STORAGE_KEY);
149
+ const next = readFlag(JOURNEY_DEBUG_STORAGE_KEY);
150
+ if (debugEnabledCache !== next) {
151
+ debugEnabledCache = next;
152
+ }
153
+ return debugEnabledCache;
98
154
  }
99
155
 
100
156
  /** Set the console-debug flag. Reload after calling from DevTools. */
101
157
  export function enableJourneyDebugging(): void {
102
158
  writeFlag(JOURNEY_DEBUG_STORAGE_KEY, true);
159
+ debugEnabledCache = true;
103
160
  }
104
161
 
105
162
  /** Clear the console-debug flag. Reload after calling from DevTools. */
106
163
  export function disableJourneyDebugging(): void {
107
164
  writeFlag(JOURNEY_DEBUG_STORAGE_KEY, false);
165
+ debugEnabledCache = false;
108
166
  }
109
167
 
110
168
  function hasRum(): boolean {
@@ -113,7 +171,39 @@ function hasRum(): boolean {
113
171
  );
114
172
  }
115
173
 
116
- function buildDebugState(runtimes: RuntimeRegistration[]): JourneyDebugState {
174
+ function snapshotOf(entry: unknown): JourneyRuntimeDebugSnapshot | undefined {
175
+ if (entry == null || typeof entry !== 'object') {
176
+ return undefined;
177
+ }
178
+ const rec = entry as {
179
+ id?: string;
180
+ getSnapshot?: () => JourneyRuntimeDebugSnapshot;
181
+ getDebugSnapshot?: () => JourneyRuntimeDebugSnapshot;
182
+ };
183
+ try {
184
+ if (typeof rec.getSnapshot === 'function') {
185
+ const snap = rec.getSnapshot();
186
+ const withId = snap.id ? snap : { ...snap, id: rec.id ?? '' };
187
+ return {
188
+ ...withId,
189
+ policyOverlay: withId.policyOverlay ?? {
190
+ slowRequests: false,
191
+ httpAbortedRequests: false,
192
+ httpClientErrorRequests: false,
193
+ autoAttributeRequests: false,
194
+ },
195
+ };
196
+ }
197
+ if (typeof rec.getDebugSnapshot === 'function') {
198
+ return rec.getDebugSnapshot();
199
+ }
200
+ } catch {
201
+ return undefined;
202
+ }
203
+ return undefined;
204
+ }
205
+
206
+ function buildDebugState(runtimes: readonly unknown[]): JourneyDebugState {
117
207
  if (!isJourneyDebugEnabled()) {
118
208
  return {
119
209
  enabled: isJourneyEnabled(),
@@ -127,26 +217,61 @@ function buildDebugState(runtimes: RuntimeRegistration[]): JourneyDebugState {
127
217
  enabled: isJourneyEnabled(),
128
218
  debug: true,
129
219
  hasRum: hasRum(),
130
- runtimes: runtimes.map(entry => ({
131
- id: entry.id,
132
- ...entry.getSnapshot(),
133
- })),
220
+ runtimes: runtimes
221
+ .map(snapshotOf)
222
+ .filter((snap): snap is JourneyRuntimeDebugSnapshot => snap != null),
223
+ };
224
+ }
225
+
226
+ function createEmptyPolicies(): JourneySharedPolicies {
227
+ return {
228
+ slowRequestsConfigured: false,
229
+ httpAbortedRequests: 'continue',
230
+ httpClientErrorRequests: 'continue',
231
+ autoAttributeRequests: true,
232
+ locked: false,
134
233
  };
135
234
  }
136
235
 
137
- function ensureGlobal(): StJourneyGlobalInternal {
236
+ function ensureGlobal(): StJourneyGlobal {
138
237
  const g = globalThis as Record<string, unknown>;
139
- const existing = g[ST_JOURNEY_GLOBAL_KEY] as StJourneyGlobalInternal | undefined;
238
+ const existing = g[ST_JOURNEY_GLOBAL_KEY] as StJourneyGlobal | undefined;
140
239
  // Reuse when a prior load already installed the full surface (incl. enable).
141
240
  if (existing?.runtimeRegistrations && typeof existing.enable === 'function') {
241
+ if (
242
+ existing.schemaVersion != null &&
243
+ existing.schemaVersion !== ST_JOURNEY_SCHEMA_VERSION
244
+ ) {
245
+ // eslint-disable-next-line no-console -- cross-bundle version skew
246
+ console.warn(
247
+ `[journey] __stJourney.schemaVersion mismatch: bag=${existing.schemaVersion}, package=${ST_JOURNEY_SCHEMA_VERSION}`
248
+ );
249
+ } else {
250
+ existing.schemaVersion ??= ST_JOURNEY_SCHEMA_VERSION;
251
+ }
252
+ existing.policies ??= createEmptyPolicies();
253
+ if (existing.policies.locked !== true && existing.policies.locked !== false) {
254
+ existing.policies.locked = false;
255
+ }
256
+ existing.instrumentedAxios ??= new WeakSet();
257
+ existing.instrumentedFetch ??= new WeakSet();
258
+ existing.instrumentedJquery ??= new WeakSet();
259
+ existing.instrumentRecords ??= [];
142
260
  return existing;
143
261
  }
144
262
 
145
263
  const runtimes: RuntimeRegistration[] = existing?.runtimeRegistrations ?? [];
146
264
 
147
- const api: StJourneyGlobalInternal = {
265
+ const api: StJourneyGlobal = {
148
266
  rum: existing?.rum,
267
+ schemaVersion: ST_JOURNEY_SCHEMA_VERSION,
268
+ defaultEnabled: existing?.defaultEnabled === true,
149
269
  runtimeRegistrations: runtimes,
270
+ policies: createEmptyPolicies(),
271
+ instrumentedAxios: new WeakSet(),
272
+ instrumentedFetch: new WeakSet(),
273
+ instrumentedJquery: new WeakSet(),
274
+ instrumentRecords: [],
150
275
  enable: enableJourney,
151
276
  disable: disableJourney,
152
277
  isEnabled: isJourneyEnabled,
@@ -162,22 +287,129 @@ function ensureGlobal(): StJourneyGlobalInternal {
162
287
  return api;
163
288
  }
164
289
 
290
+ /** @internal Shared policies bag. */
291
+ export function getSharedPolicies(): JourneySharedPolicies {
292
+ return ensureGlobal().policies;
293
+ }
294
+
295
+ export function areSharedPoliciesLocked(): boolean {
296
+ return ensureGlobal().policies.locked === true;
297
+ }
298
+
299
+ function freezePolicies(policies: JourneySharedPolicies): JourneySharedPolicies {
300
+ const next: JourneySharedPolicies = { ...policies, locked: true };
301
+ if (next.slowRequests) {
302
+ const endpoints = next.slowRequests.endpoints
303
+ ? Object.freeze([...next.slowRequests.endpoints])
304
+ : next.slowRequests.endpoints;
305
+ next.slowRequests = Object.freeze({ ...next.slowRequests, endpoints });
306
+ }
307
+ return Object.freeze(next);
308
+ }
309
+
165
310
  /**
166
- * @internal Register this bundle's module runtime on `__stJourney` so DevTools can
167
- * list every host/MFE engine copy on the page.
311
+ * First writer: merge `patch` into the shared bag and freeze it.
312
+ * Returns false when already locked (caller should overlay locally instead).
168
313
  */
169
- export function registerJourneyRuntime(runtime: JourneyRuntime): void {
170
- const globalApi = ensureGlobal();
171
- const id = `runtime-${++runtimeSeq}`;
172
- globalApi.runtimeRegistrations.push({
173
- id,
174
- getSnapshot: () => runtime.getDebugSnapshot(),
175
- });
314
+ export function seedSharedPolicies(patch: Partial<Omit<JourneySharedPolicies, 'locked'>>): boolean {
315
+ const api = ensureGlobal();
316
+ if (api.policies.locked) {
317
+ return false;
318
+ }
319
+ api.policies = freezePolicies({ ...api.policies, ...patch });
320
+ return true;
321
+ }
322
+
323
+ export function getAutoAttributeRequests(): boolean {
324
+ return ensureGlobal().policies.autoAttributeRequests !== false;
325
+ }
326
+
327
+ export function getHttpAbortedRequestsPolicy(): RequestPolicyConfig {
328
+ return ensureGlobal().policies.httpAbortedRequests;
329
+ }
330
+
331
+ export function getHttpClientErrorRequestsPolicy(): RequestPolicyConfig {
332
+ return ensureGlobal().policies.httpClientErrorRequests;
333
+ }
334
+
335
+ export function getSharedSlowRequests(): SlowRequestConfig | undefined {
336
+ return ensureGlobal().policies.slowRequests;
337
+ }
338
+
339
+ export function areSharedSlowRequestsConfigured(): boolean {
340
+ return ensureGlobal().policies.slowRequestsConfigured === true;
341
+ }
342
+
343
+ /** One-time warn when transports are wired without explicit slowRequests. */
344
+ export function warnIfSlowRequestsUnset(): void {
345
+ if (warnedMissingSlowRequests || areSharedSlowRequestsConfigured()) {
346
+ return;
347
+ }
348
+ warnedMissingSlowRequests = true;
349
+ // eslint-disable-next-line no-console -- host config guidance
350
+ console.warn(
351
+ '[journey] using default slowRequests.defaultMs=4000 — set configureJourney({ slowRequests }) to tune'
352
+ );
353
+ }
354
+
355
+ /** @internal Reset shared policies (tests / teardown). */
356
+ export function resetSharedPolicies(): void {
357
+ const api = ensureGlobal();
358
+ api.policies = createEmptyPolicies();
359
+ warnedMissingSlowRequests = false;
360
+ debugEnabledCache = undefined;
361
+ }
362
+
363
+ export function getInstrumentedAxiosSet(): WeakSet<object> {
364
+ return ensureGlobal().instrumentedAxios;
365
+ }
366
+
367
+ export function getInstrumentedFetchSet(): WeakSet<object> {
368
+ return ensureGlobal().instrumentedFetch;
369
+ }
370
+
371
+ export function getInstrumentedJquerySet(): WeakSet<object> {
372
+ return ensureGlobal().instrumentedJquery;
373
+ }
374
+
375
+ export function registerInstrumentRecord(record: InstrumentRecord): void {
376
+ const api = ensureGlobal();
377
+ if (api.instrumentRecords.length >= MAX_INSTRUMENT_RECORDS) {
378
+ // eslint-disable-next-line no-console -- defensive cap for repeated instrumentation
379
+ console.warn('[journey] instrumentRecords cap reached — call teardownJourney()');
380
+ return;
381
+ }
382
+ api.instrumentRecords.push(record);
383
+ }
384
+
385
+ export function setFetchRestore(restore: (() => void) | undefined): void {
386
+ ensureGlobal().fetchRestore = restore;
176
387
  }
177
388
 
178
- /** @internal Ensure `__stJourney` exists (idempotent). */
179
- export function installJourneyGlobal(): void {
180
- ensureGlobal();
389
+ /**
390
+ * @internal Register this bundle's engine on `__stJourney` so DevTools can list
391
+ * every host/MFE copy on the page. Auto-assigns `runtime-N` when `runtime.id` is empty.
392
+ * Replaces an existing registration with the same id (HMR / test re-register).
393
+ */
394
+ export function registerJourneyRuntime(
395
+ runtime: JourneyRuntime,
396
+ options?: { teardown?: () => void }
397
+ ): void {
398
+ const bag = ensureGlobal();
399
+ if (runtime.id === '') {
400
+ (runtime as { id: string }).id = `runtime-${bag.runtimeRegistrations.length + 1}`;
401
+ }
402
+ const entry: RuntimeRegistration = {
403
+ id: runtime.id,
404
+ getSnapshot: () => runtime.getDebugSnapshot(),
405
+ teardown: options?.teardown,
406
+ };
407
+ const existing = bag.runtimeRegistrations.findIndex(reg => reg.id === runtime.id);
408
+ if (existing >= 0) {
409
+ bag.runtimeRegistrations[existing] = entry;
410
+ } else {
411
+ bag.runtimeRegistrations.push(entry);
412
+ }
181
413
  }
182
414
 
183
415
  /**
@@ -194,3 +426,41 @@ export function getJourneyRumFallback(): unknown {
194
426
  StJourneyGlobal | undefined;
195
427
  return existing?.rum;
196
428
  }
429
+
430
+ /**
431
+ * Dispose HTTP instrumentation hooks and reset registered engines that supplied a
432
+ * teardown callback. Intended for tests (e.g. afterEach). Prefer `resetJourney`
433
+ * when you only need this bundle's engine state.
434
+ */
435
+ export function teardownJourney(): void {
436
+ const api = ensureGlobal();
437
+ for (const record of api.instrumentRecords) {
438
+ record.disposed = true;
439
+ try {
440
+ record.restore?.();
441
+ } catch {
442
+ // ignore restore failures
443
+ }
444
+ }
445
+ api.instrumentRecords = [];
446
+ try {
447
+ api.fetchRestore?.();
448
+ } catch {
449
+ // ignore
450
+ }
451
+ api.fetchRestore = undefined;
452
+
453
+ const kept: RuntimeRegistration[] = [];
454
+ for (const entry of api.runtimeRegistrations) {
455
+ if (entry.teardown) {
456
+ try {
457
+ entry.teardown();
458
+ } catch {
459
+ // ignore
460
+ }
461
+ continue;
462
+ }
463
+ kept.push(entry);
464
+ }
465
+ api.runtimeRegistrations = kept;
466
+ }
package/src/index.ts CHANGED
@@ -15,38 +15,35 @@ export {
15
15
  journeyStep,
16
16
  setJourneyTags,
17
17
  // Custom transport adapters / request attribution.
18
- activeJourneyStep,
19
- reportBackendRequest,
20
- resolveJourneyStep,
21
- JourneyStepTag,
18
+ singleInFlightJourneyStep,
19
+ resolveStamp,
20
+ JourneyStepStamp,
22
21
  type JourneyDef,
23
22
  type JourneyHandle,
24
23
  type JourneyNameLike,
25
24
  type JourneyStepOptions,
26
- type JourneyStepRef,
27
25
  type JourneyStepTarget,
28
26
  type StepHandle,
29
27
  } from './core';
30
28
 
31
- export {
32
- configureJourney,
33
- type AppRequestTimeouts,
34
- type EndpointPolicy,
35
- type JourneyConfig,
36
- } from './config';
29
+ export { configureJourney, type JourneyConfig } from './config';
30
+ export type { EndpointPolicy, SlowRequestConfig } from './core/endpoint-policy';
31
+ export type {
32
+ JourneyHttpPolicyContext,
33
+ JourneyHttpScore,
34
+ JourneyHttpScoreContext,
35
+ JourneyRequestPolicy,
36
+ JourneyStampOptions,
37
+ RequestPolicyConfig,
38
+ } from './core/request-policy';
37
39
 
38
40
  export type { JourneySink } from './core';
39
41
 
40
- /**
41
- * @internal Accidentally exported — removing it is a breaking change, so it stays
42
- * for compatibility. Not part of the supported public API. Test helper only.
43
- */
44
- export { resetJourney } from './core';
45
-
46
42
  export { instrumentAxios } from './integrations/axios';
47
43
  export { instrumentFetch } from './integrations/fetch';
48
44
  export { exposeAppJourney, instrumentJquery } from './integrations/jquery';
49
45
  export { requestKey, type JourneyTimedMeta } from './integrations/request-key';
46
+ export { onHttpComplete, type HttpCompleteReport } from './integrations/http-report';
50
47
  export {
51
48
  disableJourney,
52
49
  disableJourneyDebugging,
@@ -54,6 +51,8 @@ export {
54
51
  enableJourneyDebugging,
55
52
  isJourneyDebugEnabled,
56
53
  isJourneyEnabled,
54
+ setJourneyDefaultEnabled,
55
+ teardownJourney,
57
56
  } from './global';
58
57
  export { sendToConsole } from './sinks/console';
59
58
  export { normalizeTagValue, sendToDatadog, setJourneyRum } from './sinks/datadog';
@@ -62,3 +61,4 @@ export type { JourneyEvent, JourneyStepEvent, TagValue } from './core';
62
61
 
63
62
  export { useJourneyName, useOptionalJourneyName } from './integrations/react/context';
64
63
  export { JourneyScope } from './integrations/react/scope';
64
+ export { useJourneyStep } from './integrations/react/use-journey-step';