Make the web manifest dynamic to incorporate preferred color scheme (#398)

Remove the static manifest file and serve it via an API route instead.

Change the background color of the PWA depending on the client hint provided data
This commit is contained in:
SatsAllDay 2023-08-15 13:58:27 -04:00 committed by GitHub
parent 670c567bff
commit e6ee7f73c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 174 additions and 130 deletions

View File

@ -44,6 +44,17 @@ module.exports = withPlausibleProxy()({
crossOrigin: isProd ? 'anonymous' : undefined,
async headers () {
return [
{
source: '/',
headers: [
{
// This tells the browser to send this client hint in subsequent requests
// Only added to the "/" path since that's what is initially loaded for the PWA
key: 'Accept-CH',
value: 'Sec-CH-Prefers-Color-Scheme'
}
]
},
{
source: '/_next/:asset*',
headers: corsHeaders

View File

@ -6,7 +6,7 @@ class MyDocument extends Document {
return (
<Html lang='en'>
<Head>
<link rel='manifest' href='/site.webmanifest' />
<link rel='manifest' href='/api/site.webmanifest' />
<link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff2`} as='font' type='font/woff2' crossOrigin='' />
<link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.woff`} as='font' type='font/woff' crossOrigin='' />
<link rel='preload' href={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/Lightningvolt-xoqm.ttf`} as='font' type='font/ttf' crossOrigin='' />

View File

@ -0,0 +1,160 @@
const black = '#121214'
const yellow = '#FADA5E'
const defaultManifest = {
name: 'Stacker News',
short_name: 'SN',
icons: [
{
src: '/icons/icon_x48.png',
type: 'image/png',
sizes: '48x48',
purpose: 'any'
},
{
src: '/icons/icon_x72.png',
type: 'image/png',
sizes: '72x72',
purpose: 'any'
},
{
src: '/icons/icon_x96.png',
type: 'image/png',
sizes: '96x96',
purpose: 'any'
},
{
src: '/icons/icon_x128.png',
type: 'image/png',
sizes: '128x128',
purpose: 'any'
},
{
src: '/icons/icon_x192.png',
type: 'image/png',
sizes: '192x192',
purpose: 'any'
},
{
src: '/icons/icon_x384.png',
type: 'image/png',
sizes: '384x384',
purpose: 'any'
},
{
src: '/icons/icon_x512.png',
type: 'image/png',
sizes: '512x512',
purpose: 'any'
},
{
src: '/maskable/icon_x48.png',
type: 'image/png',
sizes: '48x48',
purpose: 'maskable'
},
{
src: '/maskable/icon_x72.png',
type: 'image/png',
sizes: '72x72',
purpose: 'maskable'
},
{
src: '/maskable/icon_x96.png',
type: 'image/png',
sizes: '96x96',
purpose: 'maskable'
},
{
src: '/maskable/icon_x128.png',
type: 'image/png',
sizes: '128x128',
purpose: 'maskable'
},
{
src: '/maskable/icon_x192.png',
type: 'image/png',
sizes: '192x192',
purpose: 'maskable'
},
{
src: '/maskable/icon_x384.png',
type: 'image/png',
sizes: '384x384',
purpose: 'maskable'
},
{
src: '/maskable/icon_x512.png',
type: 'image/png',
sizes: '512x512',
purpose: 'maskable'
}
],
display: 'standalone',
orientation: 'any',
theme_color: black,
background_color: yellow,
id: '/',
start_url: '/',
url_handlers: [
{
origin: 'https://stacker.news'
}
],
share_target: {
action: '/share',
method: 'GET',
enctype: 'application/x-www-form-urlencoded',
params: {
title: 'title',
text: 'text',
url: 'url'
}
},
description: 'Stacker News is like Hacker News but it pays you Bitcoin',
categories: ['news', 'bitcoin', 'lightning', 'zaps', 'community'],
screenshots: [
{
src: '/shot/narrow.png',
type: 'image/jpeg',
sizes: '1080x1440',
form_factor: 'narrow'
},
{
src: '/shot/wide.png',
type: 'image/jpeg',
sizes: '2048x1186',
form_factor: 'wide'
}
]
}
const getManifest = (colorScheme) => {
if (colorScheme?.toLowerCase() === 'dark') {
return {
...defaultManifest,
background_color: black
}
}
return defaultManifest
}
const handler = (req, res) => {
// Only GET requests allowed on this endpoint
if (req.method !== 'GET') {
res.status(405).end()
return
}
const PREFERS_COLOR_SCHEMA_HEADER = 'Sec-CH-Prefers-Color-Scheme'
// This endpoint wants to know the preferred color scheme
res.setHeader('Accept-CH', PREFERS_COLOR_SCHEMA_HEADER)
// The response of this endpoint will vary based on the color scheme
res.setHeader('Vary', PREFERS_COLOR_SCHEMA_HEADER)
// Ensure the header is sent in the request - forces user agent to reissue the request if it wasn't sent
res.setHeader('Critical-CH', PREFERS_COLOR_SCHEMA_HEADER)
const colorScheme = req.headers[PREFERS_COLOR_SCHEMA_HEADER.toLowerCase()]
res.status(200).json(getManifest(colorScheme))
}
export default handler

View File

@ -1,7 +1,7 @@
export default function handler (req, res) {
return res.status(200).json({
web_apps: [{
manifest: 'https://stacker.news/site.webmanifest',
manifest: 'https://stacker.news/api/site.webmanifest',
details: {
paths: ['*'],
exclude_paths: []

View File

@ -1,127 +0,0 @@
{
"name": "Stacker News",
"short_name": "SN",
"icons": [
{
"src": "/icons/icon_x48.png",
"type": "image/png",
"sizes": "48x48",
"purpose": "any"
},
{
"src": "/icons/icon_x72.png",
"type": "image/png",
"sizes": "72x72",
"purpose": "any"
},
{
"src": "/icons/icon_x96.png",
"type": "image/png",
"sizes": "96x96",
"purpose": "any"
},
{
"src": "/icons/icon_x128.png",
"type": "image/png",
"sizes": "128x128",
"purpose": "any"
},
{
"src": "/icons/icon_x192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "any"
},
{
"src": "/icons/icon_x384.png",
"type": "image/png",
"sizes": "384x384",
"purpose": "any"
},
{
"src": "/icons/icon_x512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "any"
},
{
"src": "/maskable/icon_x48.png",
"type": "image/png",
"sizes": "48x48",
"purpose": "maskable"
},
{
"src": "/maskable/icon_x72.png",
"type": "image/png",
"sizes": "72x72",
"purpose": "maskable"
},
{
"src": "/maskable/icon_x96.png",
"type": "image/png",
"sizes": "96x96",
"purpose": "maskable"
},
{
"src": "/maskable/icon_x128.png",
"type": "image/png",
"sizes": "128x128",
"purpose": "maskable"
},
{
"src": "/maskable/icon_x192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "maskable"
},
{
"src": "/maskable/icon_x384.png",
"type": "image/png",
"sizes": "384x384",
"purpose": "maskable"
},
{
"src": "/maskable/icon_x512.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"display": "standalone",
"orientation": "any",
"theme_color": "#121214",
"background_color": "#FADA5E",
"id": "/",
"start_url": "/",
"url_handlers": [
{
"origin": "https://stacker.news"
}
],
"share_target": {
"action": "/share",
"method": "GET",
"enctype": "application/x-www-form-urlencoded",
"params": {
"title": "title",
"text": "text",
"url": "url"
}
},
"description": "Stacker News is like Hacker News but it pays you Bitcoin",
"categories": ["news", "bitcoin", "lightning", "zaps", "community"],
"screenshots": [
{
"src": "/shot/narrow.png",
"type": "image/jpeg",
"sizes": "1080x1440",
"form_factor": "narrow"
},
{
"src": "/shot/wide.png",
"type": "image/jpeg",
"sizes": "2048x1186",
"form_factor": "wide"
}
]
}

View File

@ -32,7 +32,7 @@ function generatePrecacheManifest () {
const staticDir = join(__dirname, '../public')
const staticFiles = walkSync(staticDir)
const staticMatch = f => [/\.(gif|jpe?g|ico|png|ttf|woff|woff2|webmanifest)$/].some(m => m.test(f))
const staticMatch = f => [/\.(gif|jpe?g|ico|png|ttf|woff|woff2)$/].some(m => m.test(f))
staticFiles.filter(staticMatch).forEach(file => {
const stats = statSync(file)
addToManifest(file, file.slice(staticDir.length), stats.size)