From b821ac58030b8e68ddda9cb5a41b26b1614a00ef Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:27:29 +0300 Subject: [PATCH 1/4] feat(catcher): add tracking for errors from files marked with HAWK:tracked marker --- src/catcher.ts | 81 ++++++++++++++++++++++++++++++ src/types/hawk-initial-settings.ts | 9 +++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/catcher.ts b/src/catcher.ts index 3511a65..9cf2594 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -2,6 +2,7 @@ import Socket from './modules/socket'; import Sanitizer from './modules/sanitizer'; import log from './utils/log'; import StackParser from './modules/stackParser'; +import fetchTimer from './modules/fetchTimer'; import type { CatcherMessage, HawkInitialSettings } from './types'; import { VueIntegration } from './integrations/vue'; import { id } from './utils/id'; @@ -103,6 +104,11 @@ export default class Catcher { */ private readonly consoleCatcher: ConsoleCatcher | null = null; + /** + * Track only errors from files marked with HAWK:tracked marker + */ + private readonly trackOnlyMarkedFiles: boolean; + /** * Catcher constructor * @@ -129,6 +135,7 @@ export default class Catcher { settings.consoleTracking !== null && settings.consoleTracking !== undefined ? settings.consoleTracking : true; + this.trackOnlyMarkedFiles = settings.trackOnlyMarkedFiles || false; if (!this.token) { log( @@ -343,6 +350,19 @@ export default class Catcher { markErrorAsProcessed(error); } + /** + * Check if we should filter by tracking marker + */ + if (this.trackOnlyMarkedFiles) { + const hasMarker = await this.checkTrackingMarker(error); + if (!hasMarker) { + /** + * Error is not from a marked file, skip it + */ + return; + } + } + const errorFormatted = await this.prepareErrorFormatted(error, context); /** @@ -478,6 +498,67 @@ export default class Catcher { } } + /** + * Check if error comes from a file marked with HAWK:tracked marker + * + * @param error - error to check + * @returns true if at least one file in stack trace contains the marker, false otherwise + */ + private async checkTrackingMarker(error: Error | string): Promise { + const notAnError = !(error instanceof Error); + + /** + * If error is not an Error instance, we can't check stack trace + */ + if (notAnError) { + return false; + } + + try { + const backtrace = await this.stackParser.parse(error as Error); + + if (!backtrace || backtrace.length === 0) { + return false; + } + + /** + * Check each file in the stack trace + */ + const marker = '/*! HAWK:tracked */'; + const markerCheckPromises = backtrace.map(async (frame) => { + if (!frame.file) { + return false; + } + + try { + /** + * Use fetchTimer to load file with timeout (same as StackParser does) + */ + const response = await fetchTimer(frame.file, 2000); + const fileContent = await response.text(); + return fileContent.includes(marker); + } catch { + /** + * If we can't load the file, skip it + */ + return false; + } + }); + + const results = await Promise.all(markerCheckPromises); + /** + * Return true if at least one file contains the marker + */ + return results.some(hasMarker => hasMarker === true); + } catch { + /** + * If we can't parse the stack trace, allow the error to be sent + * (fail open - better to send too many than miss important errors) + */ + return true; + } + } + /** * Collects additional information * diff --git a/src/types/hawk-initial-settings.ts b/src/types/hawk-initial-settings.ts index 9035f0b..370e8a2 100644 --- a/src/types/hawk-initial-settings.ts +++ b/src/types/hawk-initial-settings.ts @@ -64,7 +64,7 @@ export interface HawkInitialSettings { /** * This Method allows you to filter any data you don't want sending to Hawk. - * + * * Return `false` to prevent the event from being sent to Hawk. */ beforeSend?(event: HawkJavaScriptEvent): HawkJavaScriptEvent | false; @@ -80,4 +80,11 @@ export interface HawkInitialSettings { * Console log handler */ consoleTracking?: boolean; + + /** + * Track only errors from files marked with HAWK:tracked marker. + * If enabled, only errors from files containing this marker will be sent. + * Default is false (all errors are tracked). + */ + trackOnlyMarkedFiles?: boolean; } From 04571a65f8769eca892676a0ae1ce1b3b6644ab1 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Sat, 29 Nov 2025 19:09:04 +0300 Subject: [PATCH 2/4] chore: bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64ac827..533bb7b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hawk.so/javascript", "type": "commonjs", - "version": "3.2.11", + "version": "3.2.12", "description": "JavaScript errors tracking for Hawk.so", "files": [ "dist" From 5ff967f9b4873f4c386f450852c3955acf900b3d Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:54:55 +0300 Subject: [PATCH 3/4] chore: update readme --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 44fca79..f5d1d65 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Initialization settings: | `disableGlobalErrorsHandling` | boolean | optional | Do not initialize global errors handling | | `disableVueErrorHandler` | boolean | optional | Do not initialize Vue errors handling | | `consoleTracking` | boolean | optional | Initialize console logs tracking | +| `trackOnlyMarkedFiles` | boolean | optional | If `true`, send only errors whose stack frames reference files marked for tracking (see [Tracking only marked files](#tracking-only-marked-files)) | | `beforeSend` | function(event) => event | optional | This Method allows you to filter any data you don't want sending to Hawk | Other available [initial settings](types/hawk-initial-settings.d.ts) are described at the type definition. @@ -175,6 +176,33 @@ window.hawk = new HawkCatcher({ }) ``` +## Tracking only marked files + +Sometimes you want Hawk to ignore errors coming from "foreign" code (third‑party bundles, other apps on the same domain, legacy scripts, etc.). +To do that, enable the `trackOnlyMarkedFiles` option: + +```js +const hawk = new HawkCatcher({ + token: 'INTEGRATION_TOKEN', + trackOnlyMarkedFiles: true, +}); +``` + +With this flag turned on, Hawk will: + +- **Inspect stack trace frames** for each error inside `formatAndSend()`. +- **Call an internal `checkTrackingMarker()`** for each file in the stack trace. +- **Drop the error** if none of the frames refer to a file that contains the tracking marker comment. + +Typical usage is to add a special marker comment into the bundles you actually want to track, for example: + +```js +// @hawk:track +// rest of your bundled app +``` + +Only files that contain this marker (or whatever marker your build inserts) will be treated as "ours" when `trackOnlyMarkedFiles` is enabled, everything else will be filtered out. + ## Dismiss error You can use the `beforeSend()` hook to prevent sending a particular event. Return `false` for that. From 4748e464f560a04fc4c99409e2caf78448252328 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:59:04 +0300 Subject: [PATCH 4/4] chore: lint fix --- README.md | 2 +- src/catcher.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f5d1d65..f1d8358 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ With this flag turned on, Hawk will: Typical usage is to add a special marker comment into the bundles you actually want to track, for example: ```js -// @hawk:track +// /*! HAWK:tracked */ // rest of your bundled app ``` diff --git a/src/catcher.ts b/src/catcher.ts index 9cf2594..1b8c975 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -355,6 +355,7 @@ export default class Catcher { */ if (this.trackOnlyMarkedFiles) { const hasMarker = await this.checkTrackingMarker(error); + if (!hasMarker) { /** * Error is not from a marked file, skip it @@ -502,7 +503,7 @@ export default class Catcher { * Check if error comes from a file marked with HAWK:tracked marker * * @param error - error to check - * @returns true if at least one file in stack trace contains the marker, false otherwise + * @returns {boolean} true if at least one file in stack trace contains the marker, false otherwise */ private async checkTrackingMarker(error: Error | string): Promise { const notAnError = !(error instanceof Error); @@ -536,6 +537,7 @@ export default class Catcher { */ const response = await fetchTimer(frame.file, 2000); const fileContent = await response.text(); + return fileContent.includes(marker); } catch { /** @@ -549,6 +551,7 @@ export default class Catcher { /** * Return true if at least one file contains the marker */ + return results.some(hasMarker => hasMarker === true); } catch { /**