diff --git a/proxy/snowflake.coffee b/proxy/snowflake.coffee index 9969ddf..4667c43 100644 --- a/proxy/snowflake.coffee +++ b/proxy/snowflake.coffee @@ -1,8 +1,56 @@ ### A Coffeescript WebRTC snowflake proxy Using Copy-paste signaling for now. + +Uses WebRTC from the client, and websocket to the server. + +Assume that the webrtc client plugin is always the offerer, in which case +this must always act as the answerer. ### +Query = + ### + Parse a URL query string or application/x-www-form-urlencoded body. The + return type is an object mapping string keys to string values. By design, + this function doesn't support multiple values for the same named parameter, + for example "a=1&a=2&a=3"; the first definition always wins. Returns null on + error. + + Always decodes from UTF-8, not any other encoding. + http://dev.w3.org/html5/spec/Overview.html#url-encoded-form-data + ### + parse: (qs) -> + result = {} + strings = [] + strings = qs.split '&' if qs + return result if 0 == strings.length + for i in [1..strings.length] + string = strings[i] + j = string.indexOf '=' + if j == -1 + name = string + value = '' + else + name = string.substr(0, j) + value = string.substr(j + 1) + name = decodeURIComponent(name.replace(/\+/g, ' ')) + value = decodeURIComponent(value.replace(/\+/g, ' ')) + result[name] = value if !(name in result) + result + +Params = + getBool: (query, param, defaultValue) -> + val = query[param] + return defaultValue if undefined == val + return true if "true" == val || "1" == val || "" == val + return false if "false" == val || "0" == val + return null + +# HEADLESS is true if we are running not in a browser with a DOM. +query = Query.parse(window.location.search.substr(1)) +HEADLESS = "undefined" == typeof(document) +DEBUG = Params.getBool(query, "debug", false) + # Janky state machine MODE = INIT: 0 @@ -10,6 +58,8 @@ MODE = CHAT: 2 currentMode = MODE.INIT + +# TODO: Different ICE servers. config = { iceServers: [ { urls: ["stun:stun.l.google.com:19302"] } @@ -21,22 +71,96 @@ $chatlog = null $send = null $input = null - window.PeerConnection = window.RTCPeerConnection || - window.mozRTCPeerConnection || window.webkitRTCPeerConnection + window.mozRTCPeerConnection || + window.webkitRTCPeerConnection window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate; -window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription +window.RTCSessionDescription = window.RTCSessionDescription || + window.mozRTCSessionDescription +# TODO: Implement +class Badge + +# Minimum viable snowflake for now - just 1 client. class Snowflake + rateLimit: 0 + proxyPairs: null + badge: null + $badge: null + MAX_NUM_CLIENTS = 1 + + constructor: -> + if HEADLESS + # No badge + else if DEBUG + @$badge = debug_div + else + @badge = new Badge() + @$badgem = @badge.elem + if (@$badge) + @$badge.setAttribute("id", "snowflake-badge") + + start: -> + log "Starting up Snowflake..." + + # Poll facilitator when this snowflake can support more clients. + proxyMain = -> + if @proxyPairs.length >= @MAX_NUM_CLIENTS * CONNECTIONS_PER_CLIENT + setTimeout(@proxyMain, @facilitator_poll_interval * 1000) + return + + params = [["r", "1"]] + params.push ["transport", "websocket"] + params.push ["transport", "webrtc"] + + beginProxy = (client, relay) -> + for i in [0..CONNECTIONS_PER_CLIENT] + makeProxyPair(client, relay) + + makeProxyPair = (client, relay) -> + pair = new ProxyPair(client, relay, @rate_limit); + @proxyPairs.push pair + pair.onCleanup = (event) => + # Delete from the list of active proxy pairs. + @proxyPairs.splice(@proxy_pairs.indexOf(pair), 1) + @badge.endProxy() if @badge + try + proxy_pair.connect() + catch err + log "ProxyPair: exception while connecting: " + safe_repr(err.message) + "." + return + @badge.beginProxy if @badge + + cease = -> + # @start = null + # @proxyMain = null + # @make_proxy_pair = function(client_addr, relay_addr) { }; + while @proxyPairs.length > 0 + @proxyPairs.pop().close() + + disable = -> + log "Disabling Snowflake." + @cease() + @badge.disable() if @badge + + die = -> + log "Snowflake died." + @cease() + @badge.die() if @badge + + +# TODO: Implement class ProxyPair + # ## -- DOM & Input Functionality -- ## # +snowflake = new Snowflake() welcome = -> - log "== snowflake JS proxy ==" + log "== snowflake browser proxy ==" log "Input offer from the snowflake client:" # Log to the message window. @@ -82,7 +206,7 @@ Signalling = log "Invalid JSON." return # Begin as answerer if peerconnection doesn't exist yet. - start false if !pc + snowflake.start false if !pc desc = recv['sdp'] ice = recv['candidate'] if !desc && ! ice