From 53a460b3f3b96e2637286a365c55260b1b359436 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Mon, 13 Nov 2023 09:41:34 +0000 Subject: [PATCH 1/5] feat(node): Add Hapi Integration --- packages/node-experimental/src/index.ts | 1 + packages/node/package.json | 2 + packages/node/src/index.ts | 2 + packages/node/src/integrations/hapi.ts | 163 +++++++++++++ packages/node/src/integrations/index.ts | 1 + yarn.lock | 302 +++++++++++++++++++++++- 6 files changed, 470 insertions(+), 1 deletion(-) create mode 100644 packages/node/src/integrations/hapi.ts diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index e14572b14b6a..67902742b2df 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -53,6 +53,7 @@ export { withScope, captureCheckIn, withMonitor, + hapiErrorPlugin, } from '@sentry/node'; export type { diff --git a/packages/node/package.json b/packages/node/package.json index 08177b80dc71..a44640cd2749 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -30,8 +30,10 @@ "https-proxy-agent": "^5.0.0" }, "devDependencies": { + "@hapi/hapi": "^21.3.2", "@types/cookie": "0.5.2", "@types/express": "^4.17.14", + "@types/hapi": "^18.0.13", "@types/lru-cache": "^5.1.0", "@types/node": "~10.17.0", "express": "^4.17.1", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 0a361f02f5d7..9dae268d1241 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -90,3 +90,5 @@ const INTEGRATIONS = { }; export { INTEGRATIONS as Integrations, Handlers }; + +export { hapiErrorPlugin } from './integrations/hapi'; diff --git a/packages/node/src/integrations/hapi.ts b/packages/node/src/integrations/hapi.ts new file mode 100644 index 000000000000..c9acd938d092 --- /dev/null +++ b/packages/node/src/integrations/hapi.ts @@ -0,0 +1,163 @@ +import type { Boom } from '@hapi/boom'; +import type { RequestEvent, ResponseObject, Server } from '@hapi/hapi'; +import { captureException, configureScope, continueTrace, getActiveTransaction, startTransaction } from '@sentry/core'; +import type { Integration } from '@sentry/types'; +import { addExceptionMechanism, dynamicSamplingContextToSentryBaggageHeader, fill } from '@sentry/utils'; + +function isResponseObject(response: ResponseObject | Boom): response is ResponseObject { + return response && (response as ResponseObject).statusCode !== undefined; +} + +function isBoomObject(response: ResponseObject | Boom): response is Boom { + return response && (response as Boom).isBoom !== undefined; +} + +function isErrorEvent(event: RequestEvent): event is RequestEvent { + return event && (event as RequestEvent).error !== undefined; +} + +function sendErrorToSentry(errorData: object): void { + captureException(errorData, scope => { + scope.addEventProcessor(event => { + addExceptionMechanism(event, { + type: 'hapi', + handled: false, + data: { + function: 'hapiErrorPlugin', + }, + }); + return event; + }); + + return scope; + }); +} + +export const hapiErrorPlugin = { + name: 'SentryHapiErrorPlugin', + version: '0.0.1', + register: async function (server: Server) { + server.events.on('request', (request, event) => { + const transaction = getActiveTransaction(); + + if (isBoomObject(request.response)) { + sendErrorToSentry(request.response); + } else if (isErrorEvent(event)) { + sendErrorToSentry(event.error); + } + + if (transaction) { + transaction.setStatus('internal_error'); + transaction.finish(); + } + }); + }, +}; + +export const hapiTracingPlugin = { + name: 'SentryHapiTracingPlugin', + version: '0.0.1', + register: async function (server: Server) { + server.ext('onPreHandler', (request, h) => { + const transaction = continueTrace( + { + sentryTrace: request.headers['sentry-trace'] || undefined, + baggage: request.headers['baggage'] || undefined, + }, + transactionContext => { + return startTransaction({ + ...transactionContext, + op: 'hapi.request', + name: request.path, + description: request.route ? request.route.path : '', + }); + }, + ); + + configureScope(scope => { + scope.setSpan(transaction); + }); + + return h.continue; + }); + + server.ext('onPreResponse', (request, h) => { + const transaction = getActiveTransaction(); + + if (isResponseObject(request.response) && transaction) { + const response = request.response as ResponseObject; + response.header('sentry-trace', transaction.toTraceparent()); + + const dynamicSamplingContext = dynamicSamplingContextToSentryBaggageHeader( + transaction.getDynamicSamplingContext(), + ); + + if (dynamicSamplingContext) { + response.header('sentry-baggage', dynamicSamplingContext); + } + } + + return h.continue; + }); + + server.ext('onPostHandler', (request, h) => { + const transaction = getActiveTransaction(); + + if (isResponseObject(request.response) && transaction) { + transaction.setHttpStatus(request.response.statusCode); + } + + if (transaction) { + transaction.finish(); + } + + return h.continue; + }); + }, +}; + +export type HapiOptions = { + /** Hapi server instance */ + server?: Server; +}; + +/** + * Hapi Framework Integration + */ +export class Hapi implements Integration { + /** + * @inheritDoc + */ + public static id: string = 'Hapi'; + + /** + * @inheritDoc + */ + public name: string; + + public _hapiServer: Server | undefined; + + public constructor(options?: HapiOptions) { + if (options?.server) { + this._hapiServer = options.server; + } + + this.name = Hapi.id; + } + + /** @inheritDoc */ + public setupOnce(): void { + if (!this._hapiServer) { + return; + } + + fill(this._hapiServer, 'start', (originalStart: () => void) => { + return async function (this: Server) { + await this.register(hapiTracingPlugin); + await this.register(hapiErrorPlugin); + const result = originalStart.apply(this); + return result; + }; + }); + } +} diff --git a/packages/node/src/integrations/index.ts b/packages/node/src/integrations/index.ts index 49820882fdc6..f2ac9c25b807 100644 --- a/packages/node/src/integrations/index.ts +++ b/packages/node/src/integrations/index.ts @@ -9,3 +9,4 @@ export { RequestData } from '@sentry/core'; export { LocalVariables } from './localvariables'; export { Undici } from './undici'; export { Spotlight } from './spotlight'; +export { Hapi } from './hapi'; diff --git a/yarn.lock b/yarn.lock index 81d72c38f57f..1ed186877581 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3389,6 +3389,21 @@ "@hapi/boom" "9.x.x" "@hapi/hoek" "9.x.x" +"@hapi/accept@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-6.0.3.tgz#eef0800a4f89cd969da8e5d0311dc877c37279ab" + integrity sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/ammo@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-6.0.1.tgz#1bc9f7102724ff288ca03b721854fc5393ad123a" + integrity sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/b64@5.x.x": version "5.0.0" resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" @@ -3396,6 +3411,13 @@ dependencies: "@hapi/hoek" "9.x.x" +"@hapi/b64@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-6.0.1.tgz#786b47dc070e14465af49e2428c1025bd06ed3df" + integrity sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/boom@9.x.x": version "9.1.2" resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.2.tgz#48bd41d67437164a2d636e3b5bc954f8c8dc5e38" @@ -3403,6 +3425,13 @@ dependencies: "@hapi/hoek" "9.x.x" +"@hapi/boom@^10.0.0", "@hapi/boom@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" + integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/boom@^9.0.0": version "9.1.4" resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" @@ -3410,11 +3439,57 @@ dependencies: "@hapi/hoek" "9.x.x" +"@hapi/bounce@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-3.0.1.tgz#25a51bf95733749c557c6bf948048bffa66435e4" + integrity sha512-G+/Pp9c1Ha4FDP+3Sy/Xwg2O4Ahaw3lIZFSX+BL4uWi64CmiETuZPxhKDUD4xBMOUZbBlzvO8HjiK8ePnhBadA== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/bourne@2.x.x": version "2.1.0" resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.1.0.tgz#66aff77094dc3080bd5df44ec63881f2676eb020" integrity sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q== +"@hapi/bourne@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7" + integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== + +"@hapi/call@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@hapi/call/-/call-9.0.1.tgz#569b87d5b67abf0e58fb82a3894a61aaed3ca92e" + integrity sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/catbox-memory@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-6.0.1.tgz#8f6b04c0cf2ce25da470324df360bd4e8d68b6ec" + integrity sha512-sVb+/ZxbZIvaMtJfAbdyY+QJUQg9oKTwamXpEg/5xnfG5WbJLTjvEn4kIGKz9pN3ENNbIL/bIdctmHmqi/AdGA== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/catbox@^12.1.1": + version "12.1.1" + resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-12.1.1.tgz#9339dca0a5b18b3ca0a825ac5dfc916dbc5bab83" + integrity sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/podium" "^5.0.0" + "@hapi/validate" "^2.0.1" + +"@hapi/content@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/content/-/content-6.0.0.tgz#2427af3bac8a2f743512fce2a70cbdc365af29df" + integrity sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA== + dependencies: + "@hapi/boom" "^10.0.0" + "@hapi/cryptiles@5.x.x": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" @@ -3422,11 +3497,61 @@ dependencies: "@hapi/boom" "9.x.x" +"@hapi/cryptiles@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-6.0.1.tgz#7868a9d4233567ed66f0a9caf85fdcc56e980621" + integrity sha512-9GM9ECEHfR8lk5ASOKG4+4ZsEzFqLfhiryIJ2ISePVB92OHLp/yne4m+zn7z9dgvM98TLpiFebjDFQ0UHcqxXQ== + dependencies: + "@hapi/boom" "^10.0.1" + +"@hapi/file@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@hapi/file/-/file-3.0.0.tgz#f1fd824493ac89a6fceaf89c824afc5ae2121c09" + integrity sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q== + +"@hapi/hapi@^21.3.2": + version "21.3.2" + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-21.3.2.tgz#f9f94c1c28ccad1444c838d04070fa1cd0409b33" + integrity sha512-tbm0zmsdUj8iw4NzFV30FST/W4qzh/Lsw6Q5o5gAhOuoirWvxm8a4G3o60bqBw8nXvRNJ8uLtE0RKLlZINxHcQ== + dependencies: + "@hapi/accept" "^6.0.1" + "@hapi/ammo" "^6.0.1" + "@hapi/boom" "^10.0.1" + "@hapi/bounce" "^3.0.1" + "@hapi/call" "^9.0.1" + "@hapi/catbox" "^12.1.1" + "@hapi/catbox-memory" "^6.0.1" + "@hapi/heavy" "^8.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/mimos" "^7.0.1" + "@hapi/podium" "^5.0.1" + "@hapi/shot" "^6.0.1" + "@hapi/somever" "^4.1.1" + "@hapi/statehood" "^8.1.1" + "@hapi/subtext" "^8.1.0" + "@hapi/teamwork" "^6.0.0" + "@hapi/topo" "^6.0.1" + "@hapi/validate" "^2.0.1" + +"@hapi/heavy@^8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-8.0.1.tgz#e2be4a6a249005b5a587f7604aafa8ed02461fb6" + integrity sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/validate" "^2.0.1" + "@hapi/hoek@9.x.x": version "9.2.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== +"@hapi/hoek@^11.0.2": + version "11.0.2" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.2.tgz#cb3ea547daac7de5c9cf1d960c3f35c34f065427" + integrity sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw== + "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -3443,6 +3568,44 @@ "@hapi/cryptiles" "5.x.x" "@hapi/hoek" "9.x.x" +"@hapi/iron@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-7.0.1.tgz#f74bace8dad9340c7c012c27c078504f070f14b5" + integrity sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ== + dependencies: + "@hapi/b64" "^6.0.1" + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/cryptiles" "^6.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/mimos@^7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-7.0.1.tgz#5b65c76bb9da28ba34b0092215891f2c72bc899d" + integrity sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew== + dependencies: + "@hapi/hoek" "^11.0.2" + mime-db "^1.52.0" + +"@hapi/nigel@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-5.0.1.tgz#a6dfe357e9d48d944e2ffc552bd95cb701d79ee9" + integrity sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/vise" "^5.0.1" + +"@hapi/pez@^6.1.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-6.1.0.tgz#64d9f95580fc7d8f1d13437ee4a8676709954fda" + integrity sha512-+FE3sFPYuXCpuVeHQ/Qag1b45clR2o54QoonE/gKHv9gukxQ8oJJZPR7o3/ydDTK6racnCJXxOyT1T93FCJMIg== + dependencies: + "@hapi/b64" "^6.0.1" + "@hapi/boom" "^10.0.1" + "@hapi/content" "^6.0.0" + "@hapi/hoek" "^11.0.2" + "@hapi/nigel" "^5.0.1" + "@hapi/podium@^4.1.3": version "4.1.3" resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" @@ -3452,11 +3615,67 @@ "@hapi/teamwork" "5.x.x" "@hapi/validate" "1.x.x" +"@hapi/podium@^5.0.0", "@hapi/podium@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-5.0.1.tgz#f292b4c0ca3118747394a102c6c3340bda96662f" + integrity sha512-eznFTw6rdBhAijXFIlBOMJJd+lXTvqbrBIS4Iu80r2KTVIo4g+7fLy4NKp/8+UnSt5Ox6mJtAlKBU/Sf5080TQ== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/teamwork" "^6.0.0" + "@hapi/validate" "^2.0.1" + +"@hapi/shot@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-6.0.1.tgz#ea84d1810b7c8599d5517c23b4ec55a529d7dc16" + integrity sha512-s5ynMKZXYoDd3dqPw5YTvOR/vjHvMTxc388+0qL0jZZP1+uwXuUD32o9DuuuLsmTlyXCWi02BJl1pBpwRuUrNA== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/validate" "^2.0.1" + +"@hapi/somever@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-4.1.1.tgz#b492c78408303c72cd1a39c5060f35d18a404b27" + integrity sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg== + dependencies: + "@hapi/bounce" "^3.0.1" + "@hapi/hoek" "^11.0.2" + +"@hapi/statehood@^8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-8.1.1.tgz#db4bd14c90810a1389763cb0b0b8f221aa4179c1" + integrity sha512-YbK7PSVUA59NArAW5Np0tKRoIZ5VNYUicOk7uJmWZF6XyH5gGL+k62w77SIJb0AoAJ0QdGQMCQ/WOGL1S3Ydow== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bounce" "^3.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/cryptiles" "^6.0.1" + "@hapi/hoek" "^11.0.2" + "@hapi/iron" "^7.0.1" + "@hapi/validate" "^2.0.1" + +"@hapi/subtext@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-8.1.0.tgz#58733020a6655bc4d978df9e2f75e31696ff3f91" + integrity sha512-PyaN4oSMtqPjjVxLny1k0iYg4+fwGusIhaom9B2StinBclHs7v46mIW706Y+Wo21lcgulGyXbQrmT/w4dus6ww== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/content" "^6.0.0" + "@hapi/file" "^3.0.0" + "@hapi/hoek" "^11.0.2" + "@hapi/pez" "^6.1.0" + "@hapi/wreck" "^18.0.1" + "@hapi/teamwork@5.x.x": version "5.1.1" resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.1.tgz#4d2ba3cac19118a36c44bf49a3a47674de52e4e4" integrity sha512-1oPx9AE5TIv+V6Ih54RP9lTZBso3rP8j4Xhb6iSVwPXtAM+sDopl5TFMv5Paw73UnpZJ9gjcrTE1BXrWt9eQrg== +"@hapi/teamwork@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-6.0.0.tgz#b3a173cf811ba59fc6ee22318a1b51f4561f06e0" + integrity sha512-05HumSy3LWfXpmJ9cr6HzwhAavrHkJ1ZRCmNE2qJMihdM5YcWreWPfyN0yKT2ZjCM92au3ZkuodjBxOibxM67A== + "@hapi/topo@^5.0.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" @@ -3464,6 +3683,13 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@hapi/topo@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-6.0.2.tgz#f219c1c60da8430228af4c1f2e40c32a0d84bbb4" + integrity sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/validate@1.x.x": version "1.1.3" resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" @@ -3472,6 +3698,30 @@ "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" +"@hapi/validate@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-2.0.1.tgz#45cf228c4c8cfc61ba2da7e0a5ba93ff3b9afff1" + integrity sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hapi/topo" "^6.0.1" + +"@hapi/vise@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-5.0.1.tgz#5c9f16bcf1c039ddd4b6cad5f32d71eeb6bb7dac" + integrity sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/wreck@^18.0.1": + version "18.0.1" + resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-18.0.1.tgz#6df04532be25fd128c5244e72ccc21438cf8bb65" + integrity sha512-OLHER70+rZxvDl75xq3xXOfd3e8XIvz8fWY0dqg92UvhZ29zo24vQgfqgHSYhB5ZiuFpSLeriOisAlxAo/1jWg== + dependencies: + "@hapi/boom" "^10.0.1" + "@hapi/bourne" "^3.0.0" + "@hapi/hoek" "^11.0.2" + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -5516,6 +5766,11 @@ "@types/connect" "*" "@types/node" "*" +"@types/boom@*": + version "7.3.5" + resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.3.5.tgz#b87dfad50d693a86b2f745efae7cf43d0af673ce" + integrity sha512-jBS0kU2s9W2sx+ILEyO4kxqIYLllqcUXTaVrBctvGptZ+4X3TWkkgY9+AmxdMPKrgiDDdLcfsaQCTu7bniLvgw== + "@types/bson@*": version "4.2.0" resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.2.0.tgz#a2f71e933ff54b2c3bf267b67fa221e295a33337" @@ -5523,6 +5778,11 @@ dependencies: bson "*" +"@types/catbox@*": + version "10.0.9" + resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.9.tgz#7564e279bd7ad4654d3adaa112f5e90f6911d805" + integrity sha512-4qXm1SmZurBMNFc/536+7gfbOlN43fWyoo4O0bdLqtpDK/cpuCYnEDou0Cl4naaMwuJ19rEwnuscR7tetGnTDA== + "@types/chai-as-promised@^7.1.2": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz#779166b90fda611963a3adbfd00b339d03b747bd" @@ -5903,6 +6163,20 @@ dependencies: "@types/node" "*" +"@types/hapi@^18.0.13": + version "18.0.13" + resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-18.0.13.tgz#248b792a3ed1f6bd8194e85a37325f8e6426ee88" + integrity sha512-iPoTYS7pg5DBcAwP2kIdVX7EQ8JJ8Wqeu98gESJ83LoK8HeJV2sRPUSKYAwU5MzE73Y8oBJovimW+dP7b5H3pQ== + dependencies: + "@types/boom" "*" + "@types/catbox" "*" + "@types/iron" "*" + "@types/mimos" "*" + "@types/node" "*" + "@types/podium" "*" + "@types/shot" "*" + joi "^17.3.0" + "@types/hapi__catbox@*": version "10.2.5" resolved "https://registry.yarnpkg.com/@types/hapi__catbox/-/hapi__catbox-10.2.5.tgz#4eb5e2fbb966acd2a3c2e86f935b7de95aceb7dd" @@ -5979,6 +6253,13 @@ resolved "https://registry.yarnpkg.com/@types/htmlbars-inline-precompile/-/htmlbars-inline-precompile-1.0.1.tgz#de564513fabb165746aecd76369c87bd85e5bbb4" integrity sha512-sVD2e6QAAHW0Y6Btse+tTA9k9g0iKm87wjxRsgZRU5EwSooz80tenbV+fA+f2BI2g0G2CqxsS1rIlwQCtPRQow== +"@types/iron@*": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@types/iron/-/iron-5.0.5.tgz#8f9944341e599ddf1afea6023a37b4368f8d81d3" + integrity sha512-ndu2RvRJ5LWsSVF0kBMJe9qnNcFcAO9eYwzr2P4FOU6m5ypRrbdiX+d8x4GNG7lIn1mKShyQf3M08CIX4wPsEA== + dependencies: + "@types/node" "*" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -6078,6 +6359,13 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== +"@types/mimos@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/mimos/-/mimos-3.0.6.tgz#fc0911e34b8524baedc7334d464e57971cd09a7f" + integrity sha512-pQlYu/Q1e5F5lyu7ATW4J2cyPOfjhRHZgAepZlKBbHqqAjshteHtNLqBXgx7KV5GjXjPLXWUvbzWaGwmVFPaYA== + dependencies: + "@types/mime-db" "*" + "@types/minimatch@*", "@types/minimatch@^3.0.3", "@types/minimatch@^3.0.4": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -6219,6 +6507,11 @@ pg-protocol "*" pg-types "^2.2.0" +"@types/podium@*": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.4.tgz#ed464334563f766b0bbbe9442421fc325eb9ccfe" + integrity sha512-HuG5/hRHs9PxuXXlNFXPy7mHMnBD6Z4riED2SFGwjs+RcszJUkxLgYHQpoiDpYrhLv7sHk9WDyswybD6aNYkig== + "@types/prettier@^2.1.5": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" @@ -6362,6 +6655,13 @@ resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f" integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg== +"@types/shot@*": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/shot/-/shot-4.0.4.tgz#3d7da5de15be422f3e599f557a5a230cd6a2181f" + integrity sha512-wxOqvlSXVjtEq2XwYbjPvbg2j7a18TRVDrAu60j4SxSNUkw13+q5SfLYHbEeqbE+72vNqLo7KGl2RG7LOCFYMQ== + dependencies: + "@types/node" "*" + "@types/sinon@^10.0.13": version "10.0.16" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.16.tgz#4bf10313bd9aa8eef1e50ec9f4decd3dd455b4d3" @@ -21331,7 +21631,7 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== From 3dddac110e332f418fe43c01f802f4eb498be5b0 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 15 Nov 2023 09:07:26 +0000 Subject: [PATCH 2/5] Vendor hapi / boom types. --- packages/node/package.json | 2 - .../integrations/{hapi.ts => hapi/index.ts} | 4 +- packages/node/src/integrations/hapi/types.ts | 83 +++++ yarn.lock | 302 +----------------- 4 files changed, 86 insertions(+), 305 deletions(-) rename packages/node/src/integrations/{hapi.ts => hapi/index.ts} (97%) create mode 100644 packages/node/src/integrations/hapi/types.ts diff --git a/packages/node/package.json b/packages/node/package.json index a44640cd2749..08177b80dc71 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -30,10 +30,8 @@ "https-proxy-agent": "^5.0.0" }, "devDependencies": { - "@hapi/hapi": "^21.3.2", "@types/cookie": "0.5.2", "@types/express": "^4.17.14", - "@types/hapi": "^18.0.13", "@types/lru-cache": "^5.1.0", "@types/node": "~10.17.0", "express": "^4.17.1", diff --git a/packages/node/src/integrations/hapi.ts b/packages/node/src/integrations/hapi/index.ts similarity index 97% rename from packages/node/src/integrations/hapi.ts rename to packages/node/src/integrations/hapi/index.ts index c9acd938d092..9f007ce281d2 100644 --- a/packages/node/src/integrations/hapi.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -1,9 +1,9 @@ -import type { Boom } from '@hapi/boom'; -import type { RequestEvent, ResponseObject, Server } from '@hapi/hapi'; import { captureException, configureScope, continueTrace, getActiveTransaction, startTransaction } from '@sentry/core'; import type { Integration } from '@sentry/types'; import { addExceptionMechanism, dynamicSamplingContextToSentryBaggageHeader, fill } from '@sentry/utils'; +import type { Boom, RequestEvent, ResponseObject, Server } from './types'; + function isResponseObject(response: ResponseObject | Boom): response is ResponseObject { return response && (response as ResponseObject).statusCode !== undefined; } diff --git a/packages/node/src/integrations/hapi/types.ts b/packages/node/src/integrations/hapi/types.ts new file mode 100644 index 000000000000..a4cb95cd79c1 --- /dev/null +++ b/packages/node/src/integrations/hapi/types.ts @@ -0,0 +1,83 @@ +// Vendored and simplified from @types/hapi__hapi and @types/boom +export interface Boom extends Error { + isBoom: boolean; + isServer: boolean; + message: string; + reformat: () => string; + isMissing?: boolean | undefined; + data: Data; +} + +export interface RequestEvent { + timestamp: string; + tags: string[]; + channel: 'internal' | 'app' | 'error'; + data: object | string; + error: object; +} + +export interface Server { + app: A; + events: ServerEvents; + ext(event: ServerExtType, method: LifecycleMethod, options?: unknown | undefined): void; + initialize(): Promise; + register(plugins: Plugin, options?: unknown | undefined): Promise; + start(): Promise; +} + +export interface ResponseObject { + statusCode: number; + header: (key: string, value: string) => void; +} + +type RequestEventHandler = (request: Request, event: RequestEvent, tags: { [key: string]: true }) => void; +type LifecycleMethod = (request: Request, h: ResponseToolkit, err?: Error | undefined) => unknown; +type Plugin = PluginBase & (PluginNameVersion | PluginPackage); +type ServerExtType = + | 'onPreStart' + | 'onPostStart' + | 'onPreStop' + | 'onPostStop' + | 'onPreAuth' + | 'onCredentials' + | 'onPostAuth' + | 'onPreHandler' + | 'onPostHandler' + | 'onPreResponse' + | 'onPostResponse'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ServerApplicationState {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface RequestEvents {} + +interface ServerEvents { + on(criteria: 'request', listener: RequestEventHandler): this; +} + +interface PluginBase { + register: (server: Server, options: T) => void | Promise; +} + +interface PluginPackage { + pkg: PluginNameVersion; +} + +interface ResponseToolkit { + readonly continue: symbol; +} + +interface PluginNameVersion { + name: string; + version?: string | undefined; +} + +interface Request { + events: RequestEvents; + response: ResponseObject | Boom; + headers: Record; + path: string; + route: { + path: string; + }; +} diff --git a/yarn.lock b/yarn.lock index 1ed186877581..81d72c38f57f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3389,21 +3389,6 @@ "@hapi/boom" "9.x.x" "@hapi/hoek" "9.x.x" -"@hapi/accept@^6.0.1": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-6.0.3.tgz#eef0800a4f89cd969da8e5d0311dc877c37279ab" - integrity sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/hoek" "^11.0.2" - -"@hapi/ammo@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-6.0.1.tgz#1bc9f7102724ff288ca03b721854fc5393ad123a" - integrity sha512-pmL+nPod4g58kXrMcsGLp05O2jF4P2Q3GiL8qYV7nKYEh3cGf+rV4P5Jyi2Uq0agGhVU63GtaSAfBEZOlrJn9w== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/b64@5.x.x": version "5.0.0" resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-5.0.0.tgz#b8210cbd72f4774985e78569b77e97498d24277d" @@ -3411,13 +3396,6 @@ dependencies: "@hapi/hoek" "9.x.x" -"@hapi/b64@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@hapi/b64/-/b64-6.0.1.tgz#786b47dc070e14465af49e2428c1025bd06ed3df" - integrity sha512-ZvjX4JQReUmBheeCq+S9YavcnMMHWqx3S0jHNXWIM1kQDxB9cyfSycpVvjfrKcIS8Mh5N3hmu/YKo4Iag9g2Kw== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/boom@9.x.x": version "9.1.2" resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.2.tgz#48bd41d67437164a2d636e3b5bc954f8c8dc5e38" @@ -3425,13 +3403,6 @@ dependencies: "@hapi/hoek" "9.x.x" -"@hapi/boom@^10.0.0", "@hapi/boom@^10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-10.0.1.tgz#ebb14688275ae150aa6af788dbe482e6a6062685" - integrity sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/boom@^9.0.0": version "9.1.4" resolved "https://registry.yarnpkg.com/@hapi/boom/-/boom-9.1.4.tgz#1f9dad367c6a7da9f8def24b4a986fc5a7bd9db6" @@ -3439,57 +3410,11 @@ dependencies: "@hapi/hoek" "9.x.x" -"@hapi/bounce@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@hapi/bounce/-/bounce-3.0.1.tgz#25a51bf95733749c557c6bf948048bffa66435e4" - integrity sha512-G+/Pp9c1Ha4FDP+3Sy/Xwg2O4Ahaw3lIZFSX+BL4uWi64CmiETuZPxhKDUD4xBMOUZbBlzvO8HjiK8ePnhBadA== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/hoek" "^11.0.2" - "@hapi/bourne@2.x.x": version "2.1.0" resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-2.1.0.tgz#66aff77094dc3080bd5df44ec63881f2676eb020" integrity sha512-i1BpaNDVLJdRBEKeJWkVO6tYX6DMFBuwMhSuWqLsY4ufeTKGVuV5rBsUhxPayXqnnWHgXUAmWK16H/ykO5Wj4Q== -"@hapi/bourne@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-3.0.0.tgz#f11fdf7dda62fe8e336fa7c6642d9041f30356d7" - integrity sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w== - -"@hapi/call@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@hapi/call/-/call-9.0.1.tgz#569b87d5b67abf0e58fb82a3894a61aaed3ca92e" - integrity sha512-uPojQRqEL1GRZR4xXPqcLMujQGaEpyVPRyBlD8Pp5rqgIwLhtveF9PkixiKru2THXvuN8mUrLeet5fqxKAAMGg== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/hoek" "^11.0.2" - -"@hapi/catbox-memory@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox-memory/-/catbox-memory-6.0.1.tgz#8f6b04c0cf2ce25da470324df360bd4e8d68b6ec" - integrity sha512-sVb+/ZxbZIvaMtJfAbdyY+QJUQg9oKTwamXpEg/5xnfG5WbJLTjvEn4kIGKz9pN3ENNbIL/bIdctmHmqi/AdGA== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/hoek" "^11.0.2" - -"@hapi/catbox@^12.1.1": - version "12.1.1" - resolved "https://registry.yarnpkg.com/@hapi/catbox/-/catbox-12.1.1.tgz#9339dca0a5b18b3ca0a825ac5dfc916dbc5bab83" - integrity sha512-hDqYB1J+R0HtZg4iPH3LEnldoaBsar6bYp0EonBmNQ9t5CO+1CqgCul2ZtFveW1ReA5SQuze9GPSU7/aecERhw== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/hoek" "^11.0.2" - "@hapi/podium" "^5.0.0" - "@hapi/validate" "^2.0.1" - -"@hapi/content@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@hapi/content/-/content-6.0.0.tgz#2427af3bac8a2f743512fce2a70cbdc365af29df" - integrity sha512-CEhs7j+H0iQffKfe5Htdak5LBOz/Qc8TRh51cF+BFv0qnuph3Em4pjGVzJMkI2gfTDdlJKWJISGWS1rK34POGA== - dependencies: - "@hapi/boom" "^10.0.0" - "@hapi/cryptiles@5.x.x": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-5.1.0.tgz#655de4cbbc052c947f696148c83b187fc2be8f43" @@ -3497,61 +3422,11 @@ dependencies: "@hapi/boom" "9.x.x" -"@hapi/cryptiles@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@hapi/cryptiles/-/cryptiles-6.0.1.tgz#7868a9d4233567ed66f0a9caf85fdcc56e980621" - integrity sha512-9GM9ECEHfR8lk5ASOKG4+4ZsEzFqLfhiryIJ2ISePVB92OHLp/yne4m+zn7z9dgvM98TLpiFebjDFQ0UHcqxXQ== - dependencies: - "@hapi/boom" "^10.0.1" - -"@hapi/file@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@hapi/file/-/file-3.0.0.tgz#f1fd824493ac89a6fceaf89c824afc5ae2121c09" - integrity sha512-w+lKW+yRrLhJu620jT3y+5g2mHqnKfepreykvdOcl9/6up8GrQQn+l3FRTsjHTKbkbfQFkuksHpdv2EcpKcJ4Q== - -"@hapi/hapi@^21.3.2": - version "21.3.2" - resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-21.3.2.tgz#f9f94c1c28ccad1444c838d04070fa1cd0409b33" - integrity sha512-tbm0zmsdUj8iw4NzFV30FST/W4qzh/Lsw6Q5o5gAhOuoirWvxm8a4G3o60bqBw8nXvRNJ8uLtE0RKLlZINxHcQ== - dependencies: - "@hapi/accept" "^6.0.1" - "@hapi/ammo" "^6.0.1" - "@hapi/boom" "^10.0.1" - "@hapi/bounce" "^3.0.1" - "@hapi/call" "^9.0.1" - "@hapi/catbox" "^12.1.1" - "@hapi/catbox-memory" "^6.0.1" - "@hapi/heavy" "^8.0.1" - "@hapi/hoek" "^11.0.2" - "@hapi/mimos" "^7.0.1" - "@hapi/podium" "^5.0.1" - "@hapi/shot" "^6.0.1" - "@hapi/somever" "^4.1.1" - "@hapi/statehood" "^8.1.1" - "@hapi/subtext" "^8.1.0" - "@hapi/teamwork" "^6.0.0" - "@hapi/topo" "^6.0.1" - "@hapi/validate" "^2.0.1" - -"@hapi/heavy@^8.0.1": - version "8.0.1" - resolved "https://registry.yarnpkg.com/@hapi/heavy/-/heavy-8.0.1.tgz#e2be4a6a249005b5a587f7604aafa8ed02461fb6" - integrity sha512-gBD/NANosNCOp6RsYTsjo2vhr5eYA3BEuogk6cxY0QdhllkkTaJFYtTXv46xd6qhBVMbMMqcSdtqey+UQU3//w== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/hoek" "^11.0.2" - "@hapi/validate" "^2.0.1" - "@hapi/hoek@9.x.x": version "9.2.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.0.tgz#f3933a44e365864f4dad5db94158106d511e8131" integrity sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug== -"@hapi/hoek@^11.0.2": - version "11.0.2" - resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.2.tgz#cb3ea547daac7de5c9cf1d960c3f35c34f065427" - integrity sha512-aKmlCO57XFZ26wso4rJsW4oTUnrgTFw2jh3io7CAtO9w4UltBNwRXvXIVzzyfkaaLRo3nluP/19msA8vDUUuKw== - "@hapi/hoek@^9.0.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -3568,44 +3443,6 @@ "@hapi/cryptiles" "5.x.x" "@hapi/hoek" "9.x.x" -"@hapi/iron@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@hapi/iron/-/iron-7.0.1.tgz#f74bace8dad9340c7c012c27c078504f070f14b5" - integrity sha512-tEZnrOujKpS6jLKliyWBl3A9PaE+ppuL/+gkbyPPDb/l2KSKQyH4lhMkVb+sBhwN+qaxxlig01JRqB8dk/mPxQ== - dependencies: - "@hapi/b64" "^6.0.1" - "@hapi/boom" "^10.0.1" - "@hapi/bourne" "^3.0.0" - "@hapi/cryptiles" "^6.0.1" - "@hapi/hoek" "^11.0.2" - -"@hapi/mimos@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@hapi/mimos/-/mimos-7.0.1.tgz#5b65c76bb9da28ba34b0092215891f2c72bc899d" - integrity sha512-b79V+BrG0gJ9zcRx1VGcCI6r6GEzzZUgiGEJVoq5gwzuB2Ig9Cax8dUuBauQCFKvl2YWSWyOc8mZ8HDaJOtkew== - dependencies: - "@hapi/hoek" "^11.0.2" - mime-db "^1.52.0" - -"@hapi/nigel@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@hapi/nigel/-/nigel-5.0.1.tgz#a6dfe357e9d48d944e2ffc552bd95cb701d79ee9" - integrity sha512-uv3dtYuB4IsNaha+tigWmN8mQw/O9Qzl5U26Gm4ZcJVtDdB1AVJOwX3X5wOX+A07qzpEZnOMBAm8jjSqGsU6Nw== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/vise" "^5.0.1" - -"@hapi/pez@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-6.1.0.tgz#64d9f95580fc7d8f1d13437ee4a8676709954fda" - integrity sha512-+FE3sFPYuXCpuVeHQ/Qag1b45clR2o54QoonE/gKHv9gukxQ8oJJZPR7o3/ydDTK6racnCJXxOyT1T93FCJMIg== - dependencies: - "@hapi/b64" "^6.0.1" - "@hapi/boom" "^10.0.1" - "@hapi/content" "^6.0.0" - "@hapi/hoek" "^11.0.2" - "@hapi/nigel" "^5.0.1" - "@hapi/podium@^4.1.3": version "4.1.3" resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-4.1.3.tgz#91e20838fc2b5437f511d664aabebbb393578a26" @@ -3615,67 +3452,11 @@ "@hapi/teamwork" "5.x.x" "@hapi/validate" "1.x.x" -"@hapi/podium@^5.0.0", "@hapi/podium@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@hapi/podium/-/podium-5.0.1.tgz#f292b4c0ca3118747394a102c6c3340bda96662f" - integrity sha512-eznFTw6rdBhAijXFIlBOMJJd+lXTvqbrBIS4Iu80r2KTVIo4g+7fLy4NKp/8+UnSt5Ox6mJtAlKBU/Sf5080TQ== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/teamwork" "^6.0.0" - "@hapi/validate" "^2.0.1" - -"@hapi/shot@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@hapi/shot/-/shot-6.0.1.tgz#ea84d1810b7c8599d5517c23b4ec55a529d7dc16" - integrity sha512-s5ynMKZXYoDd3dqPw5YTvOR/vjHvMTxc388+0qL0jZZP1+uwXuUD32o9DuuuLsmTlyXCWi02BJl1pBpwRuUrNA== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/validate" "^2.0.1" - -"@hapi/somever@^4.1.1": - version "4.1.1" - resolved "https://registry.yarnpkg.com/@hapi/somever/-/somever-4.1.1.tgz#b492c78408303c72cd1a39c5060f35d18a404b27" - integrity sha512-lt3QQiDDOVRatS0ionFDNrDIv4eXz58IibQaZQDOg4DqqdNme8oa0iPWcE0+hkq/KTeBCPtEOjDOBKBKwDumVg== - dependencies: - "@hapi/bounce" "^3.0.1" - "@hapi/hoek" "^11.0.2" - -"@hapi/statehood@^8.1.1": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@hapi/statehood/-/statehood-8.1.1.tgz#db4bd14c90810a1389763cb0b0b8f221aa4179c1" - integrity sha512-YbK7PSVUA59NArAW5Np0tKRoIZ5VNYUicOk7uJmWZF6XyH5gGL+k62w77SIJb0AoAJ0QdGQMCQ/WOGL1S3Ydow== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/bounce" "^3.0.1" - "@hapi/bourne" "^3.0.0" - "@hapi/cryptiles" "^6.0.1" - "@hapi/hoek" "^11.0.2" - "@hapi/iron" "^7.0.1" - "@hapi/validate" "^2.0.1" - -"@hapi/subtext@^8.1.0": - version "8.1.0" - resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-8.1.0.tgz#58733020a6655bc4d978df9e2f75e31696ff3f91" - integrity sha512-PyaN4oSMtqPjjVxLny1k0iYg4+fwGusIhaom9B2StinBclHs7v46mIW706Y+Wo21lcgulGyXbQrmT/w4dus6ww== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/bourne" "^3.0.0" - "@hapi/content" "^6.0.0" - "@hapi/file" "^3.0.0" - "@hapi/hoek" "^11.0.2" - "@hapi/pez" "^6.1.0" - "@hapi/wreck" "^18.0.1" - "@hapi/teamwork@5.x.x": version "5.1.1" resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-5.1.1.tgz#4d2ba3cac19118a36c44bf49a3a47674de52e4e4" integrity sha512-1oPx9AE5TIv+V6Ih54RP9lTZBso3rP8j4Xhb6iSVwPXtAM+sDopl5TFMv5Paw73UnpZJ9gjcrTE1BXrWt9eQrg== -"@hapi/teamwork@^6.0.0": - version "6.0.0" - resolved "https://registry.yarnpkg.com/@hapi/teamwork/-/teamwork-6.0.0.tgz#b3a173cf811ba59fc6ee22318a1b51f4561f06e0" - integrity sha512-05HumSy3LWfXpmJ9cr6HzwhAavrHkJ1ZRCmNE2qJMihdM5YcWreWPfyN0yKT2ZjCM92au3ZkuodjBxOibxM67A== - "@hapi/topo@^5.0.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" @@ -3683,13 +3464,6 @@ dependencies: "@hapi/hoek" "^9.0.0" -"@hapi/topo@^6.0.1": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-6.0.2.tgz#f219c1c60da8430228af4c1f2e40c32a0d84bbb4" - integrity sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/validate@1.x.x": version "1.1.3" resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-1.1.3.tgz#f750a07283929e09b51aa16be34affb44e1931ad" @@ -3698,30 +3472,6 @@ "@hapi/hoek" "^9.0.0" "@hapi/topo" "^5.0.0" -"@hapi/validate@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@hapi/validate/-/validate-2.0.1.tgz#45cf228c4c8cfc61ba2da7e0a5ba93ff3b9afff1" - integrity sha512-NZmXRnrSLK8MQ9y/CMqE9WSspgB9xA41/LlYR0k967aSZebWr4yNrpxIbov12ICwKy4APSlWXZga9jN5p6puPA== - dependencies: - "@hapi/hoek" "^11.0.2" - "@hapi/topo" "^6.0.1" - -"@hapi/vise@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@hapi/vise/-/vise-5.0.1.tgz#5c9f16bcf1c039ddd4b6cad5f32d71eeb6bb7dac" - integrity sha512-XZYWzzRtINQLedPYlIkSkUr7m5Ddwlu99V9elh8CSygXstfv3UnWIXT0QD+wmR0VAG34d2Vx3olqcEhRRoTu9A== - dependencies: - "@hapi/hoek" "^11.0.2" - -"@hapi/wreck@^18.0.1": - version "18.0.1" - resolved "https://registry.yarnpkg.com/@hapi/wreck/-/wreck-18.0.1.tgz#6df04532be25fd128c5244e72ccc21438cf8bb65" - integrity sha512-OLHER70+rZxvDl75xq3xXOfd3e8XIvz8fWY0dqg92UvhZ29zo24vQgfqgHSYhB5ZiuFpSLeriOisAlxAo/1jWg== - dependencies: - "@hapi/boom" "^10.0.1" - "@hapi/bourne" "^3.0.0" - "@hapi/hoek" "^11.0.2" - "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -5766,11 +5516,6 @@ "@types/connect" "*" "@types/node" "*" -"@types/boom@*": - version "7.3.5" - resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.3.5.tgz#b87dfad50d693a86b2f745efae7cf43d0af673ce" - integrity sha512-jBS0kU2s9W2sx+ILEyO4kxqIYLllqcUXTaVrBctvGptZ+4X3TWkkgY9+AmxdMPKrgiDDdLcfsaQCTu7bniLvgw== - "@types/bson@*": version "4.2.0" resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.2.0.tgz#a2f71e933ff54b2c3bf267b67fa221e295a33337" @@ -5778,11 +5523,6 @@ dependencies: bson "*" -"@types/catbox@*": - version "10.0.9" - resolved "https://registry.yarnpkg.com/@types/catbox/-/catbox-10.0.9.tgz#7564e279bd7ad4654d3adaa112f5e90f6911d805" - integrity sha512-4qXm1SmZurBMNFc/536+7gfbOlN43fWyoo4O0bdLqtpDK/cpuCYnEDou0Cl4naaMwuJ19rEwnuscR7tetGnTDA== - "@types/chai-as-promised@^7.1.2": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.3.tgz#779166b90fda611963a3adbfd00b339d03b747bd" @@ -6163,20 +5903,6 @@ dependencies: "@types/node" "*" -"@types/hapi@^18.0.13": - version "18.0.13" - resolved "https://registry.yarnpkg.com/@types/hapi/-/hapi-18.0.13.tgz#248b792a3ed1f6bd8194e85a37325f8e6426ee88" - integrity sha512-iPoTYS7pg5DBcAwP2kIdVX7EQ8JJ8Wqeu98gESJ83LoK8HeJV2sRPUSKYAwU5MzE73Y8oBJovimW+dP7b5H3pQ== - dependencies: - "@types/boom" "*" - "@types/catbox" "*" - "@types/iron" "*" - "@types/mimos" "*" - "@types/node" "*" - "@types/podium" "*" - "@types/shot" "*" - joi "^17.3.0" - "@types/hapi__catbox@*": version "10.2.5" resolved "https://registry.yarnpkg.com/@types/hapi__catbox/-/hapi__catbox-10.2.5.tgz#4eb5e2fbb966acd2a3c2e86f935b7de95aceb7dd" @@ -6253,13 +5979,6 @@ resolved "https://registry.yarnpkg.com/@types/htmlbars-inline-precompile/-/htmlbars-inline-precompile-1.0.1.tgz#de564513fabb165746aecd76369c87bd85e5bbb4" integrity sha512-sVD2e6QAAHW0Y6Btse+tTA9k9g0iKm87wjxRsgZRU5EwSooz80tenbV+fA+f2BI2g0G2CqxsS1rIlwQCtPRQow== -"@types/iron@*": - version "5.0.5" - resolved "https://registry.yarnpkg.com/@types/iron/-/iron-5.0.5.tgz#8f9944341e599ddf1afea6023a37b4368f8d81d3" - integrity sha512-ndu2RvRJ5LWsSVF0kBMJe9qnNcFcAO9eYwzr2P4FOU6m5ypRrbdiX+d8x4GNG7lIn1mKShyQf3M08CIX4wPsEA== - dependencies: - "@types/node" "*" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -6359,13 +6078,6 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/mimos@*": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/mimos/-/mimos-3.0.6.tgz#fc0911e34b8524baedc7334d464e57971cd09a7f" - integrity sha512-pQlYu/Q1e5F5lyu7ATW4J2cyPOfjhRHZgAepZlKBbHqqAjshteHtNLqBXgx7KV5GjXjPLXWUvbzWaGwmVFPaYA== - dependencies: - "@types/mime-db" "*" - "@types/minimatch@*", "@types/minimatch@^3.0.3", "@types/minimatch@^3.0.4": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -6507,11 +6219,6 @@ pg-protocol "*" pg-types "^2.2.0" -"@types/podium@*": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.4.tgz#ed464334563f766b0bbbe9442421fc325eb9ccfe" - integrity sha512-HuG5/hRHs9PxuXXlNFXPy7mHMnBD6Z4riED2SFGwjs+RcszJUkxLgYHQpoiDpYrhLv7sHk9WDyswybD6aNYkig== - "@types/prettier@^2.1.5": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" @@ -6655,13 +6362,6 @@ resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.0.2.tgz#93eb2c243c351f3f17d5c580c7467ae5d686b65f" integrity sha512-dKkr1bTxbEsFlh2ARpKzcaAmsYixqt9UyCdoEZk8rHyE4iQYcDCyvSjDSf7JUWJHlJiTtbIoQjxKh6ViywqDAg== -"@types/shot@*": - version "4.0.4" - resolved "https://registry.yarnpkg.com/@types/shot/-/shot-4.0.4.tgz#3d7da5de15be422f3e599f557a5a230cd6a2181f" - integrity sha512-wxOqvlSXVjtEq2XwYbjPvbg2j7a18TRVDrAu60j4SxSNUkw13+q5SfLYHbEeqbE+72vNqLo7KGl2RG7LOCFYMQ== - dependencies: - "@types/node" "*" - "@types/sinon@^10.0.13": version "10.0.16" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.16.tgz#4bf10313bd9aa8eef1e50ec9f4decd3dd455b4d3" @@ -21631,7 +21331,7 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2", mime-db@^1.52.0: +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== From b48d46addbee95e9219118a4ab5cd342ab241f49 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 17 Nov 2023 02:43:03 +0000 Subject: [PATCH 3/5] Use vendored types. --- packages/node/src/integrations/hapi/index.ts | 26 +- packages/node/src/integrations/hapi/types.ts | 291 +++++++++++++++---- 2 files changed, 255 insertions(+), 62 deletions(-) diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node/src/integrations/hapi/index.ts index 9f007ce281d2..ce353dc7ee4a 100644 --- a/packages/node/src/integrations/hapi/index.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -36,11 +36,13 @@ function sendErrorToSentry(errorData: object): void { export const hapiErrorPlugin = { name: 'SentryHapiErrorPlugin', version: '0.0.1', - register: async function (server: Server) { + register: async function (serverArg: Record) { + const server = serverArg as unknown as Server; + server.events.on('request', (request, event) => { const transaction = getActiveTransaction(); - if (isBoomObject(request.response)) { + if (request.response && isBoomObject(request.response)) { sendErrorToSentry(request.response); } else if (isErrorEvent(event)) { sendErrorToSentry(event.error); @@ -57,7 +59,9 @@ export const hapiErrorPlugin = { export const hapiTracingPlugin = { name: 'SentryHapiTracingPlugin', version: '0.0.1', - register: async function (server: Server) { + register: async function (serverArg: Record) { + const server = serverArg as unknown as Server; + server.ext('onPreHandler', (request, h) => { const transaction = continueTrace( { @@ -68,8 +72,8 @@ export const hapiTracingPlugin = { return startTransaction({ ...transactionContext, op: 'hapi.request', - name: request.path, - description: request.route ? request.route.path : '', + name: request.route.path, + description: `${request.route.method} ${request.path}`, }); }, ); @@ -84,7 +88,7 @@ export const hapiTracingPlugin = { server.ext('onPreResponse', (request, h) => { const transaction = getActiveTransaction(); - if (isResponseObject(request.response) && transaction) { + if (request.response && isResponseObject(request.response) && transaction) { const response = request.response as ResponseObject; response.header('sentry-trace', transaction.toTraceparent()); @@ -93,7 +97,7 @@ export const hapiTracingPlugin = { ); if (dynamicSamplingContext) { - response.header('sentry-baggage', dynamicSamplingContext); + response.header('baggage', dynamicSamplingContext); } } @@ -103,7 +107,7 @@ export const hapiTracingPlugin = { server.ext('onPostHandler', (request, h) => { const transaction = getActiveTransaction(); - if (isResponseObject(request.response) && transaction) { + if (request.response && isResponseObject(request.response) && transaction) { transaction.setHttpStatus(request.response.statusCode); } @@ -118,7 +122,7 @@ export const hapiTracingPlugin = { export type HapiOptions = { /** Hapi server instance */ - server?: Server; + server?: Record; }; /** @@ -139,7 +143,9 @@ export class Hapi implements Integration { public constructor(options?: HapiOptions) { if (options?.server) { - this._hapiServer = options.server; + const server = options.server as unknown as Server; + + this._hapiServer = server; } this.name = Hapi.id; diff --git a/packages/node/src/integrations/hapi/types.ts b/packages/node/src/integrations/hapi/types.ts index a4cb95cd79c1..67cee359b8e2 100644 --- a/packages/node/src/integrations/hapi/types.ts +++ b/packages/node/src/integrations/hapi/types.ts @@ -1,83 +1,270 @@ -// Vendored and simplified from @types/hapi__hapi and @types/boom -export interface Boom extends Error { +/* eslint-disable @typescript-eslint/no-misused-new */ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/unified-signatures */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/no-namespace */ + +// Vendored and simplified from: +// - @types/hapi__hapi +// - @types/podium +// - @types/boom + +import type * as stream from 'stream'; +import type * as url from 'url'; + +interface Podium { + new (events?: Events[]): Podium; + new (events?: Events): Podium; + + registerEvent(events: Events[]): void; + registerEvent(events: Events): void; + + registerPodium?(podiums: Podium[]): void; + registerPodium?(podiums: Podium): void; + + emit( + criteria: string | { name: string; channel?: string | undefined; tags?: string | string[] | undefined }, + data: any, + callback?: () => void, + ): void; + + on(criteria: string | Criteria, listener: Listener): void; + addListener(criteria: string | Criteria, listener: Listener): void; + once(criteria: string | Criteria, listener: Listener): void; + removeListener(name: string, listener: Listener): Podium; + removeAllListeners(name: string): Podium; + hasListeners(name: string): boolean; +} + +export interface Boom extends Error { isBoom: boolean; isServer: boolean; message: string; + output: Output; reformat: () => string; isMissing?: boolean | undefined; data: Data; } -export interface RequestEvent { - timestamp: string; - tags: string[]; - channel: 'internal' | 'app' | 'error'; - data: object | string; - error: object; +export interface Output { + statusCode: number; + headers: { [index: string]: string }; + payload: Payload; } -export interface Server { - app: A; - events: ServerEvents; - ext(event: ServerExtType, method: LifecycleMethod, options?: unknown | undefined): void; - initialize(): Promise; - register(plugins: Plugin, options?: unknown | undefined): Promise; - start(): Promise; +export interface Payload { + statusCode: number; + error: string; + message: string; + attributes?: any; } -export interface ResponseObject { - statusCode: number; - header: (key: string, value: string) => void; +export type Events = string | EventOptionsObject | Podium; + +export interface EventOptionsObject { + name: string; + channels?: string | string[] | undefined; + clone?: boolean | undefined; + spread?: boolean | undefined; + tags?: boolean | undefined; + shared?: boolean | undefined; } -type RequestEventHandler = (request: Request, event: RequestEvent, tags: { [key: string]: true }) => void; -type LifecycleMethod = (request: Request, h: ResponseToolkit, err?: Error | undefined) => unknown; -type Plugin = PluginBase & (PluginNameVersion | PluginPackage); -type ServerExtType = - | 'onPreStart' - | 'onPostStart' - | 'onPreStop' - | 'onPostStop' - | 'onPreAuth' - | 'onCredentials' - | 'onPostAuth' - | 'onPreHandler' - | 'onPostHandler' - | 'onPreResponse' - | 'onPostResponse'; +export interface CriteriaObject { + name: string; + block?: boolean | number | undefined; + channels?: string | string[] | undefined; + clone?: boolean | undefined; + count?: number | undefined; + filter?: string | string[] | CriteriaFilterOptionsObject | undefined; + spread?: boolean | undefined; + tags?: boolean | undefined; + listener?: Listener | undefined; +} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface ServerApplicationState {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface RequestEvents {} +export interface CriteriaFilterOptionsObject { + tags?: string | string[] | undefined; + all?: boolean | undefined; +} -interface ServerEvents { - on(criteria: 'request', listener: RequestEventHandler): this; +export type Criteria = string | CriteriaObject; + +export interface Listener { + (data: any, tags?: Tags, callback?: () => void): void; +} + +export type Tags = { [tag: string]: boolean }; + +type Dependencies = + | string + | string[] + | { + [key: string]: string; + }; + +interface PluginNameVersion { + name: string; + version?: string | undefined; +} + +interface PluginPackage { + pkg: any; } interface PluginBase { register: (server: Server, options: T) => void | Promise; + multiple?: boolean | undefined; + dependencies?: Dependencies | undefined; + requirements?: + | { + node?: string | undefined; + hapi?: string | undefined; + } + | undefined; + + once?: boolean | undefined; } -interface PluginPackage { - pkg: PluginNameVersion; +type Plugin = PluginBase & (PluginNameVersion | PluginPackage); + +interface UserCredentials {} + +interface AppCredentials {} + +interface AuthCredentials { + scope?: string[] | undefined; + user?: UserCredentials | undefined; + app?: AppCredentials | undefined; } -interface ResponseToolkit { - readonly continue: symbol; +interface RequestAuth { + artifacts: object; + credentials: AuthCredentials; + error: Error; + isAuthenticated: boolean; + isAuthorized: boolean; + mode: string; + strategy: string; } -interface PluginNameVersion { - name: string; - version?: string | undefined; +interface RequestEvents extends Podium { + on(criteria: 'peek', listener: PeekListener): void; + on(criteria: 'finish' | 'disconnect', listener: (data: undefined) => void): void; + once(criteria: 'peek', listener: PeekListener): void; + once(criteria: 'finish' | 'disconnect', listener: (data: undefined) => void): void; } -interface Request { - events: RequestEvents; - response: ResponseObject | Boom; - headers: Record; +namespace Lifecycle { + export type Method = (request: Request, h: ResponseToolkit, err?: Error) => ReturnValue; + export type ReturnValue = ReturnValueTypes | Promise; + export type ReturnValueTypes = + | (null | string | number | boolean) + | Buffer + | (Error | Boom) + | stream.Stream + | (object | object[]) + | symbol + | ResponseToolkit; + export type FailAction = 'error' | 'log' | 'ignore' | Method; +} + +namespace Util { + export interface Dictionary { + [key: string]: T; + } + + export type HTTP_METHODS_PARTIAL_LOWERCASE = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'options'; + export type HTTP_METHODS_PARTIAL = + | 'GET' + | 'POST' + | 'PUT' + | 'PATCH' + | 'DELETE' + | 'OPTIONS' + | HTTP_METHODS_PARTIAL_LOWERCASE; + export type HTTP_METHODS = 'HEAD' | 'head' | HTTP_METHODS_PARTIAL; +} + +interface RequestRoute { + method: Util.HTTP_METHODS_PARTIAL; path: string; - route: { - path: string; + vhost?: string | string[] | undefined; + realm: any; + fingerprint: string; + + auth: { + access(request: Request): boolean; }; } + +interface Request extends Podium { + app: ApplicationState; + readonly auth: RequestAuth; + events: RequestEvents; + readonly headers: Util.Dictionary; + readonly path: string; + response: ResponseObject | Boom | null; + readonly route: RequestRoute; + readonly url: url.Url; +} + +interface ResponseObjectHeaderOptions { + append?: boolean | undefined; + separator?: string | undefined; + override?: boolean | undefined; + duplicate?: boolean | undefined; +} + +export interface ResponseObject extends Podium { + readonly statusCode: number; + header(name: string, value: string, options?: ResponseObjectHeaderOptions): ResponseObject; +} + +interface ResponseToolkit { + readonly continue: symbol; +} + +interface ServerEventCriteria { + name: T; + channels?: string | string[] | undefined; + clone?: boolean | undefined; + count?: number | undefined; + filter?: string | string[] | { tags: string | string[]; all?: boolean | undefined } | undefined; + spread?: boolean | undefined; + tags?: boolean | undefined; +} + +export interface RequestEvent { + timestamp: string; + tags: string[]; + channel: 'internal' | 'app' | 'error'; + data: object; + error: object; +} + +type RequestEventHandler = (request: Request, event: RequestEvent, tags: { [key: string]: true }) => void; +interface ServerEvents { + on(criteria: 'request' | ServerEventCriteria<'request'>, listener: RequestEventHandler): void; +} + +type RouteRequestExtType = + | 'onPreAuth' + | 'onCredentials' + | 'onPostAuth' + | 'onPreHandler' + | 'onPostHandler' + | 'onPreResponse'; + +type ServerRequestExtType = RouteRequestExtType | 'onRequest'; + +export type Server = Record & { + events: ServerEvents; + ext(event: ServerRequestExtType, method: Lifecycle.Method, options?: Record): void; + initialize(): Promise; + register(plugins: Plugin | Array>, options?: Record): Promise; + start(): Promise; +}; + +interface ApplicationState {} + +type PeekListener = (chunk: string, encoding: string) => void; From 52a7f1d1901b7611ff1194fdbd1443587cea9e21 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 17 Nov 2023 02:43:31 +0000 Subject: [PATCH 4/5] Add E2E tests. --- .github/workflows/build.yml | 1 + .../node-hapi-app/.gitignore | 1 + .../test-applications/node-hapi-app/.npmrc | 2 + .../node-hapi-app/event-proxy-server.ts | 253 ++++++++++++++++++ .../node-hapi-app/package.json | 29 ++ .../node-hapi-app/playwright.config.ts | 77 ++++++ .../node-hapi-app/src/app.js | 61 +++++ .../node-hapi-app/start-event-proxy.ts | 6 + .../node-hapi-app/tests/server.test.ts | 194 ++++++++++++++ .../node-hapi-app/tsconfig.json | 10 + 10 files changed, 634 insertions(+) create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/.gitignore create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/.npmrc create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/package.json create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/playwright.config.ts create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/src/app.js create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/start-event-proxy.ts create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/tests/server.test.ts create mode 100644 packages/e2e-tests/test-applications/node-hapi-app/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 690e076ce309..7ca6ea546618 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -871,6 +871,7 @@ jobs: 'sveltekit', 'generic-ts3.8', 'node-experimental-fastify-app', + 'node-hapi-app', ] build-command: - false diff --git a/packages/e2e-tests/test-applications/node-hapi-app/.gitignore b/packages/e2e-tests/test-applications/node-hapi-app/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/.gitignore @@ -0,0 +1 @@ +dist diff --git a/packages/e2e-tests/test-applications/node-hapi-app/.npmrc b/packages/e2e-tests/test-applications/node-hapi-app/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts b/packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts new file mode 100644 index 000000000000..67cf80b4dabf --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/event-proxy-server.ts @@ -0,0 +1,253 @@ +import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; +import { parseEnvelope } from '@sentry/utils'; +import * as fs from 'fs'; +import * as http from 'http'; +import * as https from 'https'; +import type { AddressInfo } from 'net'; +import * as os from 'os'; +import * as path from 'path'; +import * as util from 'util'; +import * as zlib from 'zlib'; + +const readFile = util.promisify(fs.readFile); +const writeFile = util.promisify(fs.writeFile); + +interface EventProxyServerOptions { + /** Port to start the event proxy server at. */ + port: number; + /** The name for the proxy server used for referencing it with listener functions */ + proxyServerName: string; +} + +interface SentryRequestCallbackData { + envelope: Envelope; + rawProxyRequestBody: string; + rawSentryResponseBody: string; + sentryResponseStatusCode?: number; +} + +/** + * Starts an event proxy server that will proxy events to sentry when the `tunnel` option is used. Point the `tunnel` + * option to this server (like this `tunnel: http://localhost:${port option}/`). + */ +export async function startEventProxyServer(options: EventProxyServerOptions): Promise { + const eventCallbackListeners: Set<(data: string) => void> = new Set(); + + const proxyServer = http.createServer((proxyRequest, proxyResponse) => { + const proxyRequestChunks: Uint8Array[] = []; + + proxyRequest.addListener('data', (chunk: Buffer) => { + proxyRequestChunks.push(chunk); + }); + + proxyRequest.addListener('error', err => { + throw err; + }); + + proxyRequest.addListener('end', () => { + const proxyRequestBody = + proxyRequest.headers['content-encoding'] === 'gzip' + ? zlib.gunzipSync(Buffer.concat(proxyRequestChunks)).toString() + : Buffer.concat(proxyRequestChunks).toString(); + + let envelopeHeader = JSON.parse(proxyRequestBody.split('\n')[0]); + + if (!envelopeHeader.dsn) { + throw new Error('[event-proxy-server] No dsn on envelope header. Please set tunnel option.'); + } + + const { origin, pathname, host } = new URL(envelopeHeader.dsn); + + const projectId = pathname.substring(1); + const sentryIngestUrl = `${origin}/api/${projectId}/envelope/`; + + proxyRequest.headers.host = host; + + const sentryResponseChunks: Uint8Array[] = []; + + const sentryRequest = https.request( + sentryIngestUrl, + { headers: proxyRequest.headers, method: proxyRequest.method }, + sentryResponse => { + sentryResponse.addListener('data', (chunk: Buffer) => { + proxyResponse.write(chunk, 'binary'); + sentryResponseChunks.push(chunk); + }); + + sentryResponse.addListener('end', () => { + eventCallbackListeners.forEach(listener => { + const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); + + const data: SentryRequestCallbackData = { + envelope: parseEnvelope(proxyRequestBody, new TextEncoder(), new TextDecoder()), + rawProxyRequestBody: proxyRequestBody, + rawSentryResponseBody, + sentryResponseStatusCode: sentryResponse.statusCode, + }; + + listener(Buffer.from(JSON.stringify(data)).toString('base64')); + }); + proxyResponse.end(); + }); + + sentryResponse.addListener('error', err => { + throw err; + }); + + proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers); + }, + ); + + sentryRequest.write(Buffer.concat(proxyRequestChunks), 'binary'); + sentryRequest.end(); + }); + }); + + const proxyServerStartupPromise = new Promise(resolve => { + proxyServer.listen(options.port, () => { + resolve(); + }); + }); + + const eventCallbackServer = http.createServer((eventCallbackRequest, eventCallbackResponse) => { + eventCallbackResponse.statusCode = 200; + eventCallbackResponse.setHeader('connection', 'keep-alive'); + + const callbackListener = (data: string): void => { + eventCallbackResponse.write(data.concat('\n'), 'utf8'); + }; + + eventCallbackListeners.add(callbackListener); + + eventCallbackRequest.on('close', () => { + eventCallbackListeners.delete(callbackListener); + }); + + eventCallbackRequest.on('error', () => { + eventCallbackListeners.delete(callbackListener); + }); + }); + + const eventCallbackServerStartupPromise = new Promise(resolve => { + eventCallbackServer.listen(0, () => { + const port = String((eventCallbackServer.address() as AddressInfo).port); + void registerCallbackServerPort(options.proxyServerName, port).then(resolve); + }); + }); + + await eventCallbackServerStartupPromise; + await proxyServerStartupPromise; + return; +} + +export async function waitForRequest( + proxyServerName: string, + callback: (eventData: SentryRequestCallbackData) => Promise | boolean, +): Promise { + const eventCallbackServerPort = await retrieveCallbackServerPort(proxyServerName); + + return new Promise((resolve, reject) => { + const request = http.request(`http://localhost:${eventCallbackServerPort}/`, {}, response => { + let eventContents = ''; + + response.on('error', err => { + reject(err); + }); + + response.on('data', (chunk: Buffer) => { + const chunkString = chunk.toString('utf8'); + chunkString.split('').forEach(char => { + if (char === '\n') { + const eventCallbackData: SentryRequestCallbackData = JSON.parse( + Buffer.from(eventContents, 'base64').toString('utf8'), + ); + const callbackResult = callback(eventCallbackData); + if (typeof callbackResult !== 'boolean') { + callbackResult.then( + match => { + if (match) { + response.destroy(); + resolve(eventCallbackData); + } + }, + err => { + throw err; + }, + ); + } else if (callbackResult) { + response.destroy(); + resolve(eventCallbackData); + } + eventContents = ''; + } else { + eventContents = eventContents.concat(char); + } + }); + }); + }); + + request.end(); + }); +} + +export function waitForEnvelopeItem( + proxyServerName: string, + callback: (envelopeItem: EnvelopeItem) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForRequest(proxyServerName, async eventData => { + const envelopeItems = eventData.envelope[1]; + for (const envelopeItem of envelopeItems) { + if (await callback(envelopeItem)) { + resolve(envelopeItem); + return true; + } + } + return false; + }).catch(reject); + }); +} + +export function waitForError( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +export function waitForTransaction( + proxyServerName: string, + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { + return new Promise((resolve, reject) => { + waitForEnvelopeItem(proxyServerName, async envelopeItem => { + const [envelopeItemHeader, envelopeItemBody] = envelopeItem; + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); + return true; + } + return false; + }).catch(reject); + }); +} + +const TEMP_FILE_PREFIX = 'event-proxy-server-'; + +async function registerCallbackServerPort(serverName: string, port: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + await writeFile(tmpFilePath, port, { encoding: 'utf8' }); +} + +function retrieveCallbackServerPort(serverName: string): Promise { + const tmpFilePath = path.join(os.tmpdir(), `${TEMP_FILE_PREFIX}${serverName}`); + return readFile(tmpFilePath, 'utf8'); +} diff --git a/packages/e2e-tests/test-applications/node-hapi-app/package.json b/packages/e2e-tests/test-applications/node-hapi-app/package.json new file mode 100644 index 000000000000..1f667abc8987 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/package.json @@ -0,0 +1,29 @@ +{ + "name": "node-hapi-app", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "tsc", + "start": "node src/app.js", + "test": "playwright test", + "clean": "npx rimraf node_modules,pnpm-lock.yaml", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@hapi/hapi": "21.3.2", + "@sentry/integrations": "latest || *", + "@sentry/node": "latest || *", + "@sentry/tracing": "latest || *", + "@sentry/types": "latest || *", + "@types/node": "18.15.1", + "typescript": "4.9.5" + }, + "devDependencies": { + "@playwright/test": "^1.27.1", + "ts-node": "10.9.1" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/e2e-tests/test-applications/node-hapi-app/playwright.config.ts b/packages/e2e-tests/test-applications/node-hapi-app/playwright.config.ts new file mode 100644 index 000000000000..1b478c6ba6da --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/playwright.config.ts @@ -0,0 +1,77 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; +import { devices } from '@playwright/test'; + +const hapiPort = 3030; +const eventProxyPort = 3031; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +const config: PlaywrightTestConfig = { + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 150_000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: 0, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'list', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: `http://localhost:${hapiPort}`, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + }, + }, + // For now we only test Chrome! + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: [ + { + command: 'pnpm ts-node-script start-event-proxy.ts', + port: eventProxyPort, + }, + { + command: 'pnpm start', + port: hapiPort, + }, + ], +}; + +export default config; diff --git a/packages/e2e-tests/test-applications/node-hapi-app/src/app.js b/packages/e2e-tests/test-applications/node-hapi-app/src/app.js new file mode 100644 index 000000000000..4c71802c9be2 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/src/app.js @@ -0,0 +1,61 @@ +const Sentry = require('@sentry/node'); +const Hapi = require('@hapi/hapi'); + +const server = Hapi.server({ + port: 3030, + host: 'localhost', +}); + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + includeLocalVariables: true, + integrations: [new Sentry.Integrations.Hapi({ server })], + debug: true, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, +}); + +const init = async () => { + server.route({ + method: 'GET', + path: '/test-success', + handler: function (request, h) { + return { version: 'v1' }; + }, + }); + + server.route({ + method: 'GET', + path: '/test-param/{param}', + handler: function (request, h) { + return { paramWas: request.params.param }; + }, + }); + + server.route({ + method: 'GET', + path: '/test-error', + handler: async function (request, h) { + const exceptionId = Sentry.captureException(new Error('This is an error')); + + await Sentry.flush(2000); + + return { exceptionId }; + }, + }); + + server.route({ + method: 'GET', + path: '/test-failure', + handler: async function (request, h) { + throw new Error('This is an error'); + }, + }); +}; + +(async () => { + init(); + await server.start(); + console.log('Server running on %s', server.info.uri); +})(); diff --git a/packages/e2e-tests/test-applications/node-hapi-app/start-event-proxy.ts b/packages/e2e-tests/test-applications/node-hapi-app/start-event-proxy.ts new file mode 100644 index 000000000000..7a3ed463e2ae --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/start-event-proxy.ts @@ -0,0 +1,6 @@ +import { startEventProxyServer } from './event-proxy-server'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'node-hapi-app', +}); diff --git a/packages/e2e-tests/test-applications/node-hapi-app/tests/server.test.ts b/packages/e2e-tests/test-applications/node-hapi-app/tests/server.test.ts new file mode 100644 index 000000000000..0539ed6a3548 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/tests/server.test.ts @@ -0,0 +1,194 @@ +import { test, expect } from '@playwright/test'; +import axios, { AxiosError, AxiosResponse } from 'axios'; +import { waitForError, waitForTransaction } from '../event-proxy-server'; + +const authToken = process.env.E2E_TEST_AUTH_TOKEN; +const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; +const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; +const EVENT_POLLING_TIMEOUT = 90_000; + +test('Sends captured exception to Sentry', async ({ baseURL }) => { + const { data } = await axios.get(`${baseURL}/test-error`); + const { exceptionId } = data; + + const url = `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${exceptionId}/`; + + console.log(`Polling for error eventId: ${exceptionId}`); + + await expect + .poll( + async () => { + try { + const response = await axios.get(url, { headers: { Authorization: `Bearer ${authToken}` } }); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { timeout: EVENT_POLLING_TIMEOUT }, + ) + .toBe(200); +}); + +test('Sends thrown error to Sentry', async ({ baseURL }) => { + const errorEventPromise = waitForError('node-hapi-app', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'This is an error'; + }); + + try { + await axios.get(`${baseURL}/test-failure`); + } catch (e) {} + + const errorEvent = await errorEventPromise; + const errorEventId = errorEvent.event_id; + + await expect + .poll( + async () => { + try { + const response = await axios.get( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${errorEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); +}); + +test('Sends successful transactions to Sentry', async ({ baseURL }) => { + const pageloadTransactionEventPromise = waitForTransaction('node-hapi-app', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'hapi.request' && transactionEvent?.transaction === '/test-success' + ); + }); + + await axios.get(`${baseURL}/test-success`); + + const transactionEvent = await pageloadTransactionEventPromise; + const transactionEventId = transactionEvent.event_id; + + await expect + .poll( + async () => { + try { + const response = await axios.get( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); +}); + +test('Sends parameterized transactions to Sentry', async ({ baseURL }) => { + const pageloadTransactionEventPromise = waitForTransaction('node-hapi-app', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'hapi.request' && + transactionEvent?.transaction === '/test-param/{param}' + ); + }); + + await axios.get(`${baseURL}/test-param/123`); + + const transactionEvent = await pageloadTransactionEventPromise; + const transactionEventId = transactionEvent.event_id; + + expect(transactionEvent?.contexts?.trace?.op).toBe('hapi.request'); + expect(transactionEvent?.transaction).toBe('/test-param/{param}'); + + await expect + .poll( + async () => { + try { + const response = await axios.get( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${transactionEventId}/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + return response.status; + } catch (e) { + if (e instanceof AxiosError && e.response) { + if (e.response.status !== 404) { + throw e; + } else { + return e.response.status; + } + } else { + throw e; + } + } + }, + { + timeout: EVENT_POLLING_TIMEOUT, + }, + ) + .toBe(200); +}); + +test('Sends sentry-trace and baggage as response headers', async ({ baseURL }) => { + const data = await axios.get(`${baseURL}/test-success`); + + expect(data.headers).toHaveProperty('sentry-trace'); + expect(data.headers).toHaveProperty('baggage'); +}); + +test('Continues trace and baggage from incoming headers', async ({ baseURL }) => { + const traceContent = '12312012123120121231201212312012-1121201211212012-0'; + const baggageContent = 'sentry-release=2.0.0,sentry-environment=myEnv'; + + await axios.get(`${baseURL}/test-success`); + + const data = await axios.get(`${baseURL}/test-success`, { + headers: { + 'sentry-trace': traceContent, + baggage: baggageContent, + }, + }); + + expect(data.headers).toHaveProperty('sentry-trace'); + expect(data.headers).toHaveProperty('baggage'); + + expect(data.headers['sentry-trace']).toContain('12312012123120121231201212312012-'); + expect(data.headers['baggage']).toContain(baggageContent); +}); diff --git a/packages/e2e-tests/test-applications/node-hapi-app/tsconfig.json b/packages/e2e-tests/test-applications/node-hapi-app/tsconfig.json new file mode 100644 index 000000000000..17bd2c1f4c00 --- /dev/null +++ b/packages/e2e-tests/test-applications/node-hapi-app/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["node"], + "esModuleInterop": true, + "lib": ["dom", "dom.iterable", "esnext"], + "strict": true, + "outDir": "dist" + }, + "include": ["*.ts"] +} From d8bb0e14681cffabb5d26aa1d41303deb838c13d Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Fri, 24 Nov 2023 09:09:10 +0000 Subject: [PATCH 5/5] Address review comments. --- packages/node/src/integrations/hapi/index.ts | 36 +++++++++++--------- packages/node/src/integrations/hapi/types.ts | 8 +++++ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node/src/integrations/hapi/index.ts index ce353dc7ee4a..e8c582f52e0c 100644 --- a/packages/node/src/integrations/hapi/index.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -1,6 +1,13 @@ -import { captureException, configureScope, continueTrace, getActiveTransaction, startTransaction } from '@sentry/core'; +import { + captureException, + configureScope, + continueTrace, + getActiveTransaction, + SDK_VERSION, + startTransaction, +} from '@sentry/core'; import type { Integration } from '@sentry/types'; -import { addExceptionMechanism, dynamicSamplingContextToSentryBaggageHeader, fill } from '@sentry/utils'; +import { dynamicSamplingContextToSentryBaggageHeader, fill } from '@sentry/utils'; import type { Boom, RequestEvent, ResponseObject, Server } from './types'; @@ -17,25 +24,20 @@ function isErrorEvent(event: RequestEvent): event is RequestEvent { } function sendErrorToSentry(errorData: object): void { - captureException(errorData, scope => { - scope.addEventProcessor(event => { - addExceptionMechanism(event, { - type: 'hapi', - handled: false, - data: { - function: 'hapiErrorPlugin', - }, - }); - return event; - }); - - return scope; + captureException(errorData, { + mechanism: { + type: 'hapi', + handled: false, + data: { + function: 'hapiErrorPlugin', + }, + }, }); } export const hapiErrorPlugin = { name: 'SentryHapiErrorPlugin', - version: '0.0.1', + version: SDK_VERSION, register: async function (serverArg: Record) { const server = serverArg as unknown as Server; @@ -58,7 +60,7 @@ export const hapiErrorPlugin = { export const hapiTracingPlugin = { name: 'SentryHapiTracingPlugin', - version: '0.0.1', + version: SDK_VERSION, register: async function (serverArg: Record) { const server = serverArg as unknown as Server; diff --git a/packages/node/src/integrations/hapi/types.ts b/packages/node/src/integrations/hapi/types.ts index 67cee359b8e2..d74c171ef441 100644 --- a/packages/node/src/integrations/hapi/types.ts +++ b/packages/node/src/integrations/hapi/types.ts @@ -6,8 +6,16 @@ // Vendored and simplified from: // - @types/hapi__hapi +// v17.8.9999 +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c73060bd14bb74a2f1906ccfc714d385863bc07d/types/hapi/v17/index.d.ts +// // - @types/podium +// v1.0.9999 +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c73060bd14bb74a2f1906ccfc714d385863bc07d/types/podium/index.d.ts +// // - @types/boom +// v7.3.9999 +// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c73060bd14bb74a2f1906ccfc714d385863bc07d/types/boom/v4/index.d.ts import type * as stream from 'stream'; import type * as url from 'url';