From c9fa57b79195e3fcaa397b6faefab5a3b110703e Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 25 Apr 2022 09:56:38 -0400 Subject: [PATCH 1/2] feat: Delete store endpoint code --- packages/core/src/api.ts | 47 +----- packages/core/src/baseclient.ts | 2 +- packages/core/src/index.ts | 9 +- packages/core/src/request.ts | 121 +------------- packages/core/test/lib/api.test.ts | 31 +--- packages/core/test/lib/request.test.ts | 215 ------------------------- 6 files changed, 10 insertions(+), 415 deletions(-) delete mode 100644 packages/core/test/lib/request.test.ts diff --git a/packages/core/src/api.ts b/packages/core/src/api.ts index 8d76cb135400..09dff0b8f291 100644 --- a/packages/core/src/api.ts +++ b/packages/core/src/api.ts @@ -35,8 +35,8 @@ function getBaseApiEndpoint(dsn: DsnComponents): string { } /** Returns the ingest API endpoint for target. */ -function _getIngestEndpoint(dsn: DsnComponents, target: 'store' | 'envelope'): string { - return `${getBaseApiEndpoint(dsn)}${dsn.projectId}/${target}/`; +function _getIngestEndpoint(dsn: DsnComponents): string { + return `${getBaseApiEndpoint(dsn)}${dsn.projectId}/envelope/`; } /** Returns a URL-encoded string with auth config suitable for a query string. */ @@ -49,54 +49,13 @@ function _encodedAuth(dsn: DsnComponents): string { }); } -/** Returns the store endpoint URL. */ -export function getStoreEndpoint(dsn: DsnComponents): string { - return _getIngestEndpoint(dsn, 'store'); -} - -/** - * Returns the store endpoint URL with auth in the query string. - * - * Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests. - */ -export function getStoreEndpointWithUrlEncodedAuth(dsn: DsnComponents): string { - return `${getStoreEndpoint(dsn)}?${_encodedAuth(dsn)}`; -} - -/** Returns the envelope endpoint URL. */ -function _getEnvelopeEndpoint(dsn: DsnComponents): string { - return _getIngestEndpoint(dsn, 'envelope'); -} - /** * Returns the envelope endpoint URL with auth in the query string. * * Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests. */ export function getEnvelopeEndpointWithUrlEncodedAuth(dsn: DsnComponents, tunnel?: string): string { - return tunnel ? tunnel : `${_getEnvelopeEndpoint(dsn)}?${_encodedAuth(dsn)}`; -} - -/** - * 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: 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}`); - header.push(`sentry_key=${dsn.publicKey}`); - if (dsn.pass) { - header.push(`sentry_secret=${dsn.pass}`); - } - return { - 'Content-Type': 'application/json', - 'X-Sentry-Auth': header.join(', '), - }; + return tunnel ? tunnel : `${_getIngestEndpoint(dsn)}?${_encodedAuth(dsn)}`; } /** Returns the url to the report dialog endpoint. */ diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 833b40aaf9c7..6c76065e1829 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -275,7 +275,7 @@ export abstract class BaseClient implements Client { */ public sendSession(session: Session | SessionAggregates): void { if (this._dsn) { - const [env] = createSessionEnvelope(session, this._dsn, this._options._metadata, this._options.tunnel); + const env = createSessionEnvelope(session, this._dsn, this._options._metadata, this._options.tunnel); this.sendEnvelope(env); } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a5231620e7a3..00123055009c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -23,15 +23,8 @@ export { Scope, Session, } from '@sentry/hub'; -export { - getEnvelopeEndpointWithUrlEncodedAuth, - getStoreEndpointWithUrlEncodedAuth, - getRequestHeaders, - initAPIDetails, - getReportDialogEndpoint, -} from './api'; +export { getEnvelopeEndpointWithUrlEncodedAuth, initAPIDetails, getReportDialogEndpoint } from './api'; export { BaseClient } from './baseclient'; -export { eventToSentryRequest, sessionToSentryRequest } from './request'; export { initAndBind } from './sdk'; export { createTransport } from './transports/base'; export { SDK_VERSION } from './version'; diff --git a/packages/core/src/request.ts b/packages/core/src/request.ts index 9e58710af719..53572c844a3e 100644 --- a/packages/core/src/request.ts +++ b/packages/core/src/request.ts @@ -5,16 +5,13 @@ import { EventItem, SdkInfo, SdkMetadata, - SentryRequest, SentryRequestType, Session, SessionAggregates, SessionEnvelope, SessionItem, } from '@sentry/types'; -import { createEnvelope, dsnToString, normalize, serializeEnvelope } from '@sentry/utils'; - -import { APIDetails, getEnvelopeEndpointWithUrlEncodedAuth, getStoreEndpointWithUrlEncodedAuth } from './api'; +import { createEnvelope, dsnToString } from '@sentry/utils'; /** Extract sdk info from from the API metadata */ function getSdkMetadataForEnvelopeHeader(metadata?: SdkMetadata): SdkInfo | undefined { @@ -47,7 +44,7 @@ export function createSessionEnvelope( dsn: DsnComponents, metadata?: SdkMetadata, tunnel?: string, -): [SessionEnvelope, SentryRequestType] { +): SessionEnvelope { const sdkInfo = getSdkMetadataForEnvelopeHeader(metadata); const envelopeHeaders = { sent_at: new Date().toISOString(), @@ -62,22 +59,11 @@ export function createSessionEnvelope( const envelopeItem = [{ type } as { type: 'session' | 'sessions' }, session] as SessionItem; const envelope = createEnvelope(envelopeHeaders, [envelopeItem]); - return [envelope, type]; -} - -/** Creates a SentryRequest from a Session. */ -export function sessionToSentryRequest(session: Session | SessionAggregates, api: APIDetails): SentryRequest { - const [envelope, type] = createSessionEnvelope(session, api.dsn, api.metadata, api.tunnel); - return { - body: serializeEnvelope(envelope), - type, - url: getEnvelopeEndpointWithUrlEncodedAuth(api.dsn, api.tunnel), - }; + return envelope; } /** - * Create an Envelope from an event. Note that this is duplicated from below, - * but on purpose as this will be refactored in v7. + * Create an Envelope from an event. */ export function createEventEnvelope( event: Event, @@ -135,102 +121,3 @@ export function createEventEnvelope( ]; return createEnvelope(envelopeHeaders, [eventItem]); } - -/** Creates a SentryRequest from an event. */ -export function eventToSentryRequest(event: Event, api: APIDetails): SentryRequest { - const sdkInfo = getSdkMetadataForEnvelopeHeader(api.metadata); - const eventType = event.type || 'event'; - const useEnvelope = eventType === 'transaction' || !!api.tunnel; - - const { transactionSampling } = event.sdkProcessingMetadata || {}; - const { method: samplingMethod, rate: sampleRate } = transactionSampling || {}; - - // TODO: Below is a temporary hack in order to debug a serialization error - see - // https://github.com/getsentry/sentry-javascript/issues/2809, - // https://github.com/getsentry/sentry-javascript/pull/4425, and - // https://github.com/getsentry/sentry-javascript/pull/4574. - // - // TL; DR: even though we normalize all events (which should prevent this), something is causing `JSON.stringify` to - // throw a circular reference error. - // - // When it's time to remove it: - // 1. Delete everything between here and where the request object `req` is created, EXCEPT the line deleting - // `sdkProcessingMetadata` - // 2. Restore the original version of the request body, which is commented out - // 3. Search for either of the PR URLs above and pull out the companion hacks in the browser playwright tests and the - // baseClient tests in this package - enhanceEventWithSdkInfo(event, api.metadata.sdk); - event.tags = event.tags || {}; - event.extra = event.extra || {}; - - // In theory, all events should be marked as having gone through normalization and so - // we should never set this tag/extra data - if (!(event.sdkProcessingMetadata && event.sdkProcessingMetadata.baseClientNormalized)) { - event.tags.skippedNormalization = true; - event.extra.normalizeDepth = event.sdkProcessingMetadata ? event.sdkProcessingMetadata.normalizeDepth : 'unset'; - } - - // prevent this data from being sent to sentry - // TODO: This is NOT part of the hack - DO NOT DELETE - delete event.sdkProcessingMetadata; - - let body; - try { - // 99.9% of events should get through just fine - no change in behavior for them - body = JSON.stringify(event); - } catch (err) { - // Record data about the error without replacing original event data, then force renormalization - event.tags.JSONStringifyError = true; - event.extra.JSONStringifyError = err; - try { - body = JSON.stringify(normalize(event)); - } catch (newErr) { - // At this point even renormalization hasn't worked, meaning something about the event data has gone very wrong. - // Time to cut our losses and record only the new error. With luck, even in the problematic cases we're trying to - // debug with this hack, we won't ever land here. - const innerErr = newErr as Error; - body = JSON.stringify({ - message: 'JSON.stringify error after renormalization', - // setting `extra: { innerErr }` here for some reason results in an empty object, so unpack manually - extra: { message: innerErr.message, stack: innerErr.stack }, - }); - } - } - - const req: SentryRequest = { - // this is the relevant line of code before the hack was added, to make it easy to undo said hack once we've solved - // the mystery - // body: JSON.stringify(sdkInfo ? enhanceEventWithSdkInfo(event, api.metadata.sdk) : event), - body, - type: eventType, - url: useEnvelope - ? getEnvelopeEndpointWithUrlEncodedAuth(api.dsn, api.tunnel) - : getStoreEndpointWithUrlEncodedAuth(api.dsn), - }; - - // https://develop.sentry.dev/sdk/envelopes/ - - // Since we don't need to manipulate envelopes nor store them, there is no - // exported concept of an Envelope with operations including serialization and - // deserialization. Instead, we only implement a minimal subset of the spec to - // serialize events inline here. - if (useEnvelope) { - const envelopeHeaders = { - event_id: event.event_id as string, - sent_at: new Date().toISOString(), - ...(sdkInfo && { sdk: sdkInfo }), - ...(!!api.tunnel && { dsn: dsnToString(api.dsn) }), - }; - const eventItem: EventItem = [ - { - type: eventType, - sample_rates: [{ id: samplingMethod, rate: sampleRate }], - }, - req.body, - ]; - const envelope = createEnvelope(envelopeHeaders, [eventItem]); - req.body = serializeEnvelope(envelope); - } - - return req; -} diff --git a/packages/core/test/lib/api.test.ts b/packages/core/test/lib/api.test.ts index af3a7bfb8ca3..01f30892c593 100644 --- a/packages/core/test/lib/api.test.ts +++ b/packages/core/test/lib/api.test.ts @@ -1,14 +1,7 @@ /* eslint-disable deprecation/deprecation */ import { makeDsn } from '@sentry/utils'; -import { - getEnvelopeEndpointWithUrlEncodedAuth, - getReportDialogEndpoint, - getRequestHeaders, - getStoreEndpoint, - getStoreEndpointWithUrlEncodedAuth, - initAPIDetails, -} from '../../src/api'; +import { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint, initAPIDetails } from '../../src/api'; const ingestDsn = 'https://abc@xxxx.ingest.sentry.io:1234/subpath/123'; const dsnPublic = 'https://abc@sentry.io:1234/subpath/123'; @@ -19,14 +12,6 @@ const ingestDsnAPI = initAPIDetails(ingestDsn); const dsnPublicAPI = initAPIDetails(dsnPublic); describe('API', () => { - test('getStoreEndpoint', () => { - expect(getStoreEndpointWithUrlEncodedAuth(dsnPublicAPI.dsn)).toEqual( - 'https://sentry.io:1234/subpath/api/123/store/?sentry_key=abc&sentry_version=7', - ); - expect(getStoreEndpoint(dsnPublicAPI.dsn)).toEqual('https://sentry.io:1234/subpath/api/123/store/'); - expect(getStoreEndpoint(ingestDsnAPI.dsn)).toEqual('https://xxxx.ingest.sentry.io:1234/subpath/api/123/store/'); - }); - test('getEnvelopeEndpoint', () => { expect(getEnvelopeEndpointWithUrlEncodedAuth(dsnPublicAPI.dsn)).toEqual( 'https://sentry.io:1234/subpath/api/123/envelope/?sentry_key=abc&sentry_version=7', @@ -35,20 +20,6 @@ describe('API', () => { expect(getEnvelopeEndpointWithUrlEncodedAuth(dsnPublicAPIWithTunnel.dsn, tunnel)).toEqual(tunnel); }); - test('getRequestHeaders', () => { - 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(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$/, - ), - }); - }); - describe('getReportDialogEndpoint', () => { test.each([ [ diff --git a/packages/core/test/lib/request.test.ts b/packages/core/test/lib/request.test.ts deleted file mode 100644 index c740611a5996..000000000000 --- a/packages/core/test/lib/request.test.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { Event, SentryRequest } from '@sentry/types'; - -import { initAPIDetails } from '../../src/api'; -import { eventToSentryRequest, sessionToSentryRequest } from '../../src/request'; - -const ingestDsn = 'https://dogsarebadatkeepingsecrets@squirrelchasers.ingest.sentry.io/12312012'; -const storeUrl = - 'https://squirrelchasers.ingest.sentry.io/api/12312012/store/?sentry_key=dogsarebadatkeepingsecrets&sentry_version=7'; -const envelopeUrl = - 'https://squirrelchasers.ingest.sentry.io/api/12312012/envelope/?sentry_key=dogsarebadatkeepingsecrets&sentry_version=7'; -const tunnel = 'https://hello.com/world'; - -const api = initAPIDetails(ingestDsn, { - sdk: { - integrations: ['AWSLambda'], - name: 'sentry.javascript.browser', - version: '12.31.12', - packages: [{ name: 'npm:@sentry/browser', version: '12.31.12' }], - }, -}); - -function parseEnvelopeRequest(request: SentryRequest): any { - const [envelopeHeaderString, itemHeaderString, eventString] = request.body.split('\n'); - - return { - envelopeHeader: JSON.parse(envelopeHeaderString), - itemHeader: JSON.parse(itemHeaderString), - event: JSON.parse(eventString), - }; -} - -describe('eventToSentryRequest', () => { - let event: Event; - - beforeEach(() => { - event = { - contexts: { trace: { trace_id: '1231201211212012', span_id: '12261980', op: 'pageload' } }, - environment: 'dogpark', - event_id: '0908201304152013', - release: 'off.leash.park', - spans: [], - transaction: '/dogs/are/great/', - type: 'transaction', - user: { id: '1121', username: 'CharlieDog', ip_address: '11.21.20.12' }, - sdkProcessingMetadata: {}, - }; - }); - - it('adds transaction sampling information to item header', () => { - event.sdkProcessingMetadata = { transactionSampling: { method: 'client_rate', rate: 0.1121 } }; - - const result = eventToSentryRequest(event, api); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.itemHeader).toEqual( - expect.objectContaining({ - sample_rates: [{ id: 'client_rate', rate: 0.1121 }], - }), - ); - }); - - it('adds sdk info to envelope header', () => { - const result = eventToSentryRequest(event, api); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.envelopeHeader).toEqual( - expect.objectContaining({ sdk: { name: 'sentry.javascript.browser', version: '12.31.12' } }), - ); - }); - - it('adds sdk info to event body', () => { - const result = eventToSentryRequest(event, api); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.event).toEqual( - expect.objectContaining({ - sdk: { - integrations: ['AWSLambda'], - name: 'sentry.javascript.browser', - version: '12.31.12', - packages: [{ name: 'npm:@sentry/browser', version: '12.31.12' }], - }, - }), - ); - }); - - it('merges existing sdk info if one is present on the event body', () => { - event.sdk = { - integrations: ['Clojure'], - name: 'foo', - packages: [{ name: 'npm:@sentry/clj', version: '12.31.12' }], - version: '1337', - }; - - const result = eventToSentryRequest(event, api); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.event).toEqual( - expect.objectContaining({ - sdk: { - integrations: ['Clojure', 'AWSLambda'], - name: 'foo', - packages: [ - { name: 'npm:@sentry/clj', version: '12.31.12' }, - { name: 'npm:@sentry/browser', version: '12.31.12' }, - ], - version: '1337', - }, - }), - ); - }); - - it('uses tunnel as the url if it is configured', () => { - const tunnelRequest = eventToSentryRequest(event, initAPIDetails(ingestDsn, {}, tunnel)); - expect(tunnelRequest.url).toEqual(tunnel); - - const defaultRequest = eventToSentryRequest(event, initAPIDetails(ingestDsn, {})); - expect(defaultRequest.url).toEqual(envelopeUrl); - }); - - it('adds dsn to envelope header if tunnel is configured', () => { - const result = eventToSentryRequest(event, initAPIDetails(ingestDsn, {}, tunnel)); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.envelopeHeader).toEqual( - expect.objectContaining({ - dsn: ingestDsn, - }), - ); - }); - - it('adds default "event" item type to item header if tunnel is configured', () => { - delete event.type; - - const result = eventToSentryRequest(event, initAPIDetails(ingestDsn, {}, tunnel)); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.itemHeader).toEqual( - expect.objectContaining({ - type: 'event', - }), - ); - }); - - it('removes processing metadata before serializing event', () => { - event.sdkProcessingMetadata = { dogs: 'are great!' }; - - const result = eventToSentryRequest(event, api); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.event.processingMetadata).toBeUndefined(); - }); - - it("doesn't depend on optional event fields for success ", () => { - // all event fields are optional - const emptyEvent = {}; - - const result = eventToSentryRequest(emptyEvent, api); - expect(result).toEqual({ - // The body isn't empty because SDK info gets added in `eventToSentryRequest`. (The specifics of that SDK info are - // tested elsewhere.) - body: expect.any(String), - type: 'event', - url: storeUrl, - }); - }); -}); - -describe('sessionToSentryRequest', () => { - it('test envelope creation for aggregateSessions', () => { - const aggregatedSession = { - attrs: { release: '1.0.x', environment: 'prod' }, - aggregates: [{ started: '2021-04-08T12:18:00.000Z', exited: 2 }], - }; - const result = sessionToSentryRequest(aggregatedSession, api); - - const [envelopeHeaderString, itemHeaderString, sessionString] = result.body.split('\n'); - - expect(JSON.parse(envelopeHeaderString)).toEqual( - expect.objectContaining({ - sdk: { name: 'sentry.javascript.browser', version: '12.31.12' }, - }), - ); - expect(JSON.parse(itemHeaderString)).toEqual( - expect.objectContaining({ - type: 'sessions', - }), - ); - expect(JSON.parse(sessionString)).toEqual( - expect.objectContaining({ - attrs: { release: '1.0.x', environment: 'prod' }, - aggregates: [{ started: '2021-04-08T12:18:00.000Z', exited: 2 }], - }), - ); - }); - - it('uses tunnel as the url if it is configured', () => { - const tunnelRequest = sessionToSentryRequest({ aggregates: [] }, initAPIDetails(ingestDsn, {}, tunnel)); - expect(tunnelRequest.url).toEqual(tunnel); - - const defaultRequest = sessionToSentryRequest({ aggregates: [] }, initAPIDetails(ingestDsn, {})); - expect(defaultRequest.url).toEqual(envelopeUrl); - }); - - it('adds dsn to envelope header if tunnel is configured', () => { - const result = sessionToSentryRequest({ aggregates: [] }, initAPIDetails(ingestDsn, {}, tunnel)); - const envelope = parseEnvelopeRequest(result); - - expect(envelope.envelopeHeader).toEqual( - expect.objectContaining({ - dsn: ingestDsn, - }), - ); - }); -}); From 6d3043a0cee8cb6a5a00e198749efdd9bfdbfa11 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Mon, 25 Apr 2022 09:58:23 -0400 Subject: [PATCH 2/2] rename to envelope --- packages/core/src/baseclient.ts | 2 +- packages/core/src/{request.ts => envelope.ts} | 0 packages/core/test/lib/api.test.ts | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) rename packages/core/src/{request.ts => envelope.ts} (100%) diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 6c76065e1829..1969f2d8ef0a 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -32,9 +32,9 @@ import { } from '@sentry/utils'; import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; +import { createEventEnvelope, createSessionEnvelope } from './envelope'; import { IS_DEBUG_BUILD } from './flags'; import { IntegrationIndex, setupIntegrations } from './integration'; -import { createEventEnvelope, createSessionEnvelope } from './request'; const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured."; diff --git a/packages/core/src/request.ts b/packages/core/src/envelope.ts similarity index 100% rename from packages/core/src/request.ts rename to packages/core/src/envelope.ts diff --git a/packages/core/test/lib/api.test.ts b/packages/core/test/lib/api.test.ts index 01f30892c593..d556d9ec7cce 100644 --- a/packages/core/test/lib/api.test.ts +++ b/packages/core/test/lib/api.test.ts @@ -5,10 +5,8 @@ import { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint, initAPI const ingestDsn = 'https://abc@xxxx.ingest.sentry.io:1234/subpath/123'; const dsnPublic = 'https://abc@sentry.io:1234/subpath/123'; -const legacyDsn = 'https://abc:123@sentry.io:1234/subpath/123'; const tunnel = 'https://hello.com/world'; -const ingestDsnAPI = initAPIDetails(ingestDsn); const dsnPublicAPI = initAPIDetails(dsnPublic); describe('API', () => {