@groundcover/browser 1.0.7 → 1.0.8-rc.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,72 @@ All notable changes to `@groundcover/browser` are documented here. This project
4
4
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the
5
5
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.
6
6
 
7
+ ## [1.0.8-rc.1]
8
+
9
+ Release candidate. Published under the `rc` dist-tag — `npm install @groundcover/browser@rc`.
10
+
11
+ Supersedes 1.0.8-rc.0; everything in that entry still applies, including the two behaviour changes
12
+ under **Changed** below. This RC adds one fix on top.
13
+
14
+ ### Fixed
15
+
16
+ - **The current view's title is no longer discarded when view derivation fails.** The error boundary
17
+ added around the browser's view provider in 1.0.8-rc.0 returned a hardcoded empty view, so a throw
18
+ anywhere after the document title had already been read (a hostile `location` getter, a
19
+ user-supplied redactor) silently dropped a title that was perfectly good — the no-location path has
20
+ always preserved it. The title is now read first under its own guard, and both failure paths report
21
+ through the SDK's error handler instead of swallowing: a view provider failing on every event is a
22
+ real signal, not something to hide.
23
+
24
+ 1.0.8-rc.0 was verified end-to-end against a live deployment (batches accepted, envelope matching the
25
+ golden wire format, `attributes.location` intact, zero console errors); this fix is additive to that.
26
+
27
+ ## [1.0.8-rc.0]
28
+
29
+ Superseded by 1.0.8-rc.1.
30
+
31
+ Release candidate. Published under the `rc` dist-tag — `npm install @groundcover/browser@rc`.
32
+
33
+ The public API is unchanged: the generated type declarations are byte-identical to 1.0.7, the
34
+ published bundle contains the same 10 files, and the `/json/rum` wire format is unchanged (pinned by
35
+ a golden characterization suite). The two behaviour changes below are the reason this ships as an
36
+ RC rather than a patch.
37
+
38
+ ### Changed
39
+
40
+ - **`sessionSampleRate: 0` now samples nothing.** It was previously treated as "unset" by a falsy
41
+ check, so configuring `0` sampled *every* session — the exact opposite of the intent. If you set
42
+ `sessionSampleRate: 0` today and rely on the current (inverted) behaviour, sessions will stop
43
+ being collected when you upgrade.
44
+ - **The session sampling verdict is now re-rolled per session.** It is documented as "decided once
45
+ per session", but nothing ever removed the stored decision, so in practice it was decided once per
46
+ browser-storage lifetime and every session after the first rotation inherited the original
47
+ verdict. The decision is now cleared when a session rotates (inactivity gap, max-duration cap, or
48
+ a new session id supplied by the host). Re-supplying the *same* session id is still a continuation
49
+ and keeps its verdict.
50
+
51
+ ### Fixed
52
+
53
+ - **Session rotation could stop permanently if a host hook threw.** The in-progress flag was set
54
+ before the host's rotation-context hook ran but only cleared in a `finally` that the hook sat
55
+ outside, so a throw left it stuck — silently disabling all further session rotation for the life
56
+ of the page. Not reachable through the browser SDK's own hook, but the surrounding error handling
57
+ swallowed the throw without resetting the flag, so it would have failed invisibly.
58
+ - **Configuration option groups were shared between SDK instances.** `privacy`, `tracing`,
59
+ `transport` and `replay` groups the caller didn't supply were held by reference from the shared
60
+ defaults, so multiple SDK instances in one page aliased the same objects (and the module-level
61
+ defaults themselves).
62
+ - **A failed batch send could raise an unhandled promise rejection** instead of logging, when
63
+ `options` was absent on the resolved config.
64
+
65
+ ### Internal
66
+
67
+ - The platform-agnostic pipeline (config, session policy, event enrichment, batching, redaction,
68
+ transport) now lives in a private `@groundcover/rum-core` package, inlined into this bundle at
69
+ build time. It compiles with no DOM lib so platform coupling fails the build, and the network
70
+ send, storage, device metadata and view derivation are injected by this package. Groundwork for
71
+ the React Native SDK; no effect on the published browser artifact.
72
+
7
73
  ## [1.0.5]
8
74
 
9
75
  ### Added
package/dist/index.d.mts CHANGED
@@ -1,5 +1,20 @@
1
- type BaseEvent = {
2
- type: "dom.event" | "exception" | "network" | "log" | "pageload" | "navigation" | "custom" | "performance" | "replay";
1
+ /**
2
+ * The event kinds the browser SDK ships today. This is the DEFAULT vocabulary,
3
+ * not the only permitted one — see `BaseEvent`.
4
+ */
5
+ type KnownEventType = "dom.event" | "exception" | "network" | "log" | "pageload" | "navigation" | "custom" | "performance" | "replay";
6
+ /**
7
+ * `TType` is what lets a platform package name its own event kinds without
8
+ * editing core: React Native can pass `KnownEventType | "tap" | "screen_view"`
9
+ * and every core class below flows it through. It defaults to
10
+ * `KnownEventType`, so nothing that only deals in the shipped browser kinds
11
+ * has to spell it out.
12
+ *
13
+ * Shared shapes are still shared — the point is that the set of names is open,
14
+ * not that each platform reinvents the envelope.
15
+ */
16
+ type BaseEvent<TType extends string = KnownEventType> = {
17
+ type: TType;
3
18
  timestamp?: number;
4
19
  end_timestamp?: number;
5
20
  start_timestamp?: number;
@@ -10,11 +25,6 @@ type BaseEvent = {
10
25
  traceId: string | undefined;
11
26
  attributes?: Record<string, unknown>;
12
27
  };
13
- type LocationAttributes = {
14
- path: string;
15
- url: string;
16
- title: string;
17
- };
18
28
  type UserIdentifiers = {
19
29
  id: string;
20
30
  email: string;
@@ -165,22 +175,20 @@ type ReplayEventAttributes = {
165
175
  replay_full_snapshot_timestamp?: number;
166
176
  };
167
177
 
168
- type Event<T extends BaseEvent["type"], A> = Omit<BaseEvent, "attributes"> & {
178
+ type Event$1<T extends string, A> = Omit<BaseEvent<T>, "attributes"> & {
169
179
  type: T;
170
- attributes: A & {
171
- location?: LocationAttributes;
172
- };
180
+ attributes: A;
173
181
  };
174
- type EventTypes = {
175
- "dom.event": Event<"dom.event", DomEventAttributes>;
176
- exception: Event<"exception", ErrorEventAttributes>;
177
- network: Event<"network", NetworkEventAttributes>;
178
- log: Event<"log", LogEventAttributes>;
179
- pageload: Event<"pageload", PageLoadEventAttributes>;
180
- navigation: Event<"navigation", NavigationEventAttributes>;
181
- custom: Event<"custom", UserDefinedEventAttributes>;
182
- performance: Event<"performance", PerformanceEventAttributes>;
183
- replay: Event<"replay", ReplayEventAttributes>;
182
+ type EventTypes$1 = {
183
+ "dom.event": Event$1<"dom.event", DomEventAttributes>;
184
+ exception: Event$1<"exception", ErrorEventAttributes>;
185
+ network: Event$1<"network", NetworkEventAttributes>;
186
+ log: Event$1<"log", LogEventAttributes>;
187
+ pageload: Event$1<"pageload", PageLoadEventAttributes>;
188
+ navigation: Event$1<"navigation", NavigationEventAttributes>;
189
+ custom: Event$1<"custom", UserDefinedEventAttributes>;
190
+ performance: Event$1<"performance", PerformanceEventAttributes>;
191
+ replay: Event$1<"replay", ReplayEventAttributes>;
184
192
  };
185
193
  type PrivacyLevel = "mask-sensitive" | "mask-all" | "allow";
186
194
  /** The non-replay event surface a redactor field belongs to. */
@@ -205,18 +213,15 @@ interface RedactField {
205
213
  * only cares about one field can't accidentally disable masking elsewhere.
206
214
  */
207
215
  type Redactor = (field: RedactField) => string | number | boolean | null | undefined;
208
- interface PrivacyOptions {
216
+ interface PrivacyOptions$1 {
209
217
  /**
210
- * Master switch for replay + non-replay masking. `'mask-sensitive'`
211
- * (default) masks only SENSITIVE inputs/keystrokes (password type,
212
- * `data-private`, `maskSelectors`, or a sensitive-key attribute match) and
213
- * redacts network/log/error payloads, while capturing non-secret inputs and
214
- * keeping static text; `'mask-all'` also masks all inputs and all text;
215
- * `'allow'` opts out.
218
+ * Master switch for data masking. `'mask-sensitive'` (default) masks only
219
+ * SENSITIVE data (sensitive-key matches, platform-flagged UI elements) and
220
+ * redacts network/log/error payloads while capturing the rest;
221
+ * `'mask-all'` also masks all captured input/text; `'allow'` opts out.
222
+ * Platform packages define what "sensitive UI element" means on their host.
216
223
  */
217
224
  level?: PrivacyLevel;
218
- /** CSS selectors whose text/inputs are always masked (replay + DOM), regardless of `level` except `'allow'`. */
219
- maskSelectors?: string[];
220
225
  /** Redact network request/response bodies. Default on unless `level` is `'allow'`. */
221
226
  maskNetworkBodies?: boolean;
222
227
  /** Redact network URL query params. Default on unless `level` is `'allow'`. */
@@ -233,13 +238,14 @@ interface PrivacyOptions {
233
238
  * {@link Redactor}. For arbitrary whole-event changes use `enrichEvent`.
234
239
  */
235
240
  redact?: Redactor;
236
- /** Session-recording (rrweb replay) masking — the only place replay can be masked. */
237
- replay?: {
238
- /** rrweb `maskTextFn` pass-through. */
239
- maskTextFn?: (text: string, el: HTMLElement | null) => string;
240
- /** rrweb `maskInputFn` pass-through. */
241
- maskInputFn?: (text: string, el: HTMLElement) => string;
242
- };
241
+ /**
242
+ * Session-recording masking — replay is a platform feature (rrweb on the
243
+ * browser), so its masking surface is typed by the platform package: the
244
+ * browser SDK narrows this to `{ maskTextFn?, maskInputFn? }` over
245
+ * HTMLElement (see the browser package's `config/types.ts`). Core never
246
+ * reads it — it is resolved platform-side at record() time.
247
+ */
248
+ replay?: unknown;
243
249
  }
244
250
  /** Distributed-tracing header propagation for outgoing network requests. */
245
251
  interface TracingOptions {
@@ -266,44 +272,35 @@ interface TransportOptions {
266
272
  /** Gzip-compress outgoing batches; offloaded to the Web Worker when available (default true). */
267
273
  compression?: boolean;
268
274
  }
269
- /**
270
- * Session-replay (rrweb) recording controls. This is NOT a privacy surface —
271
- * to mask sensitive content use `privacy`.
272
- */
273
- interface ReplayOptions {
275
+ interface SDKOptions$1 {
274
276
  /**
275
- * CSS selectors whose matching elements (and their subtrees) are excluded
276
- * from session recording. Matching elements appear in the replay as empty
277
- * placeholders of their original size. Useful for skipping noisy DOM injected
278
- * by browser extensions (e.g. Grammarly).
279
- *
280
- * NOTE: this is a noise-reduction control, NOT a privacy control. To mask
281
- * sensitive content use `privacy` instead.
277
+ * Which signal listeners to enable, by name. Empty array = all. The NAMES
278
+ * are platform-defined (each platform ships its own listener set) — the
279
+ * platform package narrows this to its literal union (see the browser
280
+ * package's `config/types.ts`).
282
281
  */
283
- blockedSelectors?: string[];
284
- }
285
- interface SDKOptions {
286
- enabledEvents: Array<"dom" | "network" | "exceptions" | "logs" | "pageload" | "navigation" | "performance" | "replay">;
282
+ enabledEvents: string[];
287
283
  eventSampleRate: number;
288
284
  sessionSampleRate: number;
289
285
  debug: boolean;
290
286
  excludedUrls?: Array<string | RegExp>;
291
- beforeSend?: (event: EventTypes[keyof EventTypes]) => boolean;
292
- enrichEvent?: (event: EventTypes[keyof EventTypes]) => EventTypes[keyof EventTypes];
287
+ beforeSend?: (event: EventTypes$1[keyof EventTypes$1]) => boolean;
288
+ enrichEvent?: (event: EventTypes$1[keyof EventTypes$1]) => EventTypes$1[keyof EventTypes$1];
293
289
  /**
294
290
  * Unified privacy / data-masking config driving replay + non-replay masking.
295
291
  * ON by default (`level: 'mask-sensitive'`); see {@link PrivacyOptions}.
296
292
  */
297
- privacy?: PrivacyOptions;
293
+ privacy?: PrivacyOptions$1;
298
294
  /** Distributed-tracing header propagation; see {@link TracingOptions}. */
299
295
  tracing?: TracingOptions;
300
296
  /** Batching & delivery of outgoing events; see {@link TransportOptions}. */
301
297
  transport?: TransportOptions;
302
298
  /**
303
- * Session-replay recording controls (noise reduction). For replay MASKING use
304
- * `privacy` instead. See {@link ReplayOptions}.
299
+ * Session-recording controls — a platform feature slot core never reads.
300
+ * The platform package types it (the browser SDK narrows this to its
301
+ * rrweb `ReplayOptions`; platforms without recording ignore it).
305
302
  */
306
- replay?: ReplayOptions;
303
+ replay?: unknown;
307
304
  /**
308
305
  * Target maximum wall-clock duration of a session, in milliseconds.
309
306
  *
@@ -343,7 +340,7 @@ interface SDKOptions {
343
340
  */
344
341
  sessionMaxDuration?: number;
345
342
  }
346
- interface SDKConfigInput {
343
+ interface SDKConfigInput$1 {
347
344
  apiKey: string;
348
345
  cluster: string;
349
346
  environment: string;
@@ -352,7 +349,7 @@ interface SDKConfigInput {
352
349
  namespace?: string;
353
350
  releaseId?: string;
354
351
  user?: Partial<UserIdentifiers>;
355
- options?: Partial<SDKOptions>;
352
+ options?: Partial<SDKOptions$1>;
356
353
  sessionId?: string;
357
354
  }
358
355
 
@@ -365,9 +362,9 @@ declare class SDKConfig {
365
362
  releaseId?: string;
366
363
  apiKey: string;
367
364
  user: Partial<UserIdentifiers> | null;
368
- options: Partial<SDKOptions>;
369
- providedOptions?: Partial<SDKOptions>;
370
- constructor(input?: SDKConfigInput);
365
+ options: Partial<SDKOptions$1>;
366
+ providedOptions?: Partial<SDKOptions$1>;
367
+ constructor(input?: SDKConfigInput$1);
371
368
  private static readonly REMOVED_OPTION_KEYS;
372
369
  private static stripRemovedOptions;
373
370
  private static warnRemovedOptions;
@@ -376,13 +373,106 @@ declare class SDKConfig {
376
373
  updateConfig(updates: Partial<SDKConfig>): void;
377
374
  }
378
375
 
376
+ /**
377
+ * The view an event was captured on. Browser-owned: core has no notion of a
378
+ * "current view" — it merges whatever the platform's `enrichAttributes`
379
+ * contributor returns. `makeWebViewProvider` derives these (hash-route
380
+ * detection, query-param redaction) and the SessionManager contributes them
381
+ * under the `location` key.
382
+ */
383
+ type LocationAttributes = {
384
+ path: string;
385
+ url: string;
386
+ title: string;
387
+ };
388
+ /**
389
+ * The browser's event shape. Identical to core's `Event` plus the `location`
390
+ * this platform stamps during enrichment — which is why `beforeSend` /
391
+ * `enrichEvent` below are re-declared over THIS map rather than core's.
392
+ */
393
+ type Event<T extends string, A> = Omit<BaseEvent<T>, "attributes"> & {
394
+ type: T;
395
+ attributes: A & {
396
+ location?: LocationAttributes;
397
+ };
398
+ };
399
+ type EventTypes = {
400
+ "dom.event": Event<"dom.event", DomEventAttributes>;
401
+ exception: Event<"exception", ErrorEventAttributes>;
402
+ network: Event<"network", NetworkEventAttributes>;
403
+ log: Event<"log", LogEventAttributes>;
404
+ pageload: Event<"pageload", PageLoadEventAttributes>;
405
+ navigation: Event<"navigation", NavigationEventAttributes>;
406
+ custom: Event<"custom", UserDefinedEventAttributes>;
407
+ performance: Event<"performance", PerformanceEventAttributes>;
408
+ replay: Event<"replay", ReplayEventAttributes>;
409
+ };
410
+ /** The browser SDK's listener names for `enabledEvents`. */
411
+ type BrowserEventName = "dom" | "network" | "exceptions" | "logs" | "pageload" | "navigation" | "performance" | "replay";
412
+ /**
413
+ * Session-replay (rrweb) recording controls. This is NOT a privacy surface —
414
+ * to mask sensitive content use `privacy`.
415
+ */
416
+ interface ReplayOptions {
417
+ /**
418
+ * CSS selectors whose matching elements (and their subtrees) are excluded
419
+ * from session recording. Matching elements appear in the replay as empty
420
+ * placeholders of their original size. Useful for skipping noisy DOM injected
421
+ * by browser extensions (e.g. Grammarly).
422
+ *
423
+ * NOTE: this is a noise-reduction control, NOT a privacy control. To mask
424
+ * sensitive content use `privacy` instead.
425
+ */
426
+ blockedSelectors?: string[];
427
+ }
428
+ /** Session-recording (rrweb replay) masking — the only place replay can be masked. */
429
+ interface ReplayMaskingOptions {
430
+ /** rrweb `maskTextFn` pass-through. */
431
+ maskTextFn?: (text: string, el: HTMLElement | null) => string;
432
+ /** rrweb `maskInputFn` pass-through. */
433
+ maskInputFn?: (text: string, el: HTMLElement) => string;
434
+ }
435
+ interface PrivacyOptions extends Omit<PrivacyOptions$1, "replay"> {
436
+ /** CSS selectors whose text/inputs are always masked (replay + DOM), regardless of `level` except `'allow'`. */
437
+ maskSelectors?: string[];
438
+ /** Session-recording (rrweb replay) masking — the only place replay can be masked. */
439
+ replay?: ReplayMaskingOptions;
440
+ }
441
+ interface SDKOptions extends Omit<SDKOptions$1, "privacy" | "replay" | "enabledEvents" | "beforeSend" | "enrichEvent"> {
442
+ /**
443
+ * Inspect each event before it is queued; return false to drop it. Typed
444
+ * over the BROWSER event map, so `attributes.location` is visible here.
445
+ */
446
+ beforeSend?: (event: EventTypes[keyof EventTypes]) => boolean;
447
+ /** Modify each event before it is queued. Typed over the browser event map. */
448
+ enrichEvent?: (event: EventTypes[keyof EventTypes]) => EventTypes[keyof EventTypes];
449
+ /** Which browser listeners to enable. Empty array = all. */
450
+ enabledEvents: BrowserEventName[];
451
+ /**
452
+ * Unified privacy / data-masking config driving replay + non-replay masking.
453
+ * ON by default (`level: 'mask-sensitive'`); see {@link PrivacyOptions}.
454
+ */
455
+ privacy?: PrivacyOptions;
456
+ /**
457
+ * Session-replay recording controls (noise reduction). For replay MASKING use
458
+ * `privacy` instead. See {@link ReplayOptions}.
459
+ */
460
+ replay?: ReplayOptions;
461
+ }
462
+ interface SDKConfigInput extends Omit<SDKConfigInput$1, "options"> {
463
+ options?: Partial<SDKOptions>;
464
+ }
465
+
379
466
  declare class SessionManager {
380
- private initialized;
381
467
  private logger;
382
- private instrumentationManager;
468
+ private readonly storage;
383
469
  private idGenerator;
470
+ private core;
471
+ private hideWired;
472
+ private hideHandler;
384
473
  constructor(config: SDKConfigInput);
385
- private getSamplingDecision;
474
+ private wireHideHandler;
475
+ private unwireHideHandler;
386
476
  identifyUser(userIdentifier: Partial<UserIdentifiers>): void;
387
477
  updateConfig(params: Partial<SDKConfig>): void;
388
478
  sendCustomEvent(event: {