Class: MainPlugin

Inherits:
Plugin
  • Object
show all
Defined in:
plugins/main/main.rb

Overview

The MainPlugin is accessible as @plugins.main and just main from other plugins.

MainPlugin provides mainly client setup and the following services:

  • The root html page, which includes the scripts and sets up client startup variables.

  • The url of the client as a HValue, including the anchor.

    • Accessible via msg.session[:main][:location_href]

  • The local time of the client’s web browser as a HValue, as seconds since epoch.

    • Accessible via msg.session[:main][:client_time]

  • Sequential loading. See #delayed_call

  • Provides the #init_ui event for plugins that respond to it.

Instance Method Summary collapse

Instance Method Details

#closeObject

Frees the ticket resource id of the “loading” gif image.



105
106
107
108
# File 'plugins/main/main.rb', line 105

def close
  super
  @plugins[:ticket].del_rsrc( @loading_gif_id )
end

#delayed_call(msg, params) ⇒ nil

Interface for adding delayed calls

When adding a delayed call, use an Array to define a plugin/method with optional arguments that will be called on the next request. The client will call back immediately when a delayed call is pending. The first param of the method is a msg. Don’t include the msg of the current request in params, it will be inserted automatically for the delayed call.

It can also be used for loading sequences to the client, when using a String as the params.

Format of params for plugin callback:

Array
plugin_name, method_name, *args

Format of params for javascript sequences:

String

Javascript to send

Calls will be flushed per request with the following conditions:

  • At most four (4) delayed calls will be processed at a time

  • If the calls use more than 200ms combined, even less will be processed at a time

Parameters:

  • msg (Message)

    The message instance.

  • params (Array, String)

    The params of the delayed call.

Returns:

  • (nil)


308
309
310
# File 'plugins/main/main.rb', line 308

def delayed_call( msg, params )
  get_ses( msg )[:delayed_calls].push( params )
end

#do_init_ui(msg) ⇒ Object

Enables the init_ui event.



350
351
352
# File 'plugins/main/main.rb', line 350

def do_init_ui( msg )
  get_ses( msg )[:dont_init_ui] = false
end

#dont_init_ui(msg) ⇒ Object

Disables the init_ui event.



344
345
346
# File 'plugins/main/main.rb', line 344

def dont_init_ui( msg )
  get_ses( msg )[:dont_init_ui] = true
end

#end_polling(msg, ses) ⇒ Object

When nothing is delayed and the second poll has been made (init_ui called), sets the client to non-polling-mode, having only value synchronization trigger new requests. On the client, SesWatcher forces the change by sending the client time periodically.



413
414
415
416
417
418
# File 'plugins/main/main.rb', line 413

def end_polling( msg, ses )
  if ses[:poll_mode] == true
    msg.reply "COMM.Transporter.poll(0);"
    ses[:poll_mode] = false
  end
end

#flush_delayed(msg, ses) ⇒ Object

Flushes commands in the :delayed_calls array



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'plugins/main/main.rb', line 356

def flush_delayed( msg, ses )
  ## Limits the amount of delayed calls to process to 4.
  ## Prevents the client from choking even when the server
  ## load is light.
  if ses[:delayed_calls].size < 4
    call_count = ses[:delayed_calls].size
  else
    call_count = 4
  end
  
  time_start = Time.now.to_f
  time_taken = 0.0
  
  ## process delayed calls, until:
  ## - over 200ms of cpu time has been spent
  ## - the :delayed_calls -array is empty
  ## - call_count limit is reached
  until time_taken > 0.2 or ses[:delayed_calls].size == 0 or call_count == 0
    # gets the next call
    delayed_call = ses[:delayed_calls].shift
    if RSence.args[:debug]
      puts "delayed_call: #{delayed_call.inspect}"
    end
    # strings are always javascript, used for segmenting client load
    if delayed_call.class == String
      msg.reply delayed_call
    # arrays are plugin calls
    elsif delayed_call.class == Array
      # ['plugin_name', 'method_name'] pairs call the named plugin:method with just msg
      if delayed_call.size == 2
        (plugin_name,method_name) = delayed_call
        msg.run(plugin_name,method_name,msg)
      # if the array contains more items, they are used as additional method params
      else
        (plugin_name,method_name) = delayed_call[0..1]
        method_params = delayed_call[2..-1]
        msg.run(plugin_name,method_name,msg,*method_params)
      end
    end
    ## calculates time taken
    time_taken = Time.now.to_f - time_start
    call_count -= 1
  end
  ## Sets the client into poll mode, unless the :delayed_calls -array is empty
  if ses[:boot] > 1
    if ses[:delayed_calls].empty?
      end_polling( msg, ses )
    else
      start_polling( msg, ses )
    end
  end
end

#get(req, response, ses) ⇒ Object

Outputs the startup web page.



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'plugins/main/main.rb', line 139

def get( req, response, ses )
  index_html = render_index_html
  
  response.status = 200
  
  response['Content-Type'] = 'text/html; charset=UTF-8'
  response['Date'] = httime( Time.now )
  response['Server'] = 'RSence'
  response['Cache-Control'] = 'no-cache'
  
  if support_gzip( req.header )
    index_gzip = GZString.new('')
    gzwriter = Zlib::GzipWriter.new( index_gzip, 9 )
    gzwriter.write( index_html )
    gzwriter.close
    response['Content-Length'] = index_gzip.bytesize.to_s
    response['Content-Encoding'] = 'gzip'
    response.body = index_gzip
  else
    response['Content-Length'] = index_html.bytesize.to_s
    response.body = index_html
  end
end

#idle(msg) ⇒ Object

Called on every request of an active, valid session



429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'plugins/main/main.rb', line 429

def idle(msg)
  
  ses = get_ses( msg )
  if ses[:boot] == 0
    boot0( msg, ses )
  elsif ses[:boot] == 1
    boot1( msg, ses )
  elsif not ses[:delayed_calls].empty?
    flush_delayed( msg, ses )
  elsif ses[:boot] > 1
    end_polling( msg, ses )
  end
  ## Increment the counter forever.
  ses[:boot] += 1
end

#index_deps_setup(msg) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'plugins/main/main.rb', line 235

def index_deps_setup( msg )
  ses = msg.session
  if not ses.has_key?( :deps )
    # make an array of dependencies for this session, if not already done
    ses[:deps] = []
  end
  compound_pkgs = RSence.config[:client_pkg][:compound_packages]
  boot_dep = @conf[:boot_lib]
  unless ses[:deps].include?( boot_dep )
    if compound_pkgs.include?( boot_dep )
      compound_pkgs[ boot_dep ].each do |pkg_name|
        ses[:deps].push( pkg_name )
        msg.reply(%{jsLoader.loaded("#{pkg_name}");})
      end
    end
    ses[:deps].push( boot_dep )
    begin
      msg.reply(%{jsLoader.loaded("#{boot_dep}");})
    rescue => e
      warn %{ses_id: #{msg.ses_id} failed to load boot_dep: "#{boot_dep}", because: #{e.inspect}}
    end
    if boot_dep == 'rsence'
      ses[:deps].push( 'std_widgets' )
      msg.reply(%{jsLoader.loaded("std_widgets");})
    end
  end
  @conf[:default_libs].each do |dep_lib|
    unless ses[:deps].include?( dep_lib )
      if compound_pkgs.include?( dep_lib )
        compound_pkgs[ dep_lib ].each do |pkg_name|
          ses[:deps].push( pkg_name )
          msg.reply(%{jsLoader.loaded("#{pkg_name}");})
        end
      end
      ses[:deps].push( dep_lib )
      msg.reply(%{jsLoader.loaded("#{dep_lib}");})
    end
  end
end

#initObject

Binds configuration data as instance variables



87
88
89
90
91
92
93
94
95
# File 'plugins/main/main.rb', line 87

def init
  super
  @plugins.register_alias( :main, :index_html )
  @randgen = RandGen.new( 40 )
  ::RSence.config[:index_html][:instance] = self
  @conf  = ::RSence.config[:index_html]
  @bconf = ::RSence.config[:broker_urls]
  @goodbye_uri = File.join(@bconf[:hello],'goodbye')
end

#init_ses(msg) ⇒ Object

New session initialization, called just once per session.



230
231
232
233
# File 'plugins/main/main.rb', line 230

def init_ses( msg )
  super
  restore_ses( msg )
end

#openObject

Opens and renders the index page template



98
99
100
101
102
# File 'plugins/main/main.rb', line 98

def open
  super
  @index_html_src = file_read( ::RSence.config[:index_html][:index_tmpl] )
  render_index_html
end

#post(req, res, ses) ⇒ Object

Returns the “hello/goodbye” session termination request



164
165
166
# File 'plugins/main/main.rb', line 164

def post( req, res, ses )
  @plugins.sessions.expire_ses_by_req( req, res )
end

#pound(msg) ⇒ Object

Returns pound url of browser (after the ‘#’ sign)



220
221
222
# File 'plugins/main/main.rb', line 220

def pound( msg )
  get_ses( msg )[:url][1]
end

#render_index_htmlObject

Index page renderer



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'plugins/main/main.rb', line 55

def render_index_html
  
  index_html = @index_html_src.clone
  
  client_rev = client_pkg.client_cache.client_rev
  deps_src = ''
  @conf[:deps].each do |dep|
    deps_src += %{<script src="#{dep}" type="text/javascript"></script>}
  end
  deps_src += %{<script src="__CLIENT_BASE__/js/#{@conf[:boot_lib]}.js"></script>}
  @conf[:default_libs].each do |dep|
    deps_src += %{<script src="__CLIENT_BASE__/js/#{dep}.js"></script>}
  end
  client_base = File.join(@bconf[:h],client_rev)
  
  index_html.gsub!( '__SCRIPT_DEPS__',   deps_src          )
  index_html.gsub!( '__CLIENT_BASE__',   client_base       )
  index_html.gsub!( '__DEFAULT_TITLE__', @conf[:title]     )
  index_html.gsub!( '__CLIENT_REV__',    client_rev        )
  index_html.gsub!( '__CLIENT_HELLO__',  @bconf[:hello]    )
  index_html.gsub!( '__NOSCRIPT__',      @conf[:noscript]  )
  index_html.gsub!( '__BODY__',          @conf[:body]      )
  
  return index_html
end

#restore_ses(msg) ⇒ Object

Called once when a session is restored or cloned using the cookie’s ses_key



276
277
278
279
280
281
282
283
284
285
# File 'plugins/main/main.rb', line 276

def restore_ses( msg )
  super
  index_deps_setup( msg )
  ## Resets session data to defaults
  ses = get_ses( msg )
  ses[:boot] = 0
  ses[:url] = [nil,nil]
  ses[:delayed_calls] = []
  ses[:poll_mode] = true
end

#start_polling(msg, ses) ⇒ Object

Starts polling mode.



421
422
423
424
425
426
# File 'plugins/main/main.rb', line 421

def start_polling( msg, ses )
  if ses[:poll_mode] == false
    msg.reply( "COMM.Transporter.poll(#{::RSence.config[:transporter_conf][:client_poll_priority]});" )
    ses[:poll_mode] = true
  end
end

#support_gzip(header) ⇒ Object

Inspects the http request header to decide if the browser supports gzip compressed responses.



132
133
134
135
136
# File 'plugins/main/main.rb', line 132

def support_gzip( header )
  return false if not ::RSence.config[:no_gzip]
  return false if not header.has_key?('accept-encoding')
  return header['accept-encoding'].include?('gzip')
end

#url(msg) ⇒ Object

Returns base url of browser (before the ‘#’ sign)



214
215
216
# File 'plugins/main/main.rb', line 214

def url( msg )
  get_ses( msg )[:url][0]
end

#url_responder(msg, location_href) ⇒ Object

The #url_responder gets called whenever the anchor (pound) of location.href changes. It enables virtual url events for back/forward buttons and bookmarking in browsers whenever utilized.

Client-side support is included in js/url_responder.js

Also allows virtual-host -like behavior if utilized.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'plugins/main/main.rb', line 178

def url_responder(msg,location_href)
  
  ses = get_ses( msg )
  
  # Virtual locations:
  if location_href.data.include?('#')
    
    # split 'http://localhost:8001/#/some_uri'
    #   -> ['http://localhost:8001/','/some_uri']
    ses[:url] = location_href.data.split('#')
    
    virtual_uri = ses[:url][1]
    
    # built-in support for signing out, deletes the
    # server-side session and reloads the page
    if virtual_uri == '/sign_out'
      resp_addr = @conf[:respond_address]
      @plugins.delegate('sign_out',msg)
      msg.expire_session()
      msg.reply( [
        'COMM.Transporter.stop=true;',
        "location.href=#{resp_addr.to_json};"
      ].join('') )
    end
    
  else
    ses[:url] = [location_href.data,nil]
  end
  
  # url_responder always accepts locations
  return true
  
end