Skip to content

Commit 1bf5bec

Browse files
committed
refactor: replace hasRequiredEventFields with isValidEventPayload for payload validation
1 parent 1b86076 commit 1bf5bec

2 files changed

Lines changed: 44 additions & 40 deletions

File tree

src/index.ts

Lines changed: 10 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,11 @@ import type {
1414
} from '@hawk.so/types';
1515
import EventPayload from './modules/event.js';
1616
import { BreadcrumbManager, type BreadcrumbInput, type BreadcrumbHint } from './modules/breadcrumbs.js';
17+
import { isValidEventPayload } from './modules/validate-event.js';
1718
import type { AxiosResponse } from 'axios';
1819
import axios from 'axios';
1920
import { VERSION } from './version.js';
2021

21-
/**
22-
* Checks if value is a plain object (not array, Date, etc.)
23-
*/
24-
function isPlainObject(v: unknown): v is Record<string, unknown> {
25-
return Object.prototype.toString.call(v) === '[object Object]';
26-
}
27-
28-
/**
29-
* Minimal required fields check:
30-
* - payload must be a plain object
31-
* - payload.title must be a non-empty string
32-
*/
33-
function hasRequiredEventFields(v: unknown): v is { title: string } {
34-
if (!isPlainObject(v)) {
35-
return false;
36-
}
37-
38-
return typeof v.title === 'string' && v.title.trim() !== '';
39-
}
40-
4122
/**
4223
* Class for throwing errors inside unhandledRejection processor
4324
*/
@@ -290,31 +271,20 @@ class Catcher {
290271
}
291272

292273
/**
293-
* If user returned nothing — keep original payload
274+
* If user returned a value — use it; if undefined (no return / in-place mutation) — keep payload reference
294275
*/
295-
if (result !== undefined) {
296-
/**
297-
* Accept only payloads that still have required fields (minimal check)
298-
*/
299-
if (hasRequiredEventFields(result)) {
300-
payload = result as EventData<NodeJSAddons>;
301-
} else {
302-
console.warn(
303-
`[Hawk] beforeSend returned invalid payload. ` +
304-
`Keeping original payload. Received: ${Object.prototype.toString.call(result)}`
305-
);
306-
}
307-
}
276+
const candidate = result ?? payload;
308277

309-
/**
310-
* Final safety check:
311-
* protects from in-place mutation of `payload` when beforeSend returns undefined
312-
*/
313-
if (!hasRequiredEventFields(payload)) {
314-
console.warn('[Hawk] payload corrupted after beforeSend, event dropped');
278+
if (!isValidEventPayload(candidate)) {
279+
console.warn(
280+
'[Hawk] beforeSend produced invalid payload (missing required fields), event dropped. '
281+
+ `Received: ${Object.prototype.toString.call(candidate)}`
282+
);
315283

316284
return;
317285
}
286+
287+
payload = candidate;
318288
}
319289

320290
void this.sendErrorFormatted({

src/modules/validate-event.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type { EventData, NodeJSAddons } from '@hawk.so/types';
2+
3+
/**
4+
* Checks if value is a plain object (not array, Date, etc.)
5+
*
6+
* @param v - value to check
7+
*/
8+
function isPlainObject(v: unknown): v is Record<string, unknown> {
9+
return Object.prototype.toString.call(v) === '[object Object]';
10+
}
11+
12+
/**
13+
* Runtime check for required EventData fields.
14+
* Per @hawk.so/types EventData, `title` is the only non-optional field.
15+
* Additionally validates `backtrace` shape if present (must be an array).
16+
*
17+
* @param v - value to validate
18+
*/
19+
// eslint-disable-next-line jsdoc/require-jsdoc -- type guard documented above
20+
export function isValidEventPayload(v: unknown): v is EventData<NodeJSAddons> {
21+
if (!isPlainObject(v)) {
22+
return false;
23+
}
24+
25+
if (typeof v.title !== 'string' || v.title.trim() === '') {
26+
return false;
27+
}
28+
29+
if (v.backtrace !== undefined && !Array.isArray(v.backtrace)) {
30+
return false;
31+
}
32+
33+
return true;
34+
}

0 commit comments

Comments
 (0)