-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.mjs
159 lines (130 loc) · 4.22 KB
/
server.mjs
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import * as fs from 'node:fs'
import * as path from 'node:path'
import * as url from 'node:url'
import { createRequestHandler } from '@remix-run/express'
import { broadcastDevReady, installGlobals } from '@remix-run/node'
import compression from 'compression'
import cookieParser from 'cookie-parser'
import express from 'express'
import morgan from 'morgan'
import PocketBase, { BaseAuthStore } from 'pocketbase'
import sourceMapSupport from 'source-map-support'
import invariant from 'tiny-invariant'
class CustomAuthStore extends BaseAuthStore {
get user() {
const model = this.model
if (!model || !this.isValid) {
return null
}
return {
...model,
avatar: model?.avatar
? `${process.env.POCKETBASE_URL}/api/files/${model.collectionId}/${model.id}/${model.avatar}`
: undefined,
}
}
}
sourceMapSupport.install({
retrieveSourceMap: function (source) {
// get source file without the `file://` prefix or `?t=...` suffix
const match = source.match(/^file:\/\/(.*)\?t=[.\d]+$/)
if (match) {
return {
url: source,
map: fs.readFileSync(`${match[1]}.map`, 'utf8'),
}
}
return null
},
})
installGlobals()
/** @typedef {import('@remix-run/node').ServerBuild} ServerBuild */
const BUILD_PATH = path.resolve('build/index.js')
const VERSION_PATH = path.resolve('build/version.txt')
const initialBuild = await reimportServer()
async function getLoadContext(req) {
invariant(process.env.POCKETBASE_URL, 'POCKETBASE_URL is not set')
const pb = new PocketBase(process.env.POCKETBASE_URL, new CustomAuthStore())
const parsedCookie = JSON.parse(req.cookies['pb_auth'] || '{}')
pb.authStore.save(parsedCookie.token || '', parsedCookie.model || null)
try {
pb.authStore.isValid && (await pb.collection('users').authRefresh())
} catch (_) {
pb.authStore.clear()
}
return {
pb,
user: pb.authStore.user,
}
}
const remixHandler =
process.env.NODE_ENV === 'development'
? await createDevRequestHandler(initialBuild)
: createRequestHandler({
build: initialBuild,
getLoadContext,
mode: initialBuild.mode,
})
const app = express()
app.use(compression())
app.use(cookieParser())
// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header
app.disable('x-powered-by')
// Remix fingerprints its assets so we can cache forever.
app.use(
'/build',
express.static('public/build', { immutable: true, maxAge: '1y' }),
)
// Everything else (like favicon.ico) is cached for an hour. You may want to be
// more aggressive with this caching.
app.use(express.static('public', { maxAge: '1h' }))
app.use(morgan('tiny'))
app.all('*', remixHandler)
const host = process.env.APP_HOST || 'localhost'
const port = process.env.APP_PORT || 3000
app.listen(port, host, async () => {
console.log(`Express server listening on ${host}:${port}`)
if (process.env.NODE_ENV === 'development') {
broadcastDevReady(initialBuild)
}
})
/**
* @returns {Promise<ServerBuild>}
*/
async function reimportServer() {
const stat = fs.statSync(BUILD_PATH)
// convert build path to URL for Windows compatibility with dynamic `import`
const BUILD_URL = url.pathToFileURL(BUILD_PATH).href
// use a timestamp query parameter to bust the import cache
return import(BUILD_URL + '?t=' + stat.mtimeMs)
}
/**
* @param {ServerBuild} initialBuild
* @returns {Promise<import('@remix-run/express').RequestHandler>}
*/
async function createDevRequestHandler(initialBuild) {
let build = initialBuild
async function handleServerUpdate() {
// 1. re-import the server build
build = await reimportServer()
// 2. tell Remix that this app server is now up-to-date and ready
broadcastDevReady(build)
}
const chokidar = await import('chokidar')
chokidar
.watch(VERSION_PATH, { ignoreInitial: true })
.on('add', handleServerUpdate)
.on('change', handleServerUpdate)
// wrap request handler to make sure its recreated with the latest build for every request
return async (req, res, next) => {
try {
return createRequestHandler({
build,
getLoadContext,
mode: 'development',
})(req, res, next)
} catch (error) {
next(error)
}
}
}