Use toucan.js on Deno

pull/31/head
Yusuke Wada 2022-08-12 12:41:03 +09:00
parent 9b9b6d2744
commit cbcd8fb3da
7 changed files with 111 additions and 22 deletions

View File

@ -26,7 +26,7 @@ import { Hono } from 'https://deno.land/x/hono/mod.ts'
const app = new Hono() const app = new Hono()
app.use('*', sentry()) app.use('*', sentry({ dsn: 'https://xxxxxx@xxx.ingest.sentry.io/xxxxxx' }))
app.get('/', (c) => c.text('foo')) app.get('/', (c) => c.text('foo'))
serve(app.fetch) serve(app.fetch)

View File

@ -26,7 +26,7 @@ import { Hono } from 'https://deno.land/x/hono/mod.ts'
const app = new Hono() const app = new Hono()
app.use('*', sentry()) app.use('*', sentry({ dsn: 'https://xxxxxx@xxx.ingest.sentry.io/xxxxxx' }))
app.get('/', (c) => c.text('foo')) app.get('/', (c) => c.text('foo'))
serve(app.fetch) serve(app.fetch)

View File

@ -1,17 +1,62 @@
import type { Handler } from 'https://raw.githubusercontent.com/honojs/hono/v2.0.6/deno_dist/mod.ts' import type { Context, 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 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<any>): Promise<void> {
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<string, any>
release?: string
}
export const sentry = (options?: Options, callback?: (sentry: Toucan) => void): Handler => {
return async (c, next) => { 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, 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 { try {
await next() await next()
} catch (error) { } catch (error) {
Sentry.captureException(error) sentry.captureException(error)
throw error throw error
} }
} }
} }
export const getSentry = (c: Context) => {
return c.get('sentry')
}

View File

@ -4,11 +4,22 @@ import { assertEquals, Hono } from './deps.ts'
// Test just only minimal patterns. // Test just only minimal patterns.
// Because others are tested well in Cloudflare Workers environment already. // 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<any>): Promise<void> {
await promise
}
}
Deno.test('Sentry Middleware', async () => { Deno.test('Sentry Middleware', async () => {
const app = new Hono() const app = new Hono()
app.use('/sentry/*', sentry()) app.use('/sentry/*', sentry())
app.get('/sentry/foo', (c) => c.text('foo')) 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) assertEquals(res.status, 200)
}) })

View File

@ -1,11 +1,12 @@
{ {
"name": "@honojs/hello", "name": "@honojs/sentry",
"version": "0.0.6", "version": "0.0.1",
"description": "An example of third-party middleware for Hono", "description": "Sentry Middleware for Hono",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"files": [ "files": [
"dist" "dist/index.js",
"dist/index.d.ts"
], ],
"scripts": { "scripts": {
"test": "jest", "test": "jest",
@ -13,21 +14,19 @@
"test:all": "yarn test && yarn test:deno", "test:all": "yarn test && yarn test:deno",
"denoify": "rimraf deno_dist && denoify", "denoify": "rimraf deno_dist && denoify",
"build": "rimraf dist && tsc", "build": "rimraf dist && tsc",
"prerelease": "yarn denoify && yarn build && yarn test:all", "prerelease": "yarn build && yarn denoify && yarn test:all",
"release": "yarn publish" "release": "yarn publish"
}, },
"denoify": { "denoify": {
"port": { "replacer": "dist/replacer.js"
"hono": "honojs/hono"
}
}, },
"license": "MIT", "license": "MIT",
"private": false, "private": false,
"repository": { "repository": {
"type": "git", "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 <yusuke@kamawada.com> (https://github.com/yusukebe)", "author": "Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)",
"publishConfig": { "publishConfig": {
"registry": "https://registry.npmjs.org", "registry": "https://registry.npmjs.org",

View File

@ -1,6 +1,21 @@
import type { Handler } from 'hono' import type { Context, Handler } from 'hono'
import Toucan from 'toucan-js' 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<any>): Promise<void> {
await promise
}
}
export type Options = { export type Options = {
dsn?: string dsn?: string
allowedCookies?: string[] | RegExp allowedCookies?: string[] | RegExp
@ -16,12 +31,18 @@ export type Options = {
export const sentry = (options?: Options, callback?: (sentry: Toucan) => void): Handler => { export const sentry = (options?: Options, callback?: (sentry: Toucan) => void): Handler => {
return async (c, next) => { return async (c, next) => {
let hasExecutionContext = true
try {
c.executionCtx
} catch {
hasExecutionContext = false
}
const sentry = new Toucan({ const sentry = new Toucan({
dsn: c.env.SENTRY_DSN || c.env.NEXT_PUBLIC_SENTRY_DSN, dsn: c.env.SENTRY_DSN || c.env.NEXT_PUBLIC_SENTRY_DSN,
allowedHeaders: ['user-agent'], allowedHeaders: ['user-agent'],
allowedSearchParams: /(.*)/, allowedSearchParams: /(.*)/,
request: c.req, request: c.req,
context: c.executionCtx, context: hasExecutionContext ? c.executionCtx : new MockContext(),
...options, ...options,
}) })
@ -35,3 +56,7 @@ export const sentry = (options?: Options, callback?: (sentry: Toucan) => void):
} }
} }
} }
export const getSentry = (c: Context) => {
return c.get('sentry')
}

9
src/replacer.ts 100644
View File

@ -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
})