slanger 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of slanger might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +44 -1
- data/lib/slanger/api.rb +5 -0
- data/lib/slanger/api/event.rb +15 -0
- data/lib/slanger/api/event_publisher.rb +24 -0
- data/lib/slanger/api/request_validation.rb +103 -0
- data/lib/slanger/api/server.rb +56 -0
- data/lib/slanger/channel.rb +19 -7
- data/lib/slanger/connection.rb +2 -1
- data/lib/slanger/handler.rb +3 -3
- data/lib/slanger/presence_channel.rb +1 -2
- data/lib/slanger/service.rb +1 -1
- data/lib/slanger/version.rb +1 -1
- data/slanger.rb +4 -0
- data/spec/have_attributes.rb +64 -0
- data/spec/integration/channel_spec.rb +114 -0
- data/spec/integration/integration_spec.rb +68 -0
- data/spec/integration/presence_channel_spec.rb +158 -0
- data/spec/integration/private_channel_spec.rb +79 -0
- data/spec/integration/replaced_handler_spec.rb +23 -0
- data/spec/integration/ssl_spec.rb +18 -0
- data/spec/server.crt +12 -0
- data/spec/server.key +15 -0
- data/spec/slanger_helper_methods.rb +109 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/unit/channel_spec.rb +69 -0
- data/spec/unit/request_validation_spec.rb +64 -0
- data/spec/unit/webhook_spec.rb +43 -0
- metadata +88 -38
- data/lib/slanger/api_server.rb +0 -64
@@ -0,0 +1,114 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Integration:' do
|
5
|
+
|
6
|
+
before(:each) { start_slanger }
|
7
|
+
|
8
|
+
describe 'channel' do
|
9
|
+
it 'pushes messages to interested websocket connections' do
|
10
|
+
messages = em_stream do |websocket, messages|
|
11
|
+
case messages.length
|
12
|
+
when 1
|
13
|
+
websocket.callback { websocket.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json) }
|
14
|
+
when 2
|
15
|
+
Pusher['MY_CHANNEL'].trigger 'an_event', some: "Mit Raben Und Wölfen"
|
16
|
+
when 3
|
17
|
+
EM.stop
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
messages.should have_attributes connection_established: true, id_present: true,
|
22
|
+
last_event: 'an_event', last_data: { some: "Mit Raben Und Wölfen" }.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not send message to excluded sockets' do
|
26
|
+
messages = em_stream do |websocket, messages|
|
27
|
+
case messages.length
|
28
|
+
when 1
|
29
|
+
websocket.callback { websocket.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json) }
|
30
|
+
when 2
|
31
|
+
socket_id = JSON.parse(messages.first["data"])["socket_id"]
|
32
|
+
Pusher['MY_CHANNEL'].trigger 'not_excluded_socket_event', { some: "Mit Raben Und Wölfen" }
|
33
|
+
Pusher['MY_CHANNEL'].trigger 'excluded_socket_event', { some: "Mit Raben Und Wölfen" }, socket_id
|
34
|
+
when 3
|
35
|
+
EM.stop
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
messages.should have_attributes connection_established: true, id_present: true,
|
40
|
+
last_event: 'not_excluded_socket_event', last_data: { some: "Mit Raben Und Wölfen" }.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'enforces one subcription per channel, per socket' do
|
44
|
+
messages = em_stream do |websocket, messages|
|
45
|
+
case messages.length
|
46
|
+
when 1
|
47
|
+
websocket.callback { websocket.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json) }
|
48
|
+
when 2
|
49
|
+
websocket.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json)
|
50
|
+
when 3
|
51
|
+
EM.stop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
messages.last.should == {"event"=>"pusher:error", "data"=>"{\"code\":null,\"message\":\"Existing subscription to MY_CHANNEL\"}"}
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'supports unsubscribing to channels without closing the socket' do
|
59
|
+
client2_messages = nil
|
60
|
+
|
61
|
+
messages = em_stream do |client, messages|
|
62
|
+
case messages.length
|
63
|
+
when 1
|
64
|
+
client.callback { client.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json) }
|
65
|
+
when 2
|
66
|
+
client.send({ event: 'pusher:unsubscribe', data: { channel: 'MY_CHANNEL'} }.to_json)
|
67
|
+
|
68
|
+
client2_messages = em_stream do |client2, client2_messages|
|
69
|
+
case client2_messages.length
|
70
|
+
when 1
|
71
|
+
client2.callback { client2.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json) }
|
72
|
+
when 2
|
73
|
+
Pusher['MY_CHANNEL'].trigger 'an_event', { some: 'data' }
|
74
|
+
EM.next_tick { EM.stop }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
messages.should have_attributes connection_established: true, id_present: true,
|
81
|
+
last_event: 'pusher_internal:subscription_succeeded', count: 2
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'avoids sending duplicate events' do
|
85
|
+
client2_messages = nil
|
86
|
+
|
87
|
+
client1_messages = em_stream do |client1, client1_messages|
|
88
|
+
# if this is the first message to client 1 set up another connection from the same client
|
89
|
+
if client1_messages.one?
|
90
|
+
client1.callback do
|
91
|
+
client1.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json)
|
92
|
+
end
|
93
|
+
|
94
|
+
client2_messages = em_stream do |client2, client2_messages|
|
95
|
+
case client2_messages.length
|
96
|
+
when 1
|
97
|
+
client2.callback { client2.send({ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }.to_json) }
|
98
|
+
when 2
|
99
|
+
socket_id = JSON.parse(client1_messages.first['data'])['socket_id']
|
100
|
+
Pusher['MY_CHANNEL'].trigger 'an_event', { some: 'data' }, socket_id
|
101
|
+
when 3
|
102
|
+
EM.stop
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
client1_messages.should have_attributes count: 2
|
109
|
+
|
110
|
+
client2_messages.should have_attributes last_event: 'an_event',
|
111
|
+
last_data: { some: 'data' }.to_json
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Integration' do
|
5
|
+
|
6
|
+
before(:each) { start_slanger }
|
7
|
+
|
8
|
+
context "connecting with invalid credentials" do
|
9
|
+
it "sends an error message" do
|
10
|
+
messages = em_stream(key: 'bogus_key') do |websocket, messages|
|
11
|
+
websocket.callback { EM.stop }
|
12
|
+
end
|
13
|
+
messages.should have_attributes count: 1, last_event: 'pusher:error',
|
14
|
+
connection_established: false, id_present: false
|
15
|
+
messages.first['data'] == 'Could not find app by key bogus_key'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "connecting with valid credentials" do
|
20
|
+
it "should succeed and include activity_timeout value in handshake" do
|
21
|
+
messages = em_stream do |websocket, messages|
|
22
|
+
websocket.callback { EM.stop }
|
23
|
+
end
|
24
|
+
messages.should have_attributes activity_timeout: Slanger::Config.activity_timeout,
|
25
|
+
connection_established: true, id_present: true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "connect with valid protocol version" do
|
30
|
+
it "should connect successfuly" do
|
31
|
+
messages = em_stream do |websocket, messages|
|
32
|
+
websocket.callback { EM.stop }
|
33
|
+
end
|
34
|
+
messages.should have_attributes connection_established: true, id_present: true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "connect with invalid protocol version" do
|
39
|
+
it "should not connect successfuly with version bigger than supported" do
|
40
|
+
messages = em_stream(protocol: "20") do |websocket, messages|
|
41
|
+
websocket.callback { EM.stop }
|
42
|
+
end
|
43
|
+
messages.should have_attributes connection_established: false, id_present: false,
|
44
|
+
last_event: 'pusher:error'
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not connect successfuly without specified version" do
|
48
|
+
messages = em_stream(protocol: nil) do |websocket, messages|
|
49
|
+
websocket.callback { EM.stop }
|
50
|
+
end
|
51
|
+
messages.should have_attributes connection_established: false, id_present: false,
|
52
|
+
last_event: 'pusher:error'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "given invalid JSON as input" do
|
57
|
+
it 'should not crash' do
|
58
|
+
messages = em_stream do |websocket, messages|
|
59
|
+
websocket.callback do
|
60
|
+
websocket.send("{ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }23123")
|
61
|
+
EM.next_tick { EM.stop }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
EM.run { new_websocket.tap { |u| u.stream { EM.next_tick { EM.stop } } }}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'Integration' do
|
6
|
+
|
7
|
+
before(:each) { start_slanger }
|
8
|
+
|
9
|
+
describe 'presence channels:' do
|
10
|
+
context 'subscribing without channel data' do
|
11
|
+
context 'and bogus authentication credentials' do
|
12
|
+
it 'sends back an error message' do
|
13
|
+
messages = em_stream do |websocket, messages|
|
14
|
+
case messages.length
|
15
|
+
when 1
|
16
|
+
websocket.send({ event: 'pusher:subscribe', data: { channel: 'presence-channel', auth: 'bogus' } }.to_json)
|
17
|
+
else
|
18
|
+
EM.stop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
messages.should have_attributes connection_established: true, id_present: true,
|
23
|
+
count: 2,
|
24
|
+
last_event: 'pusher:error'
|
25
|
+
|
26
|
+
expect(JSON.parse(messages.last['data'])['message']).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'subscribing with channel data' do
|
32
|
+
context 'and bogus authentication credentials' do
|
33
|
+
it 'sends back an error message' do
|
34
|
+
messages = em_stream do |websocket, messages|
|
35
|
+
case messages.length
|
36
|
+
when 1
|
37
|
+
websocket.send({ event: 'pusher:subscribe', data: {
|
38
|
+
channel: 'presence-lel',
|
39
|
+
auth: 'boog',
|
40
|
+
channel_data: {
|
41
|
+
user_id: "barry",
|
42
|
+
}
|
43
|
+
}.to_json }.to_json)
|
44
|
+
else
|
45
|
+
EM.stop
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
messages.should have_attributes first_event: 'pusher:connection_established', count: 2,
|
50
|
+
id_present: true
|
51
|
+
|
52
|
+
# Channel id should be in the payload
|
53
|
+
messages.last['event'].should == 'pusher:error'
|
54
|
+
expect(JSON.parse(messages.last['data'])['message']).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with genuine authentication credentials' do
|
59
|
+
it 'sends back a success message' do
|
60
|
+
messages = em_stream do |websocket, messages|
|
61
|
+
case messages.length
|
62
|
+
when 1
|
63
|
+
send_subscribe( user: websocket,
|
64
|
+
user_id: '0f177369a3b71275d25ab1b44db9f95f',
|
65
|
+
name: 'SG',
|
66
|
+
message: messages.first)
|
67
|
+
else
|
68
|
+
EM.stop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
messages.should have_attributes connection_established: true, count: 2
|
73
|
+
|
74
|
+
messages.last.should == {"channel"=>"presence-channel",
|
75
|
+
"event" =>"pusher_internal:subscription_succeeded",
|
76
|
+
"data" => "{\"presence\":{\"count\":1,\"ids\":[\"0f177369a3b71275d25ab1b44db9f95f\"],\"hash\":{\"0f177369a3b71275d25ab1b44db9f95f\":{\"name\":\"SG\"}}}}"}
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
context 'with more than one subscriber subscribed to the channel' do
|
83
|
+
it 'sends a member added message to the existing subscribers' do
|
84
|
+
messages = em_stream do |user1, messages|
|
85
|
+
case messages.length
|
86
|
+
when 1
|
87
|
+
send_subscribe(user: user1,
|
88
|
+
user_id: '0f177369a3b71275d25ab1b44db9f95f',
|
89
|
+
name: 'SG',
|
90
|
+
message: messages.first
|
91
|
+
)
|
92
|
+
|
93
|
+
when 2
|
94
|
+
new_websocket.tap do |u|
|
95
|
+
u.stream do |message|
|
96
|
+
message = JSON.parse(message)
|
97
|
+
if message['event'] == 'pusher:connection_established'
|
98
|
+
send_subscribe \
|
99
|
+
user: u, user_id: '37960509766262569d504f02a0ee986d',
|
100
|
+
name: 'CHROME', message: message
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
else
|
105
|
+
EM.stop
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
messages.should have_attributes connection_established: true, count: 3
|
111
|
+
# Channel id should be in the payload
|
112
|
+
messages[1].should == {"channel"=>"presence-channel", "event"=>"pusher_internal:subscription_succeeded",
|
113
|
+
"data"=>"{\"presence\":{\"count\":1,\"ids\":[\"0f177369a3b71275d25ab1b44db9f95f\"],\"hash\":{\"0f177369a3b71275d25ab1b44db9f95f\":{\"name\":\"SG\"}}}}"}
|
114
|
+
|
115
|
+
messages.last.should == {"channel"=>"presence-channel", "event"=>"pusher_internal:member_added",
|
116
|
+
"data"=>{"user_id"=>"37960509766262569d504f02a0ee986d", "user_info"=>{"name"=>"CHROME"}}}
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'does not send multiple member added and member removed messages if one subscriber opens multiple connections, i.e. multiple browser tabs.' do
|
120
|
+
messages = em_stream do |user1, messages|
|
121
|
+
case messages.length
|
122
|
+
when 1
|
123
|
+
send_subscribe(user: user1,
|
124
|
+
user_id: '0f177369a3b71275d25ab1b44db9f95f',
|
125
|
+
name: 'SG',
|
126
|
+
message: messages.first)
|
127
|
+
|
128
|
+
when 2
|
129
|
+
10.times do
|
130
|
+
new_websocket.tap do |u|
|
131
|
+
u.stream do |message|
|
132
|
+
# remove stream callback
|
133
|
+
## close the connection in the next tick as soon as subscription is acknowledged
|
134
|
+
u.stream { EM.next_tick { u.close_connection } }
|
135
|
+
|
136
|
+
send_subscribe({ user: u,
|
137
|
+
user_id: '37960509766262569d504f02a0ee986d',
|
138
|
+
name: 'CHROME',
|
139
|
+
message: JSON.parse(message)})
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
when 4
|
144
|
+
EM.next_tick { EM.stop }
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
# There should only be one set of presence messages sent to the refernce user for the second user.
|
150
|
+
messages.one? { |message| message['event'] == 'pusher_internal:member_added' && message['data']['user_id'] == '37960509766262569d504f02a0ee986d' }.should be_true
|
151
|
+
messages.one? { |message| message['event'] == 'pusher_internal:member_removed' && message['data']['user_id'] == '37960509766262569d504f02a0ee986d' }.should be_true
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Integration' do
|
5
|
+
|
6
|
+
before(:each) { start_slanger }
|
7
|
+
|
8
|
+
describe 'private channels' do
|
9
|
+
context 'with valid authentication credentials:' do
|
10
|
+
it 'accepts the subscription request' do
|
11
|
+
messages = em_stream do |websocket, messages|
|
12
|
+
case messages.length
|
13
|
+
when 1
|
14
|
+
private_channel websocket, messages.first
|
15
|
+
else
|
16
|
+
EM.stop
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
messages.should have_attributes connection_established: true,
|
21
|
+
count: 2,
|
22
|
+
id_present: true,
|
23
|
+
last_event: 'pusher_internal:subscription_succeeded'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with bogus authentication credentials:' do
|
28
|
+
it 'sends back an error message' do
|
29
|
+
messages = em_stream do |websocket, messages|
|
30
|
+
case messages.length
|
31
|
+
when 1
|
32
|
+
websocket.send({ event: 'pusher:subscribe',
|
33
|
+
data: { channel: 'private-channel',
|
34
|
+
auth: 'bogus' } }.to_json)
|
35
|
+
else
|
36
|
+
EM.stop
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
messages.should have_attributes connection_established: true, count: 2, id_present: true, last_event:
|
41
|
+
'pusher:error'
|
42
|
+
|
43
|
+
expect(JSON.parse(messages.last['data'])['message']).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'client events' do
|
48
|
+
it "sends event to other channel subscribers" do
|
49
|
+
client1_messages, client2_messages = [], []
|
50
|
+
|
51
|
+
em_thread do
|
52
|
+
client1, client2 = new_websocket, new_websocket
|
53
|
+
client2_messages, client1_messages = [], []
|
54
|
+
|
55
|
+
stream(client1, client1_messages) do |message|
|
56
|
+
case client1_messages.length
|
57
|
+
when 1
|
58
|
+
private_channel client1, client1_messages.first
|
59
|
+
when 3
|
60
|
+
EM.next_tick { EM.stop }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
stream(client2, client2_messages) do |message|
|
65
|
+
case client2_messages.length
|
66
|
+
when 1
|
67
|
+
private_channel client2, client2_messages.first
|
68
|
+
when 2
|
69
|
+
client2.send({ event: 'client-something', data: { some: 'stuff' }, channel: 'private-channel' }.to_json)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
client1_messages.one? { |m| m['event'] == 'client-something' }.should be_true
|
75
|
+
client2_messages.none? { |m| m['event'] == 'client-something' }.should be_true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'lib/slanger/handler.rb'
|
3
|
+
|
4
|
+
class ReplacedHandler < Slanger::Handler
|
5
|
+
def authenticate
|
6
|
+
super
|
7
|
+
send_payload nil, 'pusher:info', { message: "Welcome!" }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'Replacable handler' do
|
12
|
+
it 'says welcome' do
|
13
|
+
start_slanger_with_options socket_handler: ReplacedHandler
|
14
|
+
|
15
|
+
msgs = em_stream do |websocket, messages|
|
16
|
+
if messages.length == 2
|
17
|
+
EM.stop
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
msgs.last.should == { "event" => "pusher:info", "data" =>"{\"message\":\"Welcome!\"}" }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Integration' do
|
4
|
+
describe 'Slanger when configured to use SSL' do
|
5
|
+
it 'encrypts the connection' do
|
6
|
+
start_slanger_with_options tls_options: {
|
7
|
+
cert_chain_file: 'spec/server.crt',
|
8
|
+
private_key_file: 'spec/server.key'
|
9
|
+
}
|
10
|
+
|
11
|
+
socket = TCPSocket.new('0.0.0.0', 8080)
|
12
|
+
expected_cert = OpenSSL::X509::Certificate.new(File.open('spec/server.crt'))
|
13
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket)
|
14
|
+
ssl_socket.connect
|
15
|
+
ssl_socket.peer_cert.to_s.should == expected_cert.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|