Skip to content

Commit fefb37d

Browse files
authored
fix(nitro): Support nested _platform properties in Nitro 2.11.7+ (#17596)
Since Nitro v2.11.7, the platform-specific properties like `cloudlfare` and `cf` are nested under `_platform` (nitrojs/nitro#3224). The `isEventType` function is updated to reflect this change. stumbled across this while working on this: #17588
1 parent 7f40621 commit fefb37d

File tree

4 files changed

+339
-50
lines changed

4 files changed

+339
-50
lines changed
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
// fixme: Can this be exported like this?
21
export { sentryCloudflareNitroPlugin } from './sentry-cloudflare.server';

packages/nuxt/src/runtime/plugins/sentry-cloudflare.server.ts

Lines changed: 18 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ExecutionContext, IncomingRequestCfProperties } from '@cloudflare/workers-types';
1+
import type { IncomingRequestCfProperties } from '@cloudflare/workers-types';
22
import type { CloudflareOptions } from '@sentry/cloudflare';
33
import { setAsyncLocalStorageAsyncContextStrategy, wrapRequestHandler } from '@sentry/cloudflare';
44
import { debug, getDefaultIsolationScope, getIsolationScope, getTraceData } from '@sentry/core';
@@ -8,50 +8,7 @@ import type { NuxtRenderHTMLContext } from 'nuxt/app';
88
import { sentryCaptureErrorHook } from '../hooks/captureErrorHook';
99
import { updateRouteBeforeResponse } from '../hooks/updateRouteBeforeResponse';
1010
import { addSentryTracingMetaTags } from '../utils';
11-
12-
interface CfEventType {
13-
protocol: string;
14-
host: string;
15-
method: string;
16-
headers: Record<string, string>;
17-
context: {
18-
cf: {
19-
httpProtocol?: string;
20-
country?: string;
21-
// ...other CF properties
22-
};
23-
cloudflare: {
24-
context: ExecutionContext;
25-
request?: Record<string, unknown>;
26-
env?: Record<string, unknown>;
27-
};
28-
};
29-
}
30-
31-
function isEventType(event: unknown): event is CfEventType {
32-
if (event === null || typeof event !== 'object') return false;
33-
34-
return (
35-
// basic properties
36-
'protocol' in event &&
37-
'host' in event &&
38-
typeof event.protocol === 'string' &&
39-
typeof event.host === 'string' &&
40-
// context property
41-
'context' in event &&
42-
typeof event.context === 'object' &&
43-
event.context !== null &&
44-
// context.cf properties
45-
'cf' in event.context &&
46-
typeof event.context.cf === 'object' &&
47-
event.context.cf !== null &&
48-
// context.cloudflare properties
49-
'cloudflare' in event.context &&
50-
typeof event.context.cloudflare === 'object' &&
51-
event.context.cloudflare !== null &&
52-
'context' in event.context.cloudflare
53-
);
54-
}
11+
import { getCfProperties, getCloudflareProperties, hasCfProperty, isEventType } from '../utils/event-type-check';
5512

5613
/**
5714
* Sentry Cloudflare Nitro plugin for when using the "cloudflare-pages" preset in Nuxt.
@@ -107,13 +64,13 @@ export const sentryCloudflareNitroPlugin =
10764
const request = new Request(url, {
10865
method: event.method,
10966
headers: event.headers,
110-
cf: event.context.cf,
67+
cf: getCfProperties(event),
11168
}) as Request<unknown, IncomingRequestCfProperties<unknown>>;
11269

11370
const requestHandlerOptions = {
11471
options: cloudflareOptions,
11572
request,
116-
context: event.context.cloudflare.context,
73+
context: getCloudflareProperties(event).context,
11774
};
11875

11976
return wrapRequestHandler(requestHandlerOptions, () => {
@@ -124,7 +81,7 @@ export const sentryCloudflareNitroPlugin =
12481
const traceData = getTraceData();
12582
if (traceData && Object.keys(traceData).length > 0) {
12683
// Storing trace data in the WeakMap using event.context.cf as key for later use in HTML meta-tags
127-
traceDataMap.set(event.context.cf, traceData);
84+
traceDataMap.set(getCfProperties(event), traceData);
12885
debug.log('Stored trace data for later use in HTML meta-tags: ', traceData);
12986
}
13087

@@ -144,7 +101,19 @@ export const sentryCloudflareNitroPlugin =
144101

145102
// @ts-expect-error - 'render:html' is a valid hook name in the Nuxt context
146103
nitroApp.hooks.hook('render:html', (html: NuxtRenderHTMLContext, { event }: { event: H3Event }) => {
147-
const storedTraceData = event?.context?.cf ? traceDataMap.get(event.context.cf) : undefined;
104+
let storedTraceData: ReturnType<typeof getTraceData> | undefined = undefined;
105+
106+
if (
107+
event?.context &&
108+
'_platform' in event.context &&
109+
event.context._platform &&
110+
hasCfProperty(event.context._platform)
111+
) {
112+
storedTraceData = traceDataMap.get(event.context._platform.cf);
113+
} else if (event?.context && hasCfProperty(event.context)) {
114+
// legacy support (before Nitro v2.11.7 (PR: https://github.com/nitrojs/nitro/pull/3224))
115+
storedTraceData = traceDataMap.get(event.context.cf);
116+
}
148117

149118
if (storedTraceData && Object.keys(storedTraceData).length > 0) {
150119
debug.log('Using stored trace data for HTML meta-tags: ', storedTraceData);
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { CfProperties, ExecutionContext } from '@cloudflare/workers-types';
2+
3+
interface EventBase {
4+
protocol: string;
5+
host: string;
6+
method: string;
7+
headers: Record<string, string>;
8+
}
9+
10+
interface MinimalCloudflareProps {
11+
context: ExecutionContext;
12+
request?: Record<string, unknown>;
13+
env?: Record<string, unknown>;
14+
}
15+
16+
interface CloudflareContext {
17+
cf: CfProperties;
18+
cloudflare: MinimalCloudflareProps;
19+
}
20+
21+
// Direct shape: cf and cloudflare are directly on context
22+
interface CfEventDirect extends EventBase {
23+
context: CloudflareContext;
24+
}
25+
26+
// Nested shape: cf and cloudflare are under _platform
27+
// Since Nitro v2.11.7 (PR: https://github.com/nitrojs/nitro/pull/3224)
28+
interface CfEventPlatform extends EventBase {
29+
context: {
30+
_platform: CloudflareContext;
31+
};
32+
}
33+
34+
export type CfEventType = CfEventDirect | CfEventPlatform;
35+
36+
function hasCloudflareProperty(context: unknown): boolean {
37+
return (
38+
context !== null &&
39+
typeof context === 'object' &&
40+
// context.cloudflare properties
41+
'cloudflare' in context &&
42+
typeof context.cloudflare === 'object' &&
43+
context.cloudflare !== null &&
44+
'context' in context.cloudflare
45+
);
46+
}
47+
48+
/**
49+
* Type guard to check if an event context object has cf properties
50+
*/
51+
export function hasCfProperty(context: unknown): context is { cf: CfProperties } {
52+
return (
53+
context !== null &&
54+
typeof context === 'object' &&
55+
// context.cf properties
56+
'cf' in context &&
57+
typeof context.cf === 'object' &&
58+
context.cf !== null
59+
);
60+
}
61+
62+
function hasCfAndCloudflare(context: unknown): context is CloudflareContext {
63+
return hasCfProperty(context) && hasCloudflareProperty(context);
64+
}
65+
66+
/**
67+
* Type guard to check if an event is a Cloudflare event (nested in _platform or direct)
68+
*/
69+
export function isEventType(event: unknown): event is CfEventType {
70+
if (event === null || typeof event !== 'object') return false;
71+
72+
return (
73+
// basic properties
74+
'protocol' in event &&
75+
'host' in event &&
76+
typeof event.protocol === 'string' &&
77+
typeof event.host === 'string' &&
78+
// context property
79+
'context' in event &&
80+
typeof event.context === 'object' &&
81+
event.context !== null &&
82+
// context.cf properties
83+
(hasCfAndCloudflare(event.context) || ('_platform' in event.context && hasCfAndCloudflare(event.context._platform)))
84+
);
85+
}
86+
87+
/**
88+
* Extracts cf properties from a Cloudflare event
89+
*/
90+
export function getCfProperties(event: CfEventType): CfProperties {
91+
if ('cf' in event.context) {
92+
return event.context.cf;
93+
}
94+
return event.context._platform.cf;
95+
}
96+
97+
/**
98+
* Extracts cloudflare properties from a Cloudflare event
99+
*/
100+
export function getCloudflareProperties(event: CfEventType): MinimalCloudflareProps {
101+
if ('cloudflare' in event.context) {
102+
return event.context.cloudflare;
103+
}
104+
return event.context._platform.cloudflare;
105+
}

0 commit comments

Comments
 (0)