UNPKG

6.49 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(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}
175
176function extend (filePath, configuration) {
177 const folderPath = path.dirname(filePath)
178 setCwd(folderPath, configuration)
179 if (configuration.extend) {
180 const basefilePath = path.join(folderPath, configuration.extend)
181 delete configuration.extend
182 return readFileAsync(basefilePath)
183 .then(buffer => JSON.parse(buffer.toString()))
184 .then(baseConfiguration => {
185 // Only merge mappings
186 const baseMappings = baseConfiguration.mappings
187 const mergedConfiguration = Object.assign(baseConfiguration, configuration)
188 if (baseMappings && baseMappings !== mergedConfiguration.mappings) {
189 mergedConfiguration.mappings = [...configuration.mappings, ...baseMappings]
190 }
191 return extend(basefilePath, mergedConfiguration)
192 })
193 }
194 return configuration
195}
196
197module.exports = {
198 async check (configuration) {
199 const checkedConfiguration = Object.assign({}, configuration)
200 applyDefaults(checkedConfiguration)
201 setHandlers(checkedConfiguration)
202 checkListeners(checkedConfiguration)
203 await checkProtocol(checkedConfiguration)
204 await checkMappings(checkedConfiguration)
205 checkedConfiguration[$configurationRequests] = {
206 lastId: 0,
207 holding: Promise.resolve(),
208 contexts: []
209 }
210 return checkedConfiguration
211 },
212
213 async read (fileName) {
214 let filePath
215 if (path.isAbsolute(fileName)) {
216 filePath = fileName
217 } else {
218 filePath = path.join(process.cwd(), fileName)
219 }
220 return statAsync(filePath)
221 .then(() => readFileAsync(filePath).then(buffer => JSON.parse(buffer.toString())))
222 .then(configuration => extend(filePath, configuration))
223 }
224}