From f85305a0abcfbb0f209e13a106a18e8a3213a5ea Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 07:23:43 +0100 Subject: [PATCH 01/15] ref(utils): dsn --- packages/utils/src/dsn.ts | 200 ++++++++++++++++++++------------------ 1 file changed, 105 insertions(+), 95 deletions(-) diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 89752e6ccfdf..99bef7686186 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -9,6 +9,110 @@ const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+ /** Error message */ const ERROR_MESSAGE = 'Invalid Dsn'; +function isValidProtocol(protocol?: string): protocol is DsnProtocol { + return protocol === 'http' || protocol === 'https'; +} + +/** + * Renders the string representation of this Dsn. + * + * By default, this will render the public representation without the password + * component. To get the deprecated private representation, set `withPassword` + * to true. + * + * @param withPassword When set to true, the password will be included. + */ +function dsntoString(dsn: Dsn, withPassword: boolean = false): string { + const { host, path, pass, port, projectId, protocol, publicKey } = dsn; + return ( + `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + + `@${host}${port ? `:${port}` : ''}/${path ? `${path}/` : path}${projectId}` + ); +} + +function dsnFromString(str: string): Dsn { + const match = DSN_REGEX.exec(str); + + if (!match) { + throw new SentryError(ERROR_MESSAGE); + } + + const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1); + let path = ''; + let projectId = lastPath; + + const split = projectId.split('/'); + if (split.length > 1) { + path = split.slice(0, -1).join('/'); + projectId = split.pop() as string; + } + + if (projectId) { + const projectMatch = projectId.match(/^\d+/); + if (projectMatch) { + projectId = projectMatch[0]; + } + } + + if (isValidProtocol(protocol)) { + return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol, publicKey }); + } + + throw new SentryError(ERROR_MESSAGE); +} + +function dsnFromComponents(components: DsnComponents): Dsn { + // TODO this is for backwards compatibility, and can be removed in a future version + if ('user' in components && !('publicKey' in components)) { + components.publicKey = components.user; + } + + return { + user: components.publicKey || '', + protocol: components.protocol, + publicKey: components.publicKey || '', + pass: components.pass || '', + host: components.host, + port: components.port || '', + path: components.path || '', + projectId: components.projectId, + }; +} + +function validateDsn(dsn: Dsn): boolean { + if (isDebugBuild()) { + const { port, projectId, protocol } = dsn; + + ['protocol', 'publicKey', 'host', 'projectId'].forEach(component => { + if (!dsn[component]) { + throw new SentryError(`${ERROR_MESSAGE}: ${component} missing`); + } + }); + + if (!projectId.match(/^\d+$/)) { + throw new SentryError(`${ERROR_MESSAGE}: Invalid projectId ${projectId}`); + } + + if (isValidProtocol(protocol)) { + throw new SentryError(`${ERROR_MESSAGE}: Invalid protocol ${protocol}`); + } + + if (port && isNaN(parseInt(port, 10))) { + throw new SentryError(`${ERROR_MESSAGE}: Invalid port ${port}`); + } + } + + return true; +} + +function makeDsn(from: DsnLike): Dsn { + let dsn = typeof from === 'string' ? dsnFromString(from) : dsnFromComponents(from); + + return { + ...dsn, + toString: () => dsntoString(dsn), + }; +} /** The Sentry Dsn, identifying a Sentry instance and project. */ export class Dsn implements DsnComponents { /** Protocol used to connect to Sentry. */ @@ -29,99 +133,5 @@ export class Dsn implements DsnComponents { public projectId!: string; /** Creates a new Dsn component */ - public constructor(from: DsnLike) { - if (typeof from === 'string') { - this._fromString(from); - } else { - this._fromComponents(from); - } - - this._validate(); - } - - /** - * Renders the string representation of this Dsn. - * - * By default, this will render the public representation without the password - * component. To get the deprecated private representation, set `withPassword` - * to true. - * - * @param withPassword When set to true, the password will be included. - */ - public toString(withPassword: boolean = false): string { - const { host, path, pass, port, projectId, protocol, publicKey } = this; - return ( - `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + - `@${host}${port ? `:${port}` : ''}/${path ? `${path}/` : path}${projectId}` - ); - } - - /** Parses a string into this Dsn. */ - private _fromString(str: string): void { - const match = DSN_REGEX.exec(str); - - if (!match) { - throw new SentryError(ERROR_MESSAGE); - } - - const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1); - let path = ''; - let projectId = lastPath; - - const split = projectId.split('/'); - if (split.length > 1) { - path = split.slice(0, -1).join('/'); - projectId = split.pop() as string; - } - - if (projectId) { - const projectMatch = projectId.match(/^\d+/); - if (projectMatch) { - projectId = projectMatch[0]; - } - } - - this._fromComponents({ host, pass, path, projectId, port, protocol: protocol as DsnProtocol, publicKey }); - } - - /** Maps Dsn components into this instance. */ - private _fromComponents(components: DsnComponents): void { - // TODO this is for backwards compatibility, and can be removed in a future version - if ('user' in components && !('publicKey' in components)) { - components.publicKey = components.user; - } - this.user = components.publicKey || ''; - - this.protocol = components.protocol; - this.publicKey = components.publicKey || ''; - this.pass = components.pass || ''; - this.host = components.host; - this.port = components.port || ''; - this.path = components.path || ''; - this.projectId = components.projectId; - } - - /** Validates this Dsn and throws on error. */ - private _validate(): void { - // we only validate in debug mode. This will fail later anyways. - if (isDebugBuild()) { - ['protocol', 'publicKey', 'host', 'projectId'].forEach(component => { - if (!this[component as keyof DsnComponents]) { - throw new SentryError(`${ERROR_MESSAGE}: ${component} missing`); - } - }); - - if (!this.projectId.match(/^\d+$/)) { - throw new SentryError(`${ERROR_MESSAGE}: Invalid projectId ${this.projectId}`); - } - - if (this.protocol !== 'http' && this.protocol !== 'https') { - throw new SentryError(`${ERROR_MESSAGE}: Invalid protocol ${this.protocol}`); - } - - if (this.port && isNaN(parseInt(this.port, 10))) { - throw new SentryError(`${ERROR_MESSAGE}: Invalid port ${this.port}`); - } - } - } + public constructor(from: DsnLike) {} } From f15a9b9fed52c374032babb6417ca8891b7ff4c3 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 07:41:18 +0100 Subject: [PATCH 02/15] ref(utils): dsn --- packages/core/src/api.ts | 8 +- packages/core/src/baseclient.ts | 3 +- packages/core/test/lib/api.test.ts | 8 +- packages/node/src/backend.ts | 4 +- packages/utils/src/dsn.ts | 64 +++++--------- packages/utils/test/dsn.test.ts | 132 ++++++++++++++--------------- 6 files changed, 98 insertions(+), 121 deletions(-) diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index 78369d80fcec..ebdc525d9bcb 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -1,5 +1,5 @@ import { DsnLike, SdkMetadata } from '@sentry/types'; -import { Dsn, urlEncode } from '@sentry/utils'; +import { Dsn, makeDsn, urlEncode } from '@sentry/utils'; const SENTRY_API_VERSION = '7'; @@ -40,7 +40,7 @@ export class API { /** Create a new instance of API */ public constructor(dsn: DsnLike, metadata: SdkMetadata = {}, tunnel?: string) { this.dsn = dsn; - this._dsnObject = new Dsn(dsn); + this._dsnObject = makeDsn(dsn); this.metadata = metadata; this._tunnel = tunnel; } @@ -89,7 +89,7 @@ export function initAPIDetails(dsn: DsnLike, metadata?: SdkMetadata, tunnel?: st return { initDsn: dsn, metadata: metadata || {}, - dsn: new Dsn(dsn), + dsn: makeDsn(dsn), tunnel, } as APIDetails; } @@ -171,7 +171,7 @@ export function getReportDialogEndpoint( user?: { name?: string; email?: string }; }, ): string { - const dsn = new Dsn(dsnLike); + const dsn = makeDsn(dsnLike); const endpoint = `${getBaseApiEndpoint(dsn)}embed/error-page/`; let encodedOptions = `dsn=${dsn.toString()}`; diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 8ff1bb60e739..929c0cf700e3 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -18,6 +18,7 @@ import { isPrimitive, isThenable, logger, + makeDsn, normalize, rejectedSyncPromise, resolvedSyncPromise, @@ -95,7 +96,7 @@ export abstract class BaseClient implement this._options = options; if (options.dsn) { - this._dsn = new Dsn(options.dsn); + this._dsn = makeDsn(options.dsn); } } diff --git a/packages/core/test/lib/api.test.ts b/packages/core/test/lib/api.test.ts index fd2e97b6d77d..672cc896d21d 100644 --- a/packages/core/test/lib/api.test.ts +++ b/packages/core/test/lib/api.test.ts @@ -1,5 +1,5 @@ /* eslint-disable deprecation/deprecation */ -import { Dsn } from '@sentry/utils'; +import { makeDsn } from '@sentry/utils'; import { API, getReportDialogEndpoint, getRequestHeaders } from '../../src/api'; @@ -25,12 +25,12 @@ describe('API', () => { }); test('getRequestHeaders', () => { - expect(getRequestHeaders(new Dsn(dsnPublic), 'a', '1.0')).toMatchObject({ + expect(getRequestHeaders(makeDsn(dsnPublic), 'a', '1.0')).toMatchObject({ 'Content-Type': 'application/json', 'X-Sentry-Auth': expect.stringMatching(/^Sentry sentry_version=\d, sentry_client=a\/1\.0, sentry_key=abc$/), }); - expect(getRequestHeaders(new Dsn(legacyDsn), 'a', '1.0')).toMatchObject({ + expect(getRequestHeaders(makeDsn(legacyDsn), 'a', '1.0')).toMatchObject({ 'Content-Type': 'application/json', 'X-Sentry-Auth': expect.stringMatching( /^Sentry sentry_version=\d, sentry_client=a\/1\.0, sentry_key=abc, sentry_secret=123$/, @@ -119,6 +119,6 @@ describe('API', () => { }); test('getDsn', () => { - expect(new API(dsnPublic).getDsn()).toEqual(new Dsn(dsnPublic)); + expect(new API(dsnPublic).getDsn()).toEqual(makeDsn(dsnPublic)); }); }); diff --git a/packages/node/src/backend.ts b/packages/node/src/backend.ts index c18dd3aaafd9..83070462ba5e 100644 --- a/packages/node/src/backend.ts +++ b/packages/node/src/backend.ts @@ -1,6 +1,6 @@ import { BaseBackend } from '@sentry/core'; import { Event, EventHint, SeverityLevel, Transport, TransportOptions } from '@sentry/types'; -import { Dsn } from '@sentry/utils'; +import { makeDsn } from '@sentry/utils'; import { eventFromException, eventFromMessage } from './eventbuilder'; import { HTTPSTransport, HTTPTransport } from './transports'; @@ -35,7 +35,7 @@ export class NodeBackend extends BaseBackend { return super._setupTransport(); } - const dsn = new Dsn(this._options.dsn); + const dsn = makeDsn(this._options.dsn); const transportOptions: TransportOptions = { ...this._options.transportOptions, diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 99bef7686186..7f75de1d48d1 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -3,12 +3,14 @@ import { DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { isDebugBuild } from './env'; import { SentryError } from './error'; +export interface Dsn extends DsnComponents { + /** Protocol used to connect to Sentry. */ + toString(withPassword: boolean): string; +} + /** Regular expression used to parse a Dsn. */ const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+)/; -/** Error message */ -const ERROR_MESSAGE = 'Invalid Dsn'; - function isValidProtocol(protocol?: string): protocol is DsnProtocol { return protocol === 'http' || protocol === 'https'; } @@ -34,7 +36,7 @@ function dsnFromString(str: string): Dsn { const match = DSN_REGEX.exec(str); if (!match) { - throw new SentryError(ERROR_MESSAGE); + throw new SentryError('Invalid Dsn'); } const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1); @@ -54,11 +56,7 @@ function dsnFromString(str: string): Dsn { } } - if (isValidProtocol(protocol)) { - return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol, publicKey }); - } - - throw new SentryError(ERROR_MESSAGE); + return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol as DsnProtocol, publicKey }); } function dsnFromComponents(components: DsnComponents): Dsn { @@ -83,55 +81,39 @@ function validateDsn(dsn: Dsn): boolean { if (isDebugBuild()) { const { port, projectId, protocol } = dsn; - ['protocol', 'publicKey', 'host', 'projectId'].forEach(component => { + const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'host', 'projectId']; + requiredComponents.forEach(component => { if (!dsn[component]) { - throw new SentryError(`${ERROR_MESSAGE}: ${component} missing`); + throw new SentryError(`Invalid Dsn: ${component} missing`); } }); if (!projectId.match(/^\d+$/)) { - throw new SentryError(`${ERROR_MESSAGE}: Invalid projectId ${projectId}`); + throw new SentryError(`Invalid Dsn: Invalid projectId ${projectId}`); } if (isValidProtocol(protocol)) { - throw new SentryError(`${ERROR_MESSAGE}: Invalid protocol ${protocol}`); + throw new SentryError(`Invalid Dsn: Invalid protocol ${protocol}`); } if (port && isNaN(parseInt(port, 10))) { - throw new SentryError(`${ERROR_MESSAGE}: Invalid port ${port}`); + throw new SentryError(`Invalid Dsn: Invalid port ${port}`); } } return true; } -function makeDsn(from: DsnLike): Dsn { - let dsn = typeof from === 'string' ? dsnFromString(from) : dsnFromComponents(from); +/** The Sentry Dsn, identifying a Sentry instance and project. */ +export function makeDsn(from: DsnLike): Dsn { + const components = typeof from === 'string' ? dsnFromString(from) : dsnFromComponents(from); - return { - ...dsn, - toString: () => dsntoString(dsn), + validateDsn(components); + + const dsn: Dsn = { + ...components, + toString: (withPassword: boolean) => dsntoString(dsn), }; -} -/** The Sentry Dsn, identifying a Sentry instance and project. */ -export class Dsn implements DsnComponents { - /** Protocol used to connect to Sentry. */ - public protocol!: DsnProtocol; - /** Public authorization key (deprecated, renamed to publicKey). */ - public user!: string; - /** Public authorization key. */ - public publicKey!: string; - /** Private authorization key (deprecated, optional). */ - public pass!: string; - /** Hostname of the Sentry instance. */ - public host!: string; - /** Port of the Sentry instance. */ - public port!: string; - /** Path */ - public path!: string; - /** Project ID */ - public projectId!: string; - - /** Creates a new Dsn component */ - public constructor(from: DsnLike) {} + + return dsn; } diff --git a/packages/utils/test/dsn.test.ts b/packages/utils/test/dsn.test.ts index 804fbe41536c..fb78b2a0b297 100644 --- a/packages/utils/test/dsn.test.ts +++ b/packages/utils/test/dsn.test.ts @@ -1,6 +1,6 @@ import { isDebugBuild } from '@sentry/utils'; -import { Dsn } from '../src/dsn'; +import { makeDsn } from '../src/dsn'; import { SentryError } from '../src/error'; function testIf(condition: boolean): jest.It { @@ -10,7 +10,7 @@ function testIf(condition: boolean): jest.It { describe('Dsn', () => { describe('fromComponents', () => { test('applies all components', () => { - const dsn = new Dsn({ + const dsn = makeDsn({ host: 'sentry.io', pass: 'xyz', port: '1234', @@ -28,7 +28,7 @@ describe('Dsn', () => { }); test('applies partial components', () => { - const dsn = new Dsn({ + const dsn = makeDsn({ host: 'sentry.io', projectId: '123', protocol: 'https', @@ -44,70 +44,64 @@ describe('Dsn', () => { }); testIf(isDebugBuild())('throws for missing components', () => { - expect( - () => - new Dsn({ - host: '', - projectId: '123', - protocol: 'https', - publicKey: 'abc', - }), + expect(() => + makeDsn({ + host: '', + projectId: '123', + protocol: 'https', + publicKey: 'abc', + }), ).toThrow(SentryError); - expect( - () => - new Dsn({ - host: 'sentry.io', - projectId: '', - protocol: 'https', - publicKey: 'abc', - }), + expect(() => + makeDsn({ + host: 'sentry.io', + projectId: '', + protocol: 'https', + publicKey: 'abc', + }), ).toThrow(SentryError); - expect( - () => - new Dsn({ - host: 'sentry.io', - projectId: '123', - protocol: '' as 'http', // Trick the type checker here - publicKey: 'abc', - }), + expect(() => + makeDsn({ + host: 'sentry.io', + projectId: '123', + protocol: '' as 'http', // Trick the type checker here + publicKey: 'abc', + }), ).toThrow(SentryError); - expect( - () => - new Dsn({ - host: 'sentry.io', - projectId: '123', - protocol: 'https', - publicKey: '', - }), + expect(() => + makeDsn({ + host: 'sentry.io', + projectId: '123', + protocol: 'https', + publicKey: '', + }), ).toThrow(SentryError); }); testIf(isDebugBuild())('throws for invalid components', () => { - expect( - () => - new Dsn({ - host: 'sentry.io', - projectId: '123', - protocol: 'httpx' as 'http', // Trick the type checker here - publicKey: 'abc', - }), + expect(() => + makeDsn({ + host: 'sentry.io', + projectId: '123', + protocol: 'httpx' as 'http', // Trick the type checker here + publicKey: 'abc', + }), ).toThrow(SentryError); - expect( - () => - new Dsn({ - host: 'sentry.io', - port: 'xxx', - projectId: '123', - protocol: 'https', - publicKey: 'abc', - }), + expect(() => + makeDsn({ + host: 'sentry.io', + port: 'xxx', + projectId: '123', + protocol: 'https', + publicKey: 'abc', + }), ).toThrow(SentryError); }); }); describe('fromString', () => { test('parses a valid full Dsn', () => { - const dsn = new Dsn('https://abc:xyz@sentry.io:1234/123'); + const dsn = makeDsn('https://abc:xyz@sentry.io:1234/123'); expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe('xyz'); @@ -118,7 +112,7 @@ describe('Dsn', () => { }); test('parses a valid partial Dsn', () => { - const dsn = new Dsn('https://abc@sentry.io/123/321'); + const dsn = makeDsn('https://abc@sentry.io/123/321'); expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); @@ -129,7 +123,7 @@ describe('Dsn', () => { }); test('with a long path', () => { - const dsn = new Dsn('https://abc@sentry.io/sentry/custom/installation/321'); + const dsn = makeDsn('https://abc@sentry.io/sentry/custom/installation/321'); expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); @@ -140,7 +134,7 @@ describe('Dsn', () => { }); test('with a query string', () => { - const dsn = new Dsn('https://abc@sentry.io/321?sample.rate=0.1&other=value'); + const dsn = makeDsn('https://abc@sentry.io/321?sample.rate=0.1&other=value'); expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); @@ -151,46 +145,46 @@ describe('Dsn', () => { }); testIf(isDebugBuild())('throws when provided invalid Dsn', () => { - expect(() => new Dsn('some@random.dsn')).toThrow(SentryError); + expect(() => makeDsn('some@random.dsn')).toThrow(SentryError); }); testIf(isDebugBuild())('throws without mandatory fields', () => { - expect(() => new Dsn('://abc@sentry.io/123')).toThrow(SentryError); - expect(() => new Dsn('https://@sentry.io/123')).toThrow(SentryError); - expect(() => new Dsn('https://abc@123')).toThrow(SentryError); - expect(() => new Dsn('https://abc@sentry.io/')).toThrow(SentryError); + expect(() => makeDsn('://abc@sentry.io/123')).toThrow(SentryError); + expect(() => makeDsn('https://@sentry.io/123')).toThrow(SentryError); + expect(() => makeDsn('https://abc@123')).toThrow(SentryError); + expect(() => makeDsn('https://abc@sentry.io/')).toThrow(SentryError); }); testIf(isDebugBuild())('throws for invalid fields', () => { - expect(() => new Dsn('httpx://abc@sentry.io/123')).toThrow(SentryError); - expect(() => new Dsn('httpx://abc@sentry.io:xxx/123')).toThrow(SentryError); - expect(() => new Dsn('http://abc@sentry.io/abc')).toThrow(SentryError); + expect(() => makeDsn('httpx://abc@sentry.io/123')).toThrow(SentryError); + expect(() => makeDsn('httpx://abc@sentry.io:xxx/123')).toThrow(SentryError); + expect(() => makeDsn('http://abc@sentry.io/abc')).toThrow(SentryError); }); }); describe('toString', () => { test('excludes the password by default', () => { - const dsn = new Dsn('https://abc:xyz@sentry.io:1234/123'); + const dsn = makeDsn('https://abc:xyz@sentry.io:1234/123'); expect(dsn.toString()).toBe('https://abc@sentry.io:1234/123'); }); test('optionally includes the password', () => { - const dsn = new Dsn('https://abc:xyz@sentry.io:1234/123'); + const dsn = makeDsn('https://abc:xyz@sentry.io:1234/123'); expect(dsn.toString(true)).toBe('https://abc:xyz@sentry.io:1234/123'); }); test('renders no password if missing', () => { - const dsn = new Dsn('https://abc@sentry.io:1234/123'); + const dsn = makeDsn('https://abc@sentry.io:1234/123'); expect(dsn.toString(true)).toBe('https://abc@sentry.io:1234/123'); }); test('renders no port if missing', () => { - const dsn = new Dsn('https://abc@sentry.io/123'); + const dsn = makeDsn('https://abc@sentry.io/123'); expect(dsn.toString()).toBe('https://abc@sentry.io/123'); }); test('renders the full path correctly', () => { - const dsn = new Dsn('https://abc@sentry.io/sentry/custom/installation/321'); + const dsn = makeDsn('https://abc@sentry.io/sentry/custom/installation/321'); expect(dsn.toString()).toBe('https://abc@sentry.io/sentry/custom/installation/321'); }); }); From 8bded4f97f3f0ccb3bc05959a54a240f29459e81 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 07:48:18 +0100 Subject: [PATCH 03/15] ref(utils): dsn --- packages/core/src/api.ts | 4 ++-- packages/core/src/baseclient.ts | 2 +- packages/types/src/dsn.ts | 2 +- packages/utils/src/dsn.ts | 11 +++-------- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index ebdc525d9bcb..c8f1ef011ec5 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -1,5 +1,5 @@ -import { DsnLike, SdkMetadata } from '@sentry/types'; -import { Dsn, makeDsn, urlEncode } from '@sentry/utils'; +import { Dsn, DsnLike, SdkMetadata } from '@sentry/types'; +import { makeDsn, urlEncode } from '@sentry/utils'; const SENTRY_API_VERSION = '7'; diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 929c0cf700e3..e3aa96684f16 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -2,6 +2,7 @@ import { Scope, Session } from '@sentry/hub'; import { Client, + Dsn, Event, EventHint, Integration, @@ -13,7 +14,6 @@ import { import { checkOrSetAlreadyCaught, dateTimestampInSeconds, - Dsn, isPlainObject, isPrimitive, isThenable, diff --git a/packages/types/src/dsn.ts b/packages/types/src/dsn.ts index b9f757392533..a80ce99003d0 100644 --- a/packages/types/src/dsn.ts +++ b/packages/types/src/dsn.ts @@ -35,5 +35,5 @@ export interface Dsn extends DsnComponents { * * @param withPassword When set to true, the password will be included. */ - toString(withPassword: boolean): string; + toString(withPassword?: boolean): string; } diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 7f75de1d48d1..962bb430b4c9 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -1,13 +1,8 @@ -import { DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; +import { Dsn, DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { isDebugBuild } from './env'; import { SentryError } from './error'; -export interface Dsn extends DsnComponents { - /** Protocol used to connect to Sentry. */ - toString(withPassword: boolean): string; -} - /** Regular expression used to parse a Dsn. */ const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+)/; @@ -92,7 +87,7 @@ function validateDsn(dsn: Dsn): boolean { throw new SentryError(`Invalid Dsn: Invalid projectId ${projectId}`); } - if (isValidProtocol(protocol)) { + if (!isValidProtocol(protocol)) { throw new SentryError(`Invalid Dsn: Invalid protocol ${protocol}`); } @@ -112,7 +107,7 @@ export function makeDsn(from: DsnLike): Dsn { const dsn: Dsn = { ...components, - toString: (withPassword: boolean) => dsntoString(dsn), + toString: (withPassword: boolean) => dsntoString(dsn, withPassword), }; return dsn; From 3787261f57b5788df8fa6e7c09121cb7cea2c3bb Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 09:40:14 +0100 Subject: [PATCH 04/15] ref(core): simplify dsn parsing --- packages/utils/src/dsn.ts | 49 ++++++++++++++------------------- packages/utils/test/dsn.test.ts | 12 ++++---- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 962bb430b4c9..7bb71ed3e054 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -3,13 +3,14 @@ import { Dsn, DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { isDebugBuild } from './env'; import { SentryError } from './error'; -/** Regular expression used to parse a Dsn. */ -const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+)/; - function isValidProtocol(protocol?: string): protocol is DsnProtocol { return protocol === 'http' || protocol === 'https'; } +function normalizeProtocol(input: string): string { + return input.replace(/:$/, ''); +} + /** * Renders the string representation of this Dsn. * @@ -20,38 +21,28 @@ function isValidProtocol(protocol?: string): protocol is DsnProtocol { * @param withPassword When set to true, the password will be included. */ function dsntoString(dsn: Dsn, withPassword: boolean = false): string { - const { host, path, pass, port, projectId, protocol, publicKey } = dsn; + const { host, port, path, pass, projectId, protocol, publicKey } = dsn; return ( `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + - `@${host}${port ? `:${port}` : ''}/${path ? `${path}/` : path}${projectId}` + `@${host}${port ? `:${port}` : ''}${path}/${projectId}` ); } function dsnFromString(str: string): Dsn { - const match = DSN_REGEX.exec(str); - - if (!match) { - throw new SentryError('Invalid Dsn'); - } - - const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1); - let path = ''; - let projectId = lastPath; - - const split = projectId.split('/'); - if (split.length > 1) { - path = split.slice(0, -1).join('/'); - projectId = split.pop() as string; - } - - if (projectId) { - const projectMatch = projectId.match(/^\d+/); - if (projectMatch) { - projectId = projectMatch[0]; - } - } - - return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol as DsnProtocol, publicKey }); + const url = new URL(str); + + const pathComponents = url.pathname.split('/'); + const projectId = pathComponents.pop(); + + return dsnFromComponents({ + host: url.hostname, + pass: url.password, + path: pathComponents.join('/'), + projectId: projectId || '', + port: url.port, + protocol: normalizeProtocol(url.protocol) as DsnProtocol, + publicKey: url.username, + }); } function dsnFromComponents(components: DsnComponents): Dsn { diff --git a/packages/utils/test/dsn.test.ts b/packages/utils/test/dsn.test.ts index fb78b2a0b297..bb4e3d81548c 100644 --- a/packages/utils/test/dsn.test.ts +++ b/packages/utils/test/dsn.test.ts @@ -48,7 +48,7 @@ describe('Dsn', () => { makeDsn({ host: '', projectId: '123', - protocol: 'https', + protocol: 'https:', publicKey: 'abc', }), ).toThrow(SentryError); @@ -56,7 +56,7 @@ describe('Dsn', () => { makeDsn({ host: 'sentry.io', projectId: '', - protocol: 'https', + protocol: 'https:', publicKey: 'abc', }), ).toThrow(SentryError); @@ -72,7 +72,7 @@ describe('Dsn', () => { makeDsn({ host: 'sentry.io', projectId: '123', - protocol: 'https', + protocol: 'https:', publicKey: '', }), ).toThrow(SentryError); @@ -92,7 +92,7 @@ describe('Dsn', () => { host: 'sentry.io', port: 'xxx', projectId: '123', - protocol: 'https', + protocol: 'https:', publicKey: 'abc', }), ).toThrow(SentryError); @@ -118,7 +118,7 @@ describe('Dsn', () => { expect(dsn.pass).toBe(''); expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); - expect(dsn.path).toBe('123'); + expect(dsn.path).toBe('/123'); expect(dsn.projectId).toBe('321'); }); @@ -129,7 +129,7 @@ describe('Dsn', () => { expect(dsn.pass).toBe(''); expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); - expect(dsn.path).toBe('sentry/custom/installation'); + expect(dsn.path).toBe('/sentry/custom/installation'); expect(dsn.projectId).toBe('321'); }); From ad2092189070cf39187584c55243ba408779bb49 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 11:23:20 +0100 Subject: [PATCH 05/15] ref(utils): get global url --- packages/core/src/api.ts | 2 +- packages/node/src/integrations/utils/http.ts | 2 +- packages/types/src/dsn.ts | 2 +- packages/utils/src/dsn.ts | 14 ++++---- packages/utils/test/dsn.test.ts | 36 ++++++++++---------- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index c8f1ef011ec5..7ea5a5f99ddb 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -98,7 +98,7 @@ export function initAPIDetails(dsn: DsnLike, metadata?: SdkMetadata, tunnel?: st function getBaseApiEndpoint(dsn: Dsn): string { const protocol = dsn.protocol ? `${dsn.protocol}:` : ''; const port = dsn.port ? `:${dsn.port}` : ''; - return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`; + return `${protocol}//${dsn.hostname}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`; } /** Returns the ingest API endpoint for target. */ diff --git a/packages/node/src/integrations/utils/http.ts b/packages/node/src/integrations/utils/http.ts index bb0e9a76cf00..fb3342815095 100644 --- a/packages/node/src/integrations/utils/http.ts +++ b/packages/node/src/integrations/utils/http.ts @@ -14,7 +14,7 @@ export function isSentryRequest(url: string): boolean { const dsn = getCurrentHub() .getClient() ?.getDsn(); - return dsn ? url.includes(dsn.host) : false; + return dsn ? url.includes(dsn.hostname) : false; } /** diff --git a/packages/types/src/dsn.ts b/packages/types/src/dsn.ts index a80ce99003d0..9f0aed000b1b 100644 --- a/packages/types/src/dsn.ts +++ b/packages/types/src/dsn.ts @@ -12,7 +12,7 @@ export interface DsnComponents { /** Private authorization key (deprecated, optional). */ pass?: string; /** Hostname of the Sentry instance. */ - host: string; + hostname: string; /** Port of the Sentry instance. */ port?: string; /** Sub path/ */ diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 7bb71ed3e054..89ea16bf89fa 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -2,6 +2,7 @@ import { Dsn, DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { isDebugBuild } from './env'; import { SentryError } from './error'; +import { getGlobalObject } from './global'; function isValidProtocol(protocol?: string): protocol is DsnProtocol { return protocol === 'http' || protocol === 'https'; @@ -21,21 +22,22 @@ function normalizeProtocol(input: string): string { * @param withPassword When set to true, the password will be included. */ function dsntoString(dsn: Dsn, withPassword: boolean = false): string { - const { host, port, path, pass, projectId, protocol, publicKey } = dsn; + const { hostname, port, path, pass, projectId, protocol, publicKey } = dsn; return ( `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + - `@${host}${port ? `:${port}` : ''}${path}/${projectId}` + `@${hostname}${port ? `:${port}` : ''}${path}/${projectId}` ); } function dsnFromString(str: string): Dsn { - const url = new URL(str); + const global = getGlobalObject<{ URL: typeof URL }>(); + const url = new global.URL(str); const pathComponents = url.pathname.split('/'); const projectId = pathComponents.pop(); return dsnFromComponents({ - host: url.hostname, + hostname: url.hostname, pass: url.password, path: pathComponents.join('/'), projectId: projectId || '', @@ -56,7 +58,7 @@ function dsnFromComponents(components: DsnComponents): Dsn { protocol: components.protocol, publicKey: components.publicKey || '', pass: components.pass || '', - host: components.host, + hostname: components.hostname, port: components.port || '', path: components.path || '', projectId: components.projectId, @@ -67,7 +69,7 @@ function validateDsn(dsn: Dsn): boolean { if (isDebugBuild()) { const { port, projectId, protocol } = dsn; - const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'host', 'projectId']; + const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'hostname', 'projectId']; requiredComponents.forEach(component => { if (!dsn[component]) { throw new SentryError(`Invalid Dsn: ${component} missing`); diff --git a/packages/utils/test/dsn.test.ts b/packages/utils/test/dsn.test.ts index bb4e3d81548c..9aa88c151bab 100644 --- a/packages/utils/test/dsn.test.ts +++ b/packages/utils/test/dsn.test.ts @@ -11,7 +11,7 @@ describe('Dsn', () => { describe('fromComponents', () => { test('applies all components', () => { const dsn = makeDsn({ - host: 'sentry.io', + hostname: 'sentry.io', pass: 'xyz', port: '1234', projectId: '123', @@ -21,7 +21,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe('xyz'); - expect(dsn.host).toBe('sentry.io'); + expect(dsn.hostname).toBe('sentry.io'); expect(dsn.port).toBe('1234'); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('123'); @@ -29,7 +29,7 @@ describe('Dsn', () => { test('applies partial components', () => { const dsn = makeDsn({ - host: 'sentry.io', + hostname: 'sentry.io', projectId: '123', protocol: 'https', publicKey: 'abc', @@ -37,7 +37,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.host).toBe('sentry.io'); + expect(dsn.hostname).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('123'); @@ -46,23 +46,23 @@ describe('Dsn', () => { testIf(isDebugBuild())('throws for missing components', () => { expect(() => makeDsn({ - host: '', + hostname: '', projectId: '123', - protocol: 'https:', + protocol: 'https', publicKey: 'abc', }), ).toThrow(SentryError); expect(() => makeDsn({ - host: 'sentry.io', + hostname: 'sentry.io', projectId: '', - protocol: 'https:', + protocol: 'https', publicKey: 'abc', }), ).toThrow(SentryError); expect(() => makeDsn({ - host: 'sentry.io', + hostname: 'sentry.io', projectId: '123', protocol: '' as 'http', // Trick the type checker here publicKey: 'abc', @@ -70,9 +70,9 @@ describe('Dsn', () => { ).toThrow(SentryError); expect(() => makeDsn({ - host: 'sentry.io', + hostname: 'sentry.io', projectId: '123', - protocol: 'https:', + protocol: 'https', publicKey: '', }), ).toThrow(SentryError); @@ -81,7 +81,7 @@ describe('Dsn', () => { testIf(isDebugBuild())('throws for invalid components', () => { expect(() => makeDsn({ - host: 'sentry.io', + hostname: 'sentry.io', projectId: '123', protocol: 'httpx' as 'http', // Trick the type checker here publicKey: 'abc', @@ -89,10 +89,10 @@ describe('Dsn', () => { ).toThrow(SentryError); expect(() => makeDsn({ - host: 'sentry.io', + hostname: 'sentry.io', port: 'xxx', projectId: '123', - protocol: 'https:', + protocol: 'https', publicKey: 'abc', }), ).toThrow(SentryError); @@ -105,7 +105,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe('xyz'); - expect(dsn.host).toBe('sentry.io'); + expect(dsn.hostname).toBe('sentry.io'); expect(dsn.port).toBe('1234'); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('123'); @@ -116,7 +116,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.host).toBe('sentry.io'); + expect(dsn.hostname).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe('/123'); expect(dsn.projectId).toBe('321'); @@ -127,7 +127,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.host).toBe('sentry.io'); + expect(dsn.hostname).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe('/sentry/custom/installation'); expect(dsn.projectId).toBe('321'); @@ -138,7 +138,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.host).toBe('sentry.io'); + expect(dsn.hostname).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('321'); From 3056ddaafd496b491f3a610cfc6430305e04db05 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 12:33:10 +0100 Subject: [PATCH 06/15] ref(utils): fix test --- packages/node/src/transports/base/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node/src/transports/base/index.ts b/packages/node/src/transports/base/index.ts index 6225bed07c14..c1639b74c8ce 100644 --- a/packages/node/src/transports/base/index.ts +++ b/packages/node/src/transports/base/index.ts @@ -96,9 +96,9 @@ export abstract class BaseTransport implements Transport { return proxy; } - const { host, port } = this._api.dsn; + const { hostname, port } = this._api.dsn; for (const np of no_proxy.split(',')) { - if (host.endsWith(np) || `${host}:${port}`.endsWith(np)) { + if (hostname.endsWith(np) || `${hostname}:${port}`.endsWith(np)) { return; } } From b5c9c6e618fb8471c5ff65ca8b28bf83fe96e248 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 16:58:45 +0100 Subject: [PATCH 07/15] Revert "ref(utils): fix test" This reverts commit 13cf62729aa09797581e09d01d57e0721d044c27. --- packages/node/src/transports/base/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node/src/transports/base/index.ts b/packages/node/src/transports/base/index.ts index c1639b74c8ce..6225bed07c14 100644 --- a/packages/node/src/transports/base/index.ts +++ b/packages/node/src/transports/base/index.ts @@ -96,9 +96,9 @@ export abstract class BaseTransport implements Transport { return proxy; } - const { hostname, port } = this._api.dsn; + const { host, port } = this._api.dsn; for (const np of no_proxy.split(',')) { - if (hostname.endsWith(np) || `${hostname}:${port}`.endsWith(np)) { + if (host.endsWith(np) || `${host}:${port}`.endsWith(np)) { return; } } From 6a66e669da9e4a5c75d7435e961b9b1f68548785 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 16:58:58 +0100 Subject: [PATCH 08/15] Revert "ref(utils): get global url" This reverts commit 61feeeb12f4f6394bcdc4c9d3f43e11ef6c006b5. --- packages/core/src/api.ts | 2 +- packages/node/src/integrations/utils/http.ts | 2 +- packages/types/src/dsn.ts | 2 +- packages/utils/src/dsn.ts | 14 ++++---- packages/utils/test/dsn.test.ts | 36 ++++++++++---------- 5 files changed, 27 insertions(+), 29 deletions(-) diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index 7ea5a5f99ddb..c8f1ef011ec5 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -98,7 +98,7 @@ export function initAPIDetails(dsn: DsnLike, metadata?: SdkMetadata, tunnel?: st function getBaseApiEndpoint(dsn: Dsn): string { const protocol = dsn.protocol ? `${dsn.protocol}:` : ''; const port = dsn.port ? `:${dsn.port}` : ''; - return `${protocol}//${dsn.hostname}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`; + return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`; } /** Returns the ingest API endpoint for target. */ diff --git a/packages/node/src/integrations/utils/http.ts b/packages/node/src/integrations/utils/http.ts index fb3342815095..bb0e9a76cf00 100644 --- a/packages/node/src/integrations/utils/http.ts +++ b/packages/node/src/integrations/utils/http.ts @@ -14,7 +14,7 @@ export function isSentryRequest(url: string): boolean { const dsn = getCurrentHub() .getClient() ?.getDsn(); - return dsn ? url.includes(dsn.hostname) : false; + return dsn ? url.includes(dsn.host) : false; } /** diff --git a/packages/types/src/dsn.ts b/packages/types/src/dsn.ts index 9f0aed000b1b..a80ce99003d0 100644 --- a/packages/types/src/dsn.ts +++ b/packages/types/src/dsn.ts @@ -12,7 +12,7 @@ export interface DsnComponents { /** Private authorization key (deprecated, optional). */ pass?: string; /** Hostname of the Sentry instance. */ - hostname: string; + host: string; /** Port of the Sentry instance. */ port?: string; /** Sub path/ */ diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 89ea16bf89fa..7bb71ed3e054 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -2,7 +2,6 @@ import { Dsn, DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { isDebugBuild } from './env'; import { SentryError } from './error'; -import { getGlobalObject } from './global'; function isValidProtocol(protocol?: string): protocol is DsnProtocol { return protocol === 'http' || protocol === 'https'; @@ -22,22 +21,21 @@ function normalizeProtocol(input: string): string { * @param withPassword When set to true, the password will be included. */ function dsntoString(dsn: Dsn, withPassword: boolean = false): string { - const { hostname, port, path, pass, projectId, protocol, publicKey } = dsn; + const { host, port, path, pass, projectId, protocol, publicKey } = dsn; return ( `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + - `@${hostname}${port ? `:${port}` : ''}${path}/${projectId}` + `@${host}${port ? `:${port}` : ''}${path}/${projectId}` ); } function dsnFromString(str: string): Dsn { - const global = getGlobalObject<{ URL: typeof URL }>(); - const url = new global.URL(str); + const url = new URL(str); const pathComponents = url.pathname.split('/'); const projectId = pathComponents.pop(); return dsnFromComponents({ - hostname: url.hostname, + host: url.hostname, pass: url.password, path: pathComponents.join('/'), projectId: projectId || '', @@ -58,7 +56,7 @@ function dsnFromComponents(components: DsnComponents): Dsn { protocol: components.protocol, publicKey: components.publicKey || '', pass: components.pass || '', - hostname: components.hostname, + host: components.host, port: components.port || '', path: components.path || '', projectId: components.projectId, @@ -69,7 +67,7 @@ function validateDsn(dsn: Dsn): boolean { if (isDebugBuild()) { const { port, projectId, protocol } = dsn; - const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'hostname', 'projectId']; + const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'host', 'projectId']; requiredComponents.forEach(component => { if (!dsn[component]) { throw new SentryError(`Invalid Dsn: ${component} missing`); diff --git a/packages/utils/test/dsn.test.ts b/packages/utils/test/dsn.test.ts index 9aa88c151bab..bb4e3d81548c 100644 --- a/packages/utils/test/dsn.test.ts +++ b/packages/utils/test/dsn.test.ts @@ -11,7 +11,7 @@ describe('Dsn', () => { describe('fromComponents', () => { test('applies all components', () => { const dsn = makeDsn({ - hostname: 'sentry.io', + host: 'sentry.io', pass: 'xyz', port: '1234', projectId: '123', @@ -21,7 +21,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe('xyz'); - expect(dsn.hostname).toBe('sentry.io'); + expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe('1234'); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('123'); @@ -29,7 +29,7 @@ describe('Dsn', () => { test('applies partial components', () => { const dsn = makeDsn({ - hostname: 'sentry.io', + host: 'sentry.io', projectId: '123', protocol: 'https', publicKey: 'abc', @@ -37,7 +37,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.hostname).toBe('sentry.io'); + expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('123'); @@ -46,23 +46,23 @@ describe('Dsn', () => { testIf(isDebugBuild())('throws for missing components', () => { expect(() => makeDsn({ - hostname: '', + host: '', projectId: '123', - protocol: 'https', + protocol: 'https:', publicKey: 'abc', }), ).toThrow(SentryError); expect(() => makeDsn({ - hostname: 'sentry.io', + host: 'sentry.io', projectId: '', - protocol: 'https', + protocol: 'https:', publicKey: 'abc', }), ).toThrow(SentryError); expect(() => makeDsn({ - hostname: 'sentry.io', + host: 'sentry.io', projectId: '123', protocol: '' as 'http', // Trick the type checker here publicKey: 'abc', @@ -70,9 +70,9 @@ describe('Dsn', () => { ).toThrow(SentryError); expect(() => makeDsn({ - hostname: 'sentry.io', + host: 'sentry.io', projectId: '123', - protocol: 'https', + protocol: 'https:', publicKey: '', }), ).toThrow(SentryError); @@ -81,7 +81,7 @@ describe('Dsn', () => { testIf(isDebugBuild())('throws for invalid components', () => { expect(() => makeDsn({ - hostname: 'sentry.io', + host: 'sentry.io', projectId: '123', protocol: 'httpx' as 'http', // Trick the type checker here publicKey: 'abc', @@ -89,10 +89,10 @@ describe('Dsn', () => { ).toThrow(SentryError); expect(() => makeDsn({ - hostname: 'sentry.io', + host: 'sentry.io', port: 'xxx', projectId: '123', - protocol: 'https', + protocol: 'https:', publicKey: 'abc', }), ).toThrow(SentryError); @@ -105,7 +105,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe('xyz'); - expect(dsn.hostname).toBe('sentry.io'); + expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe('1234'); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('123'); @@ -116,7 +116,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.hostname).toBe('sentry.io'); + expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe('/123'); expect(dsn.projectId).toBe('321'); @@ -127,7 +127,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.hostname).toBe('sentry.io'); + expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe('/sentry/custom/installation'); expect(dsn.projectId).toBe('321'); @@ -138,7 +138,7 @@ describe('Dsn', () => { expect(dsn.protocol).toBe('https'); expect(dsn.publicKey).toBe('abc'); expect(dsn.pass).toBe(''); - expect(dsn.hostname).toBe('sentry.io'); + expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); expect(dsn.path).toBe(''); expect(dsn.projectId).toBe('321'); From 4c4bca8b6b58c827c8dd461736dcd570df5dd3b1 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 16:59:06 +0100 Subject: [PATCH 09/15] Revert "ref(core): simplify dsn parsing" This reverts commit c90b6f96ed6c818a8e71732efdcdd9a48f492d46. --- packages/utils/src/dsn.ts | 49 +++++++++++++++++++-------------- packages/utils/test/dsn.test.ts | 12 ++++---- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 7bb71ed3e054..962bb430b4c9 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -3,14 +3,13 @@ import { Dsn, DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { isDebugBuild } from './env'; import { SentryError } from './error'; +/** Regular expression used to parse a Dsn. */ +const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+)/; + function isValidProtocol(protocol?: string): protocol is DsnProtocol { return protocol === 'http' || protocol === 'https'; } -function normalizeProtocol(input: string): string { - return input.replace(/:$/, ''); -} - /** * Renders the string representation of this Dsn. * @@ -21,28 +20,38 @@ function normalizeProtocol(input: string): string { * @param withPassword When set to true, the password will be included. */ function dsntoString(dsn: Dsn, withPassword: boolean = false): string { - const { host, port, path, pass, projectId, protocol, publicKey } = dsn; + const { host, path, pass, port, projectId, protocol, publicKey } = dsn; return ( `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + - `@${host}${port ? `:${port}` : ''}${path}/${projectId}` + `@${host}${port ? `:${port}` : ''}/${path ? `${path}/` : path}${projectId}` ); } function dsnFromString(str: string): Dsn { - const url = new URL(str); - - const pathComponents = url.pathname.split('/'); - const projectId = pathComponents.pop(); - - return dsnFromComponents({ - host: url.hostname, - pass: url.password, - path: pathComponents.join('/'), - projectId: projectId || '', - port: url.port, - protocol: normalizeProtocol(url.protocol) as DsnProtocol, - publicKey: url.username, - }); + const match = DSN_REGEX.exec(str); + + if (!match) { + throw new SentryError('Invalid Dsn'); + } + + const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1); + let path = ''; + let projectId = lastPath; + + const split = projectId.split('/'); + if (split.length > 1) { + path = split.slice(0, -1).join('/'); + projectId = split.pop() as string; + } + + if (projectId) { + const projectMatch = projectId.match(/^\d+/); + if (projectMatch) { + projectId = projectMatch[0]; + } + } + + return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol as DsnProtocol, publicKey }); } function dsnFromComponents(components: DsnComponents): Dsn { diff --git a/packages/utils/test/dsn.test.ts b/packages/utils/test/dsn.test.ts index bb4e3d81548c..fb78b2a0b297 100644 --- a/packages/utils/test/dsn.test.ts +++ b/packages/utils/test/dsn.test.ts @@ -48,7 +48,7 @@ describe('Dsn', () => { makeDsn({ host: '', projectId: '123', - protocol: 'https:', + protocol: 'https', publicKey: 'abc', }), ).toThrow(SentryError); @@ -56,7 +56,7 @@ describe('Dsn', () => { makeDsn({ host: 'sentry.io', projectId: '', - protocol: 'https:', + protocol: 'https', publicKey: 'abc', }), ).toThrow(SentryError); @@ -72,7 +72,7 @@ describe('Dsn', () => { makeDsn({ host: 'sentry.io', projectId: '123', - protocol: 'https:', + protocol: 'https', publicKey: '', }), ).toThrow(SentryError); @@ -92,7 +92,7 @@ describe('Dsn', () => { host: 'sentry.io', port: 'xxx', projectId: '123', - protocol: 'https:', + protocol: 'https', publicKey: 'abc', }), ).toThrow(SentryError); @@ -118,7 +118,7 @@ describe('Dsn', () => { expect(dsn.pass).toBe(''); expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); - expect(dsn.path).toBe('/123'); + expect(dsn.path).toBe('123'); expect(dsn.projectId).toBe('321'); }); @@ -129,7 +129,7 @@ describe('Dsn', () => { expect(dsn.pass).toBe(''); expect(dsn.host).toBe('sentry.io'); expect(dsn.port).toBe(''); - expect(dsn.path).toBe('/sentry/custom/installation'); + expect(dsn.path).toBe('sentry/custom/installation'); expect(dsn.projectId).toBe('321'); }); From 5289243fdef2ab84d860de3abcb7c9b73e38bc0e Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Dec 2021 20:03:11 +0100 Subject: [PATCH 10/15] fix(dsn): update test --- packages/core/test/lib/api.test.ts | 8 +++++++- packages/nextjs/test/integration/next-env.d.ts | 1 - packages/utils/src/dsn.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/core/test/lib/api.test.ts b/packages/core/test/lib/api.test.ts index 672cc896d21d..14b44aed9602 100644 --- a/packages/core/test/lib/api.test.ts +++ b/packages/core/test/lib/api.test.ts @@ -119,6 +119,12 @@ describe('API', () => { }); test('getDsn', () => { - expect(new API(dsnPublic).getDsn()).toEqual(makeDsn(dsnPublic)); + expect(new API(dsnPublic).getDsn().host).toEqual(makeDsn(dsnPublic).host); + expect(new API(dsnPublic).getDsn().path).toEqual(makeDsn(dsnPublic).path); + expect(new API(dsnPublic).getDsn().pass).toEqual(makeDsn(dsnPublic).pass); + expect(new API(dsnPublic).getDsn().port).toEqual(makeDsn(dsnPublic).port); + expect(new API(dsnPublic).getDsn().protocol).toEqual(makeDsn(dsnPublic).protocol); + expect(new API(dsnPublic).getDsn().projectId).toEqual(makeDsn(dsnPublic).projectId); + expect(new API(dsnPublic).getDsn().publicKey).toEqual(makeDsn(dsnPublic).publicKey); }); }); diff --git a/packages/nextjs/test/integration/next-env.d.ts b/packages/nextjs/test/integration/next-env.d.ts index 9bc3dd46b9d9..4f11a03dc6cc 100644 --- a/packages/nextjs/test/integration/next-env.d.ts +++ b/packages/nextjs/test/integration/next-env.d.ts @@ -1,5 +1,4 @@ /// -/// /// // NOTE: This file should not be edited diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index 962bb430b4c9..c221c6c6c122 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -107,7 +107,7 @@ export function makeDsn(from: DsnLike): Dsn { const dsn: Dsn = { ...components, - toString: (withPassword: boolean) => dsntoString(dsn, withPassword), + toString: (withPassword?: boolean) => dsntoString(dsn, withPassword), }; return dsn; From fddb78cead20a14b5d7514f4a4cd81eaaf629711 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Mon, 20 Dec 2021 16:50:35 +0100 Subject: [PATCH 11/15] ref(utils): drop dsn toString class method --- packages/browser/src/transports/base.ts | 3 +- packages/core/src/api.ts | 2 +- packages/core/src/request.ts | 4 +-- packages/types/src/dsn.ts | 14 -------- packages/types/src/index.ts | 2 +- packages/utils/src/dsn.ts | 45 ++++++++++++------------- packages/utils/test/dsn.test.ts | 12 +++---- 7 files changed, 33 insertions(+), 49 deletions(-) diff --git a/packages/browser/src/transports/base.ts b/packages/browser/src/transports/base.ts index 042311d53694..304e1450c7f1 100644 --- a/packages/browser/src/transports/base.ts +++ b/packages/browser/src/transports/base.ts @@ -14,6 +14,7 @@ import { } from '@sentry/types'; import { dateTimestampInSeconds, + dsnToString, eventStatusFromHttpCode, getGlobalObject, logger, @@ -116,7 +117,7 @@ export abstract class BaseTransport implements Transport { const url = getEnvelopeEndpointWithUrlEncodedAuth(this._api.dsn, this._api.tunnel); // Envelope header is required to be at least an empty object - const envelopeHeader = JSON.stringify({ ...(this._api.tunnel && { dsn: this._api.dsn.toString() }) }); + const envelopeHeader = JSON.stringify({ ...(this._api.tunnel && { dsn: dsntoString(this._api.dsn) }) }); const itemHeaders = JSON.stringify({ type: 'client_report', }); diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index c8f1ef011ec5..290221ad701f 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -174,7 +174,7 @@ export function getReportDialogEndpoint( const dsn = makeDsn(dsnLike); const endpoint = `${getBaseApiEndpoint(dsn)}embed/error-page/`; - let encodedOptions = `dsn=${dsn.toString()}`; + let encodedOptions = `dsn=${dsnToString(dsn)}`; for (const key in dialogOptions) { if (key === 'dsn') { continue; diff --git a/packages/core/src/request.ts b/packages/core/src/request.ts index c1242e0dce12..6c9fc02b0fde 100644 --- a/packages/core/src/request.ts +++ b/packages/core/src/request.ts @@ -33,7 +33,7 @@ export function sessionToSentryRequest(session: Session | SessionAggregates, api const envelopeHeaders = JSON.stringify({ sent_at: new Date().toISOString(), ...(sdkInfo && { sdk: sdkInfo }), - ...(!!api.tunnel && { dsn: api.dsn.toString() }), + ...(!!api.tunnel && { dsn: dsnToString(api.dsn) }), }); // I know this is hacky but we don't want to add `session` to request type since it's never rate limited const type: SentryRequestType = 'aggregates' in session ? ('sessions' as SentryRequestType) : 'session'; @@ -81,7 +81,7 @@ export function eventToSentryRequest(event: Event, api: APIDetails): SentryReque event_id: event.event_id, sent_at: new Date().toISOString(), ...(sdkInfo && { sdk: sdkInfo }), - ...(!!api.tunnel && { dsn: api.dsn.toString() }), + ...(!!api.tunnel && { dsn: dsnToString(api.dsn) }), }); const itemHeaders = JSON.stringify({ type: eventType, diff --git a/packages/types/src/dsn.ts b/packages/types/src/dsn.ts index a80ce99003d0..b21130802903 100644 --- a/packages/types/src/dsn.ts +++ b/packages/types/src/dsn.ts @@ -23,17 +23,3 @@ export interface DsnComponents { /** Anything that can be parsed into a Dsn. */ export type DsnLike = string | DsnComponents; - -/** The Sentry Dsn, identifying a Sentry instance and project. */ -export interface Dsn extends DsnComponents { - /** - * Renders the string representation of this Dsn. - * - * By default, this will render the public representation without the password - * component. To get the deprecated private representation, set `withPassword` - * to true. - * - * @param withPassword When set to true, the password will be included. - */ - toString(withPassword?: boolean): string; -} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index fdc33cac2d1f..a6268e586a8d 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,7 +1,7 @@ export { Breadcrumb, BreadcrumbHint } from './breadcrumb'; export { Client } from './client'; export { Context, Contexts } from './context'; -export { Dsn, DsnComponents, DsnLike, DsnProtocol } from './dsn'; +export { DsnComponents, DsnLike, DsnProtocol } from './dsn'; export { DebugImage, DebugImageType, DebugMeta } from './debugMeta'; export { ExtendedError } from './error'; export { Event, EventHint } from './event'; diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index c221c6c6c122..ac61b97c3fec 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -19,7 +19,7 @@ function isValidProtocol(protocol?: string): protocol is DsnProtocol { * * @param withPassword When set to true, the password will be included. */ -function dsntoString(dsn: Dsn, withPassword: boolean = false): string { +export function dsnToString(dsn: Dsn, withPassword: boolean = false): string { const { host, path, pass, port, projectId, protocol, publicKey } = dsn; return ( `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + @@ -72,28 +72,30 @@ function dsnFromComponents(components: DsnComponents): Dsn { }; } -function validateDsn(dsn: Dsn): boolean { - if (isDebugBuild()) { - const { port, projectId, protocol } = dsn; +function validateDsn(dsn: Dsn): boolean | void { + if (!isDebugBuild()) { + return; + } - const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'host', 'projectId']; - requiredComponents.forEach(component => { - if (!dsn[component]) { - throw new SentryError(`Invalid Dsn: ${component} missing`); - } - }); + const { port, projectId, protocol } = dsn; - if (!projectId.match(/^\d+$/)) { - throw new SentryError(`Invalid Dsn: Invalid projectId ${projectId}`); + const requiredComponents: ReadonlyArray = ['protocol', 'publicKey', 'host', 'projectId']; + requiredComponents.forEach(component => { + if (!dsn[component]) { + throw new SentryError(`Invalid Dsn: ${component} missing`); } + }); - if (!isValidProtocol(protocol)) { - throw new SentryError(`Invalid Dsn: Invalid protocol ${protocol}`); - } + if (!projectId.match(/^\d+$/)) { + throw new SentryError(`Invalid Dsn: Invalid projectId ${projectId}`); + } - if (port && isNaN(parseInt(port, 10))) { - throw new SentryError(`Invalid Dsn: Invalid port ${port}`); - } + if (!isValidProtocol(protocol)) { + throw new SentryError(`Invalid Dsn: Invalid protocol ${protocol}`); + } + + if (port && isNaN(parseInt(port, 10))) { + throw new SentryError(`Invalid Dsn: Invalid port ${port}`); } return true; @@ -105,10 +107,5 @@ export function makeDsn(from: DsnLike): Dsn { validateDsn(components); - const dsn: Dsn = { - ...components, - toString: (withPassword?: boolean) => dsntoString(dsn, withPassword), - }; - - return dsn; + return components; } diff --git a/packages/utils/test/dsn.test.ts b/packages/utils/test/dsn.test.ts index fb78b2a0b297..ed6dfe6a2caf 100644 --- a/packages/utils/test/dsn.test.ts +++ b/packages/utils/test/dsn.test.ts @@ -1,6 +1,6 @@ import { isDebugBuild } from '@sentry/utils'; -import { makeDsn } from '../src/dsn'; +import { makeDsn, dsnToString } from '../src/dsn'; import { SentryError } from '../src/error'; function testIf(condition: boolean): jest.It { @@ -165,27 +165,27 @@ describe('Dsn', () => { describe('toString', () => { test('excludes the password by default', () => { const dsn = makeDsn('https://abc:xyz@sentry.io:1234/123'); - expect(dsn.toString()).toBe('https://abc@sentry.io:1234/123'); + expect(dsnToString(dsn)).toBe('https://abc@sentry.io:1234/123'); }); test('optionally includes the password', () => { const dsn = makeDsn('https://abc:xyz@sentry.io:1234/123'); - expect(dsn.toString(true)).toBe('https://abc:xyz@sentry.io:1234/123'); + expect(dsnToString(dsn, true)).toBe('https://abc:xyz@sentry.io:1234/123'); }); test('renders no password if missing', () => { const dsn = makeDsn('https://abc@sentry.io:1234/123'); - expect(dsn.toString(true)).toBe('https://abc@sentry.io:1234/123'); + expect(dsnToString(dsn, true)).toBe('https://abc@sentry.io:1234/123'); }); test('renders no port if missing', () => { const dsn = makeDsn('https://abc@sentry.io/123'); - expect(dsn.toString()).toBe('https://abc@sentry.io/123'); + expect(dsnToString(dsn)).toBe('https://abc@sentry.io/123'); }); test('renders the full path correctly', () => { const dsn = makeDsn('https://abc@sentry.io/sentry/custom/installation/321'); - expect(dsn.toString()).toBe('https://abc@sentry.io/sentry/custom/installation/321'); + expect(dsnToString(dsn)).toBe('https://abc@sentry.io/sentry/custom/installation/321'); }); }); }); From 8e8455f5896c62447fcdce116e67fac6930b79d9 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Mon, 20 Dec 2021 17:38:09 +0100 Subject: [PATCH 12/15] ref(utils): drop export --- packages/core/src/api.ts | 30 +++++++++++++++++------------- packages/core/src/request.ts | 1 + packages/types/src/client.ts | 4 ++-- packages/utils/src/dsn.ts | 12 ++++++------ 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index 290221ad701f..bace0ad07591 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -1,5 +1,5 @@ -import { Dsn, DsnLike, SdkMetadata } from '@sentry/types'; -import { makeDsn, urlEncode } from '@sentry/utils'; +import { DsnComponents, DsnLike, SdkMetadata } from '@sentry/types'; +import { dsnToString, makeDsn, urlEncode } from '@sentry/utils'; const SENTRY_API_VERSION = '7'; @@ -12,7 +12,7 @@ export interface APIDetails { /** Metadata about the SDK (name, version, etc) for inclusion in envelope headers */ metadata: SdkMetadata; /** The internally used Dsn object. */ - readonly dsn: Dsn; + readonly dsn: DsnComponents; /** The envelope tunnel to use. */ readonly tunnel?: string; } @@ -32,7 +32,7 @@ export class API { public metadata: SdkMetadata; /** The internally used Dsn object. */ - private readonly _dsnObject: Dsn; + private readonly _dsnObject: DsnComponents; /** The envelope tunnel to use. */ private readonly _tunnel?: string; @@ -46,7 +46,7 @@ export class API { } /** Returns the Dsn object. */ - public getDsn(): Dsn { + public getDsn(): DsnComponents { return this._dsnObject; } @@ -95,19 +95,19 @@ export function initAPIDetails(dsn: DsnLike, metadata?: SdkMetadata, tunnel?: st } /** Returns the prefix to construct Sentry ingestion API endpoints. */ -function getBaseApiEndpoint(dsn: Dsn): string { +function getBaseApiEndpoint(dsn: DsnComponents): string { const protocol = dsn.protocol ? `${dsn.protocol}:` : ''; const port = dsn.port ? `:${dsn.port}` : ''; return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`; } /** Returns the ingest API endpoint for target. */ -function _getIngestEndpoint(dsn: Dsn, target: 'store' | 'envelope'): string { +function _getIngestEndpoint(dsn: DsnComponents, target: 'store' | 'envelope'): string { return `${getBaseApiEndpoint(dsn)}${dsn.projectId}/${target}/`; } /** Returns a URL-encoded string with auth config suitable for a query string. */ -function _encodedAuth(dsn: Dsn): string { +function _encodedAuth(dsn: DsnComponents): string { return urlEncode({ // We send only the minimum set of required information. See // https://github.com/getsentry/sentry-javascript/issues/2572. @@ -117,7 +117,7 @@ function _encodedAuth(dsn: Dsn): string { } /** Returns the store endpoint URL. */ -function getStoreEndpoint(dsn: Dsn): string { +function getStoreEndpoint(dsn: DsnComponents): string { return _getIngestEndpoint(dsn, 'store'); } @@ -126,12 +126,12 @@ function getStoreEndpoint(dsn: Dsn): string { * * Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests. */ -export function getStoreEndpointWithUrlEncodedAuth(dsn: Dsn): string { +export function getStoreEndpointWithUrlEncodedAuth(dsn: DsnComponents): string { return `${getStoreEndpoint(dsn)}?${_encodedAuth(dsn)}`; } /** Returns the envelope endpoint URL. */ -function _getEnvelopeEndpoint(dsn: Dsn): string { +function _getEnvelopeEndpoint(dsn: DsnComponents): string { return _getIngestEndpoint(dsn, 'envelope'); } @@ -140,7 +140,7 @@ function _getEnvelopeEndpoint(dsn: Dsn): string { * * Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests. */ -export function getEnvelopeEndpointWithUrlEncodedAuth(dsn: Dsn, tunnel?: string): string { +export function getEnvelopeEndpointWithUrlEncodedAuth(dsn: DsnComponents, tunnel?: string): string { return tunnel ? tunnel : `${_getEnvelopeEndpoint(dsn)}?${_encodedAuth(dsn)}`; } @@ -148,7 +148,11 @@ export function getEnvelopeEndpointWithUrlEncodedAuth(dsn: Dsn, tunnel?: string) * Returns an object that can be used in request headers. * This is needed for node and the old /store endpoint in sentry */ -export function getRequestHeaders(dsn: Dsn, clientName: string, clientVersion: string): { [key: string]: string } { +export function getRequestHeaders( + dsn: DsnComponents, + clientName: string, + clientVersion: string, +): { [key: string]: string } { // CHANGE THIS to use metadata but keep clientName and clientVersion compatible const header = [`Sentry sentry_version=${SENTRY_API_VERSION}`]; header.push(`sentry_client=${clientName}/${clientVersion}`); diff --git a/packages/core/src/request.ts b/packages/core/src/request.ts index 6c9fc02b0fde..db1810cdbbbd 100644 --- a/packages/core/src/request.ts +++ b/packages/core/src/request.ts @@ -1,4 +1,5 @@ import { Event, SdkInfo, SentryRequest, SentryRequestType, Session, SessionAggregates } from '@sentry/types'; +import { dsnToString } from '@sentry/utils'; import { APIDetails, getEnvelopeEndpointWithUrlEncodedAuth, getStoreEndpointWithUrlEncodedAuth } from './api'; diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index e71a3d2f39c6..109df960e0cf 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -1,4 +1,4 @@ -import { Dsn } from './dsn'; +import { DsnLike } from './dsn'; import { Event, EventHint } from './event'; import { Integration, IntegrationClass } from './integration'; import { Options } from './options'; @@ -55,7 +55,7 @@ export interface Client { captureSession?(session: Session): void; /** Returns the current Dsn. */ - getDsn(): Dsn | undefined; + getDsn(): DsnLike | undefined; /** Returns the current options. */ getOptions(): O; diff --git a/packages/utils/src/dsn.ts b/packages/utils/src/dsn.ts index ac61b97c3fec..76a7a950a64f 100644 --- a/packages/utils/src/dsn.ts +++ b/packages/utils/src/dsn.ts @@ -1,4 +1,4 @@ -import { Dsn, DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; +import { DsnComponents, DsnLike, DsnProtocol } from '@sentry/types'; import { isDebugBuild } from './env'; import { SentryError } from './error'; @@ -19,7 +19,7 @@ function isValidProtocol(protocol?: string): protocol is DsnProtocol { * * @param withPassword When set to true, the password will be included. */ -export function dsnToString(dsn: Dsn, withPassword: boolean = false): string { +export function dsnToString(dsn: DsnComponents, withPassword: boolean = false): string { const { host, path, pass, port, projectId, protocol, publicKey } = dsn; return ( `${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` + @@ -27,7 +27,7 @@ export function dsnToString(dsn: Dsn, withPassword: boolean = false): string { ); } -function dsnFromString(str: string): Dsn { +function dsnFromString(str: string): DsnComponents { const match = DSN_REGEX.exec(str); if (!match) { @@ -54,7 +54,7 @@ function dsnFromString(str: string): Dsn { return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol as DsnProtocol, publicKey }); } -function dsnFromComponents(components: DsnComponents): Dsn { +function dsnFromComponents(components: DsnComponents): DsnComponents { // TODO this is for backwards compatibility, and can be removed in a future version if ('user' in components && !('publicKey' in components)) { components.publicKey = components.user; @@ -72,7 +72,7 @@ function dsnFromComponents(components: DsnComponents): Dsn { }; } -function validateDsn(dsn: Dsn): boolean | void { +function validateDsn(dsn: DsnComponents): boolean | void { if (!isDebugBuild()) { return; } @@ -102,7 +102,7 @@ function validateDsn(dsn: Dsn): boolean | void { } /** The Sentry Dsn, identifying a Sentry instance and project. */ -export function makeDsn(from: DsnLike): Dsn { +export function makeDsn(from: DsnLike): DsnComponents { const components = typeof from === 'string' ? dsnFromString(from) : dsnFromComponents(from); validateDsn(components); From 8adfc74745980a879eec3a6706d8729f7935f8f7 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Mon, 20 Dec 2021 18:06:48 +0100 Subject: [PATCH 13/15] fix import --- packages/browser/src/transports/base.ts | 2 +- packages/core/src/baseclient.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/browser/src/transports/base.ts b/packages/browser/src/transports/base.ts index 304e1450c7f1..4b6c3218f1e8 100644 --- a/packages/browser/src/transports/base.ts +++ b/packages/browser/src/transports/base.ts @@ -117,7 +117,7 @@ export abstract class BaseTransport implements Transport { const url = getEnvelopeEndpointWithUrlEncodedAuth(this._api.dsn, this._api.tunnel); // Envelope header is required to be at least an empty object - const envelopeHeader = JSON.stringify({ ...(this._api.tunnel && { dsn: dsntoString(this._api.dsn) }) }); + const envelopeHeader = JSON.stringify({ ...(this._api.tunnel && { dsn: dsnToString(this._api.dsn) }) }); const itemHeaders = JSON.stringify({ type: 'client_report', }); diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index e3aa96684f16..ffd107b02851 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -2,7 +2,7 @@ import { Scope, Session } from '@sentry/hub'; import { Client, - Dsn, + DsnComponents, Event, EventHint, Integration, @@ -77,7 +77,7 @@ export abstract class BaseClient implement protected readonly _options: O; /** The client Dsn, if specified in options. Without this Dsn, the SDK will be disabled. */ - protected readonly _dsn?: Dsn; + protected readonly _dsn?: DsnComponents; /** Array of used integrations. */ protected _integrations: IntegrationIndex = {}; @@ -188,7 +188,7 @@ export abstract class BaseClient implement /** * @inheritDoc */ - public getDsn(): Dsn | undefined { + public getDsn(): DsnComponents | undefined { return this._dsn; } From d109d4bf68facbec859636d73f927185ef1a19dc Mon Sep 17 00:00:00 2001 From: JonasBa Date: Mon, 20 Dec 2021 18:19:11 +0100 Subject: [PATCH 14/15] fix import --- packages/core/test/lib/base.test.ts | 4 ++-- packages/types/src/client.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index cd8d7f992fb4..27b57923168b 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -1,6 +1,6 @@ import { Hub, Scope, Session } from '@sentry/hub'; import { Event, Span, Transport } from '@sentry/types'; -import { logger, SentryError, SyncPromise } from '@sentry/utils'; +import { logger, SentryError, SyncPromise, dsnToString } from '@sentry/utils'; import * as integrationModule from '../../src/integration'; import { TestBackend } from '../mocks/backend'; @@ -67,7 +67,7 @@ describe('BaseClient', () => { test('returns the Dsn', () => { expect.assertions(1); const client = new TestClient({ dsn: PUBLIC_DSN }); - expect(client.getDsn()!.toString()).toBe(PUBLIC_DSN); + expect(dsnToString(client.getDsn())).toBe(PUBLIC_DSN); }); test('allows missing Dsn', () => { diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 109df960e0cf..9c4510b91806 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -1,4 +1,4 @@ -import { DsnLike } from './dsn'; +import { DsnComponents } from './dsn'; import { Event, EventHint } from './event'; import { Integration, IntegrationClass } from './integration'; import { Options } from './options'; @@ -55,7 +55,7 @@ export interface Client { captureSession?(session: Session): void; /** Returns the current Dsn. */ - getDsn(): DsnLike | undefined; + getDsn(): DsnComponents | undefined; /** Returns the current options. */ getOptions(): O; From ef3e73fefb351372654250da1e9b83421325e58d Mon Sep 17 00:00:00 2001 From: JonasBa Date: Mon, 20 Dec 2021 19:26:37 +0100 Subject: [PATCH 15/15] fix import --- packages/core/test/lib/base.test.ts | 2 +- packages/utils/test/dsn.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index 27b57923168b..a5156e40c26e 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -1,6 +1,6 @@ import { Hub, Scope, Session } from '@sentry/hub'; import { Event, Span, Transport } from '@sentry/types'; -import { logger, SentryError, SyncPromise, dsnToString } from '@sentry/utils'; +import { dsnToString, logger, SentryError, SyncPromise } from '@sentry/utils'; import * as integrationModule from '../../src/integration'; import { TestBackend } from '../mocks/backend'; diff --git a/packages/utils/test/dsn.test.ts b/packages/utils/test/dsn.test.ts index ed6dfe6a2caf..44d6e0678210 100644 --- a/packages/utils/test/dsn.test.ts +++ b/packages/utils/test/dsn.test.ts @@ -1,6 +1,6 @@ import { isDebugBuild } from '@sentry/utils'; -import { makeDsn, dsnToString } from '../src/dsn'; +import { dsnToString, makeDsn } from '../src/dsn'; import { SentryError } from '../src/error'; function testIf(condition: boolean): jest.It {