Add web client
This commit is contained in:
parent
6ee3602128
commit
328be35ae2
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/client/web/lrpc.js
|
||||
/s
|
||||
/c
|
3
client/web/Gemfile
Normal file
3
client/web/Gemfile
Normal file
@ -0,0 +1,3 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'ruby2js'
|
19
client/web/Gemfile.lock
Normal file
19
client/web/Gemfile.lock
Normal file
@ -0,0 +1,19 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
ast (2.4.2)
|
||||
parser (3.1.2.0)
|
||||
ast (~> 2.4.1)
|
||||
regexp_parser (2.1.1)
|
||||
ruby2js (5.0.1)
|
||||
parser
|
||||
regexp_parser (~> 2.1.1)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
ruby2js
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.15
|
3
client/web/Makefile
Normal file
3
client/web/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
lrpc.js: convert.rb lrpc.rb
|
||||
bundle install
|
||||
ruby convert.rb > lrpc.js
|
6
client/web/convert.rb
Executable file
6
client/web/convert.rb
Executable file
@ -0,0 +1,6 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
require 'ruby2js'
|
||||
require 'ruby2js/filter/functions'
|
||||
|
||||
puts Ruby2JS.convert(File.read('lrpc.rb'), eslevel: 2016)
|
150
client/web/lrpc.rb
Normal file
150
client/web/lrpc.rb
Normal file
@ -0,0 +1,150 @@
|
||||
# LRPCResponseType represents the various types an LRPC
|
||||
# response can have.
|
||||
LRPCResponseType = {
|
||||
Normal: 0,
|
||||
Error: 1,
|
||||
Channel: 2,
|
||||
ChannelDone: 3,
|
||||
}
|
||||
|
||||
# LRPCClient represents a client for the LRPC protocol
|
||||
# using WebSockets and the JSON codec
|
||||
class LRPCClient
|
||||
def initialize(addr)
|
||||
# Set self variables
|
||||
@callMap = Map.new()
|
||||
@enc = TextEncoder.new()
|
||||
@dec = TextDecoder.new()
|
||||
|
||||
# Create connection to lrpc server
|
||||
@conn = WebSocket.new(addr)
|
||||
@conn.binaryType = "arraybuffer"
|
||||
@conn.onmessage = proc do |msg|
|
||||
# if msg.data is string
|
||||
if msg.data.instance_of? String
|
||||
# Set json to msg.data
|
||||
json = msg.data
|
||||
else
|
||||
# Set json to decoded msg.data
|
||||
json = @dec.decode(msg.data)
|
||||
end
|
||||
# Parse JSON string
|
||||
val = JSON.parse(json)
|
||||
# Get id from callMap
|
||||
fns = @callMap.get(val.ID)
|
||||
# If fns is undefined (key does not exist), and this is
|
||||
# a normal response, return
|
||||
return if !fns && val.Type == LRPCResponseType.Normal
|
||||
|
||||
case val.Type
|
||||
when LRPCResponseType.Normal
|
||||
# If fns is a channel, send the value. Otherwise,
|
||||
# resolve the promise with the value.
|
||||
if fns.isChannel
|
||||
fns.send(val.Return)
|
||||
else
|
||||
fns.resolve(val.Return)
|
||||
end
|
||||
when LRPCResponseType.Channel
|
||||
# Get channel ID from response
|
||||
chID = val.Return
|
||||
# Create new LRPCChannel
|
||||
ch = LRPCChannel.new(self, chID)
|
||||
# Set channel in map
|
||||
@callMap.set(chID, ch)
|
||||
# Resolve promise with channel
|
||||
fns.resolve(ch)
|
||||
when LRPCResponseType.ChannelDone
|
||||
# Close and delete channel
|
||||
fns.close()
|
||||
@callMap.delete(val.ID)
|
||||
when LRPCResponseType.Error
|
||||
# Reject promise with error
|
||||
fns.reject(val.Error)
|
||||
end
|
||||
|
||||
# Delete item from map unless it is a channel
|
||||
@callMap.delete(val.ID) unless fns.isChannel
|
||||
end
|
||||
end
|
||||
|
||||
# call calls a method on the server with the given
|
||||
# argument and returns a promise.
|
||||
def call(rcvr, method, arg)
|
||||
return Promise.new do |resolve, reject|
|
||||
# Get random UUID (this only works with TLS)
|
||||
id = crypto.randomUUID()
|
||||
# Add resolve/reject functions to callMap
|
||||
@callMap.set(id, {
|
||||
resolve: resolve,
|
||||
reject: reject,
|
||||
})
|
||||
|
||||
# Encode data as JSON
|
||||
data = @enc.encode({
|
||||
Receiver: rcvr,
|
||||
Method: method,
|
||||
Arg: arg,
|
||||
ID: id,
|
||||
}.to_json())
|
||||
|
||||
# Send data to lrpc server
|
||||
@conn.send(data.buffer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# LRPCChannel represents a channel used for lrpc.
|
||||
class LRPCChannel
|
||||
def initialize(client, id)
|
||||
# Set self variables
|
||||
@client = client
|
||||
@id = id
|
||||
@arr = []
|
||||
# Set function variables to no-ops
|
||||
@onMessage = proc {|fn|}
|
||||
@onClose = proc {}
|
||||
end
|
||||
|
||||
# isChannel is defined to allow identifying whether
|
||||
# an object is a channel.
|
||||
def isChannel() end
|
||||
|
||||
# send sends a value on the channel. This should not
|
||||
# be called by the consumer of the channel.
|
||||
def send(val)
|
||||
@arr.push(val)
|
||||
fn = @onMessage
|
||||
fn(val)
|
||||
end
|
||||
|
||||
# done cancels the context corresponding to the channel
|
||||
# on the server side and closes the channel.
|
||||
def done()
|
||||
@client.call("lrpc", "ChannelDone", @id)
|
||||
self.close()
|
||||
@client._callMap.delete(@id)
|
||||
end
|
||||
|
||||
# onMessage sets the callback to be called whenever a
|
||||
# message is received. The function should have one parameter
|
||||
# that will be set to the value received. Subsequent calls
|
||||
# will overwrite the callback
|
||||
def onMessage(fn)
|
||||
@onMessage = fn
|
||||
end
|
||||
|
||||
# onClose sets the callback to be called whenever the client
|
||||
# is closed. The function should have no parameters.
|
||||
# Subsequent calls will overwrite the callback
|
||||
def onClose(fn)
|
||||
@onClose = fn
|
||||
end
|
||||
|
||||
# close closes the channel. This should not be called by the
|
||||
# consumer of the channel. Use done() instead.
|
||||
def close()
|
||||
fn = @onClose
|
||||
fn()
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user