skynet/src/index.js

120 lines
3.3 KiB
JavaScript

const express = require("express")
const websocket = require("ws")
const cors = require("cors")
const level = require("level")
const expressWS = require("express-ws")
const cuid = require("cuid")
const db = level("./db.level", {
valueEncoding: "json"
})
const app = express()
const eWSS = expressWS(app)
app.use(cors())
app.use(express.static(__dirname))
const validateChannel = channel => { if (typeof channel !== "string" && typeof channel !== "number") throw new Error("Invalid type for channel!") }
const wildcardChannel = "*"
const messageLog = []
const broadcast = (wss, msg, sender) => {
const toSend = {
...msg,
type: "message",
ID: cuid(),
time: new Date().getTime()
}
wss.clients.forEach(socket => {
let send = socket.readyState === websocket.OPEN
if (socket.type === "client") { send = send && (socket.channels.includes(msg.channel) || socket.channels.includes(wildcardChannel)) && socket !== sender }
else if (socket.type === "peer") { send = send && true }
if (send) {
socket.send(JSON.stringify(toSend))
}
})
messageLog.unshift(toSend) // push to front for nice ordering
}
const clientCommands = {
open(message, sendBack, ctx) {
const channel = message.channel
const ws = ctx.ws
validateChannel(channel)
if (!ws.channels.includes(channel)) ws.channels.push(channel)
sendBack({ channels: ws.channels })
},
close(message, sendBack, ctx) {
const channel = message.channel
const ws = ctx.ws
validateChannel(channel)
// Remove channel from list if exists
const index = ws.channels.indexOf(channel)
if (index > -1) {
ws.channels.splice(index, 1)
sendBack({ channels: ws.channels })
} else {
throw new Error("Channel " + channel + " not open.")
}
},
log(message, sendBack) {
const start = message.start || 0
const end = message.end || 100
sendBack({ log: messageLog.slice(start, end) })
},
message(message, sendBack, { wss, ws }) {
broadcast(wss, message, ws)
}
}
const ctx = {
wss: eWSS.getWss(),
db
}
const websocketCommandProcessor = (ws, commands) => {
const send = x => ws.send(JSON.stringify(x))
ws.on("message", raw => {
try {
const message = JSON.parse(raw)
if (typeof message === "object" && message !== null && message !== undefined) {
const commandName = message.type
const command = commands[commandName]
if (!command) { throw new Error("No such command " + commandName) }
const sendBack = x => send({type: "result", for: commandName, ...x})
command(message, sendBack, { ws, ...ctx })
} else {
throw new Error("Message must be object")
}
} catch(e) {
console.error(e)
send({
type: "error",
error: e.toString()
})
return
}
})
}
app.ws("/connect", (ws, req) => {
ws.channels = []
ws.type = "client"
websocketCommandProcessor(ws, clientCommands)
})
app.ws("/peer", (ws, req) => {
})
app.listen(parseInt(process.env.PORT) || 4567)