Class: Clientside::NoResMiddleware

Inherits:
Object
  • Object
show all
Defined in:
lib/clientside.rb

Direct Known Subclasses

Middleware

Constant Summary collapse

RPATH =
'/__clientside_sock__/'
MAX_OBJECTS =
256
PENDING_TTL =
5 * 60
@@pending_sockets =
{}
@@pending_expiries =
[]
@@sockets =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ NoResMiddleware

Returns a new instance of NoResMiddleware.



56
57
58
# File 'lib/clientside.rb', line 56

def initialize(app)
  @app = app
end

Class Method Details

.add_pending(objs) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
# File 'lib/clientside.rb', line 143

def self.add_pending(objs)
  objs = Hash[objs.map {|o| [o.object_id, o]}]
  cid = SecureRandom.hex
  @@pending_sockets[cid] = objs
  @@pending_expiries << [cid, Time.now + PENDING_TTL]
  until @@pending_expiries.first[1] > Time.now
    ecid, _ = @@pending_expiries.shift
    @@pending_sockets.delete ecid
  end
  cid
end

Instance Method Details

#call(env) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/clientside.rb', line 90

def call(env)
  is_websocket = Faye::WebSocket.websocket? env
  for_us = env['REQUEST_PATH'].start_with? RPATH
  if is_websocket and for_us
    env['REQUEST_PATH'] =~ %r(\A#{RPATH}(.+)\Z)
    cid = $1
    objs = @@pending_sockets.delete(cid)

    unless objs.nil?
      ws = Faye::WebSocket.new(env)
      @@sockets[ws] = objs
    else
      return @app.call env
    end

    ws.on :message do |event|
      begin
        cmd = JSON.parse event.data, symbolize_names: true
        needed = [:receiver, :method_, :arguments, :id]
        unless needed.all? {|k| cmd.key? k}
          if cmd.key? :id
            ws.send JSON.dump({status: 'error',
                               message: 'invalid request', id: cmd[:id]})
          end
          next
        end
        cmd = Accessible.reinflate cmd, @@sockets[ws]
        cmd = OpenStruct.new cmd
        handle_message cmd, ws
      rescue JSON::ParserError
      rescue KeyError => e
        e.message =~ /\Akey not found: (.+)\Z/
        missing_id = $1
        message = "unknown object id: #{missing_id}"
        ws.send JSON.dump({status: 'error',
                           message: message, id: cmd.id})
      rescue RuntimeError => e
        ws.send JSON.dump({status: 'error',
                           message: e.message, id: cmd.id})
      end
    end

    ws.on :close do |event|
      @@sockets.delete ws
      ws = nil
    end

    ws.rack_response
  else
    @app.call env
  end
end

#handle_message(cmd, ws) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/clientside.rb', line 64

def handle_message(cmd, ws)
  can_receive = cmd.receiver.kind_of? Accessible
  raise "receiver is not js-accessible" unless can_receive
  is_name = cmd.method_.respond_to? :to_sym
  raise "not a method name: #{cmd.method_}" unless is_name
  allowed = cmd.receiver.class.js_allowed.include? cmd.method_.to_sym
  raise "unknown method: #{cmd.method_}" unless allowed

  begin
    result = cmd.receiver.send cmd.method_, *cmd.arguments
  rescue ArgumentError => e
    raise e.message
  end
  o_tj_source = JSON::Ext::Generator::GeneratorMethods::Object
  result = nil if result.method(:to_json).owner.equal? o_tj_source

  if result.kind_of? Accessible
    unless @@sockets[ws].length >= MAX_OBJECTS
      register_obj ws, result
    else
      raise "too many objects allocated"
    end
  end
  ws.send JSON.dump({status: 'success', id: cmd.id, result: result})
end

#register_obj(ws, obj) ⇒ Object



60
61
62
# File 'lib/clientside.rb', line 60

def register_obj(ws, obj)
  @@sockets[ws][obj.object_id] = obj
end