Skip to content

Commit 773fea2

Browse files
committed
fix(hono): Allow passing env and fix type issues
1 parent ac6b554 commit 773fea2

4 files changed

Lines changed: 64 additions & 47 deletions

File tree

packages/hono/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ compatibility_flags = ["nodejs_compat"]
4848
Initialize the Sentry Hono middleware as early as possible in your app:
4949

5050
```typescript
51+
import { Hono } from 'hono';
5152
import { sentry } from '@sentry/hono/cloudflare';
5253

5354
const app = new Hono();
@@ -64,3 +65,20 @@ app.use(
6465

6566
export default app;
6667
```
68+
69+
#### Access `env` from Cloudflare Worker bindings
70+
71+
Pass the options as a callback instead of a plain options object. The function receives the Cloudflare Worker `env` as defined in the Worker's `Bindings`:
72+
73+
```typescript
74+
import { Hono } from 'hono';
75+
import { sentry } from '@sentry/hono/cloudflare';
76+
77+
type Bindings = { SENTRY_DSN: string };
78+
79+
const app = new Hono<{ Bindings: Bindings }>();
80+
81+
app.use(sentry(app, env => ({ dsn: env.SENTRY_DSN })));
82+
83+
export default app;
84+
```

packages/hono/src/cloudflare/middleware.ts

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,47 @@ import {
77
type Integration,
88
type Options,
99
} from '@sentry/core';
10-
import type { Context, Hono, MiddlewareHandler } from 'hono';
10+
import type { Env, Hono, MiddlewareHandler } from 'hono';
1111
import { requestHandler, responseHandler } from '../shared/middlewareHandlers';
1212
import { patchAppUse } from '../shared/patchAppUse';
1313

14-
export interface HonoOptions extends Options<BaseTransportOptions> {
15-
context?: Context;
16-
}
14+
export interface HonoOptions extends Options<BaseTransportOptions> {}
1715

1816
const filterHonoIntegration = (integration: Integration): boolean => integration.name !== 'Hono';
1917

20-
export const sentry = (app: Hono, options: HonoOptions | undefined = {}): MiddlewareHandler => {
21-
const isDebug = options.debug;
22-
23-
isDebug && debug.log('Initialized Sentry Hono middleware (Cloudflare)');
24-
25-
applySdkMetadata(options, 'hono');
26-
27-
const { integrations: userIntegrations } = options;
18+
/**
19+
* Sentry middleware for Hono on Cloudflare Workers.
20+
*/
21+
export function sentry<E extends Env>(
22+
app: Hono<E>,
23+
options: HonoOptions | ((env: E['Bindings']) => HonoOptions),
24+
): MiddlewareHandler {
2825
withSentry(
29-
() => ({
30-
...options,
31-
// Always filter out the Hono integration from defaults and user integrations.
32-
// The Hono integration is already set up by withSentry, so adding it again would cause capturing too early (in Cloudflare SDK) and non-parametrized URLs.
33-
integrations: Array.isArray(userIntegrations)
34-
? defaults =>
35-
getIntegrationsToSetup({
36-
defaultIntegrations: defaults.filter(filterHonoIntegration),
37-
integrations: userIntegrations.filter(filterHonoIntegration),
38-
})
39-
: typeof userIntegrations === 'function'
40-
? defaults => userIntegrations(defaults).filter(filterHonoIntegration)
41-
: defaults => defaults.filter(filterHonoIntegration),
42-
}),
43-
app,
26+
env => {
27+
const honoOptions = typeof options === 'function' ? options(env as E['Bindings']) : options;
28+
29+
applySdkMetadata(honoOptions, 'hono', ['hono', 'cloudflare']);
30+
31+
honoOptions.debug && debug.log('Initialized Sentry Hono middleware (Cloudflare)');
32+
33+
const { integrations: userIntegrations } = honoOptions;
34+
return {
35+
...honoOptions,
36+
// Always filter out the Hono integration from defaults and user integrations.
37+
// The Hono integration is already set up by withSentry, so adding it again would cause capturing too early (in Cloudflare SDK) and non-parametrized URLs.
38+
integrations: Array.isArray(userIntegrations)
39+
? defaults =>
40+
getIntegrationsToSetup({
41+
defaultIntegrations: defaults.filter(filterHonoIntegration),
42+
integrations: userIntegrations.filter(filterHonoIntegration),
43+
})
44+
: typeof userIntegrations === 'function'
45+
? defaults => userIntegrations(defaults).filter(filterHonoIntegration)
46+
: defaults => defaults.filter(filterHonoIntegration),
47+
};
48+
},
49+
// Cast needed because Hono<E> exposes a narrower fetch signature than ExportedHandler<unknown>
50+
app as unknown as ExportedHandler<unknown>,
4451
);
4552

4653
patchAppUse(app);
@@ -52,4 +59,4 @@ export const sentry = (app: Hono, options: HonoOptions | undefined = {}): Middle
5259

5360
responseHandler(context);
5461
};
55-
};
62+
}

packages/hono/src/shared/patchAppUse.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import {
66
SPAN_STATUS_OK,
77
startInactiveSpan,
88
} from '@sentry/core';
9-
import type { Hono, MiddlewareHandler } from 'hono';
9+
import type { Env, Hono, MiddlewareHandler } from 'hono';
1010

1111
const MIDDLEWARE_ORIGIN = 'auto.middleware.hono';
1212

1313
/**
1414
* Patches `app.use` so that every middleware registered through it is automatically
1515
* wrapped in a Sentry span. Supports both forms: `app.use(...handlers)` and `app.use(path, ...handlers)`.
1616
*/
17-
export function patchAppUse(app: Hono): void {
17+
export function patchAppUse<E extends Env>(app: Hono<E>): void {
1818
app.use = new Proxy(app.use, {
1919
apply(target: typeof app.use, thisArg: typeof app, args: Parameters<typeof app.use>): ReturnType<typeof app.use> {
2020
const [first, ...rest] = args as [unknown, ...MiddlewareHandler[]];

packages/hono/test/cloudflare/middleware.test.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,19 @@ describe('Hono Cloudflare Middleware', () => {
2525
});
2626

2727
describe('sentry middleware', () => {
28-
it('calls applySdkMetadata with "hono"', () => {
28+
it('calls applySdkMetadata with "hono" when the options callback is invoked', () => {
2929
const app = new Hono();
3030
const options = {
3131
dsn: 'https://public@dsn.ingest.sentry.io/1337',
3232
};
3333

3434
sentry(app, options);
3535

36+
const optionsCallback = withSentryMock.mock.calls[0]?.[0];
37+
optionsCallback();
38+
3639
expect(applySdkMetadataMock).toHaveBeenCalledTimes(1);
37-
expect(applySdkMetadataMock).toHaveBeenCalledWith(options, 'hono');
40+
expect(applySdkMetadataMock).toHaveBeenCalledWith(options, 'hono', ['hono', 'cloudflare']);
3841
});
3942

4043
it('calls withSentry with modified options', () => {
@@ -63,24 +66,13 @@ describe('Hono Cloudflare Middleware', () => {
6366
name: 'npm:@sentry/hono',
6467
version: SDK_VERSION,
6568
},
69+
{
70+
name: 'npm:@sentry/cloudflare',
71+
version: SDK_VERSION,
72+
},
6673
]);
6774
});
6875

69-
it('calls applySdkMetadata before withSentry', () => {
70-
const app = new Hono();
71-
const options = {
72-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
73-
};
74-
75-
sentry(app, options);
76-
77-
// Verify applySdkMetadata was called before withSentry
78-
const applySdkMetadataCallOrder = applySdkMetadataMock.mock.invocationCallOrder[0];
79-
const withSentryCallOrder = withSentryMock.mock.invocationCallOrder[0];
80-
81-
expect(applySdkMetadataCallOrder).toBeLessThan(withSentryCallOrder as number);
82-
});
83-
8476
it('preserves all user options', () => {
8577
const app = new Hono();
8678
const options = {

0 commit comments

Comments
 (0)