undo3d
Version:
Undo3D helps you build free-roaming 3D web apps where thousands of users can collaborate creatively in real time. Expect the first public beta mid-2019, and the first production release mid-2020.
124 lines (96 loc) • 4.38 kB
JavaScript
//// Mainly needed to serve new extensions like .mjs and .wasm with the proper
//// MIME types. Later on we might add some handy developer helpers...
//// INIT
//// Ensure we’re running in Node, and that this script is in ‘undo3d/support/’.
if ('object' !== typeof process || 'function' !== typeof require)
throw Error('Run in Node.js')
const containerDirs = __dirname.split('/').slice(-2).join('/')
if ('undo3d/support' !== containerDirs )
throw Error(`In ‘${containerDirs}/’ not ‘undo3d/support/’`)
//// Load Node’s 'http' and 'exec' modules, and set some constants.
const
app = require('http').createServer(server)
, { exec } = require('child_process')
, { readFileSync, existsSync } = require('fs')
, { join } = require('path')
, port = process.env.PORT || 3000 // Heroku sets the environment $PORT value
, dir = join(__dirname, '..')
//// Shut down if the server hits an error. This wouldn’t be good in production!
app.on('error', e => {
console.error('Server error: ' + e.message)
app.close()
})
//// Start the server and open a browser window.
app.listen( port, () => {
console.log(`Server is listening on port ${port}`)
exec(`open http://localhost:${port}/`) //@TODO test in Windows and Linux
})
//// SERVE
//// Serve the proper response.
function server (req, res) {
//// Any GET request is a static file.
if ('GET' === req.method)
return serveFile(req, res)
//// Anything else is an error.
return error(res, 9300, 405, 'Use GET')
}
//// Serve a file.
function serveFile (req, res) {
//// Serve a request for the homepage.
if ('/' === req.url || '/test' === req.url || '/test/' === req.url) {
res.writeHead(200, { 'Content-Type':'text/html' })
res.end( readFileSync( join(dir, req.url, 'index.html') ) )
return
}
//// Get the extension, MIME type and absolute path.
const
ext = (req.url.match(/\.([a-z]{2,4}2?)$/) || [])[1]
, mime = extToMimeType(ext)
, path = join(dir, req.url)
if (! ext) return error(res, 9101, 404, 'Invalid extension')
if (! mime) return error(res, 9102, 404, `Extension .${ext} not recognised`)
//// Check that the resource exists.
if (! existsSync(path) )
return error(res, 9100, 404, 'No such resource, ' + req.url)
//// Serve the request.
res.writeHead(200, { 'Content-Type':mime })
res.end( readFileSync(path) )
}
//// UTILITY
//// Sends a response after a request which failed.
function error (res, code, status, remarks, contentType='text/plain') {
const headers = { 'Content-Type': contentType }
remarks = remarks.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
res.writeHead(status, headers)
res.end(`{ "code":${code}, "error":"${remarks}", "status":${status} }\n`)
return false
}
//// Returns a given extension’s MIME type, or undefined if not recognised.
function extToMimeType (ext) {
return ({
js: 'application/javascript' // a standard JavaScript script
, mjs: 'application/javascript' // the new JavaScript module extension
, json: 'application/json' // JSON data, eg package.json
, mem: 'application/wasm' // asm.js memory, used by threecap.js.mem @TODO proper MIME type?
, wasm: 'application/wasm' // WebAssembly memory
, mp3: 'audio/mpeg' // MP3 audio
, ttf: 'font/ttf' // TrueType Font
, otf: 'font/otf' // OpenType Font
, woff: 'font/woff' // Web Open Font Format
, woff2:'font/woff2' // Web Open Font Format 2.0
, gif: 'image/gif' // GIF image
, jpg: 'image/jpeg' // JPEG image
, jpeg: 'image/jpeg' // JPEG image
, png: 'image/png' // PNG image
, svg: 'image/svg+xml' // SVG image
, ico: 'image/x-icon' // usually favicon.ico
, css: 'text/css' // CSS stylesheet
, htm: 'text/html' // HTML page
, html: 'text/html' // HTML page
, txt: 'text/plain' // plain text document
, mp4: 'video/mp4' // MP4 video
, webm: 'video/webm' // WEBM video
})[ext]
}