Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hawk.so/browser",
"version": "3.4.0",
"version": "3.4.1",
"description": "JavaScript Browser errors tracking for Hawk.so",
"files": [
"dist"
Expand Down Expand Up @@ -45,7 +45,7 @@
"web-vitals": "^5.1.0"
},
"devDependencies": {
"@hawk.so/types": "0.5.8",
"@hawk.so/types": "0.6.4",
"@vitest/coverage-v8": "^4.0.18",
"jsdom": "^28.0.0",
"vite": "^7.3.1",
Expand Down
9 changes: 7 additions & 2 deletions packages/browser/src/catcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { VueIntegrationAddons } from '@hawk.so/types';
import type { JavaScriptCatcherIntegrations } from '@/types';
import { ConsoleCatcher } from './addons/consoleCatcher';
import { BrowserBreadcrumbStore } from './addons/breadcrumbs';
import { BaseCatcher, HawkUserManager, isLoggerSet, log, setLogger, decodeIntegrationId } from '@hawk.so/core';
import { BaseCatcher, HawkUserManager, isLoggerSet, log, setLogger, decodeIntegrationId, EventDedupeTransport } from '@hawk.so/core';
import { HawkLocalStorage } from './utils/hawk-local-storage';
import { createBrowserLogger } from './utils/logger';
import { BrowserRandomGenerator } from './utils/random';
Expand Down Expand Up @@ -112,6 +112,11 @@ export default class Catcher extends BaseCatcher<typeof Catcher.type> {
},
});

const batcher = new EventDedupeTransport(transport);

// Flush buffered events before the socket closes on page hide
window.addEventListener('pagehide', () => batcher.flush(), { capture: true });

let breadcrumbStore: BrowserBreadcrumbStore | null = null;

if (token && settings.breadcrumbs !== false) {
Expand All @@ -120,7 +125,7 @@ export default class Catcher extends BaseCatcher<typeof Catcher.type> {

super(
token,
transport,
batcher,
userManager,
settings.release !== undefined ? String(settings.release) : undefined,
settings.context || undefined,
Expand Down
7 changes: 6 additions & 1 deletion packages/browser/tests/catcher.addons.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import type * as HawkCore from '@hawk.so/core';
import { BrowserBreadcrumbStore } from '../src/addons/breadcrumbs';
import { wait, createTransport, getLastPayload, createCatcher } from './catcher.helpers';
Expand All @@ -21,6 +21,11 @@ describe('Catcher', () => {
localStorage.clear();
mockParse.mockResolvedValue([]);
breadcrumbStore.instance?.destroy();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

// ── Environment addons ────────────────────────────────────────────────────
Expand Down
10 changes: 9 additions & 1 deletion packages/browser/tests/catcher.before-send.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { createCatcher, createTransport, wait, getLastPayload } from './catcher.helpers';

vi.mock('@hawk.so/core', async (importOriginal) => {
Expand All @@ -7,6 +7,14 @@ vi.mock('@hawk.so/core', async (importOriginal) => {
});

describe('Catcher', () => {
beforeEach(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

it('should send event as-is when beforeSend returns it unchanged', async () => {
const { sendSpy, transport } = createTransport();
const hawk = createCatcher(transport, { beforeSend: (event) => event });
Expand Down
7 changes: 6 additions & 1 deletion packages/browser/tests/catcher.breadcrumbs.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BrowserBreadcrumbStore } from '../src/addons/breadcrumbs';
import { wait, createTransport, getLastPayload, createCatcher } from './catcher.helpers';

Expand All @@ -13,6 +13,11 @@ describe('Catcher', () => {
localStorage.clear();
mockParse.mockResolvedValue([]);
(BrowserBreadcrumbStore as any).instance?.destroy();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

// ── Breadcrumbs trail ─────────────────────────────────────────────────────
Expand Down
7 changes: 6 additions & 1 deletion packages/browser/tests/catcher.context.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BrowserBreadcrumbStore } from '../src/addons/breadcrumbs';
import { wait, createTransport, getLastPayload, createCatcher } from './catcher.helpers';

Expand All @@ -13,6 +13,11 @@ describe('Catcher', () => {
localStorage.clear();
mockParse.mockResolvedValue([]);
(BrowserBreadcrumbStore as any).instance?.destroy();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

// ── Context enrichment ────────────────────────────────────────────────────
Expand Down
5 changes: 5 additions & 0 deletions packages/browser/tests/catcher.global-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ describe('Catcher', () => {
localStorage.clear();
mockParse.mockResolvedValue([]);
(BrowserBreadcrumbStore as any).instance?.destroy();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

// ── Global error handlers ─────────────────────────────────────────────────
Expand Down
9 changes: 8 additions & 1 deletion packages/browser/tests/catcher.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import { vi } from 'vitest';
import Catcher from '../src/catcher';
import type { Transport } from '../src';
import type { MessageProcessor } from '@hawk.so/core';
import { DEFAULT_EVENT_DEDUPE_WINDOW_MS } from '@hawk.so/core';

export const TEST_TOKEN = 'eyJpbnRlZ3JhdGlvbklkIjoiOTU3MmQyOWQtNWJhZS00YmYyLTkwN2MtZDk5ZDg5MGIwOTVmIiwic2VjcmV0IjoiZTExODFiZWItMjdlMS00ZDViLWEwZmEtZmUwYTM1Mzg5OWMyIn0=';
export const wait = (): Promise<void> => new Promise((r) => setTimeout(r, 0));

/**
* Advances past the Catcher's default EventDedupeTransport window so buffered
* events are forwarded to the underlying transport. Requires fake timers
* (`vi.useFakeTimers()`) to be active in the calling test file.
*/
export const wait = (): Promise<void> => vi.advanceTimersByTimeAsync(DEFAULT_EVENT_DEDUPE_WINDOW_MS);

export function createTransport() {
const sendSpy = vi.fn().mockResolvedValue(undefined);
Expand Down
10 changes: 9 additions & 1 deletion packages/browser/tests/catcher.message-processor.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest';
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { createCatcher, createTransport, getLastPayload, injectProcessor, wait } from './catcher.helpers';
import { MessageProcessor, ProcessingPayload } from '@hawk.so/core';

Expand All @@ -10,6 +10,14 @@ vi.mock('../src/modules/stackParser', () => ({
}));

describe('Catcher', () => {
beforeEach(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

describe('message processor', () => {
it('should send original message when processor does not modify it', async () => {
const { sendSpy, transport } = createTransport();
Expand Down
10 changes: 9 additions & 1 deletion packages/browser/tests/catcher.release.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createCatcher, createTransport, getLastPayload, wait } from "./catcher.helpers";

vi.mock('@hawk.so/core', async (importOriginal) => {
Expand All @@ -7,6 +7,14 @@ vi.mock('@hawk.so/core', async (importOriginal) => {
});

describe('Catcher', () => {
beforeEach(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

it('should include release version when configured', async () => {
const { sendSpy, transport } = createTransport();

Expand Down
10 changes: 8 additions & 2 deletions packages/browser/tests/catcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ describe('Catcher', () => {
localStorage.clear();
mockParse.mockResolvedValue([]);
(BrowserBreadcrumbStore as any).instance?.destroy();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

// ── Constructor variants ──────────────────────────────────────────────────
Expand Down Expand Up @@ -114,15 +119,16 @@ describe('Catcher', () => {
expect(sendSpy).toHaveBeenCalledTimes(2);
});

it('should send payload for same strings without deduplication', async () => {
it('should merge repeated identical string occurrences into a single send with count', async () => {
const { sendSpy, transport } = createTransport();
const hawk = createCatcher(transport);

hawk.send('reason');
hawk.send('reason');
await wait();

expect(sendSpy).toHaveBeenCalledTimes(2);
expect(sendSpy).toHaveBeenCalledTimes(1);
expect(sendSpy.mock.calls[0][0].count).toBe(2);
});
});

Expand Down
7 changes: 6 additions & 1 deletion packages/browser/tests/catcher.transport.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BrowserBreadcrumbStore } from '../src/addons/breadcrumbs';
import type { Transport } from '../src';
import { wait, createCatcher, createTransport } from './catcher.helpers';
Expand All @@ -14,6 +14,11 @@ describe('Catcher', () => {
localStorage.clear();
mockParse.mockResolvedValue([]);
(BrowserBreadcrumbStore as any).instance?.destroy();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

describe('transport failure', () => {
Expand Down
7 changes: 6 additions & 1 deletion packages/browser/tests/catcher.user.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { BrowserBreadcrumbStore } from '../src/addons/breadcrumbs';
import { wait, createTransport, getLastPayload, createCatcher } from './catcher.helpers';

Expand All @@ -13,6 +13,11 @@ describe('Catcher', () => {
localStorage.clear();
mockParse.mockResolvedValue([]);
(BrowserBreadcrumbStore as any).instance?.destroy();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

// ── User identity ─────────────────────────────────────────────────────────
Expand Down
4 changes: 2 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hawk.so/core",
"version": "1.0.0",
"version": "1.0.1",
"description": "Base implementation for all Hawk.so JavaScript SDKs",
"files": [
"dist"
Expand Down Expand Up @@ -37,7 +37,7 @@
},
"homepage": "https://github.com/codex-team/hawk.javascript#readme",
"dependencies": {
"@hawk.so/types": "0.5.8"
"@hawk.so/types": "0.6.4"
},
"devDependencies": {
"@vitest/coverage-v8": "^4.0.18",
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ export type { MessageProcessor, ProcessingPayload } from './types/message-proces
export { BaseCatcher } from './catcher';
export type { BeforeSendHook } from './catcher';
export { decodeIntegrationId } from './utils/integration-id-decoder';
export { EventDedupeTransport, DEFAULT_EVENT_DEDUPE_WINDOW_MS } from './utils/event-dedupe-transport';
export type { EventDedupeTransportOptions } from './utils/event-dedupe-transport';
Loading