| 
 | 1 | +/** Supported Sentry transport protocols in a Dsn. */  | 
 | 2 | +export type DsnProtocol = 'http' | 'https';  | 
 | 3 | + | 
 | 4 | +/** Primitive components of a Dsn. */  | 
 | 5 | +interface DsnComponents {  | 
 | 6 | +  /** Protocol used to connect to Sentry. */  | 
 | 7 | +  protocol: DsnProtocol;  | 
 | 8 | +  /** Public authorization key. */  | 
 | 9 | +  publicKey?: string;  | 
 | 10 | +  /** Private authorization key (deprecated, optional). */  | 
 | 11 | +  pass?: string;  | 
 | 12 | +  /** Hostname of the Sentry instance. */  | 
 | 13 | +  host: string;  | 
 | 14 | +  /** Port of the Sentry instance. */  | 
 | 15 | +  port?: string;  | 
 | 16 | +  /** Sub path/ */  | 
 | 17 | +  path?: string;  | 
 | 18 | +  /** Project ID */  | 
 | 19 | +  projectId: string;  | 
 | 20 | +}  | 
 | 21 | + | 
 | 22 | +interface Package {  | 
 | 23 | +  name: string;  | 
 | 24 | +  version: string;  | 
 | 25 | +  dependencies?: Record<string, string>;  | 
 | 26 | +  devDependencies?: Record<string, string>;  | 
 | 27 | +}  | 
 | 28 | + | 
 | 29 | +interface SdkInfo {  | 
 | 30 | +  name?: string;  | 
 | 31 | +  version?: string;  | 
 | 32 | +  integrations?: string[];  | 
 | 33 | +  packages?: Package[];  | 
 | 34 | +  settings?: {  | 
 | 35 | +    infer_ip?: 'auto' | 'never';  | 
 | 36 | +  };  | 
 | 37 | +}  | 
 | 38 | + | 
 | 39 | +const SENTRY_API_VERSION = '7';  | 
 | 40 | + | 
 | 41 | +/**  | 
 | 42 | + * Converts a Dsn string to the ingest endpoint URL.  | 
 | 43 | + *  | 
 | 44 | + * @param dsnString A Dsn as string  | 
 | 45 | + * @returns The ingest endpoint URL  | 
 | 46 | + */  | 
 | 47 | +export function dsnStringToIngestEndpoint(dsnString: string): string {  | 
 | 48 | +  const dsn = dsnFromString(dsnString);  | 
 | 49 | +  if (!dsn) {  | 
 | 50 | +    throw new Error(`Invalid Sentry Dsn: ${dsnString}`);  | 
 | 51 | +  }  | 
 | 52 | +  return getEnvelopeEndpointWithUrlEncodedAuth(dsn);  | 
 | 53 | +}  | 
 | 54 | + | 
 | 55 | +/** Returns the prefix to construct Sentry ingestion API endpoints. */  | 
 | 56 | +function getBaseApiEndpoint(dsn: DsnComponents): string {  | 
 | 57 | +  const protocol = dsn.protocol ? `${dsn.protocol}:` : '';  | 
 | 58 | +  const port = dsn.port ? `:${dsn.port}` : '';  | 
 | 59 | +  return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`;  | 
 | 60 | +}  | 
 | 61 | + | 
 | 62 | +/** Returns the ingest API endpoint for target. */  | 
 | 63 | +function _getIngestEndpoint(dsn: DsnComponents): string {  | 
 | 64 | +  return `${getBaseApiEndpoint(dsn)}${dsn.projectId}/envelope/`;  | 
 | 65 | +}  | 
 | 66 | + | 
 | 67 | +/** Returns a URL-encoded string with auth config suitable for a query string. */  | 
 | 68 | +function _encodedAuth(dsn: DsnComponents, sdkInfo: SdkInfo | undefined): string {  | 
 | 69 | +  const params: Record<string, string> = {  | 
 | 70 | +    sentry_version: SENTRY_API_VERSION,  | 
 | 71 | +  };  | 
 | 72 | + | 
 | 73 | +  if (dsn.publicKey) {  | 
 | 74 | +    // We send only the minimum set of required information. See  | 
 | 75 | +    // https://github.com/getsentry/sentry-javascript/issues/2572.  | 
 | 76 | +    params.sentry_key = dsn.publicKey;  | 
 | 77 | +  }  | 
 | 78 | + | 
 | 79 | +  if (sdkInfo) {  | 
 | 80 | +    params.sentry_client = `${sdkInfo.name}/${sdkInfo.version}`;  | 
 | 81 | +  }  | 
 | 82 | + | 
 | 83 | +  return new URLSearchParams(params).toString();  | 
 | 84 | +}  | 
 | 85 | + | 
 | 86 | +/**  | 
 | 87 | + * Returns the envelope endpoint URL with auth in the query string.  | 
 | 88 | + *  | 
 | 89 | + * Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests.  | 
 | 90 | + */  | 
 | 91 | +function getEnvelopeEndpointWithUrlEncodedAuth(dsn: DsnComponents, tunnel?: string, sdkInfo?: SdkInfo): string {  | 
 | 92 | +  return tunnel ? tunnel : `${_getIngestEndpoint(dsn)}?${_encodedAuth(dsn, sdkInfo)}`;  | 
 | 93 | +}  | 
 | 94 | + | 
 | 95 | +const DSN_REGEX = /^(?:(\w+):)\/\/(?:(\w+)(?::(\w+)?)?@)([\w.-]+)(?::(\d+))?\/(.+)/;  | 
 | 96 | + | 
 | 97 | +/**  | 
 | 98 | + * Parses a Dsn from a given string.  | 
 | 99 | + *  | 
 | 100 | + * @param str A Dsn as string  | 
 | 101 | + * @returns Dsn as DsnComponents or undefined if @param str is not a valid DSN string  | 
 | 102 | + */  | 
 | 103 | +function dsnFromString(str: string): DsnComponents | undefined {  | 
 | 104 | +  const match = DSN_REGEX.exec(str);  | 
 | 105 | + | 
 | 106 | +  if (!match) {  | 
 | 107 | +    // eslint-disable-next-line no-console  | 
 | 108 | +    console.error(`Invalid Sentry Dsn: ${str}`);  | 
 | 109 | + | 
 | 110 | +    return undefined;  | 
 | 111 | +  }  | 
 | 112 | + | 
 | 113 | +  const [protocol, publicKey, pass = '', host = '', port = '', lastPath = ''] = match.slice(1);  | 
 | 114 | +  let path = '';  | 
 | 115 | +  let projectId = lastPath;  | 
 | 116 | + | 
 | 117 | +  const split = projectId.split('/');  | 
 | 118 | +  if (split.length > 1) {  | 
 | 119 | +    path = split.slice(0, -1).join('/');  | 
 | 120 | +    projectId = split.pop() as string;  | 
 | 121 | +  }  | 
 | 122 | + | 
 | 123 | +  if (projectId) {  | 
 | 124 | +    const projectMatch = projectId.match(/^\d+/);  | 
 | 125 | +    if (projectMatch) {  | 
 | 126 | +      projectId = projectMatch[0];  | 
 | 127 | +    }  | 
 | 128 | +  }  | 
 | 129 | + | 
 | 130 | +  return dsnFromComponents({ host, pass, path, projectId, port, protocol: protocol as DsnProtocol, publicKey });  | 
 | 131 | +}  | 
 | 132 | + | 
 | 133 | +function dsnFromComponents(components: DsnComponents): DsnComponents {  | 
 | 134 | +  return {  | 
 | 135 | +    protocol: components.protocol,  | 
 | 136 | +    publicKey: components.publicKey || '',  | 
 | 137 | +    pass: components.pass || '',  | 
 | 138 | +    host: components.host,  | 
 | 139 | +    port: components.port || '',  | 
 | 140 | +    path: components.path || '',  | 
 | 141 | +    projectId: components.projectId,  | 
 | 142 | +  };  | 
 | 143 | +}  | 
0 commit comments