phlex-reactive 0.9.0 → 0.9.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/CHANGELOG.md +56 -1
- data/lib/phlex/reactive/js.rb +41 -14
- data/lib/phlex/reactive/response.rb +4 -0
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +4 -0
- 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: 0b8404995a25e3917e5785b4e7becaceff75359c5577dd82f8b44e8dd95e2049
|
|
4
|
+
data.tar.gz: f1a6e61d8d840aab2273f95405fc19e3e58295c54d2bf10e40ef21bc9bef2b75
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4218da118f87bb6319b54ec8583437f5d208305d936eedbd81b9eaf01ff17941f5a8b7baac98ef141b29ace3b6dbae937b0cee10368b482b2fef2ba9268c49f4
|
|
7
|
+
data.tar.gz: ead67e058d25a3443286e70f9a245746604cc584de2a0160446e56a306a99c5f95f8ec0536ca0ec5a1fd1205a3c63ecf27d741e3f8eb992396155c06a84bdb58
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,58 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.0] - 2026-07-03
|
|
10
|
+
|
|
11
|
+
Consolidates everything since 0.2.6; tags 0.2.7–0.4.8 shipped without changelog
|
|
12
|
+
sections, so this section spans the whole run to 0.9.0. The jump from 0.4.8 to
|
|
13
|
+
0.9.0 signals the Tier-2 API wave (#79–#120) and the run-up to 1.0, leaving 0.9.x
|
|
14
|
+
room for dogfood fixes.
|
|
15
|
+
|
|
16
|
+
### Upgrading from 0.4.x
|
|
17
|
+
|
|
18
|
+
- **BREAKING — an unknown param-type symbol now raises at boot, not at click
|
|
19
|
+
time (#109).** A `params:` schema declared with a type the library doesn't know
|
|
20
|
+
(a typo like `:strng`, or a type you meant to register) raises
|
|
21
|
+
`Phlex::Reactive::UnknownParamType` when the component class loads — the schema
|
|
22
|
+
is compiled once at declaration. Fix the typo or register the type. Previously a
|
|
23
|
+
bad type symbol slipped through to the request and failed obscurely per click.
|
|
24
|
+
- **Minimum Ruby is now 3.4** (was 3.2). Enabling the `it` block parameter
|
|
25
|
+
requires 3.4, so `required_ruby_version` is `>= 3.4.0`. Stay on the 0.3.x line
|
|
26
|
+
if you need Ruby 3.2/3.3.
|
|
27
|
+
- **`optimistic:` / `loading:` / `disable_with:` are now RESERVED `on(...)`
|
|
28
|
+
keywords** (#98, #99), joining `window:` / `once:` / `outside:` / `throttle:`
|
|
29
|
+
from #80. If an action of yours took a free param literally named `optimistic`,
|
|
30
|
+
`loading`, or `disable_with`, rename it — those names now configure the
|
|
31
|
+
client-side hint/loading behavior instead of passing through as an action param.
|
|
32
|
+
- **`flash_builder` → `stream_builder` rename (#113) — no action needed.** The
|
|
33
|
+
builder does far more than flashes, so it was renamed;
|
|
34
|
+
`Phlex::Reactive.flash_builder` and `reset_flash_builder!` remain **permanent
|
|
35
|
+
aliases** (no deprecation). Listed only for grep-ability if you referenced the
|
|
36
|
+
old names.
|
|
37
|
+
- **The client runtime now ships pre-minified (#148) — zero change for importmap
|
|
38
|
+
consumers.** The engine pins the `.min.js` build (106 KB → 22 KB, ~7.7 KB
|
|
39
|
+
gzipped) with a linked sourcemap, so devtools still shows readable source. If
|
|
40
|
+
you vendor the client file by hand, re-vendor from the minified build (the
|
|
41
|
+
dummy app's `spec/dummy/public/vendor/reactive_controller.js` is that build).
|
|
42
|
+
|
|
43
|
+
### Security
|
|
44
|
+
|
|
45
|
+
- **The signed-token version (`v`) now fails closed on a malformed value.**
|
|
46
|
+
`upgrade_token` type-guards `v` before comparing it: a non-integer or negative
|
|
47
|
+
`v` (only producible with the signing key, or by operator error — `v` lives
|
|
48
|
+
inside the signed blob) now returns `nil` → `400` through the endpoint's
|
|
49
|
+
`|| raise(InvalidToken)`, instead of a 500 on the `v > TOKEN_VERSION`
|
|
50
|
+
comparison or silently treating a negative `v` as a legacy passthrough. The
|
|
51
|
+
fail-closed-on-rollback contract (a `v` newer than this code understands → 400)
|
|
52
|
+
is unchanged.
|
|
53
|
+
- **The raw-array `js([...])` / `broadcast_js_to([...])` escape hatch now
|
|
54
|
+
re-applies the attribute-name allowlist server-side.** The `JS` builder rejects
|
|
55
|
+
event-handler (`on*`), URL-bearing, and `style` attribute names at build time;
|
|
56
|
+
the raw `[[op, args], …]` form skipped that Ruby-side check and relied on the
|
|
57
|
+
client interpreter alone. `Phlex::Reactive::JS.assert_ops_allowed!` restores
|
|
58
|
+
full server-side parity (defense in depth — the client still enforces it too),
|
|
59
|
+
so a hand-built ops array can't emit an `onclick`/`href`/`style` attr op.
|
|
60
|
+
|
|
9
61
|
### Changed
|
|
10
62
|
|
|
11
63
|
- **One inheritance-aware registry behind the Component DSL; `component.rb` split
|
|
@@ -1552,7 +1604,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
1552
1604
|
scaffolds a reactive component (and an RSpec spec when the app uses RSpec),
|
|
1553
1605
|
state-backed by default or record-backed with `--record`.
|
|
1554
1606
|
|
|
1555
|
-
[Unreleased]: https://github.com/mhenrixon/phlex-reactive/compare/v0.
|
|
1607
|
+
[Unreleased]: https://github.com/mhenrixon/phlex-reactive/compare/v0.9.0...HEAD
|
|
1608
|
+
[0.9.0]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.6...v0.9.0
|
|
1609
|
+
[0.2.6]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.5...v0.2.6
|
|
1610
|
+
[0.2.5]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.4...v0.2.5
|
|
1556
1611
|
[0.2.4]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.3...v0.2.4
|
|
1557
1612
|
[0.2.3]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.2...v0.2.3
|
|
1558
1613
|
[0.2.2]: https://github.com/mhenrixon/phlex-reactive/compare/v0.2.1...v0.2.2
|
data/lib/phlex/reactive/js.rb
CHANGED
|
@@ -44,6 +44,44 @@ module Phlex
|
|
|
44
44
|
URL_BEARING_ATTRS = %w[href src srcdoc action formaction xlink:href].freeze
|
|
45
45
|
EVENT_HANDLER_ATTR = /\Aon/i
|
|
46
46
|
|
|
47
|
+
# The attr ops whose args carry a "name" the allowlist must gate. Used to
|
|
48
|
+
# re-validate a RAW [op, args] list (the js([...]) / broadcast_js_to([...])
|
|
49
|
+
# escape hatch) that skips the builder's build-time attr_args check.
|
|
50
|
+
ATTR_NAME_OPS = %w[set_attr remove_attr toggle_attr].freeze
|
|
51
|
+
|
|
52
|
+
# Validate a raw ops list ([[op, args], ...] as passed to js/broadcast_js_to
|
|
53
|
+
# without the builder) against the attribute allowlist, so the escape hatch
|
|
54
|
+
# gets the SAME server-side default-deny as the JS chain (defense in depth;
|
|
55
|
+
# the client also enforces it). Non-attr ops and malformed entries pass
|
|
56
|
+
# through untouched — the client interpreter default-denies unknown ops.
|
|
57
|
+
def self.assert_ops_allowed!(list)
|
|
58
|
+
Array(list).each do |op, args|
|
|
59
|
+
next unless ATTR_NAME_OPS.include?(op.to_s) && args.is_a?(::Hash)
|
|
60
|
+
|
|
61
|
+
name = args["name"] || args[:name]
|
|
62
|
+
assert_allowed_attr(name.to_s) if name
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The attribute-name allowlist (issue #96), case-insensitive. Refuses
|
|
67
|
+
# event-handler (on*), URL-bearing, and style attributes. Enforced at build
|
|
68
|
+
# time by the instance builder AND on the raw-list escape hatch — two-sided
|
|
69
|
+
# default-deny with the client interpreter.
|
|
70
|
+
def self.assert_allowed_attr(name)
|
|
71
|
+
lower = name.downcase
|
|
72
|
+
if name.match?(EVENT_HANDLER_ATTR)
|
|
73
|
+
raise ArgumentError,
|
|
74
|
+
"#{self}: attribute #{name.inspect} is an event handler (on*) — refused (XSS). " \
|
|
75
|
+
"Client attr ops target hidden/disabled/open/selected/aria-*/data-* and classes."
|
|
76
|
+
end
|
|
77
|
+
return unless URL_BEARING_ATTRS.include?(lower) || lower == "style"
|
|
78
|
+
|
|
79
|
+
raise ArgumentError,
|
|
80
|
+
"#{self}: attribute #{name.inspect} is refused — URL-bearing attributes " \
|
|
81
|
+
"(#{URL_BEARING_ATTRS.join(", ")}) and `style` can't be set from client ops " \
|
|
82
|
+
"(injection surface). Use classes for styling; target aria-*/data-*/boolean attrs."
|
|
83
|
+
end
|
|
84
|
+
|
|
47
85
|
# The accumulated [name, args] op pairs, oldest first. Frozen.
|
|
48
86
|
attr_reader :ops
|
|
49
87
|
|
|
@@ -169,21 +207,10 @@ module Phlex
|
|
|
169
207
|
args.freeze
|
|
170
208
|
end
|
|
171
209
|
|
|
172
|
-
#
|
|
173
|
-
#
|
|
210
|
+
# Build-time attr-name allowlist for the instance builder — delegates to the
|
|
211
|
+
# shared class-method check (also used by the raw-list escape hatch).
|
|
174
212
|
def assert_allowed_attr(name)
|
|
175
|
-
|
|
176
|
-
if name.match?(EVENT_HANDLER_ATTR)
|
|
177
|
-
raise ArgumentError,
|
|
178
|
-
"#{self.class}: attribute #{name.inspect} is an event handler (on*) — refused (XSS). " \
|
|
179
|
-
"Client attr ops target hidden/disabled/open/selected/aria-*/data-* and classes."
|
|
180
|
-
end
|
|
181
|
-
return unless URL_BEARING_ATTRS.include?(lower) || lower == "style"
|
|
182
|
-
|
|
183
|
-
raise ArgumentError,
|
|
184
|
-
"#{self.class}: attribute #{name.inspect} is refused — URL-bearing attributes " \
|
|
185
|
-
"(#{URL_BEARING_ATTRS.join(", ")}) and `style` can't be set from client ops " \
|
|
186
|
-
"(injection surface). Use classes for styling; target aria-*/data-*/boolean attrs."
|
|
213
|
+
self.class.assert_allowed_attr(name)
|
|
187
214
|
end
|
|
188
215
|
|
|
189
216
|
# A transition must be exactly [during, from, to] class lists (strings).
|
|
@@ -275,6 +275,10 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
275
275
|
list = Array(ops)
|
|
276
276
|
raise ArgumentError, "js(...) got no ops — a dead reactive:js stream" if list.empty?
|
|
277
277
|
|
|
278
|
+
# The builder validates attr names at build time; a raw [op, args]
|
|
279
|
+
# list skips that, so re-apply the allowlist here (defense in depth —
|
|
280
|
+
# the client interpreter also refuses on*/URL/style attrs).
|
|
281
|
+
Phlex::Reactive::JS.assert_ops_allowed!(list)
|
|
278
282
|
list.to_json
|
|
279
283
|
end
|
|
280
284
|
end
|
data/lib/phlex/reactive.rb
CHANGED
|
@@ -446,6 +446,10 @@ module Phlex
|
|
|
446
446
|
# through the endpoint's existing `|| raise(InvalidToken)` → 400.
|
|
447
447
|
def upgrade_token(payload)
|
|
448
448
|
version = payload.fetch("v", 0)
|
|
449
|
+
# "v" is inside the signed blob, so only our own key could produce a
|
|
450
|
+
# malformed one — but fail closed rather than 500 on the comparison
|
|
451
|
+
# (String vs Integer) or silently treat a negative "v" as legacy.
|
|
452
|
+
return nil unless version.is_a?(::Integer) && version >= 0
|
|
449
453
|
return payload if version == TOKEN_VERSION
|
|
450
454
|
return nil if version > TOKEN_VERSION
|
|
451
455
|
|