From cbcd8fb3da5302e155cdc84c511d4719f50af414 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Fri, 12 Aug 2022 12:41:03 +0900 Subject: [PATCH] Use toucan.js on Deno --- README.md | 2 +- deno_dist/README.md | 2 +- deno_dist/index.ts | 57 ++++++++++++++++++++++++++++++++++++----- deno_test/index.test.ts | 13 +++++++++- package.json | 21 ++++++++------- src/index.ts | 29 +++++++++++++++++++-- src/replacer.ts | 9 +++++++ 7 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 src/replacer.ts diff --git a/README.md b/README.md index 27d4f37d..cbb66830 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ import { Hono } from 'https://deno.land/x/hono/mod.ts' const app = new Hono() -app.use('*', sentry()) +app.use('*', sentry({ dsn: 'https://xxxxxx@xxx.ingest.sentry.io/xxxxxx' })) app.get('/', (c) => c.text('foo')) serve(app.fetch) diff --git a/deno_dist/README.md b/deno_dist/README.md index 27d4f37d..cbb66830 100644 --- a/deno_dist/README.md +++ b/deno_dist/README.md @@ -26,7 +26,7 @@ import { Hono } from 'https://deno.land/x/hono/mod.ts' const app = new Hono() -app.use('*', sentry()) +app.use('*', sentry({ dsn: 'https://xxxxxx@xxx.ingest.sentry.io/xxxxxx' })) app.get('/', (c) => c.text('foo')) serve(app.fetch) diff --git a/deno_dist/index.ts b/deno_dist/index.ts index 4fcf0fd1..48d10319 100644 --- a/deno_dist/index.ts +++ b/deno_dist/index.ts @@ -1,17 +1,62 @@ -import type { Handler } from 'https://raw.githubusercontent.com/honojs/hono/v2.0.6/deno_dist/mod.ts' -import * as Sentry from 'https://deno.land/x/sentry_deno/main.ts' +import type { Context, Handler } from 'https://raw.githubusercontent.com/honojs/hono/v2.0.6/deno_dist/mod.ts' +import Toucan from "https://cdn.skypack.dev/toucan-js@2.6.1" -export const sentry = (): Handler => { +declare module 'https://raw.githubusercontent.com/honojs/hono/v2.0.6/deno_dist/mod.ts' { + interface ContextVariableMap { + sentry: Toucan + } +} + +class MockContext implements ExecutionContext { + passThroughOnException(): void { + throw new Error('Method not implemented.') + } + async waitUntil(promise: Promise): Promise { + await promise + } +} + +export type Options = { + dsn?: string + allowedCookies?: string[] | RegExp + allowedHeaders?: string[] | RegExp + allowedSearchParams?: string[] | RegExp + attachStacktrace?: boolean + debug?: boolean + environment?: string + maxBreadcrumbs?: number + pkg?: Record + release?: string +} + +export const sentry = (options?: Options, callback?: (sentry: Toucan) => void): Handler => { return async (c, next) => { - Sentry.init({ + let hasExecutionContext = true + try { + c.executionCtx + } catch { + hasExecutionContext = false + } + const sentry = new Toucan({ dsn: c.env.SENTRY_DSN || c.env.NEXT_PUBLIC_SENTRY_DSN, - tracesSampleRate: 1.0, + allowedHeaders: ['user-agent'], + allowedSearchParams: /(.*)/, + request: c.req, + context: hasExecutionContext ? c.executionCtx : new MockContext(), + ...options, }) + + if (callback) callback(sentry) + try { await next() } catch (error) { - Sentry.captureException(error) + sentry.captureException(error) throw error } } } + +export const getSentry = (c: Context) => { + return c.get('sentry') +} diff --git a/deno_test/index.test.ts b/deno_test/index.test.ts index 54b5d6ce..c1129d3d 100644 --- a/deno_test/index.test.ts +++ b/deno_test/index.test.ts @@ -4,11 +4,22 @@ import { assertEquals, Hono } from './deps.ts' // Test just only minimal patterns. // Because others are tested well in Cloudflare Workers environment already. +// Mock +class Context implements ExecutionContext { + passThroughOnException(): void { + throw new Error('Method not implemented.') + } + async waitUntil(promise: Promise): Promise { + await promise + } +} + Deno.test('Sentry Middleware', async () => { const app = new Hono() app.use('/sentry/*', sentry()) app.get('/sentry/foo', (c) => c.text('foo')) - const res = await app.request('http://localhost/sentry/foo') + const req = new Request('http://localhost/sentry/foo') + const res = await app.fetch(req, {}, new Context()) assertEquals(res.status, 200) }) diff --git a/package.json b/package.json index 7726780d..7c667ee3 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { - "name": "@honojs/hello", - "version": "0.0.6", - "description": "An example of third-party middleware for Hono", + "name": "@honojs/sentry", + "version": "0.0.1", + "description": "Sentry Middleware for Hono", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ - "dist" + "dist/index.js", + "dist/index.d.ts" ], "scripts": { "test": "jest", @@ -13,21 +14,19 @@ "test:all": "yarn test && yarn test:deno", "denoify": "rimraf deno_dist && denoify", "build": "rimraf dist && tsc", - "prerelease": "yarn denoify && yarn build && yarn test:all", + "prerelease": "yarn build && yarn denoify && yarn test:all", "release": "yarn publish" }, "denoify": { - "port": { - "hono": "honojs/hono" - } + "replacer": "dist/replacer.js" }, "license": "MIT", "private": false, "repository": { "type": "git", - "url": "https://github.com/honojs/middleware-template.git" + "url": "https://github.com/honojs/sentry.git" }, - "homepage": "https://github.com/honojs/middleware-template", + "homepage": "https://github.com/honojs/sentry", "author": "Yusuke Wada (https://github.com/yusukebe)", "publishConfig": { "registry": "https://registry.npmjs.org", @@ -58,4 +57,4 @@ "ts-jest": "^28.0.5", "typescript": "^4.7.4" } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c83c398b..000fd0da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,21 @@ -import type { Handler } from 'hono' +import type { Context, Handler } from 'hono' import Toucan from 'toucan-js' +declare module 'hono' { + interface ContextVariableMap { + sentry: Toucan + } +} + +class MockContext implements ExecutionContext { + passThroughOnException(): void { + throw new Error('Method not implemented.') + } + async waitUntil(promise: Promise): Promise { + await promise + } +} + export type Options = { dsn?: string allowedCookies?: string[] | RegExp @@ -16,12 +31,18 @@ export type Options = { export const sentry = (options?: Options, callback?: (sentry: Toucan) => void): Handler => { return async (c, next) => { + let hasExecutionContext = true + try { + c.executionCtx + } catch { + hasExecutionContext = false + } const sentry = new Toucan({ dsn: c.env.SENTRY_DSN || c.env.NEXT_PUBLIC_SENTRY_DSN, allowedHeaders: ['user-agent'], allowedSearchParams: /(.*)/, request: c.req, - context: c.executionCtx, + context: hasExecutionContext ? c.executionCtx : new MockContext(), ...options, }) @@ -35,3 +56,7 @@ export const sentry = (options?: Options, callback?: (sentry: Toucan) => void): } } } + +export const getSentry = (c: Context) => { + return c.get('sentry') +} diff --git a/src/replacer.ts b/src/replacer.ts new file mode 100644 index 00000000..c1a222d8 --- /dev/null +++ b/src/replacer.ts @@ -0,0 +1,9 @@ +// @denoify-ignore +import { makeThisModuleAnExecutableReplacer } from 'denoify' + +makeThisModuleAnExecutableReplacer(async ({ parsedImportExportStatement, version }) => { + if (parsedImportExportStatement.parsedArgument.nodeModuleName === 'toucan-js') { + return `import Toucan from "https://cdn.skypack.dev/toucan-js@${version}"` + } + return undefined +})