1 | const path = require('path')
|
2 | const fs = require('fs')
|
3 | const url = require('url')
|
4 | const redirector = require('netlify-redirector')
|
5 | const chokidar = require('chokidar')
|
6 | const cookie = require('cookie')
|
7 | const redirectParser = require('netlify-redirect-parser')
|
8 | const { NETLIFYDEVWARN } = require('../utils/logo')
|
9 |
|
10 | async 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 |
|
21 | async 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 |
|
38 | function 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 |
|
47 | function 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 |
|
54 | function getCountry(req) {
|
55 | return 'us'
|
56 | }
|
57 |
|
58 | module.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 |
|
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 | }
|