mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 05:11:19 -04:00
move datachannel into ProxyPair as client, use hardcoded default Relay as fallback
This commit is contained in:
parent
cfd87d1798
commit
a8477ee402
2 changed files with 257 additions and 254 deletions
|
@ -10,13 +10,15 @@ this must always act as the answerer.
|
|||
TODO(keroserene): Complete the websocket + webrtc ProxyPair
|
||||
###
|
||||
|
||||
DEFAULT_WEBSOCKET = '92.81.135.242:9901'
|
||||
|
||||
if 'undefined' != typeof module && 'undefined' != typeof module.exports
|
||||
console.log 'not in browser.'
|
||||
else
|
||||
window.PeerConnection = window.RTCPeerConnection ||
|
||||
window.mozRTCPeerConnection ||
|
||||
window.webkitRTCPeerConnection
|
||||
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
|
||||
window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate
|
||||
window.RTCSessionDescription = window.RTCSessionDescription ||
|
||||
window.mozRTCSessionDescription
|
||||
|
||||
|
@ -25,7 +27,7 @@ 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
|
||||
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.
|
||||
|
@ -61,8 +63,8 @@ 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 true if 'true' == val || '1' == val || '' == val
|
||||
return false if 'false' == val || '0' == val
|
||||
return null
|
||||
|
||||
|
||||
|
@ -81,8 +83,8 @@ Params =
|
|||
result[name] = value if !(name in result)
|
||||
result
|
||||
|
||||
# Parse an address in the form "host:port". Returns an Object with keys "host"
|
||||
# (String) and "port" (int). Returns null on error.
|
||||
# Parse an address in the form 'host:port'. Returns an Object with keys 'host'
|
||||
# (String) and 'port' (int). Returns null on error.
|
||||
parseAddress: (spec) ->
|
||||
m = null
|
||||
# IPv6 syntax.
|
||||
|
@ -104,191 +106,20 @@ Params =
|
|||
# elems = []
|
||||
# for k in x
|
||||
# elems.push(maybe_quote(k) + ': ' + repr(x[k]));
|
||||
# return "{ " + elems.join(", ") + " }";
|
||||
# } else if (typeof x === "string") {
|
||||
# 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]' : repr(s)
|
||||
safe_repr = (s) -> SAFE_LOGGING ? '[scrubbed]' : JSON.stringify(s)
|
||||
|
||||
# HEADLESS is true if we are running not in a browser with a DOM.
|
||||
DEBUG = false
|
||||
if window && window.location
|
||||
query = Query.parse(window.location.search.substr(1))
|
||||
DEBUG = Params.getBool(query, "debug", false)
|
||||
HEADLESS = "undefined" == typeof(document)
|
||||
|
||||
# TODO: Different ICE servers.
|
||||
config = {
|
||||
iceServers: [
|
||||
{ urls: ["stun:stun.l.google.com:19302"] }
|
||||
]
|
||||
}
|
||||
|
||||
# DOM elements
|
||||
$chatlog = null
|
||||
$send = null
|
||||
$input = null
|
||||
|
||||
|
||||
# TODO: Implement
|
||||
class Badge
|
||||
|
||||
|
||||
# Janky state machine
|
||||
MODE =
|
||||
INIT: 0
|
||||
WEBRTC_CONNECTING: 1
|
||||
WEBRTC_READY: 2
|
||||
|
||||
# Minimum viable snowflake for now - just 1 client.
|
||||
class Snowflake
|
||||
|
||||
# PeerConnection
|
||||
pc: null
|
||||
rateLimit: 0
|
||||
proxyPairs: []
|
||||
relayAddr: null
|
||||
badge: null
|
||||
$badge: null
|
||||
MAX_NUM_CLIENTS = 1
|
||||
CONNECTIONS_PER_CLIENT = 1
|
||||
state: MODE.INIT
|
||||
|
||||
constructor: ->
|
||||
if HEADLESS
|
||||
# No badge
|
||||
else if DEBUG
|
||||
@$badge = debug_div
|
||||
else
|
||||
@badge = new Badge()
|
||||
@$badgem = @badge.elem
|
||||
@$badge.setAttribute("id", "snowflake-badge") if (@$badge)
|
||||
|
||||
# TODO: User-supplied for now, but should fetch from facilitator later.
|
||||
setRelayAddr: (relayAddr) ->
|
||||
addr = Params.parseAddress relayAddr
|
||||
if !addr
|
||||
log 'Invalid address spec. Try again.'
|
||||
return false
|
||||
@relayAddr = addr
|
||||
log 'Using ' + relayAddr + ' as Relay.'
|
||||
log "Input offer from the snowflake client:"
|
||||
@beginWebRTC()
|
||||
return true
|
||||
|
||||
# Initialize WebRTC PeerConnection
|
||||
beginWebRTC: ->
|
||||
log "Starting up Snowflake..."
|
||||
@state = MODE.WEBRTC_CONNECTING
|
||||
|
||||
@pc = new PeerConnection(config, {
|
||||
optional: [
|
||||
{ DtlsSrtpKeyAgreement: true }
|
||||
{ RtpDataChannels: false }
|
||||
]
|
||||
})
|
||||
|
||||
@pc.onicecandidate = (evt) =>
|
||||
# Browser sends a null candidate once the ICE gathering completes.
|
||||
# In this case, it makes sense to send one copy-paste blob.
|
||||
if null == evt.candidate
|
||||
log "Finished gathering ICE candidates."
|
||||
Signalling.send @pc.localDescription
|
||||
|
||||
# OnDataChannel triggered remotely from the client when connection succeeds.
|
||||
@pc.ondatachannel = (dc) =>
|
||||
console.log dc;
|
||||
channel = dc.channel
|
||||
log "Data Channel established..."
|
||||
@prepareDataChannel channel
|
||||
|
||||
prepareDataChannel: (channel) ->
|
||||
channel.onopen = =>
|
||||
log "Data channel opened!"
|
||||
@state = MODE.WEBRTC_READY
|
||||
# This is the point when the WebRTC datachannel is done, so the next step
|
||||
# is to establish websocket to the server.
|
||||
@beginProxy(null, @relayAddr)
|
||||
channel.onclose = =>
|
||||
log "Data channel closed."
|
||||
@state = MODE.INIT;
|
||||
$chatlog.className = ""
|
||||
channel.onerror = =>
|
||||
log "Data channel error!"
|
||||
channel.onmessage = (msg) =>
|
||||
line = recv = msg.data
|
||||
console.log(msg);
|
||||
# Go sends only raw bytes...
|
||||
if "[object ArrayBuffer]" == recv.toString()
|
||||
bytes = new Uint8Array recv
|
||||
line = String.fromCharCode.apply(null, bytes)
|
||||
line = line.trim()
|
||||
log "data: " + line
|
||||
|
||||
# Receive an SDP offer from client plugin.
|
||||
receiveOffer: (desc) =>
|
||||
sdp = new RTCSessionDescription desc
|
||||
try
|
||||
err = @pc.setRemoteDescription sdp
|
||||
catch e
|
||||
log "Invalid SDP message."
|
||||
return false
|
||||
log("SDP " + sdp.type + " successfully received.")
|
||||
@sendAnswer() if 'offer' == sdp.type
|
||||
true
|
||||
|
||||
sendAnswer: =>
|
||||
next = (sdp) =>
|
||||
log "webrtc: Answer ready."
|
||||
@pc.setLocalDescription sdp
|
||||
promise = @pc.createAnswer next
|
||||
promise.then next if promise
|
||||
|
||||
# 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 [1..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
|
||||
pair.connectRelay()
|
||||
catch err
|
||||
log 'ERROR: ProxyPair exception while connecting.'
|
||||
log err
|
||||
return
|
||||
@badge.beginProxy if @badge
|
||||
|
||||
cease: ->
|
||||
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
|
||||
|
||||
DEBUG = Params.getBool(query, 'debug', false)
|
||||
HEADLESS = 'undefined' == typeof(document)
|
||||
|
||||
DEFAULT_PORTS =
|
||||
http: 80
|
||||
|
@ -317,10 +148,10 @@ buildUrl = (scheme, host, port, path, params) ->
|
|||
path = '/' + path
|
||||
###
|
||||
Slash is significant so we must protect it from encodeURIComponent, while
|
||||
still encoding question mark and number sign. RFC 3986, section 3.3: "The
|
||||
still encoding question mark and number sign. RFC 3986, section 3.3: 'The
|
||||
path is terminated by the first question mark ('?') or number sign ('#')
|
||||
character, or by the end of the URI. ... A path consists of a sequence of
|
||||
path segments separated by a slash ('/') character."
|
||||
path segments separated by a slash ('/') character.'
|
||||
###
|
||||
path = path.replace /[^\/]+/, (m) ->
|
||||
encodeURIComponent m
|
||||
|
@ -339,59 +170,244 @@ makeWebsocket = (addr) ->
|
|||
# else
|
||||
# ws = new WebSocket url 'base64'
|
||||
###
|
||||
"User agents can use this as a hint for how to handle incoming binary data: if
|
||||
'User agents can use this as a hint for how to handle incoming binary data: if
|
||||
the attribute is set to 'blob', it is safe to spool it to disk, and if it is
|
||||
set to 'arraybuffer', it is likely more efficient to keep the data in memory."
|
||||
set to 'arraybuffer', it is likely more efficient to keep the data in memory.'
|
||||
###
|
||||
ws.binaryType = 'arraybuffer'
|
||||
ws
|
||||
|
||||
# TODO: Different ICE servers.
|
||||
config = {
|
||||
iceServers: [
|
||||
{ urls: ['stun:stun.l.google.com:19302'] }
|
||||
]
|
||||
}
|
||||
|
||||
# DOM elements
|
||||
$chatlog = null
|
||||
$send = null
|
||||
$input = null
|
||||
|
||||
# TODO: Implement
|
||||
class Badge
|
||||
|
||||
# Janky state machine
|
||||
MODE =
|
||||
INIT: 0
|
||||
WEBRTC_CONNECTING: 1
|
||||
WEBRTC_READY: 2
|
||||
|
||||
# Minimum viable snowflake for now - just 1 client.
|
||||
class Snowflake
|
||||
|
||||
MAX_NUM_CLIENTS = 1
|
||||
CONNECTIONS_PER_CLIENT = 1
|
||||
|
||||
relayAddr: null
|
||||
# TODO: Actually support multiple ProxyPairs. (makes more sense once meek-
|
||||
# signalling is ready)
|
||||
proxyPairs: []
|
||||
proxyPair: null
|
||||
|
||||
rateLimit: null # TODO
|
||||
badge: null
|
||||
$badge: null
|
||||
state: MODE.INIT
|
||||
|
||||
constructor: ->
|
||||
if HEADLESS
|
||||
# No badge
|
||||
else if DEBUG
|
||||
@$badge = debug_div
|
||||
else
|
||||
@badge = new Badge()
|
||||
@$badgem = @badge.elem
|
||||
@$badge.setAttribute('id', 'snowflake-badge') if (@$badge)
|
||||
|
||||
# TODO: User-supplied for now, but should fetch from facilitator later.
|
||||
setRelayAddr: (relayAddr) ->
|
||||
addr = Params.parseAddress relayAddr
|
||||
if !addr
|
||||
log 'Invalid address spec.'
|
||||
return false
|
||||
@relayAddr = addr
|
||||
log 'Using ' + relayAddr + ' as Relay.'
|
||||
@beginWebRTC()
|
||||
log 'Input offer from the snowflake client:'
|
||||
return true
|
||||
|
||||
# Initialize WebRTC PeerConnection
|
||||
beginWebRTC: ->
|
||||
log 'Starting up Snowflake...\n'
|
||||
@state = MODE.WEBRTC_CONNECTING
|
||||
for i in [1..CONNECTIONS_PER_CLIENT]
|
||||
@makeProxyPair @relayAddr
|
||||
@proxyPair = @proxyPairs[0]
|
||||
|
||||
# Receive an SDP offer from client plugin.
|
||||
receiveOffer: (desc) =>
|
||||
sdp = new RTCSessionDescription desc
|
||||
try
|
||||
err = @proxyPair.pc.setRemoteDescription sdp
|
||||
catch e
|
||||
log 'Invalid SDP message.'
|
||||
return false
|
||||
log('SDP ' + sdp.type + ' successfully received.')
|
||||
@sendAnswer() if 'offer' == sdp.type
|
||||
true
|
||||
|
||||
sendAnswer: =>
|
||||
next = (sdp) =>
|
||||
log 'webrtc: Answer ready.'
|
||||
@proxyPair.pc.setLocalDescription sdp
|
||||
promise = @proxyPair.pc.createAnswer next
|
||||
promise.then next if promise
|
||||
|
||||
# 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']
|
||||
|
||||
makeProxyPair: (relay) ->
|
||||
pair = new ProxyPair(null, relay, @rateLimit);
|
||||
@proxyPairs.push pair
|
||||
pair.onCleanup = (event) =>
|
||||
# Delete from the list of active proxy pairs.
|
||||
@proxyPairs.splice(@proxyPairs.indexOf(pair), 1)
|
||||
@badge.endProxy() if @badge
|
||||
try
|
||||
pair.connectClient()
|
||||
catch err
|
||||
log 'ERROR: ProxyPair exception while connecting.'
|
||||
log err
|
||||
return
|
||||
@badge.beginProxy if @badge
|
||||
|
||||
cease: ->
|
||||
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
|
||||
|
||||
|
||||
###
|
||||
Represents: client <-- webrtc --> snowflake <-- websocket --> relay
|
||||
###
|
||||
class ProxyPair
|
||||
MAX_BUFFER: 10 * 1024 * 1024
|
||||
pc: null
|
||||
c2rSchedule: []
|
||||
r2cSchedule: []
|
||||
client: null # WebRTC Data channel
|
||||
relay: null # websocket
|
||||
running: true
|
||||
flush_timeout_id: null
|
||||
|
||||
constructor: (@clientAddr, @relayAddr, @rateLimit) ->
|
||||
@c2rSchedule = []
|
||||
@r2cSchedule = []
|
||||
|
||||
# Assumes WebRTC part is already connected.
|
||||
# TODO: Put the webrtc stuff in ProxyPair, so that multiple webrtc connections
|
||||
# can be established.
|
||||
connectClient: ->
|
||||
@pc = new PeerConnection config, {
|
||||
optional: [
|
||||
{ DtlsSrtpKeyAgreement: true }
|
||||
{ RtpDataChannels: false }
|
||||
]}
|
||||
@pc.onicecandidate = (evt) =>
|
||||
# Browser sends a null candidate once the ICE gathering completes.
|
||||
# In this case, it makes sense to send one copy-paste blob.
|
||||
if null == evt.candidate
|
||||
# TODO: Use a promise.all to tell Snowflake about all offers at once,
|
||||
# once multiple proxypairs are supported.
|
||||
log 'Finished gathering ICE candidates.'
|
||||
Signalling.send @pc.localDescription
|
||||
# OnDataChannel triggered remotely from the client when connection succeeds.
|
||||
@pc.ondatachannel = (dc) =>
|
||||
console.log dc;
|
||||
channel = dc.channel
|
||||
log 'Data Channel established...'
|
||||
@prepareDataChannel channel
|
||||
@client = channel
|
||||
|
||||
prepareDataChannel: (channel) ->
|
||||
channel.onopen = =>
|
||||
log 'Data channel opened!'
|
||||
snowflake.state = MODE.WEBRTC_READY
|
||||
# This is the point when the WebRTC datachannel is done, so the next step
|
||||
# is to establish websocket to the server.
|
||||
@connectRelay()
|
||||
channel.onclose = =>
|
||||
log 'Data channel closed.'
|
||||
@state = MODE.INIT;
|
||||
$chatlog.className = ''
|
||||
channel.onerror = =>
|
||||
log 'Data channel error!'
|
||||
channel.onmessage = @onClientToRelayMessage
|
||||
|
||||
# Assumes WebRTC datachannel is connected.
|
||||
connectRelay: ->
|
||||
log "Snowflake: connecting to relay"
|
||||
|
||||
@relay = makeWebsocket(@relayAddr);
|
||||
@relay.label = 'Relay'
|
||||
log 'Connecting to relay...'
|
||||
@relay = makeWebsocket @relayAddr
|
||||
@relay.label = 'websocket-relay'
|
||||
@relay.onopen = =>
|
||||
log "Snowflake: " + ws.label + "connected"
|
||||
log 'Relay ' + @relay.label + 'connected'
|
||||
@relay.onclose = @onClose
|
||||
@relay.onerror = @onError
|
||||
@relay.onmessage = @onRelayToClientMessage
|
||||
|
||||
onClientToRelayMessage: (event) ->
|
||||
@c2r_schedule.push event.data
|
||||
# WebRTC --> websocket
|
||||
onClientToRelayMessage: (msg) =>
|
||||
line = recv = msg.data
|
||||
console.log msg
|
||||
# Go sends only raw bytes...
|
||||
if '[object ArrayBuffer]' == recv.toString()
|
||||
bytes = new Uint8Array recv
|
||||
line = String.fromCharCode.apply(null, bytes)
|
||||
line = line.trim()
|
||||
log 'WebRTC-->websocket data: ' + line
|
||||
@c2rSchedule.push recv
|
||||
@flush()
|
||||
|
||||
onRelayToClientMessage: (event) ->
|
||||
@r2c_schedule.push event.data
|
||||
# websocket --> WebRTC
|
||||
onRelayToClientMessage: (event) =>
|
||||
@r2cSchedule.push event.data
|
||||
log 'websocket-->WebRTC data: ' + event.data
|
||||
@flush()
|
||||
|
||||
onClose: (event) ->
|
||||
onClose: (event) =>
|
||||
ws = event.target
|
||||
log(ws.label + ': closed.')
|
||||
@flush()
|
||||
@maybeCleanup()
|
||||
|
||||
onError: (event) ->
|
||||
onError: (event) =>
|
||||
ws = event.target
|
||||
log ws.label + ': error.'
|
||||
this.close();
|
||||
@close()
|
||||
# we can't rely on onclose_callback to cleanup, since one common error
|
||||
# case is when the client fails to connect and the relay never starts.
|
||||
# in that case close() is a NOP and onclose_callback is never called.
|
||||
@maybeCleanup()
|
||||
|
||||
webrtcIsReady: -> null != @client && 'open' == @client.readyState
|
||||
isOpen: (ws) -> undefined != ws && WebSocket.OPEN == ws.readyState
|
||||
isClosed: (ws) -> undefined == ws || WebSocket.CLOSED == ws.readyState
|
||||
close: ->
|
||||
@client.close() if !(isClosed @client)
|
||||
@relay.close() if !(isClosed @relay)
|
||||
|
||||
maybeCleanup: ->
|
||||
if @running && @isClosed(client) && @isClosed @relay
|
||||
|
@ -402,67 +418,45 @@ class ProxyPair
|
|||
|
||||
# Send as much data as the rate limit currently allows.
|
||||
flush: ->
|
||||
###
|
||||
clearTimeout @flush_timeout_id if @flush_timeout_id
|
||||
@flush_timeout_id = null
|
||||
busy = true
|
||||
checkChunks = ->
|
||||
checkChunks = =>
|
||||
busy = false
|
||||
# if @isOpen @clientthis.client_s) &&
|
||||
# this.client_s.bufferedAmount < MAX_BUFFER &&
|
||||
# this.r2c_schedule.length > 0) {
|
||||
# chunk = this.r2c_schedule.shift();
|
||||
# this.rate_limit.update(chunk.length);
|
||||
# this.client_s.send(chunk);
|
||||
# busy = true;
|
||||
#
|
||||
if @isOpen @relay &&
|
||||
@relay.bufferedAmount < MAX_BUFFER &&
|
||||
@c2r_schedule.length > 0
|
||||
chunk = @c2r_schedule.shift()
|
||||
@rate_limit.update chunk.length
|
||||
# websocket --> WebRTC
|
||||
if @webrtcIsReady() && @client.bufferedAmount < @MAX_BUFFER && @r2cSchedule.length > 0
|
||||
chunk = @r2cSchedule.shift()
|
||||
# this.rate_limit.update(chunk.length)
|
||||
@client.send chunk
|
||||
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()
|
||||
|
||||
if @isClosed @relay &&
|
||||
# !isClosed(this.client_s) &&
|
||||
# @client_s.bufferedAmount === 0 &&
|
||||
@r2c_schedule.length == 0
|
||||
# log("Client: closing.");
|
||||
# this.client_s.close();
|
||||
# if (is_closed(this.client_s) &&
|
||||
# !is_closed(this.relay_s) &&
|
||||
# this.relay_s.bufferedAmount === 0 &&
|
||||
# this.c2r_schedule.length === 0) {
|
||||
# log("Relay: closing.");
|
||||
# this.relay_s.close();
|
||||
# }
|
||||
# TODO: rate limiting stuff
|
||||
# if @r2cSchedule.length > 0 || (@client) && @client.bufferedAmount > 0) || @c2rSchedule.length > 0 || (@isOpen(@relay) && @relay.bufferedAmount > 0)
|
||||
# @flush_timeout_id = setTimeout @flush, @rate_limit.when() * 1000
|
||||
|
||||
|
||||
while busy && !@rate_limit.is_limited()
|
||||
|
||||
if this.r2c_schedule.length > 0 ||
|
||||
(@isOpen(@client) && @client.bufferedAmount > 0) ||
|
||||
@c2r_schedule.length > 0 ||
|
||||
(@isOpen(@relay) && @relay.bufferedAmount > 0)
|
||||
@flush_timeout_id = setTimeout @flush, @rate_limit.when() * 1000
|
||||
###
|
||||
#
|
||||
## -- DOM & Input Functionality -- ##
|
||||
#
|
||||
snowflake = null
|
||||
|
||||
welcome = ->
|
||||
log "== snowflake browser proxy =="
|
||||
log "Input desired relay address:"
|
||||
log '== snowflake browser proxy =='
|
||||
log 'Input desired relay address:'
|
||||
|
||||
# Log to the message window.
|
||||
log = (msg) ->
|
||||
$chatlog.value += msg + "\n"
|
||||
console.log msg
|
||||
# Scroll to latest
|
||||
$chatlog.scrollTop = $chatlog.scrollHeight
|
||||
if $chatlog
|
||||
$chatlog.value += msg + '\n'
|
||||
$chatlog.scrollTop = $chatlog.scrollHeight
|
||||
|
||||
Interface =
|
||||
# Local input from keyboard into message window.
|
||||
|
@ -471,44 +465,46 @@ Interface =
|
|||
switch snowflake.state
|
||||
when MODE.INIT
|
||||
# Set target relay.
|
||||
snowflake.setRelayAddr msg
|
||||
if !(snowflake.setRelayAddr msg)
|
||||
log 'Defaulting to websocket relay at ' + DEFAULT_WEBSOCKET
|
||||
snowflake.setRelayAddr DEFAULT_WEBSOCKET
|
||||
when MODE.WEBRTC_CONNECTING
|
||||
Signalling.receive msg
|
||||
when MODE.WEBRTC_READY
|
||||
log "No input expected - WebRTC connected."
|
||||
log 'No input expected - WebRTC connected.'
|
||||
# data = msg
|
||||
# log(data)
|
||||
# channel.send(data)
|
||||
else
|
||||
log "ERROR: " + msg
|
||||
$input.value = ""
|
||||
log 'ERROR: ' + msg
|
||||
$input.value = ''
|
||||
$input.focus()
|
||||
|
||||
# Signalling channel - just tells user to copy paste to the peer.
|
||||
# Eventually this should go over the facilitator.
|
||||
Signalling =
|
||||
send: (msg) ->
|
||||
log "---- Please copy the below to peer ----\n"
|
||||
log '---- Please copy the below to peer ----\n'
|
||||
log JSON.stringify(msg)
|
||||
log "\n"
|
||||
log '\n'
|
||||
|
||||
receive: (msg) ->
|
||||
recv = ""
|
||||
recv = ''
|
||||
try
|
||||
recv = JSON.parse msg
|
||||
catch e
|
||||
log "Invalid JSON."
|
||||
log 'Invalid JSON.'
|
||||
return
|
||||
desc = recv['sdp']
|
||||
if !desc
|
||||
log "Invalid SDP."
|
||||
log 'Invalid SDP.'
|
||||
return false
|
||||
snowflake.receiveOffer recv if desc
|
||||
|
||||
init = ->
|
||||
|
||||
$chatlog = document.getElementById('chatlog')
|
||||
$chatlog.value = ""
|
||||
$chatlog.value = ''
|
||||
|
||||
$send = document.getElementById('send')
|
||||
$send.onclick = Interface.acceptInput
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue