web_socket_chat_server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 908c10e8b6e1d6907c83b755b5698882957ecd97
4
+ data.tar.gz: 2b8c2ff2b5beb46cc2bb995c36901193b25b5e94
5
+ SHA512:
6
+ metadata.gz: a1cebe92b3d42bfdaae20a4c8f47f5f33cb544e0009fe16eb0a38269a05b2ef13ed05f83db39d38f5d7a0fc33ab442313106338208370282e40473607dd184ad
7
+ data.tar.gz: 8b410fa05463db45710a2676f0e653df42dec982b8e04f05a8b7690e62fe29e6eebac47c72a9ffd0cd45715e68e736127649387c879d26728d0954e737080527
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in web_socket_chat_server.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Jone Samra
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # WebSocketChatServer
2
+
3
+ A wrapper class for em-websocket (https://github.com/igrigorik/em-websocket) implementing a custom chat server protocol.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'web_socket_chat_server'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install web_socket_chat_server
20
+
21
+ ## Usage
22
+
23
+ An example on how to run the server:
24
+
25
+ ```ruby
26
+ require "web_socket_chat_server.rb"
27
+
28
+ admin1 = WebSocketChatServer::ChatUser.new(:username => "admin", :password => "secret")
29
+ admin2 = WebSocketChatServer::ChatUser.new(:username => "admin2", :password => "secret2")
30
+ admins = []
31
+ admins << admin1
32
+ admins << admin2
33
+
34
+ chat = WebSocketChatServer::ChatServer.new(:host=> "0.0.0.0", :port => "8080", :admins => admins)
35
+
36
+ chat.start_server do |response|
37
+ puts response
38
+ end
39
+
40
+ ['TERM', 'INT'].each do |signal|
41
+ trap(signal){
42
+ chat.stop_server()
43
+ }
44
+ end
45
+
46
+ wait = gets
47
+ ```
48
+
49
+ The code above creates an array consisting of two admins (ChatUser objects) and passes them to initialize(). The server will listen on IP 0.0.0.0 and the port 8080 (Required parameters).
50
+
51
+ By running the code above, you do not need to do any other things. The class takes care of the of itself by handling the connections and the logic of the server’s custom protocol. You still though have the opportunity to print out the commands generated by the server by passing a block to the start_server() function as it has been done above.
52
+
53
+ On the client side, use the HTML WebSocket API to connect to the server. An example on a fully working Chat client will be attached to this repo (CHAT_CLIENT_EXAMPLE).
54
+
55
+ To explain how to connect to the server briefly:
56
+
57
+ You have to pass the query parameters username & password to the socket client when connecting.
58
+
59
+ On successful connection, you can send a JSON hash of the format {command: ”…”, data: ”…}
60
+
61
+ ### Available commands are:
62
+ {command: ”chat_message”, data: ”your message”}
63
+
64
+ {command: ”private_message”, data: {to_user: ”chuck_norris”, message: ”Private message”}}
65
+
66
+ {command: ”ban_user”, data: ”chuck_norris”}
67
+
68
+
69
+ The server will respond with a JSON hash of the format {command: ”…”, data: ”…”, information: ”…”}
70
+
71
+ ### Available responses are:
72
+ {command: ”failed_connection”, data: nil, information: ”Some details”}
73
+
74
+ {command: ”successful_connection”, data: array_of_connected_users, information: ”Some details”}
75
+
76
+ {command: ”new_connection”, data: username, information: ”A new connected user”}
77
+
78
+ {command: ”chat_message”, data: {from_user: ”chuck_norris”, message: ”The message”}, information: ”Some details.”}
79
+
80
+ {command: ”ban_user”, data: TRUE or FALSE, information: ”Some details.”}
81
+
82
+ {command: ”private_message”, data: {from_user: ”chuck_norris”, message: ”The message”}, information: ”Some details.”}
83
+
84
+ {command: ”system_information”, data: ”Issue”, information: ”Some details.”}
85
+
86
+ {command: ”user_disconnected”, data: ”username”, information: ”Some details.”}
87
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,3 @@
1
+ module WebSocketChatServer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,414 @@
1
+ require 'socket'
2
+ require "em-websocket"
3
+ require "json"
4
+ module WebSocketChatServer
5
+
6
+ # A wrapper class for em-websocket (https://github.com/igrigorik/em-websocket) implementing a custom chat server protocol.
7
+
8
+ class ChatServer
9
+ # An array of string containing the banned usernames.
10
+ attr_accessor :banned_usernames
11
+
12
+ # The max number of allowed client connections.
13
+ attr_accessor :max_connections
14
+
15
+ # An array of ChatUser objects representing the admins of the chat.
16
+ attr_reader :admins
17
+
18
+ # A hash containing the connected users. The key contains of the user's username and the value is a ChatUser object.
19
+ attr_reader :connected_users
20
+
21
+ # The host address.
22
+ attr_reader :host
23
+
24
+ # The socket port to be used.
25
+ attr_reader :port
26
+
27
+ # The class initializer
28
+ # ==== args
29
+ # * +:server+ (Required) - The host of the chat server to run on.
30
+ # * +:port+ (Required) - The port of the server.
31
+ # * +:max_connections+ (Optional) - The max client connections allowed. If not passed, the default is 100.
32
+ # * +:secure+ (Optional) - In case of using Secure Servers, pass true as allowed by em-websocket (https://github.com/igrigorik/em-websocket#secure-server).
33
+ # * +:tls-options+ (Optional) - The TLS options as allowed by em-websocket (https://github.com/igrigorik/em-websocket#secure-server).
34
+ # * +:secure_proxy+ (Optional) - When ruinng behind a SSL proxy (https://github.com/igrigorik/em-websocket#running-behind-an-ssl-proxyterminator-like-stunnel).
35
+ # * +:admins+ (Optional) - In order to have admins (for banning users). Pass an array of ChatUser objects to this key. The ChatUser object contains of username and password attributes. The username must consist of at least 3 alphanumeric characters. The password must at least consist of 6 alphanumeric characters.
36
+ def initialize(args = {:host => "0.0.0.0", :port => 8080})
37
+ raise ArgumentError, "The :host parameter is required" unless args.has_key?(:host)
38
+ raise ArgumentError, "The :port parameter is required" unless args.has_key?(:port)
39
+ raise ArgumentError, "The port value is not valid" unless valid_port?( args[:port])
40
+ @max_connections = args[:max_connections].to_s.to_i
41
+ @max_connections = 100 if @max_connections == 0
42
+ @host = args[:host]
43
+ @port = args[:port]
44
+ @server_started = false
45
+ @admins = []
46
+ @banned_usernames = []
47
+ @connected_users = Hash.new
48
+
49
+ @server_options = Hash.new
50
+ @server_options[:host] = @host
51
+ @server_options[:port] = @port.to_i
52
+ @server_options[:secure] = args[:secure] if args.has_key?(:secure)
53
+ @server_options[:tls_options] = args[:tls_options] if args.has_key?(:tls_options)
54
+ @server_options[:secure_proxy] = args[:secure_proxy] if args.has_key?(:secure_proxy)
55
+
56
+
57
+ if args.has_key?(:admins)
58
+ if args[:admins].class == Array
59
+ args[:admins].each { |chat_user|
60
+ if chat_user.class == ChatUser
61
+ raise RuntimeError, "The admin's username has to be at least 3 alphanumeric characters, and the password at least 6 alphanumeric characters" unless user_credentials_valid?(chat_user.username, chat_user.password)
62
+ @admins << chat_user unless @admins.include?(chat_user)
63
+ end
64
+ }
65
+ end
66
+ end
67
+ end
68
+
69
+ # To start the server, call this method. It will return true if successful, false if it is already started. Otherwise, RuntimeError will be raised on errors. If giving a <b>block</b>, A hash <b>{command: "...", data: "...", information: "..."}</b> is yield.
70
+ def start_server()
71
+ return false if @server_started
72
+
73
+ begin
74
+ Thread.new {run_server() do |response|
75
+ yield response if block_given?
76
+ end
77
+ }
78
+ rescue Exception => e
79
+ @server_started = false
80
+ raise RuntimeError, e.message
81
+ end
82
+
83
+ until @server_started
84
+ next
85
+ end
86
+
87
+ @server_started
88
+ end
89
+
90
+ # To stop the server, call this method. True if the server is stopped, false if it is already stopped. Otherwise, RuntimeError is raised on errors.
91
+ def stop_server()
92
+ return false unless @server_started
93
+ @server_started = false
94
+ @banned_usernames.clear if @banned_usernames.count > 0
95
+ @admins.clear if @admins.count > 0
96
+ @connected_users.clear if @connected_users.count > 0
97
+ begin
98
+ EM::WebSocket.stop()
99
+ rescue Exception => e
100
+ raise RuntimeError, e.message
101
+ end
102
+ true
103
+ end
104
+
105
+ # Returns true if the server is started, otherwise false.
106
+ def started?()
107
+ @server_started
108
+ end
109
+
110
+ private
111
+ # This method will fire the eventmachine and the containing em-socket.
112
+ def run_server()
113
+ @server_started = true
114
+
115
+ EM.run do
116
+ EM::WebSocket.run(@server_options) do |ws|
117
+ ws.onopen { |handshake|
118
+ begin
119
+ response = nil
120
+ if (@connected_users.count + 1) > @max_connections
121
+ response = create_response_json("failed_connection", nil, "The max connections limit is reached.")
122
+ yield response if block_given?
123
+ ws.send(response)
124
+ ws.close
125
+ break
126
+ end
127
+
128
+ username = handshake.query["username"]
129
+ password = handshake.query["password"]
130
+ client_ip = Socket.unpack_sockaddr_in(ws.get_peername)[1]
131
+ user = ChatUser.new({:username => username, :password => password, :ip => client_ip})
132
+
133
+ if username.nil? || password.nil?
134
+ response = create_response_json("failed_connection", nil, "The username & password have to be passed in the query string.")
135
+ yield response if block_given?
136
+ ws.send(response)
137
+ ws.close
138
+ break
139
+ end
140
+
141
+ if admin_credentials_invalid?(username, password)
142
+ response = create_response_json("failed_connection", nil, "Invalid credentials.")
143
+ yield response if block_given?
144
+ ws.send(response)
145
+ ws.close
146
+ break
147
+ end
148
+
149
+ if user_credentials_valid?(username, password) == false
150
+ response = create_response_json("failed_connection", nil, "Invalid user creadentials. The username has to be at least 3 alphanumeric characters, and the password at least 6 alphanumeric characters.")
151
+ yield response if block_given?
152
+ ws.send(response)
153
+ ws.close
154
+ break
155
+ end
156
+
157
+ if user_banned?(user)
158
+ response = create_response_json("failed_connection", nil, "The user is banned")
159
+ yield response if block_given?
160
+ ws.send(response)
161
+ ws.close
162
+ break
163
+ end
164
+
165
+ number_of_tries = 0
166
+ while user_exists?(user)
167
+ number_of_tries = number_of_tries + 1
168
+ user.username = user.username + Random.rand(1..9999).to_s
169
+
170
+ if number_of_tries > 15
171
+ response = create_response_json("failed_connection", nil, "The username exists already.")
172
+ yield response if block_given?
173
+ ws.send(response)
174
+ ws.close
175
+ break
176
+ end
177
+ end
178
+
179
+ user.connection = ws
180
+
181
+ response = create_response_json("successful_connection", connected_users_to_array, "Connection accepted")
182
+ yield response if block_given?
183
+ ws.send(response)
184
+
185
+ @connected_users[user.username] = user
186
+ response = create_response_json("new_connection", user.username, "A new user is connected.")
187
+ yield response if block_given?
188
+ broadcast_message(response)
189
+ end while false
190
+ }
191
+ ws.onmessage { |msg|
192
+ json_data = nil
193
+ command = ""
194
+ data = ""
195
+ user = nil
196
+ begin
197
+ user = get_user_by_connection(ws)
198
+
199
+ if user.nil?
200
+ ws.close
201
+ break
202
+ end
203
+
204
+ begin
205
+ json_data = JSON.parse(msg)
206
+ command = json_data["command"]
207
+ data = json_data["data"]
208
+ rescue
209
+ break
210
+ end
211
+
212
+ case command
213
+ when "chat_message"
214
+ break if data == ""
215
+ response = create_response_json("chat_message", {"from_user" => user.username, "message" => data}, "A chat message.")
216
+ yield response if block_given?
217
+ broadcast_message(response)
218
+ break
219
+
220
+ when "ban_user"
221
+ break if data == ""
222
+ unless user_admin?(user)
223
+ ws.close
224
+ break
225
+ end
226
+
227
+ user_to_ban = get_user_by_username(data)
228
+ if user_to_ban.nil?
229
+ response = create_response_json("ban_user", false, "The user to ban did not exist.")
230
+ yield response if block_given?
231
+ ws.send(response)
232
+ break
233
+ end
234
+
235
+ if user_admin?(user_to_ban)
236
+ response = create_response_json("ban_user", false, "An admin user can not be banned.")
237
+ yield response if block_given?
238
+ ws.send(response)
239
+ break
240
+ end
241
+
242
+ @banned_usernames << user_to_ban.username
243
+ response = create_response_json("ban_user", true, "The user #{user_to_ban.username} has been banned.")
244
+ yield response if block_given?
245
+ ws.send(response)
246
+ user_to_ban.connection.close
247
+ break
248
+
249
+ when "private_message"
250
+ break unless data.is_a? Hash
251
+ break if data["to_user"].nil?
252
+ break if data["message"].nil?
253
+
254
+ user_to_receive = get_user_by_username(data["to_user"])
255
+ break if user_to_receive.nil?
256
+
257
+ response = create_response_json("private_message", {"from_user" => user.username, "message" => data["message"]}, "A private message.")
258
+ yield response if block_given?
259
+ user_to_receive.connection.send(response)
260
+ break
261
+
262
+ else
263
+ ws.send(create_response_json("system_information", "Unknown command", "Please make sure that you are sending the right command."))
264
+ ws.close
265
+ break
266
+ end
267
+ end while false
268
+ }
269
+ ws.onclose {
270
+ begin
271
+ break unless @server_started
272
+ deleted = nil
273
+ @connected_users.values.each do |u|
274
+ if u.connection == ws
275
+ name = u.username
276
+ deleted = @connected_users.delete(name)
277
+ response = create_response_json("user_disconnected", deleted.username, "The user is diconnected.")
278
+ yield response if block_given?
279
+ broadcast_message(response)
280
+ break unless deleted.nil?
281
+ end
282
+ end
283
+ end while false
284
+ }
285
+ ws.onerror { |e|
286
+
287
+ unless e.kind_of?(EM::WebSocket::WebSocketError)
288
+ ws.close
289
+ end
290
+
291
+ }
292
+ end
293
+ end
294
+ end
295
+
296
+ # This method is used privately. It will check if the passwed username belongs to an admin and that the password matches.
297
+ def admin_credentials_invalid?(username = "", password = "")
298
+ @admins.each do |admin|
299
+ return true if admin.username == username && admin.password != password
300
+ end
301
+
302
+ false
303
+ end
304
+
305
+ # This method is used privately to check if the username and password are valid. The username has to be at least 3 alphanumeric characters and the password 6 alphanumeric characters.
306
+ # It returns true if the credentials are valid, otherwise false.
307
+ def user_credentials_valid?(username = "", password = "")
308
+ return false if /\A\p{Alnum}{3,}\Z/.match(username).nil?
309
+ return false if /\A\p{Alnum}{6,}\Z/.match(password).nil?
310
+
311
+ true
312
+ end
313
+
314
+ # Returns true if the port number is valid.
315
+ def valid_port?(value)
316
+ return false if /\A[0-9]{1,6}\Z/.match(value).nil?
317
+ true
318
+ end
319
+
320
+ # Returns true if the user is banned.
321
+ def user_banned?(user)
322
+ if user.class == ChatUser
323
+ return @banned_usernames.include?(user.username)
324
+ end
325
+
326
+ @banned_usernames.include?(user)
327
+ end
328
+
329
+ # Returns true if the username is already added to the connected users.
330
+ def user_exists?(user)
331
+ @connected_users.include?(user.username)
332
+ end
333
+
334
+ # Returns true is the user is admin.
335
+ def user_admin?(user)
336
+ @admins.include?(user)
337
+ end
338
+
339
+ # Sends text/json to the connected clients.
340
+ def broadcast_message(message)
341
+ return false if @connected_users.count == 0
342
+
343
+ @connected_users.values.each do |u|
344
+ begin
345
+ u.connection.send(message) unless u.connection.nil?
346
+ rescue
347
+ next
348
+ end
349
+ end
350
+ end
351
+
352
+ # Returns the ChatUser object bound to the socket connection.
353
+ def get_user_by_connection(connection)
354
+ return nil if @connected_users.count == 0
355
+ @connected_users.values.each do |user|
356
+ return user if user.connection == connection
357
+ end
358
+
359
+ nil
360
+ end
361
+
362
+ # Returns the ChatUser object by the username.
363
+ def get_user_by_username(username)
364
+ @connected_users[username]
365
+ end
366
+
367
+ # Creates the json object to be sent to the clients.
368
+ def create_response_json(command = "", data = nil, message = "")
369
+ {"command" => command, "data" => data, "information" => message}.to_json
370
+ end
371
+
372
+ def connected_users_to_array()
373
+ usernames = []
374
+ @connected_users.values.each do |u|
375
+ usernames << u.username
376
+ end
377
+
378
+ usernames
379
+ end
380
+
381
+ end
382
+
383
+
384
+ class ChatUser
385
+ attr_accessor :username, :password, :connection, :ip
386
+ def initialize(args = {})
387
+ raise RuntimeError, "The username is not given" unless args.has_key?(:username)
388
+ raise RuntimeError, "The password is not given" unless args.has_key?(:password)
389
+
390
+ @connection = nil
391
+ @connection = args[:connection] if args.has_key?(:connection)
392
+ @ip = args[:ip] if args.has_key?(:ip)
393
+ @username = args[:username]
394
+ @password = args[:password]
395
+ end
396
+
397
+ def admin?()
398
+ @is_admin
399
+ end
400
+
401
+ def ==(object)
402
+ return false unless object.class == ChatUser
403
+ return username == object.username
404
+ end
405
+
406
+ def to_json(options = {})
407
+ {"user" => @username}.to_json
408
+ end
409
+
410
+ end
411
+
412
+ end
413
+
414
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'web_socket_chat_server/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "web_socket_chat_server"
8
+ spec.version = WebSocketChatServer::VERSION
9
+ spec.authors = ["Jone Samra"]
10
+ spec.email = ["jonemob@gmail.com"]
11
+ spec.summary = %q{A wrapper class for em-websocket (https://github.com/igrigorik/em-websocket) implementing a custom chat server protocol.}
12
+ spec.description = %q{For more details, please read the documentation at http://abulewis.com/doc/WebSocketChatServer.html}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+ spec.required_ruby_version = ">= 2.0.0"
21
+
22
+ spec.add_runtime_dependency "em-websocket", "~> 0.5.1"
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: web_socket_chat_server
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jone Samra
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: em-websocket
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ description: For more details, please read the documentation at http://abulewis.com/doc/WebSocketChatServer.html
56
+ email:
57
+ - jonemob@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/web_socket_chat_server.rb
68
+ - lib/web_socket_chat_server/version.rb
69
+ - web_socket_chat_server.gemspec
70
+ homepage: ''
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.4.5
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: A wrapper class for em-websocket (https://github.com/igrigorik/em-websocket)
94
+ implementing a custom chat server protocol.
95
+ test_files: []