-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathentry.server.tsx
142 lines (125 loc) · 4.26 KB
/
entry.server.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import crypto from 'node:crypto'
import { PassThrough } from 'node:stream'
import { styleText } from 'node:util'
import { contentSecurity } from '@nichtsam/helmet/content'
import { createReadableStreamFromReadable } from '@react-router/node'
import * as Sentry from '@sentry/react-router'
import { isbot } from 'isbot'
import { renderToPipeableStream } from 'react-dom/server'
import {
ServerRouter,
type LoaderFunctionArgs,
type ActionFunctionArgs,
type HandleDocumentRequestFunction,
} from 'react-router'
import { getEnv, init } from './utils/env.server.ts'
import { getInstanceInfo } from './utils/litefs.server.ts'
import { NonceProvider } from './utils/nonce-provider.ts'
import { makeTimings } from './utils/timing.server.ts'
export const streamTimeout = 5000
init()
global.ENV = getEnv()
const MODE = process.env.NODE_ENV ?? 'development'
type DocRequestArgs = Parameters<HandleDocumentRequestFunction>
export default async function handleRequest(...args: DocRequestArgs) {
const [request, responseStatusCode, responseHeaders, reactRouterContext] =
args
const { currentInstance, primaryInstance } = await getInstanceInfo()
responseHeaders.set('fly-region', process.env.FLY_REGION ?? 'unknown')
responseHeaders.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown')
responseHeaders.set('fly-primary-instance', primaryInstance)
responseHeaders.set('fly-instance', currentInstance)
if (process.env.NODE_ENV === 'production' && process.env.SENTRY_DSN) {
responseHeaders.append('Document-Policy', 'js-profiling')
}
const callbackName = isbot(request.headers.get('user-agent'))
? 'onAllReady'
: 'onShellReady'
const nonce = crypto.randomBytes(16).toString('hex')
return new Promise(async (resolve, reject) => {
let didError = false
// NOTE: this timing will only include things that are rendered in the shell
// and will not include suspended components and deferred loaders
const timings = makeTimings('render', 'renderToPipeableStream')
const { pipe, abort } = renderToPipeableStream(
<NonceProvider value={nonce}>
<ServerRouter
nonce={nonce}
context={reactRouterContext}
url={request.url}
/>
</NonceProvider>,
{
[callbackName]: () => {
const body = new PassThrough()
responseHeaders.set('Content-Type', 'text/html')
responseHeaders.append('Server-Timing', timings.toString())
contentSecurity(responseHeaders, {
crossOriginEmbedderPolicy: false,
contentSecurityPolicy: {
// NOTE: Remove reportOnly when you're ready to enforce this CSP
reportOnly: true,
directives: {
fetch: {
'connect-src': [
MODE === 'development' ? 'ws:' : undefined,
process.env.SENTRY_DSN ? '*.sentry.io' : undefined,
"'self'",
],
'font-src': ["'self'"],
'frame-src': ["'self'"],
'img-src': ["'self'", 'data:'],
'script-src': [
"'strict-dynamic'",
"'self'",
`'nonce-${nonce}'`,
],
'script-src-attr': [`'nonce-${nonce}'`],
},
},
},
})
resolve(
new Response(createReadableStreamFromReadable(body), {
headers: responseHeaders,
status: didError ? 500 : responseStatusCode,
}),
)
pipe(body)
},
onShellError: (err: unknown) => {
reject(err)
},
onError: () => {
didError = true
},
nonce,
},
)
setTimeout(abort, streamTimeout + 5000)
})
}
export async function handleDataRequest(response: Response) {
const { currentInstance, primaryInstance } = await getInstanceInfo()
response.headers.set('fly-region', process.env.FLY_REGION ?? 'unknown')
response.headers.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown')
response.headers.set('fly-primary-instance', primaryInstance)
response.headers.set('fly-instance', currentInstance)
return response
}
export function handleError(
error: unknown,
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
): void {
// Skip capturing if the request is aborted as Remix docs suggest
// Ref: https://remix.run/docs/en/main/file-conventions/entry.server#handleerror
if (request.signal.aborted) {
return
}
if (error instanceof Error) {
console.error(styleText('red', String(error.stack)))
} else {
console.error(error)
}
Sentry.captureException(error)
}