turbo_boost-commands 0.2.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|