UNPKG

3.62 kBJavaScriptView Raw
1const path = require('path')
2const fs = require('fs')
3const url = require('url')
4const redirector = require('netlify-redirector')
5const chokidar = require('chokidar')
6const cookie = require('cookie')
7const redirectParser = require('netlify-redirect-parser')
8const { NETLIFYDEVWARN } = require('../utils/logo')
9
10async function parseFile(parser, name, filePath) {
11 const result = await parser(filePath)
12 if (result.errors.length) {
13 console.error(`${NETLIFYDEVWARN} Warnings while parsing ${name} file:`)
14 result.errors.forEach(err => {
15 console.error(` ${err.lineNum}: ${err.line} -- ${err.reason}`)
16 })
17 }
18 return result.success
19}
20
21async function parseRules(configFiles) {
22 const rules = []
23
24 for (const file of configFiles) {
25 if (!fs.existsSync(file)) continue
26
27 const fileName = file.split(path.sep).pop()
28 if (fileName.endsWith('_redirects')) {
29 rules.push(...(await parseFile(redirectParser.parseRedirectsFormat, fileName, file)))
30 } else {
31 rules.push(...(await parseFile(redirectParser.parseNetlifyConfig, fileName, file)))
32 }
33 }
34
35 return rules
36}
37
38function onChanges(files, cb) {
39 files.forEach(file => {
40 const watcher = chokidar.watch(file)
41 watcher.on('change', cb)
42 watcher.on('add', cb)
43 watcher.on('unlink', cb)
44 })
45}
46
47function getLanguage(req) {
48 if (req.headers['accept-language']) {
49 return req.headers['accept-language'].split(',')[0].slice(0, 2)
50 }
51 return 'en'
52}
53
54function getCountry(req) {
55 return 'us'
56}
57
58module.exports = function({ publicFolder, baseFolder, jwtSecret, jwtRole, configPath }) {
59 let matcher = null
60 const projectDir = path.resolve(baseFolder || process.cwd())
61 const configFiles = [
62 path.resolve(projectDir, '_redirects'),
63 path.resolve(publicFolder, '_redirects'),
64 path.resolve(configPath || path.resolve(publicFolder, 'netlify.yml')),
65 ]
66
67 onChanges(configFiles, () => {matcher = null})
68
69 const getMatcher = async () => {
70 if (matcher) {
71 return Promise.resolve(matcher)
72 }
73
74 const rules = (await parseRules(configFiles)).filter(
75 r => !(r.path === '/*' && r.to === '/index.html' && r.status === 200)
76 )
77
78 if (rules.length) {
79 return redirector
80 .parseJSON(JSON.stringify(rules), {
81 jwtSecret: jwtSecret || 'secret',
82 jwtRole: jwtRole || 'app_metadata.authorization.roles'
83 })
84 .then(m => (matcher = m))
85 }
86 return Promise.resolve({
87 match() {
88 return null
89 }
90 })
91 }
92
93 return function(req, res, next) {
94 getMatcher().then(matcher => {
95 const reqUrl = new url.URL(
96 req.url,
97 `${req.protocol || (req.headers.scheme && req.headers.scheme + ':') || 'http:'}//${req.hostname ||
98 req.headers['host']}`
99 )
100 const cookieValues = cookie.parse(req.headers.cookie || '')
101 const headers = Object.assign(
102 {},
103 {
104 'x-language': cookieValues.nf_lang || getLanguage(req),
105 'x-country': cookieValues.nf_country || getCountry(req)
106 },
107 req.headers
108 )
109
110 // Definition: https://github.com/netlify/libredirect/blob/e81bbeeff9f7c260a5fb74cad296ccc67a92325b/node/src/redirects.cpp#L28-L60
111 const matchReq = {
112 scheme: reqUrl.protocol,
113 host: reqUrl.hostname,
114 path: reqUrl.pathname,
115 query: reqUrl.search.slice(1),
116 headers,
117 cookieValues,
118 getHeader: name => headers[name.toLowerCase()] || '',
119 getCookie: key => cookieValues[key] || ''
120 }
121 const match = matcher.match(matchReq)
122 if (match) return next(match)
123
124 next()
125 })
126 }
127}