Module: RubyNative

Defined in:
lib/ruby_native.rb,
lib/ruby_native/cli.rb,
lib/ruby_native/engine.rb,
lib/ruby_native/helper.rb,
lib/ruby_native/version.rb,
lib/ruby_native/cli/login.rb,
lib/ruby_native/iap/event.rb,
lib/ruby_native/cli/deploy.rb,
lib/ruby_native/cli/preview.rb,
lib/ruby_native/iap/decodable.rb,
lib/ruby_native/iap/verifiable.rb,
lib/ruby_native/native_version.rb,
lib/ruby_native/cli/credentials.rb,
lib/ruby_native/inertia_support.rb,
lib/ruby_native/iap/normalizable.rb,
lib/ruby_native/native_detection.rb,
lib/ruby_native/oauth_middleware.rb,
lib/generators/ruby_native/iap_generator.rb,
lib/ruby_native/tunnel_cookie_middleware.rb,
app/models/ruby_native/iap/purchase_intent.rb,
lib/ruby_native/screenshots/sign_in_helper.rb,
app/controllers/ruby_native/aasa_controller.rb,
lib/ruby_native/iap/apple_webhook_processor.rb,
lib/generators/ruby_native/install_generator.rb,
app/controllers/ruby_native/config_controller.rb,
app/controllers/ruby_native/auth/start_controller.rb,
app/controllers/ruby_native/iap/restores_controller.rb,
app/controllers/ruby_native/push/devices_controller.rb,
app/controllers/ruby_native/auth/sessions_controller.rb,
app/controllers/ruby_native/iap/purchases_controller.rb,
app/controllers/ruby_native/webhooks/apple_controller.rb,
app/controllers/ruby_native/iap/completions_controller.rb,
app/controllers/ruby_native/screenshots/sessions_controller.rb

Defined Under Namespace

Modules: Auth, Generators, Helper, IAP, InertiaSupport, NativeDetection, Push, Screenshots, Webhooks Classes: AasaController, CLI, ConfigController, Engine, NativeVersion, OAuthMiddleware, TunnelCookieMiddleware

Constant Summary collapse

ERROR_SCREEN_STATES =

The native fallback screen has two states: offline (no connectivity) and generic (any other load failure). Each can carry a per-platform icon and localized copy.

%i[offline generic].freeze
ERROR_SCREEN_COPY_KEYS =
%i[title message].freeze
VERSION =
"0.10.14"

Class Method Summary collapse

Class Method Details

.backfill_error_iconsObject

Mirrors backfill_tab_icons for the error screen: fills a state's flat icon from its per-platform icons (ios first, then android), so the iOS app, which reads only the flat icon, still renders one. An explicit icon: wins.



91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/ruby_native.rb', line 91

def self.backfill_error_icons
  errors = self.config[:errors]
  return unless errors.is_a?(Hash)

  ERROR_SCREEN_STATES.each do |state|
    state_config = errors[state]
    next unless state_config.is_a?(Hash)

    icons = state_config[:icons]
    next unless icons.is_a?(Hash)

    state_config[:icon] ||= icons[:ios] || icons[:android]
  end
end

.backfill_tab_iconsObject

Mirrors per-platform icons: into the legacy flat icon: field so native binaries that only read tab.icon keep rendering an icon. Explicit icon: wins; otherwise falls back to icons.ios, then icons.android.



76
77
78
79
80
81
82
83
84
85
# File 'lib/ruby_native.rb', line 76

def self.backfill_tab_icons
  Array(self.config[:tabs]).each do |tab|
    next unless tab.is_a?(Hash)

    icons = tab[:icons]
    next unless icons.is_a?(Hash)

    tab[:icon] ||= icons[:ios] || icons[:android]
  end
end

.config_as_jsonObject

The JSON served at GET /native/config. Identical to config, except the errors block is enriched: per-state icons from config/ruby_native.yml are merged with localized title/message pulled from the host app's I18n (ruby_native.errors.<state>.<key>), one entry per available locale. Only values the developer actually provided are emitted; the native apps fall back to bundled English copy for anything missing. Built on a deep copy so the in-memory config the server reads for view helpers is never mutated.



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/ruby_native.rb', line 119

def self.config_as_json
  return config if config.nil?

  payload = config.deep_dup
  errors = error_screen_config(payload[:errors])
  if errors.empty?
    payload.delete(:errors)
  else
    payload[:errors] = errors
  end
  payload
end

.configure {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:

  • _self (RubyNative)

    the object that the method was called on



31
32
33
# File 'lib/ruby_native.rb', line 31

def self.configure
  yield self
end

.error_screen_config(yaml_errors) ⇒ Object

Merges per-state error-screen icons (from YAML) with localized copy (from I18n) into the shape the native apps decode. Omits any state with neither an icon nor copy, so an untouched app sends no errors block at all.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ruby_native.rb', line 135

def self.error_screen_config(yaml_errors)
  config = ERROR_SCREEN_STATES.each_with_object({}) do |state, result|
    entry = {}
    state_config = yaml_errors[state] if yaml_errors.is_a?(Hash)
    if state_config.is_a?(Hash)
      entry[:icon] = state_config[:icon] if state_config[:icon]
      entry[:icons] = state_config[:icons] if state_config[:icons]
    end
    ERROR_SCREEN_COPY_KEYS.each do |key|
      translations = error_screen_translations("#{state}.#{key}")
      entry[key] = translations unless translations.empty?
    end
    result[state] = entry unless entry.empty?
  end

  # The Retry button label is shared by both states, so it sits at the top of
  # the block rather than under a state.
  retry_label = error_screen_translations("retry")
  config[:retry] = retry_label unless retry_label.empty?
  config
end

.error_screen_translations(subkey) ⇒ Object

Reads ruby_native.errors.<subkey> for every available locale, keeping only the locales the developer actually translated. Copy lives in the host app's own locale files; the gem ships none.



160
161
162
163
164
165
# File 'lib/ruby_native.rb', line 160

def self.error_screen_translations(subkey)
  I18n.available_locales.each_with_object({}) do |locale, result|
    value = I18n.t("ruby_native.errors.#{subkey}", locale: locale, default: nil)
    result[locale] = value unless value.nil?
  end
end

.fire_subscription_callbacks(event) ⇒ Object



39
40
41
# File 'lib/ruby_native.rb', line 39

def self.fire_subscription_callbacks(event)
  subscription_callbacks.each { |cb| cb.call(event) }
end

.load_configObject



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ruby_native.rb', line 43

def self.load_config
  path = Rails.root.join("config", "ruby_native.yml")
  return unless path.exist?

  self.config = YAML.load(render_config(path)).deep_symbolize_keys
  self.config[:app] ||= {}
  self.config[:app][:entry_path] ||= self.config.dig(:tabs, 0, :path) || "/"
  self.config[:auth] ||= {}
  normalize_oauth_paths
  backfill_tab_icons
  backfill_error_icons
end

.normalize_oauth_pathsObject

auth.oauth_paths must list only OAuth authorize paths, never their callbacks. The native app treats every listed path as a sign-in trigger and derives the provider from the last path segment, so a callback entry like "/auth/google/callback" would launch a bogus flow for a provider named "callback" and send sign-in into a loop. The callback round-trip is handled automatically by OAuthMiddleware's tracking cookie, so it never needs listing. Drop any entry that is the "/callback" child of another listed path and warn, so a copied-in callback can't break native sign-in.



175
176
177
178
179
180
181
182
183
184
185
# File 'lib/ruby_native.rb', line 175

def self.normalize_oauth_paths
  paths = Array(self.config.dig(:auth, :oauth_paths))
  callbacks = paths.select { |path| paths.any? { |start| path == "#{start}/callback" } }
  return if callbacks.empty?

  Rails.logger.warn(
    "[RubyNative] Ignoring OAuth callback path(s) in config/ruby_native.yml " \
    "(#{callbacks.join(", ")}). List only the authorize path; callbacks are handled automatically."
  )
  self.config[:auth][:oauth_paths] = paths - callbacks
end

.on_subscription_change(&block) ⇒ Object



35
36
37
# File 'lib/ruby_native.rb', line 35

def self.on_subscription_change(&block)
  subscription_callbacks << block
end

.render_config(path) ⇒ Object

config/ruby_native.yml is rendered as ERB before it is parsed, so a developer can interpolate Rails helpers into it. The motivating case is the navbar logo: logo: "<%= image_url("logo.png") %>" resolves to a fingerprinted asset URL the native app downloads and caches, and because the digest changes whenever the asset changes, the cache busts itself. A full URL (a CDN, say) works just as well; the app only ever sees a URL to fetch.

The template renders against the controller helper proxy, so image_url and friends behave exactly as they do in a view. With no request or asset host they degrade to a relative path -- asset helpers never raise "missing host" the way routing helpers do -- and the native app resolves any relative URL against the base URL it already fetched the config from.



68
69
70
71
# File 'lib/ruby_native.rb', line 68

def self.render_config(path)
  helpers = ActionController::Base.helpers
  ERB.new(path.read, trim_mode: "-").result(helpers.instance_eval { binding })
end