-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
hub.ts
401 lines (366 loc) · 11.8 KB
/
hub.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
import {
Breadcrumb,
Integration,
IntegrationClass,
SentryBreadcrumbHint,
SentryEvent,
SentryEventHint,
Severity,
} from '@sentry/types';
import { logger } from '@sentry/utils/logger';
import { dynamicRequire, getGlobalObject, uuid4 } from '@sentry/utils/misc';
import { Carrier, Layer } from './interfaces';
import { Scope } from './scope';
declare module 'domain' {
export let active: Domain;
/**
* Extension for domain interface
*/
export interface Domain {
__SENTRY__?: Carrier;
}
}
/**
* API compatibility version of this hub.
*
* WARNING: This number should only be incresed when the global interface
* changes a and new methods are introduced.
*/
export const API_VERSION = 3;
/**
* Internal class used to make sure we always have the latest internal functions
* working in case we have a version conflict.
*/
export class Hub {
/** Is a {@link Layer}[] containing the client and scope */
private readonly stack: Layer[] = [];
/** Contains the last event id of a captured event. */
private _lastEventId?: string;
/**
* Creates a new instance of the hub, will push one {@link Layer} into the
* internal stack on creation.
*
* @param client bound to the hub.
* @param scope bound to the hub.
* @param version number, higher number means higher priority.
*/
public constructor(client?: any, scope: Scope = new Scope(), private readonly version: number = API_VERSION) {
this.stack.push({ client, scope });
}
/**
* Internal helper function to call a method on the top client if it exists.
*
* @param method The method to call on the client/client.
* @param args Arguments to pass to the client/frontend.
*/
private invokeClient(method: string, ...args: any[]): void {
const top = this.getStackTop();
if (top && top.client && top.client[method]) {
top.client[method](...args, top.scope);
}
}
/**
* Internal helper function to call an async method on the top client if it
* exists.
*
* @param method The method to call on the client/client.
* @param args Arguments to pass to the client/frontend.
*/
private invokeClientAsync(method: string, ...args: any[]): void {
const top = this.getStackTop();
if (top && top.client && top.client[method]) {
top.client[method](...args, top.scope).catch((err: any) => {
logger.error(err);
});
}
}
/**
* Checks if this hub's version is older than the given version.
*
* @param version A version number to compare to.
* @return True if the given version is newer; otherwise false.
*/
public isOlderThan(version: number): boolean {
return this.version < version;
}
/**
* This binds the given client to the current scope.
* @param client An SDK client (client) instance.
*/
public bindClient(client?: any): void {
const top = this.getStackTop();
top.client = client;
if (top && top.scope && client) {
top.scope.addScopeListener((s: Scope) => {
if (client.getBackend) {
try {
client.getBackend().storeScope(s);
} catch {
// Do nothing
}
}
});
}
}
/**
* Create a new scope to store context information.
*
* The scope will be layered on top of the current one. It is isolated, i.e. all
* breadcrumbs and context information added to this scope will be removed once
* the scope ends. Be sure to always remove this scope with {@link this.popScope}
* when the operation finishes or throws.
*
* @returns Scope, the new cloned scope
*/
public pushScope(): Scope {
// We want to clone the content of prev scope
const stack = this.getStack();
const parentScope = stack.length > 0 ? stack[stack.length - 1].scope : undefined;
const scope = Scope.clone(parentScope);
this.getStack().push({
client: this.getClient(),
scope,
});
return scope;
}
/**
* Removes a previously pushed scope from the stack.
*
* This restores the state before the scope was pushed. All breadcrumbs and
* context information added since the last call to {@link this.pushScope} are
* discarded.
*/
public popScope(): boolean {
return this.getStack().pop() !== undefined;
}
/**
* Creates a new scope with and executes the given operation within.
* The scope is automatically removed once the operation
* finishes or throws.
*
* This is essentially a convenience function for:
*
* pushScope();
* callback();
* popScope();
*
* @param callback that will be enclosed into push/popScope.
*/
public withScope(callback: ((scope: Scope) => void)): void {
const scope = this.pushScope();
try {
callback(scope);
} finally {
this.popScope();
}
}
/** Returns the client of the top stack. */
public getClient(): any | undefined {
return this.getStackTop().client;
}
/** Returns the scope of the top stack. */
public getScope(): Scope | undefined {
return this.getStackTop().scope;
}
/** Returns the scope stack for domains or the process. */
public getStack(): Layer[] {
return this.stack;
}
/** Returns the topmost scope layer in the order domain > local > process. */
public getStackTop(): Layer {
return this.stack[this.stack.length - 1];
}
/**
* Captures an exception event and sends it to Sentry.
*
* @param exception An exception-like object.
* @param hint May contain additional information about the original exception.
* @returns The generated eventId.
*/
public captureException(exception: any, hint?: SentryEventHint): string {
const eventId = (this._lastEventId = uuid4());
this.invokeClientAsync('captureException', exception, {
...hint,
event_id: eventId,
});
return eventId;
}
/**
* Captures a message event and sends it to Sentry.
*
* @param message The message to send to Sentry.
* @param level Define the level of the message.
* @param hint May contain additional information about the original exception.
* @returns The generated eventId.
*/
public captureMessage(message: string, level?: Severity, hint?: SentryEventHint): string {
const eventId = (this._lastEventId = uuid4());
this.invokeClientAsync('captureMessage', message, level, {
...hint,
event_id: eventId,
});
return eventId;
}
/**
* Captures a manually created event and sends it to Sentry.
*
* @param event The event to send to Sentry.
* @param hint May contain additional information about the original exception.
*/
public captureEvent(event: SentryEvent, hint?: SentryEventHint): string {
const eventId = (this._lastEventId = uuid4());
this.invokeClientAsync('captureEvent', event, {
...hint,
event_id: eventId,
});
return eventId;
}
/**
* This is the getter for lastEventId.
*
* @returns The last event id of a captured event.
*/
public lastEventId(): string | undefined {
return this._lastEventId;
}
/**
* Records a new breadcrumb which will be attached to future events.
*
* Breadcrumbs will be added to subsequent events to provide more context on
* user's actions prior to an error or crash.
*
* @param breadcrumb The breadcrumb to record.
* @param hint May contain additional information about the original breadcrumb.
*/
public addBreadcrumb(breadcrumb: Breadcrumb, hint?: SentryBreadcrumbHint): void {
this.invokeClient('addBreadcrumb', breadcrumb, { ...hint });
}
/**
* Callback to set context information onto the scope.
*
* @param callback Callback function that receives Scope.
*/
public configureScope(callback: (scope: Scope) => void): void {
const top = this.getStackTop();
if (top.scope && top.client) {
// TODO: freeze flag
callback(top.scope);
}
}
/**
* For the duraction of the callback, this hub will be set as the global current Hub.
* This function is useful if you want to run your own client and hook into an already initialized one
* e.g.: Reporting issues to your own sentry when running in your component while still using the users configuration.
*/
public run(callback: ((hub: Hub) => void)): void {
const oldHub = makeMain(this);
try {
callback(this);
} finally {
makeMain(oldHub);
}
}
/** Returns the integration if installed on the current client. */
public getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null {
try {
return this.getClient().getIntegration(integration);
} catch (_oO) {
logger.warn(`Cannot retrieve integration ${integration.id} from the current Hub`);
return null;
}
}
}
/** Returns the global shim registry. */
export function getMainCarrier(): Carrier {
const carrier: any = getGlobalObject();
carrier.__SENTRY__ = carrier.__SENTRY__ || {
hub: undefined,
};
return carrier;
}
/**
* Replaces the current main hub with the passed one on the global object
*
* @returns The old replaced hub
*/
export function makeMain(hub: Hub): Hub {
const registry = getMainCarrier();
const oldHub = getHubFromCarrier(registry);
setHubOnCarrier(registry, hub);
return oldHub;
}
/**
* Returns the default hub instance.
*
* If a hub is already registered in the global carrier but this module
* contains a more recent version, it replaces the registered version.
* Otherwise, the currently registered hub will be returned.
*/
export function getCurrentHub(): Hub {
// Get main carrier (global for every environment)
const registry = getMainCarrier();
// If there's no hub, or its an old API, assign a new one
if (!hasHubOnCarrier(registry) || getHubFromCarrier(registry).isOlderThan(API_VERSION)) {
setHubOnCarrier(registry, new Hub());
}
// Prefer domains over global if they are there
try {
// We need to use `dynamicRequire` because `require` on it's own will be optimized by webpack.
// We do not want this to happen, we need to try to `require` the domain node module and fail if we are in browser
// for example so we do not have to shim it and use `getCurrentHub` universally.
const domain = dynamicRequire(module, 'domain');
const activeDomain = domain.active;
// If there no active domain, just return global hub
if (!activeDomain) {
return getHubFromCarrier(registry);
}
// If there's no hub on current domain, or its an old API, assign a new one
if (!hasHubOnCarrier(activeDomain) || getHubFromCarrier(activeDomain).isOlderThan(API_VERSION)) {
const registryHubTopStack = getHubFromCarrier(registry).getStackTop();
setHubOnCarrier(activeDomain, new Hub(registryHubTopStack.client, Scope.clone(registryHubTopStack.scope)));
}
// Return hub that lives on a domain
return getHubFromCarrier(activeDomain);
} catch (_Oo) {
// Return hub that lives on a global object
return getHubFromCarrier(registry);
}
}
/**
* This will tell whether a carrier has a hub on it or not
* @param carrier object
*/
export function hasHubOnCarrier(carrier: any): boolean {
if (carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub) {
return true;
} else {
return false;
}
}
/**
* This will create a new {@link Hub} and add to the passed object on
* __SENTRY__.hub.
* @param carrier object
*/
export function getHubFromCarrier(carrier: any): Hub {
if (carrier && carrier.__SENTRY__ && carrier.__SENTRY__.hub) {
return carrier.__SENTRY__.hub;
} else {
carrier.__SENTRY__ = {};
carrier.__SENTRY__.hub = new Hub();
return carrier.__SENTRY__.hub;
}
}
/**
* This will set passed {@link Hub} on the passed object's __SENTRY__.hub attribute
* @param carrier object
* @param hub Hub
*/
export function setHubOnCarrier(carrier: any, hub: Hub): boolean {
if (!carrier) {
return false;
}
carrier.__SENTRY__ = carrier.__SENTRY__ || {};
carrier.__SENTRY__.hub = hub;
return true;
}