Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to override client assets : logo - favicon - PWA icons - PWA manifest name and description #2897

Merged
merged 5 commits into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -709,6 +710,14 @@ registerConfigChangedHandler(() => {

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

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

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

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

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -894,3 +905,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