Skip to content

Commit

Permalink
feat(lib): Export sentry hub (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
faris-imi authored Oct 30, 2023
1 parent 1a2d264 commit 54f6dc2
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 34 deletions.
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,19 @@ const { response } = await hcaptcha.execute({ async: true });
```

### Props
| Name | Values/Type | Required | Default | Description |
|------------------|-------------|----------|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `loadAsync` | Boolean | No | `true` | Set if the script should be loaded asynchronously. |
| `cleanup` | Boolean | No | `true` | Remove script tag after setup. |
| `crossOrigin` | String | No | `-` | Set script cross origin attribute such as "anonymous". |
| `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. |
| `apihost` | String | No | `-` | See enterprise docs. |
| `assethost` | String | No | `-` | See enterprise docs. |
| `endpoint` | String | No | `-` | See enterprise docs. |
| `host` | String | No | `-` | See enterprise docs. |
| `imghost` | String | No | `-` | See enterprise docs. |
| `reportapi` | String | No | `-` | See enterprise docs. |
| `sentry` | Boolean | No | `-` | See enterprise docs. |
| `custom` | Boolean | No | `-` | See enterprise docs. |
| Name | Values/Type | Required | Default | Description |
|-------------------|-------------|----------|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| `loadAsync` | Boolean | No | `true` | Set if the script should be loaded asynchronously. |
| `cleanup` | Boolean | No | `true` | Remove script tag after setup. |
| `crossOrigin` | String | No | `-` | Set script cross origin attribute such as "anonymous". |
| `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. |
| `apihost` | String | No | `-` | See enterprise docs. |
| `assethost` | String | No | `-` | See enterprise docs. |
| `endpoint` | String | No | `-` | See enterprise docs. |
| `hl` | String | No | `-` | See enterprise docs. |
| `host` | String | No | `-` | See enterprise docs. |
| `imghost` | String | No | `-` | See enterprise docs. |
| `recaptchacompat` | String | No | `-` | See enterprise docs. |
| `reportapi` | String | No | `-` | See enterprise docs. |
| `sentry` | Boolean | No | `-` | See enterprise docs. |
| `custom` | Boolean | No | `-` | See enterprise docs. |
81 changes: 81 additions & 0 deletions lib/__test__/sentry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { describe, it, jest, expect, afterEach } from '@jest/globals';

import * as Sentry from '@sentry/browser';
import {
initSentry,
getSentry,
getSentryHubWrapper,
} from '../src/sentry';

jest.mock('@sentry/browser', () => ({
BrowserClient: jest.fn(),
Hub: jest.fn(),
Breadcrumbs: jest.fn(),
GlobalHandlers: jest.fn(),
LinkedErrors: jest.fn(),
Dedupe: jest.fn(),
HttpContext: jest.fn(),
BrowserTracing: jest.fn(),
makeFetchTransport: jest.fn(),
defaultStackParser: jest.fn(),
withScope: jest.fn(),
}));

const mockScope = {
setTag: jest.fn(),
};

describe('Sentry', () => {

afterEach(() => {
jest.clearAllMocks();
});

it('should initialize Sentry Hub and return wrapper', () => {
const hub = initSentry(true);
expect(Sentry.BrowserClient).toHaveBeenCalledTimes(1);
expect(Sentry.Hub).toHaveBeenCalledTimes(1);
expect(hub).toBeTruthy();
});

it('should return null if Sentry is disabled', () => {
const hub = initSentry(false);
expect(Sentry.BrowserClient).not.toHaveBeenCalled();
expect(Sentry.Hub).not.toHaveBeenCalled();
expect(hub).toBeNull();
});

it('should get initialized Sentry Hub', () => {
const hub = initSentry(true);
const hubValues = String(Object.values(hub));

const retrievedHub = getSentry();
const retrievedValues = String(Object.values(retrievedHub));

expect(hubValues).toEqual(retrievedValues);
});

it('should wrap Sentry Hub correctly', () => {
const mockHub = {
addBreadcrumb: jest.fn(),
captureMessage: jest.fn(),
captureException: jest.fn(),
withScope: jest.fn(callback => callback(mockScope)),
};

const tag = { key: 'testKey', value: 'testValue' };
const breadcrumb = { category: 'test breadcrumb' };

const sentryHubWrapper = getSentryHubWrapper(mockHub, tag);

sentryHubWrapper.addBreadcrumb(breadcrumb);
expect(mockHub.addBreadcrumb).toHaveBeenCalledWith(breadcrumb);

sentryHubWrapper.captureMessage('test message');
expect(mockHub.captureMessage).toHaveBeenCalledWith('test message');

sentryHubWrapper.captureException('test exception');
expect(mockHub.captureException).toHaveBeenCalledWith('test exception');
});
});

2 changes: 2 additions & 0 deletions lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { hCaptchaLoader } from './loader';
export { initSentry, getSentry } from './sentry';

9 changes: 3 additions & 6 deletions lib/src/loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export function hCaptchaLoader(params: ILoaderParams = { cleanup: true }): Promi
category: 'script',
message: 'hCaptcha loader params',
data: params,
level: 'info'
});

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

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

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

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

sentry?.addBreadcrumb({
category: 'hCaptcha:script',
message: 'hCaptcha loaded',
level: 'info'
});
} catch(error) {
sentry?.addBreadcrumb({
category: 'hCaptcha:script',
message: 'hCaptcha failed to load',
level: 'info'
});
sentry?.captureMessage(error);
sentry?.captureException(error);
reject(new Error(SCRIPT_ERROR));
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ export function fetchScript({
element.appendChild(script);
}
);
}
}
45 changes: 36 additions & 9 deletions lib/src/sentry.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import * as Sentry from '@sentry/browser';

import { ScopeTag, SentryHub } from './types';

const SENTRY_DSN = process.env.SENTRY_DSN_TOKEN;

let hub = null;

export function initSentry(sentry: boolean) {
export function initSentry(
sentry: boolean,
scopeTag?: ScopeTag
): SentryHub | null {

// Sentry disabled in params
if (sentry === false) {
return;
return null;
}

// Client was already created
if (hub) {
return hub;
return getSentryHubWrapper(hub, scopeTag);
}

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

hub = new Sentry.Hub(client);

hub.configureScope(function (scope) {
scope.setTag('source', '@hCaptcha/loader');
});
return getSentryHubWrapper(hub, scopeTag);
}

return hub;
export function getSentry(tag?: ScopeTag): SentryHub | null {
return getSentryHubWrapper(hub, tag);
}

export function getSentry() {
return hub;
export function getSentryHubWrapper(
sentryHub,
tag: ScopeTag = {
key: 'source',
value: '@hCaptcha/loader'
}): SentryHub {

return {
addBreadcrumb: (breadcrumb) => sentryHub.addBreadcrumb(breadcrumb),
captureMessage: (message) => {
sentryHub.withScope(function (scope) {
scope.setTag(tag.key, tag.value);

sentryHub.captureMessage(message);
});
},
captureException: (e) => {
sentryHub.withScope(function (scope) {
scope.setTag(tag.key, tag.value);

sentryHub.captureException(e);
});
}
};
}
20 changes: 17 additions & 3 deletions lib/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export interface IScriptParams {
apihost?: string;
loadAsync?: boolean;
cleanup?: boolean;
query?: string
crossOrigin?: string
query?: string;
crossOrigin?: string;
}

export interface ILoaderParams extends IScriptParams {
Expand All @@ -16,4 +16,18 @@ export interface ILoaderParams extends IScriptParams {
reportapi?: string;
endpoint?: string;
host?: string;
}
recaptchacompat?: string;
hl?: string;
cleanup?: boolean;
}

export interface SentryHub {
addBreadcrumb: (breadcrumb: object) => void;
captureException: (e: any) => void;
captureMessage: (message: string) => void;
}

export interface ScopeTag {
key: string;
value: string;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@hcaptcha/loader",
"description": "This is a JavaScript library to easily configure the loading of the hCaptcha JS client SDK with built-in error handling.",
"version": "1.0.5",
"version": "1.0.6",
"author": "hCaptcha team and contributors",
"license": "MIT",
"keywords": [
Expand Down

0 comments on commit 54f6dc2

Please sign in to comment.