Skip to content

Commit 78f733e

Browse files
author
Luca Forstner
committed
ref: Clean up dangerous type casts in object helper file
1 parent 1e210fe commit 78f733e

File tree

5 files changed

+43
-46
lines changed

5 files changed

+43
-46
lines changed

packages/types/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export type { Mechanism } from './mechanism';
3131
export type { ExtractedNodeRequestData, Primitive, WorkerLocation } from './misc';
3232
export type { ClientOptions, Options } from './options';
3333
export type { Package } from './package';
34+
export type { PolymorphicEvent } from './polymorphics';
3435
export type { QueryParams, Request } from './request';
3536
export type { Runtime } from './runtime';
3637
export type { CaptureContext, Scope, ScopeContext } from './scope';

packages/types/src/polymorphics.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Event-like interface that's usable in browser and node.
3+
*
4+
* Property availability taken from https://developer.mozilla.org/en-US/docs/Web/API/Event#browser_compatibility
5+
*/
6+
export interface PolymorphicEvent {
7+
[key: string]: unknown;
8+
readonly type: string;
9+
readonly target?: unknown;
10+
readonly currentTarget?: unknown;
11+
}

packages/utils/src/is.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
33

4-
import { Primitive } from '@sentry/types';
4+
import { PolymorphicEvent, Primitive } from '@sentry/types';
55

66
// eslint-disable-next-line @typescript-eslint/unbound-method
77
const objectToString = Object.prototype.toString;
@@ -101,7 +101,7 @@ export function isPlainObject(wat: unknown): wat is Record<string, unknown> {
101101
* @param wat A value to be checked.
102102
* @returns A boolean representing the result.
103103
*/
104-
export function isEvent(wat: unknown): boolean {
104+
export function isEvent(wat: unknown): wat is PolymorphicEvent {
105105
return typeof Event !== 'undefined' && isInstanceOf(wat, Event);
106106
}
107107

packages/utils/src/normalize.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Primitive } from '@sentry/types';
22

3-
import { isError, isEvent, isNaN, isSyntheticEvent } from './is';
3+
import { isNaN, isSyntheticEvent } from './is';
44
import { memoBuilder, MemoFunc } from './memo';
55
import { convertToPlainObject } from './object';
66
import { getFunctionName } from './stacktrace';
@@ -117,7 +117,7 @@ function visit(
117117

118118
// Before we begin, convert`Error` and`Event` instances into plain objects, since some of each of their relevant
119119
// properties are non-enumerable and otherwise would get missed.
120-
const visitable = (isError(value) || isEvent(value) ? convertToPlainObject(value) : value) as ObjOrArray<unknown>;
120+
const visitable = convertToPlainObject(value as ObjOrArray<unknown>);
121121

122122
for (const visitKey in visitable) {
123123
// Avoid iterating over fields in the prototype if they've somehow been exposed to enumeration.

packages/utils/src/object.ts

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-lines */
22
/* eslint-disable @typescript-eslint/no-explicit-any */
3-
import { ExtendedError, WrappedFunction } from '@sentry/types';
3+
import { WrappedFunction } from '@sentry/types';
44

55
import { htmlTreeAsString } from './browser';
66
import { isElement, isError, isEvent, isInstanceOf, isPlainObject, isPrimitive } from './is';
@@ -92,50 +92,32 @@ export function urlEncode(object: { [key: string]: any }): string {
9292
}
9393

9494
/**
95-
* Transforms any object into an object literal with all its attributes
96-
* attached to it.
95+
* Transforms any `Error` or `Event` into a plain object with all of their enumerable properties, and some of their
96+
* non-enumerable properties attached.
9797
*
9898
* @param value Initial source that we have to transform in order for it to be usable by the serializer
99+
* @returns An Event or Error turned into an object - or the value argurment itself, when value is neither an Event nor
100+
* an Error.
99101
*/
100-
export function convertToPlainObject(value: unknown): {
101-
[key: string]: unknown;
102-
} {
103-
let newObj = value as {
104-
[key: string]: unknown;
105-
};
106-
102+
export function convertToPlainObject<V extends unknown>(value: V): Record<string, unknown> | V {
107103
if (isError(value)) {
108-
newObj = {
104+
return {
109105
message: value.message,
110106
name: value.name,
111107
stack: value.stack,
112-
...getOwnProperties(value as ExtendedError),
108+
...getOwnProperties(value),
113109
};
114110
} else if (isEvent(value)) {
115-
/**
116-
* Event-like interface that's usable in browser and node
117-
*/
118-
interface SimpleEvent {
119-
[key: string]: unknown;
120-
type: string;
121-
target?: unknown;
122-
currentTarget?: unknown;
123-
}
124-
125-
const event = value as SimpleEvent;
126-
127-
newObj = {
128-
type: event.type,
129-
target: serializeEventTarget(event.target),
130-
currentTarget: serializeEventTarget(event.currentTarget),
131-
...getOwnProperties(event),
111+
return {
112+
type: value.type,
113+
target: serializeEventTarget(value.target),
114+
currentTarget: serializeEventTarget(value.currentTarget),
115+
...getOwnProperties(value),
116+
detail: typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent) ? value.detail : undefined,
132117
};
133-
134-
if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {
135-
newObj.detail = event.detail;
136-
}
118+
} else {
119+
return value;
137120
}
138-
return newObj;
139121
}
140122

141123
/** Creates a string representation of the target of an `Event` object */
@@ -148,23 +130,26 @@ function serializeEventTarget(target: unknown): string {
148130
}
149131

150132
/** Filters out all but an object's own properties */
151-
function getOwnProperties(obj: { [key: string]: unknown }): { [key: string]: unknown } {
152-
const extractedProps: { [key: string]: unknown } = {};
153-
for (const property in obj) {
154-
if (Object.prototype.hasOwnProperty.call(obj, property)) {
155-
extractedProps[property] = obj[property];
133+
function getOwnProperties(obj: unknown): { [key: string]: unknown } {
134+
if (typeof obj === 'object' && obj !== null) {
135+
const extractedProps: { [key: string]: unknown } = {};
136+
for (const property in obj) {
137+
if (Object.prototype.hasOwnProperty.call(obj, property)) {
138+
extractedProps[property] = (obj as Record<string, unknown>)[property];
139+
}
156140
}
141+
return extractedProps;
142+
} else {
143+
return {};
157144
}
158-
return extractedProps;
159145
}
160146

161147
/**
162148
* Given any captured exception, extract its keys and create a sorted
163149
* and truncated list that will be used inside the event message.
164150
* eg. `Non-error exception captured with keys: foo, bar, baz`
165151
*/
166-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
167-
export function extractExceptionKeysForMessage(exception: any, maxLength: number = 40): string {
152+
export function extractExceptionKeysForMessage(exception: Record<string, unknown>, maxLength: number = 40): string {
168153
const keys = Object.keys(convertToPlainObject(exception));
169154
keys.sort();
170155

0 commit comments

Comments
 (0)