* fix padding issue

This commit is contained in:
Seth Call 2016-01-18 18:41:53 -06:00
parent 491bc8cda5
commit 86ea959972
28 changed files with 428 additions and 43 deletions

View File

@ -326,4 +326,5 @@ profile_teacher.sql
populate_languages.sql
populate_subjects.sql
reviews.sql
download_tracker_fingerprints.sql
download_tracker_fingerprints.sql
connection_active.sql

View File

@ -0,0 +1 @@
ALTER TABLE connections ADD COLUMN user_active BOOLEAN DEFAULT TRUE;

View File

@ -21,6 +21,7 @@ message ClientMessage {
UNSUBSCRIBE = 137;
SUBSCRIPTION_MESSAGE = 138;
SUBSCRIBE_BULK = 139;
USER_STATUS = 141;
// friend notifications
FRIEND_UPDATE = 140;
@ -67,6 +68,7 @@ message ClientMessage {
// text message
TEXT_MESSAGE = 236;
CHAT_MESSAGE = 237;
SEND_CHAT_MESSAGE = 238;
MUSICIAN_SESSION_FRESH = 240;
MUSICIAN_SESSION_STALE = 245;
@ -131,6 +133,7 @@ message ClientMessage {
optional Unsubscribe unsubscribe = 137;
optional SubscriptionMessage subscription_message = 138;
optional SubscribeBulk subscribe_bulk = 139;
optional UserStatus user_status = 141;
// friend notifications
optional FriendUpdate friend_update = 140; // from server to all friends of user
@ -178,6 +181,7 @@ message ClientMessage {
// text message
optional TextMessage text_message = 236;
optional ChatMessage chat_message = 237;
optional SendChatMessage send_chat_message = 238;
optional MusicianSessionFresh musician_session_fresh = 240;
optional MusicianSessionStale musician_session_stale = 245;
@ -569,6 +573,12 @@ message ChatMessage {
optional string msg = 3;
optional string msg_id = 4;
optional string created_at = 5;
optional string channel = 6;
}
message SendChatMessage {
optional string msg = 1;
optional string channel = 2;
}
// route_to: client:
@ -657,6 +667,11 @@ message SubscribeBulk {
//repeated Subscription subscriptions = 1; # the ruby protocol buffer library chokes on this. so we have to do the above
}
message UserStatus {
optional bool active = 1; // same as heartbeat 'active'... does the user appear present
optional string status = 2;
}
// route_to: session
// a test message used by ruby-client currently. just gives way to send out to rest of session
message TestSessionMessage {
@ -693,6 +708,7 @@ message TestClientMessage {
message Heartbeat {
optional string notification_seen = 1;
optional string notification_seen_at = 2;
optional bool active = 3; // is the user active?
}
// target: client

View File

@ -1650,11 +1650,11 @@ module JamRuby
output = cmd("soxi -r \"#{track.wav_file}\"", "get_sample_rate")
sample_rate = output.to_i
create_silence(tmp_dir, "padded_silence#{track.id}", amount, sample_rate, channels)
padding_file = create_silence(tmp_dir, "padded_silence#{track.id}", amount, sample_rate, channels)
output_file = File.join(tmp_dir, "with_padding_#{track.id}.wav")
cmd("sox \"#{track.wav_file}\" \"#{output_file}\"", "same_lengthening")
cmd("sox \"#{track.wav_file}\" \"#{padding_file}\" \"#{output_file}\"", "same_lengthening")
track.wav_file = output_file
end

View File

@ -897,19 +897,26 @@ module JamRuby
)
end
# creates the session chat message
def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at)
# creates the chat message
def chat_message(session_id, sender_name, sender_id, msg, msg_id, created_at, channel)
chat_message = Jampb::ChatMessage.new(
:sender_id => sender_id,
:sender_name => sender_name,
:msg => msg,
:msg_id => msg_id,
:created_at => created_at
:created_at => created_at,
:channel => channel
)
if session_id
route_to = SESSION_TARGET_PREFIX + session_id
else
route_to = ALL_ACTIVE_CLIENTS
end
Jampb::ClientMessage.new(
:type => ClientMessage::Type::CHAT_MESSAGE,
:route_to => SESSION_TARGET_PREFIX + session_id,
:route_to => route_to,
:chat_message => chat_message
)
end

View File

@ -50,7 +50,8 @@ module JamRuby
user.id,
chat_msg.message,
chat_msg.id,
chat_msg.created_at.utc.iso8601
chat_msg.created_at.utc.iso8601,
'session'
)
@@mq_router.server_publish_to_session(music_session, msg, sender = {:client_id => client_id})

View File

@ -20,6 +20,7 @@
UNSUBSCRIBE : "UNSUBSCRIBE",
SUBSCRIPTION_MESSAGE : "SUBSCRIPTION_MESSAGE",
SUBSCRIBE_BULK : "SUBSCRIBE_BULK",
USER_STATUS : "USER_STATUS",
// friend notifications
FRIEND_UPDATE : "FRIEND_UPDATE",
@ -66,6 +67,7 @@
// text message
TEXT_MESSAGE : "TEXT_MESSAGE",
CHAT_MESSAGE : "CHAT_MESSAGE",
SEND_CHAT_MESSAGE : "SEND_CHAT_MESSAGE",
// broadcast notifications
SOURCE_UP_REQUESTED : "SOURCE_UP_REQUESTED",
@ -120,13 +122,30 @@
};
// Heartbeat message
factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt) {
factory.heartbeat = function(lastNotificationSeen, lastNotificationSeenAt, active) {
var data = {};
data.notification_seen = lastNotificationSeen;
data.notification_seen_at = lastNotificationSeenAt;
data.active = active;
return client_container(msg.HEARTBEAT, route_to.SERVER, data);
};
// User Status update message
factory.userStatus = function(active, status) {
var data = {};
data.active = active;
data.status = status;
return client_container(msg.USER_STATUS, route_to.SERVER, data);
};
factory.chatMessage = function(channel, msg) {
var data = {}
data.channel = {}
data.msg = {}
return client_container(msg.SEND_CHAT_MESSAGE, route_to.SERVER, data)
}
// create a login message using user/pass
factory.login_with_user_pass = function(username, password) {
var login = { username : username , password : password };

View File

@ -36,6 +36,7 @@
var notificationLastSeen = undefined;
var clientClosedConnection = false;
var initialConnectAttempt = true;
var active = true;
// reconnection logic
var connectDeferred = null;
@ -46,6 +47,7 @@
var reconnectingWaitPeriodStart = null;
var reconnectDueTime = null;
var connectTimeout = null;
var activityTimeout;
// elements
var $inSituBanner = null;
@ -80,7 +82,6 @@
function initiateReconnect(activeElementVotes, in_error) {
var initialConnect = !!activeElementVotes;
console.log("activeElementVotes", activeElementVotes)
freezeInteraction = activeElementVotes && ((activeElementVotes.dialog && activeElementVotes.dialog.freezeInteraction === true) || (activeElementVotes.screen && activeElementVotes.screen.freezeInteraction === true));
if (in_error) {
@ -174,7 +175,8 @@
function _heartbeat() {
if (app.heartbeatActive) {
var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt);
console.log("heartbeat active?: " + active)
var message = context.JK.MessageFactory.heartbeat(notificationLastSeen, notificationLastSeenAt, active);
notificationLastSeenAt = undefined;
notificationLastSeen = undefined;
// for debugging purposes, see if the last time we've sent a heartbeat is way off (500ms) of the target interval
@ -290,6 +292,35 @@
}, 0)
}
function markAway() {
logger.debug("sleep again!")
active = false;
}
function activityCheck() {
var timeoutTime = 300000; // 5 * 1000 * 60 , 5 minutes
active = true;
activityTimeout = setTimeout(markAway, timeoutTime);
$(document).ready(function() {
$('body').bind('mousedown keydown touchstart focus', function(event) {
if (activityTimeout) {
clearTimeout(activityTimeout);
activityTimeout = null;
}
if (!active) {
if(server && server.connected) {
logger.debug("awake again!")
var userStatus = msg_factory.userStatus(true, null);
server.send(userStatus);
}
}
active = true;
activityTimeout = setTimeout(markAway, timeoutTime);
});
});
}
function heartbeatAck(header, payload) {
lastHeartbeatAckTime = new Date();
}
@ -771,6 +802,19 @@
//console.timeEnd('sendP2PMessage');
};
server.sendChatMessage = function(message) {
if (server.connected) {
var chatMsg = msg_factory.chatMessage(channel, message)
server.send(chatMsg)
return true;
}
else {
return false;
}
}
server.updateNotificationSeen = function (notificationId, notificationCreatedAt) {
var time = new Date(notificationCreatedAt);
@ -816,6 +860,7 @@
registerHeartbeatAck();
registerServerRejection();
registerSocketClosed();
activityCheck();
$inSituBanner = $('.server-connection');
$inSituBannerHolder = $('.no-websocket-connection');

View File

@ -40,6 +40,7 @@
//= require jquery.visible
//= require jquery.jstarbox
//= require fingerprint2.min
//= require ResizeSensor
//= require classnames
//= require reflux
//= require howler.core.js

View File

@ -15,9 +15,10 @@
var feedHelper = new context.JK.Feed(app);
var $scroller = $recordings;
var $content = $recordings;
var $recordsHolder = $screen.find('.recordings-content');
var $noMoreFeeds = $screen.find('.end-of-list');
var $empty = $();
feedHelper.initialize($screen, $scroller, $content, $noMoreFeeds, $empty, $empty, $empty, $empty, {sort: 'date', time_range: 'all', type: 'recording', show_checkbox: true, hide_avatar: true});
feedHelper.initialize($screen, $scroller, $recordsHolder, $noMoreFeeds, $empty, $empty, $empty, $empty, {sort: 'date', time_range: 'all', type: 'recording', show_checkbox: true, hide_avatar: true});
function beforeShow(data) {

View File

@ -41,6 +41,7 @@
trackNewUser();
trackScreenChanges();
})
$(document).on('JAMKAZAM_READY', function() {

View File

@ -13,7 +13,7 @@
var userId = null;
var bandId = null;
var currentFeedPage = 0;
var feedBatchSize = 10;
var feedBatchSize = 4;
var $screen = null;
var $scroller = null;
var $content = null;
@ -454,7 +454,6 @@
mix_class: feed['has_mix?'] ? 'has-mix' : 'no-mix',
}
console.log("OPTIONS", options)
var $feedItem = $(context._.template($('#template-feed-recording').html(), options, {variable: 'data'}));
var $controls = $feedItem.find('.recording-controls');

View File

@ -739,6 +739,13 @@
stackDialogs($dialog, $overlay);
addScreenContextToDialog($dialog)
$dialog.show();
// maintain center (un-attach previous sensor if applicable, then re-add always)
window.ResizeSensor.detach($dialog.get(0))
new window.ResizeSensor($dialog, function(){
centerDialog(dialog);
});
dialogEvent(dialog, 'afterShow', options);
$.btOffAll(); // add any prod bubbles if you open a dailog
return $dialog;

View File

@ -9,6 +9,7 @@
//= require ./react-components/stores/SubjectStore
//= require ./react-components/stores/ProfileStore
//= require ./react-components/stores/PlatformStore
//= require ./react-components/stores/ChatStore
//= require ./react-components/stores/BrowserMediaStore
//= require ./react-components/stores/RecordingStore
//= require ./react-components/stores/VideoStore

View File

@ -0,0 +1,16 @@
context = window
MIX_MODES = context.JK.MIX_MODES
@ChatWindow = React.createClass({
mixins: [Reflux.listenTo(@AppStore, "onAppInit"), Reflux.listenTo(@UserStore, "onUserChanged"), Reflux.listenTo(@ChatStore, "onChatChanged")]
getInitialState: () ->
{channels:['global', 'session']}
render: () ->
`<div className="ChatWindow">
<div className="tabs">
</div>
</div>`
})

View File

@ -21,7 +21,6 @@ logger = context.JK.logger
render: () ->
object_options = []
logger.debug("Rendering EditableList", this.props, this.props.listItems)
if this.props.listItems? && this.props.listItems.length > 0
for object,i in this.props.listItems

View File

@ -11,8 +11,14 @@ logger = context.JK.logger
@root.off("submit", ".teacher-experience-teaching-form").on("submit", ".teacher-experience-teaching-form", @addExperience)
formatListItem: (obj) ->
if obj.end_year?
endYear = obj.end_year
else
endYear = 'Present'
t = "#{obj.name}/#{obj.organization} (#{obj.start_year}"
t += "-#{obj.end_year}" if this.props.showEndDate
t += "-#{endYear}" if this.props.showEndDate
t += ")"
getInitialProps: () ->
@ -24,7 +30,6 @@ logger = context.JK.logger
addExperience: (e) ->
e.preventDefault()
logger.debug("addExperience", this.props.listItems, this.props)
$form = e.target
start_year = $("[name='start_year']", $form).val()

View File

@ -152,21 +152,23 @@ proficiencyDescriptionMap = {
</div>
</div>`
getYoutubeId:(url) ->
regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
match = url.match(regExp)
if match && match[2].length >= 9
return match[2]
else
return 'unknown'
sampleVideo: (user, teacher) ->
if teacher.introductory_video?
if teacher.introductory_video? && teacher.introductory_video.length > 0
videoUrl = teacher.introductory_video
if videoUrl.indexOf(window.location.protocol) != 0
console.log("replacing video")
if window.location.protocol == 'http:'
console.log("replacing https: " + videoUrl)
videoUrl = videoUrl.replace('https://', 'http://')
console.log("replaced : " + videoUrl)
else
videoUrl = videoUrl.replace('http://', 'https://')
videoUrl = videoUrl.replace("watch?v=", "v/")
# http://stackoverflow.com/a/21607897
embedId = @getYoutubeId(videoUrl)
embedUrl = "//www.youtube.com/embed/#{embedId}"
return `<div className="section introductory-video">
<h3>Intro Video</h3>
@ -174,7 +176,7 @@ proficiencyDescriptionMap = {
<div className="section-content">
<div className="video-wrapper">
<div className="video-container">
<iframe src={videoUrl} frameborder="0" allowfullscreen="allowfullscreen"/>
<iframe src={embedUrl} frameborder="0" allowfullscreen="allowfullscreen"/>
</div>
</div>
</div>
@ -402,7 +404,7 @@ proficiencyDescriptionMap = {
if teachingExperience.end_year && teachingExperience.end_year > 0
years = "#{teachingExperience.start_year} - #{teachingExperience.end_year}"
else
years = "#{teachingExperience.start_year} - Present"
years = "#{teachingExperience.start_year} - Present"
teachingExperiences.push(`<div key={i} className="experience">
<div className="years">{years}</div>
@ -505,7 +507,7 @@ proficiencyDescriptionMap = {
if summary.review_count == 1
reviewCount = '1 review'
else
reviewCount = sumarry.review_count + ' reviews'
reviewCount = summary.review_count + ' reviews'
reviews = []
for review in teacher.recent_reviews

View File

@ -9,10 +9,10 @@ logger = context.JK.logger
options = []
now = new Date().getFullYear()
for yr in [1901..now]
for yr in [now..1916]
options.push `<option value={yr}>{yr}</option>`
`<select className="YearSelect react-component" name={this.props.name} required placeholder="Select" defaultValue="2010">
`<select className="YearSelect react-component" name={this.props.name} required placeholder="Select" defaultValue={now}>
{options}
</select>`
})

View File

@ -0,0 +1,6 @@
context = window
@ChatActions = Reflux.createActions({
msgReceived: {}
sendMsg: {}
})

View File

@ -0,0 +1,37 @@
$ = jQuery
context = window
logger = context.JK.logger
@ChatStore = Reflux.createStore(
{
listenables: @ChatActions
msgs: {}
init: ->
# Register with the app store to get @app
this.listenTo(context.AppStore, this.onAppInit)
onAppInit: (@app) ->
onMsgReceived: (msg) ->
logger.debug("ChatStore.msgReceived", msg)
channelMsgs = @msgs[msg.channel]
if !channelMsgs?
channelMsgs = []
@msgs[msg.channel] = channelMsgs
channelMsgs.push(msg)
@changed()
onSendMsg: (msg) ->
logger.debug("ChatStore.sendMsg", msg)
window.JK.JamServer.sendChatMessage(msg)
changed: () ->
@trigger(msgs)
}
)

View File

@ -126,9 +126,13 @@
.actions {
position:absolute;
top:0;
top:2px;
right:0;
}
.display-value {
margin:2px 10px 2px 2px;
}
}
h3.margined {

View File

@ -10,7 +10,7 @@
}
.recordings {
min-height:30px;
min-height:50px;
max-height:450px;
margin-bottom:20px;
overflow-y:auto;

View File

@ -6,8 +6,10 @@
.dialog-inner
.instructions
= 'Select one or more recordings and click ADD to add JamKazam recordings to your performance samples.'
.recordings
.end-of-list No more feed entries
#recording-selector-dialog-feed-scroller.recordings
.recordings-content#recording-selector-dialog-feed-entry-list
#recording-selector-dialog-end-of-feeds-list.end-of-list= 'No more recordings'
#recording-selector-dialog-loading-feeds.infinite-scroll-loader{:style => 'padding:5px'}= 'Loading ...'
.right.action-buttons
%a.button-grey.btn-cancel-dialog{'layout-action' => 'cancel'} CANCEL
%a.button-orange.btn-select-recordings SAVE

View File

@ -0,0 +1,187 @@
/**
* Copyright (c) 2013 Marc J. Schmidt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/**
* Copyright Marc J. Schmidt. See the LICENSE file at the top-level
* directory of this distribution and at
* https://github.com/marcj/css-element-queries/blob/master/LICENSE.
*/
;
(function() {
/**
* Class for dimension change detection.
*
* @param {Element|Element[]|Elements|jQuery} element
* @param {Function} callback
*
* @constructor
*/
this.ResizeSensor = function(element, callback) {
/**
*
* @constructor
*/
function EventQueue() {
this.q = [];
this.add = function(ev) {
this.q.push(ev);
};
var i, j;
this.call = function() {
for (i = 0, j = this.q.length; i < j; i++) {
this.q[i].call();
}
};
}
/**
* @param {HTMLElement} element
* @param {String} prop
* @returns {String|Number}
*/
function getComputedStyle(element, prop) {
if (element.currentStyle) {
return element.currentStyle[prop];
} else if (window.getComputedStyle) {
return window.getComputedStyle(element, null).getPropertyValue(prop);
} else {
return element.style[prop];
}
}
/**
*
* @param {HTMLElement} element
* @param {Function} resized
*/
function attachResizeEvent(element, resized) {
if (!element.resizedAttached) {
element.resizedAttached = new EventQueue();
element.resizedAttached.add(resized);
} else if (element.resizedAttached) {
element.resizedAttached.add(resized);
return;
}
element.resizeSensor = document.createElement('div');
element.resizeSensor.className = 'resize-sensor';
var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: scroll; z-index: -1; visibility: hidden;';
var styleChild = 'position: absolute; left: 0; top: 0;';
element.resizeSensor.style.cssText = style;
element.resizeSensor.innerHTML =
'<div class="resize-sensor-expand" style="' + style + '">' +
'<div style="' + styleChild + '"></div>' +
'</div>' +
'<div class="resize-sensor-shrink" style="' + style + '">' +
'<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
'</div>';
element.appendChild(element.resizeSensor);
if (!{fixed: 1, absolute: 1}[getComputedStyle(element, 'position')]) {
element.style.position = 'relative';
}
var expand = element.resizeSensor.childNodes[0];
var expandChild = expand.childNodes[0];
var shrink = element.resizeSensor.childNodes[1];
var shrinkChild = shrink.childNodes[0];
var lastWidth, lastHeight;
var reset = function() {
expandChild.style.width = expand.offsetWidth + 10 + 'px';
expandChild.style.height = expand.offsetHeight + 10 + 'px';
expand.scrollLeft = expand.scrollWidth;
expand.scrollTop = expand.scrollHeight;
shrink.scrollLeft = shrink.scrollWidth;
shrink.scrollTop = shrink.scrollHeight;
lastWidth = element.offsetWidth;
lastHeight = element.offsetHeight;
};
reset();
var changed = function() {
if (element.resizedAttached) {
element.resizedAttached.call();
}
};
var addEvent = function(el, name, cb) {
if (el.attachEvent) {
el.attachEvent('on' + name, cb);
} else {
el.addEventListener(name, cb);
}
};
var onScroll = function() {
if (element.offsetWidth != lastWidth || element.offsetHeight != lastHeight) {
changed();
}
reset();
}
addEvent(expand, 'scroll', onScroll);
addEvent(shrink, 'scroll', onScroll);
}
var elementType = Object.prototype.toString.call(element);
var isCollectionTyped = ('[object Array]' === elementType
|| ('[object NodeList]' === elementType)
|| ('[object HTMLCollection]' === elementType)
|| ('undefined' !== typeof jQuery && element instanceof jQuery) //jquery
|| ('undefined' !== typeof Elements && element instanceof Elements) //mootools
);
if (isCollectionTyped) {
var i = 0, j = element.length;
for (; i < j; i++) {
attachResizeEvent(element[i], callback);
}
} else {
attachResizeEvent(element, callback);
}
this.detach = function() {
if (isCollectionTyped) {
var i = 0, j = element.length;
for (; i < j; i++) {
ResizeSensor.detach(element[i]);
}
} else {
ResizeSensor.detach(element);
}
};
};
this.ResizeSensor.detach = function(element) {
if (element.resizeSensor) {
element.removeChild(element.resizeSensor);
delete element.resizeSensor;
delete element.resizedAttached;
}
};
})();

View File

@ -1,7 +1,7 @@
module JamWebsockets
class ClientContext
attr_accessor :user, :client, :msg_count, :session, :client_type, :sent_bad_state_previously
attr_accessor :user, :client, :msg_count, :session, :client_type, :sent_bad_state_previously, :active
def initialize(user, client, client_type)
@user = user
@ -11,6 +11,7 @@
@msg_count = 0
@session = nil
@sent_bad_state_previously = false
@active = true
client.context = self
end

View File

@ -68,6 +68,7 @@ module JamWebsockets
@profile_it_sums = {}
@heartbeat_tracker = {}
@temp_ban = {}
@chat_enabled = {}
@login_success_count = 0
@login_fail_count = 0
@ -80,7 +81,7 @@ module JamWebsockets
end
def start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, options={:host => "localhost", :port => 5672, :max_connections_per_user => 10, :gateway => 'default', :allow_dynamic_registration => true}, &block)
def start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, options={:host => "localhost", :port => 5672, :max_connections_per_user => 10, :gateway => 'default', :allow_dynamic_registration => true, chat_enabled => true}, &block)
@log.info "startup"
@ -93,6 +94,7 @@ module JamWebsockets
@max_connections_per_user = options[:max_connections_per_user]
@gateway_name = options[:gateway]
@allow_dynamic_registration = options[:allow_dynamic_registration]
@chat_enabled = options[:chat_enabled]
# determine the maximum amount of heartbeats we should get per user
@maximum_minutely_heartbeat_rate_client = ((@heartbeat_interval_client / 60.0) * 2).ceil + 3
@ -237,6 +239,23 @@ module JamWebsockets
end
end
end
elsif client_id == MessageFactory::ALL_ACTIVE_CLIENTS
if @chat_enabled
msg = Jampb::ClientMessage.parse(msg)
@log.debug "client-directed message received from #{msg.from} to all chat clients"
@client_lookup.each do |client_id, client_context|
if client_context.active
client = client_context.client
if client
EM.schedule do
#@log.debug "sending client-directed down websocket to #{client_id}"
send_to_client(client, msg)
end
end
end
end
end
else
client_context = @client_lookup[client_id]
@ -568,6 +587,8 @@ module JamWebsockets
elsif client_msg.type == ClientMessage::Type::HEARTBEAT
time_it('heartbeat') { sane_logging { handle_heartbeat(client_msg.heartbeat, client_msg.message_id, client) } }
elsif client_msg.type == ClientMessage::Type::USER_STATUS
time_it('user_status') { sane_logging { handle_user_status(client_msg.user_status, client) } }
elsif client_msg.type == ClientMessage::Type::SUBSCRIBE_BULK
time_it('subscribe_bulk') { sane_logging { handle_bulk_subscribe(client_msg.subscribe_bulk, client) } }
elsif client_msg.type == ClientMessage::Type::SUBSCRIBE
@ -897,6 +918,10 @@ module JamWebsockets
end
def handle_user_status(user_status, client)
client.context.active = user_status.active
end
def handle_heartbeat(heartbeat, heartbeat_message_id, client)
unless context = @clients[client]
profile_it('heartbeat_context_gone') {
@ -932,8 +957,8 @@ module JamWebsockets
}
profile_it('heartbeat_touch') {
# update connection updated_at
connection.touch
# update connection updated_at and if the user is active
Connection.where(id: connection.id).update_all(user_active: heartbeat.active)
}
profile_it('heartbeat_notification') {

View File

@ -23,6 +23,7 @@ module JamWebsockets
connect_time_stale_browser = options[:connect_time_stale_browser].to_i
connect_time_expire_browser = options[:connect_time_expire_browser].to_i
max_connections_per_user = options[:max_connections_per_user].to_i
chat_enabled = options[:chat_enabled].nil? ? true : options[:chat_enabled]
gateway_name = options[:gateway_name]
rabbitmq_host = options[:rabbitmq_host]
rabbitmq_port = options[:rabbitmq_port].to_i
@ -43,7 +44,7 @@ module JamWebsockets
}
EventMachine.run do
@router.start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, host: rabbitmq_host, port: rabbitmq_port, max_connections_per_user: max_connections_per_user, gateway: gateway_name, allow_dynamic_registration: allow_dynamic_registration) do
@router.start(connect_time_stale_client, connect_time_expire_client, connect_time_stale_browser, connect_time_expire_browser, host: rabbitmq_host, port: rabbitmq_port, max_connections_per_user: max_connections_per_user, gateway: gateway_name, allow_dynamic_registration: allow_dynamic_registration, chat_enabled: chat_enabled) do
start_connection_expiration
start_client_expiration
start_connection_flagger