sinatra-inertia 0.1.4 → 0.1.5
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/CHANGELOG.md +6 -0
- data/lib/sinatra/inertia/async_sources.rb +2 -2
- data/lib/sinatra/inertia/csrf_middleware.rb +37 -26
- data/lib/sinatra/inertia/deferred.rb +2 -3
- data/lib/sinatra/inertia/helpers.rb +37 -27
- data/lib/sinatra/inertia/middleware.rb +12 -11
- data/lib/sinatra/inertia/response.rb +51 -28
- data/lib/sinatra/inertia/version.rb +1 -1
- data/lib/sinatra/inertia.rb +36 -25
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9172865c13484ad3352d36558c065514b1f3f175fd70ec8ce4f7cde77fbc3f8
|
|
4
|
+
data.tar.gz: f308c6741aed6cf48f7fc0588e7e0e11a7d5bad99b07000a20982fa01ee894b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0c4c38a5f2a2a2f16b5f3d449d442a7bd378fb491ae078c67a71543c6501e4412b93fc85751d54a0da0dd907d5f66340602aeef51a0899a990de921c495a5e28
|
|
7
|
+
data.tar.gz: 73fabe7fc1fb6191b1c454785b5450f2889e784ea12890587d203e7ccf216ca1143f51241ab06ea7eff6347bd83fb0c7b5066fb0b95e4ac413af55f0b8cc77ab
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5 — 2026-06-25
|
|
4
|
+
|
|
5
|
+
- Extract the Ruby-way Kagero layer into the separate `sinatra-kagero` gem.
|
|
6
|
+
- Keep `sinatra-inertia` focused on the Inertia v2 wire protocol.
|
|
7
|
+
- Remove Phlex and Literal from the `sinatra-inertia` runtime dependency surface.
|
|
8
|
+
|
|
3
9
|
## 0.1.4 — 2026-05-03
|
|
4
10
|
|
|
5
11
|
- Add the recommended Sinatra-native page API: `render 'Component', props`,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
if defined?(::HomuraRuntime) && defined?(::HomuraRuntime::AsyncRegistry)
|
|
14
14
|
::HomuraRuntime::AsyncRegistry.register_async_source do
|
|
15
|
-
async_method
|
|
16
|
-
async_method
|
|
15
|
+
async_method("Sinatra::Inertia::Response", :to_h)
|
|
16
|
+
async_method("Sinatra::Inertia::Response", :to_json)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require "rack/utils"
|
|
5
5
|
|
|
6
6
|
module Sinatra
|
|
7
7
|
module Inertia
|
|
@@ -36,9 +36,9 @@ module Sinatra
|
|
|
36
36
|
# this only when the consumer ships its own CSRF defence. Safer
|
|
37
37
|
# defaults assume the gem is responsible.
|
|
38
38
|
class CSRFMiddleware
|
|
39
|
-
COOKIE_NAME =
|
|
40
|
-
HEADER_KEY =
|
|
41
|
-
ENV_TOKEN_KEY =
|
|
39
|
+
COOKIE_NAME = "XSRF-TOKEN"
|
|
40
|
+
HEADER_KEY = "HTTP_X_XSRF_TOKEN"
|
|
41
|
+
ENV_TOKEN_KEY = "sinatra.inertia.csrf_token"
|
|
42
42
|
SAFE_METHODS = %w[GET HEAD OPTIONS].freeze
|
|
43
43
|
|
|
44
44
|
def initialize(app, same_site: :Lax)
|
|
@@ -54,31 +54,34 @@ module Sinatra
|
|
|
54
54
|
unless safe_method?(env)
|
|
55
55
|
header = env[HEADER_KEY].to_s
|
|
56
56
|
if existing.nil? || header.empty? || !secure_compare(header, existing)
|
|
57
|
-
return forbidden(
|
|
57
|
+
return (forbidden(
|
|
58
|
+
"CSRF token mismatch (expected matching X-XSRF-TOKEN header to XSRF-TOKEN cookie)"
|
|
59
|
+
))
|
|
58
60
|
end
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
status, headers, body = @app.call(env)
|
|
62
|
-
unless existing == token
|
|
63
|
-
set_cookie!(headers, token)
|
|
64
|
-
end
|
|
64
|
+
set_cookie!(headers, token) unless existing == token
|
|
65
65
|
[status, headers, body]
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
private
|
|
69
69
|
|
|
70
70
|
def safe_method?(env)
|
|
71
|
-
SAFE_METHODS.include?(env[
|
|
71
|
+
SAFE_METHODS.include?(env["REQUEST_METHOD"])
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def read_cookie(env)
|
|
75
|
-
cookie_header = env[
|
|
75
|
+
cookie_header = env["HTTP_COOKIE"].to_s
|
|
76
76
|
return nil if cookie_header.empty?
|
|
77
|
-
cookie_header
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
cookie_header
|
|
78
|
+
.split(/;\s*/)
|
|
79
|
+
.each do |pair|
|
|
80
|
+
name, value = pair.split("=", 2)
|
|
81
|
+
next unless name == COOKIE_NAME
|
|
82
|
+
return value
|
|
83
|
+
end
|
|
84
|
+
|
|
82
85
|
nil
|
|
83
86
|
end
|
|
84
87
|
|
|
@@ -99,26 +102,34 @@ module Sinatra
|
|
|
99
102
|
|
|
100
103
|
def set_cookie!(headers, token)
|
|
101
104
|
attrs = "#{COOKIE_NAME}=#{token}; Path=/; SameSite=#{@same_site}"
|
|
102
|
-
existing = headers[
|
|
105
|
+
existing = headers["Set-Cookie"]
|
|
103
106
|
# Normalise to a newline-joined String regardless of Rack 2/3
|
|
104
107
|
# conventions or downstream worker-runtime quirks. The Cloudflare
|
|
105
108
|
# Workers adapter that homura ships with serialises Array-shaped
|
|
106
109
|
# `Set-Cookie` headers as a literal JSON array, which breaks
|
|
107
110
|
# cookie parsing on the client.
|
|
108
111
|
prev = case existing
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
when nil, ""
|
|
113
|
+
nil
|
|
114
|
+
when Array
|
|
115
|
+
existing.join("\n")
|
|
116
|
+
else
|
|
117
|
+
existing.to_s
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
headers["Set-Cookie"] = prev ? "#{prev}\n#{attrs}" : attrs
|
|
114
121
|
end
|
|
115
122
|
|
|
116
123
|
def forbidden(message)
|
|
117
124
|
body = "#{message}\n"
|
|
118
|
-
[
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
[
|
|
126
|
+
403,
|
|
127
|
+
{
|
|
128
|
+
"Content-Type" => "text/plain; charset=utf-8",
|
|
129
|
+
"Content-Length" => body.bytesize.to_s
|
|
130
|
+
},
|
|
131
|
+
[body]
|
|
132
|
+
]
|
|
122
133
|
end
|
|
123
134
|
end
|
|
124
135
|
end
|
|
@@ -18,7 +18,7 @@ module Sinatra
|
|
|
18
18
|
class Prop
|
|
19
19
|
attr_reader :block, :value, :group
|
|
20
20
|
|
|
21
|
-
def initialize(block: nil, value: nil, group:
|
|
21
|
+
def initialize(block: nil, value: nil, group: "default")
|
|
22
22
|
@block = block
|
|
23
23
|
@value = value
|
|
24
24
|
@group = group
|
|
@@ -43,7 +43,6 @@ module Sinatra
|
|
|
43
43
|
# Should arrays returned by this prop be merged with the client's
|
|
44
44
|
# existing array (Inertia 2 merge semantics)?
|
|
45
45
|
def merge? = false
|
|
46
|
-
|
|
47
46
|
end
|
|
48
47
|
|
|
49
48
|
class AlwaysProp < Prop
|
|
@@ -71,7 +70,7 @@ module Sinatra
|
|
|
71
70
|
module_function
|
|
72
71
|
|
|
73
72
|
def always(value = nil, &block) = AlwaysProp.new(block: block, value: value)
|
|
74
|
-
def defer(group:
|
|
73
|
+
def defer(group: "default", &block) = DeferredProp.new(block: block, group: group)
|
|
75
74
|
def optional(&block) = OptionalProp.new(block: block)
|
|
76
75
|
def lazy(&block) = LazyProp.new(block: block)
|
|
77
76
|
def merge(value = nil, &block) = MergeProp.new(block: block, value: value)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "response"
|
|
5
6
|
|
|
6
7
|
module Sinatra
|
|
7
8
|
module Inertia
|
|
@@ -23,12 +24,13 @@ module Sinatra
|
|
|
23
24
|
version = current_inertia_version
|
|
24
25
|
shared = current_inertia_shared
|
|
25
26
|
encrypt = if !@inertia_encrypt_history_override.nil?
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
@inertia_encrypt_history_override == true
|
|
28
|
+
elsif settings.respond_to?(:inertia_encrypt_history)
|
|
29
|
+
settings.inertia_encrypt_history == true
|
|
30
|
+
else
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
32
34
|
clear = @inertia_clear_history == true
|
|
33
35
|
|
|
34
36
|
# Set the protocol response headers BEFORE we touch any async
|
|
@@ -40,8 +42,8 @@ module Sinatra
|
|
|
40
42
|
# regardless of how the underlying runtime schedules the
|
|
41
43
|
# awaited continuation.
|
|
42
44
|
if inertia_request?
|
|
43
|
-
content_type
|
|
44
|
-
headers
|
|
45
|
+
content_type("application/json; charset=utf-8")
|
|
46
|
+
headers("X-Inertia" => "true", "Vary" => "X-Inertia")
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
# Read errors *before* sweeping so the response carries them, then
|
|
@@ -69,7 +71,7 @@ module Sinatra
|
|
|
69
71
|
|
|
70
72
|
@page = page_hash
|
|
71
73
|
@page_json = ::Rack::Utils.escape_html(page_json)
|
|
72
|
-
erb
|
|
74
|
+
erb(layout, layout: false)
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
# Natural page-rendering API: `render 'Component', props_hash`.
|
|
@@ -80,10 +82,20 @@ module Sinatra
|
|
|
80
82
|
def render(*args, **kwargs, &block)
|
|
81
83
|
first = args.first
|
|
82
84
|
if args.length == 1 && first.is_a?(Hash) && first.key?(:inertia)
|
|
83
|
-
inertia(
|
|
85
|
+
inertia(
|
|
86
|
+
first[:inertia],
|
|
87
|
+
props: first[:props] || {},
|
|
88
|
+
layout: first[:layout]
|
|
89
|
+
)
|
|
84
90
|
elsif kwargs.key?(:inertia) && args.empty?
|
|
85
|
-
inertia(
|
|
86
|
-
|
|
91
|
+
inertia(
|
|
92
|
+
kwargs[:inertia],
|
|
93
|
+
props: kwargs[:props] || {},
|
|
94
|
+
layout: kwargs[:layout]
|
|
95
|
+
)
|
|
96
|
+
elsif first.is_a?(String) &&
|
|
97
|
+
args.length <= 2 &&
|
|
98
|
+
(args.length == 1 || args[1].is_a?(Hash))
|
|
87
99
|
layout = kwargs.delete(:layout)
|
|
88
100
|
props = {}
|
|
89
101
|
props.merge!(args[1]) if args[1].is_a?(Hash)
|
|
@@ -97,7 +109,7 @@ module Sinatra
|
|
|
97
109
|
end
|
|
98
110
|
|
|
99
111
|
def inertia_request?
|
|
100
|
-
request.env[
|
|
112
|
+
request.env["HTTP_X_INERTIA"] == "true"
|
|
101
113
|
end
|
|
102
114
|
|
|
103
115
|
def page_request?
|
|
@@ -112,14 +124,14 @@ module Sinatra
|
|
|
112
124
|
# exchange is already handled by the Inertia client; this helper is
|
|
113
125
|
# mainly for hidden-field forms or non-XHR submissions.
|
|
114
126
|
def csrf_token
|
|
115
|
-
request.env[
|
|
127
|
+
request.env["sinatra.inertia.csrf_token"]
|
|
116
128
|
end
|
|
117
129
|
|
|
118
130
|
def always(value = nil, &block)
|
|
119
131
|
Sinatra::Inertia.always(value, &block)
|
|
120
132
|
end
|
|
121
133
|
|
|
122
|
-
def defer(group:
|
|
134
|
+
def defer(group: "default", &block)
|
|
123
135
|
Sinatra::Inertia.defer(group: group, &block)
|
|
124
136
|
end
|
|
125
137
|
|
|
@@ -143,10 +155,9 @@ module Sinatra
|
|
|
143
155
|
merged = {}
|
|
144
156
|
blocks.each do |b|
|
|
145
157
|
v = instance_exec(&b)
|
|
146
|
-
if v.is_a?(Hash)
|
|
147
|
-
merged = deep_merge(merged, v)
|
|
148
|
-
end
|
|
158
|
+
merged = deep_merge(merged, v) if v.is_a?(Hash)
|
|
149
159
|
end
|
|
160
|
+
|
|
150
161
|
merged
|
|
151
162
|
end
|
|
152
163
|
|
|
@@ -154,10 +165,11 @@ module Sinatra
|
|
|
154
165
|
# Asset version
|
|
155
166
|
def current_inertia_version
|
|
156
167
|
v = if settings.respond_to?(:page_version)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
settings.page_version
|
|
169
|
+
elsif settings.respond_to?(:inertia_version)
|
|
170
|
+
settings.inertia_version
|
|
171
|
+
end
|
|
172
|
+
|
|
161
173
|
v.respond_to?(:call) ? v.call.to_s : v.to_s
|
|
162
174
|
end
|
|
163
175
|
|
|
@@ -210,9 +222,7 @@ module Sinatra
|
|
|
210
222
|
# assigning nil instead, which the JSON encoder still emits as
|
|
211
223
|
# `null` and makes `inertia_errors_payload` treat the field as
|
|
212
224
|
# absent on the next visit.
|
|
213
|
-
if session.respond_to?(:[]=)
|
|
214
|
-
session[:_inertia_errors] = nil
|
|
215
|
-
end
|
|
225
|
+
session[:_inertia_errors] = nil if session.respond_to?(:[]=)
|
|
216
226
|
end
|
|
217
227
|
|
|
218
228
|
private
|
|
@@ -19,8 +19,8 @@ module Sinatra
|
|
|
19
19
|
# The middleware is `register`-ed automatically by `Sinatra::Inertia`
|
|
20
20
|
# via `app.use`, so consumer apps don't need to wire it manually.
|
|
21
21
|
class Middleware
|
|
22
|
-
INERTIA_HEADER =
|
|
23
|
-
INERTIA_VERSION_HEADER =
|
|
22
|
+
INERTIA_HEADER = "HTTP_X_INERTIA"
|
|
23
|
+
INERTIA_VERSION_HEADER = "HTTP_X_INERTIA_VERSION"
|
|
24
24
|
|
|
25
25
|
def initialize(app, version:)
|
|
26
26
|
@app = app
|
|
@@ -29,10 +29,10 @@ module Sinatra
|
|
|
29
29
|
|
|
30
30
|
def call(env)
|
|
31
31
|
if inertia_get?(env) && version_mismatch?(env)
|
|
32
|
-
location = env[
|
|
32
|
+
location = env["REQUEST_URI"] || build_url(env)
|
|
33
33
|
return [
|
|
34
34
|
409,
|
|
35
|
-
{
|
|
35
|
+
{"X-Inertia-Location" => location, "Vary" => "X-Inertia"},
|
|
36
36
|
[]
|
|
37
37
|
]
|
|
38
38
|
end
|
|
@@ -44,8 +44,9 @@ module Sinatra
|
|
|
44
44
|
# requests: a Sinatra app may serve plain REST endpoints alongside
|
|
45
45
|
# Inertia pages, and rewriting their 302s would silently change
|
|
46
46
|
# HTTP semantics for non-Inertia clients.
|
|
47
|
-
if status == 302 &&
|
|
48
|
-
|
|
47
|
+
if status == 302 &&
|
|
48
|
+
env[INERTIA_HEADER] == "true" &&
|
|
49
|
+
%w[POST PUT PATCH DELETE].include?(env["REQUEST_METHOD"])
|
|
49
50
|
status = 303
|
|
50
51
|
end
|
|
51
52
|
|
|
@@ -55,7 +56,7 @@ module Sinatra
|
|
|
55
56
|
private
|
|
56
57
|
|
|
57
58
|
def inertia_get?(env)
|
|
58
|
-
env[INERTIA_HEADER] ==
|
|
59
|
+
env[INERTIA_HEADER] == "true" && env["REQUEST_METHOD"] == "GET"
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
def version_mismatch?(env)
|
|
@@ -70,10 +71,10 @@ module Sinatra
|
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
def build_url(env)
|
|
73
|
-
scheme = env[
|
|
74
|
-
host = env[
|
|
75
|
-
path = env[
|
|
76
|
-
qs = env[
|
|
74
|
+
scheme = env["rack.url_scheme"] || "http"
|
|
75
|
+
host = env["HTTP_HOST"] || env["SERVER_NAME"]
|
|
76
|
+
path = env["PATH_INFO"]
|
|
77
|
+
qs = env["QUERY_STRING"]
|
|
77
78
|
full = +"#{scheme}://#{host}#{path}"
|
|
78
79
|
full << "?#{qs}" if qs && !qs.empty?
|
|
79
80
|
full
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative "deferred"
|
|
4
4
|
|
|
5
5
|
module Sinatra
|
|
6
6
|
module Inertia
|
|
@@ -12,12 +12,29 @@ module Sinatra
|
|
|
12
12
|
# responses, or interpolated into the layout's `data-page` attribute
|
|
13
13
|
# for full HTML responses.
|
|
14
14
|
class Response
|
|
15
|
-
attr_reader
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
attr_reader(
|
|
16
|
+
:component,
|
|
17
|
+
:props,
|
|
18
|
+
:request,
|
|
19
|
+
:version,
|
|
20
|
+
:url,
|
|
21
|
+
:encrypt_history,
|
|
22
|
+
:clear_history,
|
|
23
|
+
:shared,
|
|
24
|
+
:errors
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def initialize(
|
|
28
|
+
component:,
|
|
29
|
+
props:,
|
|
30
|
+
request:,
|
|
31
|
+
version:,
|
|
32
|
+
url: nil,
|
|
33
|
+
encrypt_history: false,
|
|
34
|
+
clear_history: false,
|
|
35
|
+
shared: {},
|
|
36
|
+
errors: nil
|
|
37
|
+
)
|
|
21
38
|
@component = component
|
|
22
39
|
@props = props || {}
|
|
23
40
|
@request = request
|
|
@@ -64,6 +81,7 @@ module Sinatra
|
|
|
64
81
|
elsif value.is_a?(Prop) && value.deferred?
|
|
65
82
|
(deferred_groups[value.group] ||= []) << k.to_s
|
|
66
83
|
end
|
|
84
|
+
|
|
67
85
|
i += 1
|
|
68
86
|
end
|
|
69
87
|
|
|
@@ -85,19 +103,19 @@ module Sinatra
|
|
|
85
103
|
private
|
|
86
104
|
|
|
87
105
|
def partial_request?
|
|
88
|
-
request.env[
|
|
106
|
+
request.env["HTTP_X_INERTIA_PARTIAL_COMPONENT"] == component
|
|
89
107
|
end
|
|
90
108
|
|
|
91
109
|
def partial_data_keys
|
|
92
|
-
raw = request.env[
|
|
110
|
+
raw = request.env["HTTP_X_INERTIA_PARTIAL_DATA"].to_s
|
|
93
111
|
return nil if raw.empty?
|
|
94
|
-
raw.split(
|
|
112
|
+
raw.split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
|
|
95
113
|
end
|
|
96
114
|
|
|
97
115
|
def partial_except_keys
|
|
98
|
-
raw = request.env[
|
|
116
|
+
raw = request.env["HTTP_X_INERTIA_PARTIAL_EXCEPT"].to_s
|
|
99
117
|
return nil if raw.empty?
|
|
100
|
-
raw.split(
|
|
118
|
+
raw.split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
|
|
101
119
|
end
|
|
102
120
|
|
|
103
121
|
# Inertia 2.0: `X-Inertia-Reset: a,b` tells the server "the client
|
|
@@ -106,29 +124,30 @@ module Sinatra
|
|
|
106
124
|
# outbound `mergeProps` array — the value itself is still resolved
|
|
107
125
|
# and emitted, so the client just doesn't accumulate it.
|
|
108
126
|
def reset_keys
|
|
109
|
-
raw = request.env[
|
|
127
|
+
raw = request.env["HTTP_X_INERTIA_RESET"].to_s
|
|
110
128
|
return [] if raw.empty?
|
|
111
|
-
raw.split(
|
|
129
|
+
raw.split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
|
|
112
130
|
end
|
|
113
131
|
|
|
114
132
|
# Returns [included?, resolved_value]
|
|
115
133
|
def decide(value, key, partial, partial_data, partial_except)
|
|
116
134
|
if value.is_a?(Prop)
|
|
117
135
|
if value.always?
|
|
118
|
-
return
|
|
136
|
+
return true, value.resolve
|
|
119
137
|
elsif value.deferred?
|
|
120
|
-
return
|
|
121
|
-
return
|
|
138
|
+
return false, nil unless partial && partial_data&.include?(key)
|
|
139
|
+
return true, value.resolve
|
|
122
140
|
elsif value.optional?
|
|
123
|
-
return
|
|
124
|
-
return
|
|
141
|
+
return false, nil unless partial && partial_data&.include?(key)
|
|
142
|
+
return true, value.resolve
|
|
125
143
|
else
|
|
126
144
|
# merge / once / plain Prop
|
|
127
145
|
if partial
|
|
128
|
-
return
|
|
129
|
-
return
|
|
146
|
+
return false, nil if partial_data && !partial_data.include?(key)
|
|
147
|
+
return false, nil if partial_except&.include?(key)
|
|
130
148
|
end
|
|
131
|
-
|
|
149
|
+
|
|
150
|
+
return true, value.resolve
|
|
132
151
|
end
|
|
133
152
|
end
|
|
134
153
|
|
|
@@ -136,17 +155,19 @@ module Sinatra
|
|
|
136
155
|
# A bare Proc/Lambda is treated as plain lazy: resolved every
|
|
137
156
|
# request, but only when included.
|
|
138
157
|
if partial
|
|
139
|
-
return
|
|
140
|
-
return
|
|
158
|
+
return false, nil if partial_data && !partial_data.include?(key)
|
|
159
|
+
return false, nil if partial_except&.include?(key)
|
|
141
160
|
end
|
|
142
|
-
|
|
161
|
+
|
|
162
|
+
return true, value.call
|
|
143
163
|
end
|
|
144
164
|
|
|
145
165
|
# Plain value
|
|
146
166
|
if partial
|
|
147
|
-
return
|
|
148
|
-
return
|
|
167
|
+
return false, nil if partial_data && !partial_data.include?(key)
|
|
168
|
+
return false, nil if partial_except&.include?(key)
|
|
149
169
|
end
|
|
170
|
+
|
|
150
171
|
[true, value]
|
|
151
172
|
end
|
|
152
173
|
|
|
@@ -167,7 +188,9 @@ module Sinatra
|
|
|
167
188
|
# via `.__await__`. On MRI this branch is dead code (no Cloudflare
|
|
168
189
|
# constant, no js_promise?), so plain Ruby tests are unaffected.
|
|
169
190
|
def await_if_promise(value)
|
|
170
|
-
if defined?(::Cloudflare) &&
|
|
191
|
+
if defined?(::Cloudflare) &&
|
|
192
|
+
::Cloudflare.respond_to?(:js_promise?) &&
|
|
193
|
+
::Cloudflare.js_promise?(value)
|
|
171
194
|
value.__await__
|
|
172
195
|
else
|
|
173
196
|
value
|
data/lib/sinatra/inertia.rb
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
7
|
-
require_relative
|
|
8
|
-
require_relative
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
11
|
-
require_relative
|
|
3
|
+
require "sinatra/base"
|
|
4
|
+
|
|
5
|
+
require_relative "inertia/version"
|
|
6
|
+
require_relative "inertia/errors"
|
|
7
|
+
require_relative "inertia/deferred"
|
|
8
|
+
require_relative "inertia/response"
|
|
9
|
+
require_relative "inertia/middleware"
|
|
10
|
+
require_relative "inertia/csrf_middleware"
|
|
11
|
+
require_relative "inertia/helpers"
|
|
12
|
+
require_relative "inertia/async_sources"
|
|
12
13
|
|
|
13
14
|
module Sinatra
|
|
14
15
|
# Inertia.js v2 protocol adapter for Sinatra.
|
|
@@ -33,34 +34,44 @@ module Sinatra
|
|
|
33
34
|
def self.registered(app)
|
|
34
35
|
# Default settings. `page_*` is the recommended application-facing API;
|
|
35
36
|
# `inertia_*` remains supported for existing apps and lower-level tuning.
|
|
36
|
-
app.set
|
|
37
|
-
app.set
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
app.set(:inertia_version, "1") unless app.respond_to?(:inertia_version)
|
|
38
|
+
app.set(:inertia_layout, :layout) unless app.respond_to?(:inertia_layout)
|
|
39
|
+
unless app.respond_to?(:inertia_encrypt_history)
|
|
40
|
+
app.set(:inertia_encrypt_history, false)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
unless app.respond_to?(:inertia_csrf_protection)
|
|
44
|
+
app.set(:inertia_csrf_protection, true)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
app.set(:inertia_share_blocks, [])
|
|
41
48
|
|
|
42
49
|
# Mount CSRF middleware (double-submit XSRF-TOKEN cookie that
|
|
43
50
|
# @inertiajs/* clients honour). Opt-out via
|
|
44
51
|
# `set :inertia_csrf_protection, false` when the consumer ships
|
|
45
52
|
# its own (e.g. Rack::Protection::AuthenticityToken).
|
|
46
53
|
if app.settings.inertia_csrf_protection
|
|
47
|
-
app.use
|
|
54
|
+
app.use(Sinatra::Inertia::CSRFMiddleware)
|
|
48
55
|
end
|
|
49
56
|
|
|
50
57
|
# Mount protocol middleware (version mismatch + 303 redirect promotion).
|
|
51
|
-
app.use
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
app.use(
|
|
59
|
+
Sinatra::Inertia::Middleware,
|
|
60
|
+
version: lambda {
|
|
61
|
+
version = if app.settings.respond_to?(:page_version)
|
|
62
|
+
app.settings.page_version
|
|
63
|
+
else
|
|
64
|
+
app.settings.inertia_version
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
version.respond_to?(:call) ? version.call.to_s : version.to_s
|
|
68
|
+
}
|
|
69
|
+
)
|
|
59
70
|
|
|
60
71
|
# Class-level DSL: `share_props { ... }` registers a block whose return
|
|
61
72
|
# value is merged into every page's props.
|
|
62
73
|
app.define_singleton_method(:inertia_share) do |&block|
|
|
63
|
-
raise ArgumentError,
|
|
74
|
+
raise ArgumentError, "inertia_share requires a block" unless block
|
|
64
75
|
settings.inertia_share_blocks = settings.inertia_share_blocks + [block]
|
|
65
76
|
end
|
|
66
77
|
|
|
@@ -68,7 +79,7 @@ module Sinatra
|
|
|
68
79
|
inertia_share(&block)
|
|
69
80
|
end
|
|
70
81
|
|
|
71
|
-
app.helpers
|
|
82
|
+
app.helpers(Sinatra::Inertia::Helpers)
|
|
72
83
|
end
|
|
73
84
|
|
|
74
85
|
# Convenience module-level constructors so code can write
|