startback-websocket 0.14.0 → 0.14.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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -2
  3. data/README.md +64 -9
  4. data/lib/startback/ext/context.rb +6 -0
  5. data/lib/startback/ext.rb +1 -2
  6. data/lib/startback/websocket/app.rb +82 -0
  7. data/lib/startback/websocket/hub/app.rb +28 -0
  8. data/lib/startback/websocket/hub/builder.rb +55 -0
  9. data/lib/startback/websocket/hub/errors.rb +9 -0
  10. data/lib/startback/websocket/hub/message.rb +29 -0
  11. data/lib/startback/websocket/hub/middleware/command_handler.rb +34 -0
  12. data/lib/startback/websocket/hub/middleware/room_handler.rb +30 -0
  13. data/lib/startback/websocket/hub/middleware.rb +12 -0
  14. data/lib/startback/websocket/hub/participant.rb +16 -0
  15. data/lib/startback/websocket/hub/room.rb +46 -0
  16. data/lib/startback/websocket/hub.rb +15 -0
  17. data/lib/startback/websocket.rb +8 -0
  18. data/spec/spec_helper.rb +21 -32
  19. data/spec/unit/hub/test_builder.rb +141 -0
  20. data/spec/unit/hub/test_room.rb +27 -0
  21. data/spec/unit/test_app.rb +35 -0
  22. data/tasks/test.rake +0 -1
  23. metadata +20 -91
  24. data/lib/startback/audit/prometheus.rb +0 -87
  25. data/lib/startback/audit/shared.rb +0 -17
  26. data/lib/startback/audit/trailer.rb +0 -129
  27. data/lib/startback/audit.rb +0 -3
  28. data/lib/startback/caching/entity_cache.rb +0 -157
  29. data/lib/startback/caching/no_store.rb +0 -28
  30. data/lib/startback/caching/store.rb +0 -34
  31. data/lib/startback/context/h_factory.rb +0 -43
  32. data/lib/startback/context/middleware.rb +0 -53
  33. data/lib/startback/context.rb +0 -122
  34. data/lib/startback/errors.rb +0 -197
  35. data/lib/startback/event/agent.rb +0 -84
  36. data/lib/startback/event/bus/bunny/async.rb +0 -162
  37. data/lib/startback/event/bus/bunny.rb +0 -1
  38. data/lib/startback/event/bus/memory/async.rb +0 -45
  39. data/lib/startback/event/bus/memory/sync.rb +0 -35
  40. data/lib/startback/event/bus/memory.rb +0 -2
  41. data/lib/startback/event/bus.rb +0 -100
  42. data/lib/startback/event/engine.rb +0 -94
  43. data/lib/startback/event/ext/context.rb +0 -5
  44. data/lib/startback/event/ext/operation.rb +0 -13
  45. data/lib/startback/event.rb +0 -47
  46. data/lib/startback/ext/date_time.rb +0 -9
  47. data/lib/startback/ext/time.rb +0 -9
  48. data/lib/startback/model.rb +0 -6
  49. data/lib/startback/operation/error_operation.rb +0 -19
  50. data/lib/startback/operation/multi_operation.rb +0 -28
  51. data/lib/startback/operation.rb +0 -78
  52. data/lib/startback/services.rb +0 -11
  53. data/lib/startback/support/data_object.rb +0 -71
  54. data/lib/startback/support/env.rb +0 -41
  55. data/lib/startback/support/fake_logger.rb +0 -18
  56. data/lib/startback/support/hooks.rb +0 -48
  57. data/lib/startback/support/log_formatter.rb +0 -34
  58. data/lib/startback/support/logger.rb +0 -34
  59. data/lib/startback/support/operation_runner.rb +0 -150
  60. data/lib/startback/support/robustness.rb +0 -157
  61. data/lib/startback/support/transaction_manager.rb +0 -25
  62. data/lib/startback/support/transaction_policy.rb +0 -33
  63. data/lib/startback/support/world.rb +0 -54
  64. data/lib/startback/support.rb +0 -26
  65. data/lib/startback/version.rb +0 -8
  66. data/lib/startback/web/api.rb +0 -99
  67. data/lib/startback/web/auto_caching.rb +0 -85
  68. data/lib/startback/web/catch_all.rb +0 -52
  69. data/lib/startback/web/cors_headers.rb +0 -80
  70. data/lib/startback/web/health_check.rb +0 -49
  71. data/lib/startback/web/magic_assets/ng_html_transformer.rb +0 -80
  72. data/lib/startback/web/magic_assets/rake_tasks.rb +0 -64
  73. data/lib/startback/web/magic_assets.rb +0 -98
  74. data/lib/startback/web/middleware.rb +0 -13
  75. data/lib/startback/web/prometheus.rb +0 -16
  76. data/lib/startback/web/shield.rb +0 -58
  77. data/lib/startback.rb +0 -43
  78. data/spec/unit/audit/test_prometheus.rb +0 -72
  79. data/spec/unit/audit/test_trailer.rb +0 -105
  80. data/spec/unit/caching/test_entity_cache.rb +0 -136
  81. data/spec/unit/context/test_abstraction_factory.rb +0 -64
  82. data/spec/unit/context/test_dup.rb +0 -42
  83. data/spec/unit/context/test_fork.rb +0 -37
  84. data/spec/unit/context/test_h_factory.rb +0 -31
  85. data/spec/unit/context/test_middleware.rb +0 -45
  86. data/spec/unit/context/test_with_world.rb +0 -20
  87. data/spec/unit/context/test_world.rb +0 -17
  88. data/spec/unit/event/bus/memory/test_async.rb +0 -43
  89. data/spec/unit/event/bus/memory/test_sync.rb +0 -43
  90. data/spec/unit/support/hooks/test_after_hook.rb +0 -54
  91. data/spec/unit/support/hooks/test_before_hook.rb +0 -54
  92. data/spec/unit/support/operation_runner/test_around_run.rb +0 -156
  93. data/spec/unit/support/operation_runner/test_before_after_call.rb +0 -48
  94. data/spec/unit/support/test_data_object.rb +0 -156
  95. data/spec/unit/support/test_env.rb +0 -75
  96. data/spec/unit/support/test_robusteness.rb +0 -229
  97. data/spec/unit/support/test_transaction_manager.rb +0 -64
  98. data/spec/unit/support/test_world.rb +0 -72
  99. data/spec/unit/test_event.rb +0 -62
  100. data/spec/unit/test_operation.rb +0 -55
  101. data/spec/unit/test_support.rb +0 -40
  102. data/spec/unit/web/fixtures/assets/app/hello.es6 +0 -4
  103. data/spec/unit/web/fixtures/assets/app/hello.html +0 -1
  104. data/spec/unit/web/fixtures/assets/index.es6 +0 -1
  105. data/spec/unit/web/test_api.rb +0 -82
  106. data/spec/unit/web/test_auto_caching.rb +0 -81
  107. data/spec/unit/web/test_catch_all.rb +0 -77
  108. data/spec/unit/web/test_cors_headers.rb +0 -88
  109. data/spec/unit/web/test_healthcheck.rb +0 -59
  110. 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: 6d94ab898d70e35e0d5b188c459af5ab40070ae0e5e7a1d23bbbfc6aad61694f
4
+ data.tar.gz: 333ed1fbf65609758827015d725dbbed14224249141e979204b7203fa3d75405
5
5
  SHA512:
6
- metadata.gz: 48169cfbda94301c4a8ebbfea5f0ba93bedba26f59860a4b8d5376d51ec19cf0f23dcbbda9d7c8c366d483f1bd22d9d36dec2ce90ec362870adc731e7c28a7f6
7
- data.tar.gz: e304a39fb8f4ce6a3c3df896c014f8b60eda3a58b18b4e9f52c7de1eeee67b3f7fd3b12e9652d7e50ccc7344377a9c289fd9c0aa14d4a6c13dd9ee75fcf39fab
6
+ metadata.gz: 687b5855a911d1d434bbfd6c9569d26a10b9615d7b4c06f10671d5fa645c4b62b053c9c3beacb49d252ebaa595144d9192b8e165ff73f2e4733bc56c677bb99b
7
+ data.tar.gz: 9852ab507c3a9ad302bd7c323914ff248452e39d30a8bd4408e8f7c6407bf820aaafaa2abc7f7a20ec321ff80217a02dbf884bf82ada6be6802af8c505653a9d
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
+ ```
@@ -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