From 7661fc5e395932c60b9fc34a9134b9721c4374e6 Mon Sep 17 00:00:00 2001 From: Seth Call Date: Fri, 7 Feb 2014 21:28:47 +0000 Subject: [PATCH] * VRFS-1082 and VRFS-1083 - centralize facebook logic for invitationDialog, and add twitter integration into share dialog --- admin/config/application.rb | 13 ++ admin/config/environments/test.rb | 6 + ruby/lib/jam_ruby/models/invited_user.rb | 6 +- ruby/spec/support/utilities.rb | 16 ++ web/app/assets/javascripts/facebook_helper.js | 10 ++ .../assets/javascripts/invitationDialog.js | 143 ++++++------------ web/app/assets/javascripts/jam_rest.js | 10 ++ web/app/assets/javascripts/layout.js | 1 - web/app/assets/javascripts/shareDialog.js | 82 +++++----- .../assets/javascripts/web/signinDialog.js | 2 + .../stylesheets/client/shareDialog.css.scss | 6 + .../controllers/api_twitters_controller.rb | 4 + web/app/views/clients/index.html.erb | 3 +- web/app/views/layouts/web.erb | 2 + web/config/application.rb | 5 + 15 files changed, 169 insertions(+), 140 deletions(-) diff --git a/admin/config/application.rb b/admin/config/application.rb index 9e768f775..b3a1218c4 100644 --- a/admin/config/application.rb +++ b/admin/config/application.rb @@ -75,6 +75,13 @@ module JamAdmin # to make active_admin assets precompile config.assets.precompile += ['active_admin.css', 'active_admin.js', 'active_admin/print.css'] + ###### THESE ARE JAM-WEB VALUES + config.external_hostname = ENV['EXTERNAL_HOSTNAME'] || 'localhost' + config.external_port = ENV['EXTERNAL_PORT'] || 3000 + config.external_protocol = ENV['EXTERNAL_PROTOCOL'] || 'http://' + config.external_root_url = "#{config.external_protocol}#{config.external_hostname}#{(config.external_port == 80 || config.external_port == 443) ? '' : ':' + config.external_port.to_s}" + + # set to false to instead use amazon. You will also need to supply amazon secrets config.store_artifacts_to_disk = false config.storage_type = :fog @@ -101,5 +108,11 @@ module JamAdmin config.email_smtp_user_name = 'jamkazam' config.email_smtp_password = 'jamjamblueberryjam' config.email_smtp_starttls_auto = true + + config.facebook_app_id = ENV['FACEBOOK_APP_ID'] || '468555793186398' + config.facebook_app_secret = ENV['FACEBOOK_APP_SECRET'] || '546a5b253972f3e2e8b36d9a3dd5a06e' + + config.twitter_app_id = ENV['TWITTER_APP_ID'] || 'nQj2oEeoJZxECC33tiTuIg' + config.twitter_app_secret = ENV['TWITTER_APP_SECRET'] || 'Azcy3QqfzYzn2fsojFPYXcn72yfwa0vG6wWDrZ3KT8' end end diff --git a/admin/config/environments/test.rb b/admin/config/environments/test.rb index 79a74c4ac..6a51a23eb 100644 --- a/admin/config/environments/test.rb +++ b/admin/config/environments/test.rb @@ -34,4 +34,10 @@ JamAdmin::Application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr + + config.facebook_app_id = '1441492266082868' + config.facebook_app_secret = '233bd040a07e47dcec1cff3e490bfce7' + + config.twitter_app_id = 'e7hGc71gmcBgo6Wvdta6Sg' + config.twitter_app_secret = 'PfG1jAUMnyrimPcDooUVQaJrG1IuDjUyGg5KciOo' end diff --git a/ruby/lib/jam_ruby/models/invited_user.rb b/ruby/lib/jam_ruby/models/invited_user.rb index cebc0d25e..eb43e1ff5 100644 --- a/ruby/lib/jam_ruby/models/invited_user.rb +++ b/ruby/lib/jam_ruby/models/invited_user.rb @@ -64,11 +64,7 @@ module JamRuby end def generate_signup_url - if 'development'==Rails.env - "http://localhost:3000/signup?invitation_code=#{self.invitation_code}" - else - "http://www.jamkazam.com/signup?invitation_code=#{self.invitation_code}" - end + "#{APP_CONFIG.external_root_url}/signup?invitation_code=#{self.invitation_code}" end def facebook_invite? diff --git a/ruby/spec/support/utilities.rb b/ruby/spec/support/utilities.rb index 8886ac7bb..6bbcbacdb 100644 --- a/ruby/spec/support/utilities.rb +++ b/ruby/spec/support/utilities.rb @@ -61,6 +61,22 @@ def app_config 5672 end + def external_hostname + 'localhost' + end + + def external_protocol + 'http://' + end + + def external_port + 3000 + end + + def external_root_url + "#{external_protocol}#{external_hostname}#{(external_port == 80 || external_port == 443) ? '' : ':' + external_port.to_s}" + end + private diff --git a/web/app/assets/javascripts/facebook_helper.js b/web/app/assets/javascripts/facebook_helper.js index 7c6db00e2..b1ac43e07 100644 --- a/web/app/assets/javascripts/facebook_helper.js +++ b/web/app/assets/javascripts/facebook_helper.js @@ -8,6 +8,15 @@ var $self = $(this); var connected = false; + function deferredLoginStatus() { + return loginStatusDeferred; + } + + // wrap calls in FB JS API calls in this + // if the user is not logged in, they will be prompted to log in to facebook + // if they are already logged in, the deferred object is immediately returned + // returns a deferred object that never calls .fail(). Just call .done(function(response)), + // and check: if(response && response.status == "connected") to know if you should carry on wih the FB JS API call function promptLogin() { if(connected) { @@ -67,6 +76,7 @@ this.initialize = initialize; this.promptLogin = promptLogin; + this.deferredLoginStatus = deferredLoginStatus; }; diff --git a/web/app/assets/javascripts/invitationDialog.js b/web/app/assets/javascripts/invitationDialog.js index 044b54bbf..f337b690d 100644 --- a/web/app/assets/javascripts/invitationDialog.js +++ b/web/app/assets/javascripts/invitationDialog.js @@ -6,7 +6,8 @@ var rest = context.JK.Rest(); var waitForUserToStopTypingTimer; var sendingEmail = false; - var fbInviteURL_ = null; + var deferredFbInvite = null; + var facebookHelper = null; function trackMetrics(emails, googleInviteCount) { var allInvitations = emails.length; // all email invites, regardless of how they got in the form @@ -17,6 +18,15 @@ } } + function createFbInvite() { + + if(deferredFbInvite == null || deferredFbInvite.isRejected()) { + deferredFbInvite = rest.createFbInviteUrl(); + } + + return deferredFbInvite; + } + function filterInvitations() { waitForUserToStopTypingTimer = null; @@ -156,107 +166,46 @@ ////////////// // FB handlers - // Additional initialization code such as adding Event Listeners goes here - function handle_fblogin_response(response) { - if (response.status === 'connected') { - // the user is logged in and has authenticated your - // app, and response.authResponse supplies - // the user's ID, a valid access token, a signed - // request, and the time the access token - // and signed request each expire - var uid = response.authResponse.userID; - var accessToken = response.authResponse.accessToken; - window.fb_logged_in_state = "connected"; - } else if (response.status === 'not_authorized') { - // the user is logged in to Facebook, - // but has not authenticated your app - // TODO: popup authorization dialog - window.fb_logged_in_state = "not_authorized"; - } - else { - // the user isn't logged in to Facebook. - window.fb_logged_in_state = "not_logged_in"; - } + function handleFbStateChange(response) { + // if the UI needs to be updated based on the status of facebook, here's the place to do it } - this.fb_login = function() { - FB.login(function(response) { - handle_fblogin_response(response); - }, {scope:'publish_stream'}); - } + function showFeedDialog() { - function fbInviteURL() { - if (fbInviteURL_ === null) { - $.ajax({ - type: "GET", - async: false, - url: '/api/invited_users/facebook', - success: function(response) { - fbInviteURL_ = response['signup_url']; - }, - error: app.ajaxError - }); - } - return fbInviteURL_; - } + createFbInvite() + .done(function(fbInviteResponse) { + var signupUrl = fbInviteResponse["signup_url"]; + + var obj = { + method: 'feed', + link: signupUrl, + picture: 'http://jamkazam.com/assets/logo.png', + name: 'Join me on JamKazam', + caption: 'Play live music in real-time sessions with others over the Internet, as if in the same room.', + description: '', + actions: [{ name: 'Signup', link: signupUrl }] + }; + function fbFeedDialogCallback(response) { + //console.log("feedback dialog closed: " + response['post_id']) + if (response['post_id']) { + context.JK.GA.trackServiceInvitations(context.JK.GA.InvitationTypes.facebook, 1); + } + } + FB.ui(obj, fbFeedDialogCallback); + }) + .fail(app.ajaxError) - function show_feed_dialog() { - var obj = { - method: 'feed', - link: fbInviteURL(), - picture: 'http://jamkazam.com/assets/logo.png', - name: 'Join me on JamKazam', - caption: 'Play live music in real-time sessions with others over the Internet, as if in the same room.', - description: '', - actions: [{ name: 'Signup', link: fbInviteURL() }] - }; - function fbFeedDialogCallback(response) { - //console.log("feedback dialog closed: " + response['post_id']) - if (response['post_id']) { - context.JK.GA.trackServiceInvitations(context.JK.GA.InvitationTypes.facebook, 1); - } - } - FB.ui(obj, fbFeedDialogCallback); } function showFacebookDialog(evt) { if (!(evt === undefined)) evt.stopPropagation(); - var fb_state = window.fb_logged_in_state; - - if (fb_state == "connected") { - show_feed_dialog(); - } else if (fb_state == "not_authorized") { - this.fb_login(); - } else { - this.fb_login(); - } - } - - function callFB(fbAppID){ - var fbAppID_ = fbAppID; - window.fbAsyncInit = function() { - FB.init({ - appId : fbAppID_, - // channelUrl : '//WWW.YOUR_DOMAIN.COM/channel.html', - status : true, // check the login status upon init? - cookie : true, // set sessions cookies to allow server to access the session? - xfbml : true, // parse XFBML tags on this page? - oauth : true, // enable OAuth 2.0 - }); - // listen to see if the user is known/logged in - FB.getLoginStatus(function(response) { - handle_fblogin_response(response); - }); - }; - - // Load the SDK Asynchronously - (function(d){ - var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;} - js = d.createElement('script'); js.id = id; js.async = true; - js.src = "//connect.facebook.net/en_US/all.js"; - d.getElementsByTagName('head')[0].appendChild(js); - }(document)); + facebookHelper.promptLogin() + .done(function(response) { + if (response && response.status == "connected") { + showFeedDialog(); + } + }) } // END FB handlers @@ -279,15 +228,17 @@ registerEvents(false); } - function initialize(fbAppID){ + function initialize(_facebookHelper){ + facebookHelper = _facebookHelper; + var dialogBindings = { 'beforeShow' : beforeShow, 'afterHide': afterHide }; app.bindDialog('inviteUsers', dialogBindings); - - //callFB(fbAppID); + + facebookHelper.deferredLoginStatus().done(function(response) { handleFbStateChange(response); }); }; this.initialize = initialize; diff --git a/web/app/assets/javascripts/jam_rest.js b/web/app/assets/javascripts/jam_rest.js index b5ad894e5..a3317432b 100644 --- a/web/app/assets/javascripts/jam_rest.js +++ b/web/app/assets/javascripts/jam_rest.js @@ -805,6 +805,15 @@ }) } + function createFbInviteUrl() { + return $.ajax({ + type: "GET", + url: '/api/invited_users/facebook', + dataType: "json", + contentType: 'application/json' + }); + } + function initialize() { return self; } @@ -874,6 +883,7 @@ this.getShareSession = getShareSession; this.getShareRecording = getShareRecording; this.tweet = tweet; + this.createFbInviteUrl = createFbInviteUrl; return this; }; diff --git a/web/app/assets/javascripts/layout.js b/web/app/assets/javascripts/layout.js index c2f09578f..62fc5078f 100644 --- a/web/app/assets/javascripts/layout.js +++ b/web/app/assets/javascripts/layout.js @@ -542,7 +542,6 @@ function centerDialog(dialog) { var $dialog = $('[layout-id="' + dialog + '"]'); - console.log("$dialog.width, height", $dialog.width(), $dialog.height()) $dialog.css({ left: width/2 - ($dialog.width()/2) + "px", top: height/2 - ($dialog.height()/2) + "px" diff --git a/web/app/assets/javascripts/shareDialog.js b/web/app/assets/javascripts/shareDialog.js index a58b6c287..1f12afdae 100644 --- a/web/app/assets/javascripts/shareDialog.js +++ b/web/app/assets/javascripts/shareDialog.js @@ -32,6 +32,17 @@ } + function checkShareCheckbox(provider, checked) { + var checkbox = $(dialogId + ' .share-with-' + provider + ' input'); + + if(checked) { + checkbox.attr('checked', 'checked'); + } + else { + checkbox.removeAttr('checked'); + } + } + function handleRecordingShareWithGoogle(message) { var defer = $.Deferred(); @@ -41,31 +52,53 @@ return defer; } - function handleRecordingShareWithTwitter(message) { + function handleShareWithTwitter(message) { var defer = $.Deferred(); rest.tweet({message: message + ' ' + entity.share_url}) .done(function() { + + // uncheck facebook, because we don't want the user to re-post to their timeline by accident + checkShareCheckbox('twitter', false); defer.resolve(); }) .fail(function(jqXHR) { if(jqXHR.status == 422) { // implies twitter token error. - app.notify({ - title : "Failed to Tweet", - text : "You need to re-authorize JamKazam to access your Twitter account. Click (sign in) in the Share Dialog.", - "icon_url": "/assets/content/icon_alert_big.png" - }); - disableTwitter(); + + var response = JSON.parse(jqXHR.responseText); + + if(response.errors.token) { + app.notify({ + title : "Failed to Tweet", + text : "You need to re-authorize JamKazam to access your Twitter account. Click (sign in) in the Share Dialog.", + "icon_url": "/assets/content/icon_alert_big.png" + }); + disableTwitter(); + } + else if(response.errors.twitter) { + app.notify({ + title : "Failed to Tweet", + text : "Twitter rejected the tweet because '" + response.errors.twitter[0] + "'", + "icon_url": "/assets/content/icon_alert_big.png" + }); + } + else { + app.notifyServerError(jqXHR, "Unable to Share with Twitter"); + } } else { app.notifyServerError(jqXHR, "Unable to Share with Twitter"); } defer.reject(); - }) + }); return defer; } + function handleRecordingShareWithTwitter(message) { + return handleShareWithTwitter(message); + } + function handleRecordingShareWithFacebook(message) { var defer = $.Deferred(); @@ -83,6 +116,7 @@ picture: data.photo_url }) .done(function(response) { + checkShareCheckbox('facebook', false); defer.resolve(); }) .fail(function(response) { @@ -117,33 +151,8 @@ return defer; } - // 116 characters - // abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdef function handleSessionShareWithTwitter(message) { - var defer = $.Deferred(); - - rest.tweet({message: message + ' ' + entity.share_url}) - .done(function() { - defer.resolve(); - }) - .fail(function(jqXHR) { - if(jqXHR.status == 422) { - // implies twitter token error. - app.notify({ - title : "Failed to Tweet", - text : "You need to re-authorize JamKazam to access your Twitter account. Click (sign in) in the Share Dialog.", - "icon_url": "/assets/content/icon_alert_big.png" - }); - disableTwitter(); - } - else { - app.notifyServerError(jqXHR, "Unable to Share with Twitter"); - } - defer.reject(); - }) - - - return defer; + return handleShareWithTwitter(message); } function handleSessionShareWithFacebook(message) { @@ -163,6 +172,7 @@ picture: data.photo_url }) .done(function(response) { + checkShareCheckbox('facebook', false); defer.resolve(); }) .fail(function(response) { @@ -476,9 +486,7 @@ initDialog(); - $(facebookHelper).on('fb.login_response', function(e, data) { - handleFbStateChange(data.response); - }) + facebookHelper.deferredLoginStatus().done(function(response) { handleFbStateChange(response); }); }; this.initialize = initialize; diff --git a/web/app/assets/javascripts/web/signinDialog.js b/web/app/assets/javascripts/web/signinDialog.js index 5d1f4e989..a54f6bc46 100644 --- a/web/app/assets/javascripts/web/signinDialog.js +++ b/web/app/assets/javascripts/web/signinDialog.js @@ -38,6 +38,7 @@ } function events() { + console.log($(dialogId + ' .signin-cancel')); $(dialogId + ' .signin-cancel').click(function(e) { app.layout.closeDialog('signin-dialog'); e.stopPropagation(); @@ -55,6 +56,7 @@ }); $(dialogId + ' .show-signup-dialog').click(function(e) { + console.log("wtf") app.layout.closeDialog('signin-dialog') app.layout.showDialog('signup-dialog') return false; diff --git a/web/app/assets/stylesheets/client/shareDialog.css.scss b/web/app/assets/stylesheets/client/shareDialog.css.scss index e01e72955..dee33c55e 100644 --- a/web/app/assets/stylesheets/client/shareDialog.css.scss +++ b/web/app/assets/stylesheets/client/shareDialog.css.scss @@ -280,4 +280,10 @@ margin-bottom:20px; } } + + .spinner-large { + vertical-align:middle; + text-align: center; + margin: 125px auto; + } } \ No newline at end of file diff --git a/web/app/controllers/api_twitters_controller.rb b/web/app/controllers/api_twitters_controller.rb index 7b3d27d2b..b75fc8bd2 100644 --- a/web/app/controllers/api_twitters_controller.rb +++ b/web/app/controllers/api_twitters_controller.rb @@ -4,6 +4,10 @@ class ApiTwittersController < ApiController respond_to :json + rescue_from 'Twitter::Error::Forbidden' do |exception| + render :json => { :errors => { :twitter => [exception.to_s] } }, :status => 422 + end + rescue_from 'Twitter::Error::Unauthorized' do |exception| # invalidate current tokens current_user.invalidate_user_authorization('twitter') diff --git a/web/app/views/clients/index.html.erb b/web/app/views/clients/index.html.erb index 3c31b8e04..fb29e928c 100644 --- a/web/app/views/clients/index.html.erb +++ b/web/app/views/clients/index.html.erb @@ -102,10 +102,11 @@ var recordingManager = new JK.RecordingManager(); var facebookHelper = new JK.FacebookHelper(JK.app); + JK.FacebookHelperInstance = facebookHelper; facebookHelper.initialize(gon.global.facebook_app_id); var invitationDialog = new JK.InvitationDialog(JK.app); - invitationDialog.initialize(gon.global.facebook_app_id); + invitationDialog.initialize(facebookHelper); var localRecordingsDialog = new JK.LocalRecordingsDialog(JK.app); localRecordingsDialog.initialize(); diff --git a/web/app/views/layouts/web.erb b/web/app/views/layouts/web.erb index 8961a4b11..0159ba1fb 100644 --- a/web/app/views/layouts/web.erb +++ b/web/app/views/layouts/web.erb @@ -91,6 +91,8 @@ var signupDialog = new JK.SignupDialog(JK.app); signupDialog.initialize(); + var signinDialog = new JK.SigninDialog(JK.app); + signinDialog.initialize(); }) diff --git a/web/config/application.rb b/web/config/application.rb index 72d3c2bed..3fc0d980a 100644 --- a/web/config/application.rb +++ b/web/config/application.rb @@ -115,6 +115,11 @@ include JamRuby # Runs the websocket gateway within the web app config.websocket_gateway_uri = "ws://localhost:#{config.websocket_gateway_port}/websocket" + config.external_hostname = ENV['EXTERNAL_HOSTNAME'] || 'localhost' + config.external_port = ENV['EXTERNAL_PORT'] || 3000 + config.external_protocol = ENV['EXTERNAL_PROTOCOL'] || 'http://' + config.external_root_url = "#{config.external_protocol}#{config.external_hostname}#{(config.external_port == 80 || config.external_port == 443) ? '' : ':' + config.external_port.to_s}" + # set this to false if you want to disable signups (lock down public user creation) config.signup_enabled = true