From be94e67eb173ab089aab461e9469c6180c6eb06a Mon Sep 17 00:00:00 2001 From: Kirill Platonov Date: Sat, 21 Dec 2024 23:43:07 +0100 Subject: [PATCH] Use separate ActionCable server to prevent disconnects --- app/assets/javascripts/hotwire-livereload.js | 101 +++++++++---------- app/javascript/hotwire-livereload.js | 15 +-- lib/hotwire/livereload.rb | 2 + lib/hotwire/livereload/cable_server.rb | 11 ++ lib/hotwire/livereload/engine.rb | 14 ++- 5 files changed, 75 insertions(+), 68 deletions(-) create mode 100644 lib/hotwire/livereload/cable_server.rb diff --git a/app/assets/javascripts/hotwire-livereload.js b/app/assets/javascripts/hotwire-livereload.js index 6c720e0..b991d26 100644 --- a/app/assets/javascripts/hotwire-livereload.js +++ b/app/assets/javascripts/hotwire-livereload.js @@ -389,18 +389,18 @@ this.subscriptions = subscriptions; this.pendingSubscriptions = []; } - guarantee(subscription2) { - if (this.pendingSubscriptions.indexOf(subscription2) == -1) { - logger.log(`SubscriptionGuarantor guaranteeing ${subscription2.identifier}`); - this.pendingSubscriptions.push(subscription2); + guarantee(subscription) { + if (this.pendingSubscriptions.indexOf(subscription) == -1) { + logger.log(`SubscriptionGuarantor guaranteeing ${subscription.identifier}`); + this.pendingSubscriptions.push(subscription); } else { - logger.log(`SubscriptionGuarantor already guaranteeing ${subscription2.identifier}`); + logger.log(`SubscriptionGuarantor already guaranteeing ${subscription.identifier}`); } this.startGuaranteeing(); } - forget(subscription2) { - logger.log(`SubscriptionGuarantor forgetting ${subscription2.identifier}`); - this.pendingSubscriptions = this.pendingSubscriptions.filter((s) => s !== subscription2); + forget(subscription) { + logger.log(`SubscriptionGuarantor forgetting ${subscription.identifier}`); + this.pendingSubscriptions = this.pendingSubscriptions.filter((s) => s !== subscription); } startGuaranteeing() { this.stopGuaranteeing(); @@ -412,9 +412,9 @@ retrySubscribing() { this.retryTimeout = setTimeout(() => { if (this.subscriptions && typeof this.subscriptions.subscribe === "function") { - this.pendingSubscriptions.map((subscription2) => { - logger.log(`SubscriptionGuarantor resubscribing ${subscription2.identifier}`); - this.subscriptions.subscribe(subscription2); + this.pendingSubscriptions.map((subscription) => { + logger.log(`SubscriptionGuarantor resubscribing ${subscription.identifier}`); + this.subscriptions.subscribe(subscription); }); } }, 500); @@ -431,64 +431,64 @@ const params = typeof channel === "object" ? channel : { channel }; - const subscription2 = new Subscription(this.consumer, params, mixin); - return this.add(subscription2); + const subscription = new Subscription(this.consumer, params, mixin); + return this.add(subscription); } - add(subscription2) { - this.subscriptions.push(subscription2); + add(subscription) { + this.subscriptions.push(subscription); this.consumer.ensureActiveConnection(); - this.notify(subscription2, "initialized"); - this.subscribe(subscription2); - return subscription2; - } - remove(subscription2) { - this.forget(subscription2); - if (!this.findAll(subscription2.identifier).length) { - this.sendCommand(subscription2, "unsubscribe"); + this.notify(subscription, "initialized"); + this.subscribe(subscription); + return subscription; + } + remove(subscription) { + this.forget(subscription); + if (!this.findAll(subscription.identifier).length) { + this.sendCommand(subscription, "unsubscribe"); } - return subscription2; + return subscription; } reject(identifier) { - return this.findAll(identifier).map((subscription2) => { - this.forget(subscription2); - this.notify(subscription2, "rejected"); - return subscription2; + return this.findAll(identifier).map((subscription) => { + this.forget(subscription); + this.notify(subscription, "rejected"); + return subscription; }); } - forget(subscription2) { - this.guarantor.forget(subscription2); - this.subscriptions = this.subscriptions.filter((s) => s !== subscription2); - return subscription2; + forget(subscription) { + this.guarantor.forget(subscription); + this.subscriptions = this.subscriptions.filter((s) => s !== subscription); + return subscription; } findAll(identifier) { return this.subscriptions.filter((s) => s.identifier === identifier); } reload() { - return this.subscriptions.map((subscription2) => this.subscribe(subscription2)); + return this.subscriptions.map((subscription) => this.subscribe(subscription)); } notifyAll(callbackName, ...args) { - return this.subscriptions.map((subscription2) => this.notify(subscription2, callbackName, ...args)); + return this.subscriptions.map((subscription) => this.notify(subscription, callbackName, ...args)); } - notify(subscription2, callbackName, ...args) { + notify(subscription, callbackName, ...args) { let subscriptions; - if (typeof subscription2 === "string") { - subscriptions = this.findAll(subscription2); + if (typeof subscription === "string") { + subscriptions = this.findAll(subscription); } else { - subscriptions = [subscription2]; + subscriptions = [subscription]; } - return subscriptions.map((subscription3) => typeof subscription3[callbackName] === "function" ? subscription3[callbackName](...args) : void 0); + return subscriptions.map((subscription2) => typeof subscription2[callbackName] === "function" ? subscription2[callbackName](...args) : void 0); } - subscribe(subscription2) { - if (this.sendCommand(subscription2, "subscribe")) { - this.guarantor.guarantee(subscription2); + subscribe(subscription) { + if (this.sendCommand(subscription, "subscribe")) { + this.guarantor.guarantee(subscription); } } confirmSubscription(identifier) { logger.log(`Subscription confirmed ${identifier}`); - this.findAll(identifier).map((subscription2) => this.guarantor.forget(subscription2)); + this.findAll(identifier).map((subscription) => this.guarantor.forget(subscription)); } - sendCommand(subscription2, command) { - const { identifier } = subscription2; + sendCommand(subscription, command) { + const { identifier } = subscription; return this.consumer.send({ command, identifier @@ -587,9 +587,8 @@ }, 300); // app/javascript/hotwire-livereload.js - var consumer = createConsumer(); - var subscription = null; - var createSubscription = () => consumer.subscriptions.create("Hotwire::Livereload::ReloadChannel", { + var consumer = createConsumer("/hotwire-livereload"); + consumer.subscriptions.create("Hotwire::Livereload::ReloadChannel", { received: hotwire_livereload_received_default, connected() { console.log("[Hotwire::Livereload] Websocket connected"); @@ -598,14 +597,8 @@ console.log("[Hotwire::Livereload] Websocket disconnected"); } }); - subscription = createSubscription(); document.addEventListener("turbo:load", () => { hotwire_livereload_scroll_position_default.restore(); hotwire_livereload_scroll_position_default.remove(); - if (subscription) { - consumer.subscriptions.remove(subscription); - subscription = null; - } - subscription = createSubscription(); }); })(); diff --git a/app/javascript/hotwire-livereload.js b/app/javascript/hotwire-livereload.js index efa289d..103ba2c 100644 --- a/app/javascript/hotwire-livereload.js +++ b/app/javascript/hotwire-livereload.js @@ -2,10 +2,8 @@ import { createConsumer } from "@rails/actioncable" import received from "./lib/hotwire-livereload-received" import scrollPosition from "./lib/hotwire-livereload-scroll-position" -const consumer = createConsumer() -let subscription = null - -const createSubscription = () => consumer.subscriptions.create("Hotwire::Livereload::ReloadChannel", { +const consumer = createConsumer("/hotwire-livereload") +consumer.subscriptions.create("Hotwire::Livereload::ReloadChannel", { received, connected() { @@ -17,16 +15,7 @@ const createSubscription = () => consumer.subscriptions.create("Hotwire::Liverel }, }) -subscription = createSubscription() - document.addEventListener("turbo:load", () => { scrollPosition.restore() scrollPosition.remove() - - if (subscription) { - consumer.subscriptions.remove(subscription) - subscription = null - } - subscription = createSubscription() }) - diff --git a/lib/hotwire/livereload.rb b/lib/hotwire/livereload.rb index 61aa73e..c11f7b6 100644 --- a/lib/hotwire/livereload.rb +++ b/lib/hotwire/livereload.rb @@ -1,3 +1,5 @@ +require "action_cable/server/base" +require "hotwire/livereload/cable_server" require "hotwire/livereload/engine" module Hotwire diff --git a/lib/hotwire/livereload/cable_server.rb b/lib/hotwire/livereload/cable_server.rb new file mode 100644 index 0000000..fd946c9 --- /dev/null +++ b/lib/hotwire/livereload/cable_server.rb @@ -0,0 +1,11 @@ +module Hotwire + module Livereload + class CableServer < ActionCable::Server::Base + def initialize + config = ::ActionCable::Server::Base.config.dup + config.connection_class = -> { ::ActionCable::Connection::Base } + super(config: config) + end + end + end +end diff --git a/lib/hotwire/livereload/engine.rb b/lib/hotwire/livereload/engine.rb index 27e2218..b6dca2e 100644 --- a/lib/hotwire/livereload/engine.rb +++ b/lib/hotwire/livereload/engine.rb @@ -19,6 +19,14 @@ class Engine < ::Rails::Engine config.hotwire_livereload.listen_options ||= {} config.hotwire_livereload.debounce_delay_ms = 0 + initializer "hotwire_livereload.routes" do + config.after_initialize do |app| + app.routes.prepend do + mount Hotwire::Livereload.cable_server => "/hotwire-livereload", :internal => true, :anchor => true + end + end + end + initializer "hotwire_livereload.assets" do if Rails.application.config.respond_to?(:assets) Rails.application.config.assets.precompile += %w[hotwire-livereload.js hotwire-livereload-turbo-stream.js] @@ -109,8 +117,12 @@ def self.turbo_stream(locals) ) end + def self.cable_server + @cable_server ||= Hotwire::Livereload::CableServer.new + end + def self.action_cable(opts) - ActionCable.server.broadcast("hotwire-reload", opts) + cable_server.broadcast("hotwire-reload", opts) end def self.server_process?