aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorKim <1877318+kimsible@users.noreply.github.com>2020-07-10 10:20:11 +0200
committerGitHub <noreply@github.com>2020-07-10 10:20:11 +0200
commitcaf2aaf4f9d38ad441a5562c3b8720f8779d6f78 (patch)
tree2a93cbfd2aee6089fd334f28dcb47c18a368a6fd /server
parent27647da17fe53ff24ed27ef8618bc244c0be6b26 (diff)
downloadPeerTube-caf2aaf4f9d38ad441a5562c3b8720f8779d6f78.tar.gz
PeerTube-caf2aaf4f9d38ad441a5562c3b8720f8779d6f78.tar.zst
PeerTube-caf2aaf4f9d38ad441a5562c3b8720f8779d6f78.zip
Add ability to override client assets : logo - favicon - PWA icons - 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 <kimsible@users.noreply.github.com>
Diffstat (limited to 'server')
-rw-r--r--server/controllers/client.ts52
-rw-r--r--server/initializers/config.ts3
-rw-r--r--server/initializers/constants.ts17
-rw-r--r--server/lib/client-html.ts17
4 files changed, 83 insertions, 6 deletions
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 65b5a053c..88f51907b 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -1,3 +1,4 @@
1import { constants, promises as fs } from 'fs'
1import * as express from 'express' 2import * as express from 'express'
2import { join } from 'path' 3import { join } from 'path'
3import { root } from '../helpers/core-utils' 4import { root } from '../helpers/core-utils'
@@ -39,20 +40,40 @@ clientsRouter.use(
39) 40)
40 41
41// Static HTML/CSS/JS client files 42// Static HTML/CSS/JS client files
42
43const staticClientFiles = [ 43const staticClientFiles = [
44 'manifest.webmanifest',
45 'ngsw-worker.js', 44 'ngsw-worker.js',
46 'ngsw.json' 45 'ngsw.json'
47] 46]
47
48for (const staticClientFile of staticClientFiles) { 48for (const staticClientFile of staticClientFiles) {
49 const path = join(root(), 'client', 'dist', staticClientFile) 49 const path = join(root(), 'client', 'dist', staticClientFile)
50 50
51 clientsRouter.get('/' + staticClientFile, (req: express.Request, res: express.Response) => { 51 clientsRouter.get(`/${staticClientFile}`, (req: express.Request, res: express.Response) => {
52 res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER }) 52 res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER })
53 }) 53 })
54} 54}
55 55
56// Dynamic PWA manifest
57clientsRouter.get('/manifest.webmanifest', asyncMiddleware(generateManifest))
58
59// Static client overrides
60const staticClientOverrides = [
61 'assets/images/logo.svg',
62 'assets/images/favicon.png',
63 'assets/images/icons/icon-36x36.png',
64 'assets/images/icons/icon-48x48.png',
65 'assets/images/icons/icon-72x72.png',
66 'assets/images/icons/icon-96x96.png',
67 'assets/images/icons/icon-144x144.png',
68 'assets/images/icons/icon-192x192.png',
69 'assets/images/icons/icon-512x512.png'
70]
71
72for (const staticClientOverride of staticClientOverrides) {
73 const overridePhysicalPath = join(CONFIG.STORAGE.CLIENT_OVERRIDES_DIR, staticClientOverride)
74 clientsRouter.use(`/client/${staticClientOverride}`, asyncMiddleware(serveClientOverride(overridePhysicalPath)))
75}
76
56clientsRouter.use('/client/locales/:locale/:file.json', serveServerTranslations) 77clientsRouter.use('/client/locales/:locale/:file.json', serveServerTranslations)
57clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE.CLIENT })) 78clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE.CLIENT }))
58 79
@@ -130,3 +151,28 @@ function sendHTML (html: string, res: express.Response) {
130 151
131 return res.send(html) 152 return res.send(html)
132} 153}
154
155async function generateManifest (req: express.Request, res: express.Response) {
156 const manifestPhysicalPath = join(root(), 'client', 'dist', 'manifest.webmanifest')
157 const manifestJson = await fs.readFile(manifestPhysicalPath, 'utf8')
158 const manifest = JSON.parse(manifestJson)
159
160 manifest.name = CONFIG.INSTANCE.NAME
161 manifest.short_name = CONFIG.INSTANCE.NAME
162 manifest.description = CONFIG.INSTANCE.SHORT_DESCRIPTION
163
164 res.json(manifest)
165}
166
167function serveClientOverride (path: string) {
168 return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
169 try {
170 await fs.access(path, constants.F_OK)
171 // Serve override client
172 res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER })
173 } catch {
174 // Serve dist client
175 next()
176 }
177 }
178}
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index 48e2cbc1a..32bd3bbe2 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -68,7 +68,8 @@ const CONFIG = {
68 CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')), 68 CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
69 TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')), 69 TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')),
70 CACHE_DIR: buildPath(config.get<string>('storage.cache')), 70 CACHE_DIR: buildPath(config.get<string>('storage.cache')),
71 PLUGINS_DIR: buildPath(config.get<string>('storage.plugins')) 71 PLUGINS_DIR: buildPath(config.get<string>('storage.plugins')),
72 CLIENT_OVERRIDES_DIR: buildPath(config.get<string>('storage.client_overrides'))
72 }, 73 },
73 WEBSERVER: { 74 WEBSERVER: {
74 SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http', 75 SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http',
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 9a262fd4b..e730e3c84 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -1,4 +1,5 @@
1import { join } from 'path' 1import { join } from 'path'
2import { randomBytes } from 'crypto'
2import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models' 3import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models'
3import { ActivityPubActorType } from '../../shared/models/activitypub' 4import { ActivityPubActorType } from '../../shared/models/activitypub'
4import { FollowState } from '../../shared/models/actors' 5import { FollowState } from '../../shared/models/actors'
@@ -710,6 +711,14 @@ registerConfigChangedHandler(() => {
710 711
711// --------------------------------------------------------------------------- 712// ---------------------------------------------------------------------------
712 713
714const FILES_CONTENT_HASH = {
715 MANIFEST: generateContentHash(),
716 FAVICON: generateContentHash(),
717 LOGO: generateContentHash()
718}
719
720// ---------------------------------------------------------------------------
721
713export { 722export {
714 WEBSERVER, 723 WEBSERVER,
715 API_VERSION, 724 API_VERSION,
@@ -792,8 +801,10 @@ export {
792 VIDEO_PLAYLIST_PRIVACIES, 801 VIDEO_PLAYLIST_PRIVACIES,
793 PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME, 802 PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME,
794 ASSETS_PATH, 803 ASSETS_PATH,
804 FILES_CONTENT_HASH,
795 loadLanguages, 805 loadLanguages,
796 buildLanguages 806 buildLanguages,
807 generateContentHash
797} 808}
798 809
799// --------------------------------------------------------------------------- 810// ---------------------------------------------------------------------------
@@ -895,3 +906,7 @@ function buildLanguages () {
895 906
896 return languages 907 return languages
897} 908}
909
910function generateContentHash () {
911 return randomBytes(20).toString('hex')
912}
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 3e6da2898..5996f3c70 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -1,6 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' 2import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
3import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER } from '../initializers/constants' 3import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER, FILES_CONTENT_HASH } from '../initializers/constants'
4import { join } from 'path' 4import { join } from 'path'
5import { escapeHTML, sha256 } from '../helpers/core-utils' 5import { escapeHTML, sha256 } from '../helpers/core-utils'
6import { VideoModel } from '../models/video/video' 6import { VideoModel } from '../models/video/video'
@@ -101,6 +101,9 @@ export class ClientHtml {
101 let html = buffer.toString() 101 let html = buffer.toString()
102 102
103 if (paramLang) html = ClientHtml.addHtmlLang(html, paramLang) 103 if (paramLang) html = ClientHtml.addHtmlLang(html, paramLang)
104 html = ClientHtml.addManifestContentHash(html)
105 html = ClientHtml.addFaviconContentHash(html)
106 html = ClientHtml.addLogoContentHash(html)
104 html = ClientHtml.addCustomCSS(html) 107 html = ClientHtml.addCustomCSS(html)
105 html = await ClientHtml.addAsyncPluginCSS(html) 108 html = await ClientHtml.addAsyncPluginCSS(html)
106 109
@@ -136,6 +139,18 @@ export class ClientHtml {
136 return htmlStringPage.replace('<html>', `<html lang="${paramLang}">`) 139 return htmlStringPage.replace('<html>', `<html lang="${paramLang}">`)
137 } 140 }
138 141
142 private static addManifestContentHash (htmlStringPage: string) {
143 return htmlStringPage.replace('[manifestContentHash]', FILES_CONTENT_HASH.MANIFEST)
144 }
145
146 private static addFaviconContentHash(htmlStringPage: string) {
147 return htmlStringPage.replace('[faviconContentHash]', FILES_CONTENT_HASH.FAVICON)
148 }
149
150 private static addLogoContentHash(htmlStringPage: string) {
151 return htmlStringPage.replace('[logoContentHash]', FILES_CONTENT_HASH.LOGO)
152 }
153
139 private static addTitleTag (htmlStringPage: string, title?: string) { 154 private static addTitleTag (htmlStringPage: string, title?: string) {
140 let text = title || CONFIG.INSTANCE.NAME 155 let text = title || CONFIG.INSTANCE.NAME
141 if (title) text += ` - ${CONFIG.INSTANCE.NAME}` 156 if (title) text += ` - ${CONFIG.INSTANCE.NAME}`