successful bootstrap through browser proxy! (closes #5)

This commit is contained in:
Serene Han 2016-01-12 11:14:28 -08:00
parent a8477ee402
commit 9c4fc24719
2 changed files with 64 additions and 41 deletions

File diff suppressed because one or more lines are too long

View file

@ -6,11 +6,9 @@ Uses WebRTC from the client, and websocket to the server.
Assume that the webrtc client plugin is always the offerer, in which case Assume that the webrtc client plugin is always the offerer, in which case
this must always act as the answerer. this must always act as the answerer.
TODO(keroserene): Complete the websocket + webrtc ProxyPair
### ###
DEFAULT_WEBSOCKET = '92.81.135.242:9901' DEFAULT_WEBSOCKET = '192.81.135.242:9901'
if 'undefined' != typeof module && 'undefined' != typeof module.exports if 'undefined' != typeof module && 'undefined' != typeof module.exports
console.log 'not in browser.' console.log 'not in browser.'
@ -99,19 +97,6 @@ Params =
return null return null
{ host: host, port: port } { host: host, port: port }
# repr = (x) ->
# return 'null' if null == x
# return 'undefined' if 'undefined' == typeof x
# if 'object' == typeof x
# elems = []
# for k in x
# elems.push(maybe_quote(k) + ': ' + repr(x[k]));
# return '{ ' + elems.join(', ') + ' }';
# } else if (typeof x === 'string') {
# return quote(x);
# } else {
# return x.toString();
# safe_repr = (s) -> SAFE_LOGGING ? '[scrubbed]' : repr(s)
safe_repr = (s) -> SAFE_LOGGING ? '[scrubbed]' : JSON.stringify(s) safe_repr = (s) -> SAFE_LOGGING ? '[scrubbed]' : JSON.stringify(s)
# HEADLESS is true if we are running not in a browser with a DOM. # HEADLESS is true if we are running not in a browser with a DOM.
@ -120,6 +105,7 @@ if window && window.location
query = Query.parse(window.location.search.substr(1)) query = Query.parse(window.location.search.substr(1))
DEBUG = Params.getBool(query, 'debug', false) DEBUG = Params.getBool(query, 'debug', false)
HEADLESS = 'undefined' == typeof(document) HEADLESS = 'undefined' == typeof(document)
RATE_LIMIT_HISTORY = 5.0
DEFAULT_PORTS = DEFAULT_PORTS =
http: 80 http: 80
@ -177,6 +163,33 @@ makeWebsocket = (addr) ->
ws.binaryType = 'arraybuffer' ws.binaryType = 'arraybuffer'
ws ws
class BucketRateLimit
amount: 0.0
last_update: new Date()
constructor: (@capacity, @time) ->
age: ->
now = new Date()
delta = (now - @last_update) / 1000.0
@last_update = now
@amount -= delta * @capacity / @time
@amount = 0.0 if @amount < 0.0
update: (n) ->
@age()
@amount += n
@amount <= @capacity
# How many seconds in the future will the limit expire?
when: ->
age()
(@amount - @capacity) / (@capacity / @time)
is_limited: ->
@age()
@amount > @capacity
# TODO: Different ICE servers. # TODO: Different ICE servers.
config = { config = {
iceServers: [ iceServers: [
@ -224,6 +237,8 @@ class Snowflake
@badge = new Badge() @badge = new Badge()
@$badgem = @badge.elem @$badgem = @badge.elem
@$badge.setAttribute('id', 'snowflake-badge') if (@$badge) @$badge.setAttribute('id', 'snowflake-badge') if (@$badge)
rateLimitBytes = 0
@rateLimit = new BucketRateLimit(rateLimitBytes * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
# TODO: User-supplied for now, but should fetch from facilitator later. # TODO: User-supplied for now, but should fetch from facilitator later.
setRelayAddr: (relayAddr) -> setRelayAddr: (relayAddr) ->
@ -302,7 +317,6 @@ class Snowflake
@cease() @cease()
@badge.die() if @badge @badge.die() if @badge
### ###
Represents: client <-- webrtc --> snowflake <-- websocket --> relay Represents: client <-- webrtc --> snowflake <-- websocket --> relay
### ###
@ -317,10 +331,8 @@ class ProxyPair
flush_timeout_id: null flush_timeout_id: null
constructor: (@clientAddr, @relayAddr, @rateLimit) -> constructor: (@clientAddr, @relayAddr, @rateLimit) ->
@c2rSchedule = []
@r2cSchedule = []
connectClient: -> connectClient: =>
@pc = new PeerConnection config, { @pc = new PeerConnection config, {
optional: [ optional: [
{ DtlsSrtpKeyAgreement: true } { DtlsSrtpKeyAgreement: true }
@ -342,7 +354,7 @@ class ProxyPair
@prepareDataChannel channel @prepareDataChannel channel
@client = channel @client = channel
prepareDataChannel: (channel) -> prepareDataChannel: (channel) =>
channel.onopen = => channel.onopen = =>
log 'Data channel opened!' log 'Data channel opened!'
snowflake.state = MODE.WEBRTC_READY snowflake.state = MODE.WEBRTC_READY
@ -358,12 +370,12 @@ class ProxyPair
channel.onmessage = @onClientToRelayMessage channel.onmessage = @onClientToRelayMessage
# Assumes WebRTC datachannel is connected. # Assumes WebRTC datachannel is connected.
connectRelay: -> connectRelay: =>
log 'Connecting to relay...' log 'Connecting to relay...'
@relay = makeWebsocket @relayAddr @relay = makeWebsocket @relayAddr
@relay.label = 'websocket-relay' @relay.label = 'websocket-relay'
@relay.onopen = => @relay.onopen = =>
log 'Relay ' + @relay.label + 'connected' log '\nRelay ' + @relay.label + ' connected!'
@relay.onclose = @onClose @relay.onclose = @onClose
@relay.onerror = @onError @relay.onerror = @onError
@relay.onmessage = @onRelayToClientMessage @relay.onmessage = @onRelayToClientMessage
@ -377,14 +389,14 @@ class ProxyPair
bytes = new Uint8Array recv bytes = new Uint8Array recv
line = String.fromCharCode.apply(null, bytes) line = String.fromCharCode.apply(null, bytes)
line = line.trim() line = line.trim()
log 'WebRTC-->websocket data: ' + line console.log 'WebRTC --> websocket data: ' + line
@c2rSchedule.push recv @c2rSchedule.push recv
@flush() @flush()
# websocket --> WebRTC # websocket --> WebRTC
onRelayToClientMessage: (event) => onRelayToClientMessage: (event) =>
@r2cSchedule.push event.data @r2cSchedule.push event.data
log 'websocket-->WebRTC data: ' + event.data # log 'websocket-->WebRTC data: ' + event.data
@flush() @flush()
onClose: (event) => onClose: (event) =>
@ -403,42 +415,43 @@ class ProxyPair
@maybeCleanup() @maybeCleanup()
webrtcIsReady: -> null != @client && 'open' == @client.readyState webrtcIsReady: -> null != @client && 'open' == @client.readyState
isOpen: (ws) -> undefined != ws && WebSocket.OPEN == ws.readyState relayIsReady: -> (null != @relay) && (WebSocket.OPEN == @relay.readyState)
isClosed: (ws) -> undefined == ws || WebSocket.CLOSED == ws.readyState isClosed: (ws) -> undefined == ws || WebSocket.CLOSED == ws.readyState
close: -> close: ->
@client.close() if !(isClosed @client) @client.close() if !(isClosed @client)
@relay.close() if !(isClosed @relay) @relay.close() if !(isClosed @relay)
maybeCleanup: -> maybeCleanup: =>
if @running && @isClosed(client) && @isClosed @relay if @running && @isClosed @relay
@running = false @running = false
@cleanup_callback() # TODO: Call external callback
true true
false false
# Send as much data as the rate limit currently allows. # Send as much data as the rate limit currently allows.
flush: -> flush: =>
clearTimeout @flush_timeout_id if @flush_timeout_id clearTimeout @flush_timeout_id if @flush_timeout_id
@flush_timeout_id = null @flush_timeout_id = null
busy = true busy = true
checkChunks = => checkChunks = =>
busy = false busy = false
# WebRTC --> websocket
if @relayIsReady() && @relay.bufferedAmount < @MAX_BUFFER && @c2rSchedule.length > 0
chunk = @c2rSchedule.shift()
# @rate_limit.update chunk.length
@relay.send chunk
busy = true
# websocket --> WebRTC # websocket --> WebRTC
if @webrtcIsReady() && @client.bufferedAmount < @MAX_BUFFER && @r2cSchedule.length > 0 if @webrtcIsReady() && @client.bufferedAmount < @MAX_BUFFER && @r2cSchedule.length > 0
chunk = @r2cSchedule.shift() chunk = @r2cSchedule.shift()
# this.rate_limit.update(chunk.length) # this.rate_limit.update(chunk.length)
@client.send chunk @client.send chunk
busy = true busy = true
# WebRTC --> websocket
if (@isOpen @relay) && (@relay.bufferedAmount < @MAX_BUFFER) && @c2rSchedule.length > 0
chunk = @c2rSchedule.shift()
# @rate_limit.update chunk.length
@relay.send chunk
busy = true
checkChunks() while busy # && !@rate_limit.is_limited() checkChunks() while busy # && !@rate_limit.is_limited()
# TODO: rate limiting stuff # TODO: a more real rate limit
# if @r2cSchedule.length > 0 || (@client) && @client.bufferedAmount > 0) || @c2rSchedule.length > 0 || (@isOpen(@relay) && @relay.bufferedAmount > 0) if @r2cSchedule.length > 0 || @c2rSchedule.length > 0 || (@relayIsReady() && @relay.bufferedAmount > 0) || (@webrtcIsReady() && @client.bufferedAmount > 0)
@flush_timeout_id = setTimeout @flush, 1000
# @flush_timeout_id = setTimeout @flush, @rate_limit.when() * 1000 # @flush_timeout_id = setTimeout @flush, @rate_limit.when() * 1000
# #
@ -472,9 +485,6 @@ Interface =
Signalling.receive msg Signalling.receive msg
when MODE.WEBRTC_READY when MODE.WEBRTC_READY
log 'No input expected - WebRTC connected.' log 'No input expected - WebRTC connected.'
# data = msg
# log(data)
# channel.send(data)
else else
log 'ERROR: ' + msg log 'ERROR: ' + msg
$input.value = '' $input.value = ''
@ -515,6 +525,7 @@ init = ->
if 13 == e.keyCode # enter if 13 == e.keyCode # enter
$send.onclick() $send.onclick()
snowflake = new Snowflake() snowflake = new Snowflake()
window.snowflake = snowflake
welcome() welcome()
window.onload = init if window window.onload = init if window