Просмотр исходного кода

Fixed issue #2327 - Websocket messages are not working correctly via Ajax long polling (e. g. multiple browser tabs can get opened with one session).

Martin Edenhofer 6 лет назад
Родитель
Сommit
75230c3db3

+ 7 - 2
app/assets/javascripts/app/lib/app_post/websocket.coffee

@@ -35,6 +35,11 @@ class App.WebSocket
       _instance ?= new _webSocketSingleton
     _instance.support()
 
+  @queue: ->
+    if _instance == undefined
+      _instance ?= new _webSocketSingleton
+    _instance.queue
+
 # The actual Singleton class
 class _webSocketSingleton extends App.Controller
   @include App.LogInclude
@@ -355,10 +360,10 @@ class _webSocketSingleton extends App.Controller
         success: (data) =>
           if data && data.error
             @client_id = undefined
-            @_ajaxInit( force: true )
+            @_ajaxInit(force: true)
         error: =>
           @client_id = undefined
-          @_ajaxInit( force: true )
+          @_ajaxInit(force: true)
       )
 
   _ajaxReceive: =>

+ 12 - 13
app/controllers/long_polling_controller.rb

@@ -2,6 +2,7 @@
 
 class LongPollingController < ApplicationController
   skip_before_action :session_update # prevent race conditions
+  prepend_before_action :authentication_check_only
 
   # GET /api/v1/message_send
   def message_send
@@ -14,24 +15,22 @@ class LongPollingController < ApplicationController
       client_id = client_id_gen
       log 'new client connection', client_id
     end
-    if !params['data']
-      params['data'] = {}
-    end
+    data = params['data'].permit!.to_h
     session_data = {}
     if current_user&.id
       session_data = { 'id' => current_user.id }
     end
 
     # spool messages for new connects
-    if params['data']['spool']
-      Sessions.spool_create(params['data'])
+    if data['spool']
+      Sessions.spool_create(data)
     end
-    if params['data']['event'] == 'login'
+    if data['event'] == 'login'
       Sessions.create(client_id, session_data, { type: 'ajax' })
-    elsif params['data']['event']
+    elsif data['event']
       message = Sessions::Event.run(
-        event: params['data']['event'],
-        payload: params['data'],
+        event: data['event'],
+        payload: data,
         session: session_data,
         client_id: client_id,
         clients: {},
@@ -41,15 +40,15 @@ class LongPollingController < ApplicationController
         Sessions.send(client_id, message)
       end
     else
-      log "unknown message '#{params['data'].inspect}'", client_id
+      log "unknown message '#{data.inspect}'", client_id
     end
 
     if new_connection
       result = { client_id: client_id }
       render json: result
-    else
-      render json: {}
+      return
     end
+    render json: {}
   end
 
   # GET /api/v1/message_receive
@@ -110,7 +109,7 @@ class LongPollingController < ApplicationController
     params[:client_id].to_s
   end
 
-  def log( data, client_id = '-' )
+  def log(data, client_id = '-')
     logger.info "client(#{client_id}) #{data}"
   end
 end

+ 40 - 1
lib/sessions.rb

@@ -334,6 +334,15 @@ send message to recipient client
 
   Sessions.send_to(user_id, data)
 
+e. g.
+
+  Sessions.send_to(user_id, {
+    event: 'session:takeover',
+    data: {
+      taskbar_id: 12312
+    },
+  })
+
 returns
 
   true|false
@@ -476,6 +485,14 @@ remove all session and spool messages
     true
   end
 
+=begin
+
+create spool messages
+
+  Sessions.spool_create(some: 'data')
+
+=end
+
   def self.spool_create(data)
     msg = JSON.generate(data)
     path = "#{@path}/spool/"
@@ -492,6 +509,14 @@ remove all session and spool messages
     end
   end
 
+=begin
+
+get spool messages
+
+  Sessions.spool_list(junger_then, for_user_id)
+
+=end
+
   def self.spool_list(timestamp, current_user_id)
     path = "#{@path}/spool/"
     FileUtils.mkpath path
@@ -512,6 +537,7 @@ remove all session and spool messages
         file.flock(File::LOCK_SH)
         message = file.read
         file.flock(File::LOCK_UN)
+        message_parsed = {}
         begin
           spool = JSON.parse(message)
           message_parsed = JSON.parse(spool['msg'])
@@ -530,7 +556,7 @@ remove all session and spool messages
         # add spool attribute to push spool info to clients
         message_parsed['spool'] = true
 
-        # only send not already now messages
+        # only send not already older messages
         if !timestamp || timestamp < spool['timestamp']
 
           # spool to recipient list
@@ -573,6 +599,19 @@ remove all session and spool messages
     data
   end
 
+=begin
+
+delete spool messages
+
+  Sessions.spool_delete
+
+=end
+
+  def self.spool_delete
+    path = "#{@path}/spool/"
+    FileUtils.rm_rf path
+  end
+
   def self.jobs(node_id = nil)
 
     # just make sure that spool path exists

+ 1 - 1
lib/sessions/event.rb

@@ -7,7 +7,7 @@ class Sessions::Event
     begin
       backend = load_adapter(adapter)
     rescue => e
-      return { event: 'error', data: { error: "No such event #{params[:event]}", payload: params[:payload] } }
+      return { event: 'error', data: { error: "No such event #{params[:event]}: #{e.inspect}", payload: params[:payload] } }
     end
 
     begin

+ 15 - 1
lib/sessions/event/base.rb

@@ -9,6 +9,10 @@ class Sessions::Event::Base
     return if !@clients[@client_id]
 
     @is_web_socket = true
+
+    return if !self.class.instance_variable_get(:@database_connection)
+
+    ActiveRecord::Base.establish_connection
   end
 
   def websocket_send(recipient_client_id, data)
@@ -120,8 +124,18 @@ class Sessions::Event::Base
     puts "#{Time.now.utc.iso8601}:client(#{client_id}) #{data}"
     #puts "#{Time.now.utc.iso8601}:#{ level }:client(#{ client_id }) #{ data }"
     # rubocop:enable Rails/Output
+    #Rails.logger.info "#{Time.now.utc.iso8601}:client(#{client_id}) #{data}"
+  end
+
+  def self.database_connection_required
+    @database_connection = true
   end
 
-  def destroy; end
+  def destroy
+    return if !@is_web_socket
+    return if !self.class.instance_variable_get(:@database_connection)
+
+    ActiveRecord::Base.remove_connection
+  end
 
 end

+ 10 - 0
lib/sessions/event/broadcast.rb

@@ -1,5 +1,15 @@
 class Sessions::Event::Broadcast < Sessions::Event::Base
 
+=begin
+
+Event module to broadcast messages to all client connections.
+
+To execute this manually, just paste the following into the browser console
+
+  App.WebSocket.send({event:'broadcast', recipient: { user_id: [1,2,3]}, data: {some: 'key'}})
+
+=end
+
   def run
 
     # list all current clients

+ 1 - 13
lib/sessions/event/chat_base.rb

@@ -1,17 +1,5 @@
 class Sessions::Event::ChatBase < Sessions::Event::Base
-
-  def initialize(params)
-    super(params)
-    return if !@is_web_socket
-
-    ActiveRecord::Base.establish_connection
-  end
-
-  def destroy
-    return if !@is_web_socket
-
-    ActiveRecord::Base.remove_connection
-  end
+  database_connection_required
 
   def run
 

+ 11 - 8
lib/sessions/event/login.rb

@@ -1,12 +1,20 @@
 class Sessions::Event::Login < Sessions::Event::Base
+  database_connection_required
+
+=begin
+
+Event module to start websocket session for new client connections.
+
+To execute this manually, just paste the following into the browser console
+
+  App.WebSocket.send({event:'login', session_id: '123'})
+
+=end
 
   def run
 
     # get user_id
     session = nil
-    if @is_web_socket
-      ActiveRecord::Base.establish_connection
-    end
 
     app_version = AppVersion.event_data
 
@@ -14,12 +22,7 @@ class Sessions::Event::Login < Sessions::Event::Base
       session = ActiveRecord::SessionStore::Session.find_by(session_id: @payload['session_id'])
     end
 
-    if @is_web_socket
-      ActiveRecord::Base.remove_connection
-    end
-
     new_session_data = {}
-
     if session&.data && session.data['user_id']
       new_session_data = {
         'id' => session.data['user_id'],

+ 7 - 9
lib/sessions/event/maintenance.rb

@@ -1,17 +1,15 @@
 class Sessions::Event::Maintenance < Sessions::Event::Base
+  database_connection_required
 
-  def initialize(params)
-    super(params)
-    return if !@is_web_socket
+=begin
 
-    ActiveRecord::Base.establish_connection
-  end
+Event module to broadcast maintenance messages to all client connections.
 
-  def destroy
-    return if !@is_web_socket
+To execute this manually, just paste the following into the browser console
 
-    ActiveRecord::Base.remove_connection
-  end
+  App.WebSocket.send({event:'maintenance', data: {some: 'key'}})
+
+=end
 
   def run
 

+ 10 - 0
lib/sessions/event/ping.rb

@@ -1,5 +1,15 @@
 class Sessions::Event::Ping < Sessions::Event::Base
 
+=begin
+
+Event module to send pong to client connection.
+
+To execute this manually, just paste the following into the browser console
+
+  App.WebSocket.send({event:'ping'})
+
+=end
+
   def run
     {
       event: 'pong',

Некоторые файлы не были показаны из-за большого количества измененных файлов