puggernaut 0.1.5 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -1
- data/config/gemsets.yml +1 -0
- data/config/gemspec.yml +2 -1
- data/lib/puggernaut.rb +2 -1
- data/lib/puggernaut/client.rb +7 -3
- data/lib/puggernaut/server.rb +26 -29
- data/lib/puggernaut/server/channel.rb +36 -12
- data/lib/puggernaut/server/http.rb +19 -11
- data/lib/puggernaut/server/shared.rb +46 -0
- data/lib/puggernaut/server/websocket.rb +47 -0
- data/lib/puggernaut/spec_server.rb +16 -4
- data/public/puggernaut.js +157 -31
- data/public/spec.js +104 -16
- metadata +30 -8
data/README.md
CHANGED
@@ -79,7 +79,7 @@ Include [jQuery](http://jquery.com) and [puggernaut.js](https://github.com/winto
|
|
79
79
|
Javascript client example:
|
80
80
|
|
81
81
|
<pre>
|
82
|
-
Puggernaut.path = '/long_poll';
|
82
|
+
Puggernaut.path = '/long_poll'; // (default)
|
83
83
|
|
84
84
|
Puggernaut
|
85
85
|
.watch('channel', function(e, message) {
|
data/config/gemsets.yml
CHANGED
data/config/gemspec.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
name: puggernaut
|
2
|
-
version: 0.
|
2
|
+
version: 0.2.0
|
3
3
|
authors:
|
4
4
|
- Winton Welsh
|
5
5
|
email: mail@wintoni.us
|
@@ -7,5 +7,6 @@ homepage: http://github.com/winton/puggernaut
|
|
7
7
|
summary: Simple server push implementation using eventmachine and long polling
|
8
8
|
description: Simple server push implementation using eventmachine and long polling
|
9
9
|
dependencies:
|
10
|
+
- em-websocket
|
10
11
|
- eventmachine
|
11
12
|
development_dependencies: null
|
data/lib/puggernaut.rb
CHANGED
data/lib/puggernaut/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "#{File.dirname(__FILE__)}/logger"
|
2
2
|
require 'socket'
|
3
|
+
require 'timeout'
|
3
4
|
|
4
5
|
module Puggernaut
|
5
6
|
class Client
|
@@ -43,9 +44,12 @@ module Puggernaut
|
|
43
44
|
begin
|
44
45
|
host_port = "#{host}:#{port}"
|
45
46
|
logger.info "Client#send - #{host_port} - #{data}"
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
response = nil
|
48
|
+
Timeout.timeout(10) do
|
49
|
+
connection = @connections[host_port] ||= TCPSocket.open(host, port)
|
50
|
+
connection.print(data)
|
51
|
+
response = connection.gets
|
52
|
+
end
|
49
53
|
raise 'not ok' if !response || !response.include?('OK')
|
50
54
|
rescue Exception => e
|
51
55
|
logger.info "Client#send - Exception - #{e.message} - #{host_port} - #{data}"
|
data/lib/puggernaut/server.rb
CHANGED
@@ -1,44 +1,41 @@
|
|
1
|
+
require 'puggernaut/server/shared'
|
1
2
|
require 'puggernaut/server/http'
|
2
3
|
require 'puggernaut/server/channel'
|
3
4
|
require 'puggernaut/server/tcp'
|
5
|
+
require 'puggernaut/server/websocket'
|
4
6
|
|
5
7
|
module Puggernaut
|
6
8
|
class Server
|
7
9
|
|
8
10
|
include Logger
|
9
11
|
|
10
|
-
def initialize(http_port=8100, tcp_port=http_port.to_i+1)
|
11
|
-
puts "\nPuggernaut is starting on #{http_port} (HTTP) and #{
|
12
|
+
def initialize(http_port=8100, tcp_port=http_port.to_i+1, ws_port=tcp_port.to_i+1)
|
13
|
+
puts "\nPuggernaut is starting on #{http_port} (Long Poll HTTP), #{tcp_port} (Puggernaut TCP), and #{ws_port} (WebSocket TCP)"
|
12
14
|
puts "*snort*\n\n"
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
EM.
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
rescue Interrupt
|
31
|
-
logger.info "Server#initialize - Shutting down"
|
32
|
-
exit
|
33
|
-
rescue
|
34
|
-
errors += 1
|
35
|
-
logger.error "Server#initialize - Error - #{$!.message}"
|
36
|
-
logger.error "\t" + $!.backtrace.join("\n\t")
|
15
|
+
|
16
|
+
begin
|
17
|
+
Channel.channels = []
|
18
|
+
GC.start
|
19
|
+
EM.epoll if EM.epoll?
|
20
|
+
EM.run do
|
21
|
+
logger.info "Server#initialize - Starting HTTP - #{http_port}"
|
22
|
+
EM.start_server '0.0.0.0', http_port, Http
|
23
|
+
|
24
|
+
logger.info "Server#initialize - Starting TCP - #{tcp_port}"
|
25
|
+
EM.start_server '0.0.0.0', tcp_port, Tcp
|
26
|
+
|
27
|
+
logger.info "Server#initialize - Starting WebSocket - #{ws_port}"
|
28
|
+
Websocket.new '0.0.0.0', ws_port
|
29
|
+
|
30
|
+
errors = 0
|
37
31
|
end
|
32
|
+
rescue Interrupt
|
33
|
+
logger.info "Server#initialize - Shutting down"
|
34
|
+
exit
|
35
|
+
rescue
|
36
|
+
logger.error "Server#initialize - Error - #{$!.message}"
|
37
|
+
logger.error "\t" + $!.backtrace.join("\n\t")
|
38
38
|
end
|
39
|
-
|
40
|
-
puts "Exiting because of too many consecutive errors :("
|
41
|
-
puts "Check #{Dir.pwd}/log/puggernaut.log\n\n"
|
42
39
|
end
|
43
40
|
end
|
44
41
|
end
|
@@ -2,31 +2,32 @@ module Puggernaut
|
|
2
2
|
class Server
|
3
3
|
class Channel < EM::Channel
|
4
4
|
|
5
|
-
|
5
|
+
attr_reader :channels, :user_id
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(channels)
|
7
|
+
def initialize(channels, user_id)
|
10
8
|
@channels = channels
|
9
|
+
@user_id = user_id
|
11
10
|
super()
|
12
11
|
end
|
13
12
|
|
14
13
|
class <<self
|
14
|
+
|
15
|
+
include Logger
|
15
16
|
|
16
17
|
attr_accessor :channels
|
17
18
|
|
18
|
-
def create(channels)
|
19
|
-
channel = self.new(channels)
|
19
|
+
def create(channels, user_id)
|
20
|
+
channel = self.new(channels, user_id)
|
20
21
|
@channels ||= []
|
21
22
|
@channels << channel
|
22
23
|
channel
|
23
24
|
end
|
24
25
|
|
25
|
-
def
|
26
|
+
def all_messages_after_id(channel, identifier)
|
26
27
|
if @messages && @messages[channel]
|
27
28
|
found = false
|
28
29
|
(
|
29
|
-
@messages[channel].select { |(id, message)|
|
30
|
+
@messages[channel].select { |(id, message, time)|
|
30
31
|
found = true if id == identifier
|
31
32
|
found
|
32
33
|
}[1..-1] || []
|
@@ -38,22 +39,45 @@ module Puggernaut
|
|
38
39
|
[]
|
39
40
|
end
|
40
41
|
end
|
42
|
+
|
43
|
+
def all_messages_after_time(channel, after_time)
|
44
|
+
if @messages && @messages[channel]
|
45
|
+
(
|
46
|
+
@messages[channel].select { |(id, message, time)|
|
47
|
+
after_time < time
|
48
|
+
} || []
|
49
|
+
|
50
|
+
).collect { |message|
|
51
|
+
"#{channel}|#{message.join '|'}"
|
52
|
+
}
|
53
|
+
else
|
54
|
+
[]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def inhabitants(channel_name)
|
59
|
+
user_ids = @channels.collect do |channel|
|
60
|
+
channel.user_id if channel.channels.include?(channel_name)
|
61
|
+
end
|
62
|
+
user_ids.compact
|
63
|
+
end
|
41
64
|
|
42
|
-
def say(messages)
|
65
|
+
def say(messages, exclude_user_id=nil)
|
43
66
|
@messages ||= {}
|
44
67
|
messages = messages.inject({}) do |hash, (channel_name, messages)|
|
45
68
|
messages = messages.collect do |message|
|
46
|
-
[ rand.to_s[2..-1], message ]
|
69
|
+
[ rand.to_s[2..-1], message, Time.now ]
|
47
70
|
end
|
48
71
|
@messages[channel_name] ||= []
|
49
72
|
@messages[channel_name] += messages
|
50
|
-
|
51
|
-
|
73
|
+
@messages[channel_name] = @messages[channel_name].select do |message|
|
74
|
+
message[2] >= Time.now - 2 * 60 * 60
|
52
75
|
end
|
53
76
|
hash[channel_name] = messages
|
54
77
|
hash
|
55
78
|
end
|
56
79
|
@channels.each do |channel|
|
80
|
+
next if exclude_user_id && channel.user_id == exclude_user_id
|
57
81
|
push = channel.channels.collect do |channel_name|
|
58
82
|
if messages[channel_name]
|
59
83
|
messages[channel_name].collect { |message|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'cgi'
|
2
|
+
require 'time'
|
2
3
|
|
3
4
|
module Puggernaut
|
4
5
|
class Server
|
5
6
|
module Http
|
6
7
|
|
7
8
|
include Logger
|
9
|
+
include Shared
|
8
10
|
|
9
11
|
def receive_data(data)
|
10
12
|
lines = data.split(/[\r\n]+/)
|
@@ -21,27 +23,30 @@ module Puggernaut
|
|
21
23
|
end
|
22
24
|
|
23
25
|
if path == '/'
|
24
|
-
channels = query
|
25
|
-
lasts = query['last'].dup rescue []
|
26
|
+
channels, @join_leave, lasts, time, user_id = query_defaults(query)
|
26
27
|
|
27
28
|
unless channels.empty?
|
28
|
-
@channel = Channel.create(channels)
|
29
|
-
|
30
|
-
|
31
|
-
array += Channel.all_messages_after(channel, lasts.shift)
|
32
|
-
array
|
33
|
-
}.join("\n")
|
29
|
+
@channel = Channel.create(channels, user_id)
|
30
|
+
if @join_leave && user_id
|
31
|
+
join_channels(channels, user_id)
|
34
32
|
end
|
35
|
-
|
36
|
-
|
33
|
+
messages = gather_messages(channels, lasts, time)
|
34
|
+
unless messages.empty?
|
35
|
+
respond messages
|
37
36
|
else
|
38
37
|
EM::Timer.new(30) { respond }
|
39
|
-
logger.info "Server::
|
38
|
+
logger.info "Server::Http#receive_data - Subscribed - #{@channel.channels.join(", ")}"
|
40
39
|
@subscription_id = @channel.subscribe { |str| respond str }
|
41
40
|
end
|
42
41
|
else
|
43
42
|
respond "no channel specified", 500
|
44
43
|
end
|
44
|
+
elsif path == '//inhabitants'
|
45
|
+
channels = query['channel'].dup rescue []
|
46
|
+
user_ids = channels.collect do |c|
|
47
|
+
Channel.inhabitants(c)
|
48
|
+
end
|
49
|
+
respond user_ids.flatten.uniq.join('|')
|
45
50
|
else
|
46
51
|
respond "not found", 404
|
47
52
|
end
|
@@ -65,6 +70,9 @@ module Puggernaut
|
|
65
70
|
if @subscription_id
|
66
71
|
@channel.unsubscribe(@subscription_id)
|
67
72
|
logger.info "Sever::Http#unbind - Unsubscribe - #{@channel.channels.join(", ")}"
|
73
|
+
if @join_leave && @channel.user_id
|
74
|
+
leave_channel(channel)
|
75
|
+
end
|
68
76
|
Channel.channels.delete @channel
|
69
77
|
end
|
70
78
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Puggernaut
|
2
|
+
class Server
|
3
|
+
module Shared
|
4
|
+
|
5
|
+
def gather_messages(channels, lasts, time)
|
6
|
+
if time
|
7
|
+
channels.inject([]) { |array, channel|
|
8
|
+
array += Channel.all_messages_after_time(channel, Time.parse(time))
|
9
|
+
array
|
10
|
+
}.join("\n")
|
11
|
+
elsif !lasts.empty?
|
12
|
+
channels.inject([]) { |array, channel|
|
13
|
+
array += Channel.all_messages_after_id(channel, lasts.shift)
|
14
|
+
array
|
15
|
+
}.join("\n")
|
16
|
+
else
|
17
|
+
''
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def join_channels(channels, user_id)
|
22
|
+
Channel.say channels.inject({}) { |hash, channel|
|
23
|
+
hash[channel] = "!PUGJOIN#{user_id}"
|
24
|
+
hash
|
25
|
+
}, user_id
|
26
|
+
end
|
27
|
+
|
28
|
+
def leave_channel(channel)
|
29
|
+
Channel.say channel.channels.inject({}) { |hash, c|
|
30
|
+
hash[c] = "!PUGLEAVE#{channel.user_id}"
|
31
|
+
hash
|
32
|
+
}, channel.user_id
|
33
|
+
end
|
34
|
+
|
35
|
+
def query_defaults(query)
|
36
|
+
[
|
37
|
+
(query['channel'].dup rescue []),
|
38
|
+
(query['join_leave'].dup[0] rescue nil),
|
39
|
+
(query['last'].dup rescue []),
|
40
|
+
(query['time'].dup[0] rescue nil),
|
41
|
+
(query['user_id'].dup[0] rescue nil)
|
42
|
+
]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Puggernaut
|
2
|
+
class Server
|
3
|
+
class Websocket
|
4
|
+
|
5
|
+
include Logger
|
6
|
+
include Shared
|
7
|
+
|
8
|
+
def initialize(host, port)
|
9
|
+
EventMachine::WebSocket.start(:host => host, :port => port) do |ws|
|
10
|
+
ws.onopen do
|
11
|
+
logger.info "Server::Websocket#initialize - Open"
|
12
|
+
channel = nil
|
13
|
+
join_leave = nil
|
14
|
+
joined = nil
|
15
|
+
subscription_id = nil
|
16
|
+
ws.onmessage do |msg|
|
17
|
+
logger.info "Server::Websocket#initialize - Message - #{msg}"
|
18
|
+
channels, join_leave, lasts, time, user_id = query_defaults(CGI.parse(msg))
|
19
|
+
channel ||= Channel.create(channels, user_id)
|
20
|
+
if join_leave && user_id && !joined
|
21
|
+
joined = true
|
22
|
+
join_channels(channels, user_id)
|
23
|
+
end
|
24
|
+
messages = gather_messages(channels, lasts, time)
|
25
|
+
unless messages.empty?
|
26
|
+
ws.send messages
|
27
|
+
else
|
28
|
+
logger.info "Server::Websocket#initialize - Subscribed - #{channel.channels.join(", ")}"
|
29
|
+
subscription_id = channel.subscribe { |str| ws.send str }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
ws.onclose do
|
33
|
+
if subscription_id
|
34
|
+
channel.unsubscribe(subscription_id)
|
35
|
+
logger.info "Sever::Websocket#initialize - Unsubscribe - #{channel.channels.join(", ")}"
|
36
|
+
if join_leave && channel.user_id
|
37
|
+
leave_channel(channel)
|
38
|
+
end
|
39
|
+
Channel.channels.delete channel
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -26,17 +26,29 @@ class SpecServer < Sinatra::Base
|
|
26
26
|
|
27
27
|
get '/single' do
|
28
28
|
begin
|
29
|
-
client = Puggernaut::Client.new("localhost:
|
29
|
+
client = Puggernaut::Client.new("localhost:8101")
|
30
30
|
client.push :single => "single message"
|
31
31
|
client.close
|
32
32
|
rescue Exception => e
|
33
33
|
e.message
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
get '/join_leave_join' do
|
38
|
+
begin
|
39
|
+
client = Puggernaut::Client.new("localhost:8101")
|
40
|
+
client.push :single => [
|
41
|
+
"!PUGJOINtest", "!PUGLEAVEtest", "!PUGJOINtest"
|
42
|
+
]
|
43
|
+
client.close
|
44
|
+
rescue Exception => e
|
45
|
+
e.message
|
46
|
+
end
|
47
|
+
end
|
36
48
|
|
37
49
|
get '/multiple' do
|
38
50
|
begin
|
39
|
-
client = Puggernaut::Client.new("localhost:
|
51
|
+
client = Puggernaut::Client.new("localhost:8101")
|
40
52
|
client.push :multiple => [ "multiple message 1", "multiple message 2" ]
|
41
53
|
client.close
|
42
54
|
rescue Exception => e
|
@@ -46,7 +58,7 @@ class SpecServer < Sinatra::Base
|
|
46
58
|
|
47
59
|
get '/last/:count' do
|
48
60
|
begin
|
49
|
-
client = Puggernaut::Client.new("localhost:
|
61
|
+
client = Puggernaut::Client.new("localhost:8101")
|
50
62
|
client.push :last => "last message #{params[:count]}"
|
51
63
|
client.close
|
52
64
|
rescue Exception => e
|
@@ -56,7 +68,7 @@ class SpecServer < Sinatra::Base
|
|
56
68
|
|
57
69
|
get '/multiple/channels' do
|
58
70
|
begin
|
59
|
-
client = Puggernaut::Client.new("localhost:
|
71
|
+
client = Puggernaut::Client.new("localhost:8101")
|
60
72
|
client.push :single => "single message", :multiple => [ "multiple message 1", "multiple message 2" ]
|
61
73
|
client.close
|
62
74
|
rescue Exception => e
|
data/public/puggernaut.js
CHANGED
@@ -5,34 +5,43 @@ var Puggernaut = new function() {
|
|
5
5
|
|
6
6
|
this.disabled = false;
|
7
7
|
this.path = '/long_poll';
|
8
|
+
this.inhabitants = inhabitants;
|
8
9
|
this.unwatch = unwatch;
|
9
10
|
this.watch = watch;
|
11
|
+
this.watch_join = watch_join;
|
12
|
+
this.watch_leave = watch_leave;
|
13
|
+
this.webSocketPort = webSocketPort;
|
10
14
|
|
11
15
|
var channels = {};
|
12
16
|
var errors = 0;
|
13
17
|
var events = $('<div/>');
|
18
|
+
var leaves = {};
|
14
19
|
var started = false;
|
15
|
-
|
16
|
-
|
20
|
+
var request;
|
21
|
+
var request_id;
|
22
|
+
var port = 8102;
|
23
|
+
|
24
|
+
function ajax(join_leave, time, user_id) {
|
17
25
|
if (channelLength() > 0 && !self.disabled && errors <= 10) {
|
18
26
|
started = true;
|
19
|
-
$.ajax({
|
27
|
+
request = $.ajax({
|
20
28
|
cache: false,
|
21
|
-
data: params(),
|
29
|
+
data: params(join_leave, time, user_id),
|
22
30
|
dataType: 'text',
|
23
|
-
error: function() {
|
24
|
-
|
25
|
-
|
31
|
+
error: function(xhr, status, error) {
|
32
|
+
if (started && status != 'abort') {
|
33
|
+
errors += 1;
|
34
|
+
ajax(join_leave, null, user_id);
|
35
|
+
}
|
26
36
|
},
|
27
|
-
success: function(data) {
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
}
|
35
|
-
ajax();
|
37
|
+
success: function(data, status, xhr) {
|
38
|
+
if (started) {
|
39
|
+
errors = 0;
|
40
|
+
$.each(data.split("\n"), function(i, line) {
|
41
|
+
processMessage(line);
|
42
|
+
});
|
43
|
+
ajax(join_leave, null, user_id);
|
44
|
+
}
|
36
45
|
},
|
37
46
|
timeout: 100000,
|
38
47
|
traditional: true,
|
@@ -49,31 +58,82 @@ var Puggernaut = new function() {
|
|
49
58
|
});
|
50
59
|
return length;
|
51
60
|
}
|
61
|
+
|
62
|
+
function inhabitants() {
|
63
|
+
var args = $.makeArray(arguments);
|
64
|
+
var fn = args.pop();
|
65
|
+
$.ajax({
|
66
|
+
cache: false,
|
67
|
+
data: { channel: args },
|
68
|
+
dataType: 'text',
|
69
|
+
success: function(data, status, xhr) {
|
70
|
+
fn(data.split('|'));
|
71
|
+
},
|
72
|
+
traditional: true,
|
73
|
+
url: self.path + '/inhabitants'
|
74
|
+
});
|
75
|
+
}
|
52
76
|
|
53
|
-
function params() {
|
77
|
+
function params(join_leave, time, user_id) {
|
54
78
|
var ch = [];
|
55
79
|
var la = [];
|
80
|
+
|
56
81
|
$.each(channels, function(channel, last) {
|
57
82
|
ch.push(channel);
|
58
|
-
|
83
|
+
if (last)
|
84
|
+
la.push(last);
|
59
85
|
});
|
60
|
-
|
86
|
+
|
87
|
+
var data = { channel: ch };
|
88
|
+
|
89
|
+
if (la.length)
|
90
|
+
data.last = la;
|
91
|
+
if (join_leave)
|
92
|
+
data.join_leave = join_leave;
|
93
|
+
if (time)
|
94
|
+
data.time = time + '';
|
95
|
+
if (user_id)
|
96
|
+
data.user_id = user_id;
|
97
|
+
|
98
|
+
return data;
|
99
|
+
}
|
100
|
+
|
101
|
+
function processMessage(line) {
|
102
|
+
line = line.split('|', 4);
|
103
|
+
if (line[0] && typeof channels[line[0]] != 'undefined') {
|
104
|
+
var id;
|
105
|
+
channels[line[0]] = line[1];
|
106
|
+
if (line[2].substring(0, 8) == '!PUGJOIN') {
|
107
|
+
id = line[2].substring(8);
|
108
|
+
if (leaves[id]) {
|
109
|
+
delete leaves[id];
|
110
|
+
clearTimeout(leaves[id]);
|
111
|
+
} else
|
112
|
+
events.trigger('join_' + line[0], id);
|
113
|
+
} else if (line[2].substring(0, 9) == '!PUGLEAVE') {
|
114
|
+
id = line[2].substring(9);
|
115
|
+
leaves[id] = setTimeout(function() {
|
116
|
+
events.trigger('leave_' + line[0], id);
|
117
|
+
}, 10000);
|
118
|
+
} else
|
119
|
+
events.trigger(line[0], [ line[2], new Date(line[3]) ]);
|
120
|
+
}
|
61
121
|
}
|
62
122
|
|
63
123
|
function unwatch() {
|
64
124
|
var args = $.makeArray(arguments);
|
125
|
+
started = false;
|
126
|
+
if (request.abort)
|
127
|
+
request.abort();
|
128
|
+
if (request.close)
|
129
|
+
request.close();
|
65
130
|
if (args.length) {
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
if (item.constructor == String)
|
72
|
-
return 'watch.' + item;
|
73
|
-
else
|
74
|
-
return item;
|
131
|
+
$.each(args, function(i, item) {
|
132
|
+
delete channels[item];
|
133
|
+
events.unbind(item);
|
134
|
+
events.unbind('join_' + item);
|
135
|
+
events.unbind('leave_' + item);
|
75
136
|
});
|
76
|
-
events.unbind.apply(events, args);
|
77
137
|
} else
|
78
138
|
events.unbind();
|
79
139
|
return this;
|
@@ -82,6 +142,15 @@ var Puggernaut = new function() {
|
|
82
142
|
function watch() {
|
83
143
|
var ch = $.makeArray(arguments);
|
84
144
|
var fn = ch.pop();
|
145
|
+
var join_leave, time, user_id;
|
146
|
+
|
147
|
+
if (ch[ch.length-1] && ch[ch.length-1].constructor === Object) {
|
148
|
+
var options = ch.pop();
|
149
|
+
|
150
|
+
join_leave = options.join_leave;
|
151
|
+
time = options.time;
|
152
|
+
user_id = options.user_id;
|
153
|
+
}
|
85
154
|
|
86
155
|
if (ch.length && fn) {
|
87
156
|
$.each(ch, function(i, item) {
|
@@ -89,10 +158,67 @@ var Puggernaut = new function() {
|
|
89
158
|
events.bind(item, fn);
|
90
159
|
});
|
91
160
|
|
92
|
-
if (!started)
|
93
|
-
|
161
|
+
if (!started) {
|
162
|
+
if (window.WebSocket)
|
163
|
+
websocket(join_leave, time, user_id);
|
164
|
+
else
|
165
|
+
ajax(join_leave, time, user_id);
|
166
|
+
}
|
94
167
|
}
|
95
168
|
|
96
169
|
return this;
|
97
170
|
}
|
171
|
+
|
172
|
+
function watch_join() {
|
173
|
+
var args = $.makeArray(arguments);
|
174
|
+
var fn = args.pop();
|
175
|
+
$.each(args, function(i, item) {
|
176
|
+
events.bind('join_' + item, fn);
|
177
|
+
});
|
178
|
+
return this;
|
179
|
+
}
|
180
|
+
|
181
|
+
function watch_leave() {
|
182
|
+
var args = $.makeArray(arguments);
|
183
|
+
var fn = args.pop();
|
184
|
+
$.each(args, function(i, item) {
|
185
|
+
events.bind('leave_' + item, fn);
|
186
|
+
});
|
187
|
+
return this;
|
188
|
+
}
|
189
|
+
|
190
|
+
function websocket(join_leave, time, user_id) {
|
191
|
+
if (channelLength() > 0 && !self.disabled && errors <= 10) {
|
192
|
+
started = true;
|
193
|
+
request = new WebSocket("ws://" + window.location.host + ":" + webSocketPort() + "/");
|
194
|
+
request.onopen = function() {
|
195
|
+
errors = 0;
|
196
|
+
if (started)
|
197
|
+
request.send($.param(params(join_leave, time, user_id), true));
|
198
|
+
};
|
199
|
+
request.onmessage = function(evt) {
|
200
|
+
errors = 0;
|
201
|
+
if (started) {
|
202
|
+
$.each(evt.data.split("\n"), function(i, line) {
|
203
|
+
processMessage(line);
|
204
|
+
});
|
205
|
+
request.send($.param(params(join_leave, null, user_id), true));
|
206
|
+
}
|
207
|
+
};
|
208
|
+
request.onerror = function() {
|
209
|
+
errors += 1;
|
210
|
+
if (started)
|
211
|
+
websocket(join_leave, null, user_id);
|
212
|
+
};
|
213
|
+
request.onclose = function() {
|
214
|
+
if (started)
|
215
|
+
websocket(join_leave, null, user_id);
|
216
|
+
};
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
220
|
+
function webSocketPort(p) {
|
221
|
+
if (p) port = p;
|
222
|
+
return port;
|
223
|
+
}
|
98
224
|
};
|
data/public/spec.js
CHANGED
@@ -2,8 +2,9 @@ $(function() {
|
|
2
2
|
|
3
3
|
module("Single message", {
|
4
4
|
setup: function() {
|
5
|
-
Puggernaut.watch('single', function(e, message) {
|
5
|
+
Puggernaut.watch('single', function(e, message, time) {
|
6
6
|
equals(message, 'single message');
|
7
|
+
ok(time.constructor === Date);
|
7
8
|
Puggernaut.unwatch('single');
|
8
9
|
start();
|
9
10
|
});
|
@@ -12,15 +13,100 @@ $(function() {
|
|
12
13
|
|
13
14
|
test("should receive a message", function() {
|
14
15
|
stop();
|
16
|
+
expect(2);
|
15
17
|
$.get('/single');
|
16
18
|
});
|
19
|
+
|
20
|
+
module("Single message unbind", {
|
21
|
+
setup: function() {
|
22
|
+
Puggernaut.watch('single', function(e, message, time) {
|
23
|
+
equals(message, 'single message');
|
24
|
+
ok(time.constructor === Date);
|
25
|
+
time_test = [ message, time ];
|
26
|
+
Puggernaut.unwatch('single');
|
27
|
+
});
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
test("should only receive one message", function() {
|
32
|
+
stop();
|
33
|
+
expect(2);
|
34
|
+
$.get('/single', function() {
|
35
|
+
$.get('/single', function() {
|
36
|
+
setTimeout(function() {
|
37
|
+
start();
|
38
|
+
}, 2000);
|
39
|
+
});
|
40
|
+
});
|
41
|
+
});
|
42
|
+
|
43
|
+
module("Single message after time");
|
44
|
+
|
45
|
+
test("should receive messages after time", function() {
|
46
|
+
stop();
|
47
|
+
expect(2);
|
48
|
+
var time_now = new Date();
|
49
|
+
$.get('/single', function() {
|
50
|
+
Puggernaut.watch('single', { time: time_now }, function(e, message, time) {
|
51
|
+
equals(message, 'single message');
|
52
|
+
ok(time.constructor === Date);
|
53
|
+
Puggernaut.unwatch('single');
|
54
|
+
start();
|
55
|
+
});
|
56
|
+
});
|
57
|
+
});
|
58
|
+
|
59
|
+
module("Single message inhabitants", {
|
60
|
+
setup: function() {
|
61
|
+
Puggernaut.watch('single', { user_id: 'test' }, function(e, message, time) {
|
62
|
+
setTimeout(function() {
|
63
|
+
Puggernaut.inhabitants('single', function(users) {
|
64
|
+
equals(users[0], 'test');
|
65
|
+
equals(users.length, 1);
|
66
|
+
Puggernaut.unwatch('single');
|
67
|
+
start();
|
68
|
+
});
|
69
|
+
}, 1000);
|
70
|
+
});
|
71
|
+
}
|
72
|
+
});
|
73
|
+
|
74
|
+
test("should receive a message", function() {
|
75
|
+
stop();
|
76
|
+
expect(2);
|
77
|
+
$.get('/single');
|
78
|
+
});
|
79
|
+
|
80
|
+
module("Single message join/leave/join", {
|
81
|
+
setup: function() {
|
82
|
+
Puggernaut
|
83
|
+
.watch('single', { join_leave: true }, function(e, message, time) {})
|
84
|
+
.watch_join('single', function(e, user_id) {
|
85
|
+
equals(user_id, 'test');
|
86
|
+
setTimeout(function() {
|
87
|
+
Puggernaut.unwatch('single');
|
88
|
+
start();
|
89
|
+
}, 1000);
|
90
|
+
})
|
91
|
+
.watch_leave('single', function(e, user_id) {
|
92
|
+
ok(false);
|
93
|
+
});
|
94
|
+
}
|
95
|
+
});
|
96
|
+
|
97
|
+
test("should trigger join event without leave", function() {
|
98
|
+
stop();
|
99
|
+
expect(1);
|
100
|
+
$.get('/join_leave_join');
|
101
|
+
});
|
17
102
|
|
18
103
|
module("Multiple messages", {
|
19
104
|
setup: function() {
|
20
105
|
var executions = 0;
|
21
|
-
Puggernaut.watch('multiple', function(e, message) {
|
106
|
+
Puggernaut.watch('multiple', function(e, message, time) {
|
22
107
|
executions += 1;
|
23
108
|
equals(message, 'multiple message ' + executions);
|
109
|
+
ok(time.constructor === Date);
|
24
110
|
if (executions == 2) {
|
25
111
|
Puggernaut.unwatch('multiple');
|
26
112
|
start();
|
@@ -31,23 +117,21 @@ $(function() {
|
|
31
117
|
|
32
118
|
test("should receive multiple messages", function() {
|
33
119
|
stop();
|
120
|
+
expect(4);
|
34
121
|
$.get('/multiple');
|
35
122
|
});
|
36
123
|
|
37
124
|
module("Last message", {
|
38
125
|
setup: function() {
|
39
|
-
Puggernaut.watch('last', function(e, message) {
|
40
|
-
if (message
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
start();
|
49
|
-
});
|
50
|
-
});
|
126
|
+
Puggernaut.watch('last', function(e, message, time) {
|
127
|
+
if (message == 'last message 1') {
|
128
|
+
ok(time.constructor === Date);
|
129
|
+
$.get('/last/2');
|
130
|
+
}
|
131
|
+
if (message == 'last message 2') {
|
132
|
+
ok(time.constructor === Date);
|
133
|
+
Puggernaut.unwatch('last');
|
134
|
+
start();
|
51
135
|
}
|
52
136
|
});
|
53
137
|
}
|
@@ -55,6 +139,7 @@ $(function() {
|
|
55
139
|
|
56
140
|
test("should pick up last message", function() {
|
57
141
|
stop();
|
142
|
+
expect(2);
|
58
143
|
$.get('/last/1');
|
59
144
|
});
|
60
145
|
|
@@ -62,6 +147,7 @@ $(function() {
|
|
62
147
|
|
63
148
|
test("should receive all messages", function() {
|
64
149
|
stop();
|
150
|
+
expect(6);
|
65
151
|
|
66
152
|
var executions = 0;
|
67
153
|
var total_runs = 0;
|
@@ -69,9 +155,10 @@ $(function() {
|
|
69
155
|
Puggernaut.disabled = true;
|
70
156
|
|
71
157
|
Puggernaut
|
72
|
-
.watch('single', function(e, message) {
|
158
|
+
.watch('single', function(e, message, time) {
|
73
159
|
total_runs += 1;
|
74
160
|
equals(message, 'single message');
|
161
|
+
ok(time.constructor === Date);
|
75
162
|
Puggernaut.unwatch('single');
|
76
163
|
if (total_runs == 3)
|
77
164
|
start();
|
@@ -80,10 +167,11 @@ $(function() {
|
|
80
167
|
Puggernaut.disabled = false;
|
81
168
|
|
82
169
|
Puggernaut
|
83
|
-
.watch('multiple', function(e, message) {
|
170
|
+
.watch('multiple', function(e, message, time) {
|
84
171
|
executions += 1;
|
85
172
|
total_runs += 1;
|
86
173
|
equals(message, 'multiple message ' + executions);
|
174
|
+
ok(time.constructor === Date);
|
87
175
|
if (executions == 2)
|
88
176
|
Puggernaut.unwatch('multiple');
|
89
177
|
if (total_runs == 3)
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puggernaut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 23
|
5
|
+
prerelease:
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Winton Welsh
|
@@ -14,24 +15,41 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2011-
|
18
|
+
date: 2011-03-15 00:00:00 -07:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
|
-
name:
|
22
|
+
name: em-websocket
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
24
25
|
none: false
|
25
26
|
requirements:
|
26
27
|
- - ~>
|
27
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 21
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 2
|
33
|
+
- 1
|
34
|
+
version: 0.2.1
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: eventmachine
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 59
|
28
46
|
segments:
|
29
47
|
- 0
|
30
48
|
- 12
|
31
49
|
- 10
|
32
50
|
version: 0.12.10
|
33
51
|
type: :runtime
|
34
|
-
version_requirements: *
|
52
|
+
version_requirements: *id002
|
35
53
|
description: Simple server push implementation using eventmachine and long polling
|
36
54
|
email: mail@wintoni.us
|
37
55
|
executables:
|
@@ -56,7 +74,9 @@ files:
|
|
56
74
|
- lib/puggernaut/server.rb
|
57
75
|
- lib/puggernaut/server/channel.rb
|
58
76
|
- lib/puggernaut/server/http.rb
|
77
|
+
- lib/puggernaut/server/shared.rb
|
59
78
|
- lib/puggernaut/server/tcp.rb
|
79
|
+
- lib/puggernaut/server/websocket.rb
|
60
80
|
- lib/puggernaut/spec_server.rb
|
61
81
|
- public/jquery.js
|
62
82
|
- public/puggernaut.js
|
@@ -85,6 +105,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
105
|
requirements:
|
86
106
|
- - ">="
|
87
107
|
- !ruby/object:Gem::Version
|
108
|
+
hash: 3
|
88
109
|
segments:
|
89
110
|
- 0
|
90
111
|
version: "0"
|
@@ -93,13 +114,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
114
|
requirements:
|
94
115
|
- - ">="
|
95
116
|
- !ruby/object:Gem::Version
|
117
|
+
hash: 3
|
96
118
|
segments:
|
97
119
|
- 0
|
98
120
|
version: "0"
|
99
121
|
requirements: []
|
100
122
|
|
101
123
|
rubyforge_project:
|
102
|
-
rubygems_version: 1.
|
124
|
+
rubygems_version: 1.4.2
|
103
125
|
signing_key:
|
104
126
|
specification_version: 3
|
105
127
|
summary: Simple server push implementation using eventmachine and long polling
|