socky-client 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +25 -0
- data/README.md +25 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/lib/socky-client.rb +116 -0
- data/lib/socky-client/websocket.rb +230 -0
- data/socky_hosts.yml +5 -0
- data/spec/socky-client_spec.rb +143 -0
- data/spec/spec_helper.rb +5 -0
- metadata +104 -0
data/CHANGELOG.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Changelog
|
2
|
+
=========
|
3
|
+
|
4
|
+
## 0.4.2 / 2010-10-29
|
5
|
+
|
6
|
+
- new features:
|
7
|
+
- change config_path and config from constant to method
|
8
|
+
- bugfixes:
|
9
|
+
- none
|
10
|
+
|
11
|
+
## 0.4.1 / 2010-10-28
|
12
|
+
|
13
|
+
- new features:
|
14
|
+
- none
|
15
|
+
- bugfixes:
|
16
|
+
- require by 'socky-client' to stop interfering with socky-ruby-server
|
17
|
+
- return 'true' after successful sending message
|
18
|
+
|
19
|
+
## 0.4.0 / 2010-10-28
|
20
|
+
|
21
|
+
- new features:
|
22
|
+
- split project to 3 parts - socky-client-ruby, socky-client-rails and socky-js
|
23
|
+
- release as gem
|
24
|
+
- bugfixes:
|
25
|
+
- none
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Socky - client in Ruby
|
2
|
+
===========
|
3
|
+
|
4
|
+
Socky is push server for Ruby based on WebSockets. It allows you to break border between your application and client browser by letting the server initialize a connection and push data to the client.
|
5
|
+
|
6
|
+
## Getting Started
|
7
|
+
|
8
|
+
- [Install](http://github.com/socky/socky-client-ruby/wiki/install) the gem
|
9
|
+
- Read up about its [Usage](http://github.com/socky/socky-client-ruby/wiki/usage) and [Configuration](http://github.com/socky/socky-client-ruby/wiki/configuration)
|
10
|
+
- Try [Example Application](http://github.com/socky/socky-example) or [demo page](http://sockydemo.imanel.org)
|
11
|
+
- Fork and Contribute your own modifications
|
12
|
+
- See [sites using socky](http://github.com/socky/socky-server-ruby/wiki/sites)
|
13
|
+
- Discuss on [google group](http://groups.google.com/group/socky-framework)
|
14
|
+
|
15
|
+
## License
|
16
|
+
|
17
|
+
(The MIT License)
|
18
|
+
|
19
|
+
Copyright (c) 2010 Bernard Potocki
|
20
|
+
|
21
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
22
|
+
|
23
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
24
|
+
|
25
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/clean'
|
3
|
+
CLEAN.include %w(**/*.{log,rbc})
|
4
|
+
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gemspec|
|
15
|
+
gemspec.name = "socky-client"
|
16
|
+
gemspec.summary = "Socky is a WebSocket server and client for Ruby"
|
17
|
+
gemspec.description = "Socky is a WebSocket server and client for Ruby"
|
18
|
+
gemspec.email = "bernard.potocki@imanel.org"
|
19
|
+
gemspec.homepage = "http://imanel.org/projects/socky"
|
20
|
+
gemspec.authors = ["Bernard Potocki"]
|
21
|
+
gemspec.add_dependency 'json'
|
22
|
+
gemspec.add_development_dependency 'rspec', '~> 2.0'
|
23
|
+
gemspec.files.exclude ".gitignore"
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
27
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.2
|
data/lib/socky-client.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require File.dirname(__FILE__) + '/socky-client/websocket'
|
5
|
+
|
6
|
+
module Socky
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
attr_accessor :config_path
|
11
|
+
def config_path
|
12
|
+
@config_path ||= 'socky_hosts.yml'
|
13
|
+
end
|
14
|
+
|
15
|
+
def config
|
16
|
+
@config ||= YAML.load_file(config_path).freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
def send(*args)
|
20
|
+
options = normalize_options(*args)
|
21
|
+
send_message(options.delete(:data), options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def show_connections
|
25
|
+
send_query(:show_connections)
|
26
|
+
end
|
27
|
+
|
28
|
+
def hosts
|
29
|
+
config[:hosts]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def normalize_options(data, options = {})
|
35
|
+
case data
|
36
|
+
when Hash
|
37
|
+
options, data = data, nil
|
38
|
+
when String, Symbol
|
39
|
+
options[:data] = data
|
40
|
+
else
|
41
|
+
options.merge!(:data => data)
|
42
|
+
end
|
43
|
+
|
44
|
+
options[:data] = options[:data].to_s
|
45
|
+
options
|
46
|
+
end
|
47
|
+
|
48
|
+
def send_message(data, opts = {})
|
49
|
+
to = opts[:to] || {}
|
50
|
+
except = opts[:except] || {}
|
51
|
+
|
52
|
+
unless to.is_a?(Hash) && except.is_a?(Hash)
|
53
|
+
raise "recipiend data should be in hash format"
|
54
|
+
end
|
55
|
+
|
56
|
+
to_clients = to[:client] || to[:clients]
|
57
|
+
to_channels = to[:channel] || to[:channels]
|
58
|
+
except_clients = except[:client] || except[:clients]
|
59
|
+
except_channels = except[:channel] || except[:channels]
|
60
|
+
|
61
|
+
# If clients or channels are non-nil but empty then there's no users to target message
|
62
|
+
return if (to_clients.is_a?(Array) && to_clients.empty?) || (to_channels.is_a?(Array) && to_channels.empty?)
|
63
|
+
|
64
|
+
hash = {
|
65
|
+
:command => :broadcast,
|
66
|
+
:body => data,
|
67
|
+
:to => {
|
68
|
+
:clients => to_clients,
|
69
|
+
:channels => to_channels,
|
70
|
+
},
|
71
|
+
:except => {
|
72
|
+
:clients => except_clients,
|
73
|
+
:channels => except_channels,
|
74
|
+
}
|
75
|
+
}
|
76
|
+
|
77
|
+
[:to, :except].each do |type|
|
78
|
+
hash[type].reject! { |key,val| val.nil? || (type == :except && val.empty?)}
|
79
|
+
hash.delete(type) if hash[type].empty?
|
80
|
+
end
|
81
|
+
|
82
|
+
send_data(hash)
|
83
|
+
end
|
84
|
+
|
85
|
+
def send_query(type)
|
86
|
+
hash = {
|
87
|
+
:command => :query,
|
88
|
+
:type => type
|
89
|
+
}
|
90
|
+
send_data(hash, true)
|
91
|
+
end
|
92
|
+
|
93
|
+
def send_data(hash, response = false)
|
94
|
+
res = []
|
95
|
+
hosts.each do |address|
|
96
|
+
begin
|
97
|
+
scheme = (address[:secure] ? "wss" : "ws")
|
98
|
+
@socket = WebSocket.new("#{scheme}://#{address[:host]}:#{address[:port]}/?admin=1&client_secret=#{address[:secret]}")
|
99
|
+
@socket.send(hash.to_json)
|
100
|
+
res << @socket.receive if response
|
101
|
+
rescue
|
102
|
+
puts "ERROR: Connection to server at '#{scheme}://#{address[:host]}:#{address[:port]}' failed"
|
103
|
+
ensure
|
104
|
+
@socket.close if @socket && !@socket.tcp_socket.closed?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
if response
|
108
|
+
res.collect {|r| JSON.parse(r)["body"] }
|
109
|
+
else
|
110
|
+
true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
# Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
|
2
|
+
# Lincense: New BSD Lincense
|
3
|
+
# Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol
|
4
|
+
|
5
|
+
require "socket"
|
6
|
+
require "uri"
|
7
|
+
require "digest/md5"
|
8
|
+
require "openssl"
|
9
|
+
|
10
|
+
|
11
|
+
class WebSocket
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
attr_accessor(:debug)
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class Error < RuntimeError
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(arg, params = {})
|
24
|
+
|
25
|
+
uri = arg.is_a?(String) ? URI.parse(arg) : arg
|
26
|
+
|
27
|
+
if uri.scheme == "ws"
|
28
|
+
default_port = 80
|
29
|
+
elsif uri.scheme = "wss"
|
30
|
+
default_port = 443
|
31
|
+
else
|
32
|
+
raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
|
33
|
+
end
|
34
|
+
|
35
|
+
@path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
|
36
|
+
host = uri.host + (uri.port == default_port ? "" : ":#{uri.port}")
|
37
|
+
origin = params[:origin] || "http://#{uri.host}"
|
38
|
+
key1 = generate_key()
|
39
|
+
key2 = generate_key()
|
40
|
+
key3 = generate_key3()
|
41
|
+
|
42
|
+
socket = TCPSocket.new(uri.host, uri.port || default_port)
|
43
|
+
|
44
|
+
if uri.scheme == "ws"
|
45
|
+
@socket = socket
|
46
|
+
else
|
47
|
+
@socket = ssl_handshake(socket)
|
48
|
+
end
|
49
|
+
|
50
|
+
write(
|
51
|
+
"GET #{@path} HTTP/1.1\r\n" +
|
52
|
+
"Upgrade: WebSocket\r\n" +
|
53
|
+
"Connection: Upgrade\r\n" +
|
54
|
+
"Host: #{host}\r\n" +
|
55
|
+
"Origin: #{origin}\r\n" +
|
56
|
+
"Sec-WebSocket-Key1: #{key1}\r\n" +
|
57
|
+
"Sec-WebSocket-Key2: #{key2}\r\n" +
|
58
|
+
"\r\n" +
|
59
|
+
"#{key3}")
|
60
|
+
flush()
|
61
|
+
|
62
|
+
line = gets().chomp()
|
63
|
+
raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
|
64
|
+
read_header()
|
65
|
+
if @header["Sec-WebSocket-Origin"] != origin
|
66
|
+
raise(WebSocket::Error,
|
67
|
+
"origin doesn't match: '#{@header["WebSocket-Origin"]}' != '#{origin}'")
|
68
|
+
end
|
69
|
+
reply_digest = read(16)
|
70
|
+
expected_digest = security_digest(key1, key2, key3)
|
71
|
+
if reply_digest != expected_digest
|
72
|
+
raise(WebSocket::Error,
|
73
|
+
"security digest doesn't match: %p != %p" % [reply_digest, expected_digest])
|
74
|
+
end
|
75
|
+
@handshaked = true
|
76
|
+
|
77
|
+
@closing_started = false
|
78
|
+
end
|
79
|
+
|
80
|
+
attr_reader(:header, :path)
|
81
|
+
|
82
|
+
def send(data)
|
83
|
+
if !@handshaked
|
84
|
+
raise(WebSocket::Error, "call WebSocket\#handshake first")
|
85
|
+
end
|
86
|
+
data = force_encoding(data.dup(), "ASCII-8BIT")
|
87
|
+
write("\x00#{data}\xff")
|
88
|
+
flush()
|
89
|
+
end
|
90
|
+
|
91
|
+
def receive()
|
92
|
+
if !@handshaked
|
93
|
+
raise(WebSocket::Error, "call WebSocket\#handshake first")
|
94
|
+
end
|
95
|
+
packet = gets("\xff")
|
96
|
+
return nil if !packet
|
97
|
+
if packet =~ /\A\x00(.*)\xff\z/nm
|
98
|
+
return force_encoding($1, "UTF-8")
|
99
|
+
elsif packet == "\xff" && read(1) == "\x00" # closing
|
100
|
+
if @server
|
101
|
+
@socket.close()
|
102
|
+
else
|
103
|
+
close()
|
104
|
+
end
|
105
|
+
return nil
|
106
|
+
else
|
107
|
+
raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def tcp_socket
|
112
|
+
return @socket
|
113
|
+
end
|
114
|
+
|
115
|
+
def host
|
116
|
+
return @header["Host"]
|
117
|
+
end
|
118
|
+
|
119
|
+
def origin
|
120
|
+
return @header["Origin"]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Does closing handshake.
|
124
|
+
def close()
|
125
|
+
return if @closing_started
|
126
|
+
write("\xff\x00")
|
127
|
+
@socket.close() if !@server
|
128
|
+
@closing_started = true
|
129
|
+
end
|
130
|
+
|
131
|
+
def close_socket()
|
132
|
+
@socket.close()
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a()
|
138
|
+
|
139
|
+
def read_header()
|
140
|
+
@header = {}
|
141
|
+
while line = gets()
|
142
|
+
line = line.chomp()
|
143
|
+
break if line.empty?
|
144
|
+
if !(line =~ /\A(\S+): (.*)\z/n)
|
145
|
+
raise(WebSocket::Error, "invalid request: #{line}")
|
146
|
+
end
|
147
|
+
@header[$1] = $2
|
148
|
+
end
|
149
|
+
if @header["Upgrade"] != "WebSocket"
|
150
|
+
raise(WebSocket::Error, "invalid Upgrade: " + @header["Upgrade"])
|
151
|
+
end
|
152
|
+
if @header["Connection"] != "Upgrade"
|
153
|
+
raise(WebSocket::Error, "invalid Connection: " + @header["Connection"])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def gets(rs = $/)
|
158
|
+
line = @socket.gets(rs)
|
159
|
+
$stderr.printf("recv> %p\n", line) if WebSocket.debug
|
160
|
+
return line
|
161
|
+
end
|
162
|
+
|
163
|
+
def read(num_bytes)
|
164
|
+
str = @socket.read(num_bytes)
|
165
|
+
$stderr.printf("recv> %p\n", str) if WebSocket.debug
|
166
|
+
return str
|
167
|
+
end
|
168
|
+
|
169
|
+
def write(data)
|
170
|
+
if WebSocket.debug
|
171
|
+
data.scan(/\G(.*?(\n|\z))/n) do
|
172
|
+
$stderr.printf("send> %p\n", $&) if !$&.empty?
|
173
|
+
end
|
174
|
+
end
|
175
|
+
@socket.write(data)
|
176
|
+
end
|
177
|
+
|
178
|
+
def flush()
|
179
|
+
@socket.flush()
|
180
|
+
end
|
181
|
+
|
182
|
+
def security_digest(key1, key2, key3)
|
183
|
+
bytes1 = websocket_key_to_bytes(key1)
|
184
|
+
bytes2 = websocket_key_to_bytes(key2)
|
185
|
+
return Digest::MD5.digest(bytes1 + bytes2 + key3)
|
186
|
+
end
|
187
|
+
|
188
|
+
def generate_key()
|
189
|
+
spaces = 1 + rand(12)
|
190
|
+
max = 0xffffffff / spaces
|
191
|
+
number = rand(max + 1)
|
192
|
+
key = (number * spaces).to_s()
|
193
|
+
(1 + rand(12)).times() do
|
194
|
+
char = NOISE_CHARS[rand(NOISE_CHARS.size)]
|
195
|
+
pos = rand(key.size + 1)
|
196
|
+
key[pos...pos] = char
|
197
|
+
end
|
198
|
+
spaces.times() do
|
199
|
+
pos = 1 + rand(key.size - 1)
|
200
|
+
key[pos...pos] = " "
|
201
|
+
end
|
202
|
+
return key
|
203
|
+
end
|
204
|
+
|
205
|
+
def generate_key3()
|
206
|
+
return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N")
|
207
|
+
end
|
208
|
+
|
209
|
+
def websocket_key_to_bytes(key)
|
210
|
+
num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size
|
211
|
+
return [num].pack("N")
|
212
|
+
end
|
213
|
+
|
214
|
+
def force_encoding(str, encoding)
|
215
|
+
if str.respond_to?(:force_encoding)
|
216
|
+
return str.force_encoding(encoding)
|
217
|
+
else
|
218
|
+
return str
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def ssl_handshake(socket)
|
223
|
+
ssl_context = OpenSSL::SSL::SSLContext.new()
|
224
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
|
225
|
+
ssl_socket.sync_close = true
|
226
|
+
ssl_socket.connect()
|
227
|
+
return ssl_socket
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
data/socky_hosts.yml
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Socky do
|
4
|
+
it "should have config in hash form" do
|
5
|
+
Socky.config.should_not be_nil
|
6
|
+
Socky.config.class.should eql(Hash)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should have host list taken from config" do
|
10
|
+
Socky.hosts.should eql(Socky.config[:hosts])
|
11
|
+
end
|
12
|
+
|
13
|
+
context "#send" do
|
14
|
+
before(:each) do
|
15
|
+
Socky.stub!(:send_data)
|
16
|
+
end
|
17
|
+
it "should send broadcast with data" do
|
18
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
19
|
+
Socky.send("test")
|
20
|
+
end
|
21
|
+
context "should normalize options" do
|
22
|
+
it "when nil given" do
|
23
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => ""})
|
24
|
+
Socky.send(nil)
|
25
|
+
end
|
26
|
+
it "when string given" do
|
27
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
28
|
+
Socky.send("test")
|
29
|
+
end
|
30
|
+
it "when hash given" do
|
31
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
32
|
+
Socky.send({:data => "test"})
|
33
|
+
end
|
34
|
+
it "when hash without body given" do
|
35
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => ""})
|
36
|
+
Socky.send({})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
context "should handle recipient conditions for" do
|
40
|
+
it ":to => :client" do
|
41
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :clients => "first" }})
|
42
|
+
Socky.send("test", :to => { :client => "first" })
|
43
|
+
end
|
44
|
+
it ":to => :clients" do
|
45
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :clients => ["first","second"] }})
|
46
|
+
Socky.send("test", :to => { :clients => ["first","second"] })
|
47
|
+
end
|
48
|
+
it ":to => :channel" do
|
49
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :channels => "first" }})
|
50
|
+
Socky.send("test", :to => { :channel => "first" })
|
51
|
+
end
|
52
|
+
it ":to => :channels" do
|
53
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :to => { :channels => ["first","second"] }})
|
54
|
+
Socky.send("test", :to => { :channels => ["first","second"] })
|
55
|
+
end
|
56
|
+
it ":except => :client" do
|
57
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :clients => "first" }})
|
58
|
+
Socky.send("test", :except => { :client => "first" })
|
59
|
+
end
|
60
|
+
it ":except => :clients" do
|
61
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :clients => ["first","second"] }})
|
62
|
+
Socky.send("test", :except => { :clients => ["first","second"] })
|
63
|
+
end
|
64
|
+
it ":except => :channel" do
|
65
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :channels => "first" }})
|
66
|
+
Socky.send("test", :except => { :channel => "first" })
|
67
|
+
end
|
68
|
+
it ":except => :channels" do
|
69
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test", :except => { :channels => ["first","second"] }})
|
70
|
+
Socky.send("test", :except => { :channels => ["first","second"] })
|
71
|
+
end
|
72
|
+
it "combination" do
|
73
|
+
Socky.should_receive(:send_data).with({
|
74
|
+
:command => :broadcast,
|
75
|
+
:body => "test",
|
76
|
+
:to => {
|
77
|
+
:clients => "allowed_user",
|
78
|
+
:channels => "allowed_channel"
|
79
|
+
},
|
80
|
+
:except => {
|
81
|
+
:clients => "disallowed_user",
|
82
|
+
:channels => "disallowed_channel"
|
83
|
+
}
|
84
|
+
})
|
85
|
+
Socky.send("test", :to => {
|
86
|
+
:clients => "allowed_user",
|
87
|
+
:channels => "allowed_channel"
|
88
|
+
},
|
89
|
+
:except => {
|
90
|
+
:clients => "disallowed_user",
|
91
|
+
:channels => "disallowed_channel"
|
92
|
+
})
|
93
|
+
end
|
94
|
+
end
|
95
|
+
context "should ignore nil value for" do
|
96
|
+
it ":to => :clients" do
|
97
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
98
|
+
Socky.send("test", :to => { :clients => nil })
|
99
|
+
end
|
100
|
+
it ":to => :channels" do
|
101
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
102
|
+
Socky.send("test", :to => { :channels => nil })
|
103
|
+
end
|
104
|
+
it ":except => :clients" do
|
105
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
106
|
+
Socky.send("test", :except => { :clients => nil })
|
107
|
+
end
|
108
|
+
it ":except => :channels" do
|
109
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
110
|
+
Socky.send("test", :except => { :channels => nil })
|
111
|
+
end
|
112
|
+
end
|
113
|
+
context "should handle empty array for" do
|
114
|
+
it ":to => :clients by not sending message" do
|
115
|
+
Socky.should_not_receive(:send_data)
|
116
|
+
Socky.send("test", :to => { :clients => [] })
|
117
|
+
end
|
118
|
+
it ":to => :channels by not sending message" do
|
119
|
+
Socky.should_not_receive(:send_data)
|
120
|
+
Socky.send("test", :to => { :channels => [] })
|
121
|
+
end
|
122
|
+
it ":except => :clients by ignoring it" do
|
123
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
124
|
+
Socky.send("test", :except => { :clients => [] })
|
125
|
+
end
|
126
|
+
it ":except => :channels by ignoring it" do
|
127
|
+
Socky.should_receive(:send_data).with({:command => :broadcast, :body => "test"})
|
128
|
+
Socky.send("test", :except => { :channels => [] })
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "#show_connections" do
|
134
|
+
before(:each) do
|
135
|
+
Socky.stub!(:send_data)
|
136
|
+
end
|
137
|
+
it "should send query :show_connections" do
|
138
|
+
Socky.should_receive(:send_data).with({:command => :query, :type => :show_connections}, true)
|
139
|
+
Socky.show_connections
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: socky-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 11
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 2
|
10
|
+
version: 0.4.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Bernard Potocki
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-29 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: json
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 2
|
46
|
+
- 0
|
47
|
+
version: "2.0"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
50
|
+
description: Socky is a WebSocket server and client for Ruby
|
51
|
+
email: bernard.potocki@imanel.org
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files:
|
57
|
+
- README.md
|
58
|
+
files:
|
59
|
+
- CHANGELOG.md
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- VERSION
|
63
|
+
- lib/socky-client.rb
|
64
|
+
- lib/socky-client/websocket.rb
|
65
|
+
- socky_hosts.yml
|
66
|
+
- spec/socky-client_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
has_rdoc: true
|
69
|
+
homepage: http://imanel.org/projects/socky
|
70
|
+
licenses: []
|
71
|
+
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options:
|
74
|
+
- --charset=UTF-8
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
hash: 3
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
requirements: []
|
96
|
+
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.3.7
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: Socky is a WebSocket server and client for Ruby
|
102
|
+
test_files:
|
103
|
+
- spec/socky-client_spec.rb
|
104
|
+
- spec/spec_helper.rb
|