diff --git a/Gemfile b/Gemfile index b5df803e..715bf098 100644 --- a/Gemfile +++ b/Gemfile @@ -89,3 +89,6 @@ gem 'yt', '~> 0.28.0' # Simple HTTP client. gem 'faraday' + +# For device detection to determine BigBlueButton client. +gem 'browser' diff --git a/Gemfile.lock b/Gemfile.lock index 148fa847..f542f067 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -46,6 +46,7 @@ GEM sass (~> 3.2) bootstrap-social-rails (4.12.0) railties (>= 3.1) + browser (2.5.1) builder (3.2.3) byebug (9.0.6) climate_control (0.2.0) @@ -279,6 +280,7 @@ DEPENDENCIES bigbluebutton-api-ruby bootstrap-sass (= 3.3.0.0) bootstrap-social-rails (~> 4.12) + browser byebug coffee-rails (~> 4.2) dotenv-rails diff --git a/app/assets/javascripts/active_meetings.js b/app/assets/javascripts/active_meetings.js index aef51e6a..68215234 100644 --- a/app/assets/javascripts/active_meetings.js +++ b/app/assets/javascripts/active_meetings.js @@ -117,8 +117,8 @@ var initialPopulate = function(){ // Populate waiting meetings. Object.keys(data['waiting']).forEach(function(key) { - WAITING[name] = {'name': key, 'users': data['waiting'][key]} - updateMeetingText(WAITING[name]) + WAITING[key] = {'name': key, 'users': data['waiting'][key]} + updateMeetingText(WAITING[key]) }) // Add the meetings to the active meetings list. diff --git a/app/assets/javascripts/channels/meeting_updates.js b/app/assets/javascripts/channels/meeting_updates.js index 505cf143..4580490e 100644 --- a/app/assets/javascripts/channels/meeting_updates.js +++ b/app/assets/javascripts/channels/meeting_updates.js @@ -55,6 +55,8 @@ body: I18n.user_waiting_body.replace(/%{user}/, data.user).replace(/%{meeting}/, '"'+data.meeting_name+'"') }); } + } else if(data.action === 'unable_to_join') { + showAlert(I18n.unable_to_join_mobile, 6000) } } }); diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/landing.js index 92857f35..1f59002e 100644 --- a/app/assets/javascripts/landing.js +++ b/app/assets/javascripts/landing.js @@ -60,7 +60,7 @@ jqxhr.done(function(data) { if (data.messageKey === 'wait_for_moderator') { waitForModerator(Meeting.getInstance().getURL()); - } else { + } else if (data.messageKey === 'ok') { $(location).attr("href", data.response.join_url); } }); diff --git a/app/assets/javascripts/shared.js.erb b/app/assets/javascripts/shared.js.erb index 0a5f3e1b..7af47440 100644 --- a/app/assets/javascripts/shared.js.erb +++ b/app/assets/javascripts/shared.js.erb @@ -69,7 +69,8 @@ var showNotification = function(title, options) { if (Notification.permission === "granted") { var icon = '<%= asset_path("bbb-logo.png") %>'; options = $.extend(options, { - icon: icon + icon: icon, + tag: 'UserWaiting' }); var notification = new Notification(title, options); notification.onclick = function() { diff --git a/app/controllers/bbb_controller.rb b/app/controllers/bbb_controller.rb index 01491635..a87457f0 100644 --- a/app/controllers/bbb_controller.rb +++ b/app/controllers/bbb_controller.rb @@ -93,7 +93,10 @@ class BbbController < ApplicationController if bbb_res[:returncode] && current_user == user JoinMeetingJob.perform_later(user, params[:id], base_url) WaitingList.empty(options[:room_owner], options[:meeting_name]) - # user will be waiting for a moderator + # the user can't join because they are on mobile and HTML5 is not enabled. + elsif bbb_res[:messageKey] == 'unable_to_join' + NotifyUserCantJoinJob.perform_later(user.encrypted_id, params[:id], params[:name]) + # user will be waiting for a moderator else NotifyUserWaitingJob.perform_later(user.encrypted_id, params[:id], params[:name]) end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8e5be0b3..f8866ca1 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -19,13 +19,19 @@ class UsersController < ActionController::Base # For updating a users background image. def update - # Make sure they actually select a file. - if params[:user] then + if params[:commit] == t('upload') + # Make sure they actually select a file. + if params[:user] then + @user = User.find(params[:id]) + @user.assign_attributes(background: user_params[:background]) + flash[:danger] = t('invalid_file') unless @user.save + else + flash[:danger] = t('no_file') + end + elsif params[:commit] == t('switch_clients') + # Switch the users default client. @user = User.find(params[:id]) - @user.assign_attributes(background: user_params[:background]) - flash[:danger] = t('invalid_file') unless @user.save - else - flash[:danger] = t('no_file') + @user.update_attributes(use_html5: !@user.use_html5) end # Reload the page to apply changes and show flash messages. diff --git a/app/helpers/landing_helper.rb b/app/helpers/landing_helper.rb index b2b3e74a..4c74f406 100644 --- a/app/helpers/landing_helper.rb +++ b/app/helpers/landing_helper.rb @@ -16,4 +16,8 @@ module LandingHelper + def html5_enabled? + Rails.configuration.html5_enabled + end + end diff --git a/app/jobs/notify_user_cant_join_job.rb b/app/jobs/notify_user_cant_join_job.rb new file mode 100644 index 00000000..69da5dfe --- /dev/null +++ b/app/jobs/notify_user_cant_join_job.rb @@ -0,0 +1,24 @@ +# 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 NotifyUserCantJoinJob < ApplicationJob + queue_as :default + + def perform(room, meeting, user) + payload = { action: 'unable_to_join', user: user, meeting_name: meeting } + ActionCable.server.broadcast "#{room}-#{meeting}_meeting_updates_channel", payload + end +end diff --git a/app/lib/bbb_api.rb b/app/lib/bbb_api.rb index d0e3d89c..fccf0216 100644 --- a/app/lib/bbb_api.rb +++ b/app/lib/bbb_api.rb @@ -53,71 +53,109 @@ module BbbApi if !bbb return call_invalid_res else - meeting_id = bbb_meeting_id(meeting_token) + # Verify HTML5 is running. + html5_running = Rails.configuration.html5_enabled + + # Determine if the user is on mobile. + browser = Browser.new(request.user_agent) + mobile = browser.device.tablet? + + # If the user is on mobile and the BigBlueButton server isn't running the HTML5 client, + # they can't join or create meetings. + if mobile and !html5_running then + return unable_to_join_res + else + meeting_id = bbb_meeting_id(meeting_token) - # See if the meeting is running - begin - bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil) - rescue BigBlueButton::BigBlueButtonException => exc - # This means that is not created + # See if the meeting is running + begin + bbb_meeting_info = bbb.get_meeting_info(meeting_id, nil) + rescue BigBlueButton::BigBlueButtonException => exc + # This means that is not created - if options[:wait_for_moderator] && !options[:user_is_moderator] + if options[:wait_for_moderator] && !options[:user_is_moderator] + return wait_moderator_res + end + + logger.info "Message for the log file #{exc.key}: #{exc.message}" + + # Prepare parameters for create + logout_url = options[:meeting_logout_url] || "#{request.base_url}" + moderator_password = random_password(12) + viewer_password = random_password(12) + meeting_options = { + record: options[:meeting_recorded].to_s, + logoutURL: logout_url, + moderatorPW: moderator_password, + attendeePW: viewer_password, + moderatorOnlyMessage: options[:moderator_message], + "meta_#{BbbApi::META_LISTED}": false, + "meta_#{BbbApi::META_TOKEN}": meeting_token + } + + meeting_options.merge!( + { "meta_#{BbbApi::META_HOOK_URL}": options[:hook_url] } + ) if options[:hook_url] + + # these parameters are used to filter recordings by room and meeting + meeting_options.merge!( + { "meta_room-id": options[:room_owner], + "meta_meeting-name": options[:meeting_name]} + ) if options[:room_owner] + + # Only register webhooks if they are enabled it's not a guest meeting. + if Rails.configuration.use_webhooks && params[:resource] == 'rooms' + webhook_register(options[:hook_url], meeting_id) + end + + # Create the meeting + begin + bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options) + rescue BigBlueButton::BigBlueButtonException => exc + logger.info "BBB error on create #{exc.key}: #{exc.message}" + end + + # And then get meeting info + bbb_meeting_info = bbb.get_meeting_info( meeting_id, nil ) + end + + if options[:wait_for_moderator] && !options[:user_is_moderator] && bbb_meeting_info[:moderatorCount] <= 0 return wait_moderator_res end - logger.info "Message for the log file #{exc.key}: #{exc.message}" - - # Prepare parameters for create - logout_url = options[:meeting_logout_url] || "#{request.base_url}" - moderator_password = random_password(12) - viewer_password = random_password(12) - meeting_options = { - record: options[:meeting_recorded].to_s, - logoutURL: logout_url, - moderatorPW: moderator_password, - attendeePW: viewer_password, - moderatorOnlyMessage: options[:moderator_message], - "meta_#{BbbApi::META_LISTED}": false, - "meta_#{BbbApi::META_TOKEN}": meeting_token - } - meeting_options.merge!( - { "meta_#{BbbApi::META_HOOK_URL}": options[:hook_url] } - ) if options[:hook_url] - - # these parameters are used to filter recordings by room and meeting - meeting_options.merge!( - { "meta_room-id": options[:room_owner], - "meta_meeting-name": options[:meeting_name]} - ) if options[:room_owner] - - # Only register webhooks if they are enabled it's not a guest meeting. - if Rails.configuration.use_webhooks && params[:resource] == 'rooms' - webhook_register(options[:hook_url], meeting_id) + # Get the join url + if (options[:user_is_moderator]) + password = bbb_meeting_info[:moderatorPW] + else + password = bbb_meeting_info[:attendeePW] + end + + # Determine which client to join as. + if current_user.nil? + use_html5 = Rails.configuration.use_html5_by_default + else + use_html5 = current_user.use_html5 + end + + # Restrict client if needed. + if mobile && html5_running + # Must use HTML5 because they are on mobile. + use_html5 = true + elsif !mobile && !html5_running + # HTML5 is not running, so must use Flash. + use_html5 = false + end + + # Generate the join URL. + if use_html5 + clientURL = bbb_endpoint.gsub('bigbluebutton/', 'html5client/join') + join_url = bbb.join_meeting_url(meeting_id, full_name, password, {clientURL: clientURL}) + else + join_url = bbb.join_meeting_url(meeting_id, full_name, password) end - # Create the meeting - begin - bbb.create_meeting(options[:meeting_name], meeting_id, meeting_options) - rescue BigBlueButton::BigBlueButtonException => exc - logger.info "BBB error on create #{exc.key}: #{exc.message}" - end - - # And then get meeting info - bbb_meeting_info = bbb.get_meeting_info( meeting_id, nil ) + return success_join_res(join_url) end - - if options[:wait_for_moderator] && !options[:user_is_moderator] && bbb_meeting_info[:moderatorCount] <= 0 - return wait_moderator_res - end - - # Get the join url - if (options[:user_is_moderator]) - password = bbb_meeting_info[:moderatorPW] - else - password = bbb_meeting_info[:attendeePW] - end - join_url = bbb.join_meeting_url(meeting_id, full_name, password ) - return success_join_res(join_url) end end @@ -300,6 +338,15 @@ module BbbApi } } end + + def unable_to_join_res + { + returncode: false, + messageKey: "unable_to_join", + message: "Unable to join.", + status: :ok + } + end def wait_moderator_res { diff --git a/app/views/landing/preferences.html.erb b/app/views/landing/preferences.html.erb index 542d8d3a..3871cb75 100644 --- a/app/views/landing/preferences.html.erb +++ b/app/views/landing/preferences.html.erb @@ -23,6 +23,7 @@ <% if @user %>
+

<%= t('background_image') %>

<%= t('background_image') + ": " + (@user.background_file_name || '') %>

<%= form_for @user, :html => { :multipart => true } do |f| %>
@@ -31,6 +32,18 @@ <%= f.submit t('upload'), class: 'btn btn-info' %> <% end %>
+
+
+

<%= t('prefered_client') %>

+ <% if html5_enabled? %> +

<%= t('currently_joining_with', client: @user.use_html5 ? t('client_html5') : t('client_flash')) %>

+ <%= form_for @user do |f| %> + <%= f.submit t('switch_clients'), class: 'btn btn-info' %> + <% end %> + <% else %> +

<%= t('html5_not_enabled') %>

+ <% end %> +
<% else %>

<%= t('preferences_logged') %>

<% end %> diff --git a/config/application.rb b/config/application.rb index 7102d908..37390904 100644 --- a/config/application.rb +++ b/config/application.rb @@ -48,6 +48,7 @@ module Greenlight config.disable_guest_access = ENV['DISABLE_GUEST_ACCESS'] && ENV['DISABLE_GUEST_ACCESS'] == "true" config.enable_youtube_uploading = ENV['ENABLE_YOUTUBE_UPLOADING'] && ENV['ENABLE_YOUTUBE_UPLOADING'] == "true" config.enable_qrcode_generation = ENV['ENABLE_QRCODE_GENERATION'] && ENV['ENABLE_QRCODE_GENERATION'] == "true" + config.use_html5_by_default = ENV['USE_HTML5_BY_DEFAULT'] == "true" # SMTP and action mailer if config.mail_notifications diff --git a/config/initializers/html5_client.rb b/config/initializers/html5_client.rb new file mode 100644 index 00000000..37fdd8de --- /dev/null +++ b/config/initializers/html5_client.rb @@ -0,0 +1,19 @@ +# 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 . + +# Send a request to check if the HTML5 client is enabled on the BigBlueButton server. +res = Faraday.get(Rails.configuration.bigbluebutton_endpoint.gsub('bigbluebutton/', 'html5client/check')) +Rails.application.config.html5_enabled = res.status == 200 diff --git a/config/locales/en-us.yml b/config/locales/en-us.yml index 15a5de77..d7e2486e 100644 --- a/config/locales/en-us.yml +++ b/config/locales/en-us.yml @@ -70,14 +70,18 @@ en-US: recording_unpublished: Recording was unpublished share: Share successful_upload: Recording successfully uploaded to Youtube! + unable_to_join_mobile: This BigBlueButton server does not use the HTML5 client, which means you are unable to join on mobile. unpublish_recording: Hide recording unlisted: Unlisted unpublished: No one upload_youtube: Youtube user_waiting_body: "%{user} is waiting to join %{meeting}!" user_waiting_title: A user is waiting + client_html5: HTML5 + client_flash: Flash copied: Copied copy_error: Use Ctrl-c to copy + currently_joining_with: Currently prefering the %{client} client. create_your_session: Create your own meeting date_recorded: Date disallowed_characters_msg: Characters not allowed in meeting name $&, @@ -94,6 +98,7 @@ en-US: hi_all: Hi Everyone home_page: Home page home_title: Welcome to BigBlueButton + html5_not_enabled: This server is not HTML5 enabled, so you must use the Flash client. invalid_file: You may only upload an image file (jpg, gif, png). invalid_login: Invalid log in credentials. invite: Invite @@ -157,6 +162,7 @@ en-US: past_recordings: Past Recordings preferences: Preferences preferences_logged: You must be logged in to edit preferences. + prefered_client: Prefered Client presentation: Presentation previous_meetings: (previous meetings) qrcode: @@ -176,6 +182,7 @@ en-US: start: Start start_join: Start start_meeting: Start meeting + switch_clients: Switch Clients test_mailer: test_email: subject: Test Email diff --git a/db/migrate/20170928183010_add_use_html5_to_users.rb b/db/migrate/20170928183010_add_use_html5_to_users.rb new file mode 100644 index 00000000..4518ea3f --- /dev/null +++ b/db/migrate/20170928183010_add_use_html5_to_users.rb @@ -0,0 +1,5 @@ +class AddUseHtml5ToUsers < ActiveRecord::Migration[5.0] + def change + add_column :users, :use_html5, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 4aff0bb8..b35d0051 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,22 +10,23 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170518190442) do +ActiveRecord::Schema.define(version: 20170928183010) do create_table "users", force: :cascade do |t| - t.string "provider", null: false - t.string "uid", null: false + t.string "provider", null: false + t.string "uid", null: false t.string "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "username" - t.string "encrypted_id", null: false + t.string "encrypted_id", null: false t.string "email" t.string "background_file_name" t.string "background_content_type" t.integer "background_file_size" t.datetime "background_updated_at" t.string "token" + t.boolean "use_html5", default: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["encrypted_id"], name: "index_users_on_encrypted_id", unique: true t.index ["provider", "uid"], name: "index_users_on_provider_and_uid", unique: true diff --git a/sample.env b/sample.env index b3781cb0..2fdb1dc2 100644 --- a/sample.env +++ b/sample.env @@ -23,6 +23,17 @@ SECRET_KEY_BASE= # BIGBLUEBUTTON_ENDPOINT= # BIGBLUEBUTTON_SECRET= +# Set default client (optional) +# +# By default, GreenLight will join users to a BigBlueButton session using the Flash +# client. By setting USE_HTML5_BY_DEFAULT to true, you can configure GreenLight to +# use the HTML5 client by default. GreenLight will only use the default for +# non-authenticated users. Users who have authenticated are able to set their +# prefered client under preferences. Also note that if the HTML5 client is not +# running on a BigBlueButton server, all clients will be joined using Flash +# regardless on any settings. +USE_HTML5_BY_DEFAULT=false + # Twitter Login Provider (optional) # # You will need to register the app at https://apps.twitter.com/ diff --git a/scripts/greenlight_recording_notify.rb b/scripts/greenlight_recording_notify.rb index 470bfa0e..682b925c 100755 --- a/scripts/greenlight_recording_notify.rb +++ b/scripts/greenlight_recording_notify.rb @@ -49,7 +49,7 @@ def getParticipantsInfo(events_xml) end def get_id_from_event(event) - (event.xpath('externalUserId').empty?) ? event.xpath('userId').text.split('_')[0] : event.xpath('externalUserId').text + (event.xpath('externalUserId').empty?) ? event.xpath('userId').text : event.xpath('externalUserId').text end # Gets the join and leave times for each user, as well as total duration of stay.