// file system to load / look at our available modules / edit them const fs = require('fs') // express serves files, http makes the connection const app = require('express')() const http = require('http').Server(app) // websocket, to share program representations with the client (and back) const WebSocket = require('ws') const Reps = require('./reps.js') const Programs = require('./programs.js') /* SERVER AND WS SETUP -------------------------------------------------- */ var program = null var sckt = null var verbose = false function startHttp() { // serving this handful of static files app.get('/', (req, res) => { console.log('client req /') res.sendFile(__dirname + '/client/index.html') }) app.get('/:file', (req, res) => { console.log('client req', req.params.file) res.sendFile(__dirname + '/client/' + req.params.file) }) app.get('/ui/:file', (req, res) => { console.log('client req ui', req.params.file) res.sendFile(__dirname + '/client/ui/' + req.params.file) }) app.get('/ui/libs/:file', (req, res) => { console.log('client req ui lib', req.params.file) res.sendFile(__dirname + '/client/ui/libs/' + req.params.file) }) // through this window http.listen(8080, () => { console.log('RNDMC is listening on localhost:8080') }) } function startWs() { // and listening for requests here const wss = new WebSocket.Server({ port: 8081 }) wss.on('connection', (ws) => { sckt = ws // say hello socketSend('console', 'hello client') // send current config as list of all modules console.log('socket open on 8081') ws.on('message', (evt) => { socketRecv(evt) }) }) } /* HOOKUP REF TO TL ------------------------------------------------------ */ function assignProgram(prgm) { program = prgm } /* RECV / SEND PORTALS --------------------------------------------------- */ function socketRecv(evt) { var recv = JSON.parse(evt) var type = recv.type var data = recv.data //console.log('RECV', recv) // bang thru switch (type) { case 'console': console.log('RECV CONSOLE:', data) break case 'get current program': uiRequestCurrentProgram() break case 'get module menu': uiRequestModuleMenu() break case 'get program menu': uiRequestProgramMenu() break case 'load program': uiRequestLoadProgram(data) break case 'save program': uiRequestSaveProgram(data) break case 'put module': uiRequestNewModule(data) break case 'remove module': uiRequestRemoveModule(data) break case 'put state change': uiRequestStateChange(data) break case 'put link change': uiRequestLinkChange(data) // id:output > id:input break case 'put ui change': uiRequestUiChange(data) break case 'put position change': uiRequestMdlPositionChange(data) break default: console.log('ERR server recv with non recognized type', recv) break } } function socketSend(type, data) { if (sckt != null && sckt.readyState === 1) { var msg = { type: type, data: data } //console.log('SEND', msg) sckt.send(JSON.stringify(msg)) } else { console.log('on socketSend, ws not ready') } } /* HEAP -> UI HANDLES --------------------------------------------------- */ // runs when browser first loads function uiRequestCurrentProgram() { // make reps from program and send the list console.log('SEND PROGRAMS TO UI') var prgRep = { description: { name: program.description.name, id: program.description.id, path: program.description.path }, modules: {} } if(program.description.view != null){ prgRep.description.view = program.description.view } for (mdlName in program.modules) { var mdlRep = Reps.makeFromModule(program.modules[mdlName]) prgRep.modules[mdlName] = mdlRep } socketSend('put program', prgRep) } // TODO: proper heirarchy, with both of these ... function uiRequestModuleMenu() { var availableSourceRep = {} var dir = fs.readdirSync('./modules') for (i in dir) { availableSourceRep[dir[i]] = {} var subdir = fs.readdirSync('./modules/' + dir[i]) for (j in subdir) { // find js files if (subdir[j].slice(-3) === '.js') { var obj = {} obj.path = './modules/' + dir[i] + '/' + subdir[j] availableSourceRep[dir[i]][subdir[j].slice(0, -3)] = obj } } } socketSend('put module menu', availableSourceRep) } function uiRequestProgramMenu() { var availableProgramRep = {} var paths = fs.readdirSync('./programs') for (i in paths) { if (paths[i].slice(-5) === '.json') { var name = paths[i].slice(0, -5) var path = './programs/' + paths[i] availableProgramRep[paths[i]] = { name: name, path: path } } } socketSend('put program menu', availableProgramRep) } /* UI -> HEAP HANDLES --------------------------------------------------- */ function uiRequestLoadProgram(data) { console.log('UI REQUEST TO OPEN NEW PROGRAM', data) // gonna tear it doooown, maybe just kick page afterwards to restart it? program = null program = Programs.open(data) socketSend('restart', '') } function uiRequestSaveProgram(data) { console.log('UI REQUEST TO SAVE PROGRAM', data) // is data a path? add .json ? if (data) { if (!data.includes('.json')) { data = data + '.json' } path = 'programs/' + data Programs.save(program, path) socketSend('console', ('saved program at' + path)) } } function uiRequestNewModule(data) { console.log('UI REQUEST ADD MODULE TO PROGRAM', data) Programs.loadModuleFromSource(program, data) // bit of a mess to pick out the last entered module var keys = Object.keys(program.modules) var latest = keys[keys.length - 1] if (program.modules[latest].description.isLink && data != './modules/hardware/atkseriallink.js') { // we just added hardware, so added a link, so we've added two // just burn it down socketSend('restart', '') } // TODO: questionable init - here and in loadProgram, should be better handled across board if (program.modules[latest].init != null) { program.modules[latest].init() } socketSend('put module', Reps.makeFromModule(program.modules[keys[keys.length - 1]])) } function uiRequestRemoveModule(data) { console.log('UI REQUEST TO REMOVE MODULE', data.id) Programs.removeModule(program, data.id) socketSend('restart', '') } function uiRequestStateChange(data) { console.log('UI REQUEST CHANGE STATE IN MODULE', data) var mdlState = program.modules[data.id].state if (mdlState[data.key] != null) { mdlState[data.key] = data.val mdlState.emitUIChange(data.key) } else { console.log("ERR no state key,", data.key, "found here", data) console.log("THE KEY", data.key) console.log("THE MDL", mdlState) console.log("THE STATE", mdlState[data.key]) } } function uiRequestLinkChange(data) { console.log('UI REQUEST ADD EVENT LINK', data) var fromId = data.from.id var outputName = data.from.output var toId = data.to.id var inputName = data.to.input var fromMdl = program.modules[fromId] var toMdl = program.modules[toId] // if it's already attached, we rm if (fromMdl.outputs[outputName].isLinked(toMdl.inputs[inputName])) { fromMdl.outputs[outputName].remove(toMdl.inputs[inputName]) } else { fromMdl.outputs[outputName].attach(toMdl.inputs[inputName]) } var nRep = Reps.makeFromModule(fromMdl) socketSend('put module change', nRep) } function uiRequestUiChange(data){ if(verbose) console.log('UI PUSH UI DATA DOWN', data) var mdlUiElem = program.modules[data.id].ui[data.key] mdlUiElem.onMessage(data.msg) } function uiRequestMdlPositionChange(data) { if(verbose) console.log('UI REQUEST ADD / CHANGE UI INFO TO MODULE', data) var mod = program.modules[data.description.id] mod.description.position = data.description.position } /* EXPORTS -------------------------------------------------------------- */ module.exports = { startHttp: startHttp, startWs: startWs, uiSocket: { send: socketSend }, assignProgram: assignProgram }