@groundcover/browser 1.0.7 → 1.0.8-rc.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/CHANGELOG.md CHANGED
@@ -4,6 +4,50 @@ 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.0]
8
+
9
+ Release candidate. Published under the `rc` dist-tag — `npm install @groundcover/browser@rc`.
10
+
11
+ The public API is unchanged: the generated type declarations are byte-identical to 1.0.7, the
12
+ published bundle contains the same 10 files, and the `/json/rum` wire format is unchanged (pinned by
13
+ a golden characterization suite). The two behaviour changes below are the reason this ships as an
14
+ RC rather than a patch.
15
+
16
+ ### Changed
17
+
18
+ - **`sessionSampleRate: 0` now samples nothing.** It was previously treated as "unset" by a falsy
19
+ check, so configuring `0` sampled *every* session — the exact opposite of the intent. If you set
20
+ `sessionSampleRate: 0` today and rely on the current (inverted) behaviour, sessions will stop
21
+ being collected when you upgrade.
22
+ - **The session sampling verdict is now re-rolled per session.** It is documented as "decided once
23
+ per session", but nothing ever removed the stored decision, so in practice it was decided once per
24
+ browser-storage lifetime and every session after the first rotation inherited the original
25
+ verdict. The decision is now cleared when a session rotates (inactivity gap, max-duration cap, or
26
+ a new session id supplied by the host). Re-supplying the *same* session id is still a continuation
27
+ and keeps its verdict.
28
+
29
+ ### Fixed
30
+
31
+ - **Session rotation could stop permanently if a host hook threw.** The in-progress flag was set
32
+ before the host's rotation-context hook ran but only cleared in a `finally` that the hook sat
33
+ outside, so a throw left it stuck — silently disabling all further session rotation for the life
34
+ of the page. Not reachable through the browser SDK's own hook, but the surrounding error handling
35
+ swallowed the throw without resetting the flag, so it would have failed invisibly.
36
+ - **Configuration option groups were shared between SDK instances.** `privacy`, `tracing`,
37
+ `transport` and `replay` groups the caller didn't supply were held by reference from the shared
38
+ defaults, so multiple SDK instances in one page aliased the same objects (and the module-level
39
+ defaults themselves).
40
+ - **A failed batch send could raise an unhandled promise rejection** instead of logging, when
41
+ `options` was absent on the resolved config.
42
+
43
+ ### Internal
44
+
45
+ - The platform-agnostic pipeline (config, session policy, event enrichment, batching, redaction,
46
+ transport) now lives in a private `@groundcover/rum-core` package, inlined into this bundle at
47
+ build time. It compiles with no DOM lib so platform coupling fails the build, and the network
48
+ send, storage, device metadata and view derivation are injected by this package. Groundwork for
49
+ the React Native SDK; no effect on the published browser artifact.
50
+
7
51
  ## [1.0.5]
8
52
 
9
53
  ### 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: {