From 94d67b753b1b15b6669cd82f6a00231f4afe4963 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 27 Jun 2023 17:37:32 +0200 Subject: [PATCH 1/6] URL parsers --- .../model/http/BacktraceReportSubmission.ts | 18 +---- .../model/http/SubmissionUrlInformation.ts | 71 +++++++++++++++++++ packages/sdk-core/src/model/http/index.ts | 1 + .../modules/metrics/MetricsUrlInformation.ts | 64 +++++++++++++++++ .../http/submissionUrlGenerationTests.spec.ts | 37 ++++++++++ .../sdk-core/tests/http/tokenTests.spec.ts | 24 +++++++ .../sdk-core/tests/http/universeTests.spec.ts | 26 +++++++ 7 files changed, 225 insertions(+), 16 deletions(-) create mode 100644 packages/sdk-core/src/model/http/SubmissionUrlInformation.ts create mode 100644 packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts create mode 100644 packages/sdk-core/tests/http/submissionUrlGenerationTests.spec.ts create mode 100644 packages/sdk-core/tests/http/tokenTests.spec.ts create mode 100644 packages/sdk-core/tests/http/universeTests.spec.ts diff --git a/packages/sdk-core/src/model/http/BacktraceReportSubmission.ts b/packages/sdk-core/src/model/http/BacktraceReportSubmission.ts index 124a5acd..b3a29352 100644 --- a/packages/sdk-core/src/model/http/BacktraceReportSubmission.ts +++ b/packages/sdk-core/src/model/http/BacktraceReportSubmission.ts @@ -2,29 +2,15 @@ import { BacktraceAttachment } from '../attachment'; import { BacktraceConfiguration } from '../configuration/BacktraceConfiguration'; import { BacktraceData } from '../data/BacktraceData'; import { BacktraceRequestHandler } from './BacktraceRequestHandler'; +import { SubmissionUrlInformation } from './SubmissionUrlInformation'; export class BacktraceReportSubmission { private readonly _submissionUrl: string; constructor(options: BacktraceConfiguration, private readonly _requestHandler: BacktraceRequestHandler) { - this._submissionUrl = this.generateReportSubmissionUrl(options.url, options.token); + this._submissionUrl = SubmissionUrlInformation.toJsonReportSubmissionUrl(options.url, options.token); } public send(data: BacktraceData, attachments: BacktraceAttachment[]) { return this._requestHandler.postError(this._submissionUrl, data, attachments); } - - private generateReportSubmissionUrl(url: string, token?: string) { - // if the token doesn't exist - use URL - if (!token) { - return url; - } - - // if the URL has token in the URL, the user probably added a token once again - // in this case, don't do anything - if (url.indexOf(token) !== -1) { - return url; - } - - return new URL(`/post?format=json&token=${token}`, url).href; - } } diff --git a/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts b/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts new file mode 100644 index 00000000..9aa29152 --- /dev/null +++ b/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts @@ -0,0 +1,71 @@ +export class SubmissionUrlInformation { + private static SUBMIT_PREFIX = 'submit.backtrace.io/'; + + /** + * Convert url/token from credentials to JSON submission URL + * @param url credentials URL + * @param token credentials token + * @returns JSON submissionURL + */ + public static toJsonReportSubmissionUrl(url: string, token?: string): string { + // if the token doesn't exist - use URL + if (!token) { + return url; + } + + // if the url points to submit, we should always use it without any modifications + if (url.includes(this.SUBMIT_PREFIX)) { + return url; + } + + // if the URL has token in the URL, the user probably added a token once again + // in this case, don't do anything + if (url.indexOf(token) !== -1) { + return url; + } + + return new URL(`/post?format=json&token=${token}`, url).href; + } + + /** + * Find the universe based on the submission URL + * @param submissionUrl submission URL + * @returns universe name + */ + public static findUniverse(submissionUrl: string): string { + const submitIndex = submissionUrl.indexOf(this.SUBMIT_PREFIX); + if (submitIndex !== -1) { + const universeStartIndex = submitIndex + this.SUBMIT_PREFIX.length; + const endOfUniverseName = submissionUrl.indexOf('/', universeStartIndex); + return submissionUrl.substring(universeStartIndex, endOfUniverseName); + } + // the universe name should be available in the hostname + // for example abc.sp.backtrace.io or zyx.in.backtrace.io or foo.backtrace.io + const hostname = new URL(submissionUrl).host; + const endOfUniverseName = hostname.indexOf('.'); + + return hostname.substring(0, endOfUniverseName); + } + + public static findToken(submissionUrl: string): string | undefined { + const tokenLength = 64; + const submitIndex = submissionUrl.indexOf(this.SUBMIT_PREFIX); + if (submitIndex !== -1) { + const submissionUrlParts = submissionUrl.split('/'); + // submit format URL + // submit.backtrace.io/universe/token/format + // by spliting the submission URL by `/` and dropping the last + // part of the URL, the last element on the list is the token. + return submissionUrlParts[submissionUrlParts.length - 2]; + } + + const tokenQueryParameter = 'token='; + const tokenQueryParameterIndex = submissionUrl.indexOf(tokenQueryParameter); + if (tokenQueryParameterIndex === -1) { + return undefined; + } + + const tokenStartIndex = tokenQueryParameterIndex + tokenQueryParameter.length; + return submissionUrl.substring(tokenStartIndex, tokenStartIndex + tokenLength); + } +} diff --git a/packages/sdk-core/src/model/http/index.ts b/packages/sdk-core/src/model/http/index.ts index 51cf7152..d268daf7 100644 --- a/packages/sdk-core/src/model/http/index.ts +++ b/packages/sdk-core/src/model/http/index.ts @@ -3,3 +3,4 @@ export * from './common/ConnectionError'; export * from './model/BacktraceSubmissionResponse'; export * from './model/BacktraceSubmissionResult'; export * from './model/BacktraceSubmissionStatus'; +export * from './SubmissionUrlInformation'; diff --git a/packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts b/packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts new file mode 100644 index 00000000..489f2ed3 --- /dev/null +++ b/packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts @@ -0,0 +1,64 @@ +import { SubmissionUrlInformation } from '../../model/http'; + +export class MetricsUrlInformation { + public static generateSummedEventsUrl( + hostname: string, + submissionUrl: string, + credentialsToken?: string, + ): string | undefined { + const submissionInformation = this.findSubmissionInformation(submissionUrl, credentialsToken); + if (!submissionInformation) { + return undefined; + } + return this.generateEventsServiceUrl( + hostname, + 'summed-events', + submissionInformation.universe, + submissionInformation.token, + ); + } + + public static generateUniqueEventsUrl( + hostname: string, + submissionUrl: string, + credentialsToken?: string, + ): string | undefined { + const submissionInformation = this.findSubmissionInformation(submissionUrl, credentialsToken); + if (!submissionInformation) { + return undefined; + } + + return this.generateEventsServiceUrl( + hostname, + 'unique-events', + submissionInformation.universe, + submissionInformation.token, + ); + } + + private static generateEventsServiceUrl( + hostname: string, + eventServiceName: string, + universe: string, + token: string, + ): string { + return new URL(`/api/${eventServiceName}/submit?universe=${universe}&token=${token}`, hostname).toString(); + } + + private static findSubmissionInformation( + submissionUrl: string, + token?: string, + ): { universe: string; token: string } | undefined { + const universe = SubmissionUrlInformation.findUniverse(submissionUrl); + if (!universe) { + return undefined; + } + + token = token ?? SubmissionUrlInformation.findToken(submissionUrl); + + if (!token) { + return undefined; + } + return { universe, token }; + } +} diff --git a/packages/sdk-core/tests/http/submissionUrlGenerationTests.spec.ts b/packages/sdk-core/tests/http/submissionUrlGenerationTests.spec.ts new file mode 100644 index 00000000..5b2c7404 --- /dev/null +++ b/packages/sdk-core/tests/http/submissionUrlGenerationTests.spec.ts @@ -0,0 +1,37 @@ +import { SubmissionUrlInformation } from '../../src/model/http'; +describe('Submission Url generation tests', () => { + describe('Submit', () => { + const sampleSubmitUrl = `https://submit.backtrace.io/name/000000000000a1eb7ae344f6e002de2e20c81fbdedf6991c2f3bb45b11111111/json`; + it('Should use submit url from the configuration options', () => { + expect(SubmissionUrlInformation.toJsonReportSubmissionUrl(sampleSubmitUrl)).toBe(sampleSubmitUrl); + }); + + it(`Shouldnt mix token with the submission url`, () => { + expect(SubmissionUrlInformation.toJsonReportSubmissionUrl(sampleSubmitUrl, '123')).toBe(sampleSubmitUrl); + }); + }); + + describe('Direct URL', () => { + const hostname = `https://instance.sp.backtrace.io`; + const token = `000000000000a1eb7ae344f6e002de2e20c81fbdedf6991c2f3bb45b11111111`; + const fullUrl = `${hostname}/post?format=json&token=${token}`; + it('Should use the direct url if the token is not available', () => { + expect(SubmissionUrlInformation.toJsonReportSubmissionUrl(fullUrl)).toBe(fullUrl); + }); + + it(`Shouldn't mix token with the submission url if the token is already there`, () => { + expect(SubmissionUrlInformation.toJsonReportSubmissionUrl(fullUrl, token)).toBe(fullUrl); + }); + + it(`Should generate a full url if the token and instance are passed separated`, () => { + expect(SubmissionUrlInformation.toJsonReportSubmissionUrl(hostname, token)).toBe(fullUrl); + }); + + it(`Should override the token in the submission url`, () => { + const testedToken = '111111110000000000001111111100000000000020c81fbdedf6991c2f3bb45b'; + const expectedUrl = `${hostname}/post?format=json&token=${testedToken}`; + + expect(SubmissionUrlInformation.toJsonReportSubmissionUrl(fullUrl, testedToken)).toBe(expectedUrl); + }); + }); +}); diff --git a/packages/sdk-core/tests/http/tokenTests.spec.ts b/packages/sdk-core/tests/http/tokenTests.spec.ts new file mode 100644 index 00000000..fdc0398b --- /dev/null +++ b/packages/sdk-core/tests/http/tokenTests.spec.ts @@ -0,0 +1,24 @@ +import { SubmissionUrlInformation } from '../../src/model/http'; + +describe('Token tests', () => { + const testedToken = '000000000000a1eb7ae344f6e002de2e20c81fbdedf6991c2f3bb45b11111111'; + describe('Submit', () => { + const sampleSubmitUrl = `https://submit.backtrace.io/test/${testedToken}/json`; + + it('Should correctly find the universe name', () => { + expect(SubmissionUrlInformation.findToken(sampleSubmitUrl)).toBe(testedToken); + }); + }); + + describe('Direct', () => { + it(`Should return undefined if the url doesn't contain the submission token`, () => { + expect(SubmissionUrlInformation.findToken(`https://foo.sp.backtrace.io`)).toBeUndefined(); + }); + + it(`Should return token from the direct url`, () => { + expect( + SubmissionUrlInformation.findToken(`https://foo.sp.backtrace.io/post?format=json&token=${testedToken}`), + ).toBe(testedToken); + }); + }); +}); diff --git a/packages/sdk-core/tests/http/universeTests.spec.ts b/packages/sdk-core/tests/http/universeTests.spec.ts new file mode 100644 index 00000000..578ff0a4 --- /dev/null +++ b/packages/sdk-core/tests/http/universeTests.spec.ts @@ -0,0 +1,26 @@ +import { SubmissionUrlInformation } from '../../src/model/http'; +describe('Universe tests', () => { + const testedUniverseName = 'foo-bar-baz'; + describe('Submit', () => { + const sampleSubmitUrl = `https://submit.backtrace.io/${testedUniverseName}/000000000000a1eb7ae344f6e002de2e20c81fbdedf6991c2f3bb45b11111111/json`; + + it('Should correctly find the universe name', () => { + expect(SubmissionUrlInformation.findUniverse(sampleSubmitUrl)).toBe(testedUniverseName); + }); + }); + + describe('Direct', () => { + const testedBacktraceDomainPrefixes = ['', '.sp', '.in']; + for (const backtracePrefix of testedBacktraceDomainPrefixes) { + it(`Should correctly find the universe name with prefix ${backtracePrefix}`, () => { + const sampleDirectUrl = `https://${testedUniverseName}${backtracePrefix}.backtrace.io`; + expect(SubmissionUrlInformation.findUniverse(sampleDirectUrl)).toBe(testedUniverseName); + }); + } + + it('Should correctly find the universe in the direct url with the token', () => { + const sampleDirectUrl = `https://${testedUniverseName}.sp.backtrace.io/post?format=json&token=000000000000a1eb7ae344f6e002de2e20c81fbdedf6991c2f3bb45b11111111`; + expect(SubmissionUrlInformation.findUniverse(sampleDirectUrl)).toBe(testedUniverseName); + }); + }); +}); From 9ca101cf8b93467a6b45ba509bc98e9ae2a12853 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 27 Jun 2023 17:41:50 +0200 Subject: [PATCH 2/6] Command line attribute provider --- .../src/attributes/ApplicationInformationAttributeProvider.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts index c784bea2..df4c3c31 100644 --- a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts +++ b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts @@ -50,6 +50,10 @@ export class ApplicationInformationAttributeProvider implements BacktraceAttribu private generateDefaultApplicationSearchPaths() { const possibleSourcePaths = [process.cwd()]; + const potentialCommandLineStartupFile = process.argv[1]; + if (potentialCommandLineStartupFile && fs.existsSync(potentialCommandLineStartupFile)) { + possibleSourcePaths.unshift(potentialCommandLineStartupFile); + } if (require.main?.path) { possibleSourcePaths.unshift(path.dirname(require.main.path)); } From 71245a0db95f2abd17d401a0a79abfbfd789754b Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Wed, 28 Jun 2023 00:37:46 +0200 Subject: [PATCH 3/6] Code review suggestions --- .../model/http/SubmissionUrlInformation.ts | 29 ++++++++++--------- .../sdk-core/tests/http/tokenTests.spec.ts | 4 +-- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts b/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts index 9aa29152..fb654d16 100644 --- a/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts +++ b/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts @@ -24,7 +24,10 @@ export class SubmissionUrlInformation { return url; } - return new URL(`/post?format=json&token=${token}`, url).href; + const result = new URL(`/post`, url); + result.searchParams.append('format', 'json'); + result.searchParams.append('token', token); + return result.href; } /** @@ -32,23 +35,28 @@ export class SubmissionUrlInformation { * @param submissionUrl submission URL * @returns universe name */ - public static findUniverse(submissionUrl: string): string { + public static findUniverse(submissionUrl: string): string | undefined { const submitIndex = submissionUrl.indexOf(this.SUBMIT_PREFIX); if (submitIndex !== -1) { + // submit format URL + // submit.backtrace.io/universe/token/format + // we can expect the universe name just after the hostname const universeStartIndex = submitIndex + this.SUBMIT_PREFIX.length; const endOfUniverseName = submissionUrl.indexOf('/', universeStartIndex); return submissionUrl.substring(universeStartIndex, endOfUniverseName); } // the universe name should be available in the hostname // for example abc.sp.backtrace.io or zyx.in.backtrace.io or foo.backtrace.io - const hostname = new URL(submissionUrl).host; - const endOfUniverseName = hostname.indexOf('.'); + const hostname = new URL(submissionUrl).hostname; + if (!hostname.endsWith('backtrace.io')) { + return undefined; + } + const endOfUniverseName = hostname.indexOf('.'); return hostname.substring(0, endOfUniverseName); } - public static findToken(submissionUrl: string): string | undefined { - const tokenLength = 64; + public static findToken(submissionUrl: string): string | null { const submitIndex = submissionUrl.indexOf(this.SUBMIT_PREFIX); if (submitIndex !== -1) { const submissionUrlParts = submissionUrl.split('/'); @@ -59,13 +67,8 @@ export class SubmissionUrlInformation { return submissionUrlParts[submissionUrlParts.length - 2]; } - const tokenQueryParameter = 'token='; - const tokenQueryParameterIndex = submissionUrl.indexOf(tokenQueryParameter); - if (tokenQueryParameterIndex === -1) { - return undefined; - } + const url = new URL(submissionUrl); - const tokenStartIndex = tokenQueryParameterIndex + tokenQueryParameter.length; - return submissionUrl.substring(tokenStartIndex, tokenStartIndex + tokenLength); + return url.searchParams.get('token'); } } diff --git a/packages/sdk-core/tests/http/tokenTests.spec.ts b/packages/sdk-core/tests/http/tokenTests.spec.ts index fdc0398b..8eb79851 100644 --- a/packages/sdk-core/tests/http/tokenTests.spec.ts +++ b/packages/sdk-core/tests/http/tokenTests.spec.ts @@ -11,8 +11,8 @@ describe('Token tests', () => { }); describe('Direct', () => { - it(`Should return undefined if the url doesn't contain the submission token`, () => { - expect(SubmissionUrlInformation.findToken(`https://foo.sp.backtrace.io`)).toBeUndefined(); + it(`Should return null if the url doesn't contain the submission token`, () => { + expect(SubmissionUrlInformation.findToken(`https://foo.sp.backtrace.io`)).toBeNull(); }); it(`Should return token from the direct url`, () => { From 8c92345ebb7e71b7b99e988214ba94d3d0728536 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Wed, 28 Jun 2023 00:49:42 +0200 Subject: [PATCH 4/6] Nullable token --- .../sdk-core/src/modules/metrics/MetricsUrlInformation.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts b/packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts index 489f2ed3..b8903742 100644 --- a/packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts +++ b/packages/sdk-core/src/modules/metrics/MetricsUrlInformation.ts @@ -4,7 +4,7 @@ export class MetricsUrlInformation { public static generateSummedEventsUrl( hostname: string, submissionUrl: string, - credentialsToken?: string, + credentialsToken: string | null, ): string | undefined { const submissionInformation = this.findSubmissionInformation(submissionUrl, credentialsToken); if (!submissionInformation) { @@ -21,7 +21,7 @@ export class MetricsUrlInformation { public static generateUniqueEventsUrl( hostname: string, submissionUrl: string, - credentialsToken?: string, + credentialsToken: string | null, ): string | undefined { const submissionInformation = this.findSubmissionInformation(submissionUrl, credentialsToken); if (!submissionInformation) { @@ -47,7 +47,7 @@ export class MetricsUrlInformation { private static findSubmissionInformation( submissionUrl: string, - token?: string, + token: string | null, ): { universe: string; token: string } | undefined { const universe = SubmissionUrlInformation.findUniverse(submissionUrl); if (!universe) { From e050e7ac719ded4caec9839782d4d7fe213c40b7 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Thu, 29 Jun 2023 14:55:46 +0200 Subject: [PATCH 5/6] Resolved path to node file --- .../attributes/ApplicationInformationAttributeProvider.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts index df4c3c31..35ecc9f4 100644 --- a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts +++ b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts @@ -51,8 +51,11 @@ export class ApplicationInformationAttributeProvider implements BacktraceAttribu private generateDefaultApplicationSearchPaths() { const possibleSourcePaths = [process.cwd()]; const potentialCommandLineStartupFile = process.argv[1]; - if (potentialCommandLineStartupFile && fs.existsSync(potentialCommandLineStartupFile)) { - possibleSourcePaths.unshift(potentialCommandLineStartupFile); + if (potentialCommandLineStartupFile) { + const potentialCommandLineStartupFilePath = path.resolve(potentialCommandLineStartupFile); + if (fs.existsSync(potentialCommandLineStartupFilePath)) { + possibleSourcePaths.unshift(path.resolve(potentialCommandLineStartupFilePath)); + } } if (require.main?.path) { possibleSourcePaths.unshift(path.dirname(require.main.path)); From a3f51d619775db178f83f00077996672e3c25bd0 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Fri, 30 Jun 2023 11:25:08 +0200 Subject: [PATCH 6/6] Do not use resolve --- .../src/attributes/ApplicationInformationAttributeProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts index 35ecc9f4..3d5b5b6c 100644 --- a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts +++ b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts @@ -54,7 +54,7 @@ export class ApplicationInformationAttributeProvider implements BacktraceAttribu if (potentialCommandLineStartupFile) { const potentialCommandLineStartupFilePath = path.resolve(potentialCommandLineStartupFile); if (fs.existsSync(potentialCommandLineStartupFilePath)) { - possibleSourcePaths.unshift(path.resolve(potentialCommandLineStartupFilePath)); + possibleSourcePaths.unshift(potentialCommandLineStartupFilePath); } } if (require.main?.path) {