diff --git a/.eslintignore b/.eslintignore index 42c9181..325bf77 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ node_modules/ lib/ ios/ android/ +coverage/ diff --git a/jest-setup.ts b/jest-setup.ts index 8f96f5d..bde28d1 100644 --- a/jest-setup.ts +++ b/jest-setup.ts @@ -11,3 +11,19 @@ jest.mock('react-native-encrypted-storage', () => ({ removeItem: jest.fn(() => Promise.resolve()), clear: jest.fn(() => Promise.resolve()), })); + +jest.mock('@sentry/react-native', () => ({ + ReactNativeClient: jest.fn().mockReturnValue({ + init: jest.fn(), + }), + Scope: jest.fn().mockReturnValue({ + setClient: jest.fn(), + setExtra: jest.fn(), + addBreadcrumb: jest.fn(), + captureException: jest.fn(), + }), +})); + +jest.mock('@sentry/react-native/dist/js/integrations/default', () => ({ + getDefaultIntegrations: jest.fn().mockReturnValue([]), +})); diff --git a/package.json b/package.json index 881d936..b892b71 100644 --- a/package.json +++ b/package.json @@ -141,6 +141,7 @@ "version": "0.44.1" }, "dependencies": { + "@sentry/react-native": "^6.3.0", "lodash": "^4.17.21" } } diff --git a/src/Contentpass.test.ts b/src/Contentpass.test.ts index f71cd64..48f8c53 100644 --- a/src/Contentpass.test.ts +++ b/src/Contentpass.test.ts @@ -7,6 +7,7 @@ import type { ContentpassState } from './types/ContentpassState'; import OidcAuthStateStorage from './OidcAuthStateStorage'; import * as FetchContentpassTokenModule from './utils/fetchContentpassToken'; import { SCOPES } from './consts/oidcConsts'; +import * as SentryIntegrationModule from './sentryIntegration'; const config: ContentpassConfig = { propertyId: 'propertyId-1', @@ -39,6 +40,7 @@ describe('Contentpass', () => { let contentpass: Contentpass; let authorizeSpy: jest.SpyInstance; let refreshSpy: jest.SpyInstance; + let reportErrorSpy: jest.SpyInstance; let fetchContentpassTokenSpy: jest.SpyInstance; let oidcAuthStorageMock: OidcAuthStateStorage; @@ -50,6 +52,9 @@ describe('Contentpass', () => { refreshSpy = jest .spyOn(AppAuthModule, 'refresh') .mockResolvedValue(EXAMPLE_REFRESH_RESULT); + reportErrorSpy = jest + .spyOn(SentryIntegrationModule, 'reportError') + .mockReturnValue(undefined); oidcAuthStorageMock = { storeOidcAuthState: jest.fn(), @@ -166,13 +171,18 @@ describe('Contentpass', () => { it('should change contentpass state to error when authorize throws an error', async () => { const contentpassStates: ContentpassState[] = []; - authorizeSpy.mockRejectedValue(new Error('Authorize error')); + const error = new Error('Authorize error'); + authorizeSpy.mockRejectedValue(error); contentpass.registerObserver((state) => { contentpassStates.push(state); }); await contentpass.authenticate(); + expect(reportErrorSpy).toHaveBeenCalledWith(error, { + msg: 'Failed to authorize', + }); + expect(contentpassStates).toHaveLength(2); expect(contentpassStates[1]).toEqual({ state: 'ERROR', @@ -291,7 +301,8 @@ describe('Contentpass', () => { ).getTime(); const expectedDelay = expirationDate - NOW; - refreshSpy.mockRejectedValue(new Error('Refresh error')); + const refreshError = new Error('Refresh error'); + refreshSpy.mockRejectedValue(refreshError); await jest.advanceTimersByTimeAsync(expectedDelay); expect(contentpassStates).toHaveLength(2); @@ -308,7 +319,11 @@ describe('Contentpass', () => { }); // after 6 retries the state should change to error - await jest.advanceTimersByTimeAsync(180001); + await jest.advanceTimersByTimeAsync(120001); + expect(reportErrorSpy).toHaveBeenCalledTimes(7); + expect(reportErrorSpy).toHaveBeenCalledWith(refreshError, { + msg: 'Failed to refresh token after 6 retries', + }); expect(contentpassStates).toHaveLength(3); expect(contentpassStates[2]).toEqual({ state: 'UNAUTHENTICATED', diff --git a/src/Contentpass.ts b/src/Contentpass.ts index 0e07996..47d011d 100644 --- a/src/Contentpass.ts +++ b/src/Contentpass.ts @@ -16,6 +16,7 @@ import { RefreshTokenStrategy } from './types/RefreshTokenStrategy'; import fetchContentpassToken from './utils/fetchContentpassToken'; import validateSubscription from './utils/validateSubscription'; import type { ContentpassConfig } from './types/ContentpassConfig'; +import { reportError, setSentryExtraAttribute } from './sentryIntegration'; export type ContentpassObserver = (state: ContentpassState) => void; @@ -33,6 +34,7 @@ export default class Contentpass { constructor(config: ContentpassConfig) { this.authStateStorage = new OidcAuthStateStorage(config.propertyId); this.config = config; + setSentryExtraAttribute('propertyId', config.propertyId); this.initialiseAuthState(); } @@ -52,7 +54,7 @@ export default class Contentpass { }, }); } catch (err: any) { - // FIXME: logger for error + reportError(err, { msg: 'Failed to authorize' }); this.changeContentpassState({ state: ContentpassStateType.ERROR, @@ -166,7 +168,7 @@ export default class Contentpass { private refreshToken = async (counter: number) => { if (!this.oidcAuthState?.refreshToken) { - // FIXME: logger for error + reportError(new Error('No Refresh Token in oidcAuthState provided')); return; } @@ -183,17 +185,17 @@ export default class Contentpass { } ); await this.onNewAuthState(refreshResult); - } catch (err) { + } catch (err: any) { await this.onRefreshTokenError(counter, err); } }; - // @ts-expect-error remove when err starts being used - // eslint-disable-next-line @typescript-eslint/no-unused-vars - private onRefreshTokenError = async (counter: number, err: unknown) => { - // FIXME: logger for error + private onRefreshTokenError = async (counter: number, err: Error) => { + reportError(err, { + msg: `Failed to refresh token after ${counter} retries`, + }); // FIXME: add handling for specific error to not retry in every case - if (counter <= REFRESH_TOKEN_RETRIES) { + if (counter < REFRESH_TOKEN_RETRIES) { const delay = counter * 1000 * 10; await new Promise((resolve) => setTimeout(resolve, delay)); await this.refreshToken(counter + 1); diff --git a/src/index.test.tsx b/src/index.test.tsx deleted file mode 100644 index db84dd4..0000000 --- a/src/index.test.tsx +++ /dev/null @@ -1,3 +0,0 @@ -it('write a test', () => { - expect(true).toBe(true); -}); diff --git a/src/sentryIntegration.test.ts b/src/sentryIntegration.test.ts new file mode 100644 index 0000000..c778036 --- /dev/null +++ b/src/sentryIntegration.test.ts @@ -0,0 +1,108 @@ +import * as Sentry from '@sentry/react-native'; +import { defaultStackParser, makeFetchTransport } from '@sentry/react'; +import * as SentryReactNativeModule from '@sentry/react-native'; +import { reportError, setSentryExtraAttribute } from './sentryIntegration'; + +jest.mock('@sentry/react-native', () => { + const scope = { + setClient: jest.fn(), + addBreadcrumb: jest.fn(), + captureException: jest.fn(), + setExtra: jest.fn(), + }; + return { + ReactNativeClient: jest.fn().mockImplementation(() => ({ + init: jest.fn(), + captureException: jest.fn(), + })), + Scope: jest.fn().mockImplementation(() => scope), + + // Only for internal testing + __test_getScope: () => scope, + }; +}); + +describe('sentryScope', () => { + let addBreadcrumbMock: jest.Mock; + let captureExceptionMock: jest.Mock; + + beforeEach(() => { + addBreadcrumbMock = jest.fn(); + captureExceptionMock = jest.fn(); + jest.spyOn(SentryReactNativeModule, 'Scope').mockReturnValue({ + setClient: jest.fn(), + addBreadcrumb: addBreadcrumbMock, + captureException: captureExceptionMock, + } as any); + }); + + afterEach(() => { + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); + + it('should initialise sentry scope with correct options', () => { + expect(Sentry.ReactNativeClient).toHaveBeenCalledWith({ + attachStacktrace: true, + autoInitializeNativeSdk: false, + dsn: 'https://74ab84b55b30a3800255a25eac4d089c@sentry.tools.contentpass.dev/8', + enableAppStartTracking: false, + enableAutoPerformanceTracing: false, + enableCaptureFailedRequests: false, + enableNative: false, + enableNativeCrashHandling: false, + enableNativeFramesTracking: false, + enableNativeNagger: false, + enableNdk: false, + enableStallTracking: true, + enableUserInteractionTracing: false, + enableWatchdogTerminationTracking: false, + maxQueueSize: 30, + parentSpanIsAlwaysRootSpan: true, + patchGlobalPromise: true, + sendClientReports: true, + integrations: [], + stackParser: defaultStackParser, + transport: makeFetchTransport, + }); + }); + + describe('reportError', () => { + it('should add breadcrumb with message if provided', () => { + const err = new Error('test'); + const msg = 'test message'; + // @ts-ignore + const { addBreadcrumb, captureException } = Sentry.__test_getScope(); + reportError(err, { msg }); + + expect(addBreadcrumb).toHaveBeenCalledWith({ + category: 'Error', + message: msg, + level: 'log', + }); + expect(captureException).toHaveBeenCalledWith(err); + }); + + it('should not add breadcrumb if message is not provided', () => { + const err = new Error('test'); + // @ts-ignore + const { addBreadcrumb, captureException } = Sentry.__test_getScope(); + reportError(err); + + expect(addBreadcrumb).not.toHaveBeenCalled(); + expect(captureException).toHaveBeenCalledWith(err); + }); + }); + + describe('setSentryExtraAttribute', () => { + it('should set extra attribute', () => { + const key = 'testKey'; + const value = 'testValue'; + // @ts-ignore + const { setExtra } = Sentry.__test_getScope(); + setSentryExtraAttribute(key, value); + + expect(setExtra).toHaveBeenCalledWith(key, value); + }); + }); +}); diff --git a/src/sentryIntegration.ts b/src/sentryIntegration.ts new file mode 100644 index 0000000..66f4fb6 --- /dev/null +++ b/src/sentryIntegration.ts @@ -0,0 +1,59 @@ +import * as Sentry from '@sentry/react-native'; +import { defaultStackParser, makeFetchTransport } from '@sentry/react'; +import { getDefaultIntegrations } from '@sentry/react-native/dist/js/integrations/default'; + +// as it's only the open source package, we want to have minimal sentry configuration here to not override sentry instance, +// which can be used in the application +const options = { + attachStacktrace: true, + autoInitializeNativeSdk: false, + dsn: 'https://74ab84b55b30a3800255a25eac4d089c@sentry.tools.contentpass.dev/8', + enableAppStartTracking: false, + enableAutoPerformanceTracing: false, + enableCaptureFailedRequests: false, + enableNative: false, + enableNativeCrashHandling: false, + enableNativeFramesTracking: false, + enableNativeNagger: false, + enableNdk: false, + enableStallTracking: true, + enableUserInteractionTracing: false, + enableWatchdogTerminationTracking: false, + maxQueueSize: 30, + parentSpanIsAlwaysRootSpan: true, + patchGlobalPromise: true, + sendClientReports: true, + stackParser: defaultStackParser, + transport: makeFetchTransport, + integrations: [], +}; + +const sentryClient = new Sentry.ReactNativeClient({ + ...options, + integrations: getDefaultIntegrations(options), +}); + +const sentryScope = new Sentry.Scope(); +sentryScope.setClient(sentryClient); + +sentryClient.init(); + +type ReportErrorOptions = { + msg?: string; +}; + +export const reportError = (err: Error, { msg }: ReportErrorOptions = {}) => { + if (msg) { + sentryScope.addBreadcrumb({ + category: 'Error', + message: msg, + level: 'log', + }); + } + + sentryScope.captureException(err); +}; + +export const setSentryExtraAttribute = (key: string, value: string) => { + sentryScope.setExtra(key, value); +}; diff --git a/src/utils/parseContentpassToken.test.ts b/src/utils/parseContentpassToken.test.ts index b70b1d4..39afa26 100644 --- a/src/utils/parseContentpassToken.test.ts +++ b/src/utils/parseContentpassToken.test.ts @@ -36,6 +36,6 @@ describe('parseContentpassToken', () => { expect(() => { parseContentpassToken(invalidToken); - }).toThrow('Invalid token'); + }).toThrow('Invalid token, token should have at least 3 parts'); }); }); diff --git a/src/utils/parseContentpassToken.ts b/src/utils/parseContentpassToken.ts index 184d78e..e87f929 100644 --- a/src/utils/parseContentpassToken.ts +++ b/src/utils/parseContentpassToken.ts @@ -18,7 +18,7 @@ export default function parseContentpassToken( ): ParsedToken { const tokenParts = contentpassToken.split('.'); if (tokenParts.length < 3) { - throw new Error('Invalid token'); + throw new Error('Invalid token, token should have at least 3 parts'); } const header = JSON.parse(atob(tokenParts[0]!)); diff --git a/src/utils/validateSubscription.test.ts b/src/utils/validateSubscription.test.ts index 09f1e3a..c3f5d30 100644 --- a/src/utils/validateSubscription.test.ts +++ b/src/utils/validateSubscription.test.ts @@ -1,4 +1,5 @@ import parseContentpassToken, * as ParseContentpassTokenModule from './parseContentpassToken'; +import * as SentryIntegrationModule from '../sentryIntegration'; import validateSubscription from './validateSubscription'; const EXAMPLE_CONTENTPASS_TOKEN: ReturnType = { @@ -18,10 +19,16 @@ const EXAMPLE_CONTENTPASS_TOKEN: ReturnType = { describe('validateSubscription', () => { let parseContentpassTokenSpy: jest.SpyInstance; + let reportErrorSpy: jest.SpyInstance; + beforeEach(() => { parseContentpassTokenSpy = jest .spyOn(ParseContentpassTokenModule, 'default') .mockReturnValue(EXAMPLE_CONTENTPASS_TOKEN); + + reportErrorSpy = jest + .spyOn(SentryIntegrationModule, 'reportError') + .mockReturnValue(undefined); }); afterEach(() => { @@ -36,13 +43,17 @@ describe('validateSubscription', () => { expect(result).toBe(true); }); - it('should return false if the token is invalid', () => { + it('should return false and report error if the token is invalid', () => { + const error = new Error('Invalid token'); parseContentpassTokenSpy.mockImplementation(() => { - throw new Error('Invalid token'); + throw error; }); const result = validateSubscription('example_contentpass_token'); expect(result).toBe(false); + expect(reportErrorSpy).toHaveBeenCalledWith(error, { + msg: 'Failed to validate subscription', + }); }); it('should return false if the user is not authenticated', () => { diff --git a/src/utils/validateSubscription.ts b/src/utils/validateSubscription.ts index 9a08f38..6fb596b 100644 --- a/src/utils/validateSubscription.ts +++ b/src/utils/validateSubscription.ts @@ -1,11 +1,12 @@ import parseContentpassToken from './parseContentpassToken'; +import { reportError } from '../sentryIntegration'; export default function validateSubscription(contentpassToken: string) { try { const { body } = parseContentpassToken(contentpassToken); return !!body.auth && !!body.plans.length; - } catch (err) { - // FIXME: logger for error + } catch (err: any) { + reportError(err, { msg: 'Failed to validate subscription' }); return false; } } diff --git a/yarn.lock b/yarn.lock index a1f53af..f727642 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3565,6 +3565,219 @@ __metadata: languageName: node linkType: hard +"@sentry-internal/browser-utils@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry-internal/browser-utils@npm:8.40.0" + dependencies: + "@sentry/core": 8.40.0 + "@sentry/types": 8.40.0 + checksum: b32ce642ab4277c3d77d9ccfb0eebbd491f8b4e7eaf395985e63183cb0c298944753d2bc139606fc9b401d3f55a7d3ede15fbb2ac59ba8ff9089dc8a685bca61 + languageName: node + linkType: hard + +"@sentry-internal/feedback@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry-internal/feedback@npm:8.40.0" + dependencies: + "@sentry/core": 8.40.0 + "@sentry/types": 8.40.0 + checksum: 712845b89df12d4b75f293de37b5888f8dc78e58639f11368edf095185fbb7c50bcc65e992d10b276b6feca5976b5e4ff6e8e4e5d4c9096d5fa0a3becb7c581d + languageName: node + linkType: hard + +"@sentry-internal/replay-canvas@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry-internal/replay-canvas@npm:8.40.0" + dependencies: + "@sentry-internal/replay": 8.40.0 + "@sentry/core": 8.40.0 + "@sentry/types": 8.40.0 + checksum: ed4eee113ede6c3f5dfec190a8df24957f366bf0698f78b626ae53876c8319583c5cc042d93cb3542147eab3e4e1d3c95ee24cb0bb530800cc1dd926b64ed811 + languageName: node + linkType: hard + +"@sentry-internal/replay@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry-internal/replay@npm:8.40.0" + dependencies: + "@sentry-internal/browser-utils": 8.40.0 + "@sentry/core": 8.40.0 + "@sentry/types": 8.40.0 + checksum: 6e37746ec8de83551aef8001583f114b9f04bdf85dd6db9431d939263c87530ac484e826585c3ee210a9089db0743e0221974bd2e7a9aeb6ff79cd150f163d14 + languageName: node + linkType: hard + +"@sentry/babel-plugin-component-annotate@npm:2.20.1": + version: 2.20.1 + resolution: "@sentry/babel-plugin-component-annotate@npm:2.20.1" + checksum: 5fecba8c7915693fec811bb06ff0441f28496f6b12e811337a08996a7aa13a13a069c9f9ed28bac95be89d03b422a68d7236ab3376c161edbe051cb0ad2a0193 + languageName: node + linkType: hard + +"@sentry/browser@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry/browser@npm:8.40.0" + dependencies: + "@sentry-internal/browser-utils": 8.40.0 + "@sentry-internal/feedback": 8.40.0 + "@sentry-internal/replay": 8.40.0 + "@sentry-internal/replay-canvas": 8.40.0 + "@sentry/core": 8.40.0 + "@sentry/types": 8.40.0 + checksum: b8c21570ff6a26effd1ae2843ab377c3e345624e38cea2eff916b160c8b3d33eb649d088f4f9dfcb26ae0aa8e8d02a7f947ed19af7a139ac2ad2b1f9d3adca2d + languageName: node + linkType: hard + +"@sentry/cli-darwin@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli-darwin@npm:2.38.2" + conditions: os=darwin + languageName: node + linkType: hard + +"@sentry/cli-linux-arm64@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli-linux-arm64@npm:2.38.2" + conditions: (os=linux | os=freebsd) & cpu=arm64 + languageName: node + linkType: hard + +"@sentry/cli-linux-arm@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli-linux-arm@npm:2.38.2" + conditions: (os=linux | os=freebsd) & cpu=arm + languageName: node + linkType: hard + +"@sentry/cli-linux-i686@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli-linux-i686@npm:2.38.2" + conditions: (os=linux | os=freebsd) & (cpu=x86 | cpu=ia32) + languageName: node + linkType: hard + +"@sentry/cli-linux-x64@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli-linux-x64@npm:2.38.2" + conditions: (os=linux | os=freebsd) & cpu=x64 + languageName: node + linkType: hard + +"@sentry/cli-win32-i686@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli-win32-i686@npm:2.38.2" + conditions: os=win32 & (cpu=x86 | cpu=ia32) + languageName: node + linkType: hard + +"@sentry/cli-win32-x64@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli-win32-x64@npm:2.38.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@sentry/cli@npm:2.38.2": + version: 2.38.2 + resolution: "@sentry/cli@npm:2.38.2" + dependencies: + "@sentry/cli-darwin": 2.38.2 + "@sentry/cli-linux-arm": 2.38.2 + "@sentry/cli-linux-arm64": 2.38.2 + "@sentry/cli-linux-i686": 2.38.2 + "@sentry/cli-linux-x64": 2.38.2 + "@sentry/cli-win32-i686": 2.38.2 + "@sentry/cli-win32-x64": 2.38.2 + https-proxy-agent: ^5.0.0 + node-fetch: ^2.6.7 + progress: ^2.0.3 + proxy-from-env: ^1.1.0 + which: ^2.0.2 + dependenciesMeta: + "@sentry/cli-darwin": + optional: true + "@sentry/cli-linux-arm": + optional: true + "@sentry/cli-linux-arm64": + optional: true + "@sentry/cli-linux-i686": + optional: true + "@sentry/cli-linux-x64": + optional: true + "@sentry/cli-win32-i686": + optional: true + "@sentry/cli-win32-x64": + optional: true + bin: + sentry-cli: bin/sentry-cli + checksum: 6f58c3fd4838b195b0424459b1f64db6b90b238e61283eeb5c0dd9c05ef0d16ed2ee72475108d3b9e9188b3e99575ee081d8003475096b385306f093c04cfb08 + languageName: node + linkType: hard + +"@sentry/core@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry/core@npm:8.40.0" + dependencies: + "@sentry/types": 8.40.0 + checksum: db80e8410401c3dfd09d1bad0508e592b44082e58a44a354b9332f4dc7295e3f98bcdfb36e542e43d2e66fbfbb8f1e8035feb91994e7f40a2a869d1de979b1d2 + languageName: node + linkType: hard + +"@sentry/react-native@npm:^6.3.0": + version: 6.3.0 + resolution: "@sentry/react-native@npm:6.3.0" + dependencies: + "@sentry/babel-plugin-component-annotate": 2.20.1 + "@sentry/browser": 8.40.0 + "@sentry/cli": 2.38.2 + "@sentry/core": 8.40.0 + "@sentry/react": 8.40.0 + "@sentry/types": 8.40.0 + "@sentry/utils": 8.40.0 + peerDependencies: + expo: ">=49.0.0" + react: ">=17.0.0" + react-native: ">=0.65.0" + peerDependenciesMeta: + expo: + optional: true + bin: + sentry-expo-upload-sourcemaps: scripts/expo-upload-sourcemaps.js + checksum: 5968e3a84852ddee1b23c0ffeba55adcf3ef4325a77179e818a6e8bb7fa7c5f9cb8eed5f017904bcf5da813b3b1a7d10a1b2ff069a3a87249276f9b080197610 + languageName: node + linkType: hard + +"@sentry/react@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry/react@npm:8.40.0" + dependencies: + "@sentry/browser": 8.40.0 + "@sentry/core": 8.40.0 + "@sentry/types": 8.40.0 + hoist-non-react-statics: ^3.3.2 + peerDependencies: + react: ^16.14.0 || 17.x || 18.x || 19.x + checksum: 53276804fb6cb82c7ed69d62db3452c55c2dc275f4afe0a07e6572b0855231b64d487a4b13fc843ee319b5e6390c34245b80e3e6784d799cc2e14f814fedf920 + languageName: node + linkType: hard + +"@sentry/types@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry/types@npm:8.40.0" + checksum: eb7fbb606d9178559de219e86da5dfefa78a8825d5baf03a2eb8fafdcdfb51898bde943edc375adc8cb71fcae2d854b019088c6a045541e94a1c6c1c9779cfe8 + languageName: node + linkType: hard + +"@sentry/utils@npm:8.40.0": + version: 8.40.0 + resolution: "@sentry/utils@npm:8.40.0" + dependencies: + "@sentry/core": 8.40.0 + "@sentry/types": 8.40.0 + checksum: 18d1c5a1b259dbe6ceee9ae7ef22fa0cb0cc5650ecfe51dc2d9367992981d4ce9d5d8267e27ab2e32ca56525c11b0f3a1f41e34492d14d0059e3983d18042fd0 + languageName: node + linkType: hard + "@sideway/address@npm:^4.1.5": version: 4.1.5 resolution: "@sideway/address@npm:4.1.5" @@ -4134,6 +4347,15 @@ __metadata: languageName: node linkType: hard +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: 4 + checksum: f52b6872cc96fd5f622071b71ef200e01c7c4c454ee68bc9accca90c98cfb39f2810e3e9aa330435835eedc8c23f4f8a15267f67c6e245d2b33757575bdac49d + languageName: node + linkType: hard + "agent-base@npm:^7.0.2, agent-base@npm:^7.1.0, agent-base@npm:^7.1.1": version: 7.1.1 resolution: "agent-base@npm:7.1.1" @@ -8048,6 +8270,15 @@ __metadata: languageName: node linkType: hard +"hoist-non-react-statics@npm:^3.3.2": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: ^16.7.0 + checksum: b1538270429b13901ee586aa44f4cc3ecd8831c061d06cb8322e50ea17b3f5ce4d0e2e66394761e6c8e152cd8c34fb3b4b690116c6ce2bd45b18c746516cb9e8 + languageName: node + linkType: hard + "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -8110,6 +8341,16 @@ __metadata: languageName: node linkType: hard +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: 6 + debug: 4 + checksum: 571fccdf38184f05943e12d37d6ce38197becdd69e58d03f43637f7fa1269cf303a7d228aa27e5b27bbd3af8f09fd938e1c91dcfefff2df7ba77c20ed8dfc765 + languageName: node + linkType: hard + "https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.3, https-proxy-agent@npm:^7.0.5": version: 7.0.5 resolution: "https-proxy-agent@npm:7.0.5" @@ -11110,7 +11351,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12": +"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -12198,7 +12439,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.13.1": +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": version: 16.13.1 resolution: "react-is@npm:16.13.1" checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f @@ -12333,6 +12574,7 @@ __metadata: "@react-native/babel-preset": 0.76.3 "@react-native/eslint-config": ^0.73.1 "@release-it/conventional-changelog": ^9.0.2 + "@sentry/react-native": ^6.3.0 "@testing-library/react-native": ^12.9.0 "@types/jest": ^29.5.5 "@types/lodash": ^4.17.13