startback-websocket 0.14.0 → 0.14.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -2
- data/README.md +64 -9
- data/dist/client.js +1 -0
- data/lib/startback/ext/context.rb +6 -0
- data/lib/startback/ext.rb +1 -2
- data/lib/startback/websocket/app.rb +82 -0
- data/lib/startback/websocket/hub/app.rb +28 -0
- data/lib/startback/websocket/hub/builder.rb +55 -0
- data/lib/startback/websocket/hub/errors.rb +9 -0
- data/lib/startback/websocket/hub/message.rb +29 -0
- data/lib/startback/websocket/hub/middleware/command_handler.rb +34 -0
- data/lib/startback/websocket/hub/middleware/room_handler.rb +30 -0
- data/lib/startback/websocket/hub/middleware.rb +12 -0
- data/lib/startback/websocket/hub/participant.rb +16 -0
- data/lib/startback/websocket/hub/room.rb +46 -0
- data/lib/startback/websocket/hub.rb +15 -0
- data/lib/startback/websocket.rb +8 -0
- data/spec/spec_helper.rb +21 -32
- data/spec/unit/hub/test_builder.rb +141 -0
- data/spec/unit/hub/test_room.rb +27 -0
- data/spec/unit/test_app.rb +35 -0
- data/tasks/test.rake +0 -1
- metadata +21 -91
- data/lib/startback/audit/prometheus.rb +0 -87
- data/lib/startback/audit/shared.rb +0 -17
- data/lib/startback/audit/trailer.rb +0 -129
- data/lib/startback/audit.rb +0 -3
- data/lib/startback/caching/entity_cache.rb +0 -157
- data/lib/startback/caching/no_store.rb +0 -28
- data/lib/startback/caching/store.rb +0 -34
- data/lib/startback/context/h_factory.rb +0 -43
- data/lib/startback/context/middleware.rb +0 -53
- data/lib/startback/context.rb +0 -122
- data/lib/startback/errors.rb +0 -197
- data/lib/startback/event/agent.rb +0 -84
- data/lib/startback/event/bus/bunny/async.rb +0 -162
- data/lib/startback/event/bus/bunny.rb +0 -1
- data/lib/startback/event/bus/memory/async.rb +0 -45
- data/lib/startback/event/bus/memory/sync.rb +0 -35
- data/lib/startback/event/bus/memory.rb +0 -2
- data/lib/startback/event/bus.rb +0 -100
- data/lib/startback/event/engine.rb +0 -94
- data/lib/startback/event/ext/context.rb +0 -5
- data/lib/startback/event/ext/operation.rb +0 -13
- data/lib/startback/event.rb +0 -47
- data/lib/startback/ext/date_time.rb +0 -9
- data/lib/startback/ext/time.rb +0 -9
- data/lib/startback/model.rb +0 -6
- data/lib/startback/operation/error_operation.rb +0 -19
- data/lib/startback/operation/multi_operation.rb +0 -28
- data/lib/startback/operation.rb +0 -78
- data/lib/startback/services.rb +0 -11
- data/lib/startback/support/data_object.rb +0 -71
- data/lib/startback/support/env.rb +0 -41
- data/lib/startback/support/fake_logger.rb +0 -18
- data/lib/startback/support/hooks.rb +0 -48
- data/lib/startback/support/log_formatter.rb +0 -34
- data/lib/startback/support/logger.rb +0 -34
- data/lib/startback/support/operation_runner.rb +0 -150
- data/lib/startback/support/robustness.rb +0 -157
- data/lib/startback/support/transaction_manager.rb +0 -25
- data/lib/startback/support/transaction_policy.rb +0 -33
- data/lib/startback/support/world.rb +0 -54
- data/lib/startback/support.rb +0 -26
- data/lib/startback/version.rb +0 -8
- data/lib/startback/web/api.rb +0 -99
- data/lib/startback/web/auto_caching.rb +0 -85
- data/lib/startback/web/catch_all.rb +0 -52
- data/lib/startback/web/cors_headers.rb +0 -80
- data/lib/startback/web/health_check.rb +0 -49
- data/lib/startback/web/magic_assets/ng_html_transformer.rb +0 -80
- data/lib/startback/web/magic_assets/rake_tasks.rb +0 -64
- data/lib/startback/web/magic_assets.rb +0 -98
- data/lib/startback/web/middleware.rb +0 -13
- data/lib/startback/web/prometheus.rb +0 -16
- data/lib/startback/web/shield.rb +0 -58
- data/lib/startback.rb +0 -43
- data/spec/unit/audit/test_prometheus.rb +0 -72
- data/spec/unit/audit/test_trailer.rb +0 -105
- data/spec/unit/caching/test_entity_cache.rb +0 -136
- data/spec/unit/context/test_abstraction_factory.rb +0 -64
- data/spec/unit/context/test_dup.rb +0 -42
- data/spec/unit/context/test_fork.rb +0 -37
- data/spec/unit/context/test_h_factory.rb +0 -31
- data/spec/unit/context/test_middleware.rb +0 -45
- data/spec/unit/context/test_with_world.rb +0 -20
- data/spec/unit/context/test_world.rb +0 -17
- data/spec/unit/event/bus/memory/test_async.rb +0 -43
- data/spec/unit/event/bus/memory/test_sync.rb +0 -43
- data/spec/unit/support/hooks/test_after_hook.rb +0 -54
- data/spec/unit/support/hooks/test_before_hook.rb +0 -54
- data/spec/unit/support/operation_runner/test_around_run.rb +0 -156
- data/spec/unit/support/operation_runner/test_before_after_call.rb +0 -48
- data/spec/unit/support/test_data_object.rb +0 -156
- data/spec/unit/support/test_env.rb +0 -75
- data/spec/unit/support/test_robusteness.rb +0 -229
- data/spec/unit/support/test_transaction_manager.rb +0 -64
- data/spec/unit/support/test_world.rb +0 -72
- data/spec/unit/test_event.rb +0 -62
- data/spec/unit/test_operation.rb +0 -55
- data/spec/unit/test_support.rb +0 -40
- data/spec/unit/web/fixtures/assets/app/hello.es6 +0 -4
- data/spec/unit/web/fixtures/assets/app/hello.html +0 -1
- data/spec/unit/web/fixtures/assets/index.es6 +0 -1
- data/spec/unit/web/test_api.rb +0 -82
- data/spec/unit/web/test_auto_caching.rb +0 -81
- data/spec/unit/web/test_catch_all.rb +0 -77
- data/spec/unit/web/test_cors_headers.rb +0 -88
- data/spec/unit/web/test_healthcheck.rb +0 -59
- data/spec/unit/web/test_magic_assets.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2909a07d46bc34a032aa02e2f44bcce0bf3404967eb99ef9e60b92d6c547febb
|
4
|
+
data.tar.gz: e9df9bef24c2985f95b1214d3e2df7acbada61e3f2b84346e4b6562dcf4fae0b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00dc98c0337281af6b7d18a148553428983f7494cc709c120259901e9e58ea8e38b02796f26edd5c520de288dd586e0086aa2e7e205136e254297bef83048b62
|
7
|
+
data.tar.gz: a545063482457190d47d13daf336af65b5275a87a708dea7e5b5b7b7ede6dc8e344930f4de222a9293f46cc1ed5e1fb55897f02efb29e6cf2cff5a279e006326
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,13 +1,68 @@
|
|
1
|
-
|
1
|
+
## Hub
|
2
2
|
|
3
|
-
|
3
|
+
The `Hub:App` provides an opinionated protocol that eases
|
4
|
+
the handling of real-time, websocket-based, applications.
|
4
5
|
|
5
|
-
|
6
|
-
2. the operations layer, in charge of the high-level software operations
|
7
|
-
3. the database layer, abstracted using the Relations As First Class Citizen pattern
|
6
|
+
### Protocol
|
8
7
|
|
9
|
-
|
8
|
+
The protocol is mainly based on JSON-serialized messages containing both a `headers` map and a `body` property.
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
#### Rooms
|
11
|
+
|
12
|
+
Rooms provide an abstraction allowing messages to be broadcasted to a set of `participants`.
|
13
|
+
|
14
|
+
Messages are sent/received to/from rooms by providing the special `'room' header`, eg:
|
15
|
+
|
16
|
+
```json
|
17
|
+
{
|
18
|
+
"header": {
|
19
|
+
"room": "a-room-name"
|
20
|
+
},
|
21
|
+
"body": {} || "" // can be anything
|
22
|
+
}
|
23
|
+
```
|
24
|
+
|
25
|
+
#### Commands
|
26
|
+
|
27
|
+
Commands provide an abstraction allowing the execution of remote processing that can provide a result.
|
28
|
+
|
29
|
+
Commands are executed by providing the special `'command‘ header` and an additional `'reply-to' header`, eg:
|
30
|
+
|
31
|
+
```json
|
32
|
+
{
|
33
|
+
"header": {
|
34
|
+
"command": "a-command",
|
35
|
+
"reply-to": "cc3ce43a-a494-4c38-bb01-654223ee24a5" // uuid v4
|
36
|
+
},
|
37
|
+
"body": {} || "" // can be anything
|
38
|
+
}
|
39
|
+
```
|
40
|
+
|
41
|
+
The protocol contract between the Hub-servers and Hub-clients is that the result of a command execution can be sent back to the client by sending a message re-using the uuid provided in the `reply-to header` in the following way:
|
42
|
+
|
43
|
+
```json
|
44
|
+
{
|
45
|
+
"header": {
|
46
|
+
// same as the reply-to value of the original message
|
47
|
+
"in-reply-to": "cc3ce43a-a494-4c38-bb01-654223ee24a5"
|
48
|
+
},
|
49
|
+
"body": {} || "" // the result of the command run
|
50
|
+
}
|
51
|
+
```
|
52
|
+
|
53
|
+
#### Combination
|
54
|
+
|
55
|
+
The several aspects of the protocol can be combined.
|
56
|
+
|
57
|
+
For instance a command can be executed in a specific room:
|
58
|
+
|
59
|
+
```json
|
60
|
+
{
|
61
|
+
"header": {
|
62
|
+
"command": "a-command",
|
63
|
+
"room": "a-certain-room",
|
64
|
+
"reply-to": "323b7293-91f4-49ab-b374-006087b56e07"
|
65
|
+
},
|
66
|
+
"body": {} || "" // can be anything
|
67
|
+
}
|
68
|
+
```
|
data/dist/client.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
var StartbackWebsocket;(()=>{"use strict";var e,r={d:(e,s)=>{for(var t in s)r.o(s,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:s[t]})},o:(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},s={};r.r(s),r.d(s,{Client:()=>t,HubClient:()=>l});class t extends class{#e;constructor(){this.#e={}}on(e,r){this.#e[e]||=[],this.#e[e].push(r)}emit(e,r){this.#e[e]||=[],this.#e[e].forEach((s=>{if(s)try{s(r)}catch(r){console.error("Error while calling event handler for",e),console.error(r)}}))}}{#r;#s;constructor(e){super(),this.#r=e}send(e){this.#s.send(e)}connect(){if(this.#s)throw new Error("Already connected");this.#s=new WebSocket(this.#r),["close","error","message","open"].forEach((e=>{this.#s.addEventListener(e,(r=>{this.emit(e,r)}))}))}}class o{#t;#o;#n;constructor(e,r){this.#t=e,this.#o=r,this.#n={}}send(e,r={}){this.#o.send(e,{...r,room:this.#t})}on(e,r){if(["message"].indexOf(e)<0)throw new Error("You can only subscribe to the 'message' event on rooms");this.#n[e]||=[],this.#n[e].push(r)}execute(e,r,s={}){return this.#o.execute(e,r,{...s,room:this.#t})}process(e){this.#n.message||=[],this.#n.message.forEach((r=>{r(e)}))}}var n=new Uint8Array(16);function i(){if(!e&&!(e="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return e(n)}const a=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,c=function(e){return"string"==typeof e&&a.test(e)};for(var d=[],h=0;h<256;++h)d.push((h+256).toString(16).substr(1));const u=function(e,r,s){var t=(e=e||{}).random||(e.rng||i)();if(t[6]=15&t[6]|64,t[8]=63&t[8]|128,r){s=s||0;for(var o=0;o<16;++o)r[s+o]=t[o];return r}return function(e){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,s=(d[e[r+0]]+d[e[r+1]]+d[e[r+2]]+d[e[r+3]]+"-"+d[e[r+4]]+d[e[r+5]]+"-"+d[e[r+6]]+d[e[r+7]]+"-"+d[e[r+8]]+d[e[r+9]]+"-"+d[e[r+10]]+d[e[r+11]]+d[e[r+12]]+d[e[r+13]]+d[e[r+14]]+d[e[r+15]]).toLowerCase();if(!c(s))throw TypeError("Stringified UUID is invalid");return s}(t)};class l extends t{#i;#a;constructor(e){super(e),this.#i={},this.#a={}}send(e,r={}){super.send(JSON.stringify({headers:r,body:e}))}connect(){super.connect(),this.on("message",(e=>{const r=JSON.parse(e.data);if(!r.headers)return;const s=r.headers["in-reply-to"];if(s){const e=this.#a[s];e&&(e.resolve(r),delete this.#a[s])}return r.headers.room?this.#i[r.headers.room].process(r):void 0}))}execute(e,r={},s={}){const t=u();return new Promise(((o,n)=>{this.#a[t]={resolve:o,reject:n},this.send(r,{...s,command:e,"reply-to":t})}))}room(e){return this.#i[e]||=new o(e,this)}}StartbackWebsocket=s})();
|
data/lib/startback/ext.rb
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
require_relative 'ext/
|
2
|
-
require_relative 'ext/time'
|
1
|
+
require_relative 'ext/context'
|
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
#
|
5
|
+
# Can be used to easily implement a custom websocket protocol inside a Startback
|
6
|
+
# application.
|
7
|
+
#
|
8
|
+
# Please note that this rack app is not 100% Rack compliant, since it raises
|
9
|
+
# any error that the block itself raises. This class aims at being backed up
|
10
|
+
# by a Shield and/or CatchAll middleware.
|
11
|
+
#
|
12
|
+
class App
|
13
|
+
|
14
|
+
JS_CLIENT = Path.dir/'../../../dist/client.js'
|
15
|
+
|
16
|
+
def initialize(context)
|
17
|
+
@context = context
|
18
|
+
@context.websocket_app = self
|
19
|
+
@connections = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
request = Rack::Request.new(env)
|
24
|
+
if Faye::WebSocket.websocket?(env)
|
25
|
+
ws = factor_and_keep(env)
|
26
|
+
ws.rack_response
|
27
|
+
elsif request.path_info === '/client.js'
|
28
|
+
[200, { 'Content-Type' => 'application/javascript' }, JS_CLIENT.readlines]
|
29
|
+
else
|
30
|
+
[400, { 'Content-Type' => 'text/plain' }, ['Websocket only!']]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def broadcast(data)
|
35
|
+
data = data.to_json unless data.is_a?(String)
|
36
|
+
|
37
|
+
@connections.each do |socket|
|
38
|
+
socket.send(data)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_open(event, ws, env)
|
43
|
+
end
|
44
|
+
|
45
|
+
def on_close(event, ws, env)
|
46
|
+
end
|
47
|
+
|
48
|
+
def on_error(event, ws, env)
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_message(event, ws, env)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def factor_and_keep(env)
|
57
|
+
ws = Faye::WebSocket.new(env)
|
58
|
+
|
59
|
+
ws.on :open do |event|
|
60
|
+
@connections << ws
|
61
|
+
on_open(event, ws, env)
|
62
|
+
end
|
63
|
+
|
64
|
+
ws.on :close do |event|
|
65
|
+
@connections.delete(ws)
|
66
|
+
on_close(event, ws, env)
|
67
|
+
end
|
68
|
+
|
69
|
+
ws.on :message do |event|
|
70
|
+
on_message(event, ws, env)
|
71
|
+
end
|
72
|
+
|
73
|
+
ws.on :error do |event|
|
74
|
+
on_error(event, ws, env)
|
75
|
+
end
|
76
|
+
|
77
|
+
ws
|
78
|
+
end
|
79
|
+
|
80
|
+
end # class App
|
81
|
+
end # module Websocket
|
82
|
+
end # module Startback
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
#
|
6
|
+
# The Hub is a very opinionated websocket protocol based on room and
|
7
|
+
# subscriptions, allowing users to subscribe to rooms etc etc... # TODO
|
8
|
+
#
|
9
|
+
class App < Websocket::App
|
10
|
+
|
11
|
+
def initialize(context, rooms, handler)
|
12
|
+
super(context)
|
13
|
+
@handler = handler
|
14
|
+
@rooms = rooms
|
15
|
+
end
|
16
|
+
|
17
|
+
def room(name)
|
18
|
+
@rooms[name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def on_message(event, ws, env)
|
22
|
+
@handler.call(Message.new(event.data, ws), ws, env)
|
23
|
+
end
|
24
|
+
|
25
|
+
end # class App
|
26
|
+
end # module Hub
|
27
|
+
end # module Websocket
|
28
|
+
end # module Startback
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
class Builder
|
6
|
+
|
7
|
+
def initialize(context, default_handler = nil, *bl_args, &bl)
|
8
|
+
@context = context
|
9
|
+
@default_handler = default_handler
|
10
|
+
@middlewares = []
|
11
|
+
@commands = {}
|
12
|
+
@rooms = {}
|
13
|
+
@bl_args
|
14
|
+
instance_exec *bl_args, &bl
|
15
|
+
end
|
16
|
+
|
17
|
+
def use(middleware, *opts)
|
18
|
+
@middlewares << proc { |app| middleware.new(app, *opts) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def command(name, &bl)
|
22
|
+
cname = name.to_sym
|
23
|
+
raise "Command #{name} already defined: #{name}" if @commands[cname]
|
24
|
+
@commands[cname] = proc { |app| Middleware::CommandHandler.new(app, { :name => name }, &bl) }
|
25
|
+
@middlewares << @commands[cname]
|
26
|
+
end
|
27
|
+
|
28
|
+
def room(name, &bl)
|
29
|
+
raise "Room names must be strings" unless name.is_a? String
|
30
|
+
raise "Room '#{name}' already defined" if @rooms[name]
|
31
|
+
|
32
|
+
@rooms[name] ||= Room.new(name)
|
33
|
+
handler = Builder.new(@context, nil, @rooms[name], &bl).to_handler
|
34
|
+
middleware = proc { |app| Middleware::RoomHandler.new(app, @rooms[name], handler) }
|
35
|
+
|
36
|
+
@middlewares << middleware
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_handler
|
40
|
+
default_handler = @default_handler || proc {}
|
41
|
+
@middlewares
|
42
|
+
.reverse
|
43
|
+
.reduce(default_handler) do |handler, mw|
|
44
|
+
mw.call(handler)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_websocket_app
|
49
|
+
App.new(@context, @rooms, to_handler)
|
50
|
+
end
|
51
|
+
|
52
|
+
end # class Builder
|
53
|
+
end # module Hub
|
54
|
+
end # module Websocket
|
55
|
+
end # module Startback
|
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
class Message
|
6
|
+
|
7
|
+
def initialize(data, socket)
|
8
|
+
data = JSON.parse(data, symbolize_names: true)
|
9
|
+
@headers = data[:headers] || {}
|
10
|
+
@body = data[:body] || {}
|
11
|
+
@socket = socket
|
12
|
+
end
|
13
|
+
attr_reader :headers, :body, :socket
|
14
|
+
|
15
|
+
def reply(message)
|
16
|
+
raise "No reply-to header found" unless headers[:'reply-to']
|
17
|
+
response = {
|
18
|
+
headers: {
|
19
|
+
:'in-reply-to' => headers[:'reply-to']
|
20
|
+
},
|
21
|
+
body: message
|
22
|
+
}
|
23
|
+
@socket.send(response.to_json)
|
24
|
+
end
|
25
|
+
|
26
|
+
end # class Message
|
27
|
+
end # module Hub
|
28
|
+
end # module Websocket
|
29
|
+
end # module Startback
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
module Middleware
|
6
|
+
class CommandHandler
|
7
|
+
|
8
|
+
def initialize(app, opts, &bl)
|
9
|
+
@app = app
|
10
|
+
@opts = opts
|
11
|
+
@handler = bl
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(event, socket, env)
|
15
|
+
who = matches?(event) ? @handler : @app
|
16
|
+
who.call(event, socket, env)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def matches?(event)
|
22
|
+
if event.headers[:command]
|
23
|
+
event.headers[:command]&.to_sym === @opts[:name]
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end # class CommandHandler
|
30
|
+
end # module Middleware
|
31
|
+
end # module Hub
|
32
|
+
end # module Websocket
|
33
|
+
end # module Startback
|
34
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
module Middleware
|
6
|
+
class RoomHandler
|
7
|
+
|
8
|
+
def initialize(app, room, handler)
|
9
|
+
@app = app
|
10
|
+
@room = room
|
11
|
+
@handler = handler
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(event, socket, env)
|
15
|
+
who = matches?(event) ? @handler : @app
|
16
|
+
who.call(event, socket, env)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def matches?(event)
|
22
|
+
event.headers[:room] === @room.name
|
23
|
+
end
|
24
|
+
|
25
|
+
end # class RoomHandler
|
26
|
+
end # module Middleware
|
27
|
+
end # module Hub
|
28
|
+
end # module Websocket
|
29
|
+
end # module Startback
|
30
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
module Middleware
|
6
|
+
end # module Middleware
|
7
|
+
end # module Hub
|
8
|
+
end # module Websocket
|
9
|
+
end # module Startback
|
10
|
+
|
11
|
+
require_relative "middleware/command_handler"
|
12
|
+
require_relative "middleware/room_handler"
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Startback
|
2
|
+
module Websocket
|
3
|
+
module Hub
|
4
|
+
class Participant
|
5
|
+
|
6
|
+
def initialize(socket, context, metadata={})
|
7
|
+
@socket = socket
|
8
|
+
@context = context
|
9
|
+
@metadata = metadata
|
10
|
+
end
|
11
|
+
attr_reader :socket, :context, :metadata
|
12
|
+
|
13
|
+
end # class Participant
|
14
|
+
end # module Hub
|
15
|
+
end # module Websocket
|
16
|
+
end # module Startback
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
class Room < App
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
@participants = []
|
10
|
+
end
|
11
|
+
attr_reader :name, :participants
|
12
|
+
|
13
|
+
def add(participant)
|
14
|
+
raise "Participant instance expected" unless participant.is_a? Participant
|
15
|
+
@participants << participant
|
16
|
+
participant.socket.on :close do |event|
|
17
|
+
remove(participant)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def remove(participant)
|
22
|
+
raise "Participant instance expected" unless participant.is_a? Participant
|
23
|
+
@participants.delete participant
|
24
|
+
end
|
25
|
+
|
26
|
+
def include?(participant)
|
27
|
+
raise "Participant instance expected" unless participant.is_a? Participant
|
28
|
+
@participants.include? participant
|
29
|
+
end
|
30
|
+
|
31
|
+
def broadcast(message)
|
32
|
+
puts "Broadcasting to #{@participants.size} participants"
|
33
|
+
@participants.each do |p|
|
34
|
+
p.socket.send({
|
35
|
+
headers: {
|
36
|
+
room: @name,
|
37
|
+
},
|
38
|
+
body: message
|
39
|
+
}.to_json)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end # class Room
|
44
|
+
end # module Hub
|
45
|
+
end # module Websocket
|
46
|
+
end # module Startback
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
module Startback
|
3
|
+
module Websocket
|
4
|
+
module Hub
|
5
|
+
end # module Hub
|
6
|
+
end # module Websocket
|
7
|
+
end # module Startback
|
8
|
+
|
9
|
+
require_relative "hub/errors"
|
10
|
+
require_relative "hub/message"
|
11
|
+
require_relative "hub/middleware"
|
12
|
+
require_relative "hub/participant"
|
13
|
+
require_relative "hub/room"
|
14
|
+
require_relative "hub/app"
|
15
|
+
require_relative "hub/builder"
|
data/spec/spec_helper.rb
CHANGED
@@ -1,49 +1,38 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
1
2
|
require 'startback'
|
2
|
-
require 'startback/
|
3
|
-
require 'startback/support/fake_logger'
|
3
|
+
require 'startback/websocket'
|
4
4
|
require 'rack/test'
|
5
|
-
require 'ostruct'
|
6
5
|
|
7
6
|
module SpecHelpers
|
8
|
-
end
|
9
|
-
|
10
|
-
RSpec.configure do |c|
|
11
|
-
c.include SpecHelpers
|
12
|
-
end
|
13
|
-
|
14
|
-
class SubContext < Startback::Context
|
15
7
|
|
16
|
-
|
17
|
-
|
18
|
-
h_factory do |c,h|
|
19
|
-
c.foo = h["foo"]
|
8
|
+
class SubContext < Startback::Context
|
9
|
+
attr_accessor :websocket_app
|
20
10
|
end
|
21
11
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
12
|
+
class MockSocket
|
13
|
+
attr_reader :last_message
|
25
14
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
|
32
|
-
class SubContext
|
15
|
+
def send(msg)
|
16
|
+
@last_message = msg
|
17
|
+
end
|
33
18
|
|
34
|
-
|
19
|
+
def close()
|
20
|
+
end
|
35
21
|
|
36
|
-
|
37
|
-
|
22
|
+
def on(event, &bl)
|
23
|
+
end
|
38
24
|
end
|
39
25
|
|
40
|
-
|
41
|
-
|
26
|
+
class MockFayeEvent
|
27
|
+
def initialize(event)
|
28
|
+
@data = event[:data]
|
29
|
+
@headers = event[:headers] || {}
|
30
|
+
end
|
31
|
+
attr_reader :data, :headers
|
42
32
|
end
|
43
33
|
|
44
34
|
end
|
45
35
|
|
46
|
-
|
47
|
-
|
48
|
-
end
|
36
|
+
RSpec.configure do |c|
|
37
|
+
c.include SpecHelpers
|
49
38
|
end
|