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.
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotsock
4
+ module Turbo
5
+ VERSION = "0.1.0"
6
+ end
7
+ 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: []