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;
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ 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';
51 changes: 24 additions & 27 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,7 +177,7 @@ 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');
log('[BrowserBreadcrumbStore] init has already been called; breadcrumb configuration is global and subsequent init options are ignored.', 'warn');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think users should see such a log. It's better to remove it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log shortened.


return;
}
Expand Down Expand Up @@ -219,7 +216,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 +290,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 +355,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 +396,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 +416,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 +480,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 +526,7 @@ export class BreadcrumbManager {

lastUrl = to;

manager.addBreadcrumb({
manager.add({
type: 'navigation',
category: 'navigation',
message: `Navigated to ${to}`,
Expand Down Expand Up @@ -599,7 +596,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
25 changes: 13 additions & 12 deletions packages/javascript/src/catcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './modules/element-sanitizer';
import Socket from './modules/socket';
import type { BreadcrumbsAPI, CatcherMessage, HawkInitialSettings, HawkJavaScriptEvent, Transport } from './types';
import type { CatcherMessage, HawkInitialSettings, HawkJavaScriptEvent, Transport } from './types';
import { VueIntegration } from './integrations/vue';
import type {
AffectedUser,
Expand All @@ -13,7 +13,8 @@ import type {
} from '@hawk.so/types';
import type { JavaScriptCatcherIntegrations } from '@/types';
import { ConsoleCatcher } from './addons/consoleCatcher';
import { BreadcrumbManager } from './addons/breadcrumbs';
import { BrowserBreadcrumbStore } from './addons/breadcrumbs';
import type { BreadcrumbStore } from '@hawk.so/core';
import {
EventRejectedError,
HawkUserManager,
Expand Down Expand Up @@ -121,9 +122,9 @@ export default class Catcher {
private readonly consoleCatcher: ConsoleCatcher | null = null;

/**
* Breadcrumb manager instance
* Breadcrumb store instance
*/
private readonly breadcrumbManager: BreadcrumbManager | null;
private readonly breadcrumbStore: BrowserBreadcrumbStore | null;

/**
* Manages currently authenticated user identity.
Expand Down Expand Up @@ -195,10 +196,10 @@ export default class Catcher {
* Initialize breadcrumbs
*/
if (settings.breadcrumbs !== false) {
this.breadcrumbManager = BreadcrumbManager.getInstance();
this.breadcrumbManager.init(settings.breadcrumbs ?? {});
this.breadcrumbStore = BrowserBreadcrumbStore.getInstance();
this.breadcrumbStore.init(settings.breadcrumbs ?? {});
} else {
this.breadcrumbManager = null;
this.breadcrumbStore = null;
}

/**
Expand Down Expand Up @@ -297,11 +298,11 @@ export default class Catcher {
* data: { userId: '123' }
* });
*/
public get breadcrumbs(): BreadcrumbsAPI {
public get breadcrumbs(): BreadcrumbStore {
return {
add: (breadcrumb, hint) => this.breadcrumbManager?.addBreadcrumb(breadcrumb, hint),
get: () => this.breadcrumbManager?.getBreadcrumbs() ?? [],
clear: () => this.breadcrumbManager?.clearBreadcrumbs(),
add: (breadcrumb, hint) => this.breadcrumbStore?.add(breadcrumb, hint),
get: () => this.breadcrumbStore?.get() ?? [],
clear: () => this.breadcrumbStore?.clear(),
};
}

Expand Down Expand Up @@ -578,7 +579,7 @@ export default class Catcher {
* Get breadcrumbs for event payload
*/
private getBreadcrumbsForEvent(): HawkJavaScriptEvent['breadcrumbs'] {
const breadcrumbs = this.breadcrumbManager?.getBreadcrumbs();
const breadcrumbs = this.breadcrumbStore?.get();

return breadcrumbs && breadcrumbs.length > 0 ? breadcrumbs : undefined;
}
Expand Down
11 changes: 0 additions & 11 deletions packages/javascript/src/types/breadcrumbs-api.ts

This file was deleted.

10 changes: 8 additions & 2 deletions packages/javascript/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import type { CatcherMessage } from './catcher-message';
import type { HawkInitialSettings } from './hawk-initial-settings';
import type { Transport } from '@hawk.so/core';
import type { BreadcrumbsAPI, BreadcrumbStore } from '@hawk.so/core';
import type { HawkJavaScriptEvent } from './event';
import type { VueIntegrationData, NuxtIntegrationData, NuxtIntegrationAddons, JavaScriptCatcherIntegrations } from './integrations';
import type { BreadcrumbsAPI } from './breadcrumbs-api';
import type {
JavaScriptCatcherIntegrations,
NuxtIntegrationAddons,
NuxtIntegrationData,
VueIntegrationData
} from './integrations';

export type {
CatcherMessage,
Expand All @@ -14,5 +19,6 @@ export type {
NuxtIntegrationData,
NuxtIntegrationAddons,
JavaScriptCatcherIntegrations,
BreadcrumbStore,
BreadcrumbsAPI
};
Loading
Loading