ruby_everywhere 0.7.0 → 0.8.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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/exe/every +2 -13
  3. data/exe/rbe +2 -13
  4. data/lib/everywhere/boot.rb +9 -2
  5. data/lib/everywhere/builders/android.rb +16 -42
  6. data/lib/everywhere/builders/base.rb +53 -0
  7. data/lib/everywhere/builders/desktop.rb +7 -23
  8. data/lib/everywhere/builders/ios.rb +49 -52
  9. data/lib/everywhere/builders/native_sources.rb +38 -0
  10. data/lib/everywhere/child_processes.rb +4 -4
  11. data/lib/everywhere/child_supervision.rb +172 -0
  12. data/lib/everywhere/clock.rb +12 -0
  13. data/lib/everywhere/commands/build.rb +20 -9
  14. data/lib/everywhere/commands/dev.rb +59 -284
  15. data/lib/everywhere/commands/doctor.rb +6 -6
  16. data/lib/everywhere/commands/install.rb +1 -0
  17. data/lib/everywhere/commands/platform/auth_status.rb +1 -0
  18. data/lib/everywhere/commands/platform/build.rb +5 -6
  19. data/lib/everywhere/commands/platform/login.rb +4 -4
  20. data/lib/everywhere/commands/platform/logout.rb +1 -0
  21. data/lib/everywhere/commands/platform/runner.rb +62 -7
  22. data/lib/everywhere/commands/preview.rb +24 -97
  23. data/lib/everywhere/commands/publish.rb +2 -0
  24. data/lib/everywhere/commands/release.rb +14 -11
  25. data/lib/everywhere/commands/shell_dir.rb +2 -0
  26. data/lib/everywhere/commands/updates_keygen.rb +25 -1
  27. data/lib/everywhere/config/app.rb +126 -0
  28. data/lib/everywhere/config/auth.rb +108 -0
  29. data/lib/everywhere/config/data.rb +50 -0
  30. data/lib/everywhere/config/deep_linking.rb +107 -0
  31. data/lib/everywhere/config/desktop_ui.rb +153 -0
  32. data/lib/everywhere/config/mobile.rb +211 -0
  33. data/lib/everywhere/config/native_desktop.rb +168 -0
  34. data/lib/everywhere/config/native_mobile.rb +337 -0
  35. data/lib/everywhere/config/shell.rb +57 -0
  36. data/lib/everywhere/config/updates.rb +63 -0
  37. data/lib/everywhere/config.rb +30 -1423
  38. data/lib/everywhere/desktop_dev_app.rb +138 -0
  39. data/lib/everywhere/dock/state.rb +3 -1
  40. data/lib/everywhere/entrypoint.rb +24 -0
  41. data/lib/everywhere/error.rb +8 -0
  42. data/lib/everywhere/framework.rb +2 -2
  43. data/lib/everywhere/host.rb +11 -0
  44. data/lib/everywhere/ignore.rb +11 -5
  45. data/lib/everywhere/line_pump.rb +3 -1
  46. data/lib/everywhere/minisign.rb +1 -0
  47. data/lib/everywhere/native_platform.rb +44 -0
  48. data/lib/everywhere/paths.rb +23 -7
  49. data/lib/everywhere/platform/client.rb +19 -1
  50. data/lib/everywhere/platform/snapshot.rb +6 -4
  51. data/lib/everywhere/plist.rb +17 -0
  52. data/lib/everywhere/png.rb +1 -0
  53. data/lib/everywhere/raw_tty.rb +51 -0
  54. data/lib/everywhere/s3.rb +1 -0
  55. data/lib/everywhere/shell_pages.rb +109 -0
  56. data/lib/everywhere/tab_filter.rb +36 -0
  57. data/lib/everywhere/task_pool.rb +3 -4
  58. data/lib/everywhere/ui.rb +6 -1
  59. data/lib/everywhere/version.rb +6 -4
  60. data/lib/everywhere.rb +1 -2
  61. data/support/mobile/ios/App.xcodeproj/project.pbxproj +0 -2
  62. data/support/release/macos/notarize.sh +3 -0
  63. metadata +24 -1
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Everywhere
4
+ class << self
5
+ # The tiny page served at /everywhere/reset. Auth flows redirect the native
6
+ # app here so it resets cleanly (fresh web views, re-fetched tabs) before
7
+ # continuing — the standard Hotwire Native "reset the app" pattern. It talks
8
+ # to the shell's control channel directly (no bridge/importmap dependency);
9
+ # in a plain browser it just forwards to the target. `to` is constrained to
10
+ # a same-origin path.
11
+ #
12
+ # Both shells are addressed inline, and differently, for the same reason
13
+ # the bridge normalizes them: WKWebView's message handler takes an object,
14
+ # while Android's WebMessageListener channel takes a string. Reaching for
15
+ # the bridge here instead would trade that one line for an importmap
16
+ # dependency on a page whose whole job is to work before anything loads.
17
+ def mobile_reset_html(to)
18
+ require "json"
19
+ target = to.to_s
20
+ target = "/" unless target.start_with?("/") && !target.start_with?("//")
21
+ # script_safe, not to_json: this lands inside <script>, and outside Rails
22
+ # nothing escapes "</script>" in a plain JSON string.
23
+ encoded = JSON.generate(target, script_safe: true)
24
+
25
+ <<~HTML
26
+ <!DOCTYPE html><html><head><meta charset="utf-8"><title>One moment…</title>
27
+ <meta name="viewport" content="width=device-width,initial-scale=1"></head>
28
+ <body><script>
29
+ (function(){var to=#{encoded};var msg={action:"reset",to:to};
30
+ var ios=window.webkit&&window.webkit.messageHandlers&&window.webkit.messageHandlers.everywhereControl;
31
+ var android=window.everywhereControl;
32
+ if(ios){ios.postMessage(msg);}
33
+ else if(android&&android.postMessage){android.postMessage(JSON.stringify(msg));}
34
+ else{window.location.replace(to);}})();
35
+ </script></body></html>
36
+ HTML
37
+ end
38
+
39
+ # The page served at /everywhere/jump/leave — the way OUT of a Jump
40
+ # preview. Once Jump re-roots onto a previewed app (instance.set), every
41
+ # Jump surface — launcher, scanner, tabs — resolves against the previewed
42
+ # app, so the escape hatch has to be served by the previewed app itself:
43
+ # this gem is the one thing guaranteed to be there. Posts clearInstance on
44
+ # the shell's control channel; shells that aren't multi-instance ignore
45
+ # it, and a plain browser just goes home.
46
+ # Fires clearInstance only once VISIBLE. This page rides in the previewed
47
+ # app's tab bar; shells preload tabs eagerly, but a preloaded webview is
48
+ # offscreen and reports visibilityState "hidden" — firing on load would
49
+ # bounce every preview the instant it connects, and a tap on the tab
50
+ # never re-proposes a visit, so visibility flipping is the ONLY signal a
51
+ # tab tap gives the page. The button is a belt for browsers and any
52
+ # webview that lies about visibility.
53
+ def mobile_jump_leave_html
54
+ <<~HTML
55
+ <!DOCTYPE html><html><head><meta charset="utf-8"><title>Jump</title>
56
+ <meta name="viewport" content="width=device-width,initial-scale=1">
57
+ <style>
58
+ body{font-family:-apple-system,system-ui,sans-serif;margin:0;min-height:100vh;
59
+ display:flex;flex-direction:column;align-items:center;justify-content:center;
60
+ gap:12px;padding:24px;text-align:center;background:#fff;color:#171717}
61
+ @media(prefers-color-scheme:dark){body{background:#171717;color:#fafafa}}
62
+ p{margin:0;opacity:.6}
63
+ button{font:inherit;font-weight:600;font-size:18px;color:#fff;background:#dc2626;
64
+ border:0;border-radius:12px;padding:16px 32px}
65
+ </style></head>
66
+ <body>
67
+ <p>Returning to Jump…</p>
68
+ <button onclick="everywhereJumpLeave()">Return to Jump</button>
69
+ <script>
70
+ function everywhereJumpLeave(){var msg={action:"clearInstance"};
71
+ var ios=window.webkit&&window.webkit.messageHandlers&&window.webkit.messageHandlers.everywhereControl;
72
+ var android=window.everywhereControl;
73
+ if(ios){ios.postMessage(msg);}
74
+ else if(android&&android.postMessage){android.postMessage(JSON.stringify(msg));}
75
+ else{window.location.replace("/");}}
76
+ if(document.visibilityState==="visible"){everywhereJumpLeave();}
77
+ else{document.addEventListener("visibilitychange",function(){
78
+ if(document.visibilityState==="visible"){everywhereJumpLeave();}});}
79
+ </script></body></html>
80
+ HTML
81
+ end
82
+
83
+ # The page served at /everywhere/auth/native. The shell normally diverts a
84
+ # provider path natively, before the request is ever made; this covers the
85
+ # visits it can't see — a `data-turbo="false"` link, or the POST OmniAuth 2
86
+ # requires — by asking the shell, from the page, to open the auth session.
87
+ # In a browser it just continues to the provider.
88
+ def mobile_auth_html(to)
89
+ require "json"
90
+ target = to.to_s
91
+ target = "/" unless target.start_with?("/") && !target.start_with?("//")
92
+ # script_safe, as in mobile_reset_html: `to` is request input.
93
+ encoded = JSON.generate(target, script_safe: true)
94
+
95
+ <<~HTML
96
+ <!DOCTYPE html><html><head><meta charset="utf-8"><title>Signing in…</title>
97
+ <meta name="viewport" content="width=device-width,initial-scale=1"></head>
98
+ <body><p>Opening secure sign-in…</p><script>
99
+ (function(){var to=#{encoded};var msg={action:"authFlow",to:to};
100
+ var ios=window.webkit&&window.webkit.messageHandlers&&window.webkit.messageHandlers.everywhereControl;
101
+ var android=window.everywhereControl;
102
+ if(ios){ios.postMessage(msg);}
103
+ else if(android&&android.postMessage){android.postMessage(JSON.stringify(msg));}
104
+ else{window.location.replace(to);}})();
105
+ </script></body></html>
106
+ HTML
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Everywhere
4
+ class << self
5
+ # Register a per-request filter for the mobile tab bar. The block receives
6
+ # the resolved tab list (`[{ "title" =>, "path" =>, "icon" => }, …]`) and
7
+ # the current request, and returns the subset to show — return `[]` to hide
8
+ # the tab bar entirely (the shell falls back to single-screen navigation).
9
+ #
10
+ # It runs inside the mobile config endpoint, which shares the app's session
11
+ # and cookies, so it can branch on auth or any request state:
12
+ #
13
+ # # config/initializers/everywhere.rb
14
+ # Everywhere.filter_tabs do |tabs, request|
15
+ # request.session[:user_id] ? tabs : []
16
+ # end
17
+ #
18
+ # Live like the rest of the config: the shell re-reads it on launch and on
19
+ # every foreground, so a sign-in shows the tabs on next foreground — no
20
+ # rebuild, no app-store release.
21
+ def filter_tabs(&block)
22
+ @tabs_filter = block
23
+ end
24
+
25
+ # Apply the registered filter (identity when none is set). Always returns
26
+ # an Array so a stray nil/scalar from a block can't break serialization.
27
+ def resolve_tabs(tabs, request)
28
+ return tabs unless @tabs_filter
29
+
30
+ Array(@tabs_filter.call(tabs, request))
31
+ end
32
+
33
+ # Test/reset hook.
34
+ attr_writer :tabs_filter
35
+ end
36
+ end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "clock"
3
4
  require_relative "console"
4
5
  require_relative "child_processes"
5
6
 
@@ -78,8 +79,8 @@ module Everywhere
78
79
  return if live.empty?
79
80
 
80
81
  live.each { |thread| ChildProcesses.terminate(thread, grace: grace) }
81
- deadline = now + grace
82
- live.each { |thread| thread.join([deadline - now, 0].max) }
82
+ deadline = Clock.monotonic + grace
83
+ live.each { |thread| thread.join([deadline - Clock.monotonic, 0].max) }
83
84
  live.select(&:alive?).each(&:kill)
84
85
  Console.multiplexed = false
85
86
  end
@@ -117,7 +118,5 @@ module Everywhere
117
118
  live = @threads.each_value.count { |t| t.alive? && t != Thread.current }
118
119
  Console.multiplexed = live > 1
119
120
  end
120
-
121
- def now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
122
121
  end
123
122
  end
data/lib/everywhere/ui.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "clock"
3
4
  require_relative "fatal"
4
5
  require_relative "console"
5
6
 
@@ -164,7 +165,7 @@ module Everywhere
164
165
  private
165
166
 
166
167
  def headless(text)
167
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
168
+ now = Clock.monotonic
168
169
  return if @last_headless && (now - @last_headless) < HEADLESS_INTERVAL
169
170
 
170
171
  @last_headless = now
@@ -217,5 +218,9 @@ module Everywhere
217
218
  s = s.gsub("#{home}/", "~/") if home && !home.empty?
218
219
  s
219
220
  end
221
+
222
+ # short_path for a path we know the root of: drop the project root (or the
223
+ # cwd, when the command runs from somewhere else) before shortening.
224
+ def rel_path(path, root) = short_path(path.to_s.sub("#{root}/", "").sub("#{Dir.pwd}/", ""))
220
225
  end
221
226
  end
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
4
+
3
5
  module Everywhere
4
- VERSION = "0.7.0"
6
+ VERSION = "0.8.0"
5
7
 
6
8
  # Version of the @rubyeverywhere/bridge JS this gem ships. bridge/ in the
7
9
  # gem IS the npm package (served to Rails apps by Everywhere::Engine,
8
10
  # vendored to public/ for Sinatra/Hanami), so its package.json is the single
9
11
  # source of truth. The build receipt records it; it versions independently
10
12
  # of the CLI.
11
- BRIDGE_VERSION = File.read(
12
- File.expand_path("../../bridge/package.json", __dir__)
13
- )[/"version":\s*"([^"]+)"/, 1]
13
+ BRIDGE_VERSION = JSON.parse(
14
+ File.read(File.expand_path("../../bridge/package.json", __dir__))
15
+ )["version"]
14
16
  end
data/lib/everywhere.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  # Runtime entry point: what an app gets from `require "ruby_everywhere"`.
4
4
  # The CLI lives in everywhere/cli and is only loaded by the every/rbe executables.
5
5
  require_relative "everywhere/version"
6
+ require_relative "everywhere/error"
6
7
  require_relative "everywhere/config"
7
8
  require_relative "everywhere/framework"
8
9
  require_relative "everywhere/database"
@@ -12,8 +13,6 @@ require_relative "everywhere/boot"
12
13
  require_relative "everywhere/engine" if defined?(::Rails::Engine)
13
14
 
14
15
  module Everywhere
15
- class Error < StandardError; end
16
-
17
16
  # App-facing runtime configuration. Apps register overrides at load time:
18
17
  #
19
18
  # # config/initializers/everywhere.rb (Rails) or anywhere loaded by config.ru
@@ -361,7 +361,6 @@
361
361
  buildSettings = {
362
362
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
363
363
  ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
364
- CODE_SIGN_STYLE = Automatic;
365
364
  GENERATE_INFOPLIST_FILE = YES;
366
365
  INFOPLIST_FILE = App/Info.plist;
367
366
  INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -386,7 +385,6 @@
386
385
  buildSettings = {
387
386
  ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
388
387
  ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
389
- CODE_SIGN_STYLE = Automatic;
390
388
  GENERATE_INFOPLIST_FILE = YES;
391
389
  INFOPLIST_FILE = App/Info.plist;
392
390
  INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
@@ -94,6 +94,9 @@ if [ -n "${NOTARY_KEY:-}" ]; then
94
94
  elif [ -n "${NOTARY_PROFILE:-}" ]; then
95
95
  xcrun notarytool submit "$ZIP" --keychain-profile "$NOTARY_PROFILE" --wait
96
96
  else
97
+ # Last resort. --password puts the app-specific password on the command line,
98
+ # where any process on the machine can read it out of `ps` — prefer NOTARY_KEY
99
+ # (API key, above) or NOTARY_PROFILE, neither of which passes a secret in argv.
97
100
  # Accept APPLE_APP_SPECIFIC_PASSWORD as an alias for APPLE_PASSWORD.
98
101
  APPLE_PASSWORD="${APPLE_PASSWORD:-${APPLE_APP_SPECIFIC_PASSWORD:-}}"
99
102
  : "${APPLE_ID:?set APPLE_ID or NOTARY_PROFILE}"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_everywhere
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Fomera
@@ -114,10 +114,14 @@ files:
114
114
  - lib/everywhere/blake2b.rb
115
115
  - lib/everywhere/boot.rb
116
116
  - lib/everywhere/builders/android.rb
117
+ - lib/everywhere/builders/base.rb
117
118
  - lib/everywhere/builders/desktop.rb
118
119
  - lib/everywhere/builders/ios.rb
120
+ - lib/everywhere/builders/native_sources.rb
119
121
  - lib/everywhere/child_processes.rb
122
+ - lib/everywhere/child_supervision.rb
120
123
  - lib/everywhere/cli.rb
124
+ - lib/everywhere/clock.rb
121
125
  - lib/everywhere/commands/build.rb
122
126
  - lib/everywhere/commands/clean.rb
123
127
  - lib/everywhere/commands/dev.rb
@@ -136,17 +140,31 @@ files:
136
140
  - lib/everywhere/commands/shell_dir.rb
137
141
  - lib/everywhere/commands/updates_keygen.rb
138
142
  - lib/everywhere/config.rb
143
+ - lib/everywhere/config/app.rb
144
+ - lib/everywhere/config/auth.rb
145
+ - lib/everywhere/config/data.rb
146
+ - lib/everywhere/config/deep_linking.rb
147
+ - lib/everywhere/config/desktop_ui.rb
148
+ - lib/everywhere/config/mobile.rb
149
+ - lib/everywhere/config/native_desktop.rb
150
+ - lib/everywhere/config/native_mobile.rb
151
+ - lib/everywhere/config/shell.rb
152
+ - lib/everywhere/config/updates.rb
139
153
  - lib/everywhere/console.rb
140
154
  - lib/everywhere/database.rb
141
155
  - lib/everywhere/desktop_assets.rb
156
+ - lib/everywhere/desktop_dev_app.rb
142
157
  - lib/everywhere/dock.rb
143
158
  - lib/everywhere/dock/footer.rb
144
159
  - lib/everywhere/dock/screen.rb
145
160
  - lib/everywhere/dock/state.rb
146
161
  - lib/everywhere/emulator.rb
147
162
  - lib/everywhere/engine.rb
163
+ - lib/everywhere/entrypoint.rb
164
+ - lib/everywhere/error.rb
148
165
  - lib/everywhere/fatal.rb
149
166
  - lib/everywhere/framework.rb
167
+ - lib/everywhere/host.rb
150
168
  - lib/everywhere/icon.rb
151
169
  - lib/everywhere/ignore.rb
152
170
  - lib/everywhere/jump.rb
@@ -156,18 +174,23 @@ files:
156
174
  - lib/everywhere/mobile_config_endpoint.rb
157
175
  - lib/everywhere/mobile_configs_controller.rb
158
176
  - lib/everywhere/native_helper.rb
177
+ - lib/everywhere/native_platform.rb
159
178
  - lib/everywhere/omniauth.rb
160
179
  - lib/everywhere/paths.rb
161
180
  - lib/everywhere/platform/client.rb
162
181
  - lib/everywhere/platform/credentials.rb
163
182
  - lib/everywhere/platform/snapshot.rb
183
+ - lib/everywhere/plist.rb
164
184
  - lib/everywhere/png.rb
165
185
  - lib/everywhere/raster.rb
186
+ - lib/everywhere/raw_tty.rb
166
187
  - lib/everywhere/receipt.rb
167
188
  - lib/everywhere/relay.rb
168
189
  - lib/everywhere/s3.rb
190
+ - lib/everywhere/shell_pages.rb
169
191
  - lib/everywhere/shellout.rb
170
192
  - lib/everywhere/simulator.rb
193
+ - lib/everywhere/tab_filter.rb
171
194
  - lib/everywhere/task_pool.rb
172
195
  - lib/everywhere/ui.rb
173
196
  - lib/everywhere/update_manifest.rb