puggernaut 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|