Skip to content

Commit d8023f9

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

File tree

5 files changed

+45
-43
lines changed

5 files changed

+45
-43
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: 29 additions & 39 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,37 @@ 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+
const newObj: Record<string, unknown> = {
112+
type: value.type,
113+
target: serializeEventTarget(value.target),
114+
currentTarget: serializeEventTarget(value.currentTarget),
115+
...getOwnProperties(value),
132116
};
133117

134118
if (typeof CustomEvent !== 'undefined' && isInstanceOf(value, CustomEvent)) {
135-
newObj.detail = event.detail;
119+
newObj.detail = value.detail;
136120
}
121+
122+
return newObj;
123+
} else {
124+
return value;
137125
}
138-
return newObj;
139126
}
140127

141128
/** Creates a string representation of the target of an `Event` object */
@@ -148,23 +135,26 @@ function serializeEventTarget(target: unknown): string {
148135
}
149136

150137
/** 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];
138+
function getOwnProperties(obj: unknown): { [key: string]: unknown } {
139+
if (typeof obj === 'object' && obj !== null) {
140+
const extractedProps: { [key: string]: unknown } = {};
141+
for (const property in obj) {
142+
if (Object.prototype.hasOwnProperty.call(obj, property)) {
143+
extractedProps[property] = (obj as Record<string, unknown>)[property];
144+
}
156145
}
146+
return extractedProps;
147+
} else {
148+
return {};
157149
}
158-
return extractedProps;
159150
}
160151

161152
/**
162153
* Given any captured exception, extract its keys and create a sorted
163154
* and truncated list that will be used inside the event message.
164155
* eg. `Non-error exception captured with keys: foo, bar, baz`
165156
*/
166-
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
167-
export function extractExceptionKeysForMessage(exception: any, maxLength: number = 40): string {
157+
export function extractExceptionKeysForMessage(exception: Record<string, unknown>, maxLength: number = 40): string {
168158
const keys = Object.keys(convertToPlainObject(exception));
169159
keys.sort();
170160

0 commit comments

Comments
 (0)