antosdk-apps/Antunnel/coffees/Antunnel.coffee

233 lines
7.3 KiB
CoffeeScript
Raw Normal View History

2020-08-06 23:21:42 +02:00
class Msg
constructor: () ->
@header = {
sid: 0,
cid: 0,
type: 0,
size: 0
}
@data = undefined
as_raw:() ->
length = 21 + @header.size
arr = new Uint8Array(length)
arr.set(Msg.MAGIC_START, 0)
arr[4] = @header.type
bytes = Msg.bytes_of @header.cid
arr.set(bytes,5)
bytes = Msg.bytes_of @header.sid
arr.set(bytes,9)
bytes = Msg.bytes_of @header.size
arr.set(bytes,13)
if @data
arr.set(@data, 17)
arr.set(Msg.MAGIC_END, @header.size + 17)
arr.buffer
Msg.decode = (raw) ->
new Promise (resolve, reject) ->
msg = new Msg()
if(Msg.int_from(Msg.MAGIC_START, 0) != Msg.int_from(raw, 0))
return reject("Unmatch message begin magic number")
msg.header.type = raw[4]
msg.header.cid = Msg.int_from(raw, 5)
msg.header.sid = Msg.int_from(raw,9)
msg.header.size = Msg.int_from(raw, 13)
msg.data = raw.slice(17, 17+msg.header.size)
if(Msg.int_from(Msg.MAGIC_END, 0) != Msg.int_from(raw, 17+msg.header.size))
return reject("Unmatch message end magic number")
resolve msg
Msg.bytes_of = (x) ->
bytes=new Uint8Array(4)
bytes[0]=x & (255)
x=x>>8
bytes[1]=x & (255)
x=x>>8
bytes[2]=x & (255)
x=x>>8
bytes[3]=x & (255)
bytes
Msg.int_from = (bytes, offset) ->
(bytes[offset] | (bytes[offset+1]<<8) | (bytes[offset+2]<<16) | (bytes[offset+3] << 24))
Msg.OK = 0
Msg.ERROR = 1
Msg.DATA = 6
Msg.CLOSE = 5
Msg.SUBSCRIBE = 2
Msg.UNSUBSCRIBE = 3
Msg.CTRL = 7
Msg.MAGIC_END = [ 0x41, 0x4e, 0x54, 0x44]
Msg.MAGIC_START = [0x44, 0x54, 0x4e, 0x41 ]
class Subscriber
constructor: (@channel) ->
@id = undefined
@channel_id = undefined
@onmessage = undefined
@onerror = undefined
@onopen = undefined
@onclose = undefined
@tunnel = undefined
@is_opened = false
send: (type, arr) ->
if not @tunnel
@onerror "Tunnel is not opened" if @onerror
return
if not @is_opened
@onerror "Channel is not opened yet" if @onerror
return
@tunnel.send @genmsg type, arr
genmsg: (type, data) ->
msg = new Msg()
msg.header.sid = @id
msg.header.cid = @channel_id
msg.header.type = type
msg.header.size = if data then data.length else 0
msg.data = data
msg
close: (b) ->
@is_opened = false
return unless @tunnel
@tunnel.unsubscribe @, b
class AntunnelApi
constructor: (@uri) ->
@socket = undefined
@pending = {}
@subscribers = {}
@onclose = undefined
ready: () ->
return new Promise (resolve, reject) =>
return reject() if not @uri
return resolve() if @socket isnt undefined
# connect to the socket
console.log "Connect to #{@uri}"
@socket = new WebSocket(@uri)
@socket.binaryType = 'arraybuffer'
@socket.onmessage = (evt) => @process evt
@socket.onclose = (evt) =>
@socket = undefined
for k,v of @pending
v.tunnel = undefined
v.onclose() if v.onclose
for k,v of @subscribers
v.tunnel = undefined
v.is_opened = false
v.onclose() if v.onclose
@pending = {}
@subscribe = {}
@onclose() if @onclose()
@socket.onerror = (evt) =>
v.onerror(evt.toString()) for k,v of @pending when v.onerror
v.onerror(evt.toString()) for k,v of @subscribers when v.onerror
@socket.onopen = (e) => resolve()
process: (evt) ->
Msg.decode(new Uint8Array(evt.data)).then (msg) =>
# find the correct subscriber of the data
relay_msg = (m, a) =>
sub = @pending[m.header.sid]
if sub
sub[a] m if sub[a]
return
sub = @subscribers[m.header.sid]
if sub
sub[a] m if sub[a]
switch msg.header.type
when Msg.OK
# first look for the pending
sub = @pending[msg.header.sid]
if sub
delete @pending[msg.header.sid]
sub.id = Msg.int_from(msg.data,0)
sub.channel_id = msg.header.cid
@subscribers[sub.id] = sub
sub.is_opened = true
sub.onopen() if sub.onopen
else
relay_msg msg, "onmessage"
when Msg.DATA
relay_msg msg, "onmessage"
when Msg.ERROR
relay_msg msg, "onerror"
when Msg.UNSUBSCRIBE
sub = @subscribers[msg.header.sid]
return unless sub
sub.close(true)
else
console.error "Message of type #{msg.header.type} is unsupported", msg
.catch (e) =>
v.onerror(e) for k,v of @pending when v.onerror
v.onerror(e) for k,v of @subscribers when v.onerror
subscribe: (sub) ->
@ready().then ()=>
# insert it to pending list
sub.tunnel = @
sub.id = Math.floor(Math.random()*100000) + 1
while @subscribers[sub.id] or @pending[sub.id]
sub.id = Math.floor(Math.random()*100000) + 1
@pending[sub.id] = sub
# send request to connect to a channel
@send sub.genmsg Msg.SUBSCRIBE, (new TextEncoder()).encode(sub.channel)
.catch (e) ->
sub.onerror e.toString() if sub.onerror
unsubscribe: (sub, b) ->
@ready().then ()=>
return unless @subscribers[sub.id]
# insert it to pending list
# send request to connect to a channel
@send sub.genmsg Msg.UNSUBSCRIBE, undefined if not b
sub.onclose() if sub.onclose
delete @subscribers[sub.id]
sub.tunnel = undefined
sub.is_opened = false
.catch (e) ->
sub.onerror e.toString() if sub.onerror
send: (msg) ->
# return unless @subscribers[msg.header.sid]
@socket.send msg.as_raw()
close: () ->
console.log "Close connection to #{@uri}"
@socket.close() if @socket
@onclose() if @onclose()
W = this
2020-08-08 19:40:21 +02:00
if not W.Antunnel
W.Antunnel = {
tunnel: undefined
init: ((url) ->
return new Promise (resolve, reject) ->
return resolve(W.Antunnel.tunnel) if W.Antunnel.tunnel
W.Antunnel.tunnel = new AntunnelApi(url)
W.Antunnel.tunnel.onclose = () -> W.Antunnel.tunnel = undefined
W.Antunnel.tunnel.ready().then () ->
resolve(W.Antunnel.tunnel)
.catch (e) -> reject(e)),
Subscriber: Subscriber,
Msg: Msg
}