Skip to content

Commit 54f6dc2

Browse files
authored
feat(lib): Export sentry hub (#8)
1 parent 1a2d264 commit 54f6dc2

File tree

8 files changed

+157
-34
lines changed

8 files changed

+157
-34
lines changed

README.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,19 @@ const { response } = await hcaptcha.execute({ async: true });
3030
```
3131

3232
### Props
33-
| Name | Values/Type | Required | Default | Description |
34-
|------------------|-------------|----------|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
35-
| `loadAsync` | Boolean | No | `true` | Set if the script should be loaded asynchronously. |
36-
| `cleanup` | Boolean | No | `true` | Remove script tag after setup. |
37-
| `crossOrigin` | String | No | `-` | Set script cross origin attribute such as "anonymous". |
38-
| `scriptLocation` | HTMLElement | No | `document.head` | Location of where to append the script tag. Make sure to add it to an area that will persist to prevent loading multiple times in the same document view. |
39-
| `apihost` | String | No | `-` | See enterprise docs. |
40-
| `assethost` | String | No | `-` | See enterprise docs. |
41-
| `endpoint` | String | No | `-` | See enterprise docs. |
42-
| `host` | String | No | `-` | See enterprise docs. |
43-
| `imghost` | String | No | `-` | See enterprise docs. |
44-
| `reportapi` | String | No | `-` | See enterprise docs. |
45-
| `sentry` | Boolean | No | `-` | See enterprise docs. |
46-
| `custom` | Boolean | No | `-` | See enterprise docs. |
33+
| Name | Values/Type | Required | Default | Description |
34+
|-------------------|-------------|----------|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
35+
| `loadAsync` | Boolean | No | `true` | Set if the script should be loaded asynchronously. |
36+
| `cleanup` | Boolean | No | `true` | Remove script tag after setup. |
37+
| `crossOrigin` | String | No | `-` | Set script cross origin attribute such as "anonymous". |
38+
| `scriptLocation` | HTMLElement | No | `document.head` | Location of where to append the script tag. Make sure to add it to an area that will persist to prevent loading multiple times in the same document view. |
39+
| `apihost` | String | No | `-` | See enterprise docs. |
40+
| `assethost` | String | No | `-` | See enterprise docs. |
41+
| `endpoint` | String | No | `-` | See enterprise docs. |
42+
| `hl` | String | No | `-` | See enterprise docs. |
43+
| `host` | String | No | `-` | See enterprise docs. |
44+
| `imghost` | String | No | `-` | See enterprise docs. |
45+
| `recaptchacompat` | String | No | `-` | See enterprise docs. |
46+
| `reportapi` | String | No | `-` | See enterprise docs. |
47+
| `sentry` | Boolean | No | `-` | See enterprise docs. |
48+
| `custom` | Boolean | No | `-` | See enterprise docs. |

lib/__test__/sentry.test.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, it, jest, expect, afterEach } from '@jest/globals';
2+
3+
import * as Sentry from '@sentry/browser';
4+
import {
5+
initSentry,
6+
getSentry,
7+
getSentryHubWrapper,
8+
} from '../src/sentry';
9+
10+
jest.mock('@sentry/browser', () => ({
11+
BrowserClient: jest.fn(),
12+
Hub: jest.fn(),
13+
Breadcrumbs: jest.fn(),
14+
GlobalHandlers: jest.fn(),
15+
LinkedErrors: jest.fn(),
16+
Dedupe: jest.fn(),
17+
HttpContext: jest.fn(),
18+
BrowserTracing: jest.fn(),
19+
makeFetchTransport: jest.fn(),
20+
defaultStackParser: jest.fn(),
21+
withScope: jest.fn(),
22+
}));
23+
24+
const mockScope = {
25+
setTag: jest.fn(),
26+
};
27+
28+
describe('Sentry', () => {
29+
30+
afterEach(() => {
31+
jest.clearAllMocks();
32+
});
33+
34+
it('should initialize Sentry Hub and return wrapper', () => {
35+
const hub = initSentry(true);
36+
expect(Sentry.BrowserClient).toHaveBeenCalledTimes(1);
37+
expect(Sentry.Hub).toHaveBeenCalledTimes(1);
38+
expect(hub).toBeTruthy();
39+
});
40+
41+
it('should return null if Sentry is disabled', () => {
42+
const hub = initSentry(false);
43+
expect(Sentry.BrowserClient).not.toHaveBeenCalled();
44+
expect(Sentry.Hub).not.toHaveBeenCalled();
45+
expect(hub).toBeNull();
46+
});
47+
48+
it('should get initialized Sentry Hub', () => {
49+
const hub = initSentry(true);
50+
const hubValues = String(Object.values(hub));
51+
52+
const retrievedHub = getSentry();
53+
const retrievedValues = String(Object.values(retrievedHub));
54+
55+
expect(hubValues).toEqual(retrievedValues);
56+
});
57+
58+
it('should wrap Sentry Hub correctly', () => {
59+
const mockHub = {
60+
addBreadcrumb: jest.fn(),
61+
captureMessage: jest.fn(),
62+
captureException: jest.fn(),
63+
withScope: jest.fn(callback => callback(mockScope)),
64+
};
65+
66+
const tag = { key: 'testKey', value: 'testValue' };
67+
const breadcrumb = { category: 'test breadcrumb' };
68+
69+
const sentryHubWrapper = getSentryHubWrapper(mockHub, tag);
70+
71+
sentryHubWrapper.addBreadcrumb(breadcrumb);
72+
expect(mockHub.addBreadcrumb).toHaveBeenCalledWith(breadcrumb);
73+
74+
sentryHubWrapper.captureMessage('test message');
75+
expect(mockHub.captureMessage).toHaveBeenCalledWith('test message');
76+
77+
sentryHubWrapper.captureException('test exception');
78+
expect(mockHub.captureException).toHaveBeenCalledWith('test exception');
79+
});
80+
});
81+

lib/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export { hCaptchaLoader } from './loader';
2+
export { initSentry, getSentry } from './sentry';
3+

lib/src/loader.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export function hCaptchaLoader(params: ILoaderParams = { cleanup: true }): Promi
1818
category: 'script',
1919
message: 'hCaptcha loader params',
2020
data: params,
21-
level: 'info'
2221
});
2322

2423
const element = getMountElement(params.scriptLocation);
@@ -29,7 +28,6 @@ export function hCaptchaLoader(params: ILoaderParams = { cleanup: true }): Promi
2928
sentry?.addBreadcrumb({
3029
category: 'script',
3130
message: 'hCaptcha already loaded',
32-
level: 'info'
3331
});
3432

3533
// API was already requested
@@ -46,7 +44,6 @@ export function hCaptchaLoader(params: ILoaderParams = { cleanup: true }): Promi
4644
sentry?.addBreadcrumb({
4745
category: 'hCaptcha:script',
4846
message: 'hCaptcha script called onload function',
49-
level: 'info'
5047
});
5148

5249
// Resolve loader once hCaptcha library says its ready
@@ -62,22 +59,22 @@ export function hCaptchaLoader(params: ILoaderParams = { cleanup: true }): Promi
6259
reportapi: params.reportapi,
6360
endpoint: params.endpoint,
6461
host: params.host,
62+
recaptchacompat: params.recaptchacompat,
63+
hl: params.hl,
6564
});
6665

6766
await fetchScript({ query, ...params });
6867

6968
sentry?.addBreadcrumb({
7069
category: 'hCaptcha:script',
7170
message: 'hCaptcha loaded',
72-
level: 'info'
7371
});
7472
} catch(error) {
7573
sentry?.addBreadcrumb({
7674
category: 'hCaptcha:script',
7775
message: 'hCaptcha failed to load',
78-
level: 'info'
7976
});
80-
sentry?.captureMessage(error);
77+
sentry?.captureException(error);
8178
reject(new Error(SCRIPT_ERROR));
8279
}
8380
}

lib/src/script.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ export function fetchScript({
4141
element.appendChild(script);
4242
}
4343
);
44-
}
44+
}

lib/src/sentry.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import * as Sentry from '@sentry/browser';
22

3+
import { ScopeTag, SentryHub } from './types';
4+
35
const SENTRY_DSN = process.env.SENTRY_DSN_TOKEN;
46

57
let hub = null;
68

7-
export function initSentry(sentry: boolean) {
9+
export function initSentry(
10+
sentry: boolean,
11+
scopeTag?: ScopeTag
12+
): SentryHub | null {
813

914
// Sentry disabled in params
1015
if (sentry === false) {
11-
return;
16+
return null;
1217
}
1318

1419
// Client was already created
1520
if (hub) {
16-
return hub;
21+
return getSentryHubWrapper(hub, scopeTag);
1722
}
1823

1924
const client = new Sentry.BrowserClient({
@@ -32,13 +37,35 @@ export function initSentry(sentry: boolean) {
3237

3338
hub = new Sentry.Hub(client);
3439

35-
hub.configureScope(function (scope) {
36-
scope.setTag('source', '@hCaptcha/loader');
37-
});
40+
return getSentryHubWrapper(hub, scopeTag);
41+
}
3842

39-
return hub;
43+
export function getSentry(tag?: ScopeTag): SentryHub | null {
44+
return getSentryHubWrapper(hub, tag);
4045
}
4146

42-
export function getSentry() {
43-
return hub;
47+
export function getSentryHubWrapper(
48+
sentryHub,
49+
tag: ScopeTag = {
50+
key: 'source',
51+
value: '@hCaptcha/loader'
52+
}): SentryHub {
53+
54+
return {
55+
addBreadcrumb: (breadcrumb) => sentryHub.addBreadcrumb(breadcrumb),
56+
captureMessage: (message) => {
57+
sentryHub.withScope(function (scope) {
58+
scope.setTag(tag.key, tag.value);
59+
60+
sentryHub.captureMessage(message);
61+
});
62+
},
63+
captureException: (e) => {
64+
sentryHub.withScope(function (scope) {
65+
scope.setTag(tag.key, tag.value);
66+
67+
sentryHub.captureException(e);
68+
});
69+
}
70+
};
4471
}

lib/src/types.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ export interface IScriptParams {
33
apihost?: string;
44
loadAsync?: boolean;
55
cleanup?: boolean;
6-
query?: string
7-
crossOrigin?: string
6+
query?: string;
7+
crossOrigin?: string;
88
}
99

1010
export interface ILoaderParams extends IScriptParams {
@@ -16,4 +16,18 @@ export interface ILoaderParams extends IScriptParams {
1616
reportapi?: string;
1717
endpoint?: string;
1818
host?: string;
19-
}
19+
recaptchacompat?: string;
20+
hl?: string;
21+
cleanup?: boolean;
22+
}
23+
24+
export interface SentryHub {
25+
addBreadcrumb: (breadcrumb: object) => void;
26+
captureException: (e: any) => void;
27+
captureMessage: (message: string) => void;
28+
}
29+
30+
export interface ScopeTag {
31+
key: string;
32+
value: string;
33+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@hcaptcha/loader",
33
"description": "This is a JavaScript library to easily configure the loading of the hCaptcha JS client SDK with built-in error handling.",
4-
"version": "1.0.5",
4+
"version": "1.0.6",
55
"author": "hCaptcha team and contributors",
66
"license": "MIT",
77
"keywords": [

0 commit comments

Comments
 (0)