async-cable 0.1.0

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
+ SHA256:
3
+ metadata.gz: 8d7aec9e4d2af6f7e33c0684d7e5f1d7ec6484cd055f2be48bd08b4f635935a5
4
+ data.tar.gz: 4bac0fc445833442bab84556ca34aed98872e9f1d250b411a8abfef777c85555
5
+ SHA512:
6
+ metadata.gz: 3af7edbe1360eff3316b2fc88076476006ad39f607a20fdf112ddb40130dad0b9cd26316cc7c14e6c7af9cbe6a5320daab159ff71e219799ba9445592d523a2f
7
+ data.tar.gz: 8c021590476f294e34448d59d629fd9499f082ba5a8ed68d72677f52c799fcbd2a10cc7872c10e6ccc317e093ec708c992c70b8e055478ce66b1576b814cb7df
checksums.yaml.gz.sig ADDED
Binary file
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ # Released under the MIT License.
3
+ # Copyright, 2024, by Samuel Williams.
4
+
5
+ require "action_cable/subscription_adapter/base"
6
+ require "action_cable/subscription_adapter/channel_prefix"
7
+ require "action_cable/subscription_adapter/subscriber_map"
8
+
9
+ require "async/redis"
10
+
11
+ module ActionCable::SubscriptionAdapter
12
+ class AsyncCableRedis < Base
13
+ prepend ChannelPrefix
14
+
15
+ def initialize(*arguments, endpoint: nil, **options)
16
+ super(*arguments, **options)
17
+
18
+ @endpoint = endpoint || ::Async::Redis.local_endpoint
19
+ @client = ::Async::Redis::Client.new(endpoint)
20
+
21
+ @subscriber = Subscriber.new(@client, self.executor)
22
+ end
23
+
24
+ def subscribe(channel, callback, success_callback = nil)
25
+ @subscriber.add_subscriber(channel, callback, success_callback)
26
+ end
27
+
28
+ def unsubscribe(channel, callback)
29
+ @subscriber.remove_subscriber(channel, callback)
30
+ end
31
+
32
+ def broadcast(channel, payload)
33
+ @client.publish(channel, payload)
34
+ end
35
+
36
+ def shutdown
37
+ @subscriber&.close
38
+ end
39
+
40
+ private
41
+
42
+ class Subscriber < SubscriberMap::Async
43
+ CHANNEL = "_action_cable_internal"
44
+
45
+ def initialize(client, executor, parent: Async::Task.current)
46
+ super(executor)
47
+
48
+ @context = @client.subscribe(CHANNEL)
49
+ @task = parent.async{self.listen(CHANNEL)}
50
+ end
51
+
52
+ def add_channel(channel, on_success)
53
+ @context.subscribe(channel)
54
+ on_success&.call
55
+ end
56
+
57
+ def remove_channel(channel)
58
+ @context.unsubscribe(channel)
59
+ end
60
+
61
+ def close
62
+ if task = @task
63
+ @task = nil
64
+ task.stop
65
+ end
66
+
67
+ if context = @context
68
+ @context = nil
69
+ context.close
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def listen
76
+ context.each do |type, channel, message|
77
+ self.broadcast(event[1], event[2])
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ # Released under the MIT License.
3
+ # Copyright, 2024, by Samuel Williams.
4
+
5
+ require "async/websocket/adapters/rack"
6
+ require "action_cable"
7
+
8
+ require_relative "socket"
9
+
10
+ module Async
11
+ module Cable
12
+ class Middleware
13
+ def initialize(app, server: ActionCable.server)
14
+ @app = app
15
+ @server = server
16
+ @coder = ActiveSupport::JSON
17
+ @protocols = ::ActionCable::INTERNAL[:protocols]
18
+ end
19
+
20
+ attr :server
21
+
22
+ def call(env)
23
+ if Async::WebSocket::Adapters::Rack.websocket?(env) and allow_request_origin?(env)
24
+ Async::WebSocket::Adapters::Rack.open(env, protocols: @protocols) do |websocket|
25
+ handle_incoming_websocket(env, websocket)
26
+ end
27
+ else
28
+ @app.call(env)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def handle_incoming_websocket(env, websocket)
35
+ socket = Socket.new(env, websocket, @server, coder: @coder)
36
+ connection = @server.config.connection_class.call.new(@server, socket)
37
+
38
+ connection.handle_open
39
+ @server.add_connection(connection)
40
+ @server.setup_heartbeat_timer
41
+
42
+ socket_task = socket.run
43
+
44
+ while message = websocket.read
45
+ Console.debug(self, "Received cable data:", message.buffer)
46
+ connection.handle_incoming(@coder.decode(message.buffer))
47
+ end
48
+ rescue Protocol::WebSocket::ClosedError, EOFError
49
+ # This is a normal disconnection.
50
+ rescue => error
51
+ Console.warn(self, error)
52
+ ensure
53
+ if connection
54
+ @server.remove_connection(connection)
55
+ connection.handle_close
56
+ end
57
+
58
+ socket_task&.stop
59
+ end
60
+
61
+ # TODO: Shouldn't this be moved to ActionCable::Server::Base?
62
+ def allow_request_origin?(env)
63
+ if @server.config.disable_request_forgery_protection
64
+ return true
65
+ end
66
+
67
+ proto = ::Rack::Request.new(env).ssl? ? "https" : "http"
68
+
69
+ if @server.config.allow_same_origin_as_host && env["HTTP_ORIGIN"] == "#{proto}://#{env["HTTP_HOST"]}"
70
+ return true
71
+ elsif Array(@server.config.allowed_request_origins).any?{|allowed_origin| allowed_origin === env["HTTP_ORIGIN"]}
72
+ return true
73
+ end
74
+
75
+ Console.warn(self, "Request origin not allowed!", origin: env["HTTP_ORIGIN"])
76
+ return false
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require_relative "middleware"
7
+
8
+ module Async
9
+ module Cable
10
+ class Railtie < Rails::Railtie
11
+ initializer "async.cable.configure_rails_initialization" do |app|
12
+ app.middleware.use Async::Cable::Middleware
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ # Released under the MIT License.
3
+ # Copyright, 2024, by Samuel Williams.
4
+
5
+ module Async::Cable
6
+ class Socket
7
+ def initialize(env, websocket, server, coder: ActiveSupport::JSON)
8
+ @env = env
9
+ @websocket = websocket
10
+ @server = server
11
+ @coder = coder
12
+
13
+ @output = ::Thread::Queue.new
14
+ end
15
+
16
+ attr :env
17
+
18
+ def logger
19
+ @server.logger
20
+ end
21
+
22
+ def request
23
+ # Copied from ActionCable::Server::Socket#request
24
+ @request ||= begin
25
+ if defined?(Rails.application) && Rails.application
26
+ environment = Rails.application.env_config.merge(@env)
27
+ end
28
+
29
+ ActionDispatch::Request.new(environment || @env)
30
+ end
31
+ end
32
+
33
+ def run(parent: Async::Task.current)
34
+ parent.async do
35
+ while buffer = @output.pop
36
+ @websocket.send_text(buffer)
37
+ @websocket.flush if @output.empty?
38
+ end
39
+ rescue => error
40
+ ensure
41
+ @websocket.close_write(error)
42
+ end
43
+ end
44
+
45
+ def transmit(data)
46
+ # Console.info(self, "Transmitting data:", data, task: Async::Task.current?)
47
+ @output.push(@coder.encode(data))
48
+ end
49
+
50
+ def close
51
+ # Console.info(self, "Closing socket.", task: Async::Task.current?)
52
+ @output.close
53
+ end
54
+
55
+ # This can be called from the work pool, off the event loop.
56
+ def perform_work(receiver, ...)
57
+ # Console.info(self, "Performing work:", receiver)
58
+ receiver.send(...)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+ # Released under the MIT License.
3
+ # Copyright, 2023-2024, by Samuel Williams.
4
+
5
+ module Async
6
+ module Cable
7
+ VERSION = "0.1.0"
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2024, by Samuel Williams.
5
+
6
+ require_relative "cable/version"
7
+
8
+ begin
9
+ require "rails/railtie"
10
+ rescue LoadError
11
+ # Ignore.
12
+ end
13
+
14
+ if defined?(Rails::Railtie)
15
+ require_relative "cable/railtie"
16
+ end
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2023-2024, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,37 @@
1
+ # Async::Cable
2
+
3
+ This is a proof-of-concept adapter for Rails 7.2+. It depends on `actioncable-next` which completely replaces the internal implementation of Action Cable with a pluggable one.
4
+
5
+ [![Development Status](https://github.com/socketry/async-cable/workflows/Test/badge.svg)](https://github.com/socketry/async-cable/actions?workflow=Test)
6
+
7
+ ## Usage
8
+
9
+ Please see the [project documentation](https://socketry.github.io/async-cable/) for more details.
10
+
11
+ - [Getting Started](https://socketry.github.io/async-cable/guides/getting-started/index) - This guide shows you how to add `async-cable` to your project to enable real-time communication between clients and servers using Falcon and Action Cable.
12
+
13
+ ## Releases
14
+
15
+ Please see the [project releases](https://socketry.github.io/async-cable/releases/index) for all releases.
16
+
17
+ ### v0.1.0
18
+
19
+ - Initial implementation.
20
+
21
+ ## Contributing
22
+
23
+ We welcome contributions to this project.
24
+
25
+ 1. Fork it.
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
28
+ 4. Push to the branch (`git push origin my-new-feature`).
29
+ 5. Create new Pull Request.
30
+
31
+ ### Developer Certificate of Origin
32
+
33
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
34
+
35
+ ### Community Guidelines
36
+
37
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
data/releases.md ADDED
@@ -0,0 +1,5 @@
1
+ # Releases
2
+
3
+ ## v0.1.0
4
+
5
+ - Initial implementation.
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: async-cable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
14
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
15
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
16
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
17
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
18
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
19
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
20
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
21
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
22
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
23
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
24
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
25
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
26
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
27
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
28
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
30
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
31
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
32
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
33
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
34
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
35
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
36
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
37
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
38
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
39
+ -----END CERTIFICATE-----
40
+ date: 2024-11-19 00:00:00.000000000 Z
41
+ dependencies:
42
+ - !ruby/object:Gem::Dependency
43
+ name: actioncable-next
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: async
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '2.9'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '2.9'
70
+ - !ruby/object:Gem::Dependency
71
+ name: async-websocket
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description:
85
+ email:
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/action_cable/subscription_adapters/async_cable_redis.rb
91
+ - lib/async/cable.rb
92
+ - lib/async/cable/middleware.rb
93
+ - lib/async/cable/railtie.rb
94
+ - lib/async/cable/socket.rb
95
+ - lib/async/cable/version.rb
96
+ - license.md
97
+ - readme.md
98
+ - releases.md
99
+ homepage:
100
+ licenses:
101
+ - MIT
102
+ metadata:
103
+ documentation_uri: https://socketry.github.io/async-cable/
104
+ source_code_uri: https://github.com/socketry/async-cable
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '3.1'
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubygems_version: 3.5.22
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: An asynchronous adapter for ActionCable.
124
+ test_files: []
metadata.gz.sig ADDED
Binary file