Module: Everywhere::NativeHelper

Defined in:
lib/everywhere/native_helper.rb

Overview

Server-side view helpers mirroring the JS bridge's platform detection, so ERB can branch on the native shell the same way Everywhere.platform does on the client:

<%= link_to "Install the app", … unless native_app? %>
<div class="<%= "native-inset" if native_app? %>">
<% if native_version && native_version >= Gem::Version.new("1.2") %>

Detection is by User-Agent: the RubyEverywhere shells prepend "RubyEverywhere/ ()" to Hotwire Native's own "Hotwire Native iOS/Android" marker. Included into all Rails views by Everywhere::Engine.

Instance Method Summary collapse

Instance Method Details

#everywhere_auth_redirect(to) ⇒ Object

Where to send the user after an auth change. In a native shell, route through the reset page (/everywhere/reset) so the app rebuilds cleanly — fresh web views, re-fetched tabs — then lands on to. In a browser it's just to. Use it in controllers:

redirect_to everywhere_auth_redirect(after_authentication_url)

to may be a full URL or a path; only the same-origin path is forwarded.



55
56
57
58
59
60
61
# File 'lib/everywhere/native_helper.rb', line 55

def everywhere_auth_redirect(to)
  return to unless native_app? && respond_to?(:everywhere_reset_path)

  path = to.to_s.sub(%r{\Ahttps?://[^/]+}, "")
  path = "/" if path.empty? || !path.start_with?("/")
  everywhere_reset_path(to: path)
end

#everywhere_badge(count) ⇒ Object

App icon badge count, server-rendered. Emits a meta tag the JS bridge applies on every Turbo visit — CSP-safe (no inline script) and correct on the first paint. 0 clears the badge. Put it in the layout :

<%= everywhere_badge Current.user.unread_count %>

In the mobile shell this badges the app icon; in an installed PWA the Badging API; elsewhere it renders inert metadata.



71
72
73
# File 'lib/everywhere/native_helper.rb', line 71

def everywhere_badge(count)
  tag.meta(name: "everywhere:badge", content: count.to_i)
end

#everywhere_biometric_lock(reason: nil, name: nil, allow_passcode: true, unlock_label: "Unlock", locked_message: nil, wrapper_class: nil, locked_class: nil, message_class: nil, unlock_class: nil, &block) ⇒ Object

Biometric gate: wraps sensitive markup so the mobile shell keeps it hidden until Face ID / Touch ID passes — but only when the user has the device-local lock turned on (everywhere_biometric_toggle, or Everywhere.biometrics.setLockEnabled). Browsers and the desktop shell render the content plainly. Requires biometrics in everywhere.yml's permissions.

<%= everywhere_biometric_lock reason: "Unlock account settings" do %>
...profile, password, sessions...
<% end %>

name keys the once-per-session unlock (defaults to the page path). For a fully custom overlay, hand-write the contract this emits:



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/everywhere/native_helper.rb', line 105

def everywhere_biometric_lock(reason: nil, name: nil, allow_passcode: true,
                              unlock_label: "Unlock", locked_message: nil,
                              wrapper_class: nil, locked_class: nil,
                              message_class: nil, unlock_class: nil, &block)
  content = respond_to?(:capture) ? capture(&block) : block.call
  return content unless native_app?

  attrs = [%(data-everywhere-biometric-lock="#{_everywhere_attr(name)}")]
  attrs << %(data-everywhere-biometric-reason="#{_everywhere_attr(reason)}") if reason
  attrs << "data-everywhere-biometric-passcode" if allow_passcode
  attrs << %(class="#{_everywhere_attr(wrapper_class)}") if wrapper_class

  html = +"<div #{attrs.join(" ")}>"
  html << %(<div data-everywhere-biometric-content hidden>#{content}</div>)
  html << %(<div data-everywhere-biometric-locked hidden#{%( class="#{_everywhere_attr(locked_class)}") if locked_class}>)
  html << %(<p#{%( class="#{_everywhere_attr(message_class)}") if message_class}>#{_everywhere_attr(locked_message)}</p>) if locked_message
  html << %(<button type="button" data-everywhere-biometric-unlock#{%( class="#{_everywhere_attr(unlock_class)}") if unlock_class}>#{_everywhere_attr(unlock_label)}</button>)
  html << "</div></div>"
  html.respond_to?(:html_safe) ? html.html_safe : html
end

#everywhere_biometric_toggle(css_class: nil, id: nil) ⇒ Object

The settings switch for the device-local biometric lock. Renders hidden and disabled; the bridge reveals and enables it only in the mobile shell with working biometrics (toggling runs the biometric check first). Put data-everywhere-biometric-toggle-row + hidden on the surrounding row to reveal the whole thing together:



135
136
137
138
139
140
141
# File 'lib/everywhere/native_helper.rb', line 135

def everywhere_biometric_toggle(css_class: nil, id: nil)
  attrs = +""
  attrs << %( id="#{_everywhere_attr(id)}") if id
  attrs << %( class="#{_everywhere_attr(css_class)}") if css_class
  html = %(<input type="checkbox" hidden disabled data-everywhere-biometric-toggle#{attrs}>)
  html.respond_to?(:html_safe) ? html.html_safe : html
end

#everywhere_tab_badge(path, count) ⇒ Object

Native tab bar badge for the tab whose everywhere.yml path is path. Same delivery as everywhere_badge; 0 clears. Mobile shell only.

<%= everywhere_tab_badge "/inbox", Current.user.unread_count %>


79
80
81
82
# File 'lib/everywhere/native_helper.rb', line 79

def everywhere_tab_badge(path, count)
  tag.meta(name: "everywhere:tab-badge",
           content: JSON.generate({ path: path, count: count.to_i }))
end

#native_app?Boolean

True inside any RubyEverywhere native shell (iOS or Android).

Returns:

  • (Boolean)


21
22
23
# File 'lib/everywhere/native_helper.rb', line 21

def native_app?
  !native_platform.nil?
end

#native_platformObject

:ios, :android, or nil in a plain browser. Reads Hotwire Native's UA marker, so it's correct even before the JS bridge has booted.



27
28
29
30
31
32
33
# File 'lib/everywhere/native_helper.rb', line 27

def native_platform
  ua = _everywhere_user_agent or return nil
  return :ios if ua.include?("Hotwire Native iOS")
  return :android if ua.include?("Hotwire Native Android")

  nil
end

#native_versionObject

The shell's version as a Gem::Version (from the "RubyEverywhere/" UA prefix), or nil outside the shell / when unparseable. Use it to gate features that need a newer shell than some users have installed.



38
39
40
41
42
43
44
45
# File 'lib/everywhere/native_helper.rb', line 38

def native_version
  ua = _everywhere_user_agent or return nil
  match = ua.match(%r{RubyEverywhere/([\w.\-]+)}) or return nil

  Gem::Version.new(match[1])
rescue ArgumentError
  nil
end