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.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -2
  3. data/README.md +64 -9
  4. data/dist/client.js +1 -0
  5. data/lib/startback/ext/context.rb +6 -0
  6. data/lib/startback/ext.rb +1 -2
  7. data/lib/startback/websocket/app.rb +82 -0
  8. data/lib/startback/websocket/hub/app.rb +28 -0
  9. data/lib/startback/websocket/hub/builder.rb +55 -0
  10. data/lib/startback/websocket/hub/errors.rb +9 -0
  11. data/lib/startback/websocket/hub/message.rb +29 -0
  12. data/lib/startback/websocket/hub/middleware/command_handler.rb +34 -0
  13. data/lib/startback/websocket/hub/middleware/room_handler.rb +30 -0
  14. data/lib/startback/websocket/hub/middleware.rb +12 -0
  15. data/lib/startback/websocket/hub/participant.rb +16 -0
  16. data/lib/startback/websocket/hub/room.rb +46 -0
  17. data/lib/startback/websocket/hub.rb +15 -0
  18. data/lib/startback/websocket.rb +8 -0
  19. data/spec/spec_helper.rb +21 -32
  20. data/spec/unit/hub/test_builder.rb +141 -0
  21. data/spec/unit/hub/test_room.rb +27 -0
  22. data/spec/unit/test_app.rb +35 -0
  23. data/tasks/test.rake +0 -1
  24. metadata +21 -91
  25. data/lib/startback/audit/prometheus.rb +0 -87
  26. data/lib/startback/audit/shared.rb +0 -17
  27. data/lib/startback/audit/trailer.rb +0 -129
  28. data/lib/startback/audit.rb +0 -3
  29. data/lib/startback/caching/entity_cache.rb +0 -157
  30. data/lib/startback/caching/no_store.rb +0 -28
  31. data/lib/startback/caching/store.rb +0 -34
  32. data/lib/startback/context/h_factory.rb +0 -43
  33. data/lib/startback/context/middleware.rb +0 -53
  34. data/lib/startback/context.rb +0 -122
  35. data/lib/startback/errors.rb +0 -197
  36. data/lib/startback/event/agent.rb +0 -84
  37. data/lib/startback/event/bus/bunny/async.rb +0 -162
  38. data/lib/startback/event/bus/bunny.rb +0 -1
  39. data/lib/startback/event/bus/memory/async.rb +0 -45
  40. data/lib/startback/event/bus/memory/sync.rb +0 -35
  41. data/lib/startback/event/bus/memory.rb +0 -2
  42. data/lib/startback/event/bus.rb +0 -100
  43. data/lib/startback/event/engine.rb +0 -94
  44. data/lib/startback/event/ext/context.rb +0 -5
  45. data/lib/startback/event/ext/operation.rb +0 -13
  46. data/lib/startback/event.rb +0 -47
  47. data/lib/startback/ext/date_time.rb +0 -9
  48. data/lib/startback/ext/time.rb +0 -9
  49. data/lib/startback/model.rb +0 -6
  50. data/lib/startback/operation/error_operation.rb +0 -19
  51. data/lib/startback/operation/multi_operation.rb +0 -28
  52. data/lib/startback/operation.rb +0 -78
  53. data/lib/startback/services.rb +0 -11
  54. data/lib/startback/support/data_object.rb +0 -71
  55. data/lib/startback/support/env.rb +0 -41
  56. data/lib/startback/support/fake_logger.rb +0 -18
  57. data/lib/startback/support/hooks.rb +0 -48
  58. data/lib/startback/support/log_formatter.rb +0 -34
  59. data/lib/startback/support/logger.rb +0 -34
  60. data/lib/startback/support/operation_runner.rb +0 -150
  61. data/lib/startback/support/robustness.rb +0 -157
  62. data/lib/startback/support/transaction_manager.rb +0 -25
  63. data/lib/startback/support/transaction_policy.rb +0 -33
  64. data/lib/startback/support/world.rb +0 -54
  65. data/lib/startback/support.rb +0 -26
  66. data/lib/startback/version.rb +0 -8
  67. data/lib/startback/web/api.rb +0 -99
  68. data/lib/startback/web/auto_caching.rb +0 -85
  69. data/lib/startback/web/catch_all.rb +0 -52
  70. data/lib/startback/web/cors_headers.rb +0 -80
  71. data/lib/startback/web/health_check.rb +0 -49
  72. data/lib/startback/web/magic_assets/ng_html_transformer.rb +0 -80
  73. data/lib/startback/web/magic_assets/rake_tasks.rb +0 -64
  74. data/lib/startback/web/magic_assets.rb +0 -98
  75. data/lib/startback/web/middleware.rb +0 -13
  76. data/lib/startback/web/prometheus.rb +0 -16
  77. data/lib/startback/web/shield.rb +0 -58
  78. data/lib/startback.rb +0 -43
  79. data/spec/unit/audit/test_prometheus.rb +0 -72
  80. data/spec/unit/audit/test_trailer.rb +0 -105
  81. data/spec/unit/caching/test_entity_cache.rb +0 -136
  82. data/spec/unit/context/test_abstraction_factory.rb +0 -64
  83. data/spec/unit/context/test_dup.rb +0 -42
  84. data/spec/unit/context/test_fork.rb +0 -37
  85. data/spec/unit/context/test_h_factory.rb +0 -31
  86. data/spec/unit/context/test_middleware.rb +0 -45
  87. data/spec/unit/context/test_with_world.rb +0 -20
  88. data/spec/unit/context/test_world.rb +0 -17
  89. data/spec/unit/event/bus/memory/test_async.rb +0 -43
  90. data/spec/unit/event/bus/memory/test_sync.rb +0 -43
  91. data/spec/unit/support/hooks/test_after_hook.rb +0 -54
  92. data/spec/unit/support/hooks/test_before_hook.rb +0 -54
  93. data/spec/unit/support/operation_runner/test_around_run.rb +0 -156
  94. data/spec/unit/support/operation_runner/test_before_after_call.rb +0 -48
  95. data/spec/unit/support/test_data_object.rb +0 -156
  96. data/spec/unit/support/test_env.rb +0 -75
  97. data/spec/unit/support/test_robusteness.rb +0 -229
  98. data/spec/unit/support/test_transaction_manager.rb +0 -64
  99. data/spec/unit/support/test_world.rb +0 -72
  100. data/spec/unit/test_event.rb +0 -62
  101. data/spec/unit/test_operation.rb +0 -55
  102. data/spec/unit/test_support.rb +0 -40
  103. data/spec/unit/web/fixtures/assets/app/hello.es6 +0 -4
  104. data/spec/unit/web/fixtures/assets/app/hello.html +0 -1
  105. data/spec/unit/web/fixtures/assets/index.es6 +0 -1
  106. data/spec/unit/web/test_api.rb +0 -82
  107. data/spec/unit/web/test_auto_caching.rb +0 -81
  108. data/spec/unit/web/test_catch_all.rb +0 -77
  109. data/spec/unit/web/test_cors_headers.rb +0 -88
  110. data/spec/unit/web/test_healthcheck.rb +0 -59
  111. 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: 296fd63b702924e7a5952b60a67bbb90e89b99a415be62e52a4f7f63e5638753
4
- data.tar.gz: 8234e5f95a05e91f6698da787bc8479ce924a2bfc537866ea68d188f89df7b54
3
+ metadata.gz: 2909a07d46bc34a032aa02e2f44bcce0bf3404967eb99ef9e60b92d6c547febb
4
+ data.tar.gz: e9df9bef24c2985f95b1214d3e2df7acbada61e3f2b84346e4b6562dcf4fae0b
5
5
  SHA512:
6
- metadata.gz: 48169cfbda94301c4a8ebbfea5f0ba93bedba26f59860a4b8d5376d51ec19cf0f23dcbbda9d7c8c366d483f1bd22d9d36dec2ce90ec362870adc731e7c28a7f6
7
- data.tar.gz: e304a39fb8f4ce6a3c3df896c014f8b60eda3a58b18b4e9f52c7de1eeee67b3f7fd3b12e9652d7e50ccc7344377a9c289fd9c0aa14d4a6c13dd9ee75fcf39fab
6
+ metadata.gz: 00dc98c0337281af6b7d18a148553428983f7494cc709c120259901e9e58ea8e38b02796f26edd5c520de288dd586e0086aa2e7e205136e254297bef83048b62
7
+ data.tar.gz: a545063482457190d47d13daf336af65b5275a87a708dea7e5b5b7b7ede6dc8e344930f4de222a9293f46cc1ed5e1fb55897f02efb29e6cf2cff5a279e006326
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
1
  source "https://rubygems.org"
2
- gem 'startback', path: "."
3
- gemspec :name => 'startback-web'
2
+ gemspec
3
+
4
+ gem 'startback', path: '../..'
data/README.md CHANGED
@@ -1,13 +1,68 @@
1
- # Startback - Got Your Ruby Back
1
+ ## Hub
2
2
 
3
- Yet another ruby framework, I'm afraid. Here, we srongly seperate between:
3
+ The `Hub:App` provides an opinionated protocol that eases
4
+ the handling of real-time, websocket-based, applications.
4
5
 
5
- 1. the web layer, in charge of a quality HTTP handling
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
- Currently,
8
+ The protocol is mainly based on JSON-serialized messages containing both a `headers` map and a `body` property.
10
9
 
11
- 1. is handled using extra support on top of Sinatra
12
- 2. is handled using Startback specific classes
13
- 3. is handled using Bmg
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})();
@@ -0,0 +1,6 @@
1
+ module Startback
2
+ class Context
3
+ attr_accessor :websocket_app
4
+
5
+ end
6
+ end
data/lib/startback/ext.rb CHANGED
@@ -1,2 +1 @@
1
- require_relative 'ext/date_time'
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,9 @@
1
+
2
+ module Startback
3
+ module Websocket
4
+ module Hub
5
+ class Error < Startback::Errors::Error
6
+ end # class Error
7
+ end # module Hub
8
+ end # module Websocket
9
+ 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"
@@ -0,0 +1,8 @@
1
+ require 'faye/websocket'
2
+ module Startback
3
+ module Websocket
4
+ end # module Websocket
5
+ end # module Startback
6
+ require_relative 'ext'
7
+ require_relative 'websocket/app'
8
+ require_relative 'websocket/hub'
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/event'
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
- attr_accessor :foo
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
- h_dump do |h|
23
- h.merge!("foo" => foo)
24
- end
12
+ class MockSocket
13
+ attr_reader :last_message
25
14
 
26
- world(:partner) do
27
- Object.new
28
- end
29
-
30
- end
31
-
32
- class SubContext
15
+ def send(msg)
16
+ @last_message = msg
17
+ end
33
18
 
34
- attr_accessor :bar
19
+ def close()
20
+ end
35
21
 
36
- h_factory do |c,h|
37
- c.bar = h["bar"]
22
+ def on(event, &bl)
23
+ end
38
24
  end
39
25
 
40
- h_dump do |h|
41
- h.merge!("bar" => bar)
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
- class User
47
- class Changed < Startback::Event
48
- end
36
+ RSpec.configure do |c|
37
+ c.include SpecHelpers
49
38
  end