ideasbugs 0.5.1 → 0.5.2
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 +16 -0
- data/app/controllers/ideasbugs/widgets_controller.rb +26 -0
- data/config/routes.rb +3 -0
- data/lib/ideasbugs/version.rb +1 -1
- data/lib/ideasbugs/widget.rb +21 -8
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fbdc5c2d176499e4bc54ef89013a7e60c56c694787021cb6eb8ead38f8cb28ea
|
|
4
|
+
data.tar.gz: f4d6ef3316a06cd0615b8b0b11e18a2712dac7c40dedb661542d9be481618b14
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a5ce4d1fcd811b2883ec8aca1b80a9f20579690a4320af3e6ef24bde6228bf0c370d3bf116b075c27e2c35b61ced943b7c6e36df08983f0847fc657b4f6c59b
|
|
7
|
+
data.tar.gz: 8bec9adf1132529e22053c589bc2d548e5d0676eddddd381674305f27f56962edb2a00171cc41c1b9145d2cac3ae5919e8c6f2d78529933b9f8c663a4095f4ac
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.2 (2026-07-25)
|
|
4
|
+
|
|
5
|
+
- **Fixed: the widget went dead after Turbo Drive navigations under a
|
|
6
|
+
nonce-based CSP.** The widget code used to be inlined as a nonce'd
|
|
7
|
+
`<script>` block. Turbo Drive soft visits swap the `<body>` and re-execute
|
|
8
|
+
body scripts against the *original* page's CSP header, so the freshly
|
|
9
|
+
minted inline nonce was refused and the widget never booted — until the
|
|
10
|
+
user hard-reloaded. The code is now served by the engine as a same-origin,
|
|
11
|
+
content-fingerprinted script (`<mount_path>/widget.js?v=<md5>`), which
|
|
12
|
+
`script-src 'self'` covers on every visit, first or soft. The fingerprinted
|
|
13
|
+
URL is immutable (new code means a new URL) and is cached publicly for a
|
|
14
|
+
year; any other `?v` only ETag-revalidates. The `nonce` attribute is still
|
|
15
|
+
stamped on the tag for hosts whose `script-src` has no `'self'`. The config
|
|
16
|
+
`type="application/json"` block is unchanged and stays inline — it is data,
|
|
17
|
+
not code, and needs no nonce.
|
|
18
|
+
|
|
3
19
|
## 0.5.1 (2026-07-25)
|
|
4
20
|
|
|
5
21
|
- The widget dialog is now full-screen on mobile (≤ 480px): edge-to-edge,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ideasbugs
|
|
4
|
+
# Serves the widget JavaScript as a plain same-origin script. Same-origin
|
|
5
|
+
# matters: under a `script-src 'self'` (or nonce-based) CSP, an external
|
|
6
|
+
# script from the app's own host is always allowed — including when Turbo
|
|
7
|
+
# Drive swaps the <body> and re-runs body scripts under the *original*
|
|
8
|
+
# page's CSP header, where a freshly minted inline nonce would be refused.
|
|
9
|
+
class WidgetsController < ApplicationController
|
|
10
|
+
# The script is static and carries no user data; without this, Rails'
|
|
11
|
+
# cross-origin JavaScript guard refuses to serve it to a plain
|
|
12
|
+
# <script src> request.
|
|
13
|
+
skip_forgery_protection
|
|
14
|
+
|
|
15
|
+
# The script URL carries a content fingerprint (?v=<md5>), so stale code
|
|
16
|
+
# is structurally impossible: new code means a new URL. The canonical
|
|
17
|
+
# fingerprinted URL is immutable and gets long-lived caching; anything
|
|
18
|
+
# else only ETag-revalidates.
|
|
19
|
+
def show
|
|
20
|
+
expires_in 1.year, public: true if params[:v] == Widget.fingerprint
|
|
21
|
+
return unless stale?(etag: [Ideasbugs::VERSION, Widget.fingerprint])
|
|
22
|
+
|
|
23
|
+
render plain: Widget.javascript, content_type: 'text/javascript'
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/config/routes.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
Ideasbugs::Engine.routes.draw do
|
|
4
|
+
# The widget code, served same-origin (see WidgetsController for why).
|
|
5
|
+
get 'widget.js', to: 'widgets#show', as: :widget
|
|
6
|
+
|
|
4
7
|
# create is the public widget endpoint; the rest is the triage dashboard.
|
|
5
8
|
resources :feedbacks, only: %i[create index show update destroy] do
|
|
6
9
|
# Screenshots stream through the dashboard's own gate, never via public
|
data/lib/ideasbugs/version.rb
CHANGED
data/lib/ideasbugs/widget.rb
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
+
require 'digest'
|
|
4
5
|
|
|
5
6
|
module Ideasbugs
|
|
6
7
|
# Serves the self-contained browser widget. The JavaScript is plain ES (no
|
|
7
8
|
# framework, no build step) and styles itself inline, so it drops into any
|
|
8
|
-
# Rails app regardless of its CSS or JS setup. It is
|
|
9
|
-
# rather than
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
9
|
+
# Rails app regardless of its CSS or JS setup. It is served by the engine's
|
|
10
|
+
# own controller rather than through the host's asset pipeline, so there is
|
|
11
|
+
# no dependency on it — and it lives under lib/ (not app/assets/) so a host
|
|
12
|
+
# that *does* run a pipeline never ingests it either: nothing lands in the
|
|
13
|
+
# host's asset namespace or precompiled output.
|
|
13
14
|
module Widget
|
|
14
15
|
SOURCE = File.expand_path('widget.js', __dir__)
|
|
15
16
|
|
|
@@ -22,6 +23,13 @@ module Ideasbugs
|
|
|
22
23
|
@javascript ||= File.read(SOURCE)
|
|
23
24
|
end
|
|
24
25
|
|
|
26
|
+
# Content fingerprint for the cache-busting script URL: a changed file
|
|
27
|
+
# is a changed URL, so no browser can ever run stale widget code —
|
|
28
|
+
# Safari has been caught ignoring must-revalidate on same-URL scripts.
|
|
29
|
+
def fingerprint
|
|
30
|
+
@fingerprint ||= Digest::MD5.hexdigest(javascript)
|
|
31
|
+
end
|
|
32
|
+
|
|
25
33
|
# The two <script> tags the helper renders.
|
|
26
34
|
#
|
|
27
35
|
# The config rides in a `type="application/json"` block: it is *data*,
|
|
@@ -29,8 +37,12 @@ module Ideasbugs
|
|
|
29
37
|
# re-run it on a soft visit — which means it needs no CSP nonce and the
|
|
30
38
|
# widget can re-read the *current* page's config on every `turbo:load`.
|
|
31
39
|
#
|
|
32
|
-
#
|
|
33
|
-
# nonce-based
|
|
40
|
+
# The code is a same-origin `src` script served by the engine — NOT
|
|
41
|
+
# inlined. Under a nonce-based CSP, Turbo Drive body swaps re-run body
|
|
42
|
+
# scripts against the *original* page's CSP header, so a fresh inline
|
|
43
|
+
# nonce gets refused; a same-origin src is covered by `'self'` on every
|
|
44
|
+
# visit. `nonce:` is still stamped for hosts whose script-src has no
|
|
45
|
+
# 'self'; pass nil when the app has no nonce.
|
|
34
46
|
def snippet(endpoint:, locale:, nonce: nil)
|
|
35
47
|
config = {
|
|
36
48
|
endpoint: endpoint,
|
|
@@ -46,9 +58,10 @@ module Ideasbugs
|
|
|
46
58
|
# Escape "</" so a value can't close the <script> block early.
|
|
47
59
|
json = config.to_json.gsub('</', '<\/')
|
|
48
60
|
nonce_attr = nonce ? %( nonce="#{nonce}") : ''
|
|
61
|
+
src = "#{Ideasbugs.config.mount_path.chomp('/')}/widget.js?v=#{fingerprint}"
|
|
49
62
|
|
|
50
63
|
%(<script type="application/json" data-ideasbugs-config>#{json}</script>) +
|
|
51
|
-
%(<script data-ideasbugs-widget
|
|
64
|
+
%(<script src="#{src}" defer#{nonce_attr} data-ideasbugs-widget></script>)
|
|
52
65
|
end
|
|
53
66
|
|
|
54
67
|
private
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ideasbugs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -41,6 +41,7 @@ files:
|
|
|
41
41
|
- app/controllers/ideasbugs/application_controller.rb
|
|
42
42
|
- app/controllers/ideasbugs/feedbacks_controller.rb
|
|
43
43
|
- app/controllers/ideasbugs/screenshots_controller.rb
|
|
44
|
+
- app/controllers/ideasbugs/widgets_controller.rb
|
|
44
45
|
- app/helpers/ideasbugs/widget_helper.rb
|
|
45
46
|
- app/models/ideasbugs/application_record.rb
|
|
46
47
|
- app/models/ideasbugs/feedback.rb
|