From 92d79e577b5ade425842a35b43b3954a0170704b Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 15:49:31 +0300 Subject: [PATCH 01/13] add user and context management features --- example/index.html | 448 +++++++++++++++++++++------------------ example/sample-errors.js | 87 +++++++- src/catcher.ts | 68 +++++- 3 files changed, 382 insertions(+), 221 deletions(-) diff --git a/example/index.html b/example/index.html index 6f5b6cb..fd56c79 100644 --- a/example/index.html +++ b/example/index.html @@ -1,240 +1,270 @@ - - + + Hawk JavaScript example - + - - -
-

- Hawk JavaScript Catcher -

- https://github.com/codex-team/hawk.javascript -
-
-

Send test event

- -
-
-

Send real error

- -
-
-

Send Unhandled promise rejection error

- -
-
-

- Send event manually - - Through the .send(error, context) method - -

- - -
-
- -
-
-

Send the specified number of errors

- - -

- - +
+
+ +
+
+

Send the specified number of errors

+ + +

+ + -

- -
-
-

Test console catcher

- - -

- - +

+ +
+
+

Test console catcher

+ + +

+ + -

- -
- -
-

Test Vue integration: $root

-
+ +

+ +
+
+

User Management

+ + +

+ + +

+ + +

+ + + +
+
+

Context Management

+ + +

+ + +

+ + + +
+ +
+

Test Vue integration: $root

+
-
- -
-
-

Test Vue integration: <test-component>

-
- -
- +
+
+

Test Vue integration: <test-component>

+
+ +
+ +
+ + - - - - + diff --git a/example/sample-errors.js b/example/sample-errors.js index 26032f2..c15c37f 100644 --- a/example/sample-errors.js +++ b/example/sample-errors.js @@ -68,8 +68,89 @@ buttonManualSending.addEventListener('click', () => { window.hawk.send( new Error('Manual sending example'), - contextSample.trim().length - ? { contextSample } - : undefined + contextSample.trim().length ? { contextSample } : undefined ); }); + +/** + * User Management + */ +const buttonSetUser = document.getElementById('btn-set-user'); +const buttonClearUser = document.getElementById('btn-clear-user'); +const buttonGetUser = document.getElementById('btn-get-user'); + +buttonSetUser.addEventListener('click', () => { + const userId = document.getElementById('userId').value; + const userName = document.getElementById('userName').value; + const userUrl = document.getElementById('userUrl').value; + + if (!userId.trim()) { + alert('User ID is required'); + return; + } + + const user = { + id: userId, + ...(userName.trim() && { name: userName }), + ...(userUrl.trim() && { url: userUrl }), + }; + + window.hawk.setUser(user); +}); + +buttonClearUser.addEventListener('click', () => { + window.hawk.clearUser(); +}); + +buttonGetUser.addEventListener('click', () => { + const currentUser = window.hawk.getCurrentUser(); + alert('Current user: ' + (currentUser ? JSON.stringify(currentUser, null, 2) : 'No user set')); +}); + +/** + * Context Management + */ +const buttonSetContext = document.getElementById('btn-set-context'); +const buttonClearContext = document.getElementById('btn-clear-context'); +const buttonGetContext = document.getElementById('btn-get-context'); + +buttonSetContext.addEventListener('click', () => { + const contextKey = document.getElementById('contextKey').value; + const contextValue = document.getElementById('contextValue').value; + + if (!contextKey.trim()) { + alert('Context key is required'); + return; + } + + const context = { + [contextKey]: contextValue, + }; + + window.hawk.setContext(context); +}); + +buttonClearContext.addEventListener('click', () => { + window.hawk.clearContext(); +}); + +buttonGetContext.addEventListener('click', () => { + const currentContext = window.hawk.getCurrentContext(); + alert( + 'Current context: ' + + (currentContext ? JSON.stringify(currentContext, null, 2) : 'No context set') + ); +}); + +/** + * Test without user + */ +const buttonTestWithoutUser = document.getElementById('btn-test-without-user'); + +buttonTestWithoutUser.addEventListener('click', () => { + // Clear user first to ensure no user is set + window.hawk.clearUser(); + + // Send error without user + window.hawk.send(new Error('Test error without user')); +}); diff --git a/src/catcher.ts b/src/catcher.ts index 18a629f..0b38dd8 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -63,12 +63,12 @@ export default class Catcher { /** * Current authenticated user */ - private readonly user: AffectedUser; + private user: AffectedUser | null; /** * Any additional data passed by user for sending with all messages */ - private readonly context: EventContext | undefined; + private context: EventContext | undefined; /** * This Method allows developer to filter any data you don't want sending to Hawk @@ -218,13 +218,63 @@ export default class Catcher { */ public connectVue(vue): void { // eslint-disable-next-line no-new - this.vue = new VueIntegration(vue, (error: Error, addons: VueIntegrationAddons) => { - void this.formatAndSend(error, { - vue: addons, - }); - }, { - disableVueErrorHandler: this.disableVueErrorHandler, - }); + this.vue = new VueIntegration( + vue, + (error: Error, addons: VueIntegrationAddons) => { + void this.formatAndSend(error, { + vue: addons, + }); + }, + { + disableVueErrorHandler: this.disableVueErrorHandler, + } + ); + } + + /** + * Update the current user information + * + * @param user - New user information + */ + public setUser(user: AffectedUser): void { + this.user = user; + } + + /** + * Clear current user information + */ + public clearUser(): void { + this.user = null; + } + + /** + * Get current user information + */ + public getCurrentUser(): AffectedUser | null { + return this.user; + } + + /** + * Update the context data that will be sent with all events + * + * @param context - New context data + */ + public setContext(context: EventContext): void { + this.context = context; + } + + /** + * Clear current context data + */ + public clearContext(): void { + this.context = undefined; + } + + /** + * Get current context data + */ + public getCurrentContext(): EventContext | undefined { + return this.context; } /** From 9849afd1f93df56cc7b16784c6401dc6e7a0ee65 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:09:56 +0300 Subject: [PATCH 02/13] refactor: remove unused user and context retrieval buttons and clean up related code --- example/index.html | 2 -- example/sample-errors.js | 19 +++---------------- src/catcher.ts | 18 ++---------------- 3 files changed, 5 insertions(+), 34 deletions(-) diff --git a/example/index.html b/example/index.html index fd56c79..13319a0 100644 --- a/example/index.html +++ b/example/index.html @@ -165,7 +165,6 @@

User Management



-

Context Management

@@ -177,7 +176,6 @@

Context Management



-
diff --git a/example/sample-errors.js b/example/sample-errors.js index c15c37f..6c1a6fc 100644 --- a/example/sample-errors.js +++ b/example/sample-errors.js @@ -28,7 +28,9 @@ const buttonPromiseRejection = document.getElementById('btn-promise-rejection'); buttonPromiseRejection.addEventListener('click', function promiseRejectionSample() { // Promise.reject('This is a sample rejected promise'); - Promise.resolve().then(realErrorSample).then(() => {}); + Promise.resolve() + .then(realErrorSample) + .then(() => {}); }); /** @@ -77,7 +79,6 @@ buttonManualSending.addEventListener('click', () => { */ const buttonSetUser = document.getElementById('btn-set-user'); const buttonClearUser = document.getElementById('btn-clear-user'); -const buttonGetUser = document.getElementById('btn-get-user'); buttonSetUser.addEventListener('click', () => { const userId = document.getElementById('userId').value; @@ -102,17 +103,11 @@ buttonClearUser.addEventListener('click', () => { window.hawk.clearUser(); }); -buttonGetUser.addEventListener('click', () => { - const currentUser = window.hawk.getCurrentUser(); - alert('Current user: ' + (currentUser ? JSON.stringify(currentUser, null, 2) : 'No user set')); -}); - /** * Context Management */ const buttonSetContext = document.getElementById('btn-set-context'); const buttonClearContext = document.getElementById('btn-clear-context'); -const buttonGetContext = document.getElementById('btn-get-context'); buttonSetContext.addEventListener('click', () => { const contextKey = document.getElementById('contextKey').value; @@ -134,14 +129,6 @@ buttonClearContext.addEventListener('click', () => { window.hawk.clearContext(); }); -buttonGetContext.addEventListener('click', () => { - const currentContext = window.hawk.getCurrentContext(); - alert( - 'Current context: ' + - (currentContext ? JSON.stringify(currentContext, null, 2) : 'No context set') - ); -}); - /** * Test without user */ diff --git a/src/catcher.ts b/src/catcher.ts index 0b38dd8..1aac619 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -241,17 +241,10 @@ export default class Catcher { } /** - * Clear current user information + * Clear current user information (revert to generated user) */ public clearUser(): void { - this.user = null; - } - - /** - * Get current user information - */ - public getCurrentUser(): AffectedUser | null { - return this.user; + this.user = Catcher.getGeneratedUser(); } /** @@ -270,13 +263,6 @@ export default class Catcher { this.context = undefined; } - /** - * Get current context data - */ - public getCurrentContext(): EventContext | undefined { - return this.context; - } - /** * Init global errors handler */ From a2d70268f1003d3ee9846e6685aa9c1235dadf21 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:13:22 +0300 Subject: [PATCH 03/13] refactor: change user property type to ensure it is always defined --- src/catcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/catcher.ts b/src/catcher.ts index 1aac619..bd4f72a 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -63,7 +63,7 @@ export default class Catcher { /** * Current authenticated user */ - private user: AffectedUser | null; + private user: AffectedUser; /** * Any additional data passed by user for sending with all messages From 1984b1c6ef422c34c86df85d6b0319002426c877 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:27:43 +0300 Subject: [PATCH 04/13] chore: update version to 3.2.9 and enhance README with user and context management examples --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f5fc14..1e3aa40 100644 --- a/README.md +++ b/README.md @@ -106,10 +106,47 @@ const hawk = new HawkCatcher({token: 'INTEGRATION_TOKEN'}); // somewhere in try-catch block or other custom place hawk.send(new Error('Something went wrong'), { - myOwnDebugInfo: '1234' + myOwnDebugInfo: '1234', }); ``` +## User Management + +You can dynamically manage user information after the catcher is initialized: + +```js +const hawk = new HawkCatcher({ token: 'INTEGRATION_TOKEN' }); + +// Set user information +hawk.setUser({ + id: 'user123', + name: 'John Doe', + url: '/users/123', + image: 'https://example.com/avatar.jpg', +}); + +// Clear user (revert to generated user) +hawk.clearUser(); +``` + +## Context Management + +You can dynamically update context data that will be sent with all events: + +```js +const hawk = new HawkCatcher({ token: 'INTEGRATION_TOKEN' }); + +// Set context data +hawk.setContext({ + feature: 'user-dashboard', + version: '2.1.0', + environment: 'production', +}); + +// Clear context data +hawk.clearContext(); +``` + ## Source maps consuming If your bundle is minified, it is useful to pass source-map files to the Hawk. After that you will see beautiful original source code lines in Hawk Garage instead of minified code. diff --git a/package.json b/package.json index c456d6c..14d623a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hawk.so/javascript", "type": "commonjs", - "version": "3.2.8", + "version": "3.2.9", "description": "JavaScript errors tracking for Hawk.so", "files": [ "dist" From 22f7887e88ebcde32c7e3201c47caae3b7f699b6 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 18:03:34 +0300 Subject: [PATCH 05/13] feat: add user and context validation in setUser and setContext methods --- src/catcher.ts | 19 +++++++- src/utils/validation.ts | 96 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/utils/validation.ts diff --git a/src/catcher.ts b/src/catcher.ts index bd4f72a..207a2c2 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -17,6 +17,7 @@ import { EventRejectedError } from './errors'; import type { HawkJavaScriptEvent } from './types'; import { isErrorProcessed, markErrorAsProcessed } from './utils/event'; import { addErrorEvent, getConsoleLogStack, initConsoleCatcher } from './addons/consoleCatcher'; +import { validateUser, validateContext, logValidationErrors } from './utils/validation'; /** * Allow to use global VERSION, that will be overwritten by Webpack @@ -237,7 +238,14 @@ export default class Catcher { * @param user - New user information */ public setUser(user: AffectedUser): void { - this.user = user; + const validation = validateUser(user); + + if (!validation.isValid) { + logValidationErrors('setUser', validation.errors); + return; + } + + this.user = validation.data!; } /** @@ -253,7 +261,14 @@ export default class Catcher { * @param context - New context data */ public setContext(context: EventContext): void { - this.context = context; + const validation = validateContext(context); + + if (!validation.isValid) { + logValidationErrors('setContext', validation.errors); + return; + } + + this.context = validation.data!; } /** diff --git a/src/utils/validation.ts b/src/utils/validation.ts new file mode 100644 index 0000000..3dd989c --- /dev/null +++ b/src/utils/validation.ts @@ -0,0 +1,96 @@ +import type { AffectedUser, EventContext } from '@hawk.so/types'; + +/** + * Validation result interface + */ +interface ValidationResult { + isValid: boolean; + data?: T; + errors: string[]; +} + +/** + * Validates user data - basic security checks + */ +export function validateUser(user: any): ValidationResult { + const errors: string[] = []; + + if (!user || typeof user !== 'object') { + errors.push('User must be an object'); + return { isValid: false, errors }; + } + + // Validate required ID + if (!user.id || typeof user.id !== 'string' || user.id.trim() === '') { + errors.push('User ID is required and must be a non-empty string'); + return { isValid: false, errors }; + } + + const validatedUser: AffectedUser = { + id: user.id.trim(), + }; + + // Add optional fields if they exist and are strings + if (user.name && typeof user.name === 'string') { + validatedUser.name = user.name.trim(); + } + + if (user.url && typeof user.url === 'string') { + validatedUser.url = user.url.trim(); + } + + if (user.image && typeof user.image === 'string') { + validatedUser.image = user.image.trim(); + } + + return { + isValid: true, + data: validatedUser, + errors, + }; +} + +/** + * Validates context data - basic security checks + */ +export function validateContext(context: any): ValidationResult { + const errors: string[] = []; + + if (!context || typeof context !== 'object') { + errors.push('Context must be an object'); + return { isValid: false, errors }; + } + + const validatedContext: EventContext = {}; + + for (const [key, value] of Object.entries(context)) { + // Basic key validation + if (typeof key !== 'string' || key.trim() === '') { + continue; + } + + // Check if value is serializable (prevents injection) + try { + JSON.stringify(value); + } catch (e) { + continue; // Skip non-serializable values + } + + validatedContext[key.trim()] = value as any; + } + + return { + isValid: true, + data: validatedContext, + errors, + }; +} + +/** + * Logs validation errors + */ +export function logValidationErrors(prefix: string, errors: string[]): void { + errors.forEach((error) => { + console.warn(`${prefix}: ${error}`); + }); +} From 59854523f2e4b8d94b4da197c1504a37e75157e7 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:16:31 +0300 Subject: [PATCH 06/13] chore: update version to 3.2.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66b2bad..ebdf1e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hawk.so/javascript", "type": "commonjs", - "version": "3.2.9", + "version": "3.2.10", "description": "JavaScript errors tracking for Hawk.so", "files": [ "dist" From 6d85fa3b2e95980770bca5a0b09d67a542b2eb22 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:32:36 +0300 Subject: [PATCH 07/13] refactor: change validation functions to use Sanitizer for object checks --- src/modules/sanitizer.ts | 2 +- src/utils/validation.ts | 29 ++++++----------------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/modules/sanitizer.ts b/src/modules/sanitizer.ts index 749a2e0..030bc74 100644 --- a/src/modules/sanitizer.ts +++ b/src/modules/sanitizer.ts @@ -153,7 +153,7 @@ export default class Sanitizer { * * @param target - variable to check */ - private static isObject(target: any): boolean { + public static isObject(target: any): boolean { return Sanitizer.typeOf(target) === 'object'; } diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 3dd989c..4195261 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,4 +1,5 @@ import type { AffectedUser, EventContext } from '@hawk.so/types'; +import Sanitizer from 'src/modules/sanitizer'; /** * Validation result interface @@ -12,10 +13,10 @@ interface ValidationResult { /** * Validates user data - basic security checks */ -export function validateUser(user: any): ValidationResult { +export function validateUser(user: AffectedUser): ValidationResult { const errors: string[] = []; - if (!user || typeof user !== 'object') { + if (!user || !Sanitizer.isObject(user)) { errors.push('User must be an object'); return { isValid: false, errors }; } @@ -53,35 +54,17 @@ export function validateUser(user: any): ValidationResult { /** * Validates context data - basic security checks */ -export function validateContext(context: any): ValidationResult { +export function validateContext(context: EventContext): ValidationResult { const errors: string[] = []; - if (!context || typeof context !== 'object') { + if (!context || !Sanitizer.isObject(context)) { errors.push('Context must be an object'); return { isValid: false, errors }; } - const validatedContext: EventContext = {}; - - for (const [key, value] of Object.entries(context)) { - // Basic key validation - if (typeof key !== 'string' || key.trim() === '') { - continue; - } - - // Check if value is serializable (prevents injection) - try { - JSON.stringify(value); - } catch (e) { - continue; // Skip non-serializable values - } - - validatedContext[key.trim()] = value as any; - } - return { isValid: true, - data: validatedContext, + data: context, errors, }; } From e2eaf5f0b231deea7d88f9cadac307ee523e0f74 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:36:10 +0300 Subject: [PATCH 08/13] fix: correct import path for Sanitizer in validation utility --- src/utils/validation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 4195261..40716a1 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,5 +1,5 @@ import type { AffectedUser, EventContext } from '@hawk.so/types'; -import Sanitizer from 'src/modules/sanitizer'; +import Sanitizer from '../modules/sanitizer'; /** * Validation result interface From 8152f47f4570bb70802c4a8801f980f031b96b74 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:38:12 +0300 Subject: [PATCH 09/13] feat: add isObject method to Sanitizer for object type checking --- src/modules/sanitizer.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/modules/sanitizer.ts b/src/modules/sanitizer.ts index 030bc74..afad69b 100644 --- a/src/modules/sanitizer.ts +++ b/src/modules/sanitizer.ts @@ -7,6 +7,15 @@ * - represent class as or */ export default class Sanitizer { + /** + * Check if passed variable is an object + * + * @param target - variable to check + */ + public static isObject(target: any): boolean { + return Sanitizer.typeOf(target) === 'object'; + } + /** * Maximum string length */ @@ -148,15 +157,6 @@ export default class Sanitizer { return result; } - /** - * Check if passed variable is an object - * - * @param target - variable to check - */ - public static isObject(target: any): boolean { - return Sanitizer.typeOf(target) === 'object'; - } - /** * Check if passed variable is an array * From c78cc26ec077ea3c017380cdfbebc71c75b218c6 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:39:41 +0300 Subject: [PATCH 10/13] refactor: reorganize isObject method in Sanitizer for improved clarity --- src/modules/sanitizer.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/modules/sanitizer.ts b/src/modules/sanitizer.ts index afad69b..a02172d 100644 --- a/src/modules/sanitizer.ts +++ b/src/modules/sanitizer.ts @@ -7,15 +7,6 @@ * - represent class as or */ export default class Sanitizer { - /** - * Check if passed variable is an object - * - * @param target - variable to check - */ - public static isObject(target: any): boolean { - return Sanitizer.typeOf(target) === 'object'; - } - /** * Maximum string length */ @@ -37,6 +28,15 @@ export default class Sanitizer { */ private static readonly maxArrayLength: number = 10; + /** + * Check if passed variable is an object + * + * @param target - variable to check + */ + public static isObject(target: any): boolean { + return Sanitizer.typeOf(target) === 'object'; + } + /** * Apply sanitizing for array/object/primitives * From ab3e8ce0677934c1081ee72f64a510040e4aa5e2 Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:45:03 +0300 Subject: [PATCH 11/13] lint --- src/catcher.ts | 2 ++ src/utils/validation.ts | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/catcher.ts b/src/catcher.ts index 207a2c2..858646e 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -242,6 +242,7 @@ export default class Catcher { if (!validation.isValid) { logValidationErrors('setUser', validation.errors); + return; } @@ -265,6 +266,7 @@ export default class Catcher { if (!validation.isValid) { logValidationErrors('setContext', validation.errors); + return; } diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 40716a1..9536b63 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -12,19 +12,25 @@ interface ValidationResult { /** * Validates user data - basic security checks + * + * @param user */ export function validateUser(user: AffectedUser): ValidationResult { const errors: string[] = []; if (!user || !Sanitizer.isObject(user)) { errors.push('User must be an object'); - return { isValid: false, errors }; + + return { isValid: false, + errors }; } // Validate required ID if (!user.id || typeof user.id !== 'string' || user.id.trim() === '') { errors.push('User ID is required and must be a non-empty string'); - return { isValid: false, errors }; + + return { isValid: false, + errors }; } const validatedUser: AffectedUser = { @@ -53,13 +59,17 @@ export function validateUser(user: AffectedUser): ValidationResult /** * Validates context data - basic security checks + * + * @param context */ export function validateContext(context: EventContext): ValidationResult { const errors: string[] = []; if (!context || !Sanitizer.isObject(context)) { errors.push('Context must be an object'); - return { isValid: false, errors }; + + return { isValid: false, + errors }; } return { @@ -71,6 +81,9 @@ export function validateContext(context: EventContext): ValidationResult { From aa172bb85b990244840faa586fbe80d8b41e52be Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 19:59:36 +0300 Subject: [PATCH 12/13] refactor --- README.md | 3 -- example/index.html | 3 -- example/sample-errors.js | 18 ---------- src/catcher.ts | 25 +++----------- src/utils/validation.ts | 74 ++++++---------------------------------- 5 files changed, 16 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 1e3aa40..44fca79 100644 --- a/README.md +++ b/README.md @@ -142,9 +142,6 @@ hawk.setContext({ version: '2.1.0', environment: 'production', }); - -// Clear context data -hawk.clearContext(); ``` ## Source maps consuming diff --git a/example/index.html b/example/index.html index 13319a0..7b6f125 100644 --- a/example/index.html +++ b/example/index.html @@ -100,8 +100,6 @@

Hawk JavaScript Catcher

Send test event

-

-

Send real error

@@ -175,7 +173,6 @@

Context Management



-
diff --git a/example/sample-errors.js b/example/sample-errors.js index 6c1a6fc..6e8ab33 100644 --- a/example/sample-errors.js +++ b/example/sample-errors.js @@ -107,7 +107,6 @@ buttonClearUser.addEventListener('click', () => { * Context Management */ const buttonSetContext = document.getElementById('btn-set-context'); -const buttonClearContext = document.getElementById('btn-clear-context'); buttonSetContext.addEventListener('click', () => { const contextKey = document.getElementById('contextKey').value; @@ -124,20 +123,3 @@ buttonSetContext.addEventListener('click', () => { window.hawk.setContext(context); }); - -buttonClearContext.addEventListener('click', () => { - window.hawk.clearContext(); -}); - -/** - * Test without user - */ -const buttonTestWithoutUser = document.getElementById('btn-test-without-user'); - -buttonTestWithoutUser.addEventListener('click', () => { - // Clear user first to ensure no user is set - window.hawk.clearUser(); - - // Send error without user - window.hawk.send(new Error('Test error without user')); -}); diff --git a/src/catcher.ts b/src/catcher.ts index 858646e..623a593 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -17,7 +17,7 @@ import { EventRejectedError } from './errors'; import type { HawkJavaScriptEvent } from './types'; import { isErrorProcessed, markErrorAsProcessed } from './utils/event'; import { addErrorEvent, getConsoleLogStack, initConsoleCatcher } from './addons/consoleCatcher'; -import { validateUser, validateContext, logValidationErrors } from './utils/validation'; +import { validateUser, validateContext } from './utils/validation'; /** * Allow to use global VERSION, that will be overwritten by Webpack @@ -238,15 +238,11 @@ export default class Catcher { * @param user - New user information */ public setUser(user: AffectedUser): void { - const validation = validateUser(user); - - if (!validation.isValid) { - logValidationErrors('setUser', validation.errors); - + if (!validateUser(user)) { return; } - this.user = validation.data!; + this.user = user; } /** @@ -262,22 +258,11 @@ export default class Catcher { * @param context - New context data */ public setContext(context: EventContext): void { - const validation = validateContext(context); - - if (!validation.isValid) { - logValidationErrors('setContext', validation.errors); - + if (!validateContext(context)) { return; } - this.context = validation.data!; - } - - /** - * Clear current context data - */ - public clearContext(): void { - this.context = undefined; + this.context = context; } /** diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 9536b63..e6fff31 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,60 +1,27 @@ +import log from './log'; import type { AffectedUser, EventContext } from '@hawk.so/types'; import Sanitizer from '../modules/sanitizer'; -/** - * Validation result interface - */ -interface ValidationResult { - isValid: boolean; - data?: T; - errors: string[]; -} - /** * Validates user data - basic security checks * * @param user */ -export function validateUser(user: AffectedUser): ValidationResult { - const errors: string[] = []; - +export function validateUser(user: AffectedUser): boolean { if (!user || !Sanitizer.isObject(user)) { - errors.push('User must be an object'); + log('validateUser: User must be an object', 'warn'); - return { isValid: false, - errors }; + return false; } // Validate required ID if (!user.id || typeof user.id !== 'string' || user.id.trim() === '') { - errors.push('User ID is required and must be a non-empty string'); - - return { isValid: false, - errors }; - } - - const validatedUser: AffectedUser = { - id: user.id.trim(), - }; - - // Add optional fields if they exist and are strings - if (user.name && typeof user.name === 'string') { - validatedUser.name = user.name.trim(); - } - - if (user.url && typeof user.url === 'string') { - validatedUser.url = user.url.trim(); - } + log('validateUser: User ID is required and must be a non-empty string', 'warn'); - if (user.image && typeof user.image === 'string') { - validatedUser.image = user.image.trim(); + return false; } - return { - isValid: true, - data: validatedUser, - errors, - }; + return true; } /** @@ -62,31 +29,12 @@ export function validateUser(user: AffectedUser): ValidationResult * * @param context */ -export function validateContext(context: EventContext): ValidationResult { - const errors: string[] = []; - +export function validateContext(context: EventContext): boolean { if (!context || !Sanitizer.isObject(context)) { - errors.push('Context must be an object'); + log('validateContext: Context must be an object', 'warn'); - return { isValid: false, - errors }; + return false; } - return { - isValid: true, - data: context, - errors, - }; -} - -/** - * Logs validation errors - * - * @param prefix - * @param errors - */ -export function logValidationErrors(prefix: string, errors: string[]): void { - errors.forEach((error) => { - console.warn(`${prefix}: ${error}`); - }); + return true; } From d15b84bf13b45c68d1235fbabf867c2262ba557c Mon Sep 17 00:00:00 2001 From: Dobrunia Kostrigin <48620984+Dobrunia@users.noreply.github.com> Date: Tue, 14 Oct 2025 20:06:09 +0300 Subject: [PATCH 13/13] refactor: update user and context handling in Catcher class for improved clarity --- src/catcher.ts | 6 +++--- src/utils/validation.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/catcher.ts b/src/catcher.ts index 623a593..6b8d9b8 100644 --- a/src/catcher.ts +++ b/src/catcher.ts @@ -113,8 +113,8 @@ export default class Catcher { this.token = settings.token; this.debug = settings.debug || false; this.release = settings.release; - this.user = settings.user || Catcher.getGeneratedUser(); - this.context = settings.context || undefined; + this.setUser(settings.user || Catcher.getGeneratedUser()); + this.setContext(settings.context || undefined); this.beforeSend = settings.beforeSend; this.disableVueErrorHandler = settings.disableVueErrorHandler !== null && settings.disableVueErrorHandler !== undefined ? settings.disableVueErrorHandler : false; this.consoleTracking = settings.consoleTracking !== null && settings.consoleTracking !== undefined ? settings.consoleTracking : true; @@ -257,7 +257,7 @@ export default class Catcher { * * @param context - New context data */ - public setContext(context: EventContext): void { + public setContext(context: EventContext | undefined): void { if (!validateContext(context)) { return; } diff --git a/src/utils/validation.ts b/src/utils/validation.ts index e6fff31..468177f 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -29,7 +29,7 @@ export function validateUser(user: AffectedUser): boolean { * * @param context */ -export function validateContext(context: EventContext): boolean { +export function validateContext(context: EventContext | undefined): boolean { if (!context || !Sanitizer.isObject(context)) { log('validateContext: Context must be an object', 'warn');