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')
197 let data = Buffer.from(out.join(''))
199 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from jst`
201 this.server.serve(env.response, 200, env.mime_type, data)
205 Site.prototype.serve_less = async function(env, pathname) {
206 if (env.pathname.slice(env.pathname_pos) !== '.css')
211 data = await this.get_less(this.root, pathname)
214 if (err.code !== 'ENOENT')
219 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from less`
221 this.server.serve(env.response, 200, env.mime_type, data)
225 Site.prototype.serve_fs = async function(env, pathname) {
228 data = await fs_readFile(this.root + pathname)
231 if (err.code !== 'ENOENT')
236 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from fs`
238 this.server.serve(env.response, 200, env.mime_type, data)
243 Site.prototype.serve_zip = async function(env, pathname) {
246 zip = await this.get_zip(pathname)
249 if (err.code !== 'ENOENT')
253 if (!Object.prototype.hasOwnProperty.call(zip, env.pathname))
255 let data = zip[env.pathname]
257 `${env.parsed_url.host} serving ${env.pathname} length ${data.length} from zip`
259 this.server.serve(env.response, 200, env.mime_type, data)
263 Site.prototype.respond = async function(env) {
265 if (env.pathname_pos >= env.pathname.length) {
266 let pathname = env.pathname + '/index.html'
268 `${env.parsed_url.host} redirecting ${env.pathname} to ${pathname}`
270 this.server.redirect(
272 pathname + (env.parsed_url.search || '')
277 assert(env.pathname.charAt(env.pathname_pos) === '/')
278 let i = env.pathname_pos + 1
279 let j = env.pathname.indexOf('/', i)
281 j = env.pathname.length
282 let filename = env.pathname.slice(i, j)
284 if (filename.length === 0) {
285 if (j >= env.pathname.length) {
286 let pathname = env.pathname + 'index.html'
288 `${env.parsed_url.host} redirecting ${env.pathname} to ${pathname}`
290 this.server.redirect(
292 pathname + (env.parsed_url.search || '')
297 `${env.parsed_url.host} empty directory name in ${env.pathname}`
299 this.server.die(env.response)
305 filename.charAt(0) === '.' ||
306 filename.charAt(0) === '_'
309 `${env.parsed_url.host} bad component "${filename}" in ${env.pathname}`
311 this.server.die(env.response)
315 let k = filename.lastIndexOf('.')
318 let filetype = filename.slice(k)
321 filetype.length !== 0 &&
322 Object.prototype.hasOwnProperty.call(this.server.mime_types, filetype)
324 if (j < env.pathname.length) {
326 `${env.parsed_url.host} non-directory filetype "${filetype}" in ${env.pathname}`
328 this.server.die(env.response)
331 env.mime_type = this.server.mime_types[filetype]
332 env.pathname_pos = i + k // advance to "." at start of filetype
337 let pathname = env.pathname.slice(0, env.pathname_pos)
338 if (await this.serve_jst(env, pathname + '.dir.jst'))
343 stats = await fs_stat(this.root + pathname)
346 if (err.code !== 'ENOENT')
349 `${env.parsed_url.host} directory not found: ${pathname}`
351 this.server.die(env.response)
354 if (!stats.isDirectory()) {
356 `${env.parsed_url.host} not directory: ${pathname}`
358 this.server.die(env.response)
364 !await this.serve_jst(env, env.pathname + '.jst') &&
365 !await this.serve_less(env, env.pathname + '.less') &&
366 !await this.serve_fs(env, env.pathname) &&
367 !await this.serve_zip(env, '/favicons.zip')
370 `${env.parsed_url.host} file not found ${env.pathname}`
372 this.server.die(env.response)
376 module.exports = Site