-
Notifications
You must be signed in to change notification settings - Fork 390
/
Copy pathrules-proxy.ts
126 lines (113 loc) · 3.86 KB
/
rules-proxy.ts
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
import path from 'path'
import chokidar, { type FSWatcher } from 'chokidar'
import cookie from 'cookie'
import redirector from 'netlify-redirector'
import type { Match, RedirectMatcher } from 'netlify-redirector'
import pFilter from 'p-filter'
import { fileExistsAsync } from '../lib/fs.js'
import { NETLIFYDEVLOG, type NormalizedCachedConfigConfig } from './command-helpers.js'
import { parseRedirects } from './redirects.js'
import type { Request, Rewriter } from './types.js'
const watchers: FSWatcher[] = []
export const onChanges = function (files: string[], listener: () => unknown): void {
files.forEach((file) => {
const watcher = chokidar.watch(file)
watcher.on('change', listener)
watcher.on('unlink', listener)
watchers.push(watcher)
})
}
export const getWatchers = function (): FSWatcher[] {
return watchers
}
export const getLanguage = function (headers: Record<string, string | string[] | undefined>) {
if (headers['accept-language']) {
return (
Array.isArray(headers['accept-language']) ? headers['accept-language'].join(', ') : headers['accept-language']
)
.split(',')[0]
.slice(0, 2)
}
return 'en'
}
export const createRewriter = async function ({
config,
configPath,
distDir,
geoCountry,
jwtRoleClaim,
jwtSecret,
projectDir,
}: {
config: NormalizedCachedConfigConfig
configPath?: string | undefined
distDir?: string | undefined
geoCountry?: string | undefined
jwtRoleClaim: string
jwtSecret: string
projectDir: string
}): Promise<Rewriter> {
let matcher: RedirectMatcher | null = null
const redirectsFiles = [
...new Set([path.resolve(distDir ?? '', '_redirects'), path.resolve(projectDir, '_redirects')]),
]
let redirects = await parseRedirects({ config, redirectsFiles, configPath })
const watchedRedirectFiles = configPath === undefined ? redirectsFiles : [...redirectsFiles, configPath]
onChanges(watchedRedirectFiles, async (): Promise<void> => {
const existingRedirectsFiles = await pFilter(watchedRedirectFiles, fileExistsAsync)
console.log(
`${NETLIFYDEVLOG} Reloading redirect rules from`,
existingRedirectsFiles.map((redirectFile) => path.relative(projectDir, redirectFile)),
)
redirects = await parseRedirects({ config, redirectsFiles, configPath })
matcher = null
})
const getMatcher = async (): Promise<RedirectMatcher> => {
if (matcher) return matcher
if (redirects.length !== 0) {
return (matcher = await redirector.parseJSON(JSON.stringify(redirects), {
jwtSecret,
jwtRoleClaim,
}))
}
return {
match() {
return null
},
}
}
return async function rewriter(req: Request): Promise<Match | null> {
const matcherFunc = await getMatcher()
const reqUrl = new URL(
req.url ?? '',
`${req.protocol || (req.headers.scheme && `${req.headers.scheme}:`) || 'http:'}//${
req.hostname || req.headers.host
}`,
)
const cookieValues = cookie.parse(req.headers.cookie || '')
const headers: Record<string, string | string[]> = {
'x-language': cookieValues.nf_lang || getLanguage(req.headers),
'x-country': cookieValues.nf_country || geoCountry || 'us',
...req.headers,
}
// Definition: https://github.com/netlify/libredirect/blob/e81bbeeff9f7c260a5fb74cad296ccc67a92325b/node/src/redirects.cpp#L28-L60
const matchReq = {
scheme: reqUrl.protocol.replace(/:.*$/, ''),
host: reqUrl.hostname,
path: decodeURIComponent(reqUrl.pathname),
query: reqUrl.search.slice(1),
headers,
cookieValues,
getHeader: (name: string) => {
const val = headers[name.toLowerCase()]
if (Array.isArray(val)) {
return val[0]
}
return val || ''
},
getCookie: (key: string) => cookieValues[key] || '',
}
const match = matcherFunc.match(matchReq)
return match
}
}