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.11"

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.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ruby_native.rb', line 85

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`.



70
71
72
73
74
75
76
77
78
79
# File 'lib/ruby_native.rb', line 70

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.



113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/ruby_native.rb', line 113

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



25
26
27
# File 'lib/ruby_native.rb', line 25

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.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ruby_native.rb', line 129

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.



154
155
156
157
158
159
# File 'lib/ruby_native.rb', line 154

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



33
34
35
# File 'lib/ruby_native.rb', line 33

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

.load_configObject



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ruby_native.rb', line 37

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.



169
170
171
172
173
174
175
176
177
178
179
# File 'lib/ruby_native.rb', line 169

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



29
30
31
# File 'lib/ruby_native.rb', line 29

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.



62
63
64
65
# File 'lib/ruby_native.rb', line 62

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