Skip to content
27 changes: 27 additions & 0 deletions packages/core/src/breadcrumbs/breadcrumb-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Breadcrumb } from '@hawk.so/types';

/**
* Hint passed to beforeBreadcrumb callback.
*/
export interface BreadcrumbHint {
[key: string]: unknown;
}

/**
* Breadcrumb input type - breadcrumb data with optional timestamp.
*/
export type BreadcrumbInput = Omit<Breadcrumb, 'timestamp'> & { timestamp?: number };

/**
* Contract for breadcrumb storage. Also serves as public breadcrumbs API.
*/
export interface BreadcrumbStore {
add(breadcrumb: BreadcrumbInput, hint?: BreadcrumbHint): void;
get(): Breadcrumb[];
clear(): void;
}

/**
* @deprecated Use {@link BreadcrumbStore} instead.
*/
export type BreadcrumbsAPI = BreadcrumbStore;
12 changes: 0 additions & 12 deletions packages/core/src/errors.ts

This file was deleted.

4 changes: 3 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ export type { Transport } from './transports/transport';
export type { SanitizerTypeHandler } from './modules/sanitizer';
export { StackParser } from './modules/stack-parser';
export { buildElementSelector } from './utils/selector';
export { EventRejectedError } from './errors';
export { isErrorProcessed, markErrorAsProcessed } from './utils/event';
export type { BreadcrumbStore, BreadcrumbsAPI, BreadcrumbHint, BreadcrumbInput } from './breadcrumbs/breadcrumb-store';
export type { ErrorSnapshot, MessageProcessor, ProcessingPayload } from './messages/message-processor';
export { BreadcrumbsMessageProcessor } from './messages/breadcrumbs-message-processor';
24 changes: 24 additions & 0 deletions packages/core/src/messages/breadcrumbs-message-processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ErrorSnapshot, MessageProcessor, ProcessingPayload } from './message-processor';

/**
* Attaches breadcrumbs to payload.
*/
export class BreadcrumbsMessageProcessor implements MessageProcessor<'errors/javascript'> {
/**
* Sets `payload.breadcrumbs` from snapshot if non-empty; skips otherwise.
*
* @param payload - event message payload to enrich
* @param snapshot - snapshot carrying breadcrumbs captured at error time
* @returns modified payload with breadcrumbs set, or original payload unchanged

Check warning on line 12 in packages/core/src/messages/breadcrumbs-message-processor.ts

View workflow job for this annotation

GitHub Actions / lint

Missing JSDoc @returns type
*/
public apply(
payload: ProcessingPayload<'errors/javascript'>,
snapshot?: ErrorSnapshot
): ProcessingPayload<'errors/javascript'> | null {
if (snapshot?.breadcrumbs && snapshot.breadcrumbs.length > 0) {
payload.breadcrumbs = snapshot.breadcrumbs;
}

return payload;
}
}
59 changes: 59 additions & 0 deletions packages/core/src/messages/message-processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Breadcrumb, CatcherMessagePayload, CatcherMessageType } from '@hawk.so/types';

/**
* Extracted addons type from catcher message payload.
*
* @typeParam T - catcher message type

Check warning on line 6 in packages/core/src/messages/message-processor.ts

View workflow job for this annotation

GitHub Actions / lint

Invalid JSDoc tag name "typeParam"
*/
type ExtractAddons<T extends CatcherMessageType> =
CatcherMessagePayload<T> extends { addons?: infer A } ? A : never;

/**
* Payload type used during message processing pipeline.
*
* Same as {@link CatcherMessagePayload} but with `addons` always defined and partially filled —
* processors may contribute individual addon fields independently of each other.
*
* @typeParam T - catcher message type this payload belongs to

Check warning on line 17 in packages/core/src/messages/message-processor.ts

View workflow job for this annotation

GitHub Actions / lint

Invalid JSDoc tag name "typeParam"
*/
export type ProcessingPayload<T extends CatcherMessageType> =
Omit<CatcherMessagePayload<T>, 'addons'> & {
addons: Partial<ExtractAddons<T>>;
};

/**
* Snapshot of event context captured synchronously at error time,
* before any processing.
*/
export interface ErrorSnapshot {
/**
* Original caught error.
*/
error?: Error | string;

/**
* Breadcrumbs captured at error time.
*/
breadcrumbs?: Breadcrumb[];
}

/**
* Single step in message processing pipeline before message is sent.
*
* @typeParam T - catcher message type this processor handles

Check warning on line 43 in packages/core/src/messages/message-processor.ts

View workflow job for this annotation

GitHub Actions / lint

Invalid JSDoc tag name "typeParam"
*/
export interface MessageProcessor<T extends CatcherMessageType = CatcherMessageType> {
/**
* Handles input message. May mutate, replace or drop it.
*
* Dropped message won't be sent.
*
* @param payload - processed event message payload with partially-built addons
* @param snapshot - additional context with original error
* @returns modified payload, or `null` to drop message
*/
apply(
payload: ProcessingPayload<T>,
snapshot?: ErrorSnapshot,
): ProcessingPayload<T> | null
}
1 change: 0 additions & 1 deletion packages/core/src/modules/sanitizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
private static readonly maxArrayLength: number = 10;

/**
* Custom type handlers registered via {@link registerHandler}.

Check warning on line 52 in packages/core/src/modules/sanitizer.ts

View workflow job for this annotation

GitHub Actions / lint

The type 'sanitize' is undefined

Check warning on line 52 in packages/core/src/modules/sanitizer.ts

View workflow job for this annotation

GitHub Actions / lint

The type 'registerHandler' is undefined
*
* Checked in {@link sanitize} before built-in type checks.
*/
Expand Down Expand Up @@ -154,7 +154,6 @@
depth: number,
seen: WeakSet<object>
): Record<string, any> | '<deep object>' | '<big object>' {

// If the maximum depth is reached, return a placeholder
if (depth > Sanitizer.maxDepth) {
return '<deep object>';
Expand Down
51 changes: 23 additions & 28 deletions packages/javascript/src/addons/breadcrumbs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @file Breadcrumbs module - captures chronological trail of events before an error
*/
import type { Breadcrumb, BreadcrumbLevel, BreadcrumbType, Json, JsonNode } from '@hawk.so/types';
import type { BreadcrumbHint, BreadcrumbInput, BreadcrumbStore } from '@hawk.so/core';
import { buildElementSelector, isValidBreadcrumb, log, Sanitizer } from '@hawk.so/core';

/**
Expand All @@ -10,9 +11,10 @@ import { buildElementSelector, isValidBreadcrumb, log, Sanitizer } from '@hawk.s
const DEFAULT_MAX_BREADCRUMBS = 15;

/**
* Hint object passed to beforeBreadcrumb callback
* Hint object passed to beforeBreadcrumb callback.
* Extends generic {@link BreadcrumbHint} with browser-specific data.
*/
export interface BreadcrumbHint {
export interface BrowserBreadcrumbHint extends BreadcrumbHint {
/**
Comment thread
neSpecc marked this conversation as resolved.
* Original event that triggered the breadcrumb (if any)
*/
Expand Down Expand Up @@ -51,7 +53,7 @@ export interface BreadcrumbsOptions {
* - Return `false` — the breadcrumb will be discarded.
* - Any other value is invalid — the original breadcrumb is stored as-is (a warning is logged).
*/
beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | false | void;
beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BrowserBreadcrumbHint) => Breadcrumb | false | void;

/**
* Enable automatic fetch/XHR breadcrumbs
Expand All @@ -75,12 +77,6 @@ export interface BreadcrumbsOptions {
trackClicks?: boolean;
}

/**
* Breadcrumb input type - breadcrumb data with optional timestamp
* (timestamp will be auto-generated if not provided)
*/
export type BreadcrumbInput = Omit<Breadcrumb, 'timestamp'> & { timestamp?: Breadcrumb['timestamp'] };

/**
* Internal breadcrumbs options - all fields except 'beforeBreadcrumb' are required
* (they have default values and are always set during init)
Expand All @@ -90,17 +86,18 @@ interface InternalBreadcrumbsOptions {
trackFetch: boolean;
trackNavigation: boolean;
trackClicks: boolean;
beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => Breadcrumb | false | void;
beforeBreadcrumb?: (breadcrumb: Breadcrumb, hint?: BrowserBreadcrumbHint) => Breadcrumb | false | void;
}

/**
* BreadcrumbManager - singleton that manages breadcrumb collection and storage
* Browser implementation of BreadcrumbStore.
* Singleton that manages breadcrumb collection and storage.
*/
export class BreadcrumbManager {
export class BrowserBreadcrumbStore implements BreadcrumbStore {
/**
* Singleton instance
*/
private static instance: BreadcrumbManager | null = null;
private static instance: BrowserBreadcrumbStore | null = null;

/**
* Breadcrumbs buffer (FIFO)
Expand Down Expand Up @@ -167,10 +164,10 @@ export class BreadcrumbManager {
/**
* Get singleton instance
*/
public static getInstance(): BreadcrumbManager {
BreadcrumbManager.instance ??= new BreadcrumbManager();
public static getInstance(): BrowserBreadcrumbStore {
BrowserBreadcrumbStore.instance ??= new BrowserBreadcrumbStore();

return BreadcrumbManager.instance;
return BrowserBreadcrumbStore.instance;
}

/**
Expand All @@ -180,8 +177,6 @@ export class BreadcrumbManager {
*/
public init(options: BreadcrumbsOptions = {}): void {
if (this.isInitialized) {
log('[BreadcrumbManager] init has already been called; breadcrumb configuration is global and subsequent init options are ignored.', 'warn');

return;
}

Expand Down Expand Up @@ -219,7 +214,7 @@ export class BreadcrumbManager {
* @param hint - Optional hint object with original event data (Event, Response, XMLHttpRequest, etc.)
* Used by beforeBreadcrumb callback to access original event context
*/
public addBreadcrumb(breadcrumb: BreadcrumbInput, hint?: BreadcrumbHint): void {
public add(breadcrumb: BreadcrumbInput, hint?: BrowserBreadcrumbHint): void {
/**
* Ensure timestamp
*/
Expand Down Expand Up @@ -293,14 +288,14 @@ export class BreadcrumbManager {
/**
* Get current breadcrumbs snapshot (oldest to newest)
*/
public getBreadcrumbs(): Breadcrumb[] {
public get(): Breadcrumb[] {
return [ ...this.breadcrumbs ];
}

/**
* Clear all breadcrumbs
*/
public clearBreadcrumbs(): void {
public clear(): void {
this.breadcrumbs.length = 0;
}

Expand Down Expand Up @@ -358,9 +353,9 @@ export class BreadcrumbManager {
this.popstateHandler = null;
}

this.clearBreadcrumbs();
this.clear();
this.isInitialized = false;
BreadcrumbManager.instance = null;
BrowserBreadcrumbStore.instance = null;
}


Expand Down Expand Up @@ -399,7 +394,7 @@ export class BreadcrumbManager {

const duration = Date.now() - startTime;

manager.addBreadcrumb({
manager.add({
type: 'request',
category: 'fetch',
message: `${response.status} ${method} ${url}`,
Expand All @@ -419,7 +414,7 @@ export class BreadcrumbManager {
} catch (error) {
const duration = Date.now() - startTime;

manager.addBreadcrumb({
manager.add({
type: 'request',
category: 'fetch',
message: `[FAIL] ${method} ${url}`,
Expand Down Expand Up @@ -483,7 +478,7 @@ export class BreadcrumbManager {
const url = this.hawkUrl || '';
const status = this.status;

manager.addBreadcrumb({
manager.add({
type: 'request',
category: 'xhr',
message: `${status} ${method} ${url}`,
Expand Down Expand Up @@ -529,7 +524,7 @@ export class BreadcrumbManager {

lastUrl = to;

manager.addBreadcrumb({
manager.add({
type: 'navigation',
category: 'navigation',
message: `Navigated to ${to}`,
Expand Down Expand Up @@ -599,7 +594,7 @@ export class BreadcrumbManager {
*/
const text = (target.textContent || target.innerText || '').trim().substring(0, 50);

manager.addBreadcrumb({
manager.add({
type: 'ui',
category: 'ui.click',
message: `Click on ${selector}`,
Expand Down
17 changes: 0 additions & 17 deletions packages/javascript/src/addons/userAgentInfo.ts

This file was deleted.

Loading
Loading