slangerq 0.6.1

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.
@@ -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
+ expect(messages).to 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
+ expect(messages).to 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
+ expect(messages).to 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
+ expect(messages).to 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
+ expect(messages).to 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,157 @@
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
+ expect(messages).to 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
+ expect(messages).to have_attributes first_event: 'pusher:connection_established', count: 2,
50
+ id_present: true
51
+
52
+ # Channel id should be in the payload
53
+ expect(messages.last['event']).to eq('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
+ expect(messages).to have_attributes connection_established: true, count: 2
73
+
74
+ expect(messages.last).to eq({"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
+ when 2
93
+ new_websocket.tap do |u|
94
+ u.stream do |message|
95
+ message = JSON.parse(message)
96
+ if message['event'] == 'pusher:connection_established'
97
+ send_subscribe \
98
+ user: u, user_id: '37960509766262569d504f02a0ee986d',
99
+ name: 'CHROME', message: message
100
+ end
101
+ end
102
+ end
103
+ else
104
+ EM.stop
105
+ end
106
+
107
+ end
108
+
109
+ expect(messages).to have_attributes connection_established: true, count: 3
110
+ # Channel id should be in the payload
111
+ expect(messages[1]).to eq({"channel"=>"presence-channel", "event"=>"pusher_internal:subscription_succeeded",
112
+ "data"=>"{\"presence\":{\"count\":1,\"ids\":[\"0f177369a3b71275d25ab1b44db9f95f\"],\"hash\":{\"0f177369a3b71275d25ab1b44db9f95f\":{\"name\":\"SG\"}}}}"})
113
+
114
+ expect(messages.last).to eq({"channel"=>"presence-channel", "event"=>"pusher_internal:member_added",
115
+ "data"=>{"user_id"=>"37960509766262569d504f02a0ee986d", "user_info"=>{"name"=>"CHROME"}}})
116
+ end
117
+
118
+ it 'does not send multiple member added and member removed messages if one subscriber opens multiple connections, i.e. multiple browser tabs.' do
119
+ messages = em_stream do |user1, messages|
120
+ case messages.length
121
+ when 1
122
+ send_subscribe(user: user1,
123
+ user_id: '0f177369a3b71275d25ab1b44db9f95f',
124
+ name: 'SG',
125
+ message: messages.first)
126
+
127
+ when 2
128
+ 10.times do
129
+ new_websocket.tap do |u|
130
+ u.stream do |message|
131
+ # remove stream callback
132
+ ## close the connection in the next tick as soon as subscription is acknowledged
133
+ u.stream { EM.next_tick { u.close_connection } }
134
+
135
+ send_subscribe({ user: u,
136
+ user_id: '37960509766262569d504f02a0ee986d',
137
+ name: 'CHROME',
138
+ message: JSON.parse(message)})
139
+ end
140
+ end
141
+ end
142
+ when 4
143
+ EM.next_tick { EM.stop }
144
+ end
145
+
146
+ end
147
+
148
+ # There should only be one set of presence messages sent to the refernce user for the second user.
149
+ expect(messages.one? { |message| message['event'] == 'pusher_internal:member_added' && message['data']['user_id'] == '37960509766262569d504f02a0ee986d' }).to eq(true)
150
+ expect(messages.one? { |message| message['event'] == 'pusher_internal:member_removed' && message['data']['user_id'] == '37960509766262569d504f02a0ee986d' }).to eq(true)
151
+
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+ 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
+ expect(messages).to 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
+ expect(messages).to 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
+ expect(client1_messages.one? { |m| m['event'] == 'client-something' }).to eq(true)
75
+ expect(client2_messages.none? { |m| m['event'] == 'client-something' }).to eq(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
+ expect(msgs.last).to eq({ "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
+ expect(ssl_socket.peer_cert.to_s).to eq(expected_cert.to_s)
16
+ end
17
+ end
18
+ end
data/spec/server.crt ADDED
@@ -0,0 +1,12 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIBvzCCASgCCQCsMkmDVYNDETANBgkqhkiG9w0BAQUFADAkMQswCQYDVQQGEwJE
3
+ RTEVMBMGA1UEAwwMc2xhbmdlci50ZXN0MB4XDTEyMDQxMTE2NDMwNloXDTE5MDIx
4
+ NDE2NDMwNlowJDELMAkGA1UEBhMCREUxFTATBgNVBAMMDHNsYW5nZXIudGVzdDCB
5
+ nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAmlTxrcqXw+hbdjnpNENgx4p6T+x7
6
+ SgN/5ti3+gr5ZJElebEdJdGymM/KK817GFhLuYSEv72oEVitC1ISCfo/iOu4S71Y
7
+ sGsrdyPVl3cDswSkvmo27J3rtAbY1fNDs68YFAQGH8wlQtPSPvd9KBKg0klafsDU
8
+ VvDYjQ+XZ2+ZKZECAwEAATANBgkqhkiG9w0BAQUFAAOBgQCHGqUddcsTfvV0Nk3F
9
+ zZ5kGvAiZ02MUourZ4GVs5uBYtkIrQ7HAlQbHAbC8d7e0UVgcTwUKgwpw/RfNR/O
10
+ Ho/zn7lPciLQ7VMnOZ2+MfbJ2HIFgZL6qH1gTcQpBW4s3gKR5hFpaGJ+8l/cmEWj
11
+ AvywaOcSLex+q0OwZaiusiDorg==
12
+ -----END CERTIFICATE-----
data/spec/server.key ADDED
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXQIBAAKBgQCaVPGtypfD6Ft2Oek0Q2DHinpP7HtKA3/m2Lf6CvlkkSV5sR0l
3
+ 0bKYz8orzXsYWEu5hIS/vagRWK0LUhIJ+j+I67hLvViwayt3I9WXdwOzBKS+ajbs
4
+ neu0BtjV80OzrxgUBAYfzCVC09I+930oEqDSSVp+wNRW8NiND5dnb5kpkQIDAQAB
5
+ AoGABGzBDSGM3mIQFUCtzgDMiowO27HFCyc0iJLYG4QrCFYdA/MvCcGMZFM40a6v
6
+ g9AsQ6JoB/NRGUY4l+V/fOe+4Iuycf8+vN1mrSVR1lTjy/mOwj900pc4ff6cDv6S
7
+ bI/hm4BNiuj8OD11R+ZK07Lo1iCzBkAy53RkTFcBk74MYgECQQDMyAMT0DhlRBqD
8
+ 4vrPF+GZ+rMYpeTjuNDOZphIwzxpv70uyh2RNg+7F6U91Qxz6vpbIkz8Zf4TgdwM
9
+ u/rroktxAkEAwO6wyzidm2yrPMPtnwxDIYnH/ETdNraa3JyHsjXsQwGAIG80+hCv
10
+ QfCA/LmvNOm/Mpe1EyiAeI1/YJp4a2xwIQJBAKysFpQ1ZehVtbnxwaSwMWXiE/Q7
11
+ pjYyl7cCoXPxVFai+8WhXa8dE8Shmo75v2dbAsGnuZy177jJLiB6vYjFL7ECQQCI
12
+ Zri7lLVo8zVFasgO0F6N0ZmAMzeqvQNTwZ72UcVNwjvRso3j1fPyTJUFGEpUwIWa
13
+ wUMV3mal1HQf2lYUrL/BAkBBtXqOLFHINHUmffdRSV/2HECYXHazb6lAnL8nnQX0
14
+ vin2ujCli9mcYWnrY7zwlXdAxgQv5Q2ByQT9Fd8S7FjA
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,109 @@
1
+ module SlangerHelperMethods
2
+ def start_slanger_with_options options={}
3
+ # Fork service. Our integration tests MUST block the main thread because we want to wait for i/o to finish.
4
+ @server_pid = EM.fork_reactor do
5
+ Thin::Logging.silent = true
6
+
7
+ opts = { host: '0.0.0.0',
8
+ api_port: '4567',
9
+ websocket_port: '8080',
10
+ app_key: '765ec374ae0a69f4ce44',
11
+ secret: 'your-pusher-secret',
12
+ activity_timeout: 100
13
+ }
14
+
15
+ Slanger::Config.load opts.merge(options)
16
+
17
+ Slanger::Service.run
18
+ end
19
+ wait_for_slanger
20
+ end
21
+
22
+ alias start_slanger start_slanger_with_options
23
+
24
+ def stop_slanger
25
+ # Ensure Slanger is properly stopped. No orphaned processes allowed!
26
+ Process.kill 'SIGKILL', @server_pid
27
+ Process.wait @server_pid
28
+ end
29
+
30
+ def wait_for_slanger opts = {}
31
+ opts = { port: 8080 }.update opts
32
+ begin
33
+ TCPSocket.new('0.0.0.0', opts[:port]).close
34
+ rescue
35
+ sleep 0.005
36
+ retry
37
+ end
38
+ end
39
+
40
+ def new_websocket opts = {}
41
+ opts = { key: Pusher.key, protocol: 7 }.update opts
42
+ uri = "ws://0.0.0.0:8080/app/#{opts[:key]}?client=js&version=1.8.5&protocol=#{opts[:protocol]}"
43
+
44
+ EM::HttpRequest.new(uri).get.tap { |ws| ws.errback &errback }
45
+ end
46
+
47
+ def em_stream opts = {}
48
+ messages = []
49
+
50
+ em_thread do
51
+ websocket = new_websocket opts
52
+
53
+ stream(websocket, messages) do |message|
54
+ yield websocket, messages
55
+ end
56
+ end
57
+
58
+ return messages
59
+ end
60
+
61
+ def em_thread
62
+ Thread.new do
63
+ EM.run do
64
+ yield
65
+ end
66
+ end.join
67
+ end
68
+
69
+ def stream websocket, messages
70
+ websocket.stream do |message|
71
+ messages << JSON.parse(message)
72
+
73
+ yield message
74
+ end
75
+ end
76
+
77
+ def send_subscribe options
78
+ info = { user_id: options[:user_id], user_info: { name: options[:name] } }
79
+ socket_id = JSON.parse(options[:message]['data'])['socket_id']
80
+ to_sign = [socket_id, 'presence-channel', info.to_json].join ':'
81
+
82
+ digest = OpenSSL::Digest::SHA256.new
83
+
84
+ options[:user].send({
85
+ event: 'pusher:subscribe',
86
+ data: {
87
+ auth: [Pusher.key, OpenSSL::HMAC.hexdigest(digest, Pusher.secret, to_sign)].join(':'),
88
+ channel_data: info.to_json,
89
+ channel: 'presence-channel'
90
+ }
91
+ }.to_json)
92
+ end
93
+
94
+ def private_channel websocket, message
95
+ socket_id = JSON.parse(message['data'])['socket_id']
96
+ to_sign = [socket_id, 'private-channel'].join ':'
97
+
98
+ digest = OpenSSL::Digest::SHA256.new
99
+
100
+ websocket.send({
101
+ event: 'pusher:subscribe',
102
+ data: {
103
+ auth: [Pusher.key, OpenSSL::HMAC.hexdigest(digest, Pusher.secret, to_sign)].join(':'),
104
+ channel: 'private-channel'
105
+ }
106
+ }.to_json)
107
+
108
+ end
109
+ end