ruby_native 0.12.4 → 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 +4 -4
- data/README.md +2 -0
- data/app/assets/stylesheets/ruby_native.css +34 -6
- data/app/controllers/ruby_native/aasa_controller.rb +11 -1
- data/app/controllers/ruby_native/assetlinks_controller.rb +30 -0
- data/lib/generators/ruby_native/install_generator.rb +14 -4
- data/lib/generators/ruby_native/templates/ruby_native.yml +10 -0
- data/lib/ruby_native/cli/preview.rb +37 -8
- data/lib/ruby_native/engine.rb +1 -0
- data/lib/ruby_native/native_detection.rb +27 -1
- data/lib/ruby_native/version.rb +1 -1
- data/lib/ruby_native.rb +14 -0
- 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: c31d1051712c237ce11a48f7ab2681ac6083c81f8fc1bb155c918f7283390d79
|
|
4
|
+
data.tar.gz: 7f3f059d502a92a389b5110cd3e8f0028adfece442dee9e44f29f2d6d5629bff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
6
|
-
(only display cutouts), so the
|
|
7
|
-
|
|
8
|
-
undefined and falls back to env()
|
|
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-
|
|
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-
|
|
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 =
|
|
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
|
-
|
|
17
|
+
contents = File.read(File.join(destination_root, dev_config))
|
|
18
|
+
return if contents.include?("trycloudflare")
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
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 =
|
|
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 #{
|
|
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 #{
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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 #{
|
|
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 #{
|
|
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
|
data/lib/ruby_native/engine.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/ruby_native/version.rb
CHANGED
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.
|
|
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
|