fix(pwa): decouple installability from DISPLAY_MANIFEST branding flag (#272)

DISPLAY_MANIFEST previously gated the entire PWA manifest link and
theme-color meta, so white-label installs that set the flag to false
to hide Chatwoot branding also lost the mobile "Install app" prompt.

Serve /manifest.json from a new dynamic controller that reflects
INSTALLATION_NAME, LOGO_THUMBNAIL and a new BRAND_COLOR config, and
keep the manifest link, theme-color and apple-mobile-web-app meta
emitted regardless of DISPLAY_MANIFEST. The flag now gates only the
Chatwoot-branded raster assets (favicons, apple-icon PNGs, ms-icon,
marketing meta description).
This commit is contained in:
Gabriel Jablonski 2026-04-22 14:33:38 -03:00 committed by GitHub
parent 079d4b4996
commit 55c7c435bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 49 additions and 47 deletions

View File

@ -10,6 +10,7 @@ class DashboardController < ActionController::Base
TERMS_URL
BRAND_URL
BRAND_NAME
BRAND_COLOR
PRIVACY_URL
DISPLAY_MANIFEST
CREATE_NEW_ACCOUNT_FROM_DASHBOARD

View File

@ -0,0 +1,35 @@
class ManifestController < ApplicationController
PNG_MIME = 'image/png'.freeze
SVG_MIME = 'image/svg+xml'.freeze
def show
config = GlobalConfig.get('INSTALLATION_NAME', 'LOGO_THUMBNAIL', 'BRAND_COLOR')
installation_name = config['INSTALLATION_NAME'].presence || 'Chatwoot'
logo = config['LOGO_THUMBNAIL'].presence || '/brand-assets/logo_thumbnail.svg'
brand_color = config['BRAND_COLOR'].presence || '#1f93ff'
icon_type = svg?(logo) ? SVG_MIME : PNG_MIME
expires_in 1.hour, public: true
render json: {
name: installation_name,
short_name: installation_name,
id: '/',
start_url: '/',
display: 'standalone',
background_color: brand_color,
theme_color: brand_color,
icons: [
{ src: logo, sizes: '192x192', type: icon_type, purpose: 'any maskable' },
{ src: logo, sizes: '512x512', type: icon_type, purpose: 'any maskable' }
]
}, content_type: 'application/manifest+json'
end
private
def svg?(url)
File.extname(URI.parse(url).path).casecmp('.svg').zero?
rescue URI::InvalidURIError
false
end
end

View File

@ -6,10 +6,15 @@
</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=0"/>
<meta name="generator" content="fazer.ai"/>
<meta name="theme-color" content="<%= @global_config['BRAND_COLOR'] %>">
<meta name="apple-mobile-web-app-title" content="<%= @global_config['INSTALLATION_NAME'] %>">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<link rel="apple-touch-icon" href="<%= @global_config['LOGO_THUMBNAIL'] %>">
<link rel="manifest" href="/manifest.json">
<% if @global_config['DISPLAY_MANIFEST'] %>
<meta name="msapplication-TileColor" content="#1f93ff">
<meta name="msapplication-TileColor" content="<%= @global_config['BRAND_COLOR'] %>">
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
<meta name="theme-color" content="#1f93ff">
<meta name="description" content="<%= @global_config['INSTALLATION_NAME'] %> is a customer support solution that helps companies engage customers over Messenger, Twitter, Telegram, WeChat, Whatsapp. Simply connect your channels and converse with your customers from a single place. Easily add new agents to your system and have them own and resolve conversations with customers. <%= @global_config['INSTALLATION_NAME'] %> also gives you real-time reports to measure your team's performance, canned responses to easily respond to frequently asked questions and private notes for agents to collaborate among themselves.">
<% if ENV['IOS_APP_IDENTIFIER'].present? %>
<meta name="apple-itunes-app" content='app-id=<%= ENV['IOS_APP_IDENTIFIER'] %>'>
@ -27,7 +32,6 @@
<link class="favicon" rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link class="favicon" rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
<link class="favicon" rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<% end %>
<link rel="icon" type="image/png" sizes="512x512" href="<%= @global_config['LOGO_THUMBNAIL'] %>">
<%= csrf_meta_tags %>

View File

@ -42,6 +42,10 @@
value: 'Chatwoot'
display_title: 'Brand Name'
description: 'The name that would be used in emails and the widget'
- name: BRAND_COLOR
value: '#1f93ff'
display_title: 'Brand Color'
description: 'Hex color used for PWA theme/tile color (example: #1f93ff). For reliable Android PWA install prompts, pair with a 192x192 and 512x512 PNG logo thumbnail.'
- name: TERMS_URL
value: 'https://www.chatwoot.com/terms-of-service'
display_title: 'Terms URL'

View File

@ -669,6 +669,7 @@ Rails.application.routes.draw do
get 'notion/callback', to: 'notion/callbacks#show'
# ----------------------------------------------------------------------
# Routes for external service verifications
get '/manifest.json' => 'manifest#show'
get '.well-known/assetlinks.json' => 'android_app#assetlinks'
get '.well-known/apple-app-site-association' => 'apple_app#site_association'
get '.well-known/microsoft-identity-association.json' => 'microsoft#identity_association'

View File

@ -24,6 +24,7 @@ module Enterprise::SuperAdmin::AppConfigsController
LOGO
LOGO_DARK
BRAND_NAME
BRAND_COLOR
INSTALLATION_NAME
BRAND_URL
WIDGET_BRAND_URL

View File

@ -1,44 +0,0 @@
{
"name": "Chatwoot",
"short_name": "Chatwoot",
"icons": [{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}],
"start_url": "/",
"display": "standalone",
"background_color": "#1f93ff",
"theme_color": "#1f93fe"
}