em-websocket-server 0.1 → 0.1.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.
data/README.markdown CHANGED
@@ -0,0 +1,63 @@
1
+ #em-websocket-server
2
+
3
+ * em-websocket-server allows the creation of efficient, evented, websocket services
4
+
5
+ ##Installation
6
+
7
+ If you don't have gemcutter
8
+
9
+ gem install gemcutter
10
+ gem tumble
11
+
12
+ Otherwise
13
+
14
+ gem install em-websocket-server
15
+
16
+ Or
17
+
18
+ gem install em-websocket-server -s http://gemcutter.org
19
+
20
+ ##Dependencies
21
+ - eventmachine http://github.com/eventmachine/eventmachine
22
+
23
+ ##Docs
24
+
25
+ Not yet... coming soon
26
+
27
+ ##Quick Example
28
+
29
+ require 'rubygems'
30
+ require 'em-websocket-server'
31
+ require 'json'
32
+
33
+ #create a channel for pub sub
34
+ $chatroom = EM::Channel.new
35
+
36
+ class ChatServer < WebSocketServer
37
+
38
+ #subscribe to the channel on client connect
39
+ def on_connect
40
+ @sid = $chatroom.subscribe do |msg|
41
+ send_message msg
42
+ end
43
+ end
44
+
45
+ #unsubscribe on client disconnect
46
+ def on_disconnect
47
+ $chatroom.unsubscribe(@sid)
48
+ end
49
+
50
+ #publish the message to the channel on
51
+ #client message received
52
+ def on_receive msg
53
+ $chatroom.push msg
54
+ end
55
+
56
+ end
57
+
58
+ #start the event machine on port 8000 and have
59
+ #it instantiate ChatServer objects for each
60
+ #client connection
61
+ EM.run do
62
+ EM.start_server "0.0.0.0", 8000, ChatServer
63
+ end
@@ -1,6 +1,6 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'em-websocket-server'
3
- s.version = '0.1'
3
+ s.version = '0.1.1'
4
4
  s.date = '2009-12-14'
5
5
  s.summary = 'An evented ruby websocket server built on top of EventMachine'
6
6
  s.email = "dan.simpson@gmail.com"
@@ -18,6 +18,11 @@ spec = Gem::Specification.new do |s|
18
18
  "em-websocket-server.gemspec",
19
19
  "examples/chat.rb",
20
20
  "examples/chat.html",
21
+ "examples/timesync.html",
22
+ "examples/timesync.rb",
23
+ "examples/tictactoe/tictactoe.html",
24
+ "examples/tictactoe/tictactoe.js",
25
+ "examples/tictactoe/tictactoe.rb",
21
26
  "lib/em-websocket-server.rb"
22
27
  ]
23
28
  end
data/examples/chat.html CHANGED
@@ -1,38 +1,40 @@
1
1
  <!doctype html>
2
2
  <html>
3
- <head>
4
- <title>node.websocket.js test</title>
3
+ <head>
4
+ <title>em-websocket-server test</title>
5
+ </head>
6
+ <body>
5
7
 
6
- <style type="text/css" media="screen">
7
- #time { background: #ccc; }
8
- </style>
9
- </head>
10
- <body>
11
8
 
12
- <h1>websocketed demo</h1>
9
+ <h1>em-websocket-server chat demo</h1>
10
+ <div id="chat">connecting....</div>
13
11
 
14
- <h2>The time is <span id="time">opening socket</span></h2>
15
- <p>The information for the time is provided by the server. No Date() or intervals here!</p>
16
12
 
17
- <script>
18
- var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
19
13
 
20
- webSocket.onopen = function(event){
21
- document.getElementById('time').innerHTML = 'waiting for socket';
22
- webSocket.send('start');
23
- };
14
+ <script>
24
15
 
25
- webSocket.onmessage = function(event){
26
- console.log(event);
27
- var object = JSON.parse(event.data);
28
- document.getElementById('time').innerHTML = object.time;
29
- };
16
+ var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
30
17
 
31
- webSocket.onclose = function(event){
32
- document.getElementById('time').innerHTML = 'socket closed';
33
- };
18
+ webSocket.onopen = function(event){
19
+ document.getElementById('chat').innerHTML = 'connected';
20
+ };
34
21
 
35
- </script>
22
+ webSocket.onmessage = function(event){
23
+ var object = JSON.parse(event.data);
24
+ document.getElementById('chat').innerHTML += "<b>" + object.user + "</b>" + object.message;
25
+ };
36
26
 
37
- </body>
27
+ webSocket.onclose = function(event){
28
+ document.getElementById('chat').innerHTML = 'socket closed';
29
+ };
30
+
31
+ document.getElementById('chatty').addEventListener("onkeypress", function() {
32
+ alert("X")
33
+ });
34
+
35
+ </script>
36
+
37
+ <input type="text" onkeypress="webSocket.send('hi');" />
38
+
39
+ </body>
38
40
  </html>
data/examples/chat.rb CHANGED
@@ -1,24 +1,25 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
1
3
  require 'rubygems'
4
+ require 'em-websocket-server'
2
5
  require 'json'
3
6
 
4
- class TimeServer < WebSocketServer
7
+ $chatroom = EM::Channel.new
8
+
9
+ class ChatServer < WebSocketServer
5
10
 
6
11
  def on_connect
7
- @timer = EM.add_periodic_timer(5) do
8
- sync_time
12
+ @sid = $chatroom.subscribe do |msg|
13
+ send_message msg
9
14
  end
10
15
  end
11
16
 
12
17
  def on_disconnect
13
- @timer
18
+ $chatroom.unsubscribe(@sid)
14
19
  end
15
20
 
16
21
  def on_receive msg
17
- puts "msg rcv"
18
- end
19
-
20
- def sync_time
21
- send_message({ :time => Time.now }.to_json)
22
+ $chatroom.push msg
22
23
  end
23
24
 
24
25
  end
@@ -28,5 +29,5 @@ EM.epoll
28
29
  EM.set_descriptor_table_size(10240)
29
30
 
30
31
  EM.run do
31
- EM.start_server "0.0.0.0", 8000, TimeServer
32
+ EM.start_server "0.0.0.0", 8000, ChatServer
32
33
  end
@@ -0,0 +1,51 @@
1
+ <!doctype html>
2
+
3
+ <html>
4
+ <head>
5
+ <title>Tic Tac Toe</title>
6
+ <meta name="author" content="Jordan Fowler (TheBreeze)">
7
+
8
+ <style type="text/css" media="screen">
9
+ #navigation ul {
10
+ list-style-type: none;
11
+ padding: 0;
12
+ margin: 25px 0px;
13
+ }
14
+
15
+ #navigation ul li {
16
+ display: inline;
17
+ padding: 0;
18
+ margin: 0;
19
+ margin-right: 10px;
20
+ }
21
+
22
+ #game-board .cell {
23
+ width: 100px;
24
+ height: 100px;
25
+ padding: 5px;
26
+ background: #000;
27
+ color: #FFF;
28
+ font-size: 100px;
29
+ text-align: center;
30
+ vertical-align: middle;
31
+ }
32
+ </style>
33
+ </head>
34
+ <body>
35
+
36
+ <h1>Tic Tac Toe</h1>
37
+
38
+ <div id="user-count"></div>
39
+ <div id="game-stats"></div>
40
+ <div id="status"></div>
41
+
42
+ <table id="game-board">
43
+ <tr><td id="cell-0-0" class="cell"></td><td id="cell-1-0" class="cell"></td><td id="cell-2-0" class="cell"></td></tr>
44
+ <tr><td id="cell-0-1" class="cell"></td><td id="cell-1-1" class="cell"></td><td id="cell-2-1" class="cell"></td></tr>
45
+ <tr><td id="cell-0-2" class="cell"></td><td id="cell-1-2" class="cell"></td><td id="cell-2-2" class="cell"></td></tr>
46
+ </table>
47
+
48
+ <script type="text/javascript" charset="utf-8" src="mootools-1.2.4-core-nc.js"></script>
49
+ <script type="text/javascript" charset="utf-8" src="tictactoe.js"></script>
50
+ </body>
51
+ </html>
@@ -0,0 +1,130 @@
1
+ // Author: Jordan Fowler (TheBreeze)
2
+
3
+ var TicTacToe = new Class({
4
+ wins: 0,
5
+ loses: 0,
6
+ draws: 0,
7
+ methodMap: {
8
+ 'queued': 'onQueued',
9
+ 'start': 'onStart',
10
+ 'turn': 'onTurn',
11
+ 'move': 'onMove',
12
+ 'game_over': 'onGameOver',
13
+ 'win': 'onWin',
14
+ 'loss': 'onLoss',
15
+ 'draw': 'onDraw',
16
+ 'user_count': 'onUserCount'
17
+ },
18
+
19
+ initialize: function() {
20
+ if (TicTacToe.connection == null) {
21
+ TicTacToe.connection = new WebSocket('ws://192.168.0.2:8000/tictactoe');
22
+ };
23
+
24
+ TicTacToe.connection.onopen = this.join.bind(this);
25
+ TicTacToe.connection.onmessage = this.onMessage.bind(this);
26
+ TicTacToe.connection.onclose = this.onGameOver.bind(this);
27
+
28
+ this.setGameStats();
29
+ },
30
+
31
+ onMessage: function(event) {
32
+ var command = JSON.decode(event.data);
33
+
34
+ console.log('[RCV] ' + command.msg);
35
+
36
+ this[this.methodMap[command.msg]].call(this, command);
37
+ },
38
+
39
+ message: function(msg, options) {
40
+ var command = JSON.encode({msg: msg, data: options});
41
+
42
+ console.log('[SENT] ' + msg);
43
+
44
+ TicTacToe.connection.send(command);
45
+ },
46
+
47
+ setStatus: function(status) {
48
+ $('status').set('text', status);
49
+ },
50
+
51
+ setGameStats: function() {
52
+ $('game-stats').set('text', 'Wins: '+this.wins+' / Losses: '+this.wins+' / Draws: '+this.draws);
53
+ },
54
+
55
+ setUserCount: function(userCount) {
56
+ $('user-count').set('text', 'Number of players: ' + userCount);
57
+ },
58
+
59
+ join: function(event) {
60
+ this.message('join');
61
+
62
+ this.setStatus('Connecting you to a game...');
63
+ },
64
+
65
+ reset: function() {
66
+ $$('.cell').set('text', '');
67
+
68
+ this.join();
69
+ },
70
+
71
+ onQueued: function(command) {
72
+ this.setStatus('Waiting for another player...');
73
+ },
74
+
75
+ onStart: function(command) {
76
+ this.setStatus('Game found! Their turn first...');
77
+ },
78
+
79
+ onTurn: function(command) {
80
+ this.setStatus('Your turn...');
81
+ },
82
+
83
+ onMove: function(command) {
84
+ $('cell-'+command.data.x+'-'+command.data.y).set('text', command.key);
85
+
86
+ this.setStatus('Their turn...');
87
+ },
88
+
89
+ move: function(x, y) {
90
+ this.message('move', {x: x, y: y});
91
+ },
92
+
93
+ onGameOver: function(command) {
94
+ this.setStatus('Game over.');
95
+ this.setGameStats();
96
+ this.reset();
97
+ },
98
+
99
+ onWin: function(command) {
100
+ this.wins += 1;
101
+ this.setStatus('Game over. You win!');
102
+ this.setGameStats();
103
+ },
104
+
105
+ onLoss: function(command) {
106
+ this.losses += 1;
107
+ this.setStatus('Game over. You lose!');
108
+ this.setGameStats();
109
+ },
110
+
111
+ onDraw: function(command) {
112
+ this.draws += 1;
113
+ this.setStatus('Game over. It was a draw!');
114
+ this.setGameStats();
115
+ },
116
+
117
+ onUserCount: function(command) {
118
+ this.setUserCount(command.data);
119
+ }
120
+ });
121
+
122
+ $$('.cell').addEvent('click', function(event) {
123
+ try {
124
+ currentGame.move($(this).get('id').split('-')[1], $(this).get('id').split('-')[2]);
125
+ } catch(error) {
126
+ alert('Please wait while we connect you to a game...');
127
+ }
128
+ });
129
+
130
+ currentGame = new TicTacToe();
@@ -0,0 +1,215 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../../lib'
2
+
3
+ require 'rubygems'
4
+ require 'em-websocket-server'
5
+ require 'json'
6
+ require 'uuid'
7
+ require 'pp'
8
+
9
+ $games = {}
10
+ $waiting = nil
11
+ $status = nil
12
+
13
+ class StatusChannel < EM::Channel
14
+
15
+ def initialize
16
+ super
17
+ @count = 0
18
+ end
19
+
20
+ def increment
21
+ @count += 1
22
+ push @count
23
+ end
24
+
25
+ def decrement
26
+ @count -= 1
27
+ push @count
28
+ end
29
+ end
30
+
31
+ class Game < EM::Channel
32
+
33
+ attr_accessor :id, :player1, :player2, :current, :matrix
34
+
35
+
36
+
37
+ def initialize player1, player2
38
+ super()
39
+ @id = UUID.new
40
+ @player1 = player1
41
+ @player2 = player2
42
+ @matrix = (0..2).collect { [false, false, false] }
43
+ end
44
+
45
+ def set_turn p
46
+ @current = p
47
+ @current.turn!
48
+ end
49
+
50
+ def start!
51
+ @current = nil
52
+ @player1.start!
53
+ @player2.start!
54
+ toggle
55
+ end
56
+
57
+ def move p, data
58
+ if @current == p
59
+ unless @matrix[data["x"].to_i][data["y"].to_i]
60
+ @matrix[data["x"].to_i][data["y"].to_i] = p.key
61
+
62
+ @player1.send_move(p.key, data)
63
+ @player2.send_move(p.key, data)
64
+
65
+
66
+ winner = has_winner?
67
+ full = full?
68
+
69
+ if winner || full
70
+ @player1.send_command("game_over")
71
+ @player2.send_command("game_over")
72
+ if winner
73
+ p.send_command("win")
74
+ opponent(p).send_command("loss")
75
+ else full?
76
+ @player1.send_command("draw")
77
+ @player2.send_command("draw")
78
+ end
79
+ else
80
+ toggle
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def full?
87
+ @matrix.each do |row|
88
+ row.each do |col|
89
+ return false unless col
90
+ end
91
+ end
92
+ return true
93
+ end
94
+
95
+ def has_winner?
96
+ return true if @matrix[1][1] && (@matrix[0][0] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][2])
97
+ return true if @matrix[1][1] && (@matrix[0][2] == @matrix[1][1]) && (@matrix[1][1] == @matrix[2][0])
98
+ @matrix.each do |row|
99
+ return true if row[1] && (row[0] == row[1]) && (row[1] == row[2])
100
+ end
101
+ return false
102
+ end
103
+
104
+ def opponent p
105
+ @player1 == p ? @player2 : @player1
106
+ end
107
+
108
+ def toggle
109
+ set_turn(@current == @player1 ? @player2 : @player1)
110
+ end
111
+ end
112
+
113
+ class TickTackToeServer < WebSocketServer
114
+
115
+ attr_accessor :game_id, :key, :status_key
116
+
117
+ def on_connect
118
+ @status_key = $status.subscribe do |c|
119
+ send_user_count c
120
+ end
121
+ $status.increment
122
+ end
123
+
124
+ def on_disconnect
125
+ $status.unsubscribe @status_key
126
+ $status.decrement
127
+ delete_game!
128
+ end
129
+
130
+ def on_receive data
131
+
132
+ begin
133
+ msg = JSON.parse(data)
134
+ rescue
135
+ send_command "error"
136
+ return
137
+ end
138
+
139
+ case msg["msg"]
140
+ when "join"
141
+ if $waiting.empty?
142
+ $waiting.push(self)
143
+ send_command "queued"
144
+ else
145
+ $waiting.pop do |opponent|
146
+ game = Game.new(self, opponent)
147
+ self.key = "X"
148
+ opponent.key = "O"
149
+ self.game_id = opponent.game_id = game.id
150
+ game.start!
151
+ $games[game_id] = game
152
+ end
153
+ end
154
+ when "move"
155
+ if game
156
+ game.move self, msg["data"]
157
+
158
+ else
159
+ log "Cannot move on a nil game!"
160
+ end
161
+ end
162
+ end
163
+
164
+ def delete_game!
165
+ if $games.key?(game_id)
166
+ $games.delete(game_id)
167
+ end
168
+ end
169
+
170
+ def game
171
+ $games[game_id]
172
+ end
173
+
174
+ def turn!
175
+ send_command "turn"
176
+ end
177
+
178
+ def game_over!
179
+ delete_game!
180
+ send_command "game_over"
181
+ end
182
+
183
+ def start!
184
+ send_command "start"
185
+ end
186
+
187
+ def send_move key, data
188
+ send_message({:msg => "move", :key => key, :data => data}.to_json)
189
+ end
190
+
191
+ def send_command cmd
192
+ send_message({:msg => cmd}.to_json)
193
+ end
194
+
195
+ def send_user_count count
196
+ send_message({:msg => :user_count, :data => count}.to_json)
197
+ end
198
+
199
+
200
+ def send_message msg
201
+ super msg
202
+ puts "Sent: #{msg}"
203
+ end
204
+ end
205
+
206
+
207
+ EM.epoll
208
+ EM.set_descriptor_table_size(10240)
209
+
210
+ EM.run do
211
+ $waiting = EM::Queue.new
212
+ $status = StatusChannel.new
213
+
214
+ EM.start_server "0.0.0.0", 8000, TickTackToeServer
215
+ end
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>em-websocket-server test</title>
5
+ </head>
6
+ <body>
7
+
8
+ <h1>em-websocket-server timesync demo</h1>
9
+ <h2 id="time">opening socket</h2>
10
+
11
+ <script>
12
+ var webSocket = new WebSocket('ws://192.168.0.2:8000/time');
13
+
14
+ webSocket.onopen = function(event){
15
+ document.getElementById('time').innerHTML = 'waiting for socket';
16
+ };
17
+
18
+ webSocket.onmessage = function(event){
19
+ var object = JSON.parse(event.data);
20
+ document.getElementById('time').innerHTML = object.time;
21
+ };
22
+
23
+ webSocket.onclose = function(event){
24
+ document.getElementById('time').innerHTML = 'socket closed';
25
+ };
26
+ </script>
27
+
28
+ </body>
29
+ </html>
@@ -0,0 +1,35 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'rubygems'
4
+ require 'em-websocket-server'
5
+ require 'json'
6
+
7
+ class TimeServer < WebSocketServer
8
+
9
+ def on_connect
10
+ @timer = EM.add_periodic_timer(5) do
11
+ sync_time
12
+ end
13
+ end
14
+
15
+ def on_disconnect
16
+ @timer
17
+ end
18
+
19
+ def on_receive msg
20
+ puts "msg rcv"
21
+ end
22
+
23
+ def sync_time
24
+ send_message({ :time => Time.now }.to_json)
25
+ end
26
+
27
+ end
28
+
29
+
30
+ EM.epoll
31
+ EM.set_descriptor_table_size(10240)
32
+
33
+ EM.run do
34
+ EM.start_server "0.0.0.0", 8000, TimeServer
35
+ end
@@ -20,18 +20,16 @@ class WebSocketServer < EM::Connection
20
20
  end
21
21
 
22
22
  def valid_origin?
23
- true
24
- #@@accepted_origins.include?(@headers["Origin"])
23
+ @@accepted_origins.empty? || @@accepted_origins.include?(origin)
25
24
  end
26
25
 
26
+ #not doing anything with this yet
27
27
  def valid_path?
28
28
  true
29
- #@@callbacks.key?(@path)
30
29
  end
31
30
 
32
31
  def valid_upgrade?
33
- true
34
- #@headers["Upgrade"] =~ /websocket/i
32
+ @headers["Upgrade"] =~ /websocket/i
35
33
  end
36
34
 
37
35
  def origin
@@ -99,13 +97,7 @@ class WebSocketServer < EM::Connection
99
97
  unless @connected
100
98
  handshake data
101
99
  else
102
- msg = data.gsub(/^(\x00)|(\xff)$/, "")
103
-
104
- if msg == "start"
105
- on_connect
106
- else
107
- on_receive msg
108
- end
100
+ on_receive data.gsub(/^(\x00)|(\xff)$/, "")
109
101
  end
110
102
  end
111
103
 
@@ -132,6 +124,8 @@ class WebSocketServer < EM::Connection
132
124
  send_headers
133
125
 
134
126
  @connected = true
127
+
128
+ on_connect
135
129
  end
136
130
 
137
131
  # send the handshake response headers to
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-websocket-server
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.1"
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Simpson
@@ -35,6 +35,11 @@ files:
35
35
  - em-websocket-server.gemspec
36
36
  - examples/chat.rb
37
37
  - examples/chat.html
38
+ - examples/timesync.html
39
+ - examples/timesync.rb
40
+ - examples/tictactoe/tictactoe.html
41
+ - examples/tictactoe/tictactoe.js
42
+ - examples/tictactoe/tictactoe.rb
38
43
  - lib/em-websocket-server.rb
39
44
  has_rdoc: true
40
45
  homepage: http://github.com/dansimpson/em-websocket-server