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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dad6a295086cd2eb7abd60f81e264cde80726e6802deec8e185b3d6ddd2bd082
4
- data.tar.gz: b1fe5bab67f24f249b9b45055fa0a632774b99fa93fe0c156235014c9bdf1416
3
+ metadata.gz: be91dcf0d80421c3347a3d75e9e7aade7a7479f8e7b100c6d628f394016c2d37
4
+ data.tar.gz: d8bd1c71cb521c22f5f9797612cd83105bef897584d1a92d3004acaad7625b73
5
5
  SHA512:
6
- metadata.gz: a275bbc1f3a7ce4a3f8ae0b6a09b0835aeeca30713254819f747e3e3b06f0c2e8a875a6ca604a8df396ada100ce9518238e8575ec1a06a3bb06e2bec5abe8b5d
7
- data.tar.gz: 7fee9a413435e3efcf65f5196fdffe168c664935c8e1390f06545cb9f650571e47e031f04f3f9f2649f01a0b62a67229a9e7071199bdc2903c3dfe1543e625f1
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
- name: Otori.config.signals_input_name,
47
- type: "hidden"
48
- }.merge(stringify_keys(attrs)))
36
+ "id" => id,
37
+ "name" => Otori.config.signals_input_name,
38
+ "type" => "hidden"
39
+ }.merge(stringified))
49
40
 
50
- "#{input}<script>#{SIGNALS_SCRIPT}</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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Otori
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: otori
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wout