-- Handles the bridge to javascript to do peer-to-peer connections log = require("log") rng = require("rng") util = require("util") net = {} initalized = false initalize = () -> am.eval_js(require("js_bridge")) initalized = true net.call = (method, args) -> if not initalized initalize! result = am.eval_js("window.PEER." .. method .. "(" .. am.to_json(args) .. ")") result net.pull_peers = () -> if not initalized initalize! messages = am.eval_js("window.PEER.peer_message_queue") am.eval_js("window.PEER.peer_message_queue = []") messages net.pull_connections = () -> if not initalized initalize! messages = am.eval_js("window.PEER.connection_message_queue") am.eval_js("window.PEER.connection_message_queue = []") messages callbacks = {} peers = {} connections = {} --Connections are always create js side, this is just it's lua representation class Connection @connections = {} @methods = util.reverse({"data","open","close","error"}) new: (source, dest) => @source = source @dest = dest @get: (source, dest) => key = table.concat({source,dest},",") if @connections[key] return @connections[key] @@connections[key] = Connection(source,dest) @@connections[key] on: (event, callback) => if not @@methods[event] error("Tried to set an unknown event (" .. event .. ") on a connection") newid = #callbacks + 1 callbacks[newid] = callback while am.eval_js('window.PEER.connections[["' .. @source .. '","' .. @dest .. '"].sort()] == null') print("Waiting for peer") coroutine.yield("Waiting for peer") --not backwards, "on" is always called on the incomming connection net.call("conn_on", {source: @dest, dest: @source, e: event, message: newid}) send: (msgname, msg) => net.validate(msgname, msg) net.call("send",{source: @source, dest: @dest, data: {msgname, msg}}) class Peer @methods = util.reverse({"open","connection","call","close","disconnected","error"}) new: (id) => -- We can't create peers with peerjs-generated random ids, -- their names are too hard to type. -- Instead use (Year, Month), and append 4 random digits -- to make a unique id (people can't join past midnight at the -- end of the month, oh well) -- Also needs to handle id-already-taken errors. id = id or @generate_id! while peers[id] id = @generate_id! @id = id peers[@id] = @ net.call("create",{name: @id}) log.info("Creating peer: " .. @id, {"net"}) generate_id: () => os.date("%Y%e") .. rng.randomstring("ab",1) --.. rng.numstring(4) replace_id: () => log.info("Regenerating id for peer: " .. @id, {"net"}) -- peers[@id] = nil TODO: uncomment, this breaks when running multiple peers from the same tab. net.call("delete_peer",{name: @id}) @id = @generate_id! peers[@id] = @ net.call("create", {name: @id}) on: (event, callback) => if not @@methods[event] error("Tried to set an unknown event (" .. event .. ") on a peer.") newid = #callbacks + 1 callbacks[newid] = callback net.call("on",{name: @id, message:newid, e: event}) connect: (id, options) => conn = net.call("connect", {source: @id, dest: id}) log.info("Got connection: " .. tostring(conn), {"net"}) Connection\get(conn[1],conn[2]) net.Peer = Peer -- A fake peer for testing fakepeers = {} fakeconnections = {} fakecallbacks = {} channel = require("channel") class FakePeer new: (id) => if id @id = id fakepeers[id] = @ on: (event, callback) => newid = #fakecallbacks + 1 fakecallbacks[newid] = callback connect: (id, options) => conn = channel.FaultyChannel({ avg_latency: 200 latency_std: 100 loss: 0.1 }) conn messages = {} formatcache = {} message_callbacks = {} net.register_message = (name, format) -> assert(type(format) == "table", "Format must be a table") format.required = format.required or {} format.optional = format.optional or {} if not (next(format.required) or next(format.optional)) log.warn("Message " .. name .. " registered with no fields.") for set in *({format.required, format.optional}) for field, type_ in pairs(set) if type(type_) == "string" key = string.format("%s\0%s\0%s",name,field,type_) if not formatcache[key] formatcache[key] = (any) -> assert(type(any) == type_, string.format("In message %q %q must be a %q, but was a %q", name, field, type_, type(any))) set[field] = formatcache[key] messages[name] = format message_callbacks[name] = {} net.validate = (name, message) -> log.info("Validating message:" .. tostring(message), {"net"}) assert(type(message) == "table", "Message must be a table") format = messages[name] required = {} for field, validate in pairs(format.required) required[field] = validate for field, value in pairs(message) if format.required[field] required[field](value) required[field] = nil if format.optional[field] format.optional[field](value) missing = next(required) if missing error("Missing required field: " .. missing) true net.listen = (name, callback, id) -> id = id or {} message_callbacks[name] = message_callbacks[name] or {} message_callbacks[name][id] = callback id net.defen = (name, id) -> message_callbacks[name][id] = nil -- net.route = (conn, name, data) -> -- if message_callbacks[name] -- for id, callback in pairs(message_callbacks[name]) -- ret = message_callbacks[name](conn, rewrite_events = { connection: (message) -> conn = Connection\get(message.data.data[2], message.data.data[1]) assert(conn, "Failed to build conn?") assert(conn.source and conn.dest) message.data.data = conn } net.pump = () -> msg_ = net.pull_peers! if #msg_ > 0 log.info("Processing " .. tostring(#msg_) .. " peer messages", {"net"}) for message in *msg_ log.info(tostring(message), {"net", message.data.peer}) if rewrite_events[message.data.e] log.info("Rewriting data due to " .. message.data.e .. " event", {"net", message.data.peer}) rewrite_events[message.data.e](message) log.info(tostring(message), {"net", message.data.peer}) if not message.data.peer and message.data.e == "open" log.info("Setting peerid for a peer that didn't have one " ..tostring(message), {"net"}) peer = peers[message.data.peer] assert(peer, "Failed to find peer:" .. message.data.peer .. " peers:" .. tostring(net.peers!)) callback = callbacks[message.message] assert(callback, "Failed to find callback " .. message.message .. " on peer " .. message.data.peer) callback(peer,message.data) msg_ = net.pull_connections! if #msg_ > 0 log.info("Processing " .. tostring(#msg_) .. " connection messages", {"net"}) for message in *msg_ log.info(tostring(message), {"net", message.data.peer}) connection = Connection\get(message.dest, message.peer) callback = callbacks[message.message] assert(callback, "Fakled to find callback " .. message.message .. " for message" .. tostring(message)) callback(connection, message.data) net.peers = () -> peers initalize! net