UNPKG

6.29 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 return register
119 }
120 invalidListeners()
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.protocol = 'https'
134 configuration.ssl.cert = await readSslFile(configuration, configuration.ssl.cert)
135 configuration.ssl.key = await readSslFile(configuration, configuration.ssl.key)
136 } else {
137 configuration.protocol = 'http'
138 }
139}
140
141async function checkMappings (configuration) {
142 const configurationInterface = new IConfiguration(configuration)
143 configuration[$configurationInterface] = configurationInterface
144 for await (const mapping of configuration.mappings) {
145 await check(configuration, mapping)
146 }
147}
148
149function setCwd (folderPath, configuration) {
150 if (configuration.handlers) {
151 Object.keys(configuration.handlers).forEach(prefix => {
152 var handler = configuration.handlers[prefix]
153 if (typeof handler === 'string' && handler.match(/^\.\.?\//)) {
154 configuration.handlers[prefix] = path.join(folderPath, handler)
155 }
156 })
157 }
158 if (configuration.mappings) {
159 configuration.mappings.forEach(mapping => {
160 if (!mapping.cwd) {
161 mapping.cwd = folderPath
162 }
163 })
164 }
165 if (configuration.ssl && !configuration.ssl.cwd) {
166 configuration.ssl.cwd = folderPath
167 }
168}
169
170function extend (filePath, configuration) {
171 const folderPath = path.dirname(filePath)
172 setCwd(folderPath, configuration)
173 if (configuration.extend) {
174 const basefilePath = path.join(folderPath, configuration.extend)
175 delete configuration.extend
176 return readFileAsync(basefilePath)
177 .then(buffer => JSON.parse(buffer.toString()))
178 .then(baseConfiguration => {
179 // Only merge mappings
180 const baseMappings = baseConfiguration.mappings
181 const mergedConfiguration = Object.assign(baseConfiguration, configuration)
182 if (baseMappings !== mergedConfiguration.mappings) {
183 mergedConfiguration.mappings = [...configuration.mappings, ...baseMappings]
184 }
185 return extend(basefilePath, mergedConfiguration)
186 })
187 }
188 return configuration
189}
190
191module.exports = {
192 async check (configuration) {
193 const checkedConfiguration = Object.assign({}, configuration)
194 applyDefaults(checkedConfiguration)
195 setHandlers(checkedConfiguration)
196 checkListeners(checkedConfiguration)
197 await checkProtocol(checkedConfiguration)
198 await checkMappings(checkedConfiguration)
199 checkedConfiguration[$configurationRequests] = {
200 lastId: 0,
201 hold: Promise.resolve(),
202 promises: []
203 }
204 return checkedConfiguration
205 },
206
207 async read (fileName) {
208 let filePath
209 if (path.isAbsolute(fileName)) {
210 filePath = fileName
211 } else {
212 filePath = path.join(process.cwd(), fileName)
213 }
214 return statAsync(filePath)
215 .then(() => readFileAsync(filePath).then(buffer => JSON.parse(buffer.toString())))
216 .then(configuration => extend(filePath, configuration))
217 }
218}