Add web client
This commit is contained in:
		
							
								
								
									
										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