diff --git a/app/assets/javascripts/active_meetings.js b/app/assets/javascripts/active_meetings.js new file mode 100644 index 00000000..ef4029a1 --- /dev/null +++ b/app/assets/javascripts/active_meetings.js @@ -0,0 +1,159 @@ +// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. +// +// Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below). +// +// This program is free software; you can redistribute it and/or modify it under the +// terms of the GNU Lesser General Public License as published by the Free Software +// Foundation; either version 3.0 of the License, or (at your option) any later +// version. +// +// BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License along +// with BigBlueButton; if not, see . + +// Handles live updating and initial population of the previous meetings and active meetings lists on +// the landing page using custom Actioncable events. + +MEETINGS = {} +LOADING_DELAY = 1250 // milliseconds. + +updatePreviousMeetings = function(){ + $("ul.previously-joined li").each(function(idx, li) { + previous_meeting = $(li); + if(Object.keys(MEETINGS).indexOf(previous_meeting.text()) > -1){ + previous_meeting.remove() + } + }); +} + +handleUser = function(data, n){ + if(data['role'] == 'MODERATOR'){ + MEETINGS[data['meeting']]['moderators'] += n + } else { + MEETINGS[data['meeting']]['participants'] += n + } + updateMeetingText(MEETINGS[data['meeting']]) +} + +updateMeetingText = function(meeting){ + $('#' + meeting['name'].replace(' ', '_')).html('' + meeting['name'] + ' (' + + meeting['participants'] + ((meeting['participants'] == 1) ? ' user, ' : ' users, ') + + meeting['moderators'] + ((meeting['moderators'] == 1) ? ' mod)' : ' mods)')) +} + +initialPopulate = function(){ + $.get(window.location.origin + '/rooms/' + $('body').data('current-user') + '/request', function(data){ + meetings = data['meetings'] + for(var i = 0; i < meetings.length; i++){ + name = meetings[i]['meetingName'] + participants = parseInt(meetings[i]['participantCount']) + moderators = parseInt(meetings[i]['moderatorCount']) + // Create meeting. + MEETINGS[name] = {'name': name, + 'participants': participants - moderators, + 'moderators': moderators} + if(isPreviouslyJoined(name)){ + renderActiveMeeting(MEETINGS[name]) + } + } + }).done(function(){ + // Remove from previous meetings if they are active. + updatePreviousMeetings(); + $('.hidden-list').show(); + $('.fa-spinner').hide(); + }); +} + +isPreviouslyJoined = function(meeting){ + joinedMeetings = localStorage.getItem('joinedRooms-' + $('body').data('current-user')).split(','); + return joinedMeetings.indexOf(meeting) >= 0 +} + +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(name); + }); +} + +removeActiveMeeting = function(meeting){ + $('#' + meeting['name'].replace(' ', '_')).remove() +} + +// Directly join a meeting from active meetings. +joinMeeting = function(meeting_name){ + var name = $('.meeting-user-name').val(); + Meeting.getInstance().setUserName(localStorage.getItem('lastJoinedName')); + Meeting.getInstance().setMeetingId(meeting_name); + + // a user name is set, join the user into the session + if (name !== undefined && name !== null) { + var jqxhr = Meeting.getInstance().getJoinMeetingResponse(); + if (jqxhr) { + jqxhr.done(function(data) { + if (data.messageKey === 'wait_for_moderator') { + waitForModerator(Meeting.getInstance().getURL()); + } else { + $(location).attr("href", data.response.join_url); + } + }); + jqxhr.fail(function(xhr, status, error) { + console.info("meeting join failed"); + }); + } else { + $('.meeting-user-name').parent().addClass('has-error'); + } + + // if not user name was set it means we must ask for a name + } else { + $(location).attr("href", Meeting.getInstance().getURL()); + } +} + +// Only need to register for logged in users. +$(document).on('turbolinks:load', function(){ + if($('body').data('current-user')){ + + MEETINGS = {} + $('.actives').empty(); + + if(!App.messages){ + App.messages = App.cable.subscriptions.create('RefreshMeetingsChannel', { + received: function(data) { + console.log('Recieved ' + data['method'] + ' action for ' + data['meeting'] + '.') + if(isPreviouslyJoined(data['meeting'])){ + if(data['method'] == 'create'){ + // Create an empty meeting. + MEETINGS[data['meeting']] = {'name': data['meeting'], + 'participants': 0, + 'moderators': 0} + + renderActiveMeeting(MEETINGS[data['meeting']]) + updatePreviousMeetings(); + } else if(data['method'] == 'destroy'){ + removeActiveMeeting(MEETINGS[data['meeting']]) + PreviousMeetings.append([data['meeting']]) + delete MEETINGS[data['meeting']] + } else if(data['method'] == 'join'){ + handleUser(data, 1) + updateMeetingText(MEETINGS[data['meeting']]) + } else if(data['method'] == 'leave'){ + handleUser(data, -1) + } + } + } + }); + } + + console.log('Populating active meetings.'); + setTimeout(initialPopulate, LOADING_DELAY); + } +}); diff --git a/app/assets/stylesheets/main/landing.scss b/app/assets/stylesheets/main/landing.scss index 41324241..664d8524 100644 --- a/app/assets/stylesheets/main/landing.scss +++ b/app/assets/stylesheets/main/landing.scss @@ -19,7 +19,7 @@ width: 100px; } -.previously-joined { +.previously-joined, .actives { list-style-type: none; margin:auto; width: 200px; @@ -90,6 +90,11 @@ } } +.fa-spinner { + width: 100%; + text-align: center; +} + .youtube-red { color: red; } diff --git a/app/channels/refresh_meetings_channel.rb b/app/channels/refresh_meetings_channel.rb new file mode 100644 index 00000000..ebb973ff --- /dev/null +++ b/app/channels/refresh_meetings_channel.rb @@ -0,0 +1,21 @@ +# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/. +# +# Copyright (c) 2016 BigBlueButton Inc. and by respective authors (see below). +# +# This program is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free Software +# Foundation; either version 3.0 of the License, or (at your option) any later +# version. +# +# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with BigBlueButton; if not, see . + +class RefreshMeetingsChannel < ApplicationCable::Channel + def subscribed + stream_from "refresh_meetings" + end +end diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index e7a84564..9ab37f79 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -111,8 +111,8 @@ class BbbController < ApplicationController head(:ok) && return unless validate_checksum begin - data = JSON.parse(read_body(request)) - treat_callback_event(data["event"]) + data = JSON.parse(params['event']) + treat_callback_event(data) rescue Exception => e logger.error "Error parsing webhook data. Data: #{data}, exception: #{e.inspect}" @@ -277,6 +277,15 @@ class BbbController < ApplicationController else logger.error "Bad format for event #{event}, won't process" end + elsif eventName == "meeting_created_message" + # Fire an Actioncable event that updates _previously_joined for the client. + actioncable_event('create', params['id']) + elsif eventName == "meeting_destroyed_event" + actioncable_event('destroy', params['id']) + elsif eventName == "user_joined_message" + actioncable_event('join', params['id'], event['payload']['user']['role']) + elsif eventName == "user_left_message" + actioncable_event('leave', params['id'], event['payload']['user']['role']) else logger.info "Callback event will not be treated. Event name: #{eventName}" end @@ -284,15 +293,32 @@ class BbbController < ApplicationController render head(:ok) && return end + def actioncable_event(method, id, role = 'none') + ActionCable.server.broadcast 'refresh_meetings', + method: method, + meeting: id, + role: role + end + # Validates the checksum received in a callback call. # If the checksum doesn't match, renders an ok and aborts execution. def validate_checksum secret = ENV['BIGBLUEBUTTON_SECRET'] checksum = params["checksum"] - data = read_body(request) + return false unless checksum + + # Decode and break the body into parts. + parts = URI.decode_www_form(read_body(request)) + + # Convert the data into the correct checksum format, replace ruby hash arrows. + converted_data = {parts[0][0]=>parts[0][1],parts[1][0]=>parts[1][1].to_i}.to_s.gsub!('=>', ':') + + # Manually remove the space between the two elements. + converted_data[converted_data.rindex("timestamp") - 2] = '' + callback_url = uri_remove_param(request.original_url, "checksum") - checksum_str = "#{callback_url}#{data}#{secret}" + checksum_str = "#{callback_url}#{converted_data}#{secret}" calculated_checksum = Digest::SHA1.hexdigest(checksum_str) if calculated_checksum != checksum diff --git a/app/controllers/landing_controller.rb b/app/controllers/landing_controller.rb index bcc83bd1..4f67eec3 100644 --- a/app/controllers/landing_controller.rb +++ b/app/controllers/landing_controller.rb @@ -34,6 +34,10 @@ class LandingController < ApplicationController end end + def send_data + render json: bbb.get_meetings + end + def wait_for_moderator render layout: false end diff --git a/app/views/landing/_previously_joined.html.erb b/app/views/landing/_previously_joined.html.erb index 77470322..11d38c1a 100644 --- a/app/views/landing/_previously_joined.html.erb +++ b/app/views/landing/_previously_joined.html.erb @@ -13,9 +13,36 @@ # with BigBlueButton; if not, see . %> -