1 let BuildCache = require('BuildCache')
2 let JSONCache = require('JSONCache')
3 let assert = require('assert')
4 let cookie = require('cookie')
5 let emailjs = require('emailjs')
7 let js_template = require('js_template')
8 let less = require('less/lib/less-node')
9 var stream_buffers = require('stream-buffers')
10 let util = require('util')
11 let url = require('url')
12 let yauzl = require('yauzl')
13 let zetjs = require('zetjs')
15 let fs_mkdir = util.promisify(fs.mkdir)
16 let fs_readFile = util.promisify(fs.readFile)
17 let fs_stat = util.promisify(fs.stat)
18 let yauzl_open = util.promisify(yauzl.open)
20 let Site = function(server, root) {
21 if (!this instanceof Site)
22 throw Error('Site is a constructor')
25 this.socket_io_connect_listeners = []
28 Site.prototype.get_email = function(path) {
29 path = this.root + path
30 return this.server.build_cache_email.get(
33 console.log('getting', path, 'as email')
34 result.value = emailjs.this.server.connect(
35 JSON.parse(await fs_readFile(path))
41 // this is for read-only JSON files
42 // they will be reloaded from disk if modified
43 Site.prototype.get_json = function(path) {
44 path = this.root + path
45 return this.server.build_cache_json.get(
48 console.log('getting', path, 'as json')
49 result.value = JSON.parse(await fs_readFile(path))
54 Site.prototype.get_less = function(dirname, path) {
55 path = this.root + path
56 return this.server.build_cache_less.get(
59 console.log('getting', path, 'as less')
60 let render = await less.render(
61 await fs_readFile(path, {encoding: 'utf-8'}),
70 //javascriptEnabled: false,
74 paths: [this.root + dirname],
76 //reUsePluginManager: true,
78 rootpath: this.root//,
79 //strictImports: false,
84 result.deps.concat(render.imports)
85 result.value = Buffer.from(render.css)
90 Site.prototype.get_text = function(path) {
91 path = this.root + path
92 return this.server.build_cache_text.get(
95 console.log('getting', path, 'as text')
96 result.value = await fs_readFile(path, {encoding: 'utf-8'})
101 Site.prototype.get_zet = function(path) {
102 path = this.root + path
103 return this.server.build_cache_zet.get(
106 console.log('getting', path, 'as zet')
113 result.value = new zetjs.Index(path)
118 Site.prototype.get_zip = function(path) {
119 path = this.root + path
120 return this.server.build_cache_zip.get(
123 console.log('getting', path, 'as zip')
125 let zipfile = await yauzl_open(path, {autoClose: false})
128 (resolve, reject) => {
130 on('entry', entry => {entries.push(entry)}).
131 on('end', () => resolve())
134 for (let i = 0; i < entries.length; ++i) {
135 let read_stream = await new Promise(
136 (resolve, reject) => {
137 zipfile.openReadStream(
147 let write_stream = new stream_buffers.WritableStreamBuffer()
148 let data = new Promise(
149 (resolve, reject) => {
151 on('finish', () => {resolve(write_stream.getContents())}).
152 on('error', () => {reject()})
155 read_stream.pipe(write_stream)
156 let path = '/' + entries[i].fileName
158 console.log('entry path', path, 'size', data.length)
159 result.value[path] = data
161 await zipfile.close()
166 Site.prototype.ensure_dir = async function(path) {
168 await fs_mkdir(this.root + path)
171 if (err.code !== 'EEXIST') // should check error type
176 // this is for read/write JSON files
177 // they will not be reloaded from disk if modified
178 Site.prototype.open_json = async function(path, default_value) {
179 return /*await*/ this.server.json_cache.open(this.root + path, default_value)
181 Site.prototype.flush_json = async function(path) {
182 return /*await*/ this.server.json_cache.flush(this.root + path)
185 Site.prototype.serve_jst = async function(env, pathname) {
188 jst = await js_template(this.root, this.root, this.root + pathname)
191 if (err.code !== 'ENOENT')
196 let mime_type = await jst(env, out)
197 if (mime_type === undefined) {
198 // for directories the mime type must be returned, for files we
199 // can look it up from the pathname starting at current position
200 // (for files we're guaranteed to be on last pathname component)
201 let filetype = env.pathname.slice(env.pathname_pos)
203 Object.prototype.hasOwnProperty.call(this.server.mime_types, filetype)
205 mime_type = this.server.mime_types[filetype]
207 let data = Buffer.from(out.join(''))
209 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from jst`
211 this.server.serve(env.response, 200, mime_type, data)
215 Site.prototype.serve_less = async function(env, pathname) {
216 if (env.pathname.slice(env.pathname_pos) !== '.css')
221 data = await this.get_less(this.root, pathname)
224 if (err.code !== 'ENOENT')
229 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from less`
231 this.server.serve(env.response, 200, this.server.mime_types['.css'], data)
235 Site.prototype.serve_fs = async function(env, pathname) {
238 data = await fs_readFile(this.root + pathname)
241 if (err.code !== 'ENOENT')
246 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from fs`
248 let filetype = env.pathname.slice(env.pathname_pos)
249 this.server.serve(env.response, 200, this.server.mime_types[filetype], data)
254 Site.prototype.serve_zip = async function(env, pathname) {
257 zip = await this.get_zip(pathname)
260 if (err.code !== 'ENOENT')
264 if (!Object.prototype.hasOwnProperty.call(zip, env.pathname))
266 let data = zip[env.pathname]
268 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from zip`
270 let filetype = env.pathname.slice(env.pathname_pos)
271 this.server.serve(env.response, 200, this.server.mime_types[filetype], data)
275 Site.prototype.respond = async function(env) {
277 if (env.pathname_pos >= env.pathname.length) {
278 let pathname = env.pathname + '/index.html'
280 `${env.parsed_url.host} redirecting ${env.pathname} to ${pathname}`
282 this.server.redirect(
284 pathname + (env.parsed_url.search || '')
289 assert(env.pathname.charAt(env.pathname_pos) === '/')
290 let i = env.pathname_pos + 1
291 let j = env.pathname.indexOf('/', i)
293 j = env.pathname.length
294 let filename = env.pathname.slice(i, j)
296 if (filename.length === 0) {
297 if (j >= env.pathname.length) {
298 let pathname = env.pathname + 'index.html'
300 `${env.parsed_url.host} redirecting ${env.pathname} to ${pathname}`
302 this.server.redirect(
304 pathname + (env.parsed_url.search || '')
309 `${env.parsed_url.host} empty directory name in ${env.pathname}`
311 this.server.die(env.response)
317 filename.charAt(0) === '.' ||
318 filename.charAt(0) === '_'
321 `${env.parsed_url.host} bad component "${filename}" in ${env.pathname}`
323 this.server.die(env.response)
327 let k = filename.lastIndexOf('.')
330 let filetype = filename.slice(k)
333 filetype.length !== 0 &&
334 Object.prototype.hasOwnProperty.call(this.server.mime_types, filetype)
336 if (j < env.pathname.length) {
338 `${env.parsed_url.host} non-directory filetype "${filetype}" in ${env.pathname}`
340 this.server.die(env.response)
343 env.pathname_pos = i + k // advance to "." at start of filetype
348 let pathname = env.pathname.slice(0, env.pathname_pos)
349 if (await this.serve_jst(env, pathname + '.dir.jst'))
354 stats = await fs_stat(this.root + pathname)
357 if (err.code !== 'ENOENT')
360 `${env.parsed_url.host} directory not found: ${pathname}`
362 this.server.die(env.response)
365 if (!stats.isDirectory()) {
367 `${env.parsed_url.host} not directory: ${pathname}`
369 this.server.die(env.response)
375 !await this.serve_jst(env, env.pathname + '.jst') &&
376 !await this.serve_less(env, env.pathname + '.less') &&
377 !await this.serve_fs(env, env.pathname) &&
378 !await this.serve_zip(env, '/favicons.zip')
381 `${env.parsed_url.host} file not found ${env.pathname}`
383 this.server.die(env.response)
387 module.exports = Site