anycable-rails-core 1.5.6 โ 1.6.0.rc.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.
- checksums.yaml +4 -4
- data/README.md +10 -71
- data/lib/anycable/rails/action_cable_ext/channel.rb +11 -0
- data/lib/anycable/rails/channel/presence.rb +36 -0
- data/lib/anycable/rails/channel.rb +9 -0
- data/lib/anycable/rails/connection.rb +33 -58
- data/lib/anycable/rails/connections/serializable_identification.rb +14 -0
- data/lib/anycable/rails/railtie.rb +3 -2
- data/lib/anycable/rails/version.rb +1 -1
- data/lib/anycable/rails.rb +1 -0
- metadata +19 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 395f76c02adbd3f4878b55bb21bf4bd00969e4394e824fb7a23f086d8b221b1c
|
4
|
+
data.tar.gz: b300f5b2ab64aa893d462a22045587f65531bbaea8dce3a920c1fb1df67b211e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f0a2f228649539c86b8e3f1a96f0c456b6ed0f933552fb93c90ef2d4a5c49261fdbdf9f3e7b650cd69b00ca38bddcf202bb6f1d730aa92ad07ec3f8504128904
|
7
|
+
data.tar.gz: dfce4029440d91a5a7f07abccca089b6b0bb759d152aa2fadad9ccb12ca0533a5d10cba6e200f398ce58c54c76c5b0fa5903c48aeacba2354dc8b5f9ad37d10f
|
data/README.md
CHANGED
@@ -1,30 +1,18 @@
|
|
1
|
-
[](https://rubygems.org/gems/anycable)
|
2
2
|
[](https://github.com/anycable/anycable-rails/actions)
|
3
3
|
[](https://docs.anycable.io/rails/getting_started)
|
4
4
|
|
5
|
-
# AnyCable Rails
|
5
|
+
# AnyCable SDK for Ruby on Rails
|
6
6
|
|
7
|
-
|
7
|
+
<img align="right" height="150" width="129"
|
8
|
+
title="AnyCable logo" src="https://docs.anycable.io/assets/images/logo.svg">
|
8
9
|
|
9
|
-
|
10
|
+
[AnyCable](https://github.com/anycable/anycable) is an open-source language-agnostic realtime server for reliable two-way communication over WebSockets and SSE.
|
11
|
+
This repository contains code for AnyCable Rails SKD that allows you to use AnyCable as a drop-in replacement for Action Cable.
|
10
12
|
|
11
|
-
๐พ [Example Application](https://github.com/anycable/anycable_rails_demo)
|
13
|
+
๐ [Website](https://anycable.io) ยท ๐ [Documentation](https://docs.anycable.io/rails/getting_started) ยท ๐พ [Example Rails Application](https://github.com/anycable/anycable_rails_demo)
|
12
14
|
|
13
|
-
|
14
|
-
|
15
|
-
> [AnyCable Pro](https://docs.anycable.io/pro) has been launched ๐
|
16
|
-
|
17
|
-
<a href="https://evilmartians.com/">
|
18
|
-
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
19
|
-
|
20
|
-
## Requirements
|
21
|
-
|
22
|
-
- Ruby >= 3.1
|
23
|
-
- Rails >= 6.0\*
|
24
|
-
|
25
|
-
\* Recent `anycable-rails` versions only work with Rails 8+; older versions compatible with Rails 6 and Rails 7 still receive fixes and minor updates (patch releases).
|
26
|
-
|
27
|
-
## Usage
|
15
|
+
## Quick Start
|
28
16
|
|
29
17
|
Add `anycable-rails` gem to your Gemfile:
|
30
18
|
|
@@ -32,62 +20,13 @@ Add `anycable-rails` gem to your Gemfile:
|
|
32
20
|
gem "anycable-rails"
|
33
21
|
```
|
34
22
|
|
35
|
-
|
36
|
-
|
37
|
-
After the gem was installed, you can run an interactive wizard to configure your Rails application for using with AnyCable by running a generator:
|
23
|
+
Then run our interactive setup command:
|
38
24
|
|
39
25
|
```sh
|
40
26
|
bundle exec rails g anycable:setup
|
41
27
|
```
|
42
28
|
|
43
|
-
|
44
|
-
|
45
|
-
Specify AnyCable subscription adapter for Action Cable:
|
46
|
-
|
47
|
-
```yml
|
48
|
-
# config/cable.yml
|
49
|
-
development:
|
50
|
-
adapter: any_cable # or anycable
|
51
|
-
|
52
|
-
production:
|
53
|
-
adapter: any_cable
|
54
|
-
```
|
55
|
-
|
56
|
-
and specify AnyCable WebSocket server URL:
|
57
|
-
|
58
|
-
```ruby
|
59
|
-
# For development it's likely the localhost
|
60
|
-
|
61
|
-
# config/environments/development.rb
|
62
|
-
config.action_cable.url = "ws://localhost:8080/cable"
|
63
|
-
|
64
|
-
# For production it's likely to have a sub-domain and secure connection
|
65
|
-
|
66
|
-
# config/environments/production.rb
|
67
|
-
config.action_cable.url = "wss://ws.example.com/cable"
|
68
|
-
```
|
69
|
-
|
70
|
-
Then, run AnyCable RPC server:
|
71
|
-
|
72
|
-
```sh
|
73
|
-
$ bundle exec anycable
|
74
|
-
|
75
|
-
# don't forget to provide Rails env
|
76
|
-
|
77
|
-
$ RAILS_ENV=production bundle exec anycable
|
78
|
-
```
|
79
|
-
|
80
|
-
And, finally, run AnyCable WebSocket server, e.g. [anycable-go](https://docs.anycable.io/anycable-go/getting_started):
|
81
|
-
|
82
|
-
```sh
|
83
|
-
anycable-go --host=localhost --port=8080
|
84
|
-
```
|
85
|
-
|
86
|
-
See [documentation](https://docs.anycable.io/rails/getting_started) for more information on AnyCable + Rails usage.
|
87
|
-
|
88
|
-
## Action Cable Compatibility
|
89
|
-
|
90
|
-
See [documentation](https://docs.anycable.io/rails/compatibility).
|
29
|
+
Learn more [here](https://docs.anycable.io/rails/getting_started).
|
91
30
|
|
92
31
|
## Contributing
|
93
32
|
|
@@ -3,6 +3,17 @@
|
|
3
3
|
require "action_cable"
|
4
4
|
|
5
5
|
ActionCable::Channel::Base.prepend(Module.new do
|
6
|
+
def subscribe_to_channel
|
7
|
+
super unless anycabled? && !@__anycable_subscribing__
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle_subscribe
|
11
|
+
@__anycable_subscribing__ = true
|
12
|
+
subscribe_to_channel
|
13
|
+
ensure
|
14
|
+
@__anycable_subscribing__ = false
|
15
|
+
end
|
16
|
+
|
6
17
|
def start_periodic_timers
|
7
18
|
super unless anycabled?
|
8
19
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnyCable
|
4
|
+
module Rails
|
5
|
+
module Channel
|
6
|
+
# Presence API for Action Cable channels (backed by AnyCable)
|
7
|
+
module Presence
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
def join_presence(stream = nil, id: user_presence_id, info: user_presence_info)
|
11
|
+
return unless anycabled?
|
12
|
+
|
13
|
+
stream ||= connection.anycable_socket.streams[:start].first || raise(ArgumentError, "Provide a stream name for presence updates")
|
14
|
+
|
15
|
+
connection.anycable_socket.presence_join(stream, id, info)
|
16
|
+
end
|
17
|
+
|
18
|
+
def leave_presence(id = user_presence_id)
|
19
|
+
return unless anycabled?
|
20
|
+
|
21
|
+
connection.anycable_socket.presence_leave(id)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def user_presence_id
|
27
|
+
connection.connection_identifier
|
28
|
+
end
|
29
|
+
|
30
|
+
def user_presence_info
|
31
|
+
# nothing
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -31,65 +31,15 @@ module AnyCable
|
|
31
31
|
end
|
32
32
|
|
33
33
|
refine ActionCable::Connection::Subscriptions do
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
def execute_rpc_command(data)
|
38
|
-
# First, verify the channel name
|
39
|
-
raise "Channel not found: #{ActiveSupport::JSON.decode(data["identifier"]).fetch("channel")}" unless subscription_class_from_identifier(data["identifier"])
|
40
|
-
|
41
|
-
if data["command"] == "subscribe"
|
42
|
-
add data
|
43
|
-
subscription = subscriptions[data["identifier"]]
|
44
|
-
return !(subscription.nil? || subscription.rejected?)
|
45
|
-
end
|
46
|
-
|
47
|
-
load(data["identifier"])
|
34
|
+
# Find or add a subscription to the list
|
35
|
+
def fetch(identifier)
|
36
|
+
add("identifier" => identifier) unless subscriptions[identifier]
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
remove data
|
52
|
-
when "message"
|
53
|
-
perform_action data
|
54
|
-
when "whisper"
|
55
|
-
whisper data
|
56
|
-
else
|
57
|
-
raise UnknownCommandError, data["command"]
|
38
|
+
unless subscriptions[identifier]
|
39
|
+
raise "Channel not found: #{ActiveSupport::JSON.decode(identifier).fetch("channel")}"
|
58
40
|
end
|
59
41
|
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
# Restore channels from the list of identifiers and the state
|
64
|
-
def restore(subscriptions, istate)
|
65
|
-
subscriptions.each do |id|
|
66
|
-
channel = load(id)
|
67
|
-
channel.__istate__ = ActiveSupport::JSON.decode(istate[id]) if istate[id]
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Find or create a channel for a given identifier
|
72
|
-
def load(identifier)
|
73
|
-
return subscriptions[identifier] if subscriptions[identifier]
|
74
|
-
|
75
|
-
subscription = subscription_from_identifier(identifier)
|
76
|
-
raise "Channel not found: #{ActiveSupport::JSON.decode(identifier).fetch("channel")}" unless subscription
|
77
|
-
|
78
|
-
subscriptions[identifier] = subscription
|
79
|
-
end
|
80
|
-
|
81
|
-
def subscription_class_from_identifier(id_key)
|
82
|
-
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
|
83
|
-
id_options[:channel].safe_constantize
|
84
|
-
end
|
85
|
-
|
86
|
-
def subscription_from_identifier(id_key)
|
87
|
-
subscription_klass = subscription_class_from_identifier(id_key)
|
88
|
-
|
89
|
-
if subscription_klass && subscription_klass < ActionCable::Channel::Base
|
90
|
-
id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
|
91
|
-
subscription_klass.new(connection, id_key, id_options)
|
92
|
-
end
|
42
|
+
subscriptions[identifier]
|
93
43
|
end
|
94
44
|
end
|
95
45
|
end)
|
@@ -124,8 +74,15 @@ module AnyCable
|
|
124
74
|
conn.cached_ids = {}
|
125
75
|
conn.anycable_request_builder = self
|
126
76
|
|
77
|
+
return unless subscriptions
|
78
|
+
|
127
79
|
# Pre-initialize channels (for disconnect)
|
128
|
-
|
80
|
+
subscriptions.each do |id|
|
81
|
+
channel = conn.subscriptions.fetch(id)
|
82
|
+
next unless socket.istate[id]
|
83
|
+
|
84
|
+
channel.__istate__ = ActiveSupport::JSON.decode(socket.istate[id])
|
85
|
+
end
|
129
86
|
end
|
130
87
|
|
131
88
|
def handle_open
|
@@ -154,8 +111,26 @@ module AnyCable
|
|
154
111
|
|
155
112
|
def handle_channel_command(identifier, command, data)
|
156
113
|
conn.run_callbacks :command do
|
157
|
-
|
114
|
+
# We cannot use subscriptions#execute_command here,
|
115
|
+
# since we MUST return true of false, depending on the status
|
116
|
+
# of execution
|
117
|
+
channel = conn.subscriptions.fetch(identifier)
|
118
|
+
case command
|
119
|
+
when "subscribe"
|
120
|
+
channel.handle_subscribe
|
121
|
+
!channel.rejected?
|
122
|
+
when "unsubscribe"
|
123
|
+
conn.subscriptions.remove_subscription(channel)
|
124
|
+
true
|
125
|
+
when "message"
|
126
|
+
channel.perform_action ActiveSupport::JSON.decode(data)
|
127
|
+
true
|
128
|
+
else
|
129
|
+
false
|
130
|
+
end
|
158
131
|
end
|
132
|
+
# Support rescue_from
|
133
|
+
# https://github.com/rails/rails/commit/d2571e560c62116f60429c933d0c41a0e249b58b
|
159
134
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
160
135
|
rescue_with_handler(e) || raise
|
161
136
|
false
|
@@ -6,6 +6,20 @@ module AnyCable
|
|
6
6
|
module SerializableIdentification
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
+
module ConnectionGID
|
10
|
+
def connection_identifier
|
11
|
+
unless defined? @connection_identifier
|
12
|
+
@connection_identifier = connection_gid identifiers.filter_map { |id| instance_variable_get(:"@#{id}") || __send__(id) }
|
13
|
+
end
|
14
|
+
|
15
|
+
@connection_identifier
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
included do
|
20
|
+
prepend ConnectionGID
|
21
|
+
end
|
22
|
+
|
9
23
|
class_methods do
|
10
24
|
def identified_by(*identifiers)
|
11
25
|
super
|
@@ -123,11 +123,12 @@ module AnyCable
|
|
123
123
|
|
124
124
|
initializer "anycable.verify_pool_sizes" do
|
125
125
|
next if AnyCable.config.disable_rpc_pool_size_warning?
|
126
|
-
# Skip if non-gRPC server is used
|
127
|
-
next unless AnyCable.config.respond_to?(:rpc_pool_size)
|
128
126
|
|
129
127
|
# Log current db vs. gRPC pool sizes
|
130
128
|
AnyCable.configure_server do
|
129
|
+
# Skip if non-gRPC server is used
|
130
|
+
next unless AnyCable.config.respond_to?(:rpc_pool_size)
|
131
|
+
|
131
132
|
ActiveSupport.on_load(:active_record) do
|
132
133
|
db_pool_size = ::ActiveRecord::Base.connection_pool.size
|
133
134
|
rpc_pool_size = AnyCable.config.rpc_pool_size
|
data/lib/anycable/rails.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: anycable-rails-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0.rc.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: anycable-core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.6.0.rc.1
|
20
|
+
- - "<"
|
18
21
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
22
|
+
version: 1.7.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- - "
|
27
|
+
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
29
|
+
version: 1.6.0.rc.1
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.7.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: actioncable
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -77,6 +83,8 @@ files:
|
|
77
83
|
- lib/anycable/rails/action_cable_ext/channel.rb
|
78
84
|
- lib/anycable/rails/action_cable_ext/connection.rb
|
79
85
|
- lib/anycable/rails/action_cable_ext/remote_connections.rb
|
86
|
+
- lib/anycable/rails/channel.rb
|
87
|
+
- lib/anycable/rails/channel/presence.rb
|
80
88
|
- lib/anycable/rails/channel_state.rb
|
81
89
|
- lib/anycable/rails/compatibility.rb
|
82
90
|
- lib/anycable/rails/compatibility/rubocop.rb
|
@@ -131,7 +139,7 @@ metadata:
|
|
131
139
|
homepage_uri: https://anycable.io/
|
132
140
|
source_code_uri: http://github.com/anycable/anycable-rails
|
133
141
|
funding_uri: https://github.com/sponsors/anycable
|
134
|
-
post_install_message:
|
142
|
+
post_install_message:
|
135
143
|
rdoc_options: []
|
136
144
|
require_paths:
|
137
145
|
- lib
|
@@ -142,12 +150,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
150
|
version: '2.7'
|
143
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
152
|
requirements:
|
145
|
-
- - "
|
153
|
+
- - ">"
|
146
154
|
- !ruby/object:Gem::Version
|
147
|
-
version:
|
155
|
+
version: 1.3.1
|
148
156
|
requirements: []
|
149
157
|
rubygems_version: 3.4.19
|
150
|
-
signing_key:
|
158
|
+
signing_key:
|
151
159
|
specification_version: 4
|
152
160
|
summary: AnyCable integration for Rails (w/o RPC dependencies)
|
153
161
|
test_files: []
|