Skip to content

Commit

Permalink
Add ability to override client assets : logo - favicon - PWA icons - …
Browse files Browse the repository at this point in the history
…PWA manifest name and description (#2897)

* Add client-overrides storage to config

* Add static-serve for client overrides

* Move backgroun-image logo from bundle to css tag for runtime content hash

* Add dynamic JSON manifest

* Add content hash for manifest, favicon and logo

Co-authored-by: kimsible <[email protected]>
  • Loading branch information
kimsible and kimsible authored Jul 10, 2020
1 parent 27647da commit caf2aaf
Show file tree
Hide file tree
Showing 15 changed files with 114 additions and 9 deletions.
2 changes: 1 addition & 1 deletion client/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@

.icon.icon-logo {
display: inline-block;
background: url('../assets/images/logo.svg') no-repeat;
background-repeat: no-repeat;
width: 23px;
height: 24px;
margin-right: .5rem;
Expand Down
11 changes: 9 additions & 2 deletions client/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,16 @@
<meta name="theme-color" content="#fff" />
<meta property="og:platform" content="PeerTube" />
<!-- Web Manifest file -->
<link rel="manifest" href="/manifest.webmanifest">
<link rel="manifest" href="/manifest.webmanifest?[manifestContentHash]">

<link rel="icon" type="image/png" href="/client/assets/images/favicon.png" />
<link rel="icon" type="image/png" href="/client/assets/images/favicon.png?[faviconContentHash]" />

<!-- logo background-image -->
<style type="text/css">
.icon-logo {
background-image: url(/client/assets/images/logo.svg?[logoContentHash]);
}
</style>

<!-- base url -->
<base href="/">
Expand Down
5 changes: 5 additions & 0 deletions config/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ storage:
captions: 'storage/captions/'
cache: 'storage/cache/'
plugins: 'storage/plugins/'
# Overridable client files : logo.svg, favicon.png and icons/*.png (PWA) in client/dist/assets/images
# Could contain for example assets/images/favicon.png
# If the file exists, peertube will serve it
# If not, peertube will fallback to the default fil
client_overrides: 'storage/client-overrides/'

log:
level: 'info' # debug/info/warning/error
Expand Down
5 changes: 5 additions & 0 deletions config/production.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ storage:
captions: '/var/www/peertube/storage/captions/'
cache: '/var/www/peertube/storage/cache/'
plugins: '/var/www/peertube/storage/plugins/'
# Overridable client files : logo.svg, favicon.png and icons/*.png (PWA) in client/dist/assets/images
# Could contain for example assets/images/favicon.png
# If the file exists, peertube will serve it
# If not, peertube will fallback to the default fil
client_overrides: '/var/www/peertube/storage/client-overrides/'

log:
level: 'info' # debug/info/warning/error
Expand Down
1 change: 1 addition & 0 deletions config/test-1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ storage:
captions: 'test1/captions/'
cache: 'test1/cache/'
plugins: 'test1/plugins/'
client_overrides: 'test1/client-overrides/'

admin:
email: '[email protected]'
Expand Down
1 change: 1 addition & 0 deletions config/test-2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ storage:
captions: 'test2/captions/'
cache: 'test2/cache/'
plugins: 'test2/plugins/'
client_overrides: 'test2/client-overrides/'

admin:
email: '[email protected]'
Expand Down
1 change: 1 addition & 0 deletions config/test-3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ storage:
captions: 'test3/captions/'
cache: 'test3/cache/'
plugins: 'test3/plugins/'
client_overrides: 'test3/client-overrides/'

admin:
email: '[email protected]'
Expand Down
1 change: 1 addition & 0 deletions config/test-4.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ storage:
captions: 'test4/captions/'
cache: 'test4/cache/'
plugins: 'test4/plugins/'
client_overrides: 'test4/client-overrides/'

admin:
email: '[email protected]'
Expand Down
1 change: 1 addition & 0 deletions config/test-5.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ storage:
captions: 'test5/captions/'
cache: 'test5/cache/'
plugins: 'test5/plugins/'
client_overrides: 'test5/client-overrides/'

admin:
email: '[email protected]'
Expand Down
1 change: 1 addition & 0 deletions config/test-6.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ storage:
captions: 'test6/captions/'
cache: 'test6/cache/'
plugins: 'test6/plugins/'
client_overrides: 'test6/client-overrides/'

admin:
email: '[email protected]'
Expand Down
52 changes: 49 additions & 3 deletions server/controllers/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { constants, promises as fs } from 'fs'
import * as express from 'express'
import { join } from 'path'
import { root } from '../helpers/core-utils'
Expand Down Expand Up @@ -39,20 +40,40 @@ clientsRouter.use(
)

// Static HTML/CSS/JS client files

const staticClientFiles = [
'manifest.webmanifest',
'ngsw-worker.js',
'ngsw.json'
]

for (const staticClientFile of staticClientFiles) {
const path = join(root(), 'client', 'dist', staticClientFile)

clientsRouter.get('/' + staticClientFile, (req: express.Request, res: express.Response) => {
clientsRouter.get(`/${staticClientFile}`, (req: express.Request, res: express.Response) => {
res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER })
})
}

// Dynamic PWA manifest
clientsRouter.get('/manifest.webmanifest', asyncMiddleware(generateManifest))

// Static client overrides
const staticClientOverrides = [
'assets/images/logo.svg',
'assets/images/favicon.png',
'assets/images/icons/icon-36x36.png',
'assets/images/icons/icon-48x48.png',
'assets/images/icons/icon-72x72.png',
'assets/images/icons/icon-96x96.png',
'assets/images/icons/icon-144x144.png',
'assets/images/icons/icon-192x192.png',
'assets/images/icons/icon-512x512.png'
]

for (const staticClientOverride of staticClientOverrides) {
const overridePhysicalPath = join(CONFIG.STORAGE.CLIENT_OVERRIDES_DIR, staticClientOverride)
clientsRouter.use(`/client/${staticClientOverride}`, asyncMiddleware(serveClientOverride(overridePhysicalPath)))
}

clientsRouter.use('/client/locales/:locale/:file.json', serveServerTranslations)
clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE.CLIENT }))

Expand Down Expand Up @@ -130,3 +151,28 @@ function sendHTML (html: string, res: express.Response) {

return res.send(html)
}

async function generateManifest (req: express.Request, res: express.Response) {
const manifestPhysicalPath = join(root(), 'client', 'dist', 'manifest.webmanifest')
const manifestJson = await fs.readFile(manifestPhysicalPath, 'utf8')
const manifest = JSON.parse(manifestJson)

manifest.name = CONFIG.INSTANCE.NAME
manifest.short_name = CONFIG.INSTANCE.NAME
manifest.description = CONFIG.INSTANCE.SHORT_DESCRIPTION

res.json(manifest)
}

function serveClientOverride (path: string) {
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
try {
await fs.access(path, constants.F_OK)
// Serve override client
res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER })
} catch {
// Serve dist client
next()
}
}
}
3 changes: 2 additions & 1 deletion server/initializers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ const CONFIG = {
CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
CACHE_DIR: buildPath(config.get<string>('storage.cache')),
PLUGINS_DIR: buildPath(config.get<string>('storage.plugins'))
PLUGINS_DIR: buildPath(config.get<string>('storage.plugins')),
CLIENT_OVERRIDES_DIR: buildPath(config.get<string>('storage.client_overrides'))
},
WEBSERVER: {
SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
Expand Down
17 changes: 16 additions & 1 deletion server/initializers/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { join } from 'path'
import { randomBytes } from 'crypto'
import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models'
import { ActivityPubActorType } from '../../shared/models/activitypub'
import { FollowState } from '../../shared/models/actors'
Expand Down Expand Up @@ -710,6 +711,14 @@ registerConfigChangedHandler(() => {

// ---------------------------------------------------------------------------

const FILES_CONTENT_HASH = {
MANIFEST: generateContentHash(),
FAVICON: generateContentHash(),
LOGO: generateContentHash()
}

// ---------------------------------------------------------------------------

export {
WEBSERVER,
API_VERSION,
Expand Down Expand Up @@ -792,8 +801,10 @@ export {
VIDEO_PLAYLIST_PRIVACIES,
PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME,
ASSETS_PATH,
FILES_CONTENT_HASH,
loadLanguages,
buildLanguages
buildLanguages,
generateContentHash
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -895,3 +906,7 @@ function buildLanguages () {

return languages
}

function generateContentHash () {
return randomBytes(20).toString('hex')
}
17 changes: 16 additions & 1 deletion server/lib/client-html.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as express from 'express'
import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER } from '../initializers/constants'
import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER, FILES_CONTENT_HASH } from '../initializers/constants'
import { join } from 'path'
import { escapeHTML, sha256 } from '../helpers/core-utils'
import { VideoModel } from '../models/video/video'
Expand Down Expand Up @@ -101,6 +101,9 @@ export class ClientHtml {
let html = buffer.toString()

if (paramLang) html = ClientHtml.addHtmlLang(html, paramLang)
html = ClientHtml.addManifestContentHash(html)
html = ClientHtml.addFaviconContentHash(html)
html = ClientHtml.addLogoContentHash(html)
html = ClientHtml.addCustomCSS(html)
html = await ClientHtml.addAsyncPluginCSS(html)

Expand Down Expand Up @@ -136,6 +139,18 @@ export class ClientHtml {
return htmlStringPage.replace('<html>', `<html lang="${paramLang}">`)
}

private static addManifestContentHash (htmlStringPage: string) {
return htmlStringPage.replace('[manifestContentHash]', FILES_CONTENT_HASH.MANIFEST)
}

private static addFaviconContentHash(htmlStringPage: string) {
return htmlStringPage.replace('[faviconContentHash]', FILES_CONTENT_HASH.FAVICON)
}

private static addLogoContentHash(htmlStringPage: string) {
return htmlStringPage.replace('[logoContentHash]', FILES_CONTENT_HASH.LOGO)
}

private static addTitleTag (htmlStringPage: string, title?: string) {
let text = title || CONFIG.INSTANCE.NAME
if (title) text += ` - ${CONFIG.INSTANCE.NAME}`
Expand Down
5 changes: 5 additions & 0 deletions support/docker/production/config/production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ storage:
captions: '../data/captions/'
cache: '../data/cache/'
plugins: '../data/plugins/'
# Overridable client files : logo.svg, favicon.png and icons/*.png (PWA) in client/dist/assets/images
# Could contain for example assets/images/favicon.png
# If the file exists, peertube will serve it
# If not, peertube will fallback to the default fil
client_overrides: '../data/client-overrides/'

log:
level: 'info' # debug/info/warning/error
Expand Down

0 comments on commit caf2aaf

Please sign in to comment.