turbo_boost-commands 0.0.18 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of turbo_boost-commands might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +16 -22
- data/app/assets/builds/@turbo-boost/commands.js +1 -1
- data/app/assets/builds/@turbo-boost/commands.js.map +4 -4
- data/app/controllers/concerns/turbo_boost/commands/controller.rb +1 -1
- data/app/javascript/drivers/form.js +0 -3
- data/app/javascript/drivers/window.js +0 -6
- data/app/javascript/index.js +25 -31
- data/app/javascript/state/index.js +17 -35
- data/app/javascript/state/observable.js +2 -3
- data/app/javascript/turbo.js +1 -8
- data/app/javascript/urls.js +1 -1
- data/lib/turbo_boost/commands/command.rb +3 -5
- data/lib/turbo_boost/commands/controller_pack.rb +5 -8
- data/lib/turbo_boost/commands/engine.rb +11 -2
- data/lib/turbo_boost/commands/errors.rb +2 -0
- data/lib/turbo_boost/commands/runner.rb +41 -38
- data/lib/turbo_boost/commands/state.rb +78 -0
- data/lib/turbo_boost/commands/version.rb +1 -1
- metadata +20 -12
- data/app/assets/builds/@turbo-boost/commands.metafile.json +0 -1
- data/app/javascript/meta.js +0 -19
- data/lib/turbo_boost/errors.rb +0 -6
- data/lib/turbo_boost/state/errors.rb +0 -6
- data/lib/turbo_boost/state/manager.rb +0 -162
- data/lib/turbo_boost/state/provisional_state.rb +0 -31
- data/lib/turbo_boost/state.rb +0 -137
@@ -1,29 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../state/manager"
|
4
3
|
require_relative "runner"
|
5
4
|
|
6
5
|
class TurboBoost::Commands::ControllerPack
|
7
6
|
include TurboBoost::Commands::AttributeHydration
|
8
7
|
|
9
|
-
attr_reader :
|
10
|
-
alias_method :state, :state_manager
|
8
|
+
attr_reader :runner, :command
|
11
9
|
|
12
10
|
delegate(
|
13
11
|
:command_aborted?,
|
14
12
|
:command_errored?,
|
15
|
-
:command_performing?,
|
16
13
|
:command_performed?,
|
14
|
+
:command_performing?,
|
17
15
|
:command_requested?,
|
18
16
|
:command_succeeded?,
|
19
|
-
:
|
17
|
+
:controller,
|
18
|
+
:state,
|
20
19
|
to: :runner
|
21
20
|
)
|
22
21
|
|
23
22
|
def initialize(controller)
|
24
|
-
@
|
25
|
-
@state_manager = TurboBoost::State::Manager.new(controller)
|
26
|
-
@runner = TurboBoost::Commands::Runner.new(controller, state_manager)
|
23
|
+
@runner = TurboBoost::Commands::Runner.new(controller)
|
27
24
|
@command = runner.command_instance
|
28
25
|
end
|
29
26
|
end
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require "turbo-rails"
|
4
4
|
require "turbo_boost/streams"
|
5
|
+
require "universalid"
|
5
6
|
require_relative "version"
|
6
7
|
require_relative "http_status_codes"
|
7
|
-
require_relative "../errors"
|
8
8
|
require_relative "errors"
|
9
9
|
require_relative "patches"
|
10
10
|
require_relative "command"
|
@@ -19,13 +19,14 @@ module TurboBoost::Commands
|
|
19
19
|
|
20
20
|
class Engine < ::Rails::Engine
|
21
21
|
config.turbo_boost_commands = ActiveSupport::OrderedOptions.new
|
22
|
-
config.turbo_boost_commands[:max_cookie_size] = ActionDispatch::Cookies::MAX_COOKIE_SIZE / 3
|
23
22
|
config.turbo_boost_commands[:validate_client_token] = false
|
24
23
|
|
25
24
|
# must opt-in to state overrides
|
26
25
|
config.turbo_boost_commands[:apply_client_state_overrides] = false
|
27
26
|
config.turbo_boost_commands[:apply_server_state_overrides] = false
|
28
27
|
|
28
|
+
config.turbo_boost_commands.precompile_assets = true
|
29
|
+
|
29
30
|
initializer "turbo_boost_commands.configuration" do
|
30
31
|
Mime::Type.register "text/vnd.turbo-boost.html", :turbo_boost
|
31
32
|
|
@@ -40,5 +41,13 @@ module TurboBoost::Commands
|
|
40
41
|
ActionView::Helpers::TagHelper::TagBuilder.prepend TurboBoost::Commands::Patches::ActionViewHelpersTagHelperTagBuilderPatch
|
41
42
|
end
|
42
43
|
end
|
44
|
+
|
45
|
+
initializer "turbo_boost_commands.asset" do
|
46
|
+
config.after_initialize do |app|
|
47
|
+
if app.config.respond_to?(:assets) && app.config.turbo_boost_commands.precompile_assets
|
48
|
+
app.config.assets.precompile += %w[@turbo-boost/commands.js]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
43
52
|
end
|
44
53
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "sanitizer"
|
4
|
+
require_relative "state"
|
4
5
|
|
5
6
|
class TurboBoost::Commands::Runner
|
6
7
|
SUPPORTED_MEDIA_TYPES = {
|
@@ -9,22 +10,18 @@ class TurboBoost::Commands::Runner
|
|
9
10
|
"text/vnd.turbo-stream.html" => true
|
10
11
|
}.freeze
|
11
12
|
|
12
|
-
attr_reader :controller
|
13
|
+
attr_reader :controller
|
13
14
|
|
14
|
-
def initialize(controller
|
15
|
+
def initialize(controller)
|
15
16
|
@controller = controller
|
16
|
-
@state_manager = state_manager
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
data: {busy: false, state: state_manager.payload}
|
26
|
-
}
|
27
|
-
controller.view_context.tag("meta", options).html_safe
|
19
|
+
def state
|
20
|
+
@state ||= begin
|
21
|
+
sgid = command_params[:signed_state]
|
22
|
+
value = TurboBoost::Commands::State.from_sgid_param(sgid) if sgid
|
23
|
+
value || TurboBoost::Commands::State.new
|
24
|
+
end
|
28
25
|
end
|
29
26
|
|
30
27
|
def command_requested?
|
@@ -48,15 +45,15 @@ class TurboBoost::Commands::Runner
|
|
48
45
|
|
49
46
|
# validate csrf token
|
50
47
|
unless valid_client_token?
|
51
|
-
raise TurboBoost::InvalidTokenError,
|
52
|
-
"
|
48
|
+
raise TurboBoost::Commands::InvalidTokenError,
|
49
|
+
"Token mismatch! The token: #{client_token}` does not match the expected value of `#{server_token}`."
|
53
50
|
end
|
54
51
|
|
55
52
|
true
|
56
53
|
end
|
57
54
|
|
58
55
|
def command_params
|
59
|
-
return ActionController::Parameters.new if controller.params
|
56
|
+
return ActionController::Parameters.new if controller.params.keys.none?(/\A(tbc|turbo_boost_command)\z/o)
|
60
57
|
@command_params ||= begin
|
61
58
|
payload = parsed_command_params.deep_transform_keys(&:underscore)
|
62
59
|
ActionController::Parameters.new(payload).permit!
|
@@ -86,7 +83,7 @@ class TurboBoost::Commands::Runner
|
|
86
83
|
end
|
87
84
|
|
88
85
|
def command_instance
|
89
|
-
@command_instance ||= command_class&.new(controller,
|
86
|
+
@command_instance ||= command_class&.new(controller, state, command_params).tap do |instance|
|
90
87
|
instance&.add_observer self, :handle_command_event
|
91
88
|
end
|
92
89
|
end
|
@@ -126,6 +123,7 @@ class TurboBoost::Commands::Runner
|
|
126
123
|
return if command_errored?
|
127
124
|
return if command_performing?
|
128
125
|
return if command_performed?
|
126
|
+
state.resolve command_params[:client_state]
|
129
127
|
command_instance.perform_with_callbacks command_method_name
|
130
128
|
end
|
131
129
|
|
@@ -148,8 +146,8 @@ class TurboBoost::Commands::Runner
|
|
148
146
|
append_error_to_response error
|
149
147
|
end
|
150
148
|
|
151
|
-
|
152
|
-
|
149
|
+
append_command_token_to_response_body
|
150
|
+
append_command_state_to_response_body
|
153
151
|
end
|
154
152
|
|
155
153
|
def update_response
|
@@ -159,8 +157,8 @@ class TurboBoost::Commands::Runner
|
|
159
157
|
return if controller_action_prevented?
|
160
158
|
|
161
159
|
append_to_response_headers
|
162
|
-
|
163
|
-
|
160
|
+
append_command_token_to_response_body
|
161
|
+
append_command_state_to_response_body
|
164
162
|
append_success_to_response if command_succeeded?
|
165
163
|
rescue => error
|
166
164
|
Rails.logger.error "TurboBoost::Commands::Runner failed to update the response! #{error.message}"
|
@@ -181,11 +179,6 @@ class TurboBoost::Commands::Runner
|
|
181
179
|
ActiveSupport::MessageVerifier.new Rails.application.secret_key_base, digest: "SHA256"
|
182
180
|
end
|
183
181
|
|
184
|
-
# Same implementation as ActionController::Base but with public visibility
|
185
|
-
def cookies
|
186
|
-
controller.request.cookie_jar
|
187
|
-
end
|
188
|
-
|
189
182
|
def handle_command_event(*args)
|
190
183
|
event = args.shift
|
191
184
|
options = args.extract_options!
|
@@ -198,31 +191,36 @@ class TurboBoost::Commands::Runner
|
|
198
191
|
private
|
199
192
|
|
200
193
|
def parsed_command_params
|
201
|
-
@parsed_command_params ||= JSON.parse(controller.params[:turbo_boost_command])
|
194
|
+
@parsed_command_params ||= JSON.parse(controller.params[:tbc] || controller.params[:turbo_boost_command])
|
202
195
|
end
|
203
196
|
|
204
197
|
def content_sanitizer
|
205
198
|
TurboBoost::Commands::Sanitizer.instance
|
206
199
|
end
|
207
200
|
|
201
|
+
# TODO: revisit command token validation
|
208
202
|
def new_token
|
209
|
-
@new_token ||= SecureRandom.
|
203
|
+
@new_token ||= SecureRandom.alphanumeric(13)
|
210
204
|
end
|
211
205
|
|
206
|
+
# TODO: revisit command token validation
|
212
207
|
def server_token
|
213
|
-
|
208
|
+
nil
|
214
209
|
end
|
215
210
|
|
211
|
+
# TODO: revisit command token validation
|
216
212
|
def client_token
|
217
|
-
|
213
|
+
command_params[:token].to_s
|
218
214
|
end
|
219
215
|
|
216
|
+
# TODO: revisit command token validation
|
220
217
|
def valid_client_token?
|
221
|
-
return true unless Rails.configuration.turbo_boost_commands.validate_client_token
|
222
|
-
return false unless client_token.present?
|
223
|
-
return false unless message_verifier.valid_message?(client_token)
|
224
|
-
unmasked_client_token = message_verifier.verify(client_token)
|
225
|
-
unmasked_client_token == server_token
|
218
|
+
# return true unless Rails.configuration.turbo_boost_commands.validate_client_token
|
219
|
+
# return false unless client_token.present?
|
220
|
+
# return false unless message_verifier.valid_message?(client_token)
|
221
|
+
# unmasked_client_token = message_verifier.verify(client_token)
|
222
|
+
# unmasked_client_token == server_token
|
223
|
+
true
|
226
224
|
end
|
227
225
|
|
228
226
|
def should_redirect?
|
@@ -258,11 +256,16 @@ class TurboBoost::Commands::Runner
|
|
258
256
|
command_instance.turbo_streams.each { |stream| append_to_response_body stream }
|
259
257
|
end
|
260
258
|
|
261
|
-
def
|
262
|
-
|
263
|
-
|
259
|
+
def append_command_token_to_response_body
|
260
|
+
append_to_response_body turbo_stream.invoke("TurboBoost.Commands.token=", args: [new_token], camelize: false)
|
261
|
+
rescue => error
|
262
|
+
Rails.logger.error "TurboBoost::Commands::Runner failed to append the Command token to the response! #{error.message}"
|
263
|
+
end
|
264
|
+
|
265
|
+
def append_command_state_to_response_body
|
266
|
+
append_to_response_body turbo_stream.invoke("TurboBoost.State.initialize", args: [state.to_json, state.to_sgid_param], camelize: false)
|
264
267
|
rescue => error
|
265
|
-
Rails.logger.error "TurboBoost::Commands::Runner failed to append the
|
268
|
+
Rails.logger.error "TurboBoost::Commands::Runner failed to append the Command state to the response! #{error.message}"
|
266
269
|
end
|
267
270
|
|
268
271
|
def append_success_event_to_response_body
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TurboBoost::Commands::State
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def from_sgid_param(sgid)
|
8
|
+
new URI::UID.from_sgid(sgid, for: name)&.decode
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :resolver
|
12
|
+
|
13
|
+
def assign_resolver(&block)
|
14
|
+
@resolver = block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(store = nil, provisional: false)
|
19
|
+
@store = store || ActiveSupport::Cache::MemoryStore.new(expires_in: 1.day, size: 2.kilobytes)
|
20
|
+
@store.cleanup
|
21
|
+
@provisional = provisional
|
22
|
+
end
|
23
|
+
|
24
|
+
delegate :to_json, to: :to_h
|
25
|
+
delegate_missing_to :store
|
26
|
+
|
27
|
+
def each
|
28
|
+
data.keys.each do |key|
|
29
|
+
yield key, self[key]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Provisional state is for the current request/response and is exposed as `State#now`
|
34
|
+
# Standard state is preserved across multiple requests
|
35
|
+
def provisional?
|
36
|
+
!!@provisional
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: implement state resolution
|
40
|
+
def resolve(client_state)
|
41
|
+
# return unless self.class.resolver
|
42
|
+
# self.class.resolver.call self, client_state
|
43
|
+
end
|
44
|
+
|
45
|
+
def now
|
46
|
+
return nil if provisional? # provisional state cannot hold child provisional state
|
47
|
+
@now ||= self.class.new(provisional: true)
|
48
|
+
end
|
49
|
+
|
50
|
+
def cache_key
|
51
|
+
"TurboBoost::Commands::State/#{Digest::SHA2.base64digest(to_json)}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def read(...)
|
55
|
+
now&.read(...) || store.read(...)
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](...)
|
59
|
+
read(...)
|
60
|
+
end
|
61
|
+
|
62
|
+
def []=(...)
|
63
|
+
write(...)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_sgid_param
|
67
|
+
store.cleanup
|
68
|
+
URI::UID.build(store, include_blank: false).to_sgid_param for: self.class.name, expires_in: 1.week
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
attr_reader :store
|
74
|
+
|
75
|
+
def data
|
76
|
+
store.instance_variable_get(:@data) || {}
|
77
|
+
end
|
78
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbo_boost-commands
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nate Hopkins (hopsoft)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -44,14 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
47
|
+
version: 0.1.7
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
54
|
+
version: 0.1.7
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: universalid
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.1.7
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.1.7
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: capybara
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -344,7 +358,6 @@ files:
|
|
344
358
|
- README.md
|
345
359
|
- app/assets/builds/@turbo-boost/commands.js
|
346
360
|
- app/assets/builds/@turbo-boost/commands.js.map
|
347
|
-
- app/assets/builds/@turbo-boost/commands.metafile.json
|
348
361
|
- app/assets/images/apple-touch-icon.png
|
349
362
|
- app/assets/images/favicon-16x16.png
|
350
363
|
- app/assets/images/favicon-32x32.png
|
@@ -369,7 +382,6 @@ files:
|
|
369
382
|
- app/javascript/index.js
|
370
383
|
- app/javascript/lifecycle.js
|
371
384
|
- app/javascript/logger.js
|
372
|
-
- app/javascript/meta.js
|
373
385
|
- app/javascript/renderer.js
|
374
386
|
- app/javascript/schema.js
|
375
387
|
- app/javascript/state/index.js
|
@@ -390,12 +402,8 @@ files:
|
|
390
402
|
- lib/turbo_boost/commands/patches/action_view_helpers_tag_helper_tag_builder_patch.rb
|
391
403
|
- lib/turbo_boost/commands/runner.rb
|
392
404
|
- lib/turbo_boost/commands/sanitizer.rb
|
405
|
+
- lib/turbo_boost/commands/state.rb
|
393
406
|
- lib/turbo_boost/commands/version.rb
|
394
|
-
- lib/turbo_boost/errors.rb
|
395
|
-
- lib/turbo_boost/state.rb
|
396
|
-
- lib/turbo_boost/state/errors.rb
|
397
|
-
- lib/turbo_boost/state/manager.rb
|
398
|
-
- lib/turbo_boost/state/provisional_state.rb
|
399
407
|
homepage: https://github.com/hopsoft/turbo_boost-commands
|
400
408
|
licenses:
|
401
409
|
- MIT
|
@@ -411,7 +419,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
411
419
|
requirements:
|
412
420
|
- - ">="
|
413
421
|
- !ruby/object:Gem::Version
|
414
|
-
version:
|
422
|
+
version: '3.0'
|
415
423
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
416
424
|
requirements:
|
417
425
|
- - ">="
|
@@ -1 +0,0 @@
|
|
1
|
-
{"inputs":{"app/javascript/meta.js":{"bytes":337,"imports":[],"format":"esm"},"app/javascript/events.js":{"bytes":783,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/state/observable.js":{"bytes":920,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"}],"format":"esm"},"app/javascript/state/index.js":{"bytes":1835,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/state/observable.js","kind":"import-statement","original":"./observable"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/renderer.js":{"bytes":475,"imports":[],"format":"esm"},"app/javascript/activity.js":{"bytes":279,"imports":[],"format":"esm"},"app/javascript/lifecycle.js":{"bytes":610,"imports":[{"path":"app/javascript/activity.js","kind":"import-statement","original":"./activity"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"}],"format":"esm"},"app/javascript/turbo.js":{"bytes":1952,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"./meta"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"./state"},{"path":"app/javascript/renderer.js","kind":"import-statement","original":"./renderer"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/schema.js":{"bytes":226,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/confirmation.js":{"bytes":947,"imports":[{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"}],"format":"esm"},"app/javascript/delegates.js":{"bytes":923,"imports":[{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/elements.js":{"bytes":1451,"imports":[{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"}],"format":"esm"},"app/javascript/drivers/form.js":{"bytes":384,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"}],"format":"esm"},"app/javascript/urls.js":{"bytes":240,"imports":[],"format":"esm"},"app/javascript/drivers/frame.js":{"bytes":218,"imports":[{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/method.js":{"bytes":265,"imports":[{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/window.js":{"bytes":2033,"imports":[{"path":"app/javascript/meta.js","kind":"import-statement","original":"../meta"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"../state"},{"path":"app/javascript/events.js","kind":"import-statement","original":"../events"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"../lifecycle"},{"path":"app/javascript/urls.js","kind":"import-statement","original":"../urls"},{"path":"app/javascript/renderer.js","kind":"import-statement","original":"../renderer"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"},"app/javascript/drivers/index.js":{"bytes":2044,"imports":[{"path":"app/javascript/elements.js","kind":"import-statement","original":"../elements"},{"path":"app/javascript/drivers/form.js","kind":"import-statement","original":"./form"},{"path":"app/javascript/drivers/frame.js","kind":"import-statement","original":"./frame"},{"path":"app/javascript/drivers/method.js","kind":"import-statement","original":"./method"},{"path":"app/javascript/drivers/window.js","kind":"import-statement","original":"./window"}],"format":"esm"},"app/javascript/logger.js":{"bytes":729,"imports":[{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"}],"format":"esm"},"app/javascript/uuids.js":{"bytes":202,"imports":[],"format":"esm"},"app/javascript/index.js":{"bytes":3558,"imports":[{"path":"app/javascript/turbo.js","kind":"import-statement","original":"./turbo"},{"path":"app/javascript/schema.js","kind":"import-statement","original":"./schema"},{"path":"app/javascript/events.js","kind":"import-statement","original":"./events"},{"path":"app/javascript/activity.js","kind":"import-statement","original":"./activity"},{"path":"app/javascript/confirmation.js","kind":"import-statement","original":"./confirmation"},{"path":"app/javascript/delegates.js","kind":"import-statement","original":"./delegates"},{"path":"app/javascript/drivers/index.js","kind":"import-statement","original":"./drivers"},{"path":"app/javascript/meta.js","kind":"import-statement","original":"./meta"},{"path":"app/javascript/elements.js","kind":"import-statement","original":"./elements"},{"path":"app/javascript/lifecycle.js","kind":"import-statement","original":"./lifecycle"},{"path":"app/javascript/logger.js","kind":"import-statement","original":"./logger"},{"path":"app/javascript/state/index.js","kind":"import-statement","original":"./state"},{"path":"app/javascript/urls.js","kind":"import-statement","original":"./urls"},{"path":"app/javascript/uuids.js","kind":"import-statement","original":"./uuids"},{"path":"<runtime>","kind":"import-statement","external":true}],"format":"esm"}},"outputs":{"app/assets/builds/@turbo-boost/commands.js.map":{"imports":[],"exports":[],"inputs":{},"bytes":37542},"app/assets/builds/@turbo-boost/commands.js":{"imports":[],"exports":["default"],"entryPoint":"app/javascript/index.js","inputs":{"app/javascript/meta.js":{"bytesInOutput":252},"app/javascript/events.js":{"bytesInOutput":500},"app/javascript/state/observable.js":{"bytesInOutput":405},"app/javascript/state/index.js":{"bytesInOutput":762},"app/javascript/renderer.js":{"bytesInOutput":263},"app/javascript/activity.js":{"bytesInOutput":173},"app/javascript/lifecycle.js":{"bytesInOutput":294},"app/javascript/turbo.js":{"bytesInOutput":1024},"app/javascript/schema.js":{"bytesInOutput":166},"app/javascript/confirmation.js":{"bytesInOutput":424},"app/javascript/delegates.js":{"bytesInOutput":473},"app/javascript/elements.js":{"bytesInOutput":782},"app/javascript/drivers/form.js":{"bytesInOutput":246},"app/javascript/urls.js":{"bytesInOutput":166},"app/javascript/drivers/frame.js":{"bytesInOutput":96},"app/javascript/drivers/method.js":{"bytesInOutput":130},"app/javascript/drivers/window.js":{"bytesInOutput":1298},"app/javascript/drivers/index.js":{"bytesInOutput":991},"app/javascript/logger.js":{"bytesInOutput":399},"app/javascript/uuids.js":{"bytesInOutput":159},"app/javascript/index.js":{"bytesInOutput":1686}},"bytes":11186}}}
|
data/app/javascript/meta.js
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
class Meta {
|
2
|
-
get element() {
|
3
|
-
return document.querySelector('meta[name="turbo-boost"]')
|
4
|
-
}
|
5
|
-
|
6
|
-
get token() {
|
7
|
-
return this.element.getAttribute('content')
|
8
|
-
}
|
9
|
-
|
10
|
-
get busy() {
|
11
|
-
return this.element.dataset.busy === 'true'
|
12
|
-
}
|
13
|
-
|
14
|
-
set busy(value) {
|
15
|
-
return (this.element.dataset.busy = !!value)
|
16
|
-
}
|
17
|
-
}
|
18
|
-
|
19
|
-
export default new Meta()
|
data/lib/turbo_boost/errors.rb
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "../state"
|
4
|
-
require_relative "provisional_state"
|
5
|
-
|
6
|
-
# Class used to hold ephemeral state related to the rendered UI.
|
7
|
-
#
|
8
|
-
# Examples:
|
9
|
-
#
|
10
|
-
# - Sidebar open/closed state
|
11
|
-
# - Tree view open/closed state
|
12
|
-
# - Accordion collapsed/expanded state
|
13
|
-
# - Customized layout / presentation
|
14
|
-
# - Applied data filters
|
15
|
-
# - Number of data rows to display etc.
|
16
|
-
#
|
17
|
-
class TurboBoost::State::Manager
|
18
|
-
include ActiveModel::Dirty
|
19
|
-
|
20
|
-
class << self
|
21
|
-
def state_override_blocks
|
22
|
-
@state_overrides ||= {}
|
23
|
-
end
|
24
|
-
|
25
|
-
def add_state_override_block(controller_name, block)
|
26
|
-
state_override_blocks[controller_name] = block
|
27
|
-
end
|
28
|
-
|
29
|
-
def state_override_block(controller)
|
30
|
-
return nil if state_override_blocks.blank?
|
31
|
-
ancestor = controller.class.ancestors.find { |a| state_override_blocks[a.name] }
|
32
|
-
state_override_blocks[ancestor.name]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# For ActiveModel::Dirty tracking
|
37
|
-
define_attribute_methods :state
|
38
|
-
|
39
|
-
attr_reader :controller, :cookie_data, :header_data, :server_data
|
40
|
-
|
41
|
-
def initialize(controller)
|
42
|
-
@controller = controller
|
43
|
-
|
44
|
-
begin
|
45
|
-
@state = TurboBoost::State.new(cookie) # server state as stored in the cookie
|
46
|
-
rescue => error
|
47
|
-
Rails.logger.error "Failed to construct TurboBoost::State! #{error.message}"
|
48
|
-
@state = TurboBoost::State.new
|
49
|
-
end
|
50
|
-
|
51
|
-
# State the server used to render the page last time
|
52
|
-
cookie_state_hash = state.to_h
|
53
|
-
|
54
|
-
# State managed by the server on the backend (redis cache etc.)
|
55
|
-
# SEE: `TurboBoost::State::Manager.state_override_block`
|
56
|
-
server_state_hash = {}
|
57
|
-
|
58
|
-
# State the client expects... related to optimistic UI updates
|
59
|
-
# i.e. Changes made on the client before making this request
|
60
|
-
header_state_hash = {}
|
61
|
-
|
62
|
-
# Apply server state overrides (i.e. state stored in databases like Redis, Postgres, etc...)
|
63
|
-
if TurboBoost::Commands.config.apply_server_state_overrides
|
64
|
-
begin
|
65
|
-
state_override_block = self.class.state_override_block(controller)
|
66
|
-
if state_override_block
|
67
|
-
server_state_hash = controller.instance_eval(&state_override_block).with_indifferent_access
|
68
|
-
server_state_hash.each { |key, val| self[key] = val }
|
69
|
-
end
|
70
|
-
rescue => error
|
71
|
-
Rails.logger.error "Failed to apply `state_override_block` configured in #{controller.class.name} to TurboBoost::State! #{error.message}"
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Apply client state overrides (i.e. optimistic state)
|
76
|
-
# NOTE: Client state HTTP headers are only sent if/when state has changed on the client (only the changes are sent).
|
77
|
-
# This prevents race conditions (state mismatch) caused when frame and XHR requests emit immediately
|
78
|
-
# before the <meta id="turbo-boost"> has been updated with the latest state from the server.
|
79
|
-
if TurboBoost::Commands.config.apply_client_state_overrides
|
80
|
-
begin
|
81
|
-
header_state_hash = TurboBoost::State.deserialize_base64(header).with_indifferent_access
|
82
|
-
header_state_hash.each { |key, val| self[key] = val }
|
83
|
-
rescue => error
|
84
|
-
Rails.logger.error "Failed to apply client state from HTTP headers to TurboBoost::State! #{error.message}"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
@cookie_data = cookie_state_hash
|
89
|
-
@header_data = header_state_hash
|
90
|
-
@server_data = server_state_hash
|
91
|
-
rescue => error
|
92
|
-
Rails.logger.error "Failed to construct TurboBoost::State! #{error.message}"
|
93
|
-
ensure
|
94
|
-
@state ||= TurboBoost::State.new
|
95
|
-
end
|
96
|
-
|
97
|
-
delegate :cache_key, to: :state
|
98
|
-
|
99
|
-
# Same implementation as ActionController::Base but with public visibility
|
100
|
-
def cookies
|
101
|
-
controller.request.cookie_jar
|
102
|
-
end
|
103
|
-
|
104
|
-
def [](*keys, default: nil)
|
105
|
-
state.read(*keys, default: default)
|
106
|
-
end
|
107
|
-
|
108
|
-
def []=(*keys, value)
|
109
|
-
state_will_change! if value != self[*keys]
|
110
|
-
value.nil? ? state.delete(*keys) : state.write(*keys, value)
|
111
|
-
end
|
112
|
-
|
113
|
-
def provisional_state
|
114
|
-
@provisional_state ||= TurboBoost::State::ProvisionalState.new(self)
|
115
|
-
end
|
116
|
-
|
117
|
-
alias_method :now, :provisional_state
|
118
|
-
|
119
|
-
def clear
|
120
|
-
provisional_state.clear
|
121
|
-
state.clear
|
122
|
-
end
|
123
|
-
|
124
|
-
def payload
|
125
|
-
provisional_state.clear
|
126
|
-
state.shrink!
|
127
|
-
state.payload
|
128
|
-
end
|
129
|
-
|
130
|
-
def ordinal_payload
|
131
|
-
provisional_state.clear
|
132
|
-
state.shrink!
|
133
|
-
state.prune! max_bytesize: TurboBoost::Commands.config.max_cookie_size
|
134
|
-
state.ordinal_payload
|
135
|
-
end
|
136
|
-
|
137
|
-
def write_cookie
|
138
|
-
return unless changed? || cookie.blank?
|
139
|
-
cookies.signed["turbo_boost.state"] = {value: ordinal_payload, path: "/", expires: 1.day.from_now}
|
140
|
-
changes_applied
|
141
|
-
rescue => error
|
142
|
-
Rails.logger.error "Failed to write the TurboBoost::State cookie! #{error.message}"
|
143
|
-
end
|
144
|
-
|
145
|
-
private
|
146
|
-
|
147
|
-
attr_reader :state
|
148
|
-
|
149
|
-
def headers
|
150
|
-
controller.request.headers.select { |(key, _)| key.match?(/TURBOBOOST_STATE/i) }.sort
|
151
|
-
end
|
152
|
-
|
153
|
-
# State that exists on the client.
|
154
|
-
def header
|
155
|
-
headers.map(&:last).join
|
156
|
-
end
|
157
|
-
|
158
|
-
# State that the server last rendered with.
|
159
|
-
def cookie
|
160
|
-
cookies.signed["turbo_boost.state"]
|
161
|
-
end
|
162
|
-
end
|