Added idle and inactive timeouts.

This commit is contained in:
Martin Edenhofer 2015-11-26 00:40:52 +01:00
parent 6ce3116c33
commit d71090df6e
4 changed files with 183 additions and 43 deletions

View file

@ -14,13 +14,15 @@ do($ = window.jQuery, window) ->
host: '' host: ''
debug: false debug: false
flat: false flat: false
lang: undefined, lang: undefined
cssAutoload: true, cssAutoload: true
cssUrl: undefined, cssUrl: undefined
fontSize: undefined fontSize: undefined
buttonClass: 'open-zammad-chat' buttonClass: 'open-zammad-chat'
inactiveClass: 'is-inactive' inactiveClass: 'is-inactive'
title: '<strong>Chat</strong> with us!' title: '<strong>Chat</strong> with us!'
idleTimeout: 8
inactiveTimeout: 20
_messageCount: 0 _messageCount: 0
isOpen: true isOpen: true
@ -48,8 +50,8 @@ do($ = window.jQuery, window) ->
'All colleges are busy.': 'Alle Kollegen sind belegt.' 'All colleges are busy.': 'Alle Kollegen sind belegt.'
'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.' 'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.'
'Start new conversation': 'Neue Konversation starten' 'Start new conversation': 'Neue Konversation starten'
'Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.': 'Da sie in den letzten %s nichts geschrieben haben wurde ihre Konversation mit <strong>%s</strong> geschlossen.' 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit <strong>%s</strong> geschlossen.'
'minutes': 'Minuten' 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.'
sessionId: undefined sessionId: undefined
T: (string, items...) => T: (string, items...) =>
@ -86,6 +88,10 @@ do($ = window.jQuery, window) ->
@options = $.extend {}, @defaults, options @options = $.extend {}, @defaults, options
# check prerequisites # check prerequisites
if !$
@state = 'unsupported'
@log 'notice', 'Chat: no jquery found!'
return
if !window.WebSocket or !sessionStorage if !window.WebSocket or !sessionStorage
@state = 'unsupported' @state = 'unsupported'
@log 'notice', 'Chat: Browser not supported!' @log 'notice', 'Chat: Browser not supported!'
@ -143,7 +149,9 @@ do($ = window.jQuery, window) ->
@log 'debug', 'ws:onmessage', pipe @log 'debug', 'ws:onmessage', pipe
switch pipe.event switch pipe.event
when 'chat_error' when 'chat_error'
@log 'error', pipe.data @log 'notice', pipe.data
if pipe.data && pipe.data.state is 'chat_disabled'
@wsClose()
when 'chat_session_message' when 'chat_session_message'
return if pipe.data.self_written return if pipe.data.self_written
@receiveMessage pipe.data @receiveMessage pipe.data
@ -194,6 +202,8 @@ do($ = window.jQuery, window) ->
$(".#{ @options.buttonClass }").hide() $(".#{ @options.buttonClass }").hide()
reopenSession: (data) => reopenSession: (data) =>
@inactiveTimeoutStart()
unfinishedMessage = sessionStorage.getItem 'unfinished_message' unfinishedMessage = sessionStorage.getItem 'unfinished_message'
# rerender chat history # rerender chat history
@ -236,6 +246,7 @@ do($ = window.jQuery, window) ->
@isTyping = new Date() @isTyping = new Date()
@send 'chat_session_typing', @send 'chat_session_typing',
session_id: @sessionId session_id: @sessionId
@inactiveTimeoutStart()
onSubmit: (event) => onSubmit: (event) =>
event.preventDefault() event.preventDefault()
@ -243,9 +254,10 @@ do($ = window.jQuery, window) ->
sendMessage: -> sendMessage: ->
message = @input.val() message = @input.val()
return if !message return if !message
@inactiveTimeoutStart()
sessionStorage.removeItem 'unfinished_message' sessionStorage.removeItem 'unfinished_message'
messageElement = @view('message') messageElement = @view('message')
@ -273,6 +285,8 @@ do($ = window.jQuery, window) ->
session_id: @sessionId session_id: @sessionId
receiveMessage: (data) => receiveMessage: (data) =>
@inactiveTimeoutStart()
# hide writing indicator # hide writing indicator
@onAgentTypingEnd() @onAgentTypingEnd()
@ -307,10 +321,10 @@ do($ = window.jQuery, window) ->
@isOpen = true @isOpen = true
if !@sessionId if !@sessionId
@session_init() @sessionInit()
onOpenAnimationEnd: -> onOpenAnimationEnd: =>
#@showTimeout() @idleTimeoutStop()
close: (event) => close: (event) =>
return @state if @state is 'off' or @state is 'unsupported' return @state if @state is 'off' or @state is 'unsupported'
@ -319,11 +333,24 @@ do($ = window.jQuery, window) ->
# only close if session_id exists # only close if session_id exists
return if !@sessionId return if !@sessionId
# send close
@send 'chat_session_close',
session_id: @sessionId
# stop timer
@inactiveTimeoutStop()
# delete input store
sessionStorage.removeItem 'unfinished_message'
# stop delay of initial queue position # stop delay of initial queue position
if @onInitialQueueDelayId if @onInitialQueueDelayId
clearTimeout(@onInitialQueueDelayId) clearTimeout(@onInitialQueueDelayId)
@closeWindow() if event
@closeWindow()
@setSessionId undefined
closeWindow: => closeWindow: =>
@el.removeClass('zammad-chat-is-open') @el.removeClass('zammad-chat-is-open')
@ -335,12 +362,6 @@ do($ = window.jQuery, window) ->
@disconnect() @disconnect()
@isOpen = false @isOpen = false
@send 'chat_session_close',
session_id: @sessionId
@setSessionId undefined
sessionStorage.removeItem 'unfinished_message'
# restart connection # restart connection
@onWebSocketOpen() @onWebSocketOpen()
@ -448,7 +469,7 @@ do($ = window.jQuery, window) ->
scrollToBottom: -> scrollToBottom: ->
@el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')) @el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
session_init: -> sessionInit: ->
@send('chat_session_init') @send('chat_session_init')
detectHost: -> detectHost: ->
@ -484,6 +505,7 @@ do($ = window.jQuery, window) ->
@reconnectDelayId = setTimeout(@wsConnect, 5000) @reconnectDelayId = setTimeout(@wsConnect, 5000)
onWebSocketOpen: => onWebSocketOpen: =>
@idleTimeoutStart()
@sessionId = sessionStorage.getItem('sessionId') @sessionId = sessionStorage.getItem('sessionId')
@log 'debug', 'ws connected' @log 'debug', 'ws connected'
@ -511,6 +533,7 @@ do($ = window.jQuery, window) ->
@addStatus @T('Chat closed by %s', data.realname) @addStatus @T('Chat closed by %s', data.realname)
@disableInput() @disableInput()
@setAgentOnlineState 'offline' @setAgentOnlineState 'offline'
@inactiveTimeoutStop()
disconnect: -> disconnect: ->
@showLoader() @showLoader()
@ -552,8 +575,11 @@ do($ = window.jQuery, window) ->
showTimeout: -> showTimeout: ->
@el.find('.zammad-chat-body').html @view('timeout') @el.find('.zammad-chat-body').html @view('timeout')
agent: @agent.name agent: @agent.name
delay: 10 delay: @options.inactiveTimeout
unit: @T('minutes') @close()
reload = ->
location.reload()
@el.find('.js-restart').click reload
showLoader: -> showLoader: ->
@el.find('.zammad-chat-body').html @view('loader')() @el.find('.zammad-chat-body').html @view('loader')()
@ -583,4 +609,31 @@ do($ = window.jQuery, window) ->
newSS.href = 'data:text/css,' + escape(styles) newSS.href = 'data:text/css,' + escape(styles)
document.getElementsByTagName('head')[0].appendChild(newSS) document.getElementsByTagName('head')[0].appendChild(newSS)
inactiveTimeoutStart: =>
@inactiveTimeoutStop()
delay = =>
@log 'debug', "Inactive timeout of #{@options.inactiveTimeout} minutes, show timeout screen."
@state = 'off'
@setAgentOnlineState 'offline'
@showTimeout()
@wsClose()
@inactiveTimeoutStopDelayId = setTimeout(delay, @options.inactiveTimeout * 1000 * 60)
inactiveTimeoutStop: =>
return if !@inactiveTimeoutStopDelayId
clearTimeout(@inactiveTimeoutStopDelayId)
idleTimeoutStart: =>
@idleTimeoutStop()
delay = =>
@log 'debug', "Idle timeout of #{@options.idleTimeout} minutes, hide widget"
@state = 'off'
@hide()
@wsClose()
@idleTimeoutStopDelayId = setTimeout(delay, @options.idleTimeout * 1000 * 60)
idleTimeoutStop: =>
return if !@idleTimeoutStopDelayId
clearTimeout(@idleTimeoutStopDelayId)
window.ZammadChat = ZammadChat window.ZammadChat = ZammadChat

View file

@ -20,7 +20,9 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
fontSize: void 0, fontSize: void 0,
buttonClass: 'open-zammad-chat', buttonClass: 'open-zammad-chat',
inactiveClass: 'is-inactive', inactiveClass: 'is-inactive',
title: '<strong>Chat</strong> with us!' title: '<strong>Chat</strong> with us!',
idleTimeout: 8,
inactiveTimeout: 20
}; };
ZammadChat.prototype._messageCount = 0; ZammadChat.prototype._messageCount = 0;
@ -61,8 +63,8 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
'All colleges are busy.': 'Alle Kollegen sind belegt.', 'All colleges are busy.': 'Alle Kollegen sind belegt.',
'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.', 'You are on waiting list position <strong>%s</strong>.': 'Sie sind in der Warteliste an der Position <strong>%s</strong>.',
'Start new conversation': 'Neue Konversation starten', 'Start new conversation': 'Neue Konversation starten',
'Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.': 'Da sie in den letzten %s nichts geschrieben haben wurde ihre Konversation mit <strong>%s</strong> geschlossen.', 'Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation mit <strong>%s</strong> geschlossen.',
'minutes': 'Minuten' 'Since you didn\'t respond in the last %s minutes your conversation got closed.': 'Da Sie in den letzten %s Minuten nichts geschrieben haben wurde Ihre Konversation geschlossen.'
} }
}; };
@ -117,6 +119,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
}; };
function ZammadChat(options) { function ZammadChat(options) {
this.idleTimeoutStop = bind(this.idleTimeoutStop, this);
this.idleTimeoutStart = bind(this.idleTimeoutStart, this);
this.inactiveTimeoutStop = bind(this.inactiveTimeoutStop, this);
this.inactiveTimeoutStart = bind(this.inactiveTimeoutStart, this);
this.setAgentOnlineState = bind(this.setAgentOnlineState, this); this.setAgentOnlineState = bind(this.setAgentOnlineState, this);
this.onConnectionEstablished = bind(this.onConnectionEstablished, this); this.onConnectionEstablished = bind(this.onConnectionEstablished, this);
this.setSessionId = bind(this.setSessionId, this); this.setSessionId = bind(this.setSessionId, this);
@ -133,6 +139,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
this.onCloseAnimationEnd = bind(this.onCloseAnimationEnd, this); this.onCloseAnimationEnd = bind(this.onCloseAnimationEnd, this);
this.closeWindow = bind(this.closeWindow, this); this.closeWindow = bind(this.closeWindow, this);
this.close = bind(this.close, this); this.close = bind(this.close, this);
this.onOpenAnimationEnd = bind(this.onOpenAnimationEnd, this);
this.open = bind(this.open, this); this.open = bind(this.open, this);
this.renderMessage = bind(this.renderMessage, this); this.renderMessage = bind(this.renderMessage, this);
this.receiveMessage = bind(this.receiveMessage, this); this.receiveMessage = bind(this.receiveMessage, this);
@ -148,6 +155,11 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
this.log = bind(this.log, this); this.log = bind(this.log, this);
this.T = bind(this.T, this); this.T = bind(this.T, this);
this.options = $.extend({}, this.defaults, options); this.options = $.extend({}, this.defaults, options);
if (!$) {
this.state = 'unsupported';
this.log('notice', 'Chat: no jquery found!');
return;
}
if (!window.WebSocket || !sessionStorage) { if (!window.WebSocket || !sessionStorage) {
this.state = 'unsupported'; this.state = 'unsupported';
this.log('notice', 'Chat: Browser not supported!'); this.log('notice', 'Chat: Browser not supported!');
@ -211,7 +223,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
this.log('debug', 'ws:onmessage', pipe); this.log('debug', 'ws:onmessage', pipe);
switch (pipe.event) { switch (pipe.event) {
case 'chat_error': case 'chat_error':
this.log('error', pipe.data); this.log('notice', pipe.data);
if (pipe.data && pipe.data.state === 'chat_disabled') {
this.wsClose();
}
break; break;
case 'chat_session_message': case 'chat_session_message':
if (pipe.data.self_written) { if (pipe.data.self_written) {
@ -284,6 +299,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
ZammadChat.prototype.reopenSession = function(data) { ZammadChat.prototype.reopenSession = function(data) {
var i, len, message, ref, unfinishedMessage; var i, len, message, ref, unfinishedMessage;
this.inactiveTimeoutStart();
unfinishedMessage = sessionStorage.getItem('unfinished_message'); unfinishedMessage = sessionStorage.getItem('unfinished_message');
if (data.agent) { if (data.agent) {
this.onConnectionEstablished(data); this.onConnectionEstablished(data);
@ -322,9 +338,10 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
return; return;
} }
this.isTyping = new Date(); this.isTyping = new Date();
return this.send('chat_session_typing', { this.send('chat_session_typing', {
session_id: this.sessionId session_id: this.sessionId
}); });
return this.inactiveTimeoutStart();
}; };
ZammadChat.prototype.onSubmit = function(event) { ZammadChat.prototype.onSubmit = function(event) {
@ -338,6 +355,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
if (!message) { if (!message) {
return; return;
} }
this.inactiveTimeoutStart();
sessionStorage.removeItem('unfinished_message'); sessionStorage.removeItem('unfinished_message');
messageElement = this.view('message')({ messageElement = this.view('message')({
message: message, message: message,
@ -362,6 +380,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
}; };
ZammadChat.prototype.receiveMessage = function(data) { ZammadChat.prototype.receiveMessage = function(data) {
this.inactiveTimeoutStart();
this.onAgentTypingEnd(); this.onAgentTypingEnd();
this.maybeAddTimestamp(); this.maybeAddTimestamp();
return this.renderMessage({ return this.renderMessage({
@ -399,11 +418,13 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
} }
this.isOpen = true; this.isOpen = true;
if (!this.sessionId) { if (!this.sessionId) {
return this.session_init(); return this.sessionInit();
} }
}; };
ZammadChat.prototype.onOpenAnimationEnd = function() {}; ZammadChat.prototype.onOpenAnimationEnd = function() {
return this.idleTimeoutStop();
};
ZammadChat.prototype.close = function(event) { ZammadChat.prototype.close = function(event) {
if (this.state === 'off' || this.state === 'unsupported') { if (this.state === 'off' || this.state === 'unsupported') {
@ -415,10 +436,18 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
if (!this.sessionId) { if (!this.sessionId) {
return; return;
} }
this.send('chat_session_close', {
session_id: this.sessionId
});
this.inactiveTimeoutStop();
sessionStorage.removeItem('unfinished_message');
if (this.onInitialQueueDelayId) { if (this.onInitialQueueDelayId) {
clearTimeout(this.onInitialQueueDelayId); clearTimeout(this.onInitialQueueDelayId);
} }
return this.closeWindow(); if (event) {
this.closeWindow();
}
return this.setSessionId(void 0);
}; };
ZammadChat.prototype.closeWindow = function() { ZammadChat.prototype.closeWindow = function() {
@ -434,11 +463,6 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
this.el.removeClass('zammad-chat-is-visible'); this.el.removeClass('zammad-chat-is-visible');
this.disconnect(); this.disconnect();
this.isOpen = false; this.isOpen = false;
this.send('chat_session_close', {
session_id: this.sessionId
});
this.setSessionId(void 0);
sessionStorage.removeItem('unfinished_message');
return this.onWebSocketOpen(); return this.onWebSocketOpen();
}; };
@ -555,7 +579,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
return this.el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight')); return this.el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'));
}; };
ZammadChat.prototype.session_init = function() { ZammadChat.prototype.sessionInit = function() {
return this.send('chat_session_init'); return this.send('chat_session_init');
}; };
@ -604,6 +628,7 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
}; };
ZammadChat.prototype.onWebSocketOpen = function() { ZammadChat.prototype.onWebSocketOpen = function() {
this.idleTimeoutStart();
this.sessionId = sessionStorage.getItem('sessionId'); this.sessionId = sessionStorage.getItem('sessionId');
this.log('debug', 'ws connected'); this.log('debug', 'ws connected');
this.send('chat_status_customer', { this.send('chat_status_customer', {
@ -630,7 +655,8 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
ZammadChat.prototype.onSessionClosed = function(data) { ZammadChat.prototype.onSessionClosed = function(data) {
this.addStatus(this.T('Chat closed by %s', data.realname)); this.addStatus(this.T('Chat closed by %s', data.realname));
this.disableInput(); this.disableInput();
return this.setAgentOnlineState('offline'); this.setAgentOnlineState('offline');
return this.inactiveTimeoutStop();
}; };
ZammadChat.prototype.disconnect = function() { ZammadChat.prototype.disconnect = function() {
@ -673,11 +699,16 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
}; };
ZammadChat.prototype.showTimeout = function() { ZammadChat.prototype.showTimeout = function() {
return this.el.find('.zammad-chat-body').html(this.view('timeout')({ var reload;
this.el.find('.zammad-chat-body').html(this.view('timeout')({
agent: this.agent.name, agent: this.agent.name,
delay: 10, delay: this.options.inactiveTimeout
unit: this.T('minutes')
})); }));
this.close();
reload = function() {
return location.reload();
};
return this.el.find('.js-restart').click(reload);
}; };
ZammadChat.prototype.showLoader = function() { ZammadChat.prototype.showLoader = function() {
@ -709,6 +740,49 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
return document.getElementsByTagName('head')[0].appendChild(newSS); return document.getElementsByTagName('head')[0].appendChild(newSS);
}; };
ZammadChat.prototype.inactiveTimeoutStart = function() {
var delay;
this.inactiveTimeoutStop();
delay = (function(_this) {
return function() {
_this.log('debug', "Inactive timeout of " + _this.options.inactiveTimeout + " minutes, show timeout screen.");
_this.state = 'off';
_this.setAgentOnlineState('offline');
_this.showTimeout();
return _this.wsClose();
};
})(this);
return this.inactiveTimeoutStopDelayId = setTimeout(delay, this.options.inactiveTimeout * 1000 * 60);
};
ZammadChat.prototype.inactiveTimeoutStop = function() {
if (!this.inactiveTimeoutStopDelayId) {
return;
}
return clearTimeout(this.inactiveTimeoutStopDelayId);
};
ZammadChat.prototype.idleTimeoutStart = function() {
var delay;
this.idleTimeoutStop();
delay = (function(_this) {
return function() {
_this.log('debug', "Idle timeout of " + _this.options.idleTimeout + " minutes, hide widget");
_this.state = 'off';
_this.hide();
return _this.wsClose();
};
})(this);
return this.idleTimeoutStopDelayId = setTimeout(delay, this.options.idleTimeout * 1000 * 60);
};
ZammadChat.prototype.idleTimeoutStop = function() {
if (!this.idleTimeoutStopDelayId) {
return;
}
return clearTimeout(this.idleTimeoutStopDelayId);
};
return ZammadChat; return ZammadChat;
})(); })();
@ -1159,9 +1233,17 @@ window.zammadChatTemplates["timeout"] = function (__obj) {
(function() { (function() {
__out.push('<div class="zammad-chat-modal">\n <div class="zammad-chat-modal-text">\n '); __out.push('<div class="zammad-chat-modal">\n <div class="zammad-chat-modal-text">\n ');
__out.push(this.T('Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.', this.delay + " " + this.unit, this.agent)); if (this.agent) {
__out.push('\n ');
__out.push(this.T('Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.', this.delay, this.agent));
__out.push('\n ');
} else {
__out.push('\n ');
__out.push(this.T('Since you didn\'t respond in the last %s minutes your conversation got closed.', this.delay));
__out.push('\n ');
}
__out.push('<br>\n <div class="zammad-chat-button"'); __out.push('\n <br>\n <div class="zammad-chat-button js-restart"');
if (this.background) { if (this.background) {
__out.push(__sanitize(" style='background: " + this.background + "'")); __out.push(__sanitize(" style='background: " + this.background + "'"));

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,11 @@
<div class="zammad-chat-modal"> <div class="zammad-chat-modal">
<div class="zammad-chat-modal-text"> <div class="zammad-chat-modal-text">
<%- @T('Since you didn\'t respond in the last %s your conversation with <strong>%s</strong> got closed.', "#{ @delay } #{ @unit }", @agent) %><br> <% if @agent: %>
<div class="zammad-chat-button"<%= " style='background: #{ @background }'" if @background %>><%- @T('Start new conversation') %></div> <%- @T('Since you didn\'t respond in the last %s minutes your conversation with <strong>%s</strong> got closed.', @delay, @agent) %>
<% else: %>
<%- @T('Since you didn\'t respond in the last %s minutes your conversation got closed.', @delay) %>
<% end %>
<br>
<div class="zammad-chat-button js-restart"<%= " style='background: #{ @background }'" if @background %>><%- @T('Start new conversation') %></div>
</div> </div>
</div> </div>