turbo-train 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 49c31938503f032280b36b7c2cb38d021a9a1fd9c14b685fbe37b59d3a03fc76
4
+ data.tar.gz: 12da15ef47ca0a4848fe247fe5e5529704583bb68a66868c21981a0e29b43535
5
+ SHA512:
6
+ metadata.gz: 7327f19a04e59f9877deea564bd9be9e6f2573e78ee2a3a8ff47857a39da613582fa5a6b2d4caeaf53ef3b487aa3995c9e3b022cd9a54b49a8dae5415d7d093b
7
+ data.tar.gz: dc36a1d51ab9463efd671f8b6f8dcf1dcb2a2a432fa00ac96fc184d9efa3566669f0ab5c863d833af8899a04b07e71c43ea735b2f230c420ea2308a4728fb221
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 goodsign
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ ![Build Status](https://github.com/Uscreen-video/turbo-train/actions/workflows/main.yml/badge.svg)
2
+
3
+ # Turbo::Train
4
+
5
+ <img align="right" width="160" title="Turbo::Train logo"
6
+ src="./logo.svg">
7
+
8
+ Real-time page updates for your Rails app over SSE with [Mercure](https://mercure.rocks) and [Hotwire Turbo](https://turbo.hotwired.dev/handbook/streams#integration-with-server-side-frameworks).
9
+
10
+ * **Uses [SSE](https://html.spec.whatwg.org/multipage/server-sent-events.html)**. No more websockets, client libraries, JS code and handling reconnects. Just an HTTP connection. Let the [browser](https://caniuse.com/eventsource) do the work.
11
+ * **Seamless Hotwire integration.** Use it exactly like [ActionCable](https://github.com/hotwired/turbo-rails#come-alive-with-turbo-streams). Drop-in replacement for `broadcast_action_to` and usual helpers.
12
+ * **Simple.** Get running in minutes, scale easily in production 🚀
13
+
14
+ ## Before your proceed
15
+
16
+ Using this gem requires some knowledge of ActionCable and broadcasting turbo streams. Turbo::Train is designed to mimic those, so it is highly recommended to first try the original to understand the concept.
17
+
18
+ You can start [here](https://hotwired.dev/) and proceed with the [Turbo Handbook](https://turbo.hotwired.dev/handbook/introduction). One of its chapters will be covering [Turbo Streams](https://turbo.hotwired.dev/handbook/streams). Specifically [this section](https://turbo.hotwired.dev/handbook/streams#integration-with-server-side-frameworks) would be the main prerequisite to understanding what this gem is about: it covers [Broadcastable](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb) and the overall idea of working with Mercure.
19
+
20
+ ## Prerequisites
21
+
22
+ 1. Rails 7+
23
+ 2. Mercure server (setup instructions below)
24
+
25
+ This should also work for Rails 6, but you will also need to install [turbo-rails](https://github.com/hotwired/turbo-rails#installation) manually before this gem.
26
+
27
+ ## Installation
28
+
29
+ ### Step 1. Turbo::Train
30
+
31
+ Instructions for Rails 7+
32
+
33
+ 1. Add the turbo-train gem to your Gemfile: `gem 'turbo-train'`
34
+ 2. Run `bundle install`
35
+ 3. Run `rails turbo_train:install`
36
+
37
+ Instructions for Rails 6
38
+
39
+ 1. Install [turbo-rails](https://github.com/hotwired/turbo-rails#installation)
40
+ 2. Repeat steps for Rails 7 above
41
+
42
+ ### Step 2. Mercure
43
+
44
+ Mercure is installed as a plugin to [Caddy](https://github.com/caddyserver/caddy) server. For mac users everything is pretty easy:
45
+
46
+ ```
47
+ brew install caddy
48
+ ```
49
+
50
+ ```
51
+ caddy add-package github.com/dunglas/mercure/caddy
52
+ ```
53
+
54
+ Now you are ready to run 🚀
55
+
56
+ ```
57
+ caddy run
58
+ ```
59
+
60
+
61
+ ## Usage
62
+
63
+ If you are familiar with broadcasting from ActionCable, usage would be extremely familiar:
64
+
65
+ ```
66
+ <%# app/views/chat_messages/index.html.erb %>
67
+ <%= turbo_train_from "chat_messages" %>
68
+
69
+ <div id="append_new_messages_here"></div>
70
+ ```
71
+
72
+ And then you can send portions of HTML from your Rails backend to deliver live to all currently open browsers:
73
+
74
+ ```
75
+ Turbo::Train.broadcast_action_to('chat_messages', action: :append, target:'append_new_messages_here', html: '<span>Test!</span>')
76
+ ```
77
+
78
+ or in real world you'd probably have something like
79
+
80
+ ```
81
+ # app/models/chat_message.rb
82
+ after_create_commit { Turbo::Train.broadcast_action_to('chat_messages', action: :append, target:'append_new_messages_here', partial: 'somepath/message') }
83
+ ```
84
+
85
+ You have the same options as original Rails Turbo helpers: rendering partials, pure html, [same actions](https://turbo.hotwired.dev/reference/streams).
86
+
87
+ ## Configuration
88
+
89
+ To specify different Mercure server settings, please adjust the generated `config/initializers/turbo_train.rb` file:
90
+
91
+ ```
92
+ Turbo::Train.configure do |config|
93
+ config.mercure_domain = ...
94
+ config.publisher_key = ...
95
+ config.subscriber_key = ...
96
+ end
97
+ ```
98
+
99
+ * Your SSE will connect to `https://#{configuration.mercure_domain}/.well-known`.
100
+ * The publisher/subscriber key correspond to the [configuration](https://mercure.rocks/docs/hub/config) or your Mercure server.
101
+
102
+ By default, these are set to `localhost`/`test`/`testing` to match the configuration of the local development server from the installation instructions above.
103
+
104
+ ***
105
+
106
+ ## License
107
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/setup"
2
+
3
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
4
+ load "rails/tasks/engine.rake"
5
+
6
+ load "rails/tasks/statistics.rake"
7
+
8
+ require "bundler/gem_tasks"
File without changes
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@uscreentv/turbo-train",
3
+ "version": "0.0.2",
4
+ "description": "",
5
+ "main": "turbo-train.js",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./turbo-train.js"
10
+ }
11
+ },
12
+ "scripts": {
13
+ "test": "echo \"Error: no test specified\" && exit 1"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/Uscreen-video/turbo-train.git"
18
+ },
19
+ "author": "",
20
+ "license": "MIT",
21
+ "bugs": {
22
+ "url": "https://github.com/Uscreen-video/turbo-train/issues"
23
+ },
24
+ "homepage": "https://github.com/Uscreen-video/turbo-train#readme",
25
+ "peerDependencies": {
26
+ "@hotwired/turbo-rails": ">=7.0.0"
27
+ }
28
+ }
@@ -0,0 +1,39 @@
1
+ import { Turbo } from "@hotwired/turbo-rails"
2
+
3
+ export default class TurboTrain extends HTMLElement {
4
+ static get observedAttributes() {
5
+ return [ 'href', 'session', 'name' ];
6
+ }
7
+
8
+ constructor() {
9
+ super();
10
+ }
11
+
12
+ connectedCallback() {
13
+ this.eventSource = new EventSource(`${this.href}/mercure?topic=${this.name}&authorization=${this.session}`);
14
+ Turbo.connectStreamSource(this.eventSource);
15
+ }
16
+
17
+ disconnectedCallback() {
18
+ Turbo.disconnectStreamSource(this.eventSource);
19
+ }
20
+
21
+ get href() {
22
+ return this.getAttribute('href');
23
+ }
24
+
25
+ get session() {
26
+ return this.getAttribute('session');
27
+ }
28
+
29
+ get name() {
30
+ return this.getAttribute('name');
31
+ }
32
+ }
33
+
34
+ if (
35
+ typeof window !== 'undefined' &&
36
+ !window.customElements.get('turbo-train-stream-source')
37
+ ) {
38
+ window.customElements.define('turbo-train-stream-source', TurboTrain)
39
+ }
@@ -0,0 +1 @@
1
+ import{Turbo as e}from"@hotwired/turbo-rails";export default class t extends HTMLElement{static get observedAttributes(){return["href","session","name"]}constructor(){super()}connectedCallback(){this.eventSource=new EventSource(`${this.href}/mercure?topic=${this.name}&authorization=${this.session}`),e.connectStreamSource(this.eventSource)}disconnectedCallback(){e.disconnectStreamSource(this.eventSource)}get href(){return this.getAttribute("href")}get session(){return this.getAttribute("session")}get name(){return this.getAttribute("name")}};"undefined"==typeof window||window.customElements.get("turbo-train-stream-source")||window.customElements.define("turbo-train-stream-source",t);
@@ -0,0 +1,8 @@
1
+ module Turbo
2
+ module Train
3
+ module Test
4
+ class ApplicationController < ActionController::Base
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Turbo::Train::StreamsHelper
2
+ def turbo_train_from(*streamables, **attributes)
3
+ attributes[:name] = Turbo::Train.signed_stream_name(streamables)
4
+ attributes[:session] = Turbo::Train.encode({ platform: "web" })
5
+ attributes[:href] = Turbo::Train.url
6
+ tag.turbo_train_stream_source(**attributes)
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Turbo
2
+ module Train
3
+ module Test
4
+ class ApplicationJob < ActiveJob::Base
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ module Turbo
2
+ module Train
3
+ module Test
4
+ class ApplicationMailer < ActionMailer::Base
5
+ default from: "from@example.com"
6
+ layout "mailer"
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Turbo
2
+ module Train
3
+ module Test
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Turbo train test</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "turbo/train/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Turbo::Train::Engine.routes.draw do
2
+ end
@@ -0,0 +1,16 @@
1
+ say "Creating Caddyfile"
2
+ create_file Rails.root.join("Caddyfile") do
3
+ %{
4
+ localhost
5
+
6
+ route {
7
+ mercure {
8
+ publisher_jwt testing
9
+ subscriber_jwt test
10
+ cors_origins "*"
11
+ }
12
+
13
+ respond "Not Found" 404
14
+ }
15
+ }
16
+ end
@@ -0,0 +1,11 @@
1
+ say "Creating initializer"
2
+ create_file Rails.root.join("config/initializers/turbo_train.rb") do
3
+ %{
4
+ Turbo::Train.configure do |config|
5
+ config.mercure_domain = 'localhost'
6
+ config.publisher_key = 'testing'
7
+ config.subscriber_key = 'test'
8
+ config.skip_ssl_verification = true # Development only; don't do this in production
9
+ end
10
+ }
11
+ end
@@ -0,0 +1,5 @@
1
+ say "Import Turbo"
2
+ append_to_file "app/javascript/application.js", %(import "@uscreentv/turbo-train"\n)
3
+
4
+ say "Pin Turbo"
5
+ append_to_file "config/importmap.rb", %(pin "@uscreentv/turbo-train", to: "turbo-train.min.js", preload: true\n)
@@ -0,0 +1,21 @@
1
+ if Rails.root.join("yarn.lock").exist?
2
+ say "Detected yarn.lock. Installing Turbo Train with yarn"
3
+ run "yarn add @uscreentv/turbo-train"
4
+ elsif Rails.root.join("pnpm-lock.yaml").exist?
5
+ say "Detected pnpm-lock.yaml. Installing Turbo Train with pnpm"
6
+ run "pnpm add @uscreentv/turbo-train -w"
7
+ elsif Rails.root.join("package-lock.json").exist?
8
+ say "Detected package-lock.json. Installing Turbo Train with npm"
9
+ run "npm add @uscreentv/turbo-train"
10
+ else
11
+ say "We could not automatically install the @uscreentv/turbo-train JS library", :red
12
+ say " -> You must manually install the @uscreentv/turbo-train JS package.", :red
13
+ end
14
+
15
+ if (js_entrypoint_path = Rails.root.join("app/javascript/application.js")).exist?
16
+ say "Importing Turbo Train"
17
+ append_to_file "app/javascript/application.js", %(import "@uscreentv/turbo-train"\n)
18
+ else
19
+ say "We could not automatically detect your JS entrypoint (such as app/javascript/application.js)", :red
20
+ say " -> You must manually import @uscreentv/turbo-train in your JavaScript entrypoint file.", :red
21
+ end
@@ -0,0 +1,32 @@
1
+ def run_install_template(path)
2
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}"
3
+ end
4
+
5
+ namespace :turbo_train do
6
+ desc "Install Turbo::Train into the app"
7
+ task :install do
8
+ run_install_template 'create_initializer'
9
+ run_install_template 'create_caddyfile'
10
+
11
+ if Rails.root.join("config/importmap.rb").exist?
12
+ Rake::Task["turbo_train:install:importmap"].invoke
13
+ elsif Rails.root.join("package.json").exist?
14
+ Rake::Task["turbo_train:install:node"].invoke
15
+ else
16
+ puts "We can't detect neither (package.json) nor importmap-rails (config/importmap.rb) 🙁"
17
+ puts "Please refer to https://github.com/Uscreen-video/turbo-train/blob/main/README.md#installation for manual installation instructions."
18
+ end
19
+ end
20
+
21
+ namespace :install do
22
+ desc "Install Turbo::Train into the app with asset pipeline"
23
+ task :importmap do
24
+ run_install_template "install_importmap"
25
+ end
26
+
27
+ desc "Install Turbo::Train into the app with webpacker"
28
+ task :node do
29
+ run_install_template "install_node"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ module Turbo
2
+ module Train
3
+ class Configuration
4
+ attr_accessor :mercure_domain, :publisher_key, :subscriber_key, :skip_ssl_verification
5
+
6
+ def initialize
7
+ @mercure_domain = 'localhost'
8
+ @publisher_key = 'test'
9
+ @subscriber_key = 'testing'
10
+ @skip_ssl_verification = Rails.env.development? || Rails.env.test?
11
+ end
12
+ end
13
+
14
+ class << self
15
+ attr_accessor :configuration
16
+
17
+ def configure
18
+ self.configuration ||= Configuration.new
19
+ yield(configuration)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ require 'turbo-rails'
2
+
3
+ module Turbo
4
+ module Train
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace Turbo::Train
7
+
8
+ PRECOMPILE_ASSETS = %w( turbo-train.js turbo-train.min.js )
9
+
10
+ initializer "turbotrain.assets" do
11
+ if Rails.application.config.respond_to?(:assets)
12
+ Rails.application.config.assets.precompile += PRECOMPILE_ASSETS
13
+ end
14
+ end
15
+
16
+ initializer "turbotrain.helpers", before: :load_config_initializers do
17
+ ActiveSupport.on_load(:action_controller_base) do
18
+ helper Turbo::Train::StreamsHelper
19
+ end
20
+ end
21
+
22
+ initializer "turbotrain.load" do
23
+ ActiveSupport.on_load(:active_record) do
24
+ require_relative 'train'
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,114 @@
1
+ require 'jwt'
2
+ require 'net/http'
3
+
4
+ module Turbo
5
+ module Train
6
+ extend Turbo::Streams::ActionHelper
7
+
8
+ ALGORITHM = "HS256"
9
+
10
+ class << self
11
+ def url
12
+ "https://#{configuration.mercure_domain}/.well-known"
13
+ end
14
+
15
+ def encode(payload)
16
+ structured_payload = { mercure: { payload: payload } }
17
+ JWT.encode structured_payload, configuration.subscriber_key, ALGORITHM
18
+ end
19
+
20
+ def signed_stream_name(streamables)
21
+ Turbo.signed_stream_verifier.generate stream_name_from(streamables)
22
+ end
23
+
24
+ def broadcast_action_to(*streamables, action:, target: nil, targets: nil, **rendering)
25
+ broadcast(streamables, content: turbo_stream_action_tag(action, target: target, targets: targets, template:
26
+ rendering.delete(:content) || rendering.delete(:html) || (rendering.any? ? render_format(:html, **rendering) : nil)
27
+ ))
28
+ end
29
+
30
+ def broadcast_render_to(*streamables, **rendering)
31
+ broadcast(*streamables, content: render_format(:turbo_stream, **rendering))
32
+ end
33
+
34
+ def broadcast_remove_to(*streamables, **opts)
35
+ broadcast_action_to(*streamables, action: :remove, **opts)
36
+ end
37
+
38
+ def broadcast_replace_to(*streamables, **opts)
39
+ broadcast_action_to(*streamables, action: :replace, **opts)
40
+ end
41
+
42
+ def broadcast_update_to(*streamables, **opts)
43
+ broadcast_action_to(*streamables, action: :update, **opts)
44
+ end
45
+
46
+ def broadcast_before_to(*streamables, **opts)
47
+ broadcast_action_to(*streamables, action: :before, **opts)
48
+ end
49
+
50
+ def broadcast_after_to(*streamables, **opts)
51
+ broadcast_action_to(*streamables, action: :after, **opts)
52
+ end
53
+
54
+ def broadcast_append_to(*streamables, **opts)
55
+ broadcast_action_to(*streamables, action: :append, **opts)
56
+ end
57
+
58
+ def broadcast_prepend_to(*streamables, **opts)
59
+ broadcast_action_to(*streamables, action: :prepend, **opts)
60
+ end
61
+
62
+ private
63
+
64
+ def domain
65
+ Rails.configuration.turbo_train.mercure_domain
66
+ end
67
+
68
+ def render_format(format, **rendering)
69
+ ApplicationController.render(formats: [ format ], **rendering)
70
+ end
71
+
72
+ def broadcast(streamables, content:)
73
+ topics = if streamables.is_a?(Array)
74
+ streamables.map { |s| signed_stream_name(s) }
75
+ else
76
+ [signed_stream_name(streamables)]
77
+ end
78
+
79
+ data = {
80
+ topic: topics,
81
+ data: content
82
+ }
83
+ payload = { mercure: { publish: topics } }
84
+ token = JWT.encode payload, configuration.publisher_key, ALGORITHM
85
+
86
+ uri = URI("#{url}/mercure")
87
+
88
+ req = Net::HTTP::Post.new(uri)
89
+ req['Content-Type'] = 'application/x-www-form-urlencoded'
90
+ req['Authorization'] = "Bearer #{token}"
91
+ req.body = URI.encode_www_form(data)
92
+ opts = {
93
+ use_ssl: uri.scheme == 'https'
94
+ }
95
+
96
+ if configuration.skip_ssl_verification
97
+ opts[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
98
+ end
99
+
100
+ Net::HTTP.start(uri.host, uri.port, opts) do |http|
101
+ http.request(req)
102
+ end
103
+ end
104
+
105
+ def stream_name_from(streamables)
106
+ if streamables.is_a?(Array)
107
+ streamables.map { |streamable| stream_name_from(streamable) }.join(":")
108
+ else
109
+ streamables.then { |streamable| streamable.try(:to_gid_param) || streamable.to_param }
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,5 @@
1
+ module Turbo
2
+ module Train
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ require "turbo/train/version"
2
+ require "turbo/train/config"
3
+ require "turbo/train/engine"
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: turbo-train
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Savrov
8
+ - Dima Bondarenko
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2022-12-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '6.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '6.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: turbo-rails
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: jwt
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ description:
57
+ email:
58
+ - nick@uscreen.tv
59
+ - dmitry@uscreen.tv
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - MIT-LICENSE
65
+ - README.md
66
+ - Rakefile
67
+ - app/assets/config/turbo_train_test_manifest.js
68
+ - app/assets/javascripts/package.json
69
+ - app/assets/javascripts/turbo-train.js
70
+ - app/assets/javascripts/turbo-train.min.js
71
+ - app/controllers/turbo/train/test/application_controller.rb
72
+ - app/helpers/turbo/train/streams_helper.rb
73
+ - app/jobs/turbo/train/test/application_job.rb
74
+ - app/mailers/turbo/train/test/application_mailer.rb
75
+ - app/models/turbo/train/test/application_record.rb
76
+ - app/views/layouts/turbo/train/test/application.html.erb
77
+ - config/routes.rb
78
+ - lib/install/create_caddyfile.rb
79
+ - lib/install/create_initializer.rb
80
+ - lib/install/install_importmap.rb
81
+ - lib/install/install_node.rb
82
+ - lib/tasks/install_tasks.rake
83
+ - lib/turbo/train.rb
84
+ - lib/turbo/train/config.rb
85
+ - lib/turbo/train/engine.rb
86
+ - lib/turbo/train/train.rb
87
+ - lib/turbo/train/version.rb
88
+ homepage: https://github.com/Uscreen-video/turbo-train
89
+ licenses:
90
+ - MIT
91
+ metadata:
92
+ homepage_uri: https://github.com/Uscreen-video/turbo-train
93
+ source_code_uri: https://github.com/goodsign/turbo-train-test
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubygems_version: 3.1.6
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Rails Turbo Stream broadcasting over SSE instead of WebSockets. Uses Mercure
113
+ server.
114
+ test_files: []