Moved to websockets.

This commit is contained in:
Martin Edenhofer 2012-07-24 00:22:23 +02:00
parent e6f4bd021f
commit b2209ff868
26 changed files with 868 additions and 403 deletions

View file

@ -58,3 +58,6 @@ gem 'simple-rss'
# To use debugger # To use debugger
# gem 'ruby-debug' # gem 'ruby-debug'
# event machine
gem 'eventmachine'
gem 'em-websocket'

View file

@ -2,7 +2,8 @@ class App.Controller extends Spine.Controller
# add @title methode to set title # add @title methode to set title
title: (name) -> title: (name) ->
$('html head title').html( Config.product_name + ' - ' + Ti(name) ) # $('html head title').html( Config.product_name + ' - ' + Ti(name) )
document.title = Config.product_name + ' - ' + Ti(name)
# add @notify methode to create notification # add @notify methode to create notification
notify: (data) -> notify: (data) ->
@ -545,6 +546,13 @@ class App.Controller extends Spine.Controller
return newInstance return newInstance
clearInterval: (interval_id) =>
# check global var
if !@intervalID
@intervalID = {}
clearInterval( @intervalID[interval_id] ) if @intervalID[interval_id]
interval: (action, interval, interval_id) => interval: (action, interval, interval_id) =>
# check global var # check global var
@ -778,11 +786,14 @@ class App.ControllerModal extends App.Controller
super(options) super(options)
modalShow: (params) => modalShow: (params) =>
@el.modal({ defaults = {
backdrop: true, backdrop: true,
keyboard: true, keyboard: true,
show: true show: true,
}) }
data = $.extend({}, defaults, params)
@el.modal(data)
@el.bind('hidden', => @el.bind('hidden', =>
# navigate back to home page # navigate back to home page
@ -805,3 +816,17 @@ class App.ControllerModal extends App.Controller
submit: (e) => submit: (e) =>
e.preventDefault() e.preventDefault()
@log 'You need to implement your own "submit" method!' @log 'You need to implement your own "submit" method!'
class App.ErrorModal extends App.ControllerModal
constructor: ->
super
@render()
render: ->
@html App.view('error')(
message: @message
)
@modalShow(
backdrop: false,
keyboard: false,
)

View file

@ -9,25 +9,29 @@ class App.DashboardActivityStream extends App.Controller
@items = [] @items = []
# refresh list ever 140 sec. # refresh list ever 140 sec.
@interval( @fetch, 1400000, 'dashboard_activity_stream' ) # @interval( @fetch, 1400000, 'dashboard_activity_stream' )
@fetch()
Spine.bind 'activity_stream_rebuild', (data) =>
@log 'a_stream', data
@fetch()
fetch: => fetch: =>
# use cache of first page # use cache of first page
if window.LastRefresh[ 'dashboard_activity_stream' ] if window.LastRefresh[ 'activity_stream' ]
@render( window.LastRefresh[ 'dashboard_activity_stream' ] ) @load( window.LastRefresh[ 'activity_stream' ] )
# get data # # get data
App.Com.ajax( # App.Com.ajax(
id: 'dashoard_activity_stream', # id: 'dashoard_activity_stream',
type: 'GET', # type: 'GET',
url: '/activity_stream', # url: '/activity_stream',
data: { # data: {
limit: @limit, # limit: @limit,
} # }
processData: true, # processData: true,
success: @load # success: @load
) # )
load: (data) => load: (data) =>
items = data.activity_stream items = data.activity_stream

View file

@ -3,36 +3,18 @@ $ = jQuery.sub()
class App.DashboardRss extends App.Controller class App.DashboardRss extends App.Controller
constructor: -> constructor: ->
super super
# refresh list ever 600 sec.
@interval( @fetch, 6000000, 'dashboard_rss' )
fetch: => # refresh list ever 600 sec.
Spine.bind 'rss_rebuild', (data) =>
@load(data)
# use cache of first page # use cache of first page
if window.LastRefresh[ 'dashboard_rss' ] if window.LastRefresh[ 'dashboard_rss' ]
@render( window.LastRefresh[ 'dashboard_rss' ] ) @load( window.LastRefresh[ 'dashboard_rss' ] )
# get data
App.Com.ajax(
id: 'dashboard_rss',
type: 'GET',
url: '/rss_fetch',
data: {
limit: @limit,
url: @url,
}
processData: true,
success: @load
)
load: (data) => load: (data) =>
items = data.items || [] items = data.items || []
@head = data.head || '?'
# set cache
window.LastRefresh[ 'dashboard_rss' ] = items
@render(items) @render(items)
render: (items) -> render: (items) ->

View file

@ -271,7 +271,7 @@ class Index extends App.Controller
@tickets = [] @tickets = []
# rebuild navbar with updated ticket count of overviews # rebuild navbar with updated ticket count of overviews
Spine.trigger 'navupdate_remote' App.WebSocket.send( event: 'navupdate_ticket_overview' )
# fetch overview data again # fetch overview data again
@fetch() @fetch()

View file

@ -9,13 +9,12 @@ class Index extends App.Controller
constructor: -> constructor: ->
super super
# set title # set title
@title 'Get Started' @title 'Get Started'
@navupdate '#get_started' @navupdate '#get_started'
@master_user = 0 @master_user = 0
# @render() # @render()
@fetch() @fetch()
@ -43,7 +42,7 @@ class Index extends App.Controller
) )
render: -> render: ->
# check authentication, redirect to login if master user already exists # check authentication, redirect to login if master user already exists
if !@master_user && !@authenticate() if !@master_user && !@authenticate()
@navigate '#login' @navigate '#login'
@ -59,7 +58,7 @@ class Index extends App.Controller
submit: (e) -> submit: (e) ->
e.preventDefault() e.preventDefault()
@params = @formParam(e.target) @params = @formParam(e.target)
# if no login is given, use emails as fallback # if no login is given, use emails as fallback
if !@params.login && @params.email if !@params.login && @params.email
@params.login = @params.email @params.login = @params.email
@ -102,28 +101,24 @@ class Index extends App.Controller
# rerender page # rerender page
@render() @render()
# error: => # error: =>
# @modalHide() # @modalHide()
) )
relogin: (data, status, xhr) => relogin: (data, status, xhr) =>
@log 'login:success', data @log 'login:success', data
# login check # login check
App.Auth.loginCheck() App.Auth.loginCheck()
# add notify # add notify
Spine.trigger 'notify:removeall' Spine.trigger 'notify:removeall'
# @notify # @notify
# type: 'success', # type: 'success',
# msg: 'Thanks for joining. Email sent to "' + @params.email + '". Please verify your email address.' # msg: 'Thanks for joining. Email sent to "' + @params.email + '". Please verify your email address.'
@el.find('.master_user').fadeOut('slow', => @el.find('.master_user').fadeOut('slow', =>
@el.find('.agent_user').fadeIn() @el.find('.agent_user').fadeIn()
) )
Config.Routes['getting_started'] = Index Config.Routes['getting_started'] = Index

View file

@ -3,7 +3,7 @@ $ = jQuery.sub()
class Index extends App.Controller class Index extends App.Controller
events: events:
'submit #login': 'login', 'submit #login': 'login',
constructor: -> constructor: ->
super super
@title 'Sign in' @title 'Sign in'
@ -37,12 +37,12 @@ class Index extends App.Controller
for key, provider of auth_provider_all for key, provider of auth_provider_all
if Config[provider.config] is true || Config[provider.config] is "true" if Config[provider.config] is true || Config[provider.config] is "true"
auth_providers.push provider auth_providers.push provider
@html App.view('login')( @html App.view('login')(
item: data, item: data,
auth_providers: auth_providers, auth_providers: auth_providers,
) )
# set focus # set focus
if !$(@el).find('[name="username"]').val() if !$(@el).find('[name="username"]').val()
$(@el).find('[name="username"]').focus() $(@el).find('[name="username"]').focus()
@ -55,17 +55,17 @@ class Index extends App.Controller
login: (e) -> login: (e) ->
e.preventDefault() e.preventDefault()
params = @formParam(e.target) params = @formParam(e.target)
# remember username # remember username
@username = params['username'] @username = params['username']
# session create with login/password # session create with login/password
App.Auth.login( App.Auth.login(
data: params, data: params,
success: @success success: @success
error: @error, error: @error,
) )
success: (data, status, xhr) => success: (data, status, xhr) =>
@log 'login:success', data @log 'login:success', data
@ -85,10 +85,14 @@ class Index extends App.Controller
for key, value of data.default_collections for key, value of data.default_collections
App[key].refresh( value, options: { clear: true } ) App[key].refresh( value, options: { clear: true } )
# rebuild navbar with user data
Spine.trigger 'navrebuild', data.session Spine.trigger 'navrebuild', data.session
# rebuild navbar with updated ticket count of overviews # update websocked auth info
Spine.trigger 'navupdate_remote' App.WebSocket.auth()
# rebuild navbar with ticket overview counter
App.WebSocket.send( event: 'navupdate_ticket_overview' )
# add notify # add notify
Spine.trigger 'notify:removeall' Spine.trigger 'notify:removeall'
@ -100,7 +104,7 @@ class Index extends App.Controller
# redirect to # # redirect to #
if window.Config['requested_url'] isnt '' if window.Config['requested_url'] isnt ''
@navigate window.Config['requested_url'] @navigate window.Config['requested_url']
# reset # reset
window.Config['requested_url'] = '' window.Config['requested_url'] = ''
else else
@ -108,14 +112,14 @@ class Index extends App.Controller
error: (xhr, statusText, error) => error: (xhr, statusText, error) =>
console.log 'login:error' console.log 'login:error'
# add notify # add notify
Spine.trigger 'notify:removeall' Spine.trigger 'notify:removeall'
Spine.trigger 'notify', { Spine.trigger 'notify', {
type: 'error', type: 'error',
msg: T('Wrong Username and Password combination.'), msg: T('Wrong Username and Password combination.'),
} }
# rerender login page # rerender login page
@render( @render(
username: @username username: @username

View file

@ -4,11 +4,10 @@ class Index extends Spine.Controller
constructor: -> constructor: ->
super super
@signout() @signout()
signout: -> signout: ->
# remove remote session # remove remote session
App.Auth.logout() App.Auth.logout()
@ -17,7 +16,7 @@ class Index extends Spine.Controller
window.Session = {} window.Session = {}
@log 'Session', window.Session @log 'Session', window.Session
Spine.trigger 'navrebuild' Spine.trigger 'navrebuild'
# redirect to login # redirect to login
@navigate 'login' @navigate 'login'

View file

@ -5,36 +5,47 @@ class App.Navigation extends App.Controller
super super
@log 'nav...' @log 'nav...'
@render() @render()
sync_ticket_overview = =>
@interval( @ticket_overview, 30000, 'nav_ticket_overview' )
sync_recent_viewed = => # update selected item
@interval( @recent_viewed, 40000, 'nav_recent_viewed' )
Spine.bind 'navupdate', (data) => Spine.bind 'navupdate', (data) =>
@update(arguments[0]) @update(arguments[0])
# rebuild nav bar with given user data
Spine.bind 'navrebuild', (user) => Spine.bind 'navrebuild', (user) =>
@log 'navbarrebuild', user @log 'navbarrebuild', user
@render(user) @render(user)
Spine.bind 'navupdate_remote', (user) => # rebuild ticket overview data
@log 'navupdate_remote' Spine.bind 'navupdate_ticket_overview', (data) =>
@delay( sync_ticket_overview, 500 ) @ticket_overview_build(data)
@delay( sync_recent_viewed, 1000 )
# rebuild recent viewd data
# rerender if new overview data is there Spine.bind 'update_recent_viewed', (data) =>
@delay( sync_ticket_overview, 800 ) @recent_viewed_build(data)
@delay( sync_recent_viewed, 1000 )
render: (user) -> render: (user) ->
nav_left = @getItems( navbar: Config.NavBar ) nav_left = @getItems( navbar: Config.NavBar )
nav_right = @getItems( navbar: Config.NavBarRight ) nav_right = @getItems( navbar: Config.NavBarRight )
# get open tabs to repopen on rerender
open_tab = {}
@el.find('.open').children('a').each( (i,d) =>
href = $(d).attr('href')
open_tab[href] = true
)
# get active tabs to reactivate on rerender
active_tab = {}
@el.find('.active').children('a').each( (i,d) =>
href = $(d).attr('href')
active_tab[href] = true
)
@html App.view('navigation')( @html App.view('navigation')(
navbar_left: nav_left, navbar_left: nav_left,
navbar_right: nav_right, navbar_right: nav_right,
open_tab: open_tab,
active_tab: active_tab,
user: user, user: user,
) )
@ -119,118 +130,65 @@ class App.Navigation extends App.Controller
update: (url) => update: (url) =>
@el.find('li').removeClass('active') @el.find('li').removeClass('active')
# if url isnt '#'
@el.find("[href=\"#{url}\"]").parents('li').addClass('active') @el.find("[href=\"#{url}\"]").parents('li').addClass('active')
# @el.find("[href*=\"#{url}\"]").parents('li').addClass('active')
# get data ticket_overview_build: (data) =>
ticket_overview: =>
# do no load and rerender if sub-menu is open # remove old views
open = @el.find('.open').val() for key of Config.NavBar
if open isnt undefined if Config.NavBar[key].parent is '#ticket/view'
return delete Config.NavBar[key]
# do no load and rerender if user is not logged in
if !window.Session['id']
return
# only of lod request is already done # add new views
for item in data
Config.NavBar['TicketOverview' + item.url] = {
prio: item.prio,
parent: '#ticket/view',
name: item.name,
count: item.count,
target: '#ticket/view/' + item.url,
role: ['Agent'],
}
if !@req_overview # rebuild navbar
@req_overview = App.Com.ajax( Spine.trigger 'navrebuild', window.Session
id: 'navbar_ticket_overviews',
type: 'GET',
url: '/ticket_overviews',
data: {},
processData: true,
success: (data, status, xhr) =>
# remove old views
for key of Config.NavBar
if Config.NavBar[key].parent is '#ticket/view'
delete Config.NavBar[key]
# add new views
for item in data
Config.NavBar['TicketOverview' + item.url] = {
prio: item.prio,
parent: '#ticket/view',
name: item.name,
count: item.count,
target: '#ticket/view/' + item.url,
role: ['Agent'],
}
# rebuild navbar
Spine.trigger 'navrebuild', window.Session
# reset ajax call recent_viewed_build: (data) =>
@req_overview = undefined
)
# get data items = data.recent_viewed
recent_viewed: =>
# do no load and rerender if sub-menu is open # load user collection
open = @el.find('.open').val() @loadCollection( type: 'User', data: data.users )
if open isnt undefined
return
# do no load and rerender if user is not logged in
if !window.Session['id']
return
# only of lod request is already done # load ticket collection
if !@req_recent_viewed @loadCollection( type: 'Ticket', data: data.tickets )
@req_recent_viewed = App.Com.ajax(
id: 'navbar_recent_viewed',
type: 'GET',
url: '/recent_viewed',
data: {
limit: 5,
}
processData: true,
success: (data, status, xhr) =>
items = data.recent_viewed
# load user collection
@loadCollection( type: 'User', data: data.users )
# load ticket collection
@loadCollection( type: 'Ticket', data: data.tickets )
# remove old views
for key of Config.NavBarRight
if Config.NavBarRight[key].parent is '#current_user'
part = Config.NavBarRight[key].target.split '::'
if part is 'RecendViewed'
delete Config.NavBarRight[key]
# add new views
prio = 5000
for item in items
divider = false
navheader = false
if prio is 5000
divider = true
navheader = 'Recent Viewed'
ticket = App.Ticket.find(item.o_id)
prio++
Config.NavBarRight['RecendViewed::' + ticket.id] = {
prio: prio,
parent: '#current_user',
name: item.history_object.name + ' (' + ticket.title + ')',
target: '#ticket/zoom/' + ticket.id,
role: ['Agent'],
divider: divider,
navheader: navheader
}
# rebuild navbar
Spine.trigger 'navrebuild', window.Session
# reset ajax call # remove old views
@req_recent_viewed = undefined for key of Config.NavBarRight
) if Config.NavBarRight[key].parent is '#current_user'
part = key.split '::'
if part[0] is 'RecendViewed'
delete Config.NavBarRight[key]
# add new views
prio = 8000
for item in items
divider = false
navheader = false
if prio is 8000
divider = true
navheader = 'Recent Viewed'
ticket = App.Ticket.find(item.o_id)
prio++
Config.NavBarRight['RecendViewed::' + ticket.id + '-' + prio ] = {
prio: prio,
parent: '#current_user',
name: item.history_object.name + ' (' + ticket.title + ')',
target: '#ticket/zoom/' + ticket.id,
role: ['Agent'],
divider: divider,
navheader: navheader
}
# rebuild navbar
Spine.trigger 'navrebuild', window.Session

View file

@ -11,6 +11,7 @@
#= require ./lib/bootstrap-popover.js #= require ./lib/bootstrap-popover.js
#= require ./lib/bootstrap-modal.js #= require ./lib/bootstrap-modal.js
#= require ./lib/bootstrap-tab.js #= require ./lib/bootstrap-tab.js
#= require ./lib/bootstrap-transition.js
#= require ./lib/underscore.coffee #= require ./lib/underscore.coffee
#= require ./lib/ba-linkify.js #= require ./lib/ba-linkify.js
@ -23,6 +24,7 @@
#not_used= require_tree ./lib #not_used= require_tree ./lib
#= require_self #= require_self
#= require ./lib/ajax.js.coffee #= require ./lib/ajax.js.coffee
#= require ./lib/websocket.js.coffee
#= require ./lib/auth.js.coffee #= require ./lib/auth.js.coffee
#= require ./lib/i18n.js.coffee #= require ./lib/i18n.js.coffee
#= require_tree ./models #= require_tree ./models
@ -57,13 +59,15 @@ Config2.set( 'a', 123)
console.log '1112222', Config2.get( 'a') console.log '1112222', Config2.get( 'a')
### ###
class App.Run extends Spine.Controller class App.Run extends Spine.Controller
constructor: -> constructor: ->
super super
@log 'RUN app'#, @ @log 'RUN app'
@el = $('#app') @el = $('#app')
# create web socket connection
App.WebSocket.connect()
# init of i18n # init of i18n
new App.i18n new App.i18n
@ -84,27 +88,6 @@ class App.Run extends Spine.Controller
window.Session['UISelection'] = @getSelected() + '' window.Session['UISelection'] = @getSelected() + ''
) )
# @ws = new WebSocket("ws://localhost:3001/");
# Set event handlers.
# @ws.onopen = ->
# console.log("onopen")
# @ws.onmessage = (e) ->
# e.data contains received string.
# console.log("onmessage: " + e.data)
# eval e.data
# Spine.bind 'ws:send', (data) =>
# @log 'ws:send', data
# @ws.send(data);
# @ws.onclose = ->
# console.log("onclose")
# @ws.onerror = ->
# console.log("onerror")
getSelected: -> getSelected: ->
text = ''; text = '';
if window.getSelection if window.getSelection
@ -125,24 +108,35 @@ class App.Content extends Spine.Controller
for route, callback of Config.Routes for route, callback of Config.Routes
do (route, callback) => do (route, callback) =>
@route(route, (params) -> @route(route, (params) ->
# remember current controller
Config['ActiveController'] = route Config['ActiveController'] = route
Spine.trigger( 'ws:send', JSON.stringify( { action: 'active_controller', controller: route, params: params } ) )
# send current controller
params_only = {}
for i of params
if typeof params[i] isnt 'object'
params_only[i] = params[i]
App.WebSocket.send(
action: 'active_controller',
controller: route,
params: params_only,
)
# unbind in controller area # unbind in controller area
@el.unbind() @el.unbind()
@el.undelegate() @el.undelegate()
# remove waypoints # remove waypoints
$('footer').waypoint('remove') $('footer').waypoint('remove')
params.el = @el params.el = @el
params.auth = @auth
new callback( params ) new callback( params )
# scroll to top # scroll to top
# window.scrollTo(0,0) # window.scrollTo(0,0)
) )
Spine.Route.setup() Spine.Route.setup()
window.App = App window.App = App

View file

@ -34,6 +34,9 @@ class App.Auth
# empty session # empty session
window.Session = {} window.Session = {}
# update websocked auth info
App.WebSocket.auth()
# rebuild navbar with new navbar items # rebuild navbar with new navbar items
Spine.trigger 'navrebuild' Spine.trigger 'navrebuild'
@ -50,6 +53,9 @@ class App.Auth
# store user data # store user data
for key, value of data.session for key, value of data.session
window.Session[key] = value window.Session[key] = value
# update websocked auth info
App.WebSocket.auth()
# refresh/load default collections # refresh/load default collections
for key, value of data.default_collections for key, value of data.default_collections
@ -61,12 +67,14 @@ class App.Auth
# rebuild navbar with updated ticket count of overviews # rebuild navbar with updated ticket count of overviews
Spine.trigger 'navupdate_remote' Spine.trigger 'navupdate_remote'
error: (xhr, statusText, error) => error: (xhr, statusText, error) =>
console.log 'loginCheck:error'#, error, statusText, xhr.statusCode console.log 'loginCheck:error'#, error, statusText, xhr.statusCode
# empty session # empty session
window.Session = {} window.Session = {}
# update websocked auth info
App.WebSocket.auth()
) )
@logout: -> @logout: ->
@ -75,4 +83,13 @@ class App.Auth
id: 'logout', id: 'logout',
type: 'DELETE', type: 'DELETE',
url: '/signout', url: '/signout',
success: =>
# update websocked auth info
App.WebSocket.auth()
error: (xhr, statusText, error) =>
# update websocked auth info
App.WebSocket.auth()
) )

View file

@ -0,0 +1,61 @@
/* ===================================================
* bootstrap-transition.js v2.0.4
* http://twitter.github.com/bootstrap/javascript.html#transitions
* ===================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function ($) {
$(function () {
"use strict"; // jshint ;_;
/* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
* ======================================================= */
$.support.transition = (function () {
var transitionEnd = (function () {
var el = document.createElement('bootstrap')
, transEndEventNames = {
'WebkitTransition' : 'webkitTransitionEnd'
, 'MozTransition' : 'transitionend'
, 'OTransition' : 'oTransitionEnd'
, 'msTransition' : 'MSTransitionEnd'
, 'transition' : 'transitionend'
}
, name
for (name in transEndEventNames){
if (el.style[name] !== undefined) {
return transEndEventNames[name]
}
}
}())
return transitionEnd && {
end: transitionEnd
}
})()
})
}(window.jQuery);

View file

@ -0,0 +1,114 @@
$ = jQuery.sub()
class App.WebSocket
_instance = undefined # Must be declared here to force the closure on the class
@connect: (args) -> # Must be a static method
if _instance == undefined
_instance ?= new _Singleton
_instance
@send: (args) -> # Must be a static method
@connect()
_instance.send(args)
@auth: (args) -> # Must be a static method
@connect()
_instance.auth(args)
# The actual Singleton class
class _Singleton extends Spine.Controller
queue: []
constructor: (@args) ->
@connect()
send: (data) =>
console.log 'ws:send trying', data, @ws, @ws.readyState
# A value of 0 indicates that the connection has not yet been established.
# A value of 1 indicates that the connection is established and communication is possible.
# A value of 2 indicates that the connection is going through the closing handshake.
# A value of 3 indicates that the connection has been closed or could not be opened.
if @ws.readyState is 0
@queue.push data
else
console.log( 'ws:send', data )
string = JSON.stringify( data )
@ws.send(string)
auth: (data) =>
# logon websocket
data = {
action: 'login',
session: window.Session
}
@send(data)
close: =>
@ws.close()
connect: =>
# console.log '------------ws connect....--------------'
if !window.WebSocket
@error = new App.ErrorModal(
message: 'Sorry, no websocket support!'
)
return
@ws = new window.WebSocket( "ws://" + window.location.hostname + ":6042/" )
# Set event handlers.
@ws.onopen = =>
console.log( "onopen" )
# close error message if exists
if @error
@error.modalHide()
@error = undefined
@auth()
# empty queue
for item in @queue
console.log( 'ws:send queue', item )
@send(item)
@queue = []
@ws.onmessage = (e) ->
pipe = JSON.parse( e.data )
# console.log( "ws:onmessage", pipe )
# go through all blocks
for item in pipe
# fill collection
if item['collection']
console.log( "ws:onmessage collection:" + item['collection'] )
window.LastRefresh[ item['collection'] ] = item['data']
# fire event
if item['event']
console.log( "ws:onmessage event:" + item['event'] )
Spine.trigger( item['event'], item['data'] )
# bind to send messages
Spine.bind 'ws:send', (data) =>
@send(data)
@ws.onclose = (e) =>
console.log( "onclose", e )
# show error message
if !@error
@error = new App.ErrorModal(
message: 'No connection to websocket, trying to reconnect...'
)
# try reconnect after 5 sec.
@delay @connect, 5000
@ws.onerror = ->
console.log( "onerror" )

View file

@ -0,0 +1,17 @@
<div class="modal-header">
<!--
<a href="#" class="close">&times;</a>
-->
<h3><%- T('Error') %></h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<p><%= @message %>
</form>
</div>
<div class="modal-footer">
<!--
<button class="btn btn-primary submit"><%- T('Submit') %></button>
<button class="btn cancel"><%- T('Cancel') %></button>
-->
</div>

View file

@ -5,7 +5,7 @@
<ul class="nav"> <ul class="nav">
<% for item in @navbar_left: %> <% for item in @navbar_left: %>
<% if item.child: %> <% if item.child: %>
<li class="dropdown"> <li class="dropdown <% if @open_tab[item.target] : %>open<% end %>">
<a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a> <a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<% for item in item.child: %> <% for item in item.child: %>
@ -15,12 +15,12 @@
<% if item.navheader: %> <% if item.navheader: %>
<li class="nav-header"><%- T(item.navheader) %></li> <li class="nav-header"><%- T(item.navheader) %></li>
<% end %> <% end %>
<li><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li> <li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li>
<% end %> <% end %>
</ul> </ul>
</li> </li>
<% else: %> <% else: %>
<li><a href="<%= item.target %>"><%- T(item.name) %></a></li> <li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %></a></li>
<% end %> <% end %>
<% end %> <% end %>
</ul> </ul>
@ -32,7 +32,7 @@
<ul class="nav pull-right"> <ul class="nav pull-right">
<% for item in @navbar_right: %> <% for item in @navbar_right: %>
<% if item.child: %> <% if item.child: %>
<li class="dropdown"> <li class="dropdown <% if @open_tab[item.target] : %>open<% end %>">
<a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a> <a href="<%= item.target %>" class="dropdown-toggle" data-toggle="dropdown"><%- T(item.name) %> <b class="caret"></b></a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<% for item in item.child: %> <% for item in item.child: %>
@ -42,12 +42,12 @@
<% if item.navheader: %> <% if item.navheader: %>
<li class="nav-header"><%- T(item.navheader) %></li> <li class="nav-header"><%- T(item.navheader) %></li>
<% end %> <% end %>
<li><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li> <li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %><% if item['count'] isnt undefined: %> <span class="count">(<%= item['count'] %>)</span><% end %></a></li>
<% end %> <% end %>
</ul> </ul>
</li> </li>
<% else: %> <% else: %>
<li><a href="<%= item.target %>"><%- T(item.name) %></a></li> <li class="<% if @active_tab[item.target] : %>active<% end %>"><a href="<%= item.target %>"><%- T(item.name) %></a></li>
<% end %> <% end %>
<% end %> <% end %>
</ul> </ul>

View file

@ -3,56 +3,10 @@ class ActivityController < ApplicationController
# GET /activity_stream # GET /activity_stream
def activity_stream def activity_stream
activity_stream = History.activity_stream(current_user, params[:limit]) activity_stream = History.activity_stream_fulldata(current_user, params[:limit])
# get related users
users = {}
tickets = []
articles = []
activity_stream.each {|item|
# load article ids
if item['history_object'] == 'Ticket'
ticket = Ticket.find( item['o_id'] ).attributes
tickets.push ticket
# load users
if !users[ ticket['owner_id'] ]
users[ ticket['owner_id'] ] = user_data_full( ticket['owner_id'] )
end
if !users[ ticket['customer_id'] ]
users[ ticket['customer_id'] ] = user_data_full( ticket['customer_id'] )
end
end
if item['history_object'] == 'Ticket::Article'
article = Ticket::Article.find( item['o_id'] ).attributes
if !article['subject'] || article['subject'] == ''
article['subject'] = Ticket.find( article['ticket_id'] ).title
end
articles.push article
# load users
if !users[ article['created_by_id'] ]
users[ article['created_by_id'] ] = user_data_full( article['created_by_id'] )
end
end
if item['history_object'] == 'User'
users[ item['o_id'] ] = user_data_full( item['o_id'] )
end
# load users
if !users[ item['created_by_id'] ]
users[ item['created_by_id'] ] = user_data_full( item['created_by_id'] )
end
}
# return result # return result
render :json => { render :json => activity_stream
:activity_stream => activity_stream,
:tickets => tickets,
:articles => articles,
:users => users,
}
end end
end end

View file

@ -161,52 +161,7 @@ class ApplicationController < ActionController::Base
end end
def user_data_full (user_id) def user_data_full (user_id)
user = User.user_data_full(user_id)
# get user
user = User.find_fulldata(user_id)
# do not show password
user['password'] = ''
# TEMP: compat. reasons
user['preferences'] = {} if user['preferences'] == nil
items = []
if user['preferences'][:tickets_open].to_i > 0
item = {
:url => '',
:name => 'open',
:count => user['preferences'][:tickets_open] || 0,
:title => 'Open Tickets',
:class => 'user-tickets',
:data => 'open'
}
items.push item
end
if user['preferences'][:tickets_closed].to_i > 0
item = {
:url => '',
:name => 'closed',
:count => user['preferences'][:tickets_closed] || 0,
:title => 'Closed Tickets',
:class => 'user-tickets',
:data => 'closed'
}
items.push item
end
# show linked topics and items
if items.count > 0
topic = {
:title => 'Tickets',
:items => items,
}
user['links'] = []
user['links'].push topic
end
return user
end end
end end

View file

@ -3,36 +3,10 @@ class RecentViewedController < ApplicationController
# GET /recent_viewed # GET /recent_viewed
def recent_viewed def recent_viewed
recent_viewed = History.recent_viewed(current_user) recent_viewed = History.recent_viewed_fulldata(current_user)
# get related users
users = {}
tickets = []
recent_viewed.each {|item|
# load article ids
# if item.history_object == 'Ticket'
tickets.push Ticket.find( item['o_id'] ).attributes
# end
# if item.history_object 'Ticket::Article'
# tickets.push Ticket::Article.find(item.o_id)
# end
# if item.history_object 'User'
# tickets.push User.find(item.o_id)
# end
# load users
if !users[ item['created_by_id'] ]
users[ item['created_by_id'] ] = user_data_full( item['created_by_id'] )
end
}
# return result # return result
render :json => { render :json => recent_viewed
:recent_viewed => recent_viewed,
:tickets => tickets,
:users => users,
}
end end
end end

View file

@ -3,33 +3,9 @@ class RssController < ApplicationController
# GET /rss_fetch # GET /rss_fetch
def fetch def fetch
url = params[:url] items = RSS.fetch(params[:url], params[:limit])
limit = params[:limit] || 10 if items == nil
render :json => { :message => "failed to fetch #{ params[:url] }", :status => :unprocessable_entity }
cache_key = 'rss::' + url
items = Rails.cache.read( cache_key )
if !items
response = Net::HTTP.get_response( URI.parse(url) )
if response.code.to_s != '200'
render :json => { :message => "failed to fetch #{url}, code: #{response.code}"}, :status => :unprocessable_entity
return
end
rss = SimpleRSS.parse response.body
items = []
fetched = 0
rss.items.each { |item|
record = {
:id => item.id,
:title => item.title,
:summary => item.summary,
:link => item.link,
:published => item.published
}
items.push record
fetched += 1
break item if fetched == limit.to_i
}
Rails.cache.write( cache_key, items, :expires_in => 4.hours )
end end
render :json => { :items => items } render :json => { :items => items }
end end

View file

@ -136,14 +136,65 @@ class History < ActiveRecord::Base
end end
return datas return datas
end end
def self.activity_stream_fulldata(user, limit = 10)
activity_stream = History.activity_stream( user, limit )
# get related users
users = {}
tickets = []
articles = []
activity_stream.each {|item|
# load article ids
if item['history_object'] == 'Ticket'
ticket = Ticket.find( item['o_id'] ).attributes
tickets.push ticket
# load users
if !users[ ticket['owner_id'] ]
users[ ticket['owner_id'] ] = User.user_data_full( ticket['owner_id'] )
end
if !users[ ticket['customer_id'] ]
users[ ticket['customer_id'] ] = User.user_data_full( ticket['customer_id'] )
end
end
if item['history_object'] == 'Ticket::Article'
article = Ticket::Article.find( item['o_id'] ).attributes
if !article['subject'] || article['subject'] == ''
article['subject'] = Ticket.find( article['ticket_id'] ).title
end
articles.push article
# load users
if !users[ article['created_by_id'] ]
users[ article['created_by_id'] ] = User.user_data_full( article['created_by_id'] )
end
end
if item['history_object'] == 'User'
users[ item['o_id'] ] = User.user_data_full( item['o_id'] )
end
# load users
if !users[ item['created_by_id'] ]
users[ item['created_by_id'] ] = User.user_data_full( item['created_by_id'] )
end
}
return {
:activity_stream => activity_stream,
:tickets => tickets,
:articles => articles,
:users => users,
}
end
def self.recent_viewed(user) def self.recent_viewed(user)
# g = Group.where( :active => true ).joins(:users).where( 'users.id' => user.id ) # g = Group.where( :active => true ).joins(:users).where( 'users.id' => user.id )
stream = History.select("distinct(histories.o_id), created_by_id, history_attribute_id, history_type_id, history_object_id, value_from, value_to"). stream = History.select("distinct(o_id), created_by_id, history_type_id, history_object_id, created_at").
where( :history_object_id => History::Object.where( :name => 'Ticket').first.id ). where( :history_object_id => History::Object.where( :name => 'Ticket').first.id ).
where( :history_type_id => History::Type.where( :name => ['viewed']) ). where( :history_type_id => History::Type.where( :name => ['viewed'] ) ).
where( :created_by_id => user.id ). where( :created_by_id => user.id ).
order('created_at DESC, id DESC'). order('created_at DESC, id ASC').
limit(10) limit(10)
datas = [] datas = []
stream.each do |item| stream.each do |item|
@ -153,9 +204,49 @@ class History < ActiveRecord::Base
datas.push data datas.push data
# item['history_attribute'] = item.history_attribute # item['history_attribute'] = item.history_attribute
end end
# puts 'pppppppppp'
# puts datas.inspect
return datas return datas
end end
def self.recent_viewed_fulldata(user)
recent_viewed = History.recent_viewed(user)
# get related users
users = {}
tickets = []
recent_viewed.each {|item|
# load article ids
# if item.history_object == 'Ticket'
ticket = Ticket.find( item['o_id'] ).attributes
tickets.push ticket
# end
# if item.history_object 'Ticket::Article'
# tickets.push Ticket::Article.find(item.o_id)
# end
# if item.history_object 'User'
# tickets.push User.find(item.o_id)
# end
# load users
if !users[ ticket['owner_id'] ]
users[ ticket['owner_id'] ] = User.user_data_full( ticket['owner_id'] )
end
if !users[ ticket['created_by_id'] ]
users[ ticket['created_by_id'] ] = User.user_data_full( ticket['created_by_id'] )
end
if !users[ item['created_by_id'] ]
users[ item['created_by_id'] ] = User.user_data_full( item['created_by_id'] )
end
}
return {
:recent_viewed => recent_viewed,
:tickets => tickets,
:users => users,
}
end
private private
def check_type def check_type
puts '--------------' puts '--------------'

View file

@ -123,7 +123,6 @@ class Ticket < ActiveRecord::Base
return subject return subject
end end
# Ticket.overview( # Ticket.overview(
# :view => 'some_view_url', # :view => 'some_view_url',
# :current_user_id => 123, # :current_user_id => 123,
@ -142,7 +141,7 @@ class Ticket < ActiveRecord::Base
overview.condition[item] = 'current_user.id' overview.condition[item] = 'current_user.id'
end end
} }
# remember selected view # remember selected view
if data[:view] && data[:view] == overview.meta[:url] if data[:view] && data[:view] == overview.meta[:url]
overview_selected = overview overview_selected = overview
@ -192,10 +191,10 @@ class Ticket < ActiveRecord::Base
# get count # get count
count = Ticket.where( :group_id => group_ids ).where( overview.condition ).count() count = Ticket.where( :group_id => group_ids ).where( overview.condition ).count()
# get meta info # get meta info
all = overview.meta all = overview.meta
# push to result data # push to result data
result.push all.merge( { :count => count } ) result.push all.merge( { :count => count } )
} }
@ -261,7 +260,7 @@ class Ticket < ActiveRecord::Base
end end
end end
def destroy_dependencies def destroy_dependencies
# delete history # delete history
History.history_destroy( 'Ticket', self.id ) History.history_destroy( 'Ticket', self.id )
@ -293,14 +292,14 @@ class Ticket < ActiveRecord::Base
belongs_to :ticket_article_type, :class_name => 'Ticket::Article::Type' belongs_to :ticket_article_type, :class_name => 'Ticket::Article::Type'
belongs_to :ticket_article_sender, :class_name => 'Ticket::Article::Sender' belongs_to :ticket_article_sender, :class_name => 'Ticket::Article::Sender'
belongs_to :created_by, :class_name => 'User' belongs_to :created_by, :class_name => 'User'
private private
def fillup def fillup
# if sender is customer, do not change anything # if sender is customer, do not change anything
sender = Ticket::Article::Sender.where( :id => self.ticket_article_sender_id ).first sender = Ticket::Article::Sender.where( :id => self.ticket_article_sender_id ).first
return if sender == nil || sender['name'] == 'Customer' return if sender == nil || sender['name'] == 'Customer'
type = Ticket::Article::Type.where( :id => self.ticket_article_type_id ).first type = Ticket::Article::Type.where( :id => self.ticket_article_type_id ).first
ticket = Ticket.find(self.ticket_id) ticket = Ticket.find(self.ticket_id)
@ -337,10 +336,10 @@ class Ticket < ActiveRecord::Base
end end
end end
def attachment_check def attachment_check
# do nothing if no attachment exists # do nothing if no attachment exists
return 1 if self['attachments'] == nil return 1 if self['attachments'] == nil
# store attachments # store attachments
article_store = [] article_store = []
self.attachments.each do |attachment| self.attachments.each do |attachment|
@ -363,9 +362,9 @@ class Ticket < ActiveRecord::Base
type = Ticket::Article::Type.where( :id => self.ticket_article_type_id ).first type = Ticket::Article::Type.where( :id => self.ticket_article_type_id ).first
ticket = Ticket.find(self.ticket_id) ticket = Ticket.find(self.ticket_id)
# if sender is agent or system # if sender is agent or system
# create tweet # create tweet
if type['name'] == 'twitter direct-message' || type['name'] == 'twitter status' if type['name'] == 'twitter direct-message' || type['name'] == 'twitter status'
a = Channel::Twitter2.new a = Channel::Twitter2.new
@ -381,7 +380,7 @@ class Ticket < ActiveRecord::Base
self.message_id = message.id self.message_id = message.id
self.save self.save
end end
# post facebook comment # post facebook comment
if type['name'] == 'facebook' if type['name'] == 'facebook'
a = Channel::Facebook.new a = Channel::Facebook.new
@ -393,13 +392,13 @@ class Ticket < ActiveRecord::Base
} }
) )
end end
# send email # send email
if type['name'] == 'email' if type['name'] == 'email'
# build subject # build subject
subject = ticket.subject_build(self.subject) subject = ticket.subject_build(self.subject)
# send email # send email
a = Channel::IMAP.new a = Channel::IMAP.new
message = a.send( message = a.send(
@ -414,7 +413,7 @@ class Ticket < ActiveRecord::Base
:attachments => self.attachments :attachments => self.attachments
} }
) )
# store mail plain # store mail plain
Store.add( Store.add(
:object => 'Ticket::Article::Mail', :object => 'Ticket::Article::Mail',
@ -428,12 +427,12 @@ class Ticket < ActiveRecord::Base
class Flag < ActiveRecord::Base class Flag < ActiveRecord::Base
end end
class Sender < ActiveRecord::Base class Sender < ActiveRecord::Base
end end
class Type < ActiveRecord::Base class Type < ActiveRecord::Base
end end
end end
end end

View file

@ -142,14 +142,14 @@ Your #{config.product_name} Team
end end
def self.password_reset_via_token(token,password) def self.password_reset_via_token(token,password)
# check token # check token
token = Token.check( :action => 'PasswordReset', :name => token ) token = Token.check( :action => 'PasswordReset', :name => token )
return if !token return if !token
# reset password # reset password
token.user.update_attributes( :password => password ) token.user.update_attributes( :password => password )
# delete token # delete token
token.delete token.delete
token.save token.save
@ -176,7 +176,7 @@ Your #{config.product_name} Team
:username => authorization[:username] :username => authorization[:username]
} }
end end
# set roles # set roles
roles = [] roles = []
user.roles.select('id, name').where( :active => true ).each { |role| user.roles.select('id, name').where( :active => true ).each { |role|
@ -206,6 +206,54 @@ Your #{config.product_name} Team
return data return data
end end
def self.user_data_full (user_id)
# get user
user = User.find_fulldata(user_id)
# do not show password
user['password'] = ''
# TEMP: compat. reasons
user['preferences'] = {} if user['preferences'] == nil
items = []
if user['preferences'][:tickets_open].to_i > 0
item = {
:url => '',
:name => 'open',
:count => user['preferences'][:tickets_open] || 0,
:title => 'Open Tickets',
:class => 'user-tickets',
:data => 'open'
}
items.push item
end
if user['preferences'][:tickets_closed].to_i > 0
item = {
:url => '',
:name => 'closed',
:count => user['preferences'][:tickets_closed] || 0,
:title => 'Closed Tickets',
:class => 'user-tickets',
:data => 'closed'
}
items.push item
end
# show linked topics and items
if items.count > 0
topic = {
:title => 'Tickets',
:items => items,
}
user['links'] = []
user['links'].push topic
end
return user
end
# update all users geo data # update all users geo data
def self.geo_update_all def self.geo_update_all

View file

@ -13,8 +13,11 @@ require 'google_oauth2_database'
# load notification factory (replace all tags) # load notification factory (replace all tags)
require 'notification_factory' require 'notification_factory'
# load gmaps lookup # load lib
require 'gmaps' require 'gmaps'
require 'rss'
require 'web_socket'
# Initialize the rails application # Initialize the rails application
Zammad::Application.initialize! Zammad::Application.initialize!

31
lib/rss.rb Normal file
View file

@ -0,0 +1,31 @@
module RSS
def self.fetch(url, limit = 10)
cache_key = 'rss::' + url
items = Rails.cache.read( cache_key )
if !items
puts 'fetch rss...'
response = Net::HTTP.get_response( URI.parse(url) )
if response.code.to_s != '200'
return
end
rss = SimpleRSS.parse response.body
items = []
fetched = 0
rss.items.each { |item|
record = {
:id => item.id,
:title => item.title,
:summary => item.summary,
:link => item.link,
:published => item.published
}
items.push record
fetched += 1
break item if fetched == limit.to_i
}
Rails.cache.write( cache_key, items, :expires_in => 4.hours )
end
return items
end
end

175
lib/web_socket.rb Normal file
View file

@ -0,0 +1,175 @@
require 'json'
module Session
@path = '/tmp/websocket'
def self.create( client_id, session )
path = @path + '/' + client_id.to_s
FileUtils.mkpath path
File.open( path + '/session', 'w' ) { |file|
user = { :id => session['id'] }
file.puts Marshal.dump(user)
}
end
def self.get( client_id )
session_file = @path + '/' + client_id.to_s + '/session'
data = nil
return if !File.exist? session_file
File.open( session_file, 'r' ) { |file|
all = ''
while line = file.gets
all = all + line
end
begin
data = Marshal.load( all )
rescue
return
end
}
return data
end
def self.transaction( client_id, data )
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s
if File::exists?( filename )
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-1'
if File::exists?( filename )
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-2'
if File::exists?( filename )
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-3'
if File::exists?( filename )
filename = @path + '/' + client_id.to_s + '/transaction-' + Time.new().to_i.to_s + '-4'
end
end
end
end
File.open( filename, 'w' ) { |file|
file.puts data.to_json
}
return true
end
def self.jobs
state_client_ids = {}
while true
client_ids = self.sessions
client_ids.each { |client_id|
if !state_client_ids[client_id]
state_client_ids[client_id] = {}
end
# get current user
user_session = Session.get( client_id )
next if !user_session
next if !user_session[:id]
user = User.find( user_session[:id] )
# overviews
result = Ticket.overview(
:current_user_id => user.id,
)
if state_client_ids[client_id][:overview] != result
state_client_ids[client_id][:overview] = result
# send update to browser
Session.transaction( client_id, {
:action => 'load',
:data => result,
:event => 'navupdate_ticket_overview',
})
end
# recent viewed
recent_viewed = History.recent_viewed(user)
if state_client_ids[client_id][:recent_viewed] != recent_viewed
state_client_ids[client_id][:recent_viewed] = recent_viewed
# tickets and users
recent_viewed = History.recent_viewed_fulldata(user)
# send update to browser
Session.transaction( client_id, {
:action => 'load',
:data => recent_viewed,
:event => 'update_recent_viewed',
})
end
# activity stream
activity_stream = History.activity_stream(user)
if state_client_ids[client_id][:activity_stream] != activity_stream
state_client_ids[client_id][:activity_stream] = activity_stream
activity_stream = History.activity_stream_fulldata(user)
# send update to browser
Session.transaction( client_id, {
:event => 'activity_stream_rebuild',
:collection => 'activity_stream',
:data => activity_stream,
})
end
# rss view
rss_items = RSS.fetch( 'http://www.heise.de/newsticker/heise-atom.xml', 8 )
if state_client_ids[client_id][:rss_items] != rss_items
state_client_ids[client_id][:rss_items] = rss_items
# send update to browser
Session.transaction( client_id, {
:event => 'rss_rebuild',
:collection => 'dashboard_rss',
:data => {
head: 'Heise ATOM',
items: rss_items,
},
})
end
sleep 1
}
end
end
def self.sessions
path = @path + '/'
data = []
Dir.foreach( path ) do |entry|
if entry != '.' && entry != '..'
data.push entry
end
end
return data
end
def self.queue( client_id )
path = @path + '/' + client_id.to_s + '/'
data = []
Dir.foreach( path ) do |entry|
if /^transaction/.match( entry )
data.push Session.queue_file( path + entry )
end
end
return data
end
def self.queue_file( filename )
data = nil
File.open( filename, 'r' ) { |file|
all = ''
while line = file.gets
all = all + line
end
data = JSON.parse( all )
}
File.delete( filename )
return data
end
def self.destory( client_id )
path = @path + '/' + client_id.to_s
FileUtils.rm_rf path
end
end

View file

@ -0,0 +1,86 @@
$LOAD_PATH << './lib'
require 'rubygems'
require 'eventmachine'
require 'em-websocket'
require 'json'
require 'fileutils'
require 'web_socket'
require 'optparse'
# Look for -o with argument, and -I and -D boolean arguments
options = {
:p => 6042,
:b => '0.0.0.0',
}
OptionParser.new do |opts|
opts.banner = "Usage: websocket-server.rb [options]"
opts.on("-p", "--port [OPT]", "port of websocket server") do |p|
options[:p] = p
end
opts.on("-b", "--bind [OPT]", "bind address") do |b|
options[:b] = b
end
end.parse!
puts "Starting websocket server on #{ options[:b] }:#{ options[:p] }"
@clients = {}
EventMachine.run {
EventMachine::WebSocket.start( :host => options[:b], :port => options[:p] ) do |ws|
# register client connection
ws.onopen {
client_id = ws.object_id
puts 'Client ' + client_id.to_s + ' connected'
if !@clients.include? client_id
@clients[client_id] = {
:websocket => ws,
}
end
}
# unregister client connection
ws.onclose {
client_id = ws.object_id
puts 'Client ' + client_id.to_s + ' disconnected'
if @clients.include? client_id
@clients.delete client_id
end
Session.destory( client_id )
}
# manage messages
ws.onmessage { |msg|
client_id = ws.object_id
puts 'From Client ' + client_id.to_s + ' received message: ' + msg
data = JSON.parse(msg)
# get session
if data['action'] == 'login'
@clients[client_id][:session] = data['session']
Session.create( client_id, data['session'] )
end
}
end
EventMachine.add_periodic_timer(0.4) {
# puts "loop"
@clients.each { |client_id, client|
# puts 'checking client...' + client_id.to_s
begin
queue = Session.queue( client_id )
if queue && queue[0]
puts "send to #{client_id} " + queue.inspect
client[:websocket].send( queue.to_json )
end
rescue
puts 'problem'
end
}
}
}