ruby_native 0.12.1 → 0.13.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: b7e336fc49236887320d23ecfc7718229370029d7a2b7981df2e888fd427e853
4
- data.tar.gz: 16ca5ee3894370636c36193591a135c3829c3a9bb1449065994e04fc09be6b47
3
+ metadata.gz: c31d1051712c237ce11a48f7ab2681ac6083c81f8fc1bb155c918f7283390d79
4
+ data.tar.gz: 7f3f059d502a92a389b5110cd3e8f0028adfece442dee9e44f29f2d6d5629bff
5
5
  SHA512:
6
- metadata.gz: 8039052ae6fe99f9a429564c534b9b78b1df59c5cfa9a4b0157dd3b606244d609666b360242ff25b2a9931511262bb933f0e1b668c6570ade89413dd4c058009
7
- data.tar.gz: 5b3b966610654c7f0625b2ffdb4aa42f4cb0ecccf773f06a2795faea962b6e54db0324d246959a3436083c907e1cc6b8f208275718fb598ceef74f4d42e04afd
6
+ metadata.gz: 15d77625c818cd4a63f82b1e03490a7365b3a91cd1c75e41ca893f64d9813c197683f27198e9aaa7fc96a2eb25f702f042e55cd8e333fa9812749cea9d16839c
7
+ data.tar.gz: b7cca8fcc74f7151a6eabfe2706f5644cb6e51ccf7cb9f375c60a029c036565005a9d9d1dd698dd517f08529ba98ad8126006f39e6540b2e43b2df68957c3b46
data/README.md CHANGED
@@ -72,6 +72,8 @@ This starts a Cloudflare tunnel and shows a QR code for the Ruby Native app to s
72
72
  brew install cloudflare/cloudflare/cloudflared
73
73
  ```
74
74
 
75
+ The tunnel points at port 3000, or at `PORT` when it is set, the same way `rails server` picks its port. Pass `--port 4000` to override, or `--url` for an upstream that isn't on localhost.
76
+
75
77
  See [rubynative.com/docs/cli](https://rubynative.com/docs/cli) for `deploy`, `login`, and auto-deploying from CI.
76
78
 
77
79
  ## React and Vue
@@ -2,22 +2,50 @@
2
2
  display: none !important;
3
3
  }
4
4
 
5
- /* Android's WebView doesn't populate env(safe-area-inset-*) from system bars
6
- (only display cutouts), so the native shell pushes the real top/bottom
7
- system bar heights into these CSS variables. iOS leaves the variables
8
- undefined and falls back to env(), which works there. */
5
+ /* Public API for app CSS. Android's WebView doesn't populate
6
+ env(safe-area-inset-*) from system bars (only display cutouts), so the
7
+ native shell pushes the real heights into the internal -inset- variables;
8
+ iOS leaves them undefined and falls back to env(). */
9
+ :root {
10
+ --ruby-native-safe-area-top: var(--ruby-native-safe-area-inset-top, env(safe-area-inset-top, 0px));
11
+ --ruby-native-safe-area-bottom: var(--ruby-native-safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
12
+ }
13
+
9
14
  .native-inset-top::before,
10
15
  .native-inset::before {
11
16
  content: "";
12
17
  display: block;
13
- height: var(--ruby-native-safe-area-inset-top, env(safe-area-inset-top));
18
+ height: var(--ruby-native-safe-area-top);
14
19
  }
15
20
 
16
21
  .native-inset-bottom::after,
17
22
  .native-inset::after {
18
23
  content: "";
19
24
  display: block;
20
- height: var(--ruby-native-safe-area-inset-bottom, env(safe-area-inset-bottom));
25
+ height: var(--ruby-native-safe-area-bottom);
26
+ }
27
+
28
+ /* In-app defaults so pages feel native without any setup: no gray tap flash,
29
+ no selecting button labels on long-press, no double-tap zoom on controls.
30
+ Pair with :active styles for pressed feedback; the bridge makes them apply
31
+ instantly on touch. */
32
+ [data-native-app] * {
33
+ -webkit-tap-highlight-color: transparent;
34
+ }
35
+
36
+ [data-native-app] button,
37
+ [data-native-app] [role="button"] {
38
+ -webkit-user-select: none;
39
+ user-select: none;
40
+ -webkit-touch-callout: none;
41
+ }
42
+
43
+ [data-native-app] a,
44
+ [data-native-app] button,
45
+ [data-native-app] input,
46
+ [data-native-app] label,
47
+ [data-native-app] [role="button"] {
48
+ touch-action: manipulation;
21
49
  }
22
50
 
23
51
  .native-back-button {
@@ -12,7 +12,7 @@ module RubyNative
12
12
  render json: {
13
13
  applinks: {
14
14
  details: [
15
- { appIDs: [ app_id ], components: oauth_exclusions + [ { "/": "*" } ] }
15
+ { appIDs: [ app_id ], components: oauth_exclusions + linked_components }
16
16
  ]
17
17
  },
18
18
  webcredentials: {
@@ -37,5 +37,15 @@ module RubyNative
37
37
  { "/": "#{path}*", exclude: true }
38
38
  end
39
39
  end
40
+
41
+ # `linked_paths` scopes Universal Links to those prefixes; without it every
42
+ # URL on the domain is linked. Password sharing (webcredentials) stays
43
+ # domain-wide either way; there is no path concept for it.
44
+ def linked_components
45
+ paths = Array(RubyNative.config&.dig(:linked_paths))
46
+ return [ { "/": "*" } ] if paths.empty?
47
+
48
+ paths.map { |path| { "/": "#{path}*" } }
49
+ end
40
50
  end
41
51
  end
@@ -0,0 +1,30 @@
1
+ module RubyNative
2
+ class AssetlinksController < ::ActionController::Base
3
+ def show
4
+ RubyNative.load_config if Rails.env.development?
5
+
6
+ package = RubyNative.config&.dig(:android, :package)
7
+ # The Digital Asset Links spec wants uppercase colon-separated hex, so a
8
+ # lowercase paste from keytool still verifies.
9
+ fingerprints = Array(RubyNative.config&.dig(:android, :cert_fingerprint))
10
+ .map { |fingerprint| fingerprint.to_s.strip.upcase }
11
+ .reject(&:empty?)
12
+ return head :not_found unless package.present? && fingerprints.present?
13
+
14
+ response.set_header("Cache-Control", "no-cache")
15
+ render json: [
16
+ {
17
+ relation: [
18
+ "delegate_permission/common.handle_all_urls",
19
+ "delegate_permission/common.get_login_creds"
20
+ ],
21
+ target: {
22
+ namespace: "android_app",
23
+ package_name: package,
24
+ sha256_cert_fingerprints: fingerprints
25
+ }
26
+ }
27
+ ]
28
+ end
29
+ end
30
+ end
@@ -10,14 +10,24 @@ module RubyNative
10
10
  end
11
11
 
12
12
  def add_allowed_host
13
- host_line = ' config.hosts << ".trycloudflare.com"'
13
+ host_line = %( config.hosts << ".trycloudflare.com" if config.hosts.is_a?(Array)\n)
14
14
  dev_config = "config/environments/development.rb"
15
15
 
16
16
  return unless File.exist?(File.join(destination_root, dev_config))
17
- return if File.read(File.join(destination_root, dev_config)).include?("trycloudflare")
17
+ contents = File.read(File.join(destination_root, dev_config))
18
+ return if contents.include?("trycloudflare")
18
19
 
19
- environment(host_line, env: "development")
20
- say " Added .trycloudflare.com to allowed hosts in development.rb", :green
20
+ # Match any configure block, not just Rails.application.configure; some
21
+ # apps use MyApp::Application.configure and Thor's environment helper
22
+ # silently skips those.
23
+ configure_block = /^[\w:.]+\.configure do\s*\n/
24
+ if contents.match?(configure_block)
25
+ inject_into_file dev_config, host_line, after: configure_block, verbose: false
26
+ say " Added .trycloudflare.com to allowed hosts in development.rb", :green
27
+ else
28
+ say " Couldn't find a configure block in development.rb. Add this line inside it:", :yellow
29
+ say " #{host_line.strip}", :yellow
30
+ end
21
31
  end
22
32
 
23
33
  def add_gitignore
@@ -109,6 +109,16 @@ tabs:
109
109
  # ios:
110
110
  # bundle_id: com.example.myapp
111
111
  # team_id: ABCD123456
112
+ # android:
113
+ # package: com.example.myapp
114
+ # # Play Console -> Setup -> App integrity -> App signing key certificate.
115
+ # cert_fingerprint: "AA:BB:CC:..."
116
+
117
+ # Link only these path prefixes instead of the whole domain. Links outside
118
+ # them keep opening in the browser. Applies to iOS on your next deploy and
119
+ # to Android on your next build.
120
+ # linked_paths:
121
+ # - /pair/
112
122
 
113
123
  # Enable OAuth. Each path triggers a native ASWebAuthenticationSession
114
124
  # on iOS instead of an in-app web view. List only the authorize path for each
@@ -11,10 +11,12 @@ module RubyNative
11
11
  TUNNEL_READY_TIMEOUT = 60
12
12
  TUNNEL_POLL_INTERVAL = 1
13
13
  PUBLIC_NAMESERVERS = ["1.1.1.1", "8.8.8.8"].freeze
14
+ DEFAULT_PORT = 3000
15
+ PORT_RANGE = (1..65_535)
14
16
 
15
17
  def initialize(argv)
16
18
  @url = parse_option(argv, "--url")
17
- @port = parse_port(argv)
19
+ @port, @port_from_env = resolve_port(argv)
18
20
  @upstream = @url || "http://localhost:#{@port}"
19
21
  end
20
22
 
@@ -32,7 +34,7 @@ module RubyNative
32
34
 
33
35
  return if response.is_a?(Net::HTTPSuccess)
34
36
 
35
- puts "Rails server is reachable at #{@upstream}, but #{CONFIG_PATH} returned #{response.code}."
37
+ puts "Rails server is reachable at #{upstream_description}, but #{CONFIG_PATH} returned #{response.code}."
36
38
  puts ""
37
39
 
38
40
  # 404 is what an unmounted engine returns; any other code means the app
@@ -57,7 +59,7 @@ module RubyNative
57
59
  puts ""
58
60
  puts "Make sure your Rails server is reachable at that URL."
59
61
  else
60
- puts "Nothing is running on port #{@port}."
62
+ puts "Nothing is running on port #{port_description}."
61
63
  puts ""
62
64
  puts "Start your Rails server in another terminal:"
63
65
  puts " bin/rails server -p #{@port}"
@@ -121,9 +123,36 @@ module RubyNative
121
123
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
122
124
  end
123
125
 
124
- def parse_port(argv)
125
- value = parse_option(argv, "--port")
126
- value ? value.to_i : 3000
126
+ # --port, then PORT, then 3000 is the precedence rails server itself uses,
127
+ # so a preview lands on the port the app is already serving. Anything
128
+ # unusable in PORT falls back rather than failing: the variable gets set
129
+ # for plenty of reasons that have nothing to do with a local Rails server.
130
+ def resolve_port(argv)
131
+ flag = parse_option(argv, "--port")
132
+ return [flag.to_i, false] if flag
133
+
134
+ env = env_port
135
+ env ? [env, true] : [DEFAULT_PORT, false]
136
+ end
137
+
138
+ def env_port
139
+ value = ENV["PORT"].to_s.strip
140
+ return unless value.match?(/\A\d+\z/)
141
+
142
+ port = value.to_i
143
+ port if PORT_RANGE.cover?(port)
144
+ end
145
+
146
+ # Naming the source keeps a port nobody typed from reading as a bug.
147
+ def port_description
148
+ @port_from_env ? "#{@port} (from PORT)" : @port.to_s
149
+ end
150
+
151
+ # @port_from_env can be true alongside --url, which ignores the port.
152
+ def upstream_description
153
+ return @upstream if @url || !@port_from_env
154
+
155
+ "#{@upstream} (port from PORT)"
127
156
  end
128
157
 
129
158
  def parse_option(argv, flag)
@@ -148,7 +177,7 @@ module RubyNative
148
177
  if @url
149
178
  puts "Make sure your Rails server is reachable at #{@url}."
150
179
  else
151
- puts "Make sure your Rails server is running on port #{@port} in another terminal."
180
+ puts "Make sure your Rails server is running on port #{port_description} in another terminal."
152
181
  end
153
182
  puts ""
154
183
 
@@ -201,7 +230,7 @@ module RubyNative
201
230
  if @url
202
231
  puts "Keep this running and your Rails server reachable at #{@url}."
203
232
  else
204
- puts "Keep this running and your Rails server on port #{@port} in another terminal."
233
+ puts "Keep this running and your Rails server on port #{port_description} in another terminal."
205
234
  end
206
235
  puts "Press Ctrl+C to stop."
207
236
  end
@@ -51,6 +51,7 @@ module RubyNative
51
51
  initializer "ruby_native.routes" do |app|
52
52
  app.routes.prepend do
53
53
  get "/.well-known/apple-app-site-association", to: "ruby_native/aasa#show"
54
+ get "/.well-known/assetlinks.json", to: "ruby_native/assetlinks#show"
54
55
  mount RubyNative::Engine, at: "/native"
55
56
  end
56
57
  end
@@ -2,8 +2,17 @@ module RubyNative
2
2
  module NativeDetection
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ # Matches "Ruby Native iOS/5.2/35 iOS/26.5.2 RubyNative/0.12.5". The build and
6
+ # OS groups are optional: apps built before the UA carried them send only
7
+ # "Ruby Native iOS/5.2", and the optional group keeps the OS capture from
8
+ # swallowing that app version.
9
+ SIGNATURE = %r{Ruby Native (?:iOS|Android)/([\d.]+)(?:/([\d.]+) (?:iOS|Android)/([\d.]+))?}
10
+
5
11
  included do
6
- helper_method :native_app?, :native_version, :native_platform if respond_to?(:helper_method)
12
+ if respond_to?(:helper_method)
13
+ helper_method :native_app?, :native_version, :native_platform,
14
+ :native_app_version, :native_app_build, :native_os_version
15
+ end
7
16
  end
8
17
 
9
18
  def native_app?
@@ -23,5 +32,22 @@ module RubyNative
23
32
  return "android" if ua.include?("Ruby Native Android")
24
33
  nil
25
34
  end
35
+
36
+ # The app's marketing version, like "5.2". Nil for web browsers.
37
+ def native_app_version
38
+ request.user_agent.to_s[SIGNATURE, 1]
39
+ end
40
+
41
+ # The app's build number (CFBundleVersion / versionCode), like "35". Nil for
42
+ # web browsers and apps built before the User-Agent carried it.
43
+ def native_app_build
44
+ request.user_agent.to_s[SIGNATURE, 2]
45
+ end
46
+
47
+ # The device's OS version, like "26.5.2". Nil for web browsers and apps built
48
+ # before the User-Agent carried it.
49
+ def native_os_version
50
+ request.user_agent.to_s[SIGNATURE, 3]
51
+ end
26
52
  end
27
53
  end
@@ -1,3 +1,3 @@
1
1
  module RubyNative
2
- VERSION = "0.12.1"
2
+ VERSION = "0.13.0"
3
3
  end
data/lib/ruby_native.rb CHANGED
@@ -49,6 +49,7 @@ module RubyNative
49
49
  self.config[:app][:entry_path] ||= self.config.dig(:tabs, 0, :path) || "/"
50
50
  self.config[:auth] ||= {}
51
51
  normalize_oauth_paths
52
+ normalize_linked_paths
52
53
  backfill_tab_icons
53
54
  backfill_error_icons
54
55
  end
@@ -183,4 +184,17 @@ module RubyNative
183
184
  )
184
185
  self.config[:auth][:oauth_paths] = paths - callbacks
185
186
  end
187
+
188
+ # Entries are path prefixes: "/pair/" links every URL under it. A leading
189
+ # slash is added when missing, and a trailing "*" (an easy slip, since the
190
+ # AASA file uses one) is stripped so both platforms receive a plain prefix.
191
+ # Left absent when unconfigured so config.json doesn't grow an empty key.
192
+ def self.normalize_linked_paths
193
+ return unless self.config.key?(:linked_paths)
194
+
195
+ self.config[:linked_paths] = Array(self.config[:linked_paths])
196
+ .map { |path| path.to_s.strip.sub(/\*+\z/, "") }
197
+ .reject(&:empty?)
198
+ .map { |path| path.start_with?("/") ? path : "/#{path}" }
199
+ end
186
200
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Masilotti
@@ -70,6 +70,7 @@ files:
70
70
  - README.md
71
71
  - app/assets/stylesheets/ruby_native.css
72
72
  - app/controllers/ruby_native/aasa_controller.rb
73
+ - app/controllers/ruby_native/assetlinks_controller.rb
73
74
  - app/controllers/ruby_native/auth/sessions_controller.rb
74
75
  - app/controllers/ruby_native/auth/start_controller.rb
75
76
  - app/controllers/ruby_native/config_controller.rb