Skip to content

Commit 8d475b1

Browse files
authored
ref(utils): Clean up dangerous type casts in object helper file (#5047)
1 parent 8b16378 commit 8d475b1

File tree

5 files changed

+66
-42
lines changed

5 files changed

+66
-42
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: 50 additions & 38 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,59 @@ 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>(
103+
value: V,
104+
):
105+
| {
106+
[ownProps: string]: unknown;
107+
type: string;
108+
target: string;
109+
currentTarget: string;
110+
detail?: unknown;
111+
}
112+
| {
113+
[ownProps: string]: unknown;
114+
message: string;
115+
name: string;
116+
stack?: string;
117+
}
118+
| V {
107119
if (isError(value)) {
108-
newObj = {
120+
return {
109121
message: value.message,
110122
name: value.name,
111123
stack: value.stack,
112-
...getOwnProperties(value as ExtendedError),
124+
...getOwnProperties(value),
113125
};
114126
} else if (isEvent(value)) {
115-
/**
116-
* Event-like interface that's usable in browser and node
117-
*/
118-
interface SimpleEvent {
119-
[key: string]: unknown;
127+
const newObj: {
128+
[ownProps: string]: unknown;
120129
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),
130+
target: string;
131+
currentTarget: string;
132+
detail?: unknown;
133+
} = {
134+
type: value.type,
135+
target: serializeEventTarget(value.target),
136+
currentTarget: serializeEventTarget(value.currentTarget),
137+
...getOwnProperties(value),
132138
};
133139

134140
if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {
135-
newObj.detail = event.detail;
141+
newObj.detail = value.detail;
136142
}
143+
144+
return newObj;
145+
} else {
146+
return value;
137147
}
138-
return newObj;
139148
}
140149

141150
/** Creates a string representation of the target of an `Event` object */
@@ -148,23 +157,26 @@ function serializeEventTarget(target: unknown): string {
148157
}
149158

150159
/** 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];
160+
function getOwnProperties(obj: unknown): { [key: string]: unknown } {
161+
if (typeof obj === 'object' && obj !== null) {
162+
const extractedProps: { [key: string]: unknown } = {};
163+
for (const property in obj) {
164+
if (Object.prototype.hasOwnProperty.call(obj, property)) {
165+
extractedProps[property] = (obj as Record<string, unknown>)[property];
166+
}
156167
}
168+
return extractedProps;
169+
} else {
170+
return {};
157171
}
158-
return extractedProps;
159172
}
160173

161174
/**
162175
* Given any captured exception, extract its keys and create a sorted
163176
* and truncated list that will be used inside the event message.
164177
* eg. `Non-error exception captured with keys: foo, bar, baz`
165178
*/
166-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
167-
export function extractExceptionKeysForMessage(exception: any, maxLength: number = 40): string {
179+
export function extractExceptionKeysForMessage(exception: Record<string, unknown>, maxLength: number = 40): string {
168180
const keys = Object.keys(convertToPlainObject(exception));
169181
keys.sort();
170182

0 commit comments

Comments
 (0)