live_cable 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a08ab617cc974271c9f4b2784bb52ca53d1430942fd72e761fe52cd650a86141
4
- data.tar.gz: abd1b26ca97b149de015b9fb7b9b457ff6b6f21e224a61318788876115fcd9d9
3
+ metadata.gz: 9eab70af0621e69a52c7eaccce2d76a400a8627223c6aa91525230d262179782
4
+ data.tar.gz: 1a6ca70d53e728fc4893607bc5de7ab12094bbdefb06d52cd2a83afefd653ac5
5
5
  SHA512:
6
- metadata.gz: e3b932fcbdfcf3fb5d6b939320798eb151c82d0e6bc85c61c2452ff729233855b477752e10465c8098f4601651d04f9e67449293b9635cde3eefa47bde08733c
7
- data.tar.gz: 6476ba238c1960655922da93588b175ed0468294364dd1252b05ad044eb26cb0d94360254581cb791ddc6def81155940c82d310835398b7c791b8cb3daf6884c
6
+ metadata.gz: b43cf0bd548271aeae7534a26d1d950266078f3f571165fb99d5daec8021d1c8af16b29f2b71866aa6030594b5e829ecc59bcf4186b4209666472f81d3b7c9bf
7
+ data.tar.gz: 3d7753820b0ff173ae72a210eb4ecfbf77704e1897a2ebf570c085e7bad6ec1a7531bb7138beb64cf0fadd2af39ee77cf6be0f0e493961d770cd11182e2d3a43
data/CHANGELOG.md CHANGED
@@ -6,6 +6,104 @@ The Ruby gem (`live_cable`) and the npm package (`@isometriks/live_cable`) are
6
6
  released together and share a single version number. Entries below note which
7
7
  side of the pair a change affects when it isn't both.
8
8
 
9
+ ## 0.3.0 - 2026-09-26
10
+
11
+ ### Upgrading from 0.2
12
+
13
+ Upgrade the gem and the npm package together. The 0.3 client no longer sends
14
+ the `_csrf_token` field that the 0.2 gem checks on every message, so a newer
15
+ client talking to an older gem has every action refused.
16
+
17
+ If your `ApplicationCable::Connection` follows the 0.2 installation guide,
18
+ delete both LiveCable lines from it. LiveCable now attaches `live_connection`
19
+ to every connection itself.
20
+
21
+ ```ruby
22
+ # app/channels/application_cable/connection.rb - delete these
23
+ identified_by :live_connection
24
+
25
+ def connect
26
+ self.live_connection = LiveCable::Connection.new(request)
27
+ end
28
+ ```
29
+
30
+ Delete them together. Without `identified_by` there is no `live_connection=`
31
+ for `connect` to call and every handshake fails. Without the `connect` override
32
+ the identifier shadows the `live_connection` LiveCable attaches with a nil one,
33
+ and every subscribe fails with a `LiveCable::Error` that says so. Leaving both
34
+ in place keeps working, at the cost described under *Changed*.
35
+
36
+ ### Fixed
37
+
38
+ - **A message could be refused with no answer, leaving the component stuck in
39
+ its loading state.** `LiveChannel#receive` had no rescue of its own, so
40
+ anything raised before an action's own rescue — a component that failed to
41
+ subscribe, the CSRF check — went to ActionCable, which only logs it. The
42
+ client holds `live-loading` and `live-disable-with` until it hears back, so
43
+ the button stayed disabled until a reload. Every batch is now answered with a
44
+ `_refresh`, `_ack` or `_error`. A subscribe that fails at any point transmits
45
+ an `_error` too, and the client's unsubscribe then removes the half-built
46
+ component from the connection rather than leaving it for the next subscribe
47
+ to resurrect (gem).
48
+
49
+ ### Changed
50
+
51
+ - `live_connection` is attached to every `ActionCable::Connection` by LiveCable
52
+ itself, so the connection no longer declares it. As an identifier, the
53
+ per-socket `LiveCable::Connection` became part of the connection's identity,
54
+ which `ActionCable.server.remote_connections` has to match in full, so an
55
+ application could never disconnect its users' sockets on sign-out. See
56
+ *Upgrading from 0.2* above (gem).
57
+
58
+ ### Removed
59
+
60
+ - The per-message CSRF token check, and with it the `_csrf_token` field the
61
+ client sent with every batch. A WebSocket is protected at its handshake by
62
+ ActionCable's origin check and the `SameSite=Lax` session cookie, and the
63
+ check added nothing on top of those except a way to wedge a socket: it ran
64
+ against the session captured at the handshake, and a socket never sees
65
+ cookies set afterwards, so once the session's token rotated — Devise does
66
+ this on every sign-in — every message on that socket was refused until the
67
+ socket reconnected. A socket that outlives a sign-in is handled as for any
68
+ other channel, by the application disconnecting it; see the architecture
69
+ guide (gem + npm).
70
+
71
+ ## 0.2.1 - 2026-08-13
72
+
73
+ ### Removed
74
+
75
+ - `Component#channel_name` and `Connection#channel_name`. A component no longer
76
+ subscribes to a stream of its own now that payloads are written straight to its
77
+ channel, so the name identified a stream nothing published to or read from.
78
+ Subscribing to external streams with `stream_from` is unaffected (gem).
79
+
80
+ ### Fixed
81
+
82
+ - **The opening payload from `LiveChannel#subscribed` could be dropped.**
83
+ Payloads were published through the pubsub adapter, but ActionCable registers
84
+ `stream_from` asynchronously — so the initial render could be published before
85
+ the subscription it targets existed, and pubsub delivers only to subscribers
86
+ present at that moment, with no buffering or retry. The component was left
87
+ showing `data-live-status-value="disconnected"`, with no cached render on the
88
+ client for a Turbo reattach to replay, until some later action happened to
89
+ produce a refresh. When a subscribe render raised, the `_error` payload was
90
+ lost outright and the failure never surfaced in the browser at all. A
91
+ component's stream belongs to exactly one connection, so payloads are now
92
+ written straight to that connection rather than published, which removes the
93
+ race along with a pubsub round trip (gem).
94
+ - **Components not reattaching after a Turbo Drive navigation.** Navigating to a
95
+ page containing a component that is already subscribed deliberately keeps the
96
+ existing subscription, so `LiveChannel#subscribed` does not run again and the
97
+ server sends nothing. The newly rendered element was left with its
98
+ server-rendered status of `disconnected` and never received the component's
99
+ current state. A reconnecting controller now re-syncs the subscription's status
100
+ and replays the last render into the new element (npm).
101
+ - Building DOM from a server render used iterator helpers
102
+ (`childNodes.values().find(...)`), an ES2025 feature, which raised
103
+ `TypeError: ... .find is not a function` on runtimes that do not implement them
104
+ — Node 20 and earlier, and browsers older than Chrome 122 / Firefox 131 /
105
+ Safari 18.4. Rewritten with `Array.from` (npm).
106
+
9
107
  ## 0.2.0
10
108
 
11
109
  The gem and npm package versions are realigned in this release. The npm package
data/README.md CHANGED
@@ -30,21 +30,19 @@ bundle install
30
30
 
31
31
  ## Configuration
32
32
 
33
- To use LiveCable, you need to set up your `ApplicationCable::Connection` to initialize a `LiveCable::Connection`.
34
-
35
- Add this to your `app/channels/application_cable/connection.rb`:
36
-
37
- ```ruby
38
- module ApplicationCable
39
- class Connection < ActionCable::Connection::Base
40
- identified_by :live_connection
41
-
42
- def connect
43
- self.live_connection = LiveCable::Connection.new(self.request)
44
- end
45
- end
46
- end
47
- ```
33
+ There is nothing to configure on the cable side. LiveCable attaches itself to
34
+ `ActionCable::Connection::Base`, so your `ApplicationCable::Connection` stays as
35
+ it is, with whatever `identified_by` it declares or none at all. LiveCable needs
36
+ no identifiers of its own.
37
+
38
+ If you followed an earlier version of this guide, remove the
39
+ `identified_by :live_connection` line and the `connect` override that went with
40
+ it. Remove both at once, since either one on its own breaks every connection.
41
+ Together they keep working, but they make a per-socket object part of the
42
+ connection's identity, which stops `ActionCable.server.remote_connections` from
43
+ ever finding your users' sockets. See the
44
+ [architecture guide](https://livecable.io/guide/architecture#sign-in-and-sign-out)
45
+ for what to do about sockets when a user signs in or out.
48
46
 
49
47
  ## JavaScript Setup
50
48
 
@@ -166,20 +166,17 @@ export default class extends Controller {
166
166
  }
167
167
  }
168
168
 
169
+ // Batch any pending debounced messages ahead of this one so they are sent
170
+ // in the order they were triggered
169
171
  #flushDebounced(message) {
170
172
  const messages = [message]
171
173
 
172
- // Add all pending debounced messages to be sent immediately
173
174
  for (const [source, { timeout, message: debouncedMessage }] of this.#debounces) {
174
175
  clearTimeout(timeout)
175
176
  messages.unshift(debouncedMessage)
176
177
  }
177
178
  this.#debounces.clear()
178
179
 
179
- return { messages, _csrf_token: this.#csrfToken }
180
- }
181
-
182
- get #csrfToken() {
183
- return document.querySelector("meta[name='csrf-token']")?.getAttribute("content")
180
+ return { messages }
184
181
  }
185
182
  }
@@ -34,8 +34,7 @@ function createDOMFromHTML(html) {
34
34
  template.innerHTML = html
35
35
 
36
36
  // Find a node that isn't a comment
37
- const node = template.content.childNodes
38
- .values()
37
+ const node = Array.from(template.content.childNodes)
39
38
  .find(n => n.nodeName !== '#comment')
40
39
 
41
40
  if (node) {
@@ -230,6 +229,15 @@ class ComponentState {
230
229
  return this.#element
231
230
  }
232
231
 
232
+ /**
233
+ * Whether a render has been received and cached, and can therefore be
234
+ * replayed into a freshly attached element.
235
+ * @returns {boolean}
236
+ */
237
+ get hasRender() {
238
+ return this.#lastTemplate !== null && Boolean(this.#partsByTemplate[this.#lastTemplate])
239
+ }
240
+
233
241
  /**
234
242
  * Create a DOM element from stored state.
235
243
  * @param {Object} refresh - Optional refresh data to update state
@@ -345,12 +353,44 @@ class Subscription {
345
353
 
346
354
  /**
347
355
  * Update the controller reference.
348
- * Called when a Stimulus controller reconnects to an existing subscription.
356
+ * Called when a Stimulus controller reconnects to an existing subscription —
357
+ * most importantly after a Turbo Drive navigation to a page that contains the
358
+ * same component, where prune() deliberately keeps the subscription alive.
359
+ *
360
+ * In that case the ActionCable subscription is never recreated, so
361
+ * LiveChannel#subscribed does not run again and the server sends nothing.
362
+ * Without re-syncing here, the newly rendered element keeps the
363
+ * server-rendered status of "disconnected" forever and never receives the
364
+ * component's current state.
349
365
  *
350
366
  * @param {Object} controller - Stimulus controller instance
351
367
  */
352
368
  set controller(controller) {
369
+ const previous = this.#controller
370
+
353
371
  this.#controller = controller
372
+ this.#componentState.element = controller.element
373
+
374
+ if (previous && previous !== controller) {
375
+ this.#reattach()
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Bring a freshly connected controller up to date with state this
381
+ * subscription already holds.
382
+ * @private
383
+ */
384
+ #reattach() {
385
+ if (this.#currentStatus) {
386
+ this.#controller.statusValue = this.#currentStatus
387
+ }
388
+
389
+ // Replay the last render into the new element. The server-side component
390
+ // is the same instance, so the cached parts are its current state.
391
+ if (this.#componentState.hasRender) {
392
+ this.#handleRefresh(null)
393
+ }
354
394
  }
355
395
 
356
396
  /**
@@ -1,37 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class LiveChannel < ActionCable::Channel::Base
4
- def subscribed
5
- instance = nil
4
+ # Private so ActionCable does not expose it as an action the client can call
5
+ delegate :live_connection, to: :connection, private: true
6
+
7
+ before_subscribe :ensure_live_connection
6
8
 
7
- # Build live_id from component and id params
9
+ # @component is assigned as soon as the component is known, so a failure
10
+ # later in here still leaves it for unsubscribed to clean up
11
+ def subscribed
8
12
  live_id = "#{params[:component]}/#{params[:id]}"
9
13
 
10
- instance = live_connection.get_component(live_id)
11
- rendered = instance.present?
14
+ @component = live_connection.get_component(live_id)
15
+ rendered = component.present?
12
16
 
13
- unless instance
14
- instance = LiveCable.instance_from_string(params[:component], params[:id])
15
- live_connection.add_component(instance)
16
- instance.defaults = params[:defaults]
17
- instance.apply_defaults
17
+ unless component
18
+ @component = LiveCable.instance_from_string(params[:component], params[:id])
19
+ live_connection.add_component(component)
20
+ component.defaults = params[:defaults]
21
+ component.apply_defaults
18
22
  end
19
23
 
20
- instance.connect(self)
24
+ component.connect(self)
21
25
 
22
26
  if rendered
23
- instance.broadcast_subscribe
27
+ component.broadcast_subscribe
24
28
  else
25
- instance.broadcast_render
29
+ component.broadcast_render
26
30
  end
27
-
28
- @component = instance
29
31
  rescue StandardError => error
30
- live_connection.handle_error(instance, error) if instance
32
+ live_connection.handle_error(component, error, channel: self)
31
33
  end
32
34
 
35
+ # Every batch must be answered - the client holds its loading state until a
36
+ # _refresh, _ack or _error arrives - so nothing raised here may escape to
37
+ # ActionCable, which would only log it and leave the client hanging.
33
38
  def receive(data)
39
+ raise LiveCable::Error, 'No component was built, so this subscription cannot receive messages' unless component
40
+
34
41
  live_connection.receive(component, data)
42
+ rescue StandardError => error
43
+ live_connection.handle_error(component, error, channel: self)
35
44
  end
36
45
 
37
46
  def unsubscribed
@@ -41,8 +50,22 @@ class LiveChannel < ActionCable::Channel::Base
41
50
  @component = nil
42
51
  end
43
52
 
53
+ # Exposes #transmit, which ActionCable keeps private to the channel.
54
+ def broadcast(data)
55
+ transmit(data)
56
+ end
57
+
44
58
  private
45
59
 
46
60
  # @return [LiveCable::Component, nil]
47
61
  attr_reader :component
62
+
63
+ def ensure_live_connection
64
+ return if live_connection
65
+
66
+ raise LiveCable::Error, "#{connection.class.name} still declares identified_by :live_connection, " \
67
+ 'which shadows the live_connection LiveCable attaches and leaves it nil. ' \
68
+ 'Remove that line and the connect override that set it; see the 0.3.0 ' \
69
+ 'upgrade notes.'
70
+ end
48
71
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiveCable
4
+ # Mixed into ActionCable::Connection::Base by the engine, so every socket
5
+ # carries its LiveCable::Connection without the application declaring
6
+ # anything.
7
+ #
8
+ # Deliberately not an identifier. The identified_by values make up a
9
+ # connection's identity, which remote_connections.where(...) has to match in
10
+ # full to disconnect a socket, and a per-socket object can never be named
11
+ # there. Keeping LiveCable off the identifiers leaves that mechanism working
12
+ # for the application's own, such as current_user.
13
+ module ActionCableConnection
14
+ # @return [LiveCable::Connection]
15
+ def live_connection
16
+ @live_connection ||= LiveCable::Connection.new(request)
17
+ end
18
+ end
19
+ end
@@ -5,8 +5,10 @@ module LiveCable
5
5
  module Broadcasting
6
6
  extend ActiveSupport::Concern
7
7
 
8
+ # Nil until the component's own controller subscribes; children rendered
9
+ # by a parent have no channel to deliver to yet.
8
10
  def broadcast(data)
9
- ActionCable.server.broadcast(channel_name, data)
11
+ channel&.broadcast(data)
10
12
  end
11
13
 
12
14
  def broadcast_subscribe
@@ -22,10 +22,6 @@ module LiveCable
22
22
  def live_id
23
23
  self.class.component_id(id)
24
24
  end
25
-
26
- def channel_name
27
- "#{live_connection.channel_name}/#{live_id}"
28
- end
29
25
  end
30
26
  end
31
27
  end
@@ -23,7 +23,6 @@ module LiveCable
23
23
  def connect(channel)
24
24
  run_callbacks :connect do
25
25
  @channel = channel
26
- start_stream
27
26
  @subscribed = true
28
27
  end
29
28
  end
@@ -7,14 +7,7 @@ module LiveCable
7
7
 
8
8
  private
9
9
 
10
- def start_stream
11
- channel.stream_from(channel_name)
12
- end
13
-
14
10
  def stop_stream
15
- channel.stop_stream_from(channel_name)
16
-
17
- # Stop all additional streams started via stream_from
18
11
  additional_streams.each do |stream_name|
19
12
  channel.stop_stream_from(stream_name)
20
13
  end
@@ -5,11 +5,35 @@ module LiveCable
5
5
  module ErrorHandling
6
6
  extend ActiveSupport::Concern
7
7
 
8
- def handle_error(component, error)
8
+ # Report an error and replace the component on the client with an
9
+ # error box.
10
+ #
11
+ # @param component [LiveCable::Component, nil] nil when the failure
12
+ # happened before a component existed, such as a subscribe that could
13
+ # not build one
14
+ # @param error [Exception]
15
+ # @param channel [#broadcast, nil] the channel to deliver the _error
16
+ # through; needed when the component failed before it connected to
17
+ # one, or when there is no component at all
18
+ def handle_error(component, error, channel: nil)
9
19
  Rails.error.report(error)
10
20
 
21
+ html = error_html(component, error)
22
+
23
+ # Destroy children first so their _status:destroy messages arrive before _error
24
+ component&.rendered_children&.each(&:destroy)
25
+
26
+ # Broadcast the error - JS replaces the DOM and calls unsubscribe(),
27
+ # which triggers LiveChannel#unsubscribed -> component.disconnect for server cleanup
28
+ (channel || component)&.broadcast(_error: html)
29
+ end
30
+
31
+ private
32
+
33
+ def error_html(component, error)
11
34
  if LiveCable.configuration.verbose_errors
12
- summary = "#{component.class.name} - #{error.class.name}: #{ERB::Util.html_escape(error.message)}"
35
+ name = component ? component.class.name : 'LiveCable'
36
+ summary = "#{name} - #{error.class.name}: #{ERB::Util.html_escape(error.message)}"
13
37
  backtrace_html = <<~HTML
14
38
  <small>
15
39
  <ol>
@@ -21,19 +45,12 @@ module LiveCable
21
45
  summary = 'An error occurred'
22
46
  end
23
47
 
24
- html = <<~HTML
48
+ <<~HTML
25
49
  <details>
26
50
  <summary style="color: #f00; cursor: pointer">#{summary}</summary>
27
51
  #{backtrace_html}
28
52
  </details>
29
53
  HTML
30
-
31
- # Destroy children first so their _status:destroy messages arrive before _error
32
- component.rendered_children.each(&:destroy)
33
-
34
- # Broadcast the error - JS replaces the DOM and calls unsubscribe(),
35
- # which triggers LiveChannel#unsubscribed -> component.disconnect for server cleanup
36
- component.broadcast(_error: html)
37
54
  end
38
55
  end
39
56
  end
@@ -6,7 +6,6 @@ module LiveCable
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  def receive(component, data)
9
- check_csrf_token(data)
10
9
  reset_changeset
11
10
 
12
11
  return unless data['messages'].present?
@@ -72,20 +71,6 @@ module LiveCable
72
71
 
73
72
  private
74
73
 
75
- def check_csrf_token(data)
76
- session = request.session
77
- return unless session[:_csrf_token]
78
-
79
- token = data['_csrf_token']
80
- unless csrf_checker.valid?(session, token)
81
- raise LiveCable::Error, 'Invalid CSRF token'
82
- end
83
- end
84
-
85
- def csrf_checker
86
- @csrf_checker ||= LiveCable::CsrfChecker.new(request)
87
- end
88
-
89
74
  def parse_params(data)
90
75
  params = data['params'] || ''
91
76
 
@@ -3,7 +3,6 @@
3
3
  module LiveCable
4
4
  class Connection
5
5
  include ComponentManagement
6
- include ChannelManagement
7
6
  include StateManagement
8
7
  include Messaging
9
8
  include Broadcasting
@@ -13,17 +12,12 @@ module LiveCable
13
12
 
14
13
  def initialize(request)
15
14
  @request = request
16
- @session_id = SecureRandom.uuid
17
15
  @containers = Hash.new { |hash, key| hash[key] = Container.new }
18
16
  @components = {}
19
- @channels = {}
20
17
  end
21
18
 
22
19
  private
23
20
 
24
- # @return [String]
25
- attr_reader :session_id
26
-
27
21
  # @return [ActionDispatch::Request]
28
22
  attr_reader :request
29
23
 
@@ -26,6 +26,12 @@ module LiveCable
26
26
  ActionView::Template.register_template_handler(:'live.erb', Rendering::Handler)
27
27
  end
28
28
 
29
+ initializer 'live_cable.action_cable' do
30
+ ActiveSupport.on_load(:action_cable_connection) do
31
+ include LiveCable::ActionCableConnection
32
+ end
33
+ end
34
+
29
35
  initializer 'live_cable.active_record' do
30
36
  ActiveSupport.on_load :active_record do
31
37
  include ModelObserver
@@ -3,19 +3,28 @@
3
3
  module LiveCable
4
4
  module Testing
5
5
  # Stand-in for an ActionCable channel. Records streams started via
6
- # stream_from so tests can trigger their callbacks with receive_stream.
6
+ # stream_from so tests can trigger their callbacks with receive_stream,
7
+ # and payloads sent by components.
7
8
  class TestChannel
8
9
  # @return [Hash<String, Hash>] Stream name => { coder:, callback: }
9
10
  attr_reader :streams
10
11
 
12
+ # @return [Array<Hash>] Payloads sent by components through this channel
13
+ attr_reader :transmissions
14
+
11
15
  # @return [LiveCable::Testing::TestCableConnection]
12
16
  attr_reader :connection
13
17
 
14
18
  def initialize(identifiers = {})
15
19
  @streams = {}
20
+ @transmissions = []
16
21
  @connection = TestCableConnection.new(identifiers)
17
22
  end
18
23
 
24
+ def broadcast(data)
25
+ @transmissions << data
26
+ end
27
+
19
28
  def stream_from(name, coder: nil, &block)
20
29
  @streams[name] = { coder:, callback: block }
21
30
  end
@@ -18,9 +18,6 @@ module LiveCable
18
18
  super(component)
19
19
  @connection = connection
20
20
  @channel = channel
21
- @broadcasts = []
22
-
23
- capture_broadcasts(component)
24
21
  end
25
22
 
26
23
  # @return [LiveCable::Component] The underlying component instance
@@ -74,15 +71,15 @@ module LiveCable
74
71
  # (e.g. :_refresh, :_ack, :_error, :_status)
75
72
  # @return [Array<Hash>]
76
73
  def broadcasts(key = nil)
77
- return @broadcasts.dup unless key
74
+ return channel.transmissions.dup unless key
78
75
 
79
- @broadcasts.select { |broadcast| broadcast.key?(key) }
76
+ channel.transmissions.select { |broadcast| broadcast.key?(key) }
80
77
  end
81
78
 
82
79
  # Forget previously captured broadcasts. Useful after mounting, to
83
80
  # assert on the effects of a single action.
84
81
  def clear_broadcasts
85
- @broadcasts.clear
82
+ channel.transmissions.clear
86
83
  end
87
84
 
88
85
  # All events the component has dispatched via dispatch_event, in
@@ -134,14 +131,6 @@ module LiveCable
134
131
  def receive_message(message)
135
132
  connection.receive(component, { 'messages' => [message] })
136
133
  end
137
-
138
- def capture_broadcasts(component)
139
- captured = @broadcasts
140
-
141
- component.define_singleton_method(:broadcast) do |data|
142
- captured << data
143
- end
144
- end
145
134
  end
146
135
  end
147
136
  end
@@ -68,12 +68,11 @@ module LiveCable
68
68
  def build_test_connection(raise_errors:)
69
69
  require 'action_dispatch/testing/test_request'
70
70
 
71
- # An empty session skips the CSRF check, like a session-less request
72
- request = ActionDispatch::TestRequest.create('rack.session' => {})
71
+ request = ActionDispatch::TestRequest.create
73
72
  connection = LiveCable::Connection.new(request)
74
73
 
75
74
  if raise_errors
76
- def connection.handle_error(_component, error)
75
+ def connection.handle_error(_component, error, **)
77
76
  raise error
78
77
  end
79
78
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LiveCable
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: live_cable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig Blanchette
@@ -133,6 +133,7 @@ files:
133
133
  - lib/generators/live_cable/component/templates/view.html.live.erb.tt
134
134
  - lib/live.rb
135
135
  - lib/live_cable.rb
136
+ - lib/live_cable/action_cable_connection.rb
136
137
  - lib/live_cable/component.rb
137
138
  - lib/live_cable/component/broadcasting.rb
138
139
  - lib/live_cable/component/events.rb
@@ -145,13 +146,11 @@ files:
145
146
  - lib/live_cable/configuration.rb
146
147
  - lib/live_cable/connection.rb
147
148
  - lib/live_cable/connection/broadcasting.rb
148
- - lib/live_cable/connection/channel_management.rb
149
149
  - lib/live_cable/connection/component_management.rb
150
150
  - lib/live_cable/connection/error_handling.rb
151
151
  - lib/live_cable/connection/messaging.rb
152
152
  - lib/live_cable/connection/state_management.rb
153
153
  - lib/live_cable/container.rb
154
- - lib/live_cable/csrf_checker.rb
155
154
  - lib/live_cable/delegation/array.rb
156
155
  - lib/live_cable/delegation/hash.rb
157
156
  - lib/live_cable/delegation/methods.rb
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LiveCable
4
- class Connection
5
- module ChannelManagement
6
- extend ActiveSupport::Concern
7
-
8
- def channel_name
9
- "live_#{session_id}"
10
- end
11
- end
12
- end
13
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LiveCable
4
- class CsrfChecker
5
- include ActiveSupport::Configurable
6
- include ActionController::RequestForgeryProtection
7
-
8
- def initialize(request)
9
- @request = request
10
- end
11
-
12
- def valid?(session, token)
13
- valid_authenticity_token?(session, token)
14
- end
15
-
16
- private
17
-
18
- attr_reader :request
19
- end
20
- end