oceanex-slanger 0.7.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,67 @@
1
+ #encoding: utf-8
2
+ require "spec_helper"
3
+
4
+ describe "Integration" do
5
+ before(:each) { start_slanger }
6
+
7
+ context "connecting with invalid credentials" do
8
+ it "sends an error message" do
9
+ messages = em_stream(key: "bogus_key") do |websocket, messages|
10
+ websocket.callback { EM.stop }
11
+ end
12
+ expect(messages).to have_attributes count: 1, last_event: "pusher:error",
13
+ connection_established: false, id_present: false
14
+ messages.first["data"] == "Could not find app by key bogus_key"
15
+ end
16
+ end
17
+
18
+ context "connecting with valid credentials" do
19
+ it "should succeed and include activity_timeout value in handshake" do
20
+ messages = em_stream do |websocket, messages|
21
+ websocket.callback { EM.stop }
22
+ end
23
+ expect(messages).to have_attributes activity_timeout: Slanger::Config.activity_timeout,
24
+ connection_established: true, id_present: true
25
+ end
26
+ end
27
+
28
+ context "connect with valid protocol version" do
29
+ it "should connect successfuly" do
30
+ messages = em_stream do |websocket, messages|
31
+ websocket.callback { EM.stop }
32
+ end
33
+ expect(messages).to have_attributes connection_established: true, id_present: true
34
+ end
35
+ end
36
+
37
+ context "connect with invalid protocol version" do
38
+ it "should not connect successfuly with version bigger than supported" do
39
+ messages = em_stream(protocol: "20") do |websocket, messages|
40
+ websocket.callback { EM.stop }
41
+ end
42
+ expect(messages).to have_attributes connection_established: false, id_present: false,
43
+ last_event: "pusher:error"
44
+ end
45
+
46
+ it "should not connect successfuly without specified version" do
47
+ messages = em_stream(protocol: nil) do |websocket, messages|
48
+ websocket.callback { EM.stop }
49
+ end
50
+ expect(messages).to have_attributes connection_established: false, id_present: false,
51
+ last_event: "pusher:error"
52
+ end
53
+ end
54
+
55
+ context "given invalid JSON as input" do
56
+ it "should not crash" do
57
+ messages = em_stream do |websocket, messages|
58
+ websocket.callback do
59
+ websocket.send("{ event: 'pusher:subscribe', data: { channel: 'MY_CHANNEL'} }23123")
60
+ EM.next_tick { EM.stop }
61
+ end
62
+ end
63
+
64
+ EM.run { new_websocket.tap { |u| u.stream { EM.next_tick { EM.stop } } } }
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,148 @@
1
+ #encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Integration" do
6
+ before(:each) { start_slanger }
7
+
8
+ describe "presence channels:" do
9
+ context "subscribing without channel data" do
10
+ context "and bogus authentication credentials" do
11
+ it "sends back an error message" do
12
+ messages = em_stream do |websocket, messages|
13
+ case messages.length
14
+ when 1
15
+ websocket.send({ event: "pusher:subscribe", data: { channel: "presence-channel", auth: "bogus" } }.to_json)
16
+ else
17
+ EM.stop
18
+ end
19
+ end
20
+
21
+ expect(messages).to have_attributes connection_established: true, id_present: true,
22
+ count: 2,
23
+ last_event: "pusher:error"
24
+
25
+ expect(JSON.parse(messages.last["data"])["message"]).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
26
+ end
27
+ end
28
+ end
29
+
30
+ context "subscribing with channel data" do
31
+ context "and bogus authentication credentials" do
32
+ it "sends back an error message" do
33
+ messages = em_stream do |websocket, messages|
34
+ case messages.length
35
+ when 1
36
+ websocket.send({ event: "pusher:subscribe", data: {
37
+ channel: "presence-lel",
38
+ auth: "boog",
39
+ channel_data: {
40
+ user_id: "barry",
41
+ },
42
+ }.to_json }.to_json)
43
+ else
44
+ EM.stop
45
+ end
46
+ end
47
+
48
+ expect(messages).to have_attributes first_event: "pusher:connection_established", count: 2,
49
+ id_present: true
50
+
51
+ # Channel id should be in the payload
52
+ expect(messages.last["event"]).to eq("pusher:error")
53
+ expect(JSON.parse(messages.last["data"])["message"]).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
54
+ end
55
+ end
56
+
57
+ context "with genuine authentication credentials" do
58
+ it "sends back a success message" do
59
+ messages = em_stream do |websocket, messages|
60
+ case messages.length
61
+ when 1
62
+ send_subscribe(user: websocket,
63
+ user_id: "0f177369a3b71275d25ab1b44db9f95f",
64
+ name: "SG",
65
+ message: messages.first)
66
+ else
67
+ EM.stop
68
+ end
69
+ end
70
+
71
+ expect(messages).to have_attributes connection_established: true, count: 2
72
+
73
+ expect(messages.last).to eq({ "channel" => "presence-channel",
74
+ "event" => "pusher_internal:subscription_succeeded",
75
+ "data" => "{\"presence\":{\"count\":1,\"ids\":[\"0f177369a3b71275d25ab1b44db9f95f\"],\"hash\":{\"0f177369a3b71275d25ab1b44db9f95f\":{\"name\":\"SG\"}}}}" })
76
+ end
77
+
78
+ context "with more than one subscriber subscribed to the channel" do
79
+ it "sends a member added message to the existing subscribers" do
80
+ messages = em_stream do |user1, messages|
81
+ case messages.length
82
+ when 1
83
+ send_subscribe(user: user1,
84
+ user_id: "0f177369a3b71275d25ab1b44db9f95f",
85
+ name: "SG",
86
+ message: messages.first)
87
+ when 2
88
+ new_websocket.tap do |u|
89
+ u.stream do |message|
90
+ message = JSON.parse(message)
91
+ if message["event"] == "pusher:connection_established"
92
+ send_subscribe \
93
+ user: u, user_id: "37960509766262569d504f02a0ee986d",
94
+ name: "CHROME", message: message
95
+ end
96
+ end
97
+ end
98
+ else
99
+ EM.stop
100
+ end
101
+ end
102
+
103
+ expect(messages).to have_attributes connection_established: true, count: 3
104
+ # Channel id should be in the payload
105
+ expect(messages[1]).to eq({ "channel" => "presence-channel", "event" => "pusher_internal:subscription_succeeded",
106
+ "data" => "{\"presence\":{\"count\":1,\"ids\":[\"0f177369a3b71275d25ab1b44db9f95f\"],\"hash\":{\"0f177369a3b71275d25ab1b44db9f95f\":{\"name\":\"SG\"}}}}" })
107
+
108
+ expect(messages.last).to eq({ "channel" => "presence-channel", "event" => "pusher_internal:member_added",
109
+ "data" => { "user_id" => "37960509766262569d504f02a0ee986d", "user_info" => { "name" => "CHROME" } } })
110
+ end
111
+
112
+ it "does not send multiple member added and member removed messages if one subscriber opens multiple connections, i.e. multiple browser tabs." do
113
+ messages = em_stream do |user1, messages|
114
+ case messages.length
115
+ when 1
116
+ send_subscribe(user: user1,
117
+ user_id: "0f177369a3b71275d25ab1b44db9f95f",
118
+ name: "SG",
119
+ message: messages.first)
120
+ when 2
121
+ 10.times do
122
+ new_websocket.tap do |u|
123
+ u.stream do |message|
124
+ # remove stream callback
125
+ ## close the connection in the next tick as soon as subscription is acknowledged
126
+ u.stream { EM.next_tick { u.close_connection } }
127
+
128
+ send_subscribe({ user: u,
129
+ user_id: "37960509766262569d504f02a0ee986d",
130
+ name: "CHROME",
131
+ message: JSON.parse(message) })
132
+ end
133
+ end
134
+ end
135
+ when 4
136
+ EM.next_tick { EM.stop }
137
+ end
138
+ end
139
+
140
+ # There should only be one set of presence messages sent to the refernce user for the second user.
141
+ expect(messages.one? { |message| message["event"] == "pusher_internal:member_added" && message["data"]["user_id"] == "37960509766262569d504f02a0ee986d" }).to eq(true)
142
+ expect(messages.one? { |message| message["event"] == "pusher_internal:member_removed" && message["data"]["user_id"] == "37960509766262569d504f02a0ee986d" }).to eq(true)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,77 @@
1
+ #encoding: utf-8
2
+ require "spec_helper"
3
+
4
+ describe "Integration" do
5
+ before(:each) { start_slanger }
6
+
7
+ describe "private channels" do
8
+ context "with valid authentication credentials:" do
9
+ it "accepts the subscription request" do
10
+ messages = em_stream do |websocket, messages|
11
+ case messages.length
12
+ when 1
13
+ private_channel websocket, messages.first
14
+ else
15
+ EM.stop
16
+ end
17
+ end
18
+
19
+ expect(messages).to have_attributes connection_established: true,
20
+ count: 2,
21
+ id_present: true,
22
+ last_event: "pusher_internal:subscription_succeeded"
23
+ end
24
+ end
25
+
26
+ context "with bogus authentication credentials:" do
27
+ it "sends back an error message" do
28
+ messages = em_stream do |websocket, messages|
29
+ case messages.length
30
+ when 1
31
+ websocket.send({ event: "pusher:subscribe",
32
+ data: { channel: "private-channel",
33
+ auth: "bogus" } }.to_json)
34
+ else
35
+ EM.stop
36
+ end
37
+ end
38
+
39
+ expect(messages).to have_attributes connection_established: true, count: 2, id_present: true, last_event: "pusher:error"
40
+
41
+ expect(JSON.parse(messages.last["data"])["message"]).to match /^Invalid signature: Expected HMAC SHA256 hex digest of/
42
+ end
43
+ end
44
+
45
+ describe "client events" do
46
+ it "sends event to other channel subscribers" do
47
+ client1_messages, client2_messages = [], []
48
+
49
+ em_thread do
50
+ client1, client2 = new_websocket, new_websocket
51
+ client2_messages, client1_messages = [], []
52
+
53
+ stream(client1, client1_messages) do |message|
54
+ case client1_messages.length
55
+ when 1
56
+ private_channel client1, client1_messages.first
57
+ when 3
58
+ EM.next_tick { EM.stop }
59
+ end
60
+ end
61
+
62
+ stream(client2, client2_messages) do |message|
63
+ case client2_messages.length
64
+ when 1
65
+ private_channel client2, client2_messages.first
66
+ when 2
67
+ client2.send({ event: "client-something", data: { some: "stuff" }, channel: "private-channel" }.to_json)
68
+ end
69
+ end
70
+ end
71
+
72
+ expect(client1_messages.one? { |m| m["event"] == "client-something" }).to eq(true)
73
+ expect(client2_messages.none? { |m| m["event"] == "client-something" }).to eq(true)
74
+ end
75
+ end
76
+ end
77
+ 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
@@ -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-----
@@ -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,107 @@
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
+ Slanger::Config.load opts.merge(options)
15
+
16
+ Slanger::Service.run
17
+ end
18
+ wait_for_slanger
19
+ end
20
+
21
+ alias start_slanger start_slanger_with_options
22
+
23
+ def stop_slanger
24
+ # Ensure Slanger is properly stopped. No orphaned processes allowed!
25
+ Process.kill "SIGKILL", @server_pid
26
+ Process.wait @server_pid
27
+ end
28
+
29
+ def wait_for_slanger(opts = {})
30
+ opts = { port: 8080 }.update opts
31
+ begin
32
+ TCPSocket.new("0.0.0.0", opts[:port]).close
33
+ rescue
34
+ sleep 0.005
35
+ retry
36
+ end
37
+ end
38
+
39
+ def new_websocket(opts = {})
40
+ opts = { key: Pusher.key, protocol: 7 }.update opts
41
+ uri = "ws://0.0.0.0:8080/app/#{opts[:key]}?client=js&version=1.8.5&protocol=#{opts[:protocol]}"
42
+
43
+ EM::HttpRequest.new(uri).get.tap { |ws| ws.errback &errback }
44
+ end
45
+
46
+ def em_stream(opts = {})
47
+ messages = []
48
+
49
+ em_thread do
50
+ websocket = new_websocket opts
51
+
52
+ stream(websocket, messages) do |message|
53
+ yield websocket, messages
54
+ end
55
+ end
56
+
57
+ return messages
58
+ end
59
+
60
+ def em_thread
61
+ Thread.new do
62
+ EM.run do
63
+ yield
64
+ end
65
+ end.join
66
+ end
67
+
68
+ def stream(websocket, messages)
69
+ websocket.stream do |message|
70
+ messages << JSON.parse(message)
71
+
72
+ yield message
73
+ end
74
+ end
75
+
76
+ def send_subscribe(options)
77
+ info = { user_id: options[:user_id], user_info: { name: options[:name] } }
78
+ socket_id = JSON.parse(options[:message]["data"])["socket_id"]
79
+ to_sign = [socket_id, "presence-channel", info.to_json].join ":"
80
+
81
+ digest = OpenSSL::Digest::SHA256.new
82
+
83
+ options[:user].send({
84
+ event: "pusher:subscribe",
85
+ data: {
86
+ auth: [Pusher.key, OpenSSL::HMAC.hexdigest(digest, Pusher.secret, to_sign)].join(":"),
87
+ channel_data: info.to_json,
88
+ channel: "presence-channel",
89
+ },
90
+ }.to_json)
91
+ end
92
+
93
+ def private_channel(websocket, message)
94
+ socket_id = JSON.parse(message["data"])["socket_id"]
95
+ to_sign = [socket_id, "private-channel"].join ":"
96
+
97
+ digest = OpenSSL::Digest::SHA256.new
98
+
99
+ websocket.send({
100
+ event: "pusher:subscribe",
101
+ data: {
102
+ auth: [Pusher.key, OpenSSL::HMAC.hexdigest(digest, Pusher.secret, to_sign)].join(":"),
103
+ channel: "private-channel",
104
+ },
105
+ }.to_json)
106
+ end
107
+ end