Skip to content

Commit 7e3e279

Browse files
committed
Add webmanifest and basic PWA support
For new Rails apps, a suitable webmanifest is added by default
1 parent 67d951d commit 7e3e279

9 files changed

Lines changed: 142 additions & 47 deletions

File tree

app/controllers/application_controller.rb

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base
55
# allow_browser versions: :modern
66

77
include ApplicationHelper
8+
include I18nHelper
89
include Pundit::Authorization
910

1011
MEMBER_ACTIONS = %i[destroy edit show update].freeze
@@ -99,49 +100,4 @@ def mnemosyne_trace
99100
::Mnemosyne::Instrumenter.current_trace.meta['csrf_token'] = session[:_csrf_token]
100101
end
101102
end
102-
103-
def sanitized_locale_param
104-
sanitize_locale(params[:locale])
105-
end
106-
107-
def sanitized_session_locale
108-
sanitize_locale(session[:locale])
109-
end
110-
111-
def sanitized_user_preferred_locale
112-
return if current_user.nil?
113-
114-
sanitize_locale(current_user.preferred_locale)
115-
end
116-
117-
def choose_locale
118-
sanitized_locale_param ||
119-
sanitized_session_locale ||
120-
sanitized_user_preferred_locale ||
121-
http_accept_language.compatible_language_from(I18n.available_locales) ||
122-
I18n.default_locale
123-
end
124-
125-
def switch_locale(&)
126-
locale = choose_locale
127-
session[:locale] = locale
128-
Sentry.set_extras(locale:)
129-
if current_user.present? && locale != sanitized_user_preferred_locale
130-
current_user.update(preferred_locale: locale)
131-
end
132-
I18n.with_locale(locale, &)
133-
end
134-
135-
# Sanitize given locale.
136-
#
137-
# Return `nil` if the locale is blank or not available.
138-
#
139-
def sanitize_locale(locale)
140-
return if locale.blank?
141-
142-
locale = locale.downcase.to_sym
143-
return unless I18n.available_locales.include?(locale)
144-
145-
locale
146-
end
147103
end

app/helpers/i18n_helper.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
module I18nHelper
4+
include HttpAcceptLanguage::EasyAccess
5+
6+
def sanitized_locale_param
7+
sanitize_locale(params[:locale])
8+
end
9+
10+
def sanitized_session_locale
11+
sanitize_locale(session[:locale])
12+
end
13+
14+
def sanitized_user_preferred_locale
15+
return if current_user.nil?
16+
17+
sanitize_locale(current_user.preferred_locale)
18+
end
19+
20+
def choose_locale
21+
sanitized_locale_param ||
22+
sanitized_session_locale ||
23+
sanitized_user_preferred_locale ||
24+
http_accept_language.compatible_language_from(I18n.available_locales) ||
25+
I18n.default_locale
26+
end
27+
28+
def switch_locale(&)
29+
locale = choose_locale
30+
session[:locale] = locale
31+
Sentry.set_extras(locale:)
32+
if current_user.present? && locale != sanitized_user_preferred_locale
33+
current_user.update(preferred_locale: locale)
34+
end
35+
I18n.with_locale(locale, &)
36+
end
37+
38+
# Sanitize given locale.
39+
#
40+
# Return `nil` if the locale is blank or not available.
41+
#
42+
def sanitize_locale(locale)
43+
return if locale.blank?
44+
45+
locale = locale.downcase.to_sym
46+
return unless I18n.available_locales.include?(locale)
47+
48+
locale
49+
end
50+
end

app/views/layouts/application.html.slim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ html lang=I18n.locale data-default-locale=I18n.default_locale
33
head
44
meta charset='utf8'
55
meta name='viewport' content='width=device-width, initial-scale=1'
6+
meta name='mobile-web-app-capable' content='yes'
67
= render('breadcrumbs_and_title')
78
title
89
= yield(:title)
910
= favicon_link_tag('/icon.png', type: 'image/png')
1011
= favicon_link_tag('/icon.svg', type: 'image/svg+xml')
1112
= favicon_link_tag('/icon.png', rel: 'apple-touch-icon', type: 'image/png')
13+
= tag.link rel: 'manifest', href: pwa_manifest_path
1214
= stylesheet_pack_tag('stylesheets', media: 'all', 'data-turbolinks-track': 'reload', integrity: true, crossorigin: 'anonymous')
1315
= stylesheet_link_tag('application', media: 'all', 'data-turbolinks-track': 'reload', integrity: true, crossorigin: 'anonymous')
1416
= javascript_pack_tag('application', 'data-turbolinks-track': 'reload', defer: false, integrity: true, crossorigin: 'anonymous')
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# frozen_string_literal: true
2+
3+
switch_locale do
4+
json.name t('common.app_name')
5+
json.description t('application.footer.about.paragraph')
6+
json.lang I18n.locale
7+
# Categories: https://github.com/w3c/manifest/wiki/Categories
8+
json.categories %w[education productivity]
9+
json.start_url root_url
10+
json.scope Rails.application.config.relative_url_root
11+
json.display 'standalone'
12+
json.orientation 'any'
13+
json.theme_color '#6C757D'
14+
json.background_color '#FFFFFF'
15+
16+
json.icons do
17+
json.array!([
18+
{
19+
src: '/icon.png',
20+
sizes: '512x512',
21+
type: 'image/png',
22+
},
23+
{
24+
src: '/icon.svg',
25+
sizes: 'any',
26+
type: 'image/svg+xml',
27+
},
28+
])
29+
end
30+
31+
json.shortcuts do
32+
json.array!([
33+
{
34+
name: Task.model_name.human(count: :many).capitalize,
35+
url: tasks_url,
36+
},
37+
{
38+
name: Collection.model_name.human(count: :many).capitalize,
39+
url: collections_url,
40+
},
41+
{
42+
name: Group.model_name.human(count: :many).capitalize,
43+
url: groups_url,
44+
},
45+
])
46+
end
47+
48+
json.related_applications do
49+
json.array!([
50+
# No app for CodeHarbor yet :(, but empty array required for installation prompt
51+
])
52+
end
53+
json.prefer_related_applications true
54+
end

app/views/pwa/service-worker.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Add a service worker for processing Web Push notifications:
2+
//
3+
// self.addEventListener("push", async (event) => {
4+
// const { title, options } = await event.data.json()
5+
// event.waitUntil(self.registration.showNotification(title, options))
6+
// })
7+
//
8+
// self.addEventListener("notificationclick", function(event) {
9+
// event.notification.close()
10+
// event.waitUntil(
11+
// clients.matchAll({ type: "window" }).then((clientList) => {
12+
// for (let i = 0; i < clientList.length; i++) {
13+
// let client = clientList[i]
14+
// let clientPath = (new URL(client.url)).pathname
15+
//
16+
// if (clientPath == event.notification.data.path && "focus" in client) {
17+
// return client.focus()
18+
// }
19+
// }
20+
//
21+
// if (clients.openWindow) {
22+
// return clients.openWindow(event.notification.data.path)
23+
// }
24+
// })
25+
// )
26+
// })

config/initializers/content_security_policy.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def self.get_host_source(url)
8484
policy.form_action :self
8585
policy.frame_ancestors :none
8686
policy.frame_src :none
87-
policy.manifest_src :none
87+
policy.manifest_src :self
8888

8989
# Trusted Types are not yet added to the application, thus we cannot enforce them.
9090
# policy.require_trusted_types_for :script

config/initializers/mime_types.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44

55
# Add new mime types for use in respond_to blocks:
66
# Mime::Type.register "text/richtext", :rtf
7+
8+
# https://w3c.github.io/manifest/#media-type-registration
9+
Mime::Type.register 'application/manifest+json', :webmanifest

config/initializers/sentry.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
when /http/
3030
# for Rails applications, transaction_name would be the request's path (env["PATH_INFO"]) instead of "Controller#action"
3131
case transaction_name
32-
when '/', '/ping', '/users/auth/bird/metadata'
32+
when '/', '/ping', '/up', '/users/auth/bird/metadata'
3333
0.00 # ignore health check
3434
else
3535
ENV.fetch('SENTRY_TRACE_SAMPLE_RATE', 1.0).to_f

config/routes.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@
138138
# Can be used by load balancers and uptime monitors to verify that the app is live.
139139
get 'up', to: 'rails/health#show', as: :rails_health_check
140140

141+
# Render dynamic PWA files from app/views/pwa/*
142+
get 'service-worker', to: 'rails/pwa#service_worker', as: :pwa_service_worker, defaults: {format: :js}
143+
get 'manifest', to: 'rails/pwa#manifest', as: :pwa_manifest, defaults: {format: :webmanifest}
144+
141145
# Defines the root path route ("/")
142146
root to: 'home#index'
143147
end

0 commit comments

Comments
 (0)