commit b1e76420bd7c3838d128407c9822b089c0296bc2 Author: Serene Han keroserene+git@gmail.com Date: Mon Mar 14 22:12:28 2016 -0700
per-proxypair Snowflake ID generation instead of just one for the Broker (#11) --- proxy/broker.coffee | 19 +++++++------------ proxy/proxypair.coffee | 8 ++++++-- proxy/snowflake.coffee | 30 ++++++++++++++++++++++++------ proxy/spec/broker.spec.coffee | 6 +++--- proxy/util.coffee | 4 ++++ 5 files changed, 44 insertions(+), 23 deletions(-)
diff --git a/proxy/broker.coffee b/proxy/broker.coffee index 78577e7..9eb378c 100644 --- a/proxy/broker.coffee +++ b/proxy/broker.coffee @@ -12,21 +12,16 @@ STATUS_GATEWAY_TIMEOUT = 504 MESSAGE_TIMEOUT = 'Timed out waiting for a client offer.' MESSAGE_UNEXPECTED = 'Unexpected status.'
-genSnowflakeID = -> - Math.random().toString(36).substring(2) - # Represents a broker running remotely. class Broker
clients: 0 - id: null
# When interacting with the Broker, snowflake must generate a unique session # ID so the Broker can keep track of which signalling channel it's speaking # to. constructor: (@url) -> @clients = 0 - @id = genSnowflakeID() # Ensure url has the right protocol + trailing slash. @url = 'http://' + @url if 0 == @url.indexOf('localhost', 0) @url = 'https://' + @url if 0 != @url.indexOf('http', 0) @@ -37,7 +32,7 @@ class Broker # waits for a response containing some client offer that the Broker chooses # for this proxy.. # TODO: Actually support multiple clients. - getClientOffer: => + getClientOffer: (id) => new Promise (fulfill, reject) => xhr = new XMLHttpRequest() xhr.onreadystatechange = -> @@ -53,12 +48,12 @@ class Broker snowflake.ui.setStatus ' failure. Please refresh.' reject MESSAGE_UNEXPECTED @_xhr = xhr # Used by spec to fake async Broker interaction - @_postRequest xhr, 'proxy', @id + @_postRequest id, xhr, 'proxy', id
# Assumes getClientOffer happened, and a WebRTC SDP answer has been generated. # Sends it back to the broker, which passes it to back to the original client. - sendAnswer: (answer) -> - dbg @id + ' - Sending answer back to broker...\n' + sendAnswer: (id, answer) -> + dbg id + ' - Sending answer back to broker...\n' dbg answer.sdp xhr = new XMLHttpRequest() xhr.onreadystatechange = -> @@ -73,14 +68,14 @@ class Broker dbg 'Broker ERROR: Unexpected ' + xhr.status + ' - ' + xhr.statusText snowflake.ui.setStatus ' failure. Please refresh.' - @_postRequest xhr, 'answer', JSON.stringify(answer) + @_postRequest id, xhr, 'answer', JSON.stringify(answer)
# urlSuffix for the broker is different depending on what action # is desired. - _postRequest: (xhr, urlSuffix, payload) => + _postRequest: (id, xhr, urlSuffix, payload) => try xhr.open 'POST', @url + urlSuffix - xhr.setRequestHeader('X-Session-ID', @id) + xhr.setRequestHeader('X-Session-ID', id) catch err ### An exception happens here when, for example, NoScript allows the domain diff --git a/proxy/proxypair.coffee b/proxy/proxypair.coffee index db53612..3a3fb01 100644 --- a/proxy/proxypair.coffee +++ b/proxy/proxypair.coffee @@ -3,6 +3,8 @@ Represents a single:
client <-- webrtc --> snowflake <-- websocket --> relay
+Every ProxyPair has a Snowflake ID, which is necessary when responding to the +Broker with an WebRTC answer. ###
class ProxyPair @@ -16,10 +18,12 @@ class ProxyPair running: true active: false # Whether serving a client. flush_timeout_id: null - onCleanup: null + onCleanup: null + id: null
constructor: (@clientAddr, @relayAddr, @rateLimit) -> @active = false + @id = genSnowflakeID()
# Prepare a WebRTC PeerConnection and await for an SDP offer. begin: -> @@ -38,7 +42,7 @@ class ProxyPair if COPY_PASTE_ENABLED Signalling.send @pc.localDescription else - snowflake.broker.sendAnswer @pc.localDescription + snowflake.broker.sendAnswer @id, @pc.localDescription # OnDataChannel triggered remotely from the client when connection succeeds. @pc.ondatachannel = (dc) => channel = dc.channel diff --git a/proxy/snowflake.coffee b/proxy/snowflake.coffee index e8c00aa..7cc01c9 100644 --- a/proxy/snowflake.coffee +++ b/proxy/snowflake.coffee @@ -84,6 +84,9 @@ class Snowflake for i in [1..CONNECTIONS_PER_CLIENT] @makeProxyPair @relayAddr return if COPY_PASTE_ENABLED + log 'ProxyPair Slots: ' + @proxyPairs.length + log 'Snowflake IDs: ' + (@proxyPairs.map (p) -> p.id).join ' | ' + timer = null # Temporary countdown. countdown = (msg, sec) => @@ -99,22 +102,37 @@ class Snowflake msg = 'polling for client... ' msg += '[retries: ' + @retries + ']' if @retries > 0 @ui.setStatus msg - recv = @broker.getClientOffer() - @retries++ + # Pick an available ProxyPair to poll with. + pair = @nextAvailableProxyPair() + if !pair + log 'No more available ProxyPair slots.' + countdown(err, DEFAULT_BROKER_POLL_INTERVAL / 1000) + return + log 'Polling for ' + pair.id + recv = @broker.getClientOffer pair.id recv.then (desc) => offer = JSON.parse desc dbg 'Received:\n\n' + offer.sdp + '\n' - @receiveOffer offer + console.log desc + sdp = new RTCSessionDescription offer + # @receiveOffer offer + if pair.receiveWebRTCOffer sdp + @sendAnswer pair if 'offer' == sdp.type , (err) -> countdown(err, DEFAULT_BROKER_POLL_INTERVAL / 1000) + @retries++
findClients()
+ # Returns the first ProxyPair that's available to connect. + nextAvailableProxyPair: -> + return @proxyPairs.find (pp, i, arr) -> return !pp.active + # Receive an SDP offer from some client assigned by the Broker, + # TODO: remove receiveOffer: (desc) => sdp = new RTCSessionDescription desc - # Use the first proxyPair that's available. - pair = @proxyPairs.find (pp, i, arr) -> return !pp.active + pair = @nextAvailableProxyPair() if pair.receiveWebRTCOffer sdp @sendAnswer pair if 'offer' == sdp.type
@@ -190,7 +208,7 @@ init = -> broker = new Broker brokerUrl snowflake = new Snowflake broker, ui
- dbg 'Contacting Broker at ' + broker.url + '\nSnowflake ID: ' + broker.id + dbg 'Contacting Broker at ' + broker.url log '== snowflake proxy ==' log 'Copy-Paste mode detected.' if COPY_PASTE_ENABLED
diff --git a/proxy/spec/broker.spec.coffee b/proxy/spec/broker.spec.coffee index db936c1..aef115f 100644 --- a/proxy/spec/broker.spec.coffee +++ b/proxy/spec/broker.spec.coffee @@ -76,9 +76,9 @@ describe 'Broker', -> it 'responds to the broker with answer', -> b = new Broker 'fake' spyOn(b, '_postRequest') - b.sendAnswer 123 + b.sendAnswer 'fake id', 123 expect(b._postRequest).toHaveBeenCalledWith( - jasmine.any(Object), 'answer', '123') + 'fake id', jasmine.any(Object), 'answer', '123')
it 'POST XMLHttpRequests to the broker', -> b = new Broker 'fake' @@ -86,7 +86,7 @@ describe 'Broker', -> spyOn(b._xhr, 'open') spyOn(b._xhr, 'setRequestHeader') spyOn(b._xhr, 'send') - b._postRequest b._xhr, 'test', 'data' + b._postRequest 0, b._xhr, 'test', 'data' expect(b._xhr.open).toHaveBeenCalled() expect(b._xhr.setRequestHeader).toHaveBeenCalled() expect(b._xhr.send).toHaveBeenCalled() diff --git a/proxy/util.coffee b/proxy/util.coffee index ea65e63..a5b247c 100644 --- a/proxy/util.coffee +++ b/proxy/util.coffee @@ -5,6 +5,10 @@ Contains helpers for parsing query strings and other utilities. ###
+genSnowflakeID = -> + Math.random().toString(36).substring(2) + + Query = ### Parse a URL query string or application/x-www-form-urlencoded body. The