Skip to content

Commit e059f5c

Browse files
committed
ref(utils): dsn
1 parent d032b06 commit e059f5c

File tree

1 file changed

+105
-95
lines changed

1 file changed

+105
-95
lines changed

packages/utils/src/dsn.ts

Lines changed: 105 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,110 @@ const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+))?@)([\w.-]+)(?::(\d+))?\/(.+
99
/** Error message */
1010
const ERROR_MESSAGE = 'Invalid Dsn';
1111

12+
function isValidProtocol(protocol?: string): protocol is DsnProtocol {
13+
return protocol === 'http' || protocol === 'https';
14+
}
15+
16+
/**
17+
* Renders the string representation of this Dsn.
18+
*
19+
* By default, this will render the public representation without the password
20+
* component. To get the deprecated private representation, set `withPassword`
21+
* to true.
22+
*
23+
* @param withPassword When set to true, the password will be included.
24+
*/
25+
function dsntoString(dsn: Dsn, withPassword: boolean = false): string {
26+
const { host, path, pass, port, projectId, protocol, publicKey } = dsn;
27+
return (
28+
`${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` +
29+
`@${host}${port ? `:${port}` : ''}/${path ? `${path}/` : path}${projectId}`
30+
);
31+
}
32+
33+
function dsnFromString(str: string): Dsn {
34+
const match = DSN_REGEX.exec(str);
35+
36+
if (!match) {
37+
throw new SentryError(ERROR_MESSAGE);
38+
}
39+
40+
const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1);
41+
let path = '';
42+
let projectId = lastPath;
43+
44+
const split = projectId.split('/');
45+
if (split.length > 1) {
46+
path = split.slice(0, -1).join('/');
47+
projectId = split.pop() as string;
48+
}
49+
50+
if (projectId) {
51+
const projectMatch = projectId.match(/^\d+/);
52+
if (projectMatch) {
53+
projectId = projectMatch[0];
54+
}
55+
}
56+
57+
if (isValidProtocol(protocol)) {
58+
return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol, publicKey });
59+
}
60+
61+
throw new SentryError(ERROR_MESSAGE);
62+
}
63+
64+
function dsnFromComponents(components: DsnComponents): Dsn {
65+
// TODO this is for backwards compatibility, and can be removed in a future version
66+
if ('user' in components && !('publicKey' in components)) {
67+
components.publicKey = components.user;
68+
}
69+
70+
return {
71+
user: components.publicKey || '',
72+
protocol: components.protocol,
73+
publicKey: components.publicKey || '',
74+
pass: components.pass || '',
75+
host: components.host,
76+
port: components.port || '',
77+
path: components.path || '',
78+
projectId: components.projectId,
79+
};
80+
}
81+
82+
function validateDsn(dsn: Dsn): boolean {
83+
if (isDebugBuild()) {
84+
const { port, projectId, protocol } = dsn;
85+
86+
['protocol', 'publicKey', 'host', 'projectId'].forEach(component => {
87+
if (!dsn[component]) {
88+
throw new SentryError(`${ERROR_MESSAGE}: ${component} missing`);
89+
}
90+
});
91+
92+
if (!projectId.match(/^\d+$/)) {
93+
throw new SentryError(`${ERROR_MESSAGE}: Invalid projectId ${projectId}`);
94+
}
95+
96+
if (isValidProtocol(protocol)) {
97+
throw new SentryError(`${ERROR_MESSAGE}: Invalid protocol ${protocol}`);
98+
}
99+
100+
if (port && isNaN(parseInt(port, 10))) {
101+
throw new SentryError(`${ERROR_MESSAGE}: Invalid port ${port}`);
102+
}
103+
}
104+
105+
return true;
106+
}
107+
108+
function makeDsn(from: DsnLike): Dsn {
109+
let dsn = typeof from === 'string' ? dsnFromString(from) : dsnFromComponents(from);
110+
111+
return {
112+
...dsn,
113+
toString: () => dsntoString(dsn),
114+
};
115+
}
12116
/** The Sentry Dsn, identifying a Sentry instance and project. */
13117
export class Dsn implements DsnComponents {
14118
/** Protocol used to connect to Sentry. */
@@ -29,99 +133,5 @@ export class Dsn implements DsnComponents {
29133
public projectId!: string;
30134

31135
/** Creates a new Dsn component */
32-
public constructor(from: DsnLike) {
33-
if (typeof from === 'string') {
34-
this._fromString(from);
35-
} else {
36-
this._fromComponents(from);
37-
}
38-
39-
this._validate();
40-
}
41-
42-
/**
43-
* Renders the string representation of this Dsn.
44-
*
45-
* By default, this will render the public representation without the password
46-
* component. To get the deprecated private representation, set `withPassword`
47-
* to true.
48-
*
49-
* @param withPassword When set to true, the password will be included.
50-
*/
51-
public toString(withPassword: boolean = false): string {
52-
const { host, path, pass, port, projectId, protocol, publicKey } = this;
53-
return (
54-
`${protocol}://${publicKey}${withPassword && pass ? `:${pass}` : ''}` +
55-
`@${host}${port ? `:${port}` : ''}/${path ? `${path}/` : path}${projectId}`
56-
);
57-
}
58-
59-
/** Parses a string into this Dsn. */
60-
private _fromString(str: string): void {
61-
const match = DSN_REGEX.exec(str);
62-
63-
if (!match) {
64-
throw new SentryError(ERROR_MESSAGE);
65-
}
66-
67-
const [protocol, publicKey, pass = '', host, port = '', lastPath] = match.slice(1);
68-
let path = '';
69-
let projectId = lastPath;
70-
71-
const split = projectId.split('/');
72-
if (split.length > 1) {
73-
path = split.slice(0, -1).join('/');
74-
projectId = split.pop() as string;
75-
}
76-
77-
if (projectId) {
78-
const projectMatch = projectId.match(/^\d+/);
79-
if (projectMatch) {
80-
projectId = projectMatch[0];
81-
}
82-
}
83-
84-
this._fromComponents({ host, pass, path, projectId, port, protocol: protocol as DsnProtocol, publicKey });
85-
}
86-
87-
/** Maps Dsn components into this instance. */
88-
private _fromComponents(components: DsnComponents): void {
89-
// TODO this is for backwards compatibility, and can be removed in a future version
90-
if ('user' in components && !('publicKey' in components)) {
91-
components.publicKey = components.user;
92-
}
93-
this.user = components.publicKey || '';
94-
95-
this.protocol = components.protocol;
96-
this.publicKey = components.publicKey || '';
97-
this.pass = components.pass || '';
98-
this.host = components.host;
99-
this.port = components.port || '';
100-
this.path = components.path || '';
101-
this.projectId = components.projectId;
102-
}
103-
104-
/** Validates this Dsn and throws on error. */
105-
private _validate(): void {
106-
// we only validate in debug mode. This will fail later anyways.
107-
if (isDebugBuild()) {
108-
['protocol', 'publicKey', 'host', 'projectId'].forEach(component => {
109-
if (!this[component as keyof DsnComponents]) {
110-
throw new SentryError(`${ERROR_MESSAGE}: ${component} missing`);
111-
}
112-
});
113-
114-
if (!this.projectId.match(/^\d+$/)) {
115-
throw new SentryError(`${ERROR_MESSAGE}: Invalid projectId ${this.projectId}`);
116-
}
117-
118-
if (this.protocol !== 'http' && this.protocol !== 'https') {
119-
throw new SentryError(`${ERROR_MESSAGE}: Invalid protocol ${this.protocol}`);
120-
}
121-
122-
if (this.port && isNaN(parseInt(this.port, 10))) {
123-
throw new SentryError(`${ERROR_MESSAGE}: Invalid port ${this.port}`);
124-
}
125-
}
126-
}
136+
public constructor(from: DsnLike) {}
127137
}

0 commit comments

Comments
 (0)