mp 0.1.2 → 0.1.3

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: 5129f3b2c3b3d69f8dfd2ccbf055ff7beeb95befb40fc92bbaede6f0e6aa90ed
4
- data.tar.gz: bf6f1b7fc5b27dbf4c3358d71c5e7f78b9fd8dfb5356c42afe50746034b80227
3
+ metadata.gz: e0e1a8144762d89c2522e81fbb0b6e979802ea3989730337d45e49c31cbe6c2f
4
+ data.tar.gz: 86aced39b324abbbc2b7dcdb20d42b7dd8ede154055bfaec651997d93ba08f7b
5
5
  SHA512:
6
- metadata.gz: 68d1748098ff537a0a9a0c868831f625c525b5a19b7bd1cef064716c25a12912fe3538c866281ac72fe0e006945e95a0bcdbaac11c67c3c04fc03c26492a0459
7
- data.tar.gz: a54861155242c7e478cca020fabe42047cdef83087067b48b2af71e2599f38212894dd5ad42ab3aa27c341ce72bddffab399e25ca3feb0295664467b59ca70bd
6
+ metadata.gz: c9907e65113fe1743a3f09bee33ad883d3b01dafc792fa6b78971549d5a1323e75062f1e6c0ef59c9b4d767e5ebd63ece2859745c54fdb0a4e4449c86c48554e
7
+ data.tar.gz: 51943720c9294c0af440068ade78de35a1b95468b6fbcc944dbe3a1fa1d01c9933cec15228a8cd6892900561e0b4c2e7c0c54e3376f62c2a645c57f12d5c56ad
data/README.md CHANGED
@@ -59,7 +59,7 @@ https://example.com
59
59
  ```
60
60
  ````
61
61
 
62
- 本文には `http(s)` の URL か、デッキからの相対パス(`./page.html`)を書きます。`X-Frame-Options` などで埋め込みを拒否するサイトも表示できます。
62
+ 本文には `http(s)` の URL か、デッキからの相対パス(`./page.html`)を書きます。`X-Frame-Options` などで埋め込みを拒否するサイトも表示できます。JavaScript、WebAssembly、Web Worker を使うページも動きます。ディレクトリを指すときは `./app/` のように末尾に `/` を付けると `index.html` を開きます。
63
63
 
64
64
  | 属性 | 説明 |
65
65
  | --- | --- |
@@ -181,7 +181,7 @@ Ruby のローカルサーバ(puma)がスライドを配信し、Chrome をア
181
181
  - サーバは `127.0.0.1` のみで待ち受け、全パスにランダムトークンを要求する
182
182
  - WebSocket は Origin を検証する
183
183
  - CSP により、デッキ内の HTML に書いたスクリプトは実行されない
184
- - デッキ配下の HTML は sandbox で配信し、ターミナルへ接続できない
184
+ - 埋め込むローカルページは別 origin(`localhost`)で配信し、ターミナルへ接続できない
185
185
  - コマンドは Enter を押すまで実行されない
186
186
 
187
187
  それでも、Enter を押せば書かれたコマンドは実行されます。**他人が作ったデッキは内容を確認してから発表してください。**
@@ -190,6 +190,7 @@ Ruby のローカルサーバ(puma)がスライドを配信し、Chrome をア
190
190
 
191
191
  ## 制限
192
192
 
193
+ - `SharedArrayBuffer` が必要なページ(Cross-Origin Isolation が前提のもの)は動きません
193
194
  - 埋め込みページは iframe なので、サードパーティ Cookie の制限によりログイン状態を維持できないサイトがあります
194
195
  - `:smile:` 形式の絵文字ショートコードは未対応です(Unicode の絵文字は使えます)
195
196
  - 発表者ビューから入力できるのは `Enter` だけです。台本にないコマンドを打つときは、聴衆画面のターミナルをクリックしてください
@@ -200,9 +201,10 @@ Ruby のローカルサーバ(puma)がスライドを配信し、Chrome をア
200
201
  bundle exec rake test
201
202
  bundle exec ruby test/e2e.rb
202
203
  bundle exec ruby test/presenter_e2e.rb
204
+ bundle exec ruby test/embed_e2e.rb
203
205
  ```
204
206
 
205
- `test/e2e.rb` と `test/presenter_e2e.rb` はヘッドレス Chrome で実際に動かして確認します。Web 埋め込みの確認にはネットワーク接続が必要です。
207
+ `test/*_e2e.rb` と `test/e2e.rb` はヘッドレス Chrome で実際に動かして確認します。Web 埋め込みの確認にはネットワーク接続が必要です。
206
208
 
207
209
  `vendor/assets` には xterm.js と KaTeX を同梱しています。実行に Node.js は不要です。
208
210
 
@@ -45,10 +45,13 @@ module Mp
45
45
  private
46
46
 
47
47
  # サイトをまたぐ iframe は別プロセスの子ターゲットになるので、そちらにも同じ設定をする
48
+ # Worker なども一時停止した状態で届く。Fetch を持たないので iframe だけを設定し、
49
+ # どのターゲットも必ず再開する(再開しないと Worker が止まったままになる)
48
50
  def prepare_child(params)
49
51
  session = params["sessionId"]
50
- watch(session)
51
- @cdp.call("Runtime.runIfWaitingForDebugger", {}, session_id: session)
52
+ watch(session) if params.dig("targetInfo", "type") == "iframe"
53
+ ensure
54
+ @cdp.cast("Runtime.runIfWaitingForDebugger", {}, session_id: session)
52
55
  end
53
56
 
54
57
  def watch(session)
data/lib/mp/server.rb CHANGED
@@ -18,6 +18,7 @@ module Mp
18
18
  # ランダムトークンを要求し、WebSocket は Origin も検証する。
19
19
  class Server
20
20
  HOST = "127.0.0.1"
21
+ CONTENT_HOST = "localhost"
21
22
  PUBLIC_DIR = File.expand_path("../../public", __dir__)
22
23
  VENDOR_DIR = File.expand_path("../../vendor/assets", __dir__)
23
24
  THEME_DIR = File.expand_path("../../themes", __dir__)
@@ -51,6 +52,10 @@ module Mp
51
52
  end
52
53
 
53
54
  def origin = "http://#{HOST}:#{@port}"
55
+
56
+ # 埋め込むローカルページの配信元。アプリと別 origin にして、ページの JS が
57
+ # ターミナルの WebSocket やデッキの API へ触れないようにする
58
+ def content_origin = "http://#{CONTENT_HOST}:#{@port}"
54
59
  def boot_url = "#{origin}/#{@token}/_mp/boot"
55
60
  def app_url = "#{origin}/#{@token}/"
56
61
  def presenter_url = "#{origin}/#{@token}/presenter"
@@ -78,7 +83,10 @@ module Mp
78
83
  prefix = "/#{@token}/"
79
84
  return not_found unless path.start_with?(prefix)
80
85
 
81
- route(path.delete_prefix(prefix), env)
86
+ relative = path.delete_prefix(prefix)
87
+ return embedded_page(relative) if env["HTTP_HOST"] == "#{CONTENT_HOST}:#{@port}"
88
+
89
+ route(relative, env)
82
90
  end
83
91
 
84
92
  private
@@ -108,7 +116,15 @@ module Mp
108
116
  end
109
117
 
110
118
  def deck_json
111
- @deck.to_h.merge(themeCss: theme_css)
119
+ deck = @deck.to_h
120
+ embeds = deck[:embeds].transform_values { |embed| embed[:type] == "web" ? embed.merge(src: embed_url(embed[:src])) : embed }
121
+ deck.merge(embeds: embeds, themeCss: theme_css)
122
+ end
123
+
124
+ def embed_url(src)
125
+ return src if src.empty? || src.match?(%r{\Ahttps?://}i)
126
+
127
+ "#{content_origin}/#{@token}/#{src.sub(%r{\A(\./|/)+}, "")}"
112
128
  end
113
129
 
114
130
  def theme_css
@@ -136,18 +152,26 @@ module Mp
136
152
 
137
153
  # デッキのあるディレクトリのファイル(画像など)
138
154
  def deck_file(relative)
155
+ full = resolve_deck_path(relative)
156
+ full ? file(full, sandbox_headers(full)) : not_found
157
+ end
158
+
159
+ # 別 origin で配信するので sandbox は不要。wasm や ES モジュールを普通に読み込める
160
+ def embedded_page(relative)
161
+ full = resolve_deck_path(relative.empty? || relative.end_with?("/") ? "#{relative}index.html" : relative)
162
+ full ? file(full) : not_found
163
+ end
164
+
165
+ def resolve_deck_path(relative)
139
166
  relative = Rack::Utils.unescape_path(relative)
140
- return not_found if relative.split("/").any? { _1.start_with?(".") }
167
+ return if relative.split("/").any? { _1.start_with?(".") }
141
168
 
142
169
  base = File.realpath(@deck.base_dir)
143
170
  full = File.expand_path(relative, base)
144
- return not_found unless File.file?(full)
145
- return not_found unless inside?(base, File.realpath(full))
146
-
147
- file(full, sandbox_headers(full))
171
+ full if File.file?(full) && inside?(base, File.realpath(full))
148
172
  end
149
173
 
150
- # デッキ配下の HTML はアプリと同一オリジンにせず、PTY の WebSocket へ触れないようにする
174
+ # アプリの origin で HTML が動くと PTY の WebSocket へ届くので、origin を持たせない
151
175
  def sandbox_headers(path)
152
176
  return {} unless SANDBOXED_TYPES.include?(mime(path))
153
177
 
data/lib/mp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mp
4
- VERSION = "0.1.2"
4
+ VERSION = "0.1.3"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - takkanm