hotsock-turbo 0.1.0
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 +7 -0
- data/.standard.yml +1 -0
- data/Gemfile +16 -0
- data/README.md +323 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/hotsock-turbo.js +142 -0
- data/app/controllers/hotsock/turbo/tokens_controller.rb +21 -0
- data/app/jobs/hotsock/turbo/action_broadcast_job.rb +23 -0
- data/app/jobs/hotsock/turbo/broadcast_job.rb +16 -0
- data/app/jobs/hotsock/turbo/broadcast_refresh_job.rb +15 -0
- data/config/routes.rb +5 -0
- data/hotsock-turbo.gemspec +33 -0
- data/lib/hotsock/turbo/broadcastable.rb +518 -0
- data/lib/hotsock/turbo/config.rb +17 -0
- data/lib/hotsock/turbo/engine.rb +33 -0
- data/lib/hotsock/turbo/streams_channel.rb +228 -0
- data/lib/hotsock/turbo/streams_helper.rb +47 -0
- data/lib/hotsock/turbo/version.rb +7 -0
- data/lib/hotsock-turbo.rb +23 -0
- metadata +78 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hotsock
|
|
4
|
+
module Turbo
|
|
5
|
+
class StreamsChannel
|
|
6
|
+
def self.enabled?
|
|
7
|
+
defined?(::Turbo::StreamsChannel)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.turbo_stream_action_tag(
|
|
11
|
+
action,
|
|
12
|
+
target: nil,
|
|
13
|
+
targets: nil,
|
|
14
|
+
template: nil,
|
|
15
|
+
**attributes
|
|
16
|
+
)
|
|
17
|
+
target_attr = target ? %(target="#{ERB::Util.html_escape(target)}") : nil
|
|
18
|
+
targets_attr = targets ? %(targets="#{ERB::Util.html_escape(targets)}") : nil
|
|
19
|
+
attrs = [target_attr, targets_attr]
|
|
20
|
+
attributes.each { |k, v| attrs << %(#{k}="#{ERB::Util.html_escape(v)}") }
|
|
21
|
+
attrs_str = attrs.compact.join(" ")
|
|
22
|
+
inner = template || ""
|
|
23
|
+
%(<turbo-stream action="#{ERB::Util.html_escape(action)}" #{attrs_str}><template>#{inner}</template></turbo-stream>)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.render_broadcast_action(rendering)
|
|
27
|
+
rendering = rendering.dup
|
|
28
|
+
content = rendering.delete(:content)
|
|
29
|
+
html = rendering.delete(:html)
|
|
30
|
+
render = rendering.delete(:render)
|
|
31
|
+
|
|
32
|
+
return nil if render == false
|
|
33
|
+
return content if content
|
|
34
|
+
return html if html
|
|
35
|
+
return ApplicationController.render(formats: [:html], **rendering) if rendering.present?
|
|
36
|
+
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# This is designed this way to be consistent with Action Cable.
|
|
41
|
+
# We can extend this to allow for multiple channels on a single model in the future.
|
|
42
|
+
def broadcasting_for(model)
|
|
43
|
+
self.class.serialize_broadcasting(model)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.broadcast_action_to(
|
|
47
|
+
*streamables,
|
|
48
|
+
action:,
|
|
49
|
+
target: nil,
|
|
50
|
+
targets: nil,
|
|
51
|
+
attributes: {},
|
|
52
|
+
timestamp: nil,
|
|
53
|
+
**rendering
|
|
54
|
+
)
|
|
55
|
+
timestamp ||= Time.current.to_f
|
|
56
|
+
attributes = attributes.merge(timestamp: timestamp)
|
|
57
|
+
|
|
58
|
+
options = {
|
|
59
|
+
action: :"#{target}_#{action}",
|
|
60
|
+
timestamp: timestamp
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
broadcast_to(
|
|
64
|
+
*streamables,
|
|
65
|
+
turbo_stream_action_tag(
|
|
66
|
+
action,
|
|
67
|
+
target: target,
|
|
68
|
+
targets: targets,
|
|
69
|
+
template: render_broadcast_action(rendering),
|
|
70
|
+
**attributes
|
|
71
|
+
),
|
|
72
|
+
options
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.broadcast_to(stream_or_streamable, content, options = {})
|
|
77
|
+
unless enabled?
|
|
78
|
+
fail "Turbo Streams is not enabled. Please install turbo-rails to use this method."
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
channel = serialize_broadcasting(stream_or_streamable)
|
|
82
|
+
event = "turbo_stream"
|
|
83
|
+
data = {
|
|
84
|
+
html: content,
|
|
85
|
+
action: options[:action],
|
|
86
|
+
timestamp: options[:timestamp]
|
|
87
|
+
}.compact
|
|
88
|
+
|
|
89
|
+
Hotsock.publish_message(channel:, event:, data:)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.broadcast_append_to(stream, target:, partial:, locals: {}, timestamp: nil)
|
|
93
|
+
broadcast_action_to(
|
|
94
|
+
stream,
|
|
95
|
+
action: :append,
|
|
96
|
+
target: target,
|
|
97
|
+
partial: partial,
|
|
98
|
+
locals: locals,
|
|
99
|
+
timestamp: timestamp
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.broadcast_replace_to(stream, target:, partial:, locals: {}, timestamp: nil)
|
|
104
|
+
broadcast_action_to(
|
|
105
|
+
stream,
|
|
106
|
+
action: :replace,
|
|
107
|
+
target: target,
|
|
108
|
+
partial: partial,
|
|
109
|
+
locals: locals,
|
|
110
|
+
timestamp: timestamp
|
|
111
|
+
)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.broadcast_prepend_to(stream, target:, partial:, locals: {}, timestamp: nil)
|
|
115
|
+
broadcast_action_to(
|
|
116
|
+
stream,
|
|
117
|
+
action: :prepend,
|
|
118
|
+
target: target,
|
|
119
|
+
partial: partial,
|
|
120
|
+
locals: locals,
|
|
121
|
+
timestamp: timestamp
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.broadcast_remove_to(stream, target:)
|
|
126
|
+
html =
|
|
127
|
+
%(<turbo-stream action="remove" target="#{ERB::Util.html_escape(target)}"></turbo-stream>)
|
|
128
|
+
broadcast_to(stream, html)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.broadcast_refresh_to(*streamables, request_id: nil, **attributes)
|
|
132
|
+
broadcast_to(
|
|
133
|
+
*streamables,
|
|
134
|
+
turbo_stream_refresh_tag(request_id: request_id, **attributes),
|
|
135
|
+
{action: :refresh}
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.turbo_stream_refresh_tag(request_id: nil, **attributes)
|
|
140
|
+
attrs = attributes.dup
|
|
141
|
+
attrs[:"request-id"] = request_id if request_id.present?
|
|
142
|
+
attrs_str = attrs.map { |k, v| %(#{k}="#{ERB::Util.html_escape(v)}") }.join(" ")
|
|
143
|
+
attrs_str = " #{attrs_str}" if attrs_str.present?
|
|
144
|
+
%(<turbo-stream action="refresh"#{attrs_str}></turbo-stream>)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def self.broadcast_refresh_later_to(*streamables, request_id: nil)
|
|
148
|
+
stream_name = serialize_broadcasting(streamables)
|
|
149
|
+
Hotsock::Turbo::BroadcastRefreshJob.perform_later(stream_name, request_id: request_id)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def self.broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
|
|
153
|
+
stream_name = serialize_broadcasting(streamables)
|
|
154
|
+
Hotsock::Turbo::ActionBroadcastJob.perform_later(
|
|
155
|
+
stream_name,
|
|
156
|
+
action: action,
|
|
157
|
+
target: target,
|
|
158
|
+
targets: targets,
|
|
159
|
+
attributes: attributes,
|
|
160
|
+
**rendering
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def self.broadcast_replace_later_to(*streamables, **opts)
|
|
165
|
+
broadcast_action_later_to(*streamables, action: :replace, **opts)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def self.broadcast_update_later_to(*streamables, **opts)
|
|
169
|
+
broadcast_action_later_to(*streamables, action: :update, **opts)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def self.broadcast_append_later_to(*streamables, **opts)
|
|
173
|
+
broadcast_action_later_to(*streamables, action: :append, **opts)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def self.broadcast_prepend_later_to(*streamables, **opts)
|
|
177
|
+
broadcast_action_later_to(*streamables, action: :prepend, **opts)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def self.broadcast_before_later_to(*streamables, **opts)
|
|
181
|
+
broadcast_action_later_to(*streamables, action: :before, **opts)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def self.broadcast_after_later_to(*streamables, **opts)
|
|
185
|
+
broadcast_action_later_to(*streamables, action: :after, **opts)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def self.broadcast_render_to(*streamables, **rendering)
|
|
189
|
+
broadcast_to(
|
|
190
|
+
*streamables,
|
|
191
|
+
ApplicationController.render(formats: [:turbo_stream], **rendering),
|
|
192
|
+
{}
|
|
193
|
+
)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def self.broadcast_render_later_to(*streamables, **rendering)
|
|
197
|
+
stream_name = serialize_broadcasting(streamables)
|
|
198
|
+
Hotsock::Turbo::BroadcastJob.perform_later(stream_name, **rendering)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def self.broadcast_update_to(stream, target:, partial:, locals: {}, timestamp: nil)
|
|
202
|
+
broadcast_action_to(
|
|
203
|
+
stream,
|
|
204
|
+
action: :update,
|
|
205
|
+
target: target,
|
|
206
|
+
partial: partial,
|
|
207
|
+
locals: locals,
|
|
208
|
+
timestamp: timestamp
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Convert the method to a class method to be used by both instance and class methods
|
|
213
|
+
def self.serialize_broadcasting(streamable)
|
|
214
|
+
if streamable.is_a?(String)
|
|
215
|
+
streamable
|
|
216
|
+
elsif streamable.is_a?(Array)
|
|
217
|
+
streamable.map { |s| serialize_broadcasting(s) }.join(",")
|
|
218
|
+
elsif streamable.respond_to?(:to_gid_param)
|
|
219
|
+
streamable.to_gid_param
|
|
220
|
+
elsif streamable.respond_to?(:to_param)
|
|
221
|
+
streamable.to_param
|
|
222
|
+
else
|
|
223
|
+
fail ArgumentError, "Invalid streamable object: #{streamable.inspect}"
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hotsock
|
|
4
|
+
module Turbo
|
|
5
|
+
module StreamsHelper
|
|
6
|
+
def hotsock_turbo_meta_tags(connect_token_path: nil, wss_url: nil, log_level: nil)
|
|
7
|
+
config = Hotsock::Turbo.config
|
|
8
|
+
tags = [
|
|
9
|
+
tag(:meta, name: "hotsock:connect-token-path", content: connect_token_path || config.connect_token_path),
|
|
10
|
+
tag(:meta, name: "hotsock:log-level", content: log_level || config.log_level),
|
|
11
|
+
tag(:meta, name: "hotsock:wss-url", content: wss_url || config.wss_url)
|
|
12
|
+
]
|
|
13
|
+
safe_join(tags, "\n")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def hotsock_turbo_stream_from(*streamables, **attributes)
|
|
17
|
+
channel = Hotsock::Turbo::StreamsChannel.new
|
|
18
|
+
channel_name = channel.broadcasting_for(streamables)
|
|
19
|
+
token = create_subscription_token(channel_name)
|
|
20
|
+
set_attributes(attributes, token, channel_name)
|
|
21
|
+
|
|
22
|
+
tag.hotsock_turbo_stream_source(**attributes)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def create_subscription_token(channel_name)
|
|
28
|
+
Hotsock.issue_token(
|
|
29
|
+
scope: "subscribe",
|
|
30
|
+
channels: {channel_name => {omitSubCount: true, subscribe: true}},
|
|
31
|
+
uid:,
|
|
32
|
+
exp: 1.week.from_now.to_i
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def set_attributes(attributes, token, channel_name)
|
|
37
|
+
attributes[:"data-token"] = token
|
|
38
|
+
attributes[:"data-channel"] = channel_name
|
|
39
|
+
attributes[:"data-user-id"] = uid
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def uid
|
|
43
|
+
respond_to?(:hotsock_uid, true) ? hotsock_uid : session.id.to_s
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hotsock"
|
|
4
|
+
require "hotsock/turbo/version"
|
|
5
|
+
require "hotsock/turbo/config"
|
|
6
|
+
require "hotsock/turbo/streams_channel"
|
|
7
|
+
require "hotsock/turbo/streams_helper"
|
|
8
|
+
require "hotsock/turbo/broadcastable"
|
|
9
|
+
require "hotsock/turbo/engine" if defined?(Rails)
|
|
10
|
+
|
|
11
|
+
module Hotsock
|
|
12
|
+
module Turbo
|
|
13
|
+
class << self
|
|
14
|
+
def configure
|
|
15
|
+
yield config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config
|
|
19
|
+
@config ||= Config.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hotsock-turbo
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- James Miller
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: hotsock
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.0'
|
|
26
|
+
description: Provides Turbo Streams integration for Hotsock, enabling real-time updates
|
|
27
|
+
in Rails applications using Hotsock's WebSocket infrastructure.
|
|
28
|
+
email:
|
|
29
|
+
- support@hotsock.io
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- ".standard.yml"
|
|
35
|
+
- Gemfile
|
|
36
|
+
- README.md
|
|
37
|
+
- Rakefile
|
|
38
|
+
- app/assets/javascripts/hotsock-turbo.js
|
|
39
|
+
- app/controllers/hotsock/turbo/tokens_controller.rb
|
|
40
|
+
- app/jobs/hotsock/turbo/action_broadcast_job.rb
|
|
41
|
+
- app/jobs/hotsock/turbo/broadcast_job.rb
|
|
42
|
+
- app/jobs/hotsock/turbo/broadcast_refresh_job.rb
|
|
43
|
+
- config/routes.rb
|
|
44
|
+
- hotsock-turbo.gemspec
|
|
45
|
+
- lib/hotsock-turbo.rb
|
|
46
|
+
- lib/hotsock/turbo/broadcastable.rb
|
|
47
|
+
- lib/hotsock/turbo/config.rb
|
|
48
|
+
- lib/hotsock/turbo/engine.rb
|
|
49
|
+
- lib/hotsock/turbo/streams_channel.rb
|
|
50
|
+
- lib/hotsock/turbo/streams_helper.rb
|
|
51
|
+
- lib/hotsock/turbo/version.rb
|
|
52
|
+
homepage: https://www.hotsock.io
|
|
53
|
+
licenses:
|
|
54
|
+
- MIT
|
|
55
|
+
metadata:
|
|
56
|
+
homepage_uri: https://www.hotsock.io
|
|
57
|
+
bug_tracker_uri: https://github.com/hotsock/hotsock-turbo/issues
|
|
58
|
+
documentation_uri: https://github.com/hotsock/hotsock-turbo/blob/main/README.md
|
|
59
|
+
changelog_uri: https://github.com/hotsock/hotsock-turbo/releases
|
|
60
|
+
source_code_uri: https://github.com/hotsock/hotsock-turbo
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.2'
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 3.6.9
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: Turbo Streams integration for Hotsock
|
|
78
|
+
test_files: []
|