rails_live_reload 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +6 -19
- data/lib/rails_live_reload/checker.rb +9 -3
- data/lib/rails_live_reload/config.rb +10 -6
- data/lib/rails_live_reload/engine.rb +16 -7
- data/lib/rails_live_reload/javascript/websocket.js +6 -0
- data/lib/rails_live_reload/middleware/base.rb +55 -0
- data/lib/rails_live_reload/server/base.rb +72 -0
- data/lib/rails_live_reload/server/connections.rb +27 -0
- data/lib/rails_live_reload/version.rb +1 -1
- data/lib/rails_live_reload/watcher.rb +39 -3
- data/lib/rails_live_reload/web_socket/base.rb +116 -0
- data/lib/rails_live_reload/web_socket/client_socket.rb +153 -0
- data/lib/rails_live_reload/web_socket/event_loop.rb +131 -0
- data/lib/rails_live_reload/web_socket/message_buffer.rb +53 -0
- data/lib/rails_live_reload/web_socket/stream.rb +109 -0
- data/lib/rails_live_reload/web_socket/wrapper.rb +27 -0
- data/lib/rails_live_reload.rb +21 -9
- metadata +17 -39
- data/lib/rails_live_reload/client.rb +0 -75
- data/lib/rails_live_reload/rails/middleware/base.rb +0 -63
- data/lib/rails_live_reload/rails/middleware/long_polling.rb +0 -51
- data/lib/rails_live_reload/rails/middleware/polling.rb +0 -21
@@ -0,0 +1,153 @@
|
|
1
|
+
require "websocket/driver"
|
2
|
+
|
3
|
+
module RailsLiveReload
|
4
|
+
module WebSocket
|
5
|
+
# This class is basically copied from ActionCable
|
6
|
+
# https://github.com/rails/rails/blob/v7.0.3/actioncable/lib/action_cable/connection/client_socket.rb
|
7
|
+
class ClientSocket
|
8
|
+
def self.determine_url(env)
|
9
|
+
scheme = secure_request?(env) ? "wss:" : "ws:"
|
10
|
+
"#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.secure_request?(env)
|
14
|
+
return true if env["HTTPS"] == "on"
|
15
|
+
return true if env["HTTP_X_FORWARDED_SSL"] == "on"
|
16
|
+
return true if env["HTTP_X_FORWARDED_SCHEME"] == "https"
|
17
|
+
return true if env["HTTP_X_FORWARDED_PROTO"] == "https"
|
18
|
+
return true if env["rack.url_scheme"] == "https"
|
19
|
+
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
CONNECTING = 0
|
24
|
+
OPEN = 1
|
25
|
+
CLOSING = 2
|
26
|
+
CLOSED = 3
|
27
|
+
|
28
|
+
attr_reader :env, :url
|
29
|
+
|
30
|
+
def initialize(env, event_target, event_loop, protocols)
|
31
|
+
@env = env
|
32
|
+
@event_target = event_target
|
33
|
+
@event_loop = event_loop
|
34
|
+
|
35
|
+
@url = ClientSocket.determine_url(@env)
|
36
|
+
|
37
|
+
@driver = @driver_started = nil
|
38
|
+
@close_params = ["", 1006]
|
39
|
+
|
40
|
+
@ready_state = CONNECTING
|
41
|
+
|
42
|
+
@driver = ::WebSocket::Driver.rack(self, protocols: protocols)
|
43
|
+
|
44
|
+
@driver.on(:open) { |e| open }
|
45
|
+
@driver.on(:message) { |e| receive_message(e.data) }
|
46
|
+
@driver.on(:close) { |e| begin_close(e.reason, e.code) }
|
47
|
+
@driver.on(:error) { |e| emit_error(e.message) }
|
48
|
+
|
49
|
+
@stream = RailsLiveReload::WebSocket::Stream.new(@event_loop, self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def start_driver
|
53
|
+
return if @driver.nil? || @driver_started
|
54
|
+
@stream.hijack_rack_socket
|
55
|
+
|
56
|
+
if callback = @env["async.callback"]
|
57
|
+
callback.call([101, {}, @stream])
|
58
|
+
end
|
59
|
+
|
60
|
+
@driver_started = true
|
61
|
+
@driver.start
|
62
|
+
end
|
63
|
+
|
64
|
+
def rack_response
|
65
|
+
start_driver
|
66
|
+
[ -1, {}, [] ]
|
67
|
+
end
|
68
|
+
|
69
|
+
def write(data)
|
70
|
+
@stream.write(data)
|
71
|
+
rescue => e
|
72
|
+
emit_error e.message
|
73
|
+
end
|
74
|
+
|
75
|
+
def transmit(message)
|
76
|
+
return false if @ready_state > OPEN
|
77
|
+
case message
|
78
|
+
when Numeric then @driver.text(message.to_s)
|
79
|
+
when String then @driver.text(message)
|
80
|
+
when Array then @driver.binary(message)
|
81
|
+
else false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def close(code = nil, reason = nil)
|
86
|
+
code ||= 1000
|
87
|
+
reason ||= ""
|
88
|
+
|
89
|
+
unless code == 1000 || (code >= 3000 && code <= 4999)
|
90
|
+
raise ArgumentError, "Failed to execute 'close' on WebSocket: " \
|
91
|
+
"The code must be either 1000, or between 3000 and 4999. " \
|
92
|
+
"#{code} is neither."
|
93
|
+
end
|
94
|
+
|
95
|
+
@ready_state = CLOSING unless @ready_state == CLOSED
|
96
|
+
@driver.close(reason, code)
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse(data)
|
100
|
+
@driver.parse(data)
|
101
|
+
end
|
102
|
+
|
103
|
+
def client_gone
|
104
|
+
finalize_close
|
105
|
+
end
|
106
|
+
|
107
|
+
def alive?
|
108
|
+
@ready_state == OPEN
|
109
|
+
end
|
110
|
+
|
111
|
+
def protocol
|
112
|
+
@driver.protocol
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def open
|
118
|
+
return unless @ready_state == CONNECTING
|
119
|
+
@ready_state = OPEN
|
120
|
+
|
121
|
+
@event_target.on_open
|
122
|
+
end
|
123
|
+
|
124
|
+
def receive_message(data)
|
125
|
+
return unless @ready_state == OPEN
|
126
|
+
|
127
|
+
@event_target.on_message(data)
|
128
|
+
end
|
129
|
+
|
130
|
+
def emit_error(message)
|
131
|
+
return if @ready_state >= CLOSING
|
132
|
+
|
133
|
+
@event_target.on_error(message)
|
134
|
+
end
|
135
|
+
|
136
|
+
def begin_close(reason, code)
|
137
|
+
return if @ready_state == CLOSED
|
138
|
+
@ready_state = CLOSING
|
139
|
+
@close_params = [reason, code]
|
140
|
+
|
141
|
+
@stream.shutdown if @stream
|
142
|
+
finalize_close
|
143
|
+
end
|
144
|
+
|
145
|
+
def finalize_close
|
146
|
+
return if @ready_state == CLOSED
|
147
|
+
@ready_state = CLOSED
|
148
|
+
|
149
|
+
@event_target.on_close(*@close_params)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require "nio"
|
2
|
+
|
3
|
+
module RailsLiveReload
|
4
|
+
module WebSocket
|
5
|
+
# This class is basically copied from ActionCable
|
6
|
+
# https://github.com/rails/rails/blob/v7.0.3/actioncable/lib/action_cable/connection/stream_event_loop.rb
|
7
|
+
class EventLoop
|
8
|
+
def initialize
|
9
|
+
@nio = @executor = @thread = nil
|
10
|
+
@map = {}
|
11
|
+
@stopping = false
|
12
|
+
@todo = Queue.new
|
13
|
+
@spawn_mutex = Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def timer(interval, &block)
|
17
|
+
Concurrent::TimerTask.new(execution_interval: interval, &block).tap(&:execute)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(task = nil, &block)
|
21
|
+
task ||= block
|
22
|
+
|
23
|
+
spawn
|
24
|
+
@executor << task
|
25
|
+
end
|
26
|
+
|
27
|
+
def attach(io, stream)
|
28
|
+
@todo << lambda do
|
29
|
+
@map[io] = @nio.register(io, :r)
|
30
|
+
@map[io].value = stream
|
31
|
+
end
|
32
|
+
wakeup
|
33
|
+
end
|
34
|
+
|
35
|
+
def detach(io, stream)
|
36
|
+
@todo << lambda do
|
37
|
+
@nio.deregister io
|
38
|
+
@map.delete io
|
39
|
+
io.close
|
40
|
+
end
|
41
|
+
wakeup
|
42
|
+
end
|
43
|
+
|
44
|
+
def writes_pending(io)
|
45
|
+
@todo << lambda do
|
46
|
+
if monitor = @map[io]
|
47
|
+
monitor.interests = :rw
|
48
|
+
end
|
49
|
+
end
|
50
|
+
wakeup
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop
|
54
|
+
@stopping = true
|
55
|
+
wakeup if @nio
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def spawn
|
61
|
+
return if @thread && @thread.status
|
62
|
+
|
63
|
+
@spawn_mutex.synchronize do
|
64
|
+
return if @thread && @thread.status
|
65
|
+
|
66
|
+
@nio ||= NIO::Selector.new
|
67
|
+
|
68
|
+
@executor ||= Concurrent::ThreadPoolExecutor.new(
|
69
|
+
min_threads: 1,
|
70
|
+
max_threads: 10,
|
71
|
+
max_queue: 0,
|
72
|
+
)
|
73
|
+
|
74
|
+
@thread = Thread.new { run }
|
75
|
+
|
76
|
+
return true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def wakeup
|
81
|
+
spawn || @nio.wakeup
|
82
|
+
end
|
83
|
+
|
84
|
+
def run
|
85
|
+
loop do
|
86
|
+
if @stopping
|
87
|
+
@nio.close
|
88
|
+
break
|
89
|
+
end
|
90
|
+
|
91
|
+
until @todo.empty?
|
92
|
+
@todo.pop(true).call
|
93
|
+
end
|
94
|
+
|
95
|
+
next unless monitors = @nio.select
|
96
|
+
|
97
|
+
monitors.each do |monitor|
|
98
|
+
io = monitor.io
|
99
|
+
stream = monitor.value
|
100
|
+
|
101
|
+
begin
|
102
|
+
if monitor.writable?
|
103
|
+
if stream.flush_write_buffer
|
104
|
+
monitor.interests = :r
|
105
|
+
end
|
106
|
+
next unless monitor.readable?
|
107
|
+
end
|
108
|
+
|
109
|
+
incoming = io.read_nonblock(4096, exception: false)
|
110
|
+
case incoming
|
111
|
+
when :wait_readable
|
112
|
+
next
|
113
|
+
when nil
|
114
|
+
stream.close
|
115
|
+
else
|
116
|
+
stream.receive incoming
|
117
|
+
end
|
118
|
+
rescue
|
119
|
+
begin
|
120
|
+
stream.close
|
121
|
+
rescue
|
122
|
+
@nio.deregister io
|
123
|
+
@map.delete io
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RailsLiveReload
|
2
|
+
module WebSocket
|
3
|
+
# This class is basically copied from ActionCable
|
4
|
+
# https://github.com/rails/rails/blob/v7.0.3/actioncable/lib/action_cable/connection/message_buffer.rb
|
5
|
+
class MessageBuffer
|
6
|
+
def initialize(connection)
|
7
|
+
@connection = connection
|
8
|
+
@buffered_messages = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def append(message)
|
12
|
+
if valid? message
|
13
|
+
if processing?
|
14
|
+
receive message
|
15
|
+
else
|
16
|
+
buffer message
|
17
|
+
end
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Couldn't handle non-string message: #{message.class}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def processing?
|
24
|
+
@processing
|
25
|
+
end
|
26
|
+
|
27
|
+
def process!
|
28
|
+
@processing = true
|
29
|
+
receive_buffered_messages
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :connection, :buffered_messages
|
35
|
+
|
36
|
+
def valid?(message)
|
37
|
+
message.is_a?(String)
|
38
|
+
end
|
39
|
+
|
40
|
+
def receive(message)
|
41
|
+
connection.receive message
|
42
|
+
end
|
43
|
+
|
44
|
+
def buffer(message)
|
45
|
+
buffered_messages << message
|
46
|
+
end
|
47
|
+
|
48
|
+
def receive_buffered_messages
|
49
|
+
receive buffered_messages.shift until buffered_messages.empty?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module RailsLiveReload
|
2
|
+
module WebSocket
|
3
|
+
# This class is basically copied from ActionCable
|
4
|
+
# https://github.com/rails/rails/blob/v7.0.3/actioncable/lib/action_cable/connection/stream.rb
|
5
|
+
class Stream
|
6
|
+
def initialize(event_loop, socket)
|
7
|
+
@event_loop = event_loop
|
8
|
+
@socket_object = socket
|
9
|
+
@stream_send = socket.env["stream.send"]
|
10
|
+
|
11
|
+
@rack_hijack_io = nil
|
12
|
+
@write_lock = Mutex.new
|
13
|
+
|
14
|
+
@write_head = nil
|
15
|
+
@write_buffer = Queue.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&callback)
|
19
|
+
@stream_send ||= callback
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
shutdown
|
24
|
+
@socket_object.client_gone
|
25
|
+
end
|
26
|
+
|
27
|
+
def shutdown
|
28
|
+
clean_rack_hijack
|
29
|
+
end
|
30
|
+
|
31
|
+
def write(data)
|
32
|
+
if @stream_send
|
33
|
+
return @stream_send.call(data)
|
34
|
+
end
|
35
|
+
|
36
|
+
if @write_lock.try_lock
|
37
|
+
begin
|
38
|
+
if @write_head.nil? && @write_buffer.empty?
|
39
|
+
written = @rack_hijack_io.write_nonblock(data, exception: false)
|
40
|
+
|
41
|
+
case written
|
42
|
+
when :wait_writable
|
43
|
+
when data.bytesize
|
44
|
+
return data.bytesize
|
45
|
+
else
|
46
|
+
@write_head = data.byteslice(written, data.bytesize)
|
47
|
+
@event_loop.writes_pending @rack_hijack_io
|
48
|
+
|
49
|
+
return data.bytesize
|
50
|
+
end
|
51
|
+
end
|
52
|
+
ensure
|
53
|
+
@write_lock.unlock
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@write_buffer << data
|
58
|
+
@event_loop.writes_pending @rack_hijack_io
|
59
|
+
|
60
|
+
data.bytesize
|
61
|
+
rescue EOFError, Errno::ECONNRESET
|
62
|
+
@socket_object.client_gone
|
63
|
+
end
|
64
|
+
|
65
|
+
def flush_write_buffer
|
66
|
+
@write_lock.synchronize do
|
67
|
+
loop do
|
68
|
+
if @write_head.nil?
|
69
|
+
return true if @write_buffer.empty?
|
70
|
+
@write_head = @write_buffer.pop
|
71
|
+
end
|
72
|
+
|
73
|
+
written = @rack_hijack_io.write_nonblock(@write_head, exception: false)
|
74
|
+
case written
|
75
|
+
when :wait_writable
|
76
|
+
return false
|
77
|
+
when @write_head.bytesize
|
78
|
+
@write_head = nil
|
79
|
+
else
|
80
|
+
@write_head = @write_head.byteslice(written, @write_head.bytesize)
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def receive(data)
|
88
|
+
@socket_object.parse(data)
|
89
|
+
end
|
90
|
+
|
91
|
+
def hijack_rack_socket
|
92
|
+
return unless @socket_object.env["rack.hijack"]
|
93
|
+
|
94
|
+
@rack_hijack_io = @socket_object.env["rack.hijack"].call
|
95
|
+
@rack_hijack_io ||= @socket_object.env["rack.hijack_io"]
|
96
|
+
|
97
|
+
@event_loop.attach(@rack_hijack_io, self)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def clean_rack_hijack
|
103
|
+
return unless @rack_hijack_io
|
104
|
+
@event_loop.detach(@rack_hijack_io, self)
|
105
|
+
@rack_hijack_io = nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "websocket/driver"
|
2
|
+
|
3
|
+
module RailsLiveReload
|
4
|
+
module WebSocket
|
5
|
+
# This class is basically copied from ActionCable
|
6
|
+
# https://github.com/rails/rails/blob/v7.0.3/actioncable/lib/action_cable/connection/web_socket.rb
|
7
|
+
class Wrapper
|
8
|
+
delegate :transmit, :close, :protocol, :rack_response, to: :websocket
|
9
|
+
|
10
|
+
def initialize(env, event_target, event_loop, protocols: RailsLiveReload::INTERNAL[:protocols])
|
11
|
+
@websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, event_loop, protocols) : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def possible?
|
15
|
+
websocket
|
16
|
+
end
|
17
|
+
|
18
|
+
def alive?
|
19
|
+
websocket && websocket.alive?
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :websocket
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/rails_live_reload.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require "listen"
|
2
2
|
require "rails_live_reload/version"
|
3
3
|
require "rails_live_reload/config"
|
4
|
-
require "rails_live_reload/client"
|
5
4
|
require "rails_live_reload/watcher"
|
6
|
-
require "rails_live_reload/
|
7
|
-
require "rails_live_reload/
|
8
|
-
require "rails_live_reload/
|
5
|
+
require "rails_live_reload/server/connections"
|
6
|
+
require "rails_live_reload/server/base"
|
7
|
+
require "rails_live_reload/middleware/base"
|
9
8
|
require "rails_live_reload/instrument/metrics_collector"
|
10
9
|
require "rails_live_reload/thread/current_request"
|
11
10
|
require "rails_live_reload/checker"
|
@@ -16,10 +15,23 @@ module RailsLiveReload
|
|
16
15
|
mattr_accessor :watcher
|
17
16
|
@@watcher = {}
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
INTERNAL = {
|
19
|
+
message_types: {
|
20
|
+
welcome: "welcome",
|
21
|
+
disconnect: "disconnect",
|
22
|
+
ping: "ping",
|
23
|
+
},
|
24
|
+
disconnect_reasons: {
|
25
|
+
invalid_request: "invalid_request",
|
26
|
+
remote: "remote"
|
27
|
+
},
|
28
|
+
socket_events: {
|
29
|
+
reload: 'reload'
|
30
|
+
},
|
31
|
+
protocols: ["rails-live-reload-v1-json"].freeze
|
32
|
+
}
|
33
|
+
|
34
|
+
module_function def server
|
35
|
+
@server ||= RailsLiveReload::Server::Base.new
|
24
36
|
end
|
25
37
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_live_reload
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Kasyanchuk
|
@@ -9,10 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-07-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: railties
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
@@ -40,41 +40,13 @@ dependencies:
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: websocket-driver
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - ">="
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: '0'
|
49
|
-
type: :
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: pry
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: pry-nav
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
73
|
-
requirements:
|
74
|
-
- - ">="
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: '0'
|
77
|
-
type: :development
|
49
|
+
type: :runtime
|
78
50
|
prerelease: false
|
79
51
|
version_requirements: !ruby/object:Gem::Requirement
|
80
52
|
requirements:
|
@@ -82,13 +54,13 @@ dependencies:
|
|
82
54
|
- !ruby/object:Gem::Version
|
83
55
|
version: '0'
|
84
56
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
57
|
+
name: nio4r
|
86
58
|
requirement: !ruby/object:Gem::Requirement
|
87
59
|
requirements:
|
88
60
|
- - ">="
|
89
61
|
- !ruby/object:Gem::Version
|
90
62
|
version: '0'
|
91
|
-
type: :
|
63
|
+
type: :runtime
|
92
64
|
prerelease: false
|
93
65
|
version_requirements: !ruby/object:Gem::Requirement
|
94
66
|
requirements:
|
@@ -123,17 +95,23 @@ files:
|
|
123
95
|
- Rakefile
|
124
96
|
- lib/rails_live_reload.rb
|
125
97
|
- lib/rails_live_reload/checker.rb
|
126
|
-
- lib/rails_live_reload/client.rb
|
127
98
|
- lib/rails_live_reload/command.rb
|
128
99
|
- lib/rails_live_reload/config.rb
|
129
100
|
- lib/rails_live_reload/engine.rb
|
130
101
|
- lib/rails_live_reload/instrument/metrics_collector.rb
|
131
|
-
- lib/rails_live_reload/
|
132
|
-
- lib/rails_live_reload/
|
133
|
-
- lib/rails_live_reload/
|
102
|
+
- lib/rails_live_reload/javascript/websocket.js
|
103
|
+
- lib/rails_live_reload/middleware/base.rb
|
104
|
+
- lib/rails_live_reload/server/base.rb
|
105
|
+
- lib/rails_live_reload/server/connections.rb
|
134
106
|
- lib/rails_live_reload/thread/current_request.rb
|
135
107
|
- lib/rails_live_reload/version.rb
|
136
108
|
- lib/rails_live_reload/watcher.rb
|
109
|
+
- lib/rails_live_reload/web_socket/base.rb
|
110
|
+
- lib/rails_live_reload/web_socket/client_socket.rb
|
111
|
+
- lib/rails_live_reload/web_socket/event_loop.rb
|
112
|
+
- lib/rails_live_reload/web_socket/message_buffer.rb
|
113
|
+
- lib/rails_live_reload/web_socket/stream.rb
|
114
|
+
- lib/rails_live_reload/web_socket/wrapper.rb
|
137
115
|
homepage: https://github.com/railsjazz/rails_live_reload
|
138
116
|
licenses:
|
139
117
|
- MIT
|
@@ -1,75 +0,0 @@
|
|
1
|
-
module RailsLiveReload
|
2
|
-
module Client
|
3
|
-
|
4
|
-
def Client.long_polling_js
|
5
|
-
<<~HTML.html_safe
|
6
|
-
<script>
|
7
|
-
(function() {
|
8
|
-
const files = #{CurrentRequest.current.data.to_a.to_json};
|
9
|
-
let retries_count = 0;
|
10
|
-
function poll() {
|
11
|
-
const formData = new FormData();
|
12
|
-
formData.append('dt', #{Time.now.to_i})
|
13
|
-
formData.append('files', JSON.stringify(files))
|
14
|
-
|
15
|
-
fetch(
|
16
|
-
"#{RailsLiveReload.config.url}",
|
17
|
-
{
|
18
|
-
method: "post",
|
19
|
-
headers: { 'Accept': 'application/json', },
|
20
|
-
body: formData
|
21
|
-
}
|
22
|
-
)
|
23
|
-
.then(response => response.json())
|
24
|
-
.then(data => {
|
25
|
-
retries_count = 0;
|
26
|
-
if(data['command'] === 'RELOAD') {
|
27
|
-
window.location.reload();
|
28
|
-
} else {
|
29
|
-
poll();
|
30
|
-
}
|
31
|
-
}).catch(() => {
|
32
|
-
retries_count++;
|
33
|
-
|
34
|
-
if(retries_count < 10) {
|
35
|
-
setTimeout(poll, 5000)
|
36
|
-
}
|
37
|
-
})
|
38
|
-
}
|
39
|
-
poll();
|
40
|
-
})();
|
41
|
-
</script>
|
42
|
-
HTML
|
43
|
-
end
|
44
|
-
|
45
|
-
def Client.polling_js
|
46
|
-
<<~HTML.html_safe
|
47
|
-
<script>
|
48
|
-
const files = #{CurrentRequest.current.data.to_a.to_json};
|
49
|
-
const timer = setInterval(
|
50
|
-
() => {
|
51
|
-
const formData = new FormData();
|
52
|
-
formData.append('dt', #{Time.now.to_i})
|
53
|
-
formData.append('files', JSON.stringify(files))
|
54
|
-
fetch(
|
55
|
-
"#{RailsLiveReload.config.url}",
|
56
|
-
{
|
57
|
-
method: "post",
|
58
|
-
headers: { 'Accept': 'application/json', },
|
59
|
-
body: formData
|
60
|
-
}
|
61
|
-
)
|
62
|
-
.then(response => response.json())
|
63
|
-
.then(data => {
|
64
|
-
if(data['command'] === 'RELOAD') {
|
65
|
-
clearInterval(timer);
|
66
|
-
window.location.reload();
|
67
|
-
}
|
68
|
-
})
|
69
|
-
}, #{RailsLiveReload.config.polling_interval}
|
70
|
-
)
|
71
|
-
</script>
|
72
|
-
HTML
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|