copy_tuner_client 2.1.1 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 447684c86dea9d93b959e3adaf7e4dcc7c555461a22caa153e6a04e3b6fae211
4
- data.tar.gz: fd0976c79ae1199c496f55025556aa1d2879615b87137b618ab8e15221c665ad
3
+ metadata.gz: 34fcab16da8e238f416a8d4be1c68212818ae95afb7f82112391517e901fbb8f
4
+ data.tar.gz: dc9a727af71a83e39cb4d4c4aa41a8d1e982244936f294462da8e44b0b57d9fc
5
5
  SHA512:
6
- metadata.gz: 78e1334502deb656827a36911e877fe6f336d4e5b36b41c59665c52b04eba1a69482a8b5dd83a79416445c471002c3218fcc18c4b086fba5038dae67cc98302d
7
- data.tar.gz: 49edcbbab88a562edcc05dda73ded2ac248c5323a890787d274261adbe9864ea1451fca04668fe68dba03910116315e16ba2f539eef9dd446c2c0e39638e2d3a
6
+ metadata.gz: aed1f966e3c35e5d25f82fc96c56ff5532dd0b8d651be0a275f7f6f85f10a0bc58efe6b414ef004140306be70c2c2294c8c1054a112530a32e21070b098f4557
7
+ data.tar.gz: 9179c177ecbc48104ea0b17db2e6adc1f88e8aa4a5dac4eb0366e171543d2f83bc03e160d7f6cadf65ccb4e8664280904b0c06eca2260d222c9463a51449ff04
@@ -19,7 +19,10 @@ module CopyTunerClient
19
19
  # NOTE: 戻り値は [html, skipped]。skipped は data-copyray-key を付与できなかったことを表す
20
20
  # (巨大DOMでのスキップ・Nokogiri 例外の双方で true)。ミドルウェアがこれを JS に伝え、
21
21
  # オーバーレイ非対応である旨をツールバーで案内する。
22
- def rewrite(html)
22
+ # NOTE: fragment: true は turbo stream など body に差し込む HTML 断片向け。
23
+ # Nokogiri::HTML だと html/body/DOCTYPE ラッパが付いて断片が壊れるため、
24
+ # fragment パーサでラッパを付けずに走査・除去する。
25
+ def rewrite(html, fragment: false)
23
26
  # NOTE: ボディが ASCII-8BIT に転落していると UTF-8 の Marker::PREFIX との include? 比較が
24
27
  # Encoding::CompatibilityError を投げる(ミドルウェアのボディ連結で非ASCIIバイトを含む
25
28
  # ASCII-8BIT チャンクが混じると発生)。実バイト列は本来 UTF-8 なので判定用に UTF-8 とみなす。
@@ -35,7 +38,7 @@ module CopyTunerClient
35
38
  # NOTE: 閾値超は Nokogiri を通さず可視トークン除去のみ。skipped=true で編集導線を諦めた旨を伝える。
36
39
  return [strip_markers(scannable), true] if scannable.bytesize > MAX_REWRITE_BYTESIZE
37
40
 
38
- [rewrite_with_nokogiri(scannable), false]
41
+ [rewrite_with_nokogiri(scannable, fragment: fragment), false]
39
42
  rescue StandardError => e
40
43
  # NOTE: Copyray は開発支援機能なので、壊れた HTML 等で Nokogiri 処理が落ちても
41
44
  # ページを 500 にしない。data-copyray-key 付与(編集導線)は諦め、最低限可視トークンだけ除去する。
@@ -51,8 +54,8 @@ module CopyTunerClient
51
54
  scannable.gsub(Marker::SCAN_REGEXP, '')
52
55
  end
53
56
 
54
- def rewrite_with_nokogiri(scannable)
55
- doc = Nokogiri::HTML(scannable)
57
+ def rewrite_with_nokogiri(scannable, fragment: false)
58
+ doc = fragment ? Nokogiri::HTML.fragment(scannable) : Nokogiri::HTML(scannable)
56
59
 
57
60
  doc.traverse do |node|
58
61
  if node.text?
@@ -11,18 +11,22 @@ module CopyTunerClient
11
11
  def call(env)
12
12
  CopyTunerClient::TranslationLog.clear
13
13
  status, headers, response = @app.call(env)
14
- if html_headers?(status, headers) && body = response_body(response)
14
+ if rewritable?(status, headers) && body = response_body(response)
15
15
  csp_nonce = env['action_dispatch.content_security_policy_nonce'] || env['secure_headers_content_security_policy_nonce']
16
16
  # NOTE: CSS/JS 挿入の前に Rewriter を通す。serialize 後も </body> は必ず出力されるので
17
17
  # append_to_html_body の rindex は機能し、CSS/JS タグはトークン非含有なので二重処理も起きない。
18
18
  # NOTE: skipped は data-copyray-key を付与できなかったこと(巨大DOM/Nokogiri例外)を表す。
19
19
  # JS にこれを伝え、オーバーレイ非対応である旨をツールバーで案内させる。
20
- body, skipped = CopyTunerClient::Copyray::Rewriter.rewrite(body)
21
- body = append_js(body, csp_nonce, skipped: skipped)
20
+ # NOTE: turbo stream はページ断片なので fragment パーサで走査する(html/body ラッパを付けない)。
21
+ turbo_stream = turbo_stream?(headers)
22
+ body, skipped = CopyTunerClient::Copyray::Rewriter.rewrite(body, fragment: turbo_stream)
23
+ # NOTE: ブートストラップ JS はフルページ読み込み時に一度だけ挿入すればよい。turbo stream 断片には
24
+ # 挿入先の </body> も無く、既に初期化済みのページへマージされるだけなので挿入しない。
25
+ body = append_js(body, csp_nonce, skipped: skipped) unless turbo_stream
22
26
  content_length = body.bytesize.to_s
23
27
  headers['Content-Length'] = content_length
24
28
  # maintains compatibility with other middlewares
25
- if defined?(ActionDispatch::Response::RackBody) && ActionDispatch::Response::RackBody === response
29
+ if defined?(ActionDispatch::Response::RackBody) && response.is_a?(ActionDispatch::Response::RackBody)
26
30
  ActionDispatch::Response.new(status, headers, [body]).to_a
27
31
  else
28
32
  [status, headers, [body]]
@@ -65,14 +69,19 @@ module CopyTunerClient
65
69
  end
66
70
 
67
71
  def file?(headers)
68
- headers["Content-Transfer-Encoding"] == 'binary'
72
+ headers['Content-Transfer-Encoding'] == 'binary'
69
73
  end
70
74
 
71
- def html_headers?(status, headers)
75
+ def rewritable?(status, headers)
72
76
  [200, 422].include?(status) &&
77
+ headers['Content-Type'] &&
78
+ (headers['Content-Type'].include?('text/html') || turbo_stream?(headers)) &&
79
+ !file?(headers)
80
+ end
81
+
82
+ def turbo_stream?(headers)
73
83
  headers['Content-Type'] &&
74
- headers['Content-Type'].include?('text/html') &&
75
- !file?(headers)
84
+ headers['Content-Type'].include?('text/vnd.turbo-stream.html')
76
85
  end
77
86
 
78
87
  def response_body(response)
@@ -1,6 +1,6 @@
1
1
  module CopyTunerClient
2
2
  # Client version
3
- VERSION = '2.1.1'.freeze
3
+ VERSION = '2.1.2'.freeze
4
4
 
5
5
  # API version being used to communicate with the server
6
6
  API_VERSION = '2.0'.freeze
@@ -202,6 +202,22 @@ describe CopyTunerClient::Copyray::Rewriter do
202
202
  end
203
203
  end
204
204
 
205
+ context 'fragment: true(turbo stream などの HTML 断片)' do
206
+ let(:html) { %(<turbo-stream action="replace" target="x"><template><p>#{marker('a.b')}Hello</p></template></turbo-stream>) }
207
+ subject(:result) { described_class.rewrite(html, fragment: true).first }
208
+
209
+ it 'html/body ラッパを足さず断片のまま返す' do
210
+ expect(result).not_to include('<html>')
211
+ expect(result).not_to include('<body>')
212
+ expect(result).to start_with('<turbo-stream')
213
+ end
214
+
215
+ it 'template 内の要素に data-copyray-key を付与しトークンを除去する' do
216
+ expect(result).to include('data-copyray-key="a.b"')
217
+ expect(result).not_to match CopyTunerClient::Copyray::Marker::SCAN_REGEXP
218
+ end
219
+ end
220
+
205
221
  context '出力にマーカートークンが一切残らない' do
206
222
  let(:html) do
207
223
  "<html><head><title>#{marker('t')}T</title></head>" \
@@ -41,6 +41,40 @@ describe CopyTunerClient::CopyrayMiddleware do
41
41
  end
42
42
  end
43
43
 
44
+ context 'turbo stream レスポンスのとき' do
45
+ let(:headers) { { 'Content-Type' => 'text/vnd.turbo-stream.html' } }
46
+ let(:body) { %(<turbo-stream action="replace" target="x"><template><p>#{marker('a.b')}Hello</p></template></turbo-stream>) }
47
+
48
+ before do
49
+ # NOTE: append_js のトップレベル no-op スタブを外し、turbo stream では JS を挿入しないことを検証する。
50
+ allow(middleware).to receive(:append_js).and_call_original
51
+ end
52
+
53
+ it 'マーカーを data-copyray-key 属性に書き換え、トークンを除去する' do
54
+ _status, _headers, response = middleware.call({})
55
+ result = response.join
56
+
57
+ expect(result).to include('data-copyray-key="a.b"')
58
+ expect(result).not_to match CopyTunerClient::Copyray::Marker::SCAN_REGEXP
59
+ end
60
+
61
+ it 'html/body ラッパを付けず turbo-stream 断片のまま返す' do
62
+ _status, _headers, response = middleware.call({})
63
+ result = response.join
64
+
65
+ expect(result).to start_with('<turbo-stream')
66
+ expect(result).not_to include('<body>')
67
+ end
68
+
69
+ it 'ブートストラップ JS を挿入しない(断片なので)' do
70
+ _status, _headers, response = middleware.call({})
71
+ result = response.join
72
+
73
+ expect(result).not_to include('window.CopyTuner')
74
+ expect(result).not_to include('copytuner')
75
+ end
76
+ end
77
+
44
78
  context 'HTML 以外のレスポンスのとき' do
45
79
  let(:headers) { { 'Content-Type' => 'application/json' } }
46
80
  let(:body) { "{\"x\":\"#{marker('a.b')}\"}" }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: copy_tuner_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - SonicGarden
@@ -301,7 +301,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
301
301
  - !ruby/object:Gem::Version
302
302
  version: '0'
303
303
  requirements: []
304
- rubygems_version: 4.0.10
304
+ rubygems_version: 4.0.15
305
305
  specification_version: 4
306
306
  summary: Client for the CopyTuner copy management service
307
307
  test_files: []