otori 0.2.0 → 0.3.0
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 +21 -0
- data/lib/otori/form.rb +30 -18
- data/lib/otori/version.rb +1 -1
- 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: be91dcf0d80421c3347a3d75e9e7aade7a7479f8e7b100c6d628f394016c2d37
|
|
4
|
+
data.tar.gz: d8bd1c71cb521c22f5f9797612cd83105bef897584d1a92d3004acaad7625b73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dbcb90604352965d8f951c2caf025978deafb6e8b23772cafb953a0fba20a2f3a292567ffa48441a0fb82a9794a4c5abb3d82297cfc824effa11bbc8c65bf25
|
|
7
|
+
data.tar.gz: 2c0df32a81ca7fdce8234e91d52b10379e8931b597732f54de8393c0460409ecd50a5bbe01551a876dd12711fd4ee6db63a12197128db0ad53fb59a157ec18c6
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-10-12
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `signals_field` now accepts an `id:` attribute. When omitted, a unique id
|
|
15
|
+
of the form `otori-signals-<hex>` is generated per call so multiple
|
|
16
|
+
signals fields can safely coexist on the same page.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `signals_field` no longer relies on
|
|
21
|
+
`document.currentScript.previousElementSibling` to locate its hidden input.
|
|
22
|
+
The input now carries a unique `id` and the tracking script looks it up via
|
|
23
|
+
`document.getElementById`. The previous approach broke when any DOM mutation
|
|
24
|
+
(Turbo cache restoration, injected elements, browser extensions) landed
|
|
25
|
+
between the input and the script, which could cause the signals JSON to be
|
|
26
|
+
written into a neighbouring field (such as a honeypot) and trigger false
|
|
27
|
+
positives. Callers can supply their own `id:` to override the generated
|
|
28
|
+
value, and the script now bails out silently when the input or its form
|
|
29
|
+
cannot be resolved.
|
|
30
|
+
|
|
10
31
|
## [0.2.0] - 2026-09-17
|
|
11
32
|
|
|
12
33
|
### Added
|
data/lib/otori/form.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "cgi"
|
|
4
|
+
require "securerandom"
|
|
4
5
|
|
|
5
6
|
require_relative "validator"
|
|
6
7
|
|
|
@@ -10,20 +11,6 @@ module Otori
|
|
|
10
11
|
|
|
11
12
|
HIDDEN_STYLE = "position:absolute;left:-9999px;width:1px;height:1px;pointer-events:none;"
|
|
12
13
|
|
|
13
|
-
SIGNALS_SCRIPT = <<~JS
|
|
14
|
-
(() => {
|
|
15
|
-
const s = { m: false, t: false, s: false, k: false, f: false };
|
|
16
|
-
const input = document.currentScript.previousElementSibling;
|
|
17
|
-
const form = input.form;
|
|
18
|
-
form.addEventListener('mousemove', () => s.m = true, { once: true });
|
|
19
|
-
form.addEventListener('touchstart', () => s.t = true, { once: true });
|
|
20
|
-
form.addEventListener('keydown', () => s.k = true, { once: true });
|
|
21
|
-
form.addEventListener('focusin', () => s.f = true, { once: true });
|
|
22
|
-
window.addEventListener('scroll', () => s.s = true, { once: true });
|
|
23
|
-
form.addEventListener('submit', () => input.value = JSON.stringify(s));
|
|
24
|
-
})();
|
|
25
|
-
JS
|
|
26
|
-
|
|
27
14
|
def field(name, session:, **attrs)
|
|
28
15
|
raise MissingSession unless session_like?(session)
|
|
29
16
|
|
|
@@ -42,16 +29,41 @@ module Otori
|
|
|
42
29
|
end
|
|
43
30
|
|
|
44
31
|
def signals_field(**attrs)
|
|
32
|
+
stringified = stringify_keys(attrs)
|
|
33
|
+
id = stringified["id"] || generate_signals_id
|
|
34
|
+
|
|
45
35
|
input = tag(:input, {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
"id" => id,
|
|
37
|
+
"name" => Otori.config.signals_input_name,
|
|
38
|
+
"type" => "hidden"
|
|
39
|
+
}.merge(stringified))
|
|
49
40
|
|
|
50
|
-
"#{input}<script>#{
|
|
41
|
+
"#{input}<script>#{signals_script(id)}</script>"
|
|
51
42
|
end
|
|
52
43
|
|
|
53
44
|
private
|
|
54
45
|
|
|
46
|
+
def generate_signals_id
|
|
47
|
+
"otori-signals-#{SecureRandom.hex(6)}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def signals_script(id)
|
|
51
|
+
<<~JS
|
|
52
|
+
(() => {
|
|
53
|
+
const input = document.getElementById(#{id.to_json});
|
|
54
|
+
if (!input || !input.form) return;
|
|
55
|
+
const form = input.form;
|
|
56
|
+
const s = { m: false, t: false, s: false, k: false, f: false };
|
|
57
|
+
form.addEventListener('mousemove', () => s.m = true, { once: true });
|
|
58
|
+
form.addEventListener('touchstart', () => s.t = true, { once: true });
|
|
59
|
+
form.addEventListener('keydown', () => s.k = true, { once: true });
|
|
60
|
+
form.addEventListener('focusin', () => s.f = true, { once: true });
|
|
61
|
+
window.addEventListener('scroll', () => s.s = true, { once: true });
|
|
62
|
+
form.addEventListener('submit', () => input.value = JSON.stringify(s));
|
|
63
|
+
})();
|
|
64
|
+
JS
|
|
65
|
+
end
|
|
66
|
+
|
|
55
67
|
def session_like?(object)
|
|
56
68
|
object.respond_to?(:[]) && object.respond_to?(:[]=)
|
|
57
69
|
end
|
data/lib/otori/version.rb
CHANGED