Skip to content

Commit 5c139cf

Browse files
authored
feat: Core integrations and new is/supports utils with tests (#1354)
1 parent cdc7393 commit 5c139cf

File tree

14 files changed

+386
-7
lines changed

14 files changed

+386
-7
lines changed

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
"test": "lerna run --stream --concurrency 1 --sort test"
99
},
1010
"workspaces": [
11-
"packages/*"
11+
"packages/browser",
12+
"packages/core",
13+
"packages/node",
14+
"packages/shim",
15+
"packages/types",
16+
"packages/typescript",
17+
"packages/utils"
1218
],
1319
"devDependencies": {
1420
"@types/chai": "^4.1.3",

packages/core/src/interfaces.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Scope } from '@sentry/shim';
2-
import { Breadcrumb, SentryEvent } from '@sentry/types';
2+
import { Breadcrumb, Integration, SentryEvent } from '@sentry/types';
33
import { DSN } from './dsn';
44
import { SendStatus } from './status';
55

@@ -30,6 +30,15 @@ export interface Options {
3030
*/
3131
dsn?: string;
3232

33+
/**
34+
* List of integrations that should be installed after SDK was initialized.
35+
* Accepts either a list of integrations or a function that receives
36+
* default integrations and returns a new, updated list.
37+
*/
38+
integrations?:
39+
| Integration[]
40+
| ((integrations: Integration[]) => Integration[]);
41+
3342
/**
3443
* The release identifier used when uploading respective source maps. Specify
3544
* this value to allow Sentry to resolve the correct source maps when

packages/core/src/sdk.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
bindClient as shimBindClient,
33
getCurrentClient as shimGetCurrentClient,
44
} from '@sentry/shim';
5+
import { Integration } from '@sentry/types';
56
import { Client, Options } from './interfaces';
67

78
/** A class object that can instanciate Client objects. */
@@ -20,12 +21,28 @@ export interface ClientClass<F extends Client, O extends Options> {
2021
export function initAndBind<F extends Client, O extends Options>(
2122
clientClass: ClientClass<F, O>,
2223
options: O,
24+
defaultIntegrations: Integration[] = [],
2325
): void {
2426
if (shimGetCurrentClient()) {
2527
return;
2628
}
2729

2830
const client = new clientClass(options);
2931
client.install();
32+
33+
let integrations = [...defaultIntegrations];
34+
if (Array.isArray(options.integrations)) {
35+
integrations = [...integrations, ...options.integrations];
36+
} else if (typeof options.integrations === 'function') {
37+
integrations = options.integrations(integrations);
38+
}
39+
40+
// Just in case someone will return non-array from a `itegrations` callback
41+
if (Array.isArray(integrations)) {
42+
integrations.forEach(integration => {
43+
integration.install();
44+
});
45+
}
46+
3047
shimBindClient(client);
3148
}

packages/core/test/lib/sdk.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { Integration } from '@sentry/types';
2+
import { initAndBind } from '../../src/sdk';
3+
import { TestClient } from '../mocks/client';
4+
5+
declare var global: any;
6+
7+
class MockIntegration implements Integration {
8+
public name: string = 'MockIntegration';
9+
public handler: () => void = jest.fn();
10+
public install: () => void = () => {
11+
this.handler();
12+
};
13+
}
14+
15+
describe('SDK', () => {
16+
beforeEach(() => {
17+
global.__SENTRY__ = {
18+
shim: undefined,
19+
stack: [],
20+
};
21+
});
22+
23+
describe('initAndBind', () => {
24+
test('installs default integrations', () => {
25+
const DEFAULT_INTEGRATIONS: Integration[] = [
26+
new MockIntegration(),
27+
new MockIntegration(),
28+
];
29+
initAndBind(TestClient, {}, DEFAULT_INTEGRATIONS);
30+
expect(DEFAULT_INTEGRATIONS[0].handler.mock.calls.length).toBe(1);
31+
expect(DEFAULT_INTEGRATIONS[1].handler.mock.calls.length).toBe(1);
32+
});
33+
34+
test('installs integrations provided through options', () => {
35+
const integrations: Integration[] = [
36+
new MockIntegration(),
37+
new MockIntegration(),
38+
];
39+
initAndBind(TestClient, { integrations }, []);
40+
expect(integrations[0].handler.mock.calls.length).toBe(1);
41+
expect(integrations[1].handler.mock.calls.length).toBe(1);
42+
});
43+
44+
test('installs merged default integrations and one provided through options', () => {
45+
const DEFAULT_INTEGRATIONS: Integration[] = [
46+
new MockIntegration(),
47+
new MockIntegration(),
48+
];
49+
const integrations: Integration[] = [
50+
new MockIntegration(),
51+
new MockIntegration(),
52+
];
53+
initAndBind(TestClient, { integrations }, DEFAULT_INTEGRATIONS);
54+
expect(DEFAULT_INTEGRATIONS[0].handler.mock.calls.length).toBe(1);
55+
expect(DEFAULT_INTEGRATIONS[1].handler.mock.calls.length).toBe(1);
56+
expect(integrations[0].handler.mock.calls.length).toBe(1);
57+
expect(integrations[1].handler.mock.calls.length).toBe(1);
58+
});
59+
60+
test('installs integrations returned from a callback function', () => {
61+
const DEFAULT_INTEGRATIONS: Integration[] = [
62+
new MockIntegration(),
63+
new MockIntegration(),
64+
];
65+
const newIntegration = new MockIntegration();
66+
initAndBind(
67+
TestClient,
68+
{
69+
// Take only the first one and add a new one to it
70+
integrations: (integrations: Integration[]) =>
71+
integrations.slice(0, 1).concat(newIntegration),
72+
},
73+
DEFAULT_INTEGRATIONS,
74+
);
75+
expect(DEFAULT_INTEGRATIONS[0].handler.mock.calls.length).toBe(1);
76+
expect(newIntegration.handler.mock.calls.length).toBe(1);
77+
expect(DEFAULT_INTEGRATIONS[1].handler.mock.calls.length).toBe(0);
78+
});
79+
});
80+
});

packages/core/test/tslint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"completed-docs": false,
55
"no-non-null-assertion": false,
66
"no-implicit-dependencies": [true, "dev"],
7-
"no-unused-expression": false
7+
"no-unused-expression": false,
8+
"no-unsafe-any": false
89
}
910
}

packages/types/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export enum Severity {
77
/** TODO */
88
Warning = 'warning',
99
/** TODO */
10+
Log = 'log',
11+
/** TODO */
1012
Info = 'info',
1113
/** TODO */
1214
Debug = 'debug',
@@ -116,3 +118,10 @@ export interface SentryEvent {
116118
extra?: { [key: string]: object };
117119
user?: User;
118120
}
121+
122+
/** TODO */
123+
export interface Integration {
124+
name: string;
125+
handler?: any;
126+
install(): void;
127+
}

packages/typescript/tslint.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"no-require-imports": false,
1313
"prefer-function-over-method": [false],
1414
"strict-boolean-expressions": false,
15-
15+
"no-inferrable-types": false,
1616
// These are too strict in tslint:all
1717
"comment-format": [true, "check-space"],
1818
"completed-docs": [

packages/utils/src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
export { forget, filterAsync } from './async';
22
export { mkdirp, mkdirpSync } from './fs';
3-
export { clone, deserialize, serialize } from './object';
3+
export { clone, deserialize, fill, serialize } from './object';
44
export { Store } from './store';
5+
export { isError, isErrorEvent, isDOMError, isDOMException } from './is';
6+
export {
7+
supportsErrorEvent,
8+
supportsDOMError,
9+
supportsDOMException,
10+
} from './supports';

packages/utils/src/is.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Checks whether given value's type is one of a few Error or Error-like
3+
* {@link isError}.
4+
*
5+
* @param wat A value to be checked.
6+
* @returns A boolean representing the result.
7+
*/
8+
export function isError(wat: any): boolean {
9+
switch (Object.prototype.toString.call(wat)) {
10+
case '[object Error]':
11+
return true;
12+
case '[object Exception]':
13+
return true;
14+
case '[object DOMException]':
15+
return true;
16+
default:
17+
return wat instanceof Error;
18+
}
19+
}
20+
21+
/**
22+
* Checks whether given value's type is ErrorEvent
23+
* {@link isErrorEvent}.
24+
*
25+
* @param wat A value to be checked.
26+
* @returns A boolean representing the result.
27+
*/
28+
export function isErrorEvent(wat: any): boolean {
29+
return Object.prototype.toString.call(wat) === '[object ErrorEvent]';
30+
}
31+
32+
/**
33+
* Checks whether given value's type is DOMError
34+
* {@link isDOMError}.
35+
*
36+
* @param wat A value to be checked.
37+
* @returns A boolean representing the result.
38+
*/
39+
export function isDOMError(wat: any): boolean {
40+
return Object.prototype.toString.call(wat) === '[object DOMError]';
41+
}
42+
43+
/**
44+
* Checks whether given value's type is DOMException
45+
* {@link isDOMException}.
46+
*
47+
* @param wat A value to be checked.
48+
* @returns A boolean representing the result.
49+
*/
50+
export function isDOMException(wat: any): boolean {
51+
return Object.prototype.toString.call(wat) === '[object DOMException]';
52+
}

packages/utils/src/object.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,31 @@ export function deserialize<T>(str: string): T {
3939
export function clone<T>(object: T): T {
4040
return deserialize(serialize(object));
4141
}
42+
43+
/**
44+
* Wrap a given object method with a higher-order function
45+
* and keep track of the original within `track` array
46+
*
47+
* @param source An object that contains a method to be wrapped.
48+
* @param name A name of method to be wrapped.
49+
* @param replacement A function that should be used to wrap a given method.
50+
* @param [track] An array containing original methods that were wrapped.
51+
* @returns void
52+
*/
53+
54+
export function fill(
55+
source: { [key: string]: any },
56+
name: string,
57+
replacement: (...args: any[]) => any,
58+
track?: Array<[{ [key: string]: any }, string, any]>,
59+
): void {
60+
const orig = source[name];
61+
source[name] = replacement(orig);
62+
// tslint:disable:no-unsafe-any
63+
source[name].__raven__ = true;
64+
// tslint:disable:no-unsafe-any
65+
source[name].__orig__ = orig;
66+
if (track) {
67+
track.push([source, name, orig]);
68+
}
69+
}

0 commit comments

Comments
 (0)