|
1 |
| -import { Backend, DSN, Options, SentryError } from '../../core/dist'; |
2 |
| -import { addBreadcrumb, captureEvent } from '../../minimal/dist'; |
3 |
| -import { SentryEvent, SentryResponse, StackFrame } from '../../types/dist'; |
4 |
| -import { supportsFetch } from '../../utils/supports'; |
5 |
| -import { Raven } from './raven'; |
| 1 | +import { Backend, DSN, Options, SentryError } from '@sentry/core'; |
| 2 | +import { SentryEvent, SentryResponse } from '@sentry/types'; |
6 | 3 | import {
|
7 |
| - StackFrame as TraceKitStackFrame, |
8 |
| - StackTrace as TraceKitStackTrace, |
9 |
| -} from './tracekit'; |
| 4 | + isDOMError, |
| 5 | + isDOMException, |
| 6 | + isError, |
| 7 | + isErrorEvent, |
| 8 | + isPlainObject, |
| 9 | +} from '@sentry/utils/is'; |
| 10 | +import { supportsFetch } from '@sentry/utils/supports'; |
| 11 | +import { |
| 12 | + eventFromStacktrace, |
| 13 | + getEventOptionsFromPlainObject, |
| 14 | + prepareFramesForEvent, |
| 15 | +} from './parsers'; |
| 16 | +import { computeStackTrace } from './tracekit'; |
10 | 17 | import { FetchTransport, XHRTransport } from './transports';
|
11 | 18 |
|
12 |
| -const STACKTRACE_LIMIT = 50; |
13 |
| - |
14 | 19 | /**
|
15 | 20 | * Configuration options for the Sentry Browser SDK.
|
16 | 21 | * @see BrowserClient for more information.
|
@@ -86,34 +91,108 @@ export class BrowserBackend implements Backend {
|
86 | 91 | * @inheritDoc
|
87 | 92 | */
|
88 | 93 | public async eventFromException(exception: any): Promise<SentryEvent> {
|
89 |
| - const originalSend = Raven._sendProcessedPayload; |
90 |
| - try { |
91 |
| - let event!: SentryEvent; |
92 |
| - Raven._sendProcessedPayload = evt => { |
93 |
| - event = evt; |
94 |
| - }; |
95 |
| - Raven.captureException(exception); |
96 |
| - return event; |
97 |
| - } finally { |
98 |
| - Raven._sendProcessedPayload = originalSend; |
| 94 | + if (isErrorEvent(exception) && exception.error) { |
| 95 | + // If it is an ErrorEvent with `error` property, extract it to get actual Error |
| 96 | + exception = exception.error; // tslint:disable-line:no-parameter-reassignment |
| 97 | + } else if (isDOMError(exception) || isDOMException(exception)) { |
| 98 | + // If it is a DOMError or DOMException (which are legacy APIs, but still supported in some browsers) |
| 99 | + // then we just extract the name and message, as they don't provide anything else |
| 100 | + // https://developer.mozilla.org/en-US/docs/Web/API/DOMError |
| 101 | + // https://developer.mozilla.org/en-US/docs/Web/API/DOMException |
| 102 | + const name = |
| 103 | + exception.name || (isDOMError(exception) ? 'DOMError' : 'DOMException'); |
| 104 | + const message = exception.message |
| 105 | + ? `${name}: ${exception.message}` |
| 106 | + : name; |
| 107 | + |
| 108 | + return this.eventFromMessage(message); |
| 109 | + } else if (isError(exception)) { |
| 110 | + // we have a real Error object, do nothing |
| 111 | + } else if (isPlainObject(exception)) { |
| 112 | + // If it is plain Object, serialize it manually and extract options |
| 113 | + // This will allow us to group events based on top-level keys |
| 114 | + // which is much better than creating new group when any key/value change |
| 115 | + const options = getEventOptionsFromPlainObject(exception); |
| 116 | + exception = new Error(options.message); // tslint:disable-line:no-parameter-reassignment |
| 117 | + } else { |
| 118 | + // If none of previous checks were valid, then it means that |
| 119 | + // it's not a DOMError/DOMException |
| 120 | + // it's not a plain Object |
| 121 | + // it's not a valid ErrorEvent (one with an error property) |
| 122 | + // it's not an Error |
| 123 | + // So bail out and capture it as a simple message: |
| 124 | + return this.eventFromMessage(exception); |
99 | 125 | }
|
| 126 | + |
| 127 | + // TODO: Create `shouldDropEvent` method to gather all user-options |
| 128 | + |
| 129 | + const event = eventFromStacktrace(computeStackTrace(exception)); |
| 130 | + |
| 131 | + return { |
| 132 | + ...event, |
| 133 | + exception: { |
| 134 | + ...event.exception, |
| 135 | + mechanism: { |
| 136 | + handled: true, |
| 137 | + type: 'generic', |
| 138 | + }, |
| 139 | + }, |
| 140 | + }; |
100 | 141 | }
|
101 | 142 |
|
102 | 143 | /**
|
103 | 144 | * @inheritDoc
|
104 | 145 | */
|
105 | 146 | public async eventFromMessage(message: string): Promise<SentryEvent> {
|
106 |
| - const originalSend = Raven._sendProcessedPayload; |
| 147 | + message = String(message); // tslint:disable-line:no-parameter-reassignment |
| 148 | + |
| 149 | + // Generate a "synthetic" stack trace from this point. |
| 150 | + // NOTE: If you are a Sentry user, and you are seeing this stack frame, it is NOT indicative |
| 151 | + // of a bug with Raven.js. Sentry generates synthetic traces either by configuration, |
| 152 | + // or if it catches a thrown object without a "stack" property. |
| 153 | + // Neither DOMError or DOMException provide stacktrace and we most likely wont get it this way as well |
| 154 | + // but it's barely any overhead so we may at least try |
| 155 | + let syntheticException: Error; |
107 | 156 | try {
|
108 |
| - let event!: SentryEvent; |
109 |
| - Raven._sendProcessedPayload = evt => { |
110 |
| - event = evt; |
111 |
| - }; |
112 |
| - Raven.captureMessage(message); |
113 |
| - return event; |
114 |
| - } finally { |
115 |
| - Raven._sendProcessedPayload = originalSend; |
| 157 | + throw new Error(message); |
| 158 | + } catch (exception) { |
| 159 | + syntheticException = exception; |
| 160 | + // null exception name so `Error` isn't prefixed to msg |
| 161 | + (syntheticException as any).name = null; // tslint:disable-line:no-null-keyword |
116 | 162 | }
|
| 163 | + |
| 164 | + const stacktrace = computeStackTrace(syntheticException); |
| 165 | + const frames = prepareFramesForEvent(stacktrace.stack); |
| 166 | + |
| 167 | + return { |
| 168 | + fingerprint: [message], |
| 169 | + message, |
| 170 | + stacktrace: { |
| 171 | + frames, |
| 172 | + }, |
| 173 | + }; |
| 174 | + |
| 175 | + // TODO: Revisit ignoreUrl behavior |
| 176 | + |
| 177 | + // Since we know this is a synthetic trace, the top frame (this function call) |
| 178 | + // MUST be from Raven.js, so mark it for trimming |
| 179 | + // We add to the trim counter so that callers can choose to trim extra frames, such |
| 180 | + // as utility functions. |
| 181 | + |
| 182 | + // stack[0] is `throw new Error(msg)` call itself, we are interested in the frame that was just before that, stack[1] |
| 183 | + // let initialCall = Array.isArray(stack.stack) && stack.stack[1]; |
| 184 | + |
| 185 | + // if stack[1] is `eventFromException`, it means that someone passed a string to it and we redirected that call |
| 186 | + // to be handled by `eventFromMessage`, thus `initialCall` is the 3rd one, not 2nd |
| 187 | + // initialCall => captureException(string) => captureMessage(string) |
| 188 | + // TODO: Verify if this is actually a correct name |
| 189 | + // if (initialCall && initialCall.func === 'eventFromException') { |
| 190 | + // initialCall = stack.stack[2]; |
| 191 | + // } |
| 192 | + |
| 193 | + // const fileurl = (initialCall && initialCall.url) || ''; |
| 194 | + |
| 195 | + // TODO: Create `shouldDropEvent` method to gather all user-options |
117 | 196 | }
|
118 | 197 |
|
119 | 198 | /**
|
@@ -152,66 +231,4 @@ export class BrowserBackend implements Backend {
|
152 | 231 | public storeScope(): void {
|
153 | 232 | // Noop
|
154 | 233 | }
|
155 |
| - |
156 |
| - private prepareFrames( |
157 |
| - stackInfo: TraceKitStackTrace, |
158 |
| - options: { |
159 |
| - trimHeadFrames: number; |
160 |
| - } = { |
161 |
| - trimHeadFrames: 0, |
162 |
| - }, |
163 |
| - ): StackFrame[] { |
164 |
| - if (stackInfo.stack && stackInfo.stack.length) { |
165 |
| - const frames = stackInfo.stack.map((frame: TraceKitStackFrame) => |
166 |
| - this.normalizeFrame(frame, stackInfo.url), |
167 |
| - ); |
168 |
| - |
169 |
| - // e.g. frames captured via captureMessage throw |
170 |
| - for (let j = 0; j < options.trimHeadFrames && j < frames.length; j++) { |
171 |
| - frames[j].in_app = false; |
172 |
| - } |
173 |
| - |
174 |
| - return frames.slice(0, STACKTRACE_LIMIT); |
175 |
| - } else { |
176 |
| - return []; |
177 |
| - } |
178 |
| - } |
179 |
| - |
180 |
| - /** |
181 |
| - * @inheritDoc |
182 |
| - */ |
183 |
| - private normalizeFrame( |
184 |
| - frame: TraceKitStackFrame, |
185 |
| - stackInfoUrl: string, |
186 |
| - ): StackFrame { |
187 |
| - // normalize the frames data |
188 |
| - const normalized = { |
189 |
| - colno: frame.column, |
190 |
| - filename: frame.url, |
191 |
| - function: frame.func || '?', |
192 |
| - in_app: true, |
193 |
| - lineno: frame.line, |
194 |
| - }; |
195 |
| - |
196 |
| - // Case when we don't have any information about the error |
197 |
| - // E.g. throwing a string or raw object, instead of an `Error` in Firefox |
198 |
| - // Generating synthetic error doesn't add any value here |
199 |
| - // |
200 |
| - // We should probably somehow let a user know that they should fix their code |
201 |
| - if (!frame.url) { |
202 |
| - normalized.filename = stackInfoUrl; // fallback to whole stacks url from onerror handler |
203 |
| - } |
204 |
| - |
205 |
| - // TODO: This has to be fixed |
206 |
| - // determine if an exception came from outside of our app |
207 |
| - // first we check the global includePaths list. |
208 |
| - // Now we check for fun, if the function name is Raven or TraceKit |
209 |
| - // finally, we do a last ditch effort and check for raven.min.js |
210 |
| - normalized.in_app = !( |
211 |
| - /(Sentry|TraceKit)\./.test(normalized.function) || |
212 |
| - /raven\.(min\.)?js$/.test(normalized.filename) |
213 |
| - ); |
214 |
| - |
215 |
| - return normalized; |
216 |
| - } |
217 | 234 | }
|
0 commit comments