UNPKG

6.71 kBJavaScriptView Raw
1'use strict'
2
3const fs = require('fs')
4const path = require('path')
5const util = require('util')
6const IConfiguration = require('./iconfiguration')
7const { check } = require('./mapping')
8const checkMethod = require('./checkMethod')
9const { parse } = require('./schema')
10const {
11 $configurationInterface,
12 $configurationRequests,
13 $handlerMethod,
14 $handlerSchema
15} = require('./symbols')
16
17const readFileAsync = util.promisify(fs.readFile)
18const statAsync = util.promisify(fs.stat)
19
20const defaultHandlers = {
21 custom: require('./handlers/custom'),
22 file: require('./handlers/file'),
23 status: require('./handlers/status'),
24 url: require('./handlers/url'),
25 use: require('./handlers/use')
26}
27
28const defaults = {
29 hostname: undefined,
30 port: 5000,
31 'max-redirect': 10,
32 listeners: [],
33 mappings: [{
34 match: /^\/proxy\/(https?)\/(.*)/,
35 url: '$1://$2',
36 'unsecure-cookies': true
37 }, {
38 match: '(.*)',
39 file: './$1'
40 }]
41}
42
43function applyDefaults (configuration) {
44 Object.keys(defaults).forEach(property => {
45 if (!Object.prototype.hasOwnProperty.call(configuration, property)) {
46 configuration[property] = defaults[property]
47 }
48 })
49}
50
51function getHandler (handlers, types, mapping) {
52 for (let index = 0; index < types.length; ++index) {
53 const type = types[index]
54 const redirect = mapping[type]
55 if (redirect !== undefined) {
56 return {
57 handler: handlers[type],
58 redirect,
59 type
60 }
61 }
62 }
63 return {}
64}
65
66function checkHandler (handler, type) {
67 if (handler.schema) {
68 handler[$handlerSchema] = parse(handler.schema)
69 delete handler.schema
70 }
71 if (handler.method) {
72 checkMethod(handler, $handlerMethod)
73 delete handler.method
74 }
75 if (typeof handler.redirect !== 'function') {
76 throw new Error('Invalid "' + type + '" handler: redirect is not a function')
77 }
78}
79
80function validateHandler (type) {
81 const handlers = this.handlers
82 let handler = handlers[type]
83 if (typeof handler === 'string') {
84 handler = require(handler)
85 handlers[type] = handler
86 }
87 checkHandler(handler, type)
88 Object.freeze(handler)
89}
90
91function setHandlers (configuration) {
92 if (configuration.handlers) {
93 // Default hanlders can't be overridden
94 configuration.handlers = Object.assign({}, configuration.handlers, defaultHandlers)
95 } else {
96 configuration.handlers = Object.assign({}, defaultHandlers)
97 }
98 Object.keys(configuration.handlers).forEach(validateHandler.bind(configuration))
99 configuration.handler = getHandler.bind(null, configuration.handlers, Object.keys(configuration.handlers))
100}
101
102function invalidListeners () {
103 throw new Error('Invalid listeners member, must be an array of functions')
104}
105
106function checkListeners (configuration) {
107 const listeners = configuration.listeners
108 if (!Array.isArray(listeners)) {
109 invalidListeners()
110 }
111 configuration.listeners = listeners.map(register => {
112 let registerType = typeof register
113 if (registerType === 'string') {
114 register = require(path.join(configuration.cwd || process.cwd(), register))
115 registerType = typeof register
116 }
117 if (registerType !== 'function') {
118 invalidListeners()
119 }
120 return register
121 })
122}
123
124async function readSslFile (configuration, filePath) {
125 if (path.isAbsolute(filePath)) {
126 return (await readFileAsync(filePath)).toString()
127 }
128 return (await readFileAsync(path.join(configuration.ssl.cwd, filePath))).toString()
129}
130
131async function checkProtocol (configuration) {
132 if (configuration.ssl) {
133 configuration.ssl.cert = await readSslFile(configuration, configuration.ssl.cert)
134 configuration.ssl.key = await readSslFile(configuration, configuration.ssl.key)
135 configuration.protocol = 'https'
136 } else {
137 configuration.protocol = 'http'
138 }
139 if (![true, false, undefined].includes(configuration.http2)) {
140 throw new Error('Invalid http2 setting')
141 }
142 if (!configuration.http2) {
143 configuration.http2 = false
144 }
145}
146
147async function checkMappings (configuration) {
148 const configurationInterface = new IConfiguration(configuration)
149 configuration[$configurationInterface] = configurationInterface
150 for await (const mapping of configuration.mappings) {
151 await check(configuration, mapping)
152 }
153}
154
155function setCwd (folderPath, configuration) {
156 if (configuration.handlers) {
157 Object.keys(configuration.handlers).forEach(prefix => {
158 const handler = configuration.handlers[prefix]
159 if (typeof handler === 'string' && handler.match(/^\.\.?\//)) {
160 configuration.handlers[prefix] = path.join(folderPath, handler)
161 }
162 })
163 }
164 if (configuration.mappings) {
165 configuration.mappings.forEach(mapping => {
166 if (!mapping.cwd) {
167 mapping.cwd = folderPath
168 }
169 })
170 }
171 if (configuration.ssl && !configuration.ssl.cwd) {
172 configuration.ssl.cwd = folderPath
173 }
174 configuration.cwd = folderPath
175}
176
177function extend (filePath, configuration) {
178 const folderPath = path.dirname(filePath)
179 setCwd(folderPath, configuration)
180 if (configuration.extend) {
181 const basefilePath = path.join(folderPath, configuration.extend)
182 delete configuration.extend
183 return readFileAsync(basefilePath)
184 .then(buffer => JSON.parse(buffer.toString()))
185 .then(baseConfiguration => {
186 // Only merge mappings
187 const baseMappings = baseConfiguration.mappings
188 const mergedConfiguration = Object.assign(baseConfiguration, configuration)
189 if (baseMappings && baseMappings !== mergedConfiguration.mappings) {
190 mergedConfiguration.mappings = [...configuration.mappings, ...baseMappings]
191 }
192 return extend(basefilePath, mergedConfiguration)
193 })
194 }
195 return configuration
196}
197
198module.exports = {
199 async check (configuration) {
200 if (typeof configuration !== 'object' || configuration === null) {
201 throw new Error('Configuration must be an object')
202 }
203 const checkedConfiguration = Object.assign({}, configuration)
204 applyDefaults(checkedConfiguration)
205 setHandlers(checkedConfiguration)
206 checkListeners(checkedConfiguration)
207 await checkProtocol(checkedConfiguration)
208 await checkMappings(checkedConfiguration)
209 checkedConfiguration[$configurationRequests] = {
210 lastId: 0,
211 holding: Promise.resolve(),
212 contexts: []
213 }
214 return checkedConfiguration
215 },
216
217 async read (fileName) {
218 let filePath
219 if (path.isAbsolute(fileName)) {
220 filePath = fileName
221 } else {
222 filePath = path.join(process.cwd(), fileName)
223 }
224 return statAsync(filePath)
225 .then(() => readFileAsync(filePath).then(buffer => JSON.parse(buffer.toString())))
226 .then(configuration => extend(filePath, configuration))
227 }
228}