From 27f6076954b596ea079dde52cc36a6bb6a46fdc9 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 4 Jul 2017 11:52:35 -0400 Subject: [PATCH] improvements to active meetings --- app/assets/javascripts/active_meetings.js | 119 +++++++++++++----- app/assets/javascripts/landing.js | 3 +- app/controllers/bbb_controller.rb | 10 +- app/controllers/landing_controller.rb | 19 ++- app/lib/bbb_api.rb | 2 + app/views/landing/wait_for_moderator.html.erb | 8 ++ config/initializers/waiting_list.rb | 35 ++++++ config/routes.rb | 5 +- 8 files changed, 162 insertions(+), 39 deletions(-) create mode 100644 config/initializers/waiting_list.rb diff --git a/app/assets/javascripts/active_meetings.js b/app/assets/javascripts/active_meetings.js index c7b92bb4..e3308791 100644 --- a/app/assets/javascripts/active_meetings.js +++ b/app/assets/javascripts/active_meetings.js @@ -18,6 +18,7 @@ // the landing page using custom Actioncable events. var MEETINGS = {} +var WAITING = {} var LOADING_DELAY = 1750 // milliseconds. var updatePreviousMeetings = function(){ @@ -29,19 +30,44 @@ var updatePreviousMeetings = function(){ }); } -var handleUser = function(data, n){ +var addUser = function(data){ if(data['role'] == 'MODERATOR'){ - MEETINGS[data['meeting']]['moderators'] += n + MEETINGS[data['meeting']]['moderators'].push(data['user']) } else { - MEETINGS[data['meeting']]['participants'] += n + MEETINGS[data['meeting']]['participants'].push(data['user']) } updateMeetingText(MEETINGS[data['meeting']]) } -var updateMeetingText = function(meeting){ - $('#' + meeting['name'].replace(' ', '_')).html('' + meeting['name'] + ' (' + - meeting['participants'] + ((meeting['participants'] == 1) ? ' user, ' : ' users, ') + - meeting['moderators'] + ((meeting['moderators'] == 1) ? ' mod)' : ' mods)')) +var removeUser = function(data){ + if(data['role'] == 'MODERATOR'){ + MEETINGS[data['meeting']]['moderators'].splice(MEETINGS[data['meeting']]['moderators'].indexOf(data['user']), 1); + } else { + MEETINGS[data['meeting']]['participants'].splice(MEETINGS[data['meeting']]['participants'].indexOf(data['user']), 1); + } + updateMeetingText(MEETINGS[data['meeting']]) +} + +var updateMeetingText = function(m){ + if(m.hasOwnProperty('moderators')){ + var body = '' + m['name'] + ': ' + m['moderators'].join('(mod), ') + (m['moderators'].length > 0 ? '(mod)' : '') + + (m['participants'].length > 0 && m['moderators'].length != 0 ? ', ' : '') + m['participants'].join(', ') + '' + } else { + var body = '' + m['name'] + ' (not yet started): ' + + m['users'].join(', ') + '' + } + + if($('#' + m['name'].replace(' ', '_')).length == 0){ + var meeting_item = $('
  • ' + body + '
  • ') + $('.actives').append(meeting_item); + + // Set up join on click. + meeting_item.click(function(){ + joinMeeting(m['name']); + }); + } else { + $('#' + m['name'].replace(' ', '_')).html(body) + } } var initialPopulate = function(){ @@ -49,21 +75,48 @@ var initialPopulate = function(){ var chopped = window.location.href.split('/') if (!window.location.href.includes('rooms') || chopped[chopped.length - 2] == $('body').data('current-user')) { return; } $.get((window.location.href + '/request').replace('#', ''), function(data){ - var meetings = data['meetings'] + var meetings = data['active']['meetings'] + var waiting = data['waiting'] + + jQuery.each(waiting[$('body').data('current-user')], function(name, users){ + WAITING[name] = {'name': name, + 'users': users} + updateMeetingText(WAITING[name]) + }); + for(var i = 0; i < meetings.length; i++){ // Make sure the meeting actually belongs to the current user. if(meetings[i]['metadata']['room-id'] != $('body').data('current-user')) { continue; } var name = meetings[i]['meetingName'] - var participants = parseInt(meetings[i]['participantCount']) - var moderators = parseInt(meetings[i]['moderatorCount']) + + var participants = [] + var moderators = [] + + var attendees; + if(meetings[i]['attendees']['attendee'] instanceof Array){ + attendees = meetings[i]['attendees']['attendee'] + } else { + attendees = [meetings[i]['attendees']['attendee']] + } + + jQuery.each(attendees, function(i, attendee){ + if(attendee['role'] == "MODERATOR"){ + moderators.push(attendee['fullName']) + } else { + participants.push(attendee['fullName']) + } + }); + // Create meeting. MEETINGS[name] = {'name': name, - 'participants': participants - moderators, + 'participants': participants, 'moderators': moderators} + if(isPreviouslyJoined(name)){ - renderActiveMeeting(MEETINGS[name]) + updateMeetingText(MEETINGS[name]) } } + }).done(function(){ // Remove from previous meetings if they are active. updatePreviousMeetings(); @@ -81,18 +134,6 @@ var isPreviouslyJoined = function(meeting){ return joinedMeetings.split(',').indexOf(meeting) >= 0 } -var renderActiveMeeting = function(m){ - var meeting_item = $('
  • ' + m['name'] + '' + - ' (' + m['participants'] + ((m['participants'] == 1) ? ' user, ' : ' users, ') + - m['moderators'] + ((m['moderators'] == 1) ? ' mod)' : ' mods)') + '' + '
  • ') - $('.actives').append(meeting_item); - - // Set up join on click. - meeting_item.click(function(){ - joinMeeting(m['name']); - }); -} - var removeActiveMeeting = function(meeting){ if(meeting){ $('#' + meeting['name'].replace(' ', '_')).remove() @@ -137,20 +178,36 @@ $(document).on('turbolinks:load', function(){ if(data['method'] == 'create'){ // Create an empty meeting. MEETINGS[data['meeting']] = {'name': data['meeting'], - 'participants': 0, - 'moderators': 0} - - renderActiveMeeting(MEETINGS[data['meeting']]) + 'participants': [], + 'moderators': []} + updateMeetingText(MEETINGS[data['meeting']]) updatePreviousMeetings(); + if (WAITING.hasOwnProperty(data['meeting'])){ delete WAITING[data['meeting']]; } } else if(data['method'] == 'destroy'){ removeActiveMeeting(MEETINGS[data['meeting']]) PreviousMeetings.uniqueAdd([data['meeting']]) delete MEETINGS[data['meeting']] } else if(data['method'] == 'join'){ - handleUser(data, 1) - updateMeetingText(MEETINGS[data['meeting']]) + addUser(data) } else if(data['method'] == 'leave'){ - handleUser(data, -1) + removeUser(data) + } else if(data['method'] == 'waiting'){ + // Handle waiting meeting. + if(WAITING.hasOwnProperty(data['meeting'])){ + WAITING[data['meeting']]['users'].push(data['user']) + updateMeetingText(WAITING[data['meeting']]) + } else { + WAITING[data['meeting']] = {'name': data['meeting'], + 'users': [data['user']]} + updateMeetingText(WAITING[data['meeting']]) + } + } else if((data['method'] == 'no_longer_waiting') && (WAITING.hasOwnProperty(data['meeting']))){ + WAITING[data['meeting']]['users'].splice(WAITING[data['meeting']]['users'].indexOf(data['user']), 1) + updateMeetingText(WAITING[data['meeting']]) + if(WAITING[data['meeting']]['users'].length == 0){ + removeActiveMeeting(WAITING[data['meeting']]) + delete WAITING[data['meeting']] + } } } } diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js index 7f293a9b..46759da1 100644 --- a/app/assets/javascripts/landing.js +++ b/app/assets/javascripts/landing.js @@ -17,7 +17,8 @@ (function() { var waitForModerator = function(url) { - $.get(url + "/wait", function(html) { + localStorage.setItem("waitingName", $('.meeting-user-name').val()); + $.post(url + "/wait", {name: $('.meeting-user-name').val()}, function(html) { $(".center-panel-wrapper").html(html); }); if (!Meeting.getInstance().getWaitingForMod()) { diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index e9fe764c..2b74290b 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -287,14 +287,17 @@ class BbbController < ApplicationController actioncable_event('create', params['id'], params['room_id']) elsif eventName == "meeting_destroyed_event" actioncable_event('destroy', params['id'], params['room_id']) + record_id = event['payload']['meeting_id'].split('-')[0] rec_info = bbb_get_recordings({recordID: record_id}) rec_info = rec_info[:recordings].first + + # Remove the webhook. webhook_remove(rec_info[:metadata][:"gl-webhooks-callback-url"]) elsif eventName == "user_joined_message" - actioncable_event('join', params['id'], params['room_id'], event['payload']['user']['role']) + actioncable_event('join', params['id'], params['room_id'], event['payload']['user']['name'], event['payload']['user']['role']) elsif eventName == "user_left_message" - actioncable_event('leave', params['id'], params['room_id'], event['payload']['user']['role']) + actioncable_event('leave', params['id'], params['room_id'], event['payload']['user']['name'], event['payload']['user']['role']) else logger.info "Callback event will not be treated. Event name: #{eventName}" end @@ -302,11 +305,12 @@ class BbbController < ApplicationController render head(:ok) && return end - def actioncable_event(method, id, room_id, role = 'none') + def actioncable_event(method, id, room_id, user = 'none', role = 'none') ActionCable.server.broadcast 'refresh_meetings', method: method, meeting: id, room: room_id, + user: user, role: role end diff --git a/app/controllers/landing_controller.rb b/app/controllers/landing_controller.rb index 4f67eec3..58baeeac 100644 --- a/app/controllers/landing_controller.rb +++ b/app/controllers/landing_controller.rb @@ -34,14 +34,29 @@ class LandingController < ApplicationController end end - def send_data - render json: bbb.get_meetings + def send_meetings_data + render json: {active: bbb.get_meetings, waiting: WaitingList.waiting} end def wait_for_moderator + WaitingList.add(params[:room_id], params[:name], params[:id]) + ActionCable.server.broadcast 'refresh_meetings', + method: 'waiting', + meeting: params[:id], + room: params[:room_id], + user: params[:name] render layout: false end + def no_longer_waiting + WaitingList.remove(params[:room_id], params[:name], params[:id]) + ActionCable.server.broadcast 'refresh_meetings', + method: 'no_longer_waiting', + meeting: params[:id], + room: params[:room_id], + user: params[:name] + end + def session_status_refresh @user = User.find_by(encrypted_id: params[:room_id]) if @user.nil? diff --git a/app/lib/bbb_api.rb b/app/lib/bbb_api.rb index f68a7897..420c5747 100644 --- a/app/lib/bbb_api.rb +++ b/app/lib/bbb_api.rb @@ -100,6 +100,8 @@ module BbbApi rescue BigBlueButton::BigBlueButtonException => exc logger.info "BBB error on create #{exc.key}: #{exc.message}" end + + WaitingList.empty(options[:room_owner], options[:meeting_name]) # And then get meeting info bbb_meeting_info = bbb.get_meeting_info( meeting_id, nil ) diff --git a/app/views/landing/wait_for_moderator.html.erb b/app/views/landing/wait_for_moderator.html.erb index 8d161c27..ba6156f5 100644 --- a/app/views/landing/wait_for_moderator.html.erb +++ b/app/views/landing/wait_for_moderator.html.erb @@ -28,3 +28,11 @@ <%= image_tag "loading-indicator.gif", :alt => "" %> <% end %> + + diff --git a/config/initializers/waiting_list.rb b/config/initializers/waiting_list.rb new file mode 100644 index 00000000..a3c536b4 --- /dev/null +++ b/config/initializers/waiting_list.rb @@ -0,0 +1,35 @@ +# Stores data on waiting users on the server side so +# we can pass it to clients when they reload the page. + +class WaitingList + @waiting = {} + + def self.waiting + @waiting + end + + def self.add(room, user, meeting) + @waiting[room] = {} unless @waiting.has_key?(room) + @waiting[room][meeting] = [] unless @waiting[room].has_key?(meeting) + @waiting[room][meeting] << user + end + + def self.remove(room, user, meeting) + if @waiting.has_key?(room) then + if @waiting[room].has_key?(meeting) then + @waiting[room][meeting].slice!(@waiting[room][meeting].index(user)) + @waiting[room].delete(meeting) if @waiting[room][meeting].length == 0 + @waiting.delete(room) if @waiting[room].length == 0 + end + end + end + + def self.empty(room, meeting) + if @waiting.has_key?(room) then + if @waiting[room].has_key?(meeting) then + @waiting[room].delete(meeting) + @waiting.delete(room) if @waiting[room].length == 0 + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 68166c0c..1bf019f7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,13 +44,14 @@ Rails.application.routes.draw do delete '/(:id)/recordings/:record_id', to: 'bbb#delete_recordings', defaults: {id: nil, format: 'json'}, :constraints => {:id => disallow_slash} delete '/:id/end', to: 'bbb#end', defaults: {format: 'json'}, :constraints => {:id => disallow_slash} - get '/:id/wait', to: 'landing#wait_for_moderator', :constraints => {:id => disallow_slash} + post '/:id/wait', to: 'landing#wait_for_moderator', :constraints => {:id => disallow_slash} + post '/:id/no_longer_wait', to: 'landing#no_longer_waiting', :constraints => {:id => disallow_slash} get '/:id/session_status_refresh', to: 'landing#session_status_refresh', :constraints => {:id => disallow_slash} end post '/:room_id/:id/callback', to: 'bbb#callback', :constraints => {:id => disallow_slash, :room_id => disallow_slash} # routes shared between meetings and rooms - get '/(:room_id)/request', to: 'landing#send_data', :defaults => { :format => 'xml' } + get '/(:room_id)/request', to: 'landing#send_meetings_data', :defaults => { :format => 'xml' } get '/(:room_id)/:id/join', to: 'bbb#join', defaults: {room_id: nil, format: 'json'}, :constraints => {:id => disallow_slash, :room_id => disallow_slash} get '/(:room_id)/:id', to: 'landing#resource', as: :meeting_room, defaults: {room_id: nil}, :constraints => {:id => disallow_slash, :room_id => disallow_slash} end