diff --git a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts index c784bea2..3d5b5b6c 100644 --- a/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts +++ b/packages/node/src/attributes/ApplicationInformationAttributeProvider.ts @@ -50,6 +50,13 @@ export class ApplicationInformationAttributeProvider implements BacktraceAttribu private generateDefaultApplicationSearchPaths() { const possibleSourcePaths = [process.cwd()]; + const potentialCommandLineStartupFile = process.argv[1]; + if (potentialCommandLineStartupFile) { + const potentialCommandLineStartupFilePath = path.resolve(potentialCommandLineStartupFile); + if (fs.existsSync(potentialCommandLineStartupFilePath)) { + possibleSourcePaths.unshift(potentialCommandLineStartupFilePath); + } + } if (require.main?.path) { possibleSourcePaths.unshift(path.dirname(require.main.path)); } 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..fb654d16 --- /dev/null +++ b/packages/sdk-core/src/model/http/SubmissionUrlInformation.ts @@ -0,0 +1,74 @@ +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; + } + + const result = new URL(`/post`, url); + result.searchParams.append('format', 'json'); + result.searchParams.append('token', token); + return result.href; + } + + /** + * Find the universe based on the submission URL + * @param submissionUrl submission URL + * @returns universe name + */ + 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).hostname; + if (!hostname.endsWith('backtrace.io')) { + return undefined; + } + + const endOfUniverseName = hostname.indexOf('.'); + return hostname.substring(0, endOfUniverseName); + } + + public static findToken(submissionUrl: string): string | null { + 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 url = new URL(submissionUrl); + + return url.searchParams.get('token'); + } +} 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..b8903742 --- /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 | null, + ): 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 | null, + ): 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 | null, + ): { 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..8eb79851 --- /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 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`, () => { + 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); + }); + }); +});