turbo_boost-commands 0.2.2 → 0.3.1
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 +4 -4
- data/README.md +122 -37
- data/app/assets/builds/@turbo-boost/commands.js +1 -1
- data/app/assets/builds/@turbo-boost/commands.js.map +4 -4
- data/app/assets/builds/@turbo-boost/commands.metafile.json +1 -1
- data/app/controllers/concerns/turbo_boost/commands/controller.rb +1 -1
- data/app/javascript/drivers/index.js +1 -1
- data/app/javascript/elements.js +0 -1
- data/app/javascript/events.js +6 -3
- data/app/javascript/headers.js +2 -2
- data/app/javascript/index.js +20 -11
- data/app/javascript/invoker.js +2 -10
- data/app/javascript/lifecycle.js +3 -6
- data/app/javascript/logger.js +29 -2
- data/app/javascript/renderer.js +11 -5
- data/app/javascript/schema.js +2 -1
- data/app/javascript/state/index.js +50 -33
- data/app/javascript/state/observable.js +1 -1
- data/app/javascript/state/page.js +34 -0
- data/app/javascript/state/storage.js +11 -0
- data/app/javascript/turbo.js +0 -10
- data/app/javascript/version.js +1 -1
- data/lib/turbo_boost/commands/attribute_set.rb +8 -0
- data/lib/turbo_boost/commands/command.rb +8 -3
- data/lib/turbo_boost/commands/command_callbacks.rb +23 -6
- data/lib/turbo_boost/commands/command_validator.rb +44 -0
- data/lib/turbo_boost/commands/controller_pack.rb +10 -10
- data/lib/turbo_boost/commands/engine.rb +14 -10
- data/lib/turbo_boost/commands/errors.rb +15 -8
- data/lib/turbo_boost/commands/{middleware.rb → middlewares/entry_middleware.rb} +30 -21
- data/lib/turbo_boost/commands/middlewares/exit_middleware.rb +63 -0
- data/lib/turbo_boost/commands/patches/action_view_helpers_tag_helper_tag_builder_patch.rb +10 -2
- data/lib/turbo_boost/commands/responder.rb +28 -0
- data/lib/turbo_boost/commands/runner.rb +150 -186
- data/lib/turbo_boost/commands/sanitizer.rb +1 -1
- data/lib/turbo_boost/commands/state.rb +97 -47
- data/lib/turbo_boost/commands/state_store.rb +72 -0
- data/lib/turbo_boost/commands/token_validator.rb +51 -0
- data/lib/turbo_boost/commands/version.rb +1 -1
- metadata +29 -8
@@ -1,16 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module TurboBoost::Commands
|
4
|
-
class
|
5
|
-
|
6
|
-
class InvalidClassError < StandardError; end
|
7
|
-
|
8
|
-
class InvalidMethodError < StandardError; end
|
9
|
-
|
10
|
-
class InvalidElementError < StandardError; end
|
4
|
+
class StateError < StandardError
|
5
|
+
end
|
11
6
|
|
12
7
|
class CommandError < StandardError
|
13
|
-
def initialize(*messages, command:, http_status
|
8
|
+
def initialize(*messages, command:, http_status: :internal_server_error, cause: nil)
|
14
9
|
@cause = cause
|
15
10
|
@command = command
|
16
11
|
@http_status_code = TurboBoost::Commands.http_status_code(http_status)
|
@@ -30,6 +25,18 @@ module TurboBoost::Commands
|
|
30
25
|
end
|
31
26
|
end
|
32
27
|
|
28
|
+
class InvalidTokenError < CommandError
|
29
|
+
end
|
30
|
+
|
31
|
+
class InvalidClassError < CommandError
|
32
|
+
end
|
33
|
+
|
34
|
+
class InvalidMethodError < CommandError
|
35
|
+
end
|
36
|
+
|
37
|
+
class InvalidElementError < CommandError
|
38
|
+
end
|
39
|
+
|
33
40
|
class AbortError < CommandError
|
34
41
|
def initialize(*messages, **kwargs)
|
35
42
|
messages.prepend "TurboBoost Command intentionally aborted via `throw` in a `[before,after,around]_command` lifecycle callback!"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class TurboBoost::Commands::
|
3
|
+
class TurboBoost::Commands::EntryMiddleware
|
4
4
|
PATH = "/turbo-boost-command-invocation"
|
5
5
|
|
6
6
|
def initialize(app)
|
@@ -35,6 +35,11 @@ class TurboBoost::Commands::Middleware
|
|
35
35
|
false
|
36
36
|
end
|
37
37
|
|
38
|
+
def convert_to_get_request?(driver)
|
39
|
+
return true if driver == "frame" || driver == "window"
|
40
|
+
false
|
41
|
+
end
|
42
|
+
|
38
43
|
# Modifies the given POST request so Rails sees it as GET.
|
39
44
|
#
|
40
45
|
# The posted JSON body content holds the TurboBoost Command meta data.
|
@@ -42,19 +47,21 @@ class TurboBoost::Commands::Middleware
|
|
42
47
|
#
|
43
48
|
# @example POST payload for: /turbo-boost-command-invocation
|
44
49
|
# {
|
45
|
-
# "
|
46
|
-
# "
|
47
|
-
# "
|
48
|
-
# "
|
49
|
-
# "
|
50
|
-
# "
|
51
|
-
# "
|
52
|
-
#
|
50
|
+
# "csrfToken" => "..." # Rails' CSRF token
|
51
|
+
# "id" => "turbo-command-f824ded1-a86e-4a36-9442-ea2165a64569", # Unique ID for the command invocation
|
52
|
+
# "name" => "ExampleCommand#perform", # Name of command being invoked
|
53
|
+
# "elementId" => nil, # Triggering element's DOM id
|
54
|
+
# "elementAttributes" => {...}, # Triggering element's attributes
|
55
|
+
# "startedAt" => 1708213193567, # Time the command was invoked
|
56
|
+
# "elementCache" => {...}, # Cache of ALL tracked element attributes (optimistic changes)
|
57
|
+
# "state" => { # State ... TODO: HOPSOFT
|
58
|
+
# "page" => {...}, # - transient page state (element attributes, etc.)
|
59
|
+
# "signed" => "", # - signed state used for the last server render (untampered)
|
60
|
+
# "unsigned" => {...} # - state with optimistic changes from the client
|
53
61
|
# },
|
54
|
-
# "
|
55
|
-
# "
|
56
|
-
# "
|
57
|
-
# "src" => "/basic_command.turbo_stream" # the URL to present to Rails (turbo-frame src, window location, etc.)
|
62
|
+
# "driver" => "frame", # Driver used to invoke the command
|
63
|
+
# "frameId" => "...", # TurboFrame id (if applicable)
|
64
|
+
# "src" => "..." # URL to present to Rails (turbo-frame src, window location, etc.)
|
58
65
|
# }
|
59
66
|
#
|
60
67
|
# @param request [Rack::Request] the request to modify
|
@@ -64,20 +71,22 @@ class TurboBoost::Commands::Middleware
|
|
64
71
|
|
65
72
|
request.env.tap do |env|
|
66
73
|
# Store the command params in the environment
|
67
|
-
env["
|
68
|
-
|
69
|
-
# Change the method from POST to GET
|
70
|
-
env["REQUEST_METHOD"] = "GET"
|
74
|
+
env["turbo_boost_command_params"] = params
|
71
75
|
|
72
76
|
# Update the URI, PATH_INFO, and QUERY_STRING
|
73
77
|
env["REQUEST_URI"] = uri.to_s if env.key?("REQUEST_URI")
|
74
78
|
env["PATH_INFO"] = uri.path
|
75
79
|
env["QUERY_STRING"] = uri.query.to_s
|
76
80
|
|
77
|
-
#
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
+
# Change the method from POST to GET
|
82
|
+
if convert_to_get_request?(params["driver"])
|
83
|
+
env["REQUEST_METHOD"] = "GET"
|
84
|
+
|
85
|
+
# Clear the body and related headers so the appears and behaves like a GET
|
86
|
+
env["rack.input"] = StringIO.new
|
87
|
+
env["CONTENT_LENGTH"] = "0"
|
88
|
+
env.delete("CONTENT_TYPE")
|
89
|
+
end
|
81
90
|
end
|
82
91
|
rescue => error
|
83
92
|
puts "#{self.class.name} failed to modify the request! #{error.message}"
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TurboBoost::Commands::ExitMiddleware
|
4
|
+
BODY_PATTERN = /<\/\s*body/io
|
5
|
+
TURBO_FRAME_PATTERN = /<\/\s*turbo-frame/io
|
6
|
+
TURBO_STREAM_PATTERN = /<\/\s*turbo-stream/io
|
7
|
+
TAIL_PATTERN = /\z/io
|
8
|
+
|
9
|
+
def initialize(app)
|
10
|
+
@app = app
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
@params = env["turbo_boost_command_params"]
|
15
|
+
response = @app.call(env)
|
16
|
+
return modify!(env, response) if modify?(env)
|
17
|
+
response
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def modify?(env)
|
23
|
+
env["turbo_boost_command_responder"].is_a? TurboBoost::Commands::Responder
|
24
|
+
end
|
25
|
+
|
26
|
+
def modify!(env, response)
|
27
|
+
responder = env["turbo_boost_command_responder"]
|
28
|
+
status, headers, body = response
|
29
|
+
new_body = body_to_a(body).join
|
30
|
+
|
31
|
+
case response_type(new_body)
|
32
|
+
when :body
|
33
|
+
match = new_body.match(BODY_PATTERN).to_s
|
34
|
+
new_body.sub! BODY_PATTERN, [responder.body, match].join
|
35
|
+
when :frame
|
36
|
+
match = new_body.match(TURBO_FRAME_PATTERN).to_s
|
37
|
+
new_body.sub! TURBO_FRAME_PATTERN, [responder.body, match].join
|
38
|
+
else
|
39
|
+
new_body << responder.body
|
40
|
+
end
|
41
|
+
|
42
|
+
[status, headers.merge(responder.headers), [new_body]]
|
43
|
+
rescue => error
|
44
|
+
Rails.logger.error "TurboBoost::Commands::Runner failed to append to the response! #{error.message}"
|
45
|
+
[status, headers, body]
|
46
|
+
end
|
47
|
+
|
48
|
+
def body_to_a(body)
|
49
|
+
return [] if body.nil?
|
50
|
+
return body if body.is_a?(Array)
|
51
|
+
return [body] if body.is_a?(String)
|
52
|
+
return body.to_ary if body.respond_to?(:to_ary)
|
53
|
+
return body.each.to_a if body.respond_to?(:each)
|
54
|
+
[body.to_s]
|
55
|
+
end
|
56
|
+
|
57
|
+
def response_type(body)
|
58
|
+
return :body if body.match?(BODY_PATTERN)
|
59
|
+
return :frame if body.match?(TURBO_FRAME_PATTERN)
|
60
|
+
return :stream if body.match?(TURBO_STREAM_PATTERN)
|
61
|
+
:unknown
|
62
|
+
end
|
63
|
+
end
|
@@ -4,7 +4,15 @@ require_relative "../attribute_hydration"
|
|
4
4
|
|
5
5
|
module TurboBoost::Commands::Patches::ActionViewHelpersTagHelperTagBuilderPatch
|
6
6
|
def tag_options(options, ...)
|
7
|
-
|
8
|
-
|
7
|
+
options = turbo_boost&.state&.tag_options(options) || options
|
8
|
+
options = TurboBoost::Commands::AttributeHydration.dehydrate(options)
|
9
|
+
super(options, ...)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def turbo_boost
|
15
|
+
return nil unless @view_context.respond_to?(:turbo_boost)
|
16
|
+
@view_context.turbo_boost
|
9
17
|
end
|
10
18
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TurboBoost::Commands::Responder
|
4
|
+
attr_accessor :headers
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@headers = HashWithIndifferentAccess.new
|
8
|
+
@body = Set.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def body
|
12
|
+
@body.join.html_safe
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_header(key, value)
|
16
|
+
headers[key.to_s.downcase] = value.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_content(content)
|
20
|
+
@body << sanitizer.sanitize(content) + "\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def sanitizer
|
26
|
+
TurboBoost::Commands::Sanitizer.instance
|
27
|
+
end
|
28
|
+
end
|