aboutsummaryrefslogtreecommitdiffhomepage
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
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>
-rw-r--r--client/src/app/app.component.scss2
-rw-r--r--client/src/index.html11
-rw-r--r--config/default.yaml5
-rw-r--r--config/production.yaml.example5
-rw-r--r--config/test-1.yaml1
-rw-r--r--config/test-2.yaml1
-rw-r--r--config/test-3.yaml1
-rw-r--r--config/test-4.yaml1
-rw-r--r--config/test-5.yaml1
-rw-r--r--config/test-6.yaml1
-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
-rw-r--r--support/docker/production/config/production.yaml5
15 files changed, 114 insertions, 9 deletions
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss
index d121ebad2..38ec11b5b 100644
--- a/client/src/app/app.component.scss
+++ b/client/src/app/app.component.scss
@@ -62,7 +62,7 @@
62 62
63 .icon.icon-logo { 63 .icon.icon-logo {
64 display: inline-block; 64 display: inline-block;
65 background: url('../assets/images/logo.svg') no-repeat; 65 background-repeat: no-repeat;
66 width: 23px; 66 width: 23px;
67 height: 24px; 67 height: 24px;
68 margin-right: .5rem; 68 margin-right: .5rem;
diff --git a/client/src/index.html b/client/src/index.html
index 52ae000bb..e5d1569aa 100644
--- a/client/src/index.html
+++ b/client/src/index.html
@@ -7,9 +7,16 @@
7 <meta name="theme-color" content="#fff" /> 7 <meta name="theme-color" content="#fff" />
8 <meta property="og:platform" content="PeerTube" /> 8 <meta property="og:platform" content="PeerTube" />
9 <!-- Web Manifest file --> 9 <!-- Web Manifest file -->
10 <link rel="manifest" href="/manifest.webmanifest"> 10 <link rel="manifest" href="/manifest.webmanifest?[manifestContentHash]">
11 11
12 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" /> 12 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png?[faviconContentHash]" />
13
14 <!-- logo background-image -->
15 <style type="text/css">
16 .icon-logo {
17 background-image: url(/client/assets/images/logo.svg?[logoContentHash]);
18 }
19 </style>
13 20
14 <!-- base url --> 21 <!-- base url -->
15 <base href="/"> 22 <base href="/">
diff --git a/config/default.yaml b/config/default.yaml
index a3df1bd45..d6f7f7afe 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -85,6 +85,11 @@ storage:
85 captions: 'storage/captions/' 85 captions: 'storage/captions/'
86 cache: 'storage/cache/' 86 cache: 'storage/cache/'
87 plugins: 'storage/plugins/' 87 plugins: 'storage/plugins/'
88 # Overridable client files : logo.svg, favicon.png and icons/*.png (PWA) in client/dist/assets/images
89 # Could contain for example assets/images/favicon.png
90 # If the file exists, peertube will serve it
91 # If not, peertube will fallback to the default fil
92 client_overrides: 'storage/client-overrides/'
88 93
89log: 94log:
90 level: 'info' # debug/info/warning/error 95 level: 'info' # debug/info/warning/error
diff --git a/config/production.yaml.example b/config/production.yaml.example
index a494bdb03..f57861eca 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -86,6 +86,11 @@ storage:
86 captions: '/var/www/peertube/storage/captions/' 86 captions: '/var/www/peertube/storage/captions/'
87 cache: '/var/www/peertube/storage/cache/' 87 cache: '/var/www/peertube/storage/cache/'
88 plugins: '/var/www/peertube/storage/plugins/' 88 plugins: '/var/www/peertube/storage/plugins/'
89 # Overridable client files : logo.svg, favicon.png and icons/*.png (PWA) in client/dist/assets/images
90 # Could contain for example assets/images/favicon.png
91 # If the file exists, peertube will serve it
92 # If not, peertube will fallback to the default fil
93 client_overrides: '/var/www/peertube/storage/client-overrides/'
89 94
90log: 95log:
91 level: 'info' # debug/info/warning/error 96 level: 'info' # debug/info/warning/error
diff --git a/config/test-1.yaml b/config/test-1.yaml
index 7b25f5cf3..2ef9e6c7c 100644
--- a/config/test-1.yaml
+++ b/config/test-1.yaml
@@ -22,6 +22,7 @@ storage:
22 captions: 'test1/captions/' 22 captions: 'test1/captions/'
23 cache: 'test1/cache/' 23 cache: 'test1/cache/'
24 plugins: 'test1/plugins/' 24 plugins: 'test1/plugins/'
25 client_overrides: 'test1/client-overrides/'
25 26
26admin: 27admin:
27 email: 'admin1@example.com' 28 email: 'admin1@example.com'
diff --git a/config/test-2.yaml b/config/test-2.yaml
index 82d4aa35f..b559769c3 100644
--- a/config/test-2.yaml
+++ b/config/test-2.yaml
@@ -22,6 +22,7 @@ storage:
22 captions: 'test2/captions/' 22 captions: 'test2/captions/'
23 cache: 'test2/cache/' 23 cache: 'test2/cache/'
24 plugins: 'test2/plugins/' 24 plugins: 'test2/plugins/'
25 client_overrides: 'test2/client-overrides/'
25 26
26admin: 27admin:
27 email: 'admin2@example.com' 28 email: 'admin2@example.com'
diff --git a/config/test-3.yaml b/config/test-3.yaml
index d2734f469..9a7a944e9 100644
--- a/config/test-3.yaml
+++ b/config/test-3.yaml
@@ -22,6 +22,7 @@ storage:
22 captions: 'test3/captions/' 22 captions: 'test3/captions/'
23 cache: 'test3/cache/' 23 cache: 'test3/cache/'
24 plugins: 'test3/plugins/' 24 plugins: 'test3/plugins/'
25 client_overrides: 'test3/client-overrides/'
25 26
26admin: 27admin:
27 email: 'admin3@example.com' 28 email: 'admin3@example.com'
diff --git a/config/test-4.yaml b/config/test-4.yaml
index 9ec45b024..1e4bee974 100644
--- a/config/test-4.yaml
+++ b/config/test-4.yaml
@@ -22,6 +22,7 @@ storage:
22 captions: 'test4/captions/' 22 captions: 'test4/captions/'
23 cache: 'test4/cache/' 23 cache: 'test4/cache/'
24 plugins: 'test4/plugins/' 24 plugins: 'test4/plugins/'
25 client_overrides: 'test4/client-overrides/'
25 26
26admin: 27admin:
27 email: 'admin4@example.com' 28 email: 'admin4@example.com'
diff --git a/config/test-5.yaml b/config/test-5.yaml
index 92cc113b9..9725e84f4 100644
--- a/config/test-5.yaml
+++ b/config/test-5.yaml
@@ -22,6 +22,7 @@ storage:
22 captions: 'test5/captions/' 22 captions: 'test5/captions/'
23 cache: 'test5/cache/' 23 cache: 'test5/cache/'
24 plugins: 'test5/plugins/' 24 plugins: 'test5/plugins/'
25 client_overrides: 'test5/client-overrides/'
25 26
26admin: 27admin:
27 email: 'admin5@example.com' 28 email: 'admin5@example.com'
diff --git a/config/test-6.yaml b/config/test-6.yaml
index 205d99797..a04c8a6a9 100644
--- a/config/test-6.yaml
+++ b/config/test-6.yaml
@@ -22,6 +22,7 @@ storage:
22 captions: 'test6/captions/' 22 captions: 'test6/captions/'
23 cache: 'test6/cache/' 23 cache: 'test6/cache/'
24 plugins: 'test6/plugins/' 24 plugins: 'test6/plugins/'
25 client_overrides: 'test6/client-overrides/'
25 26
26admin: 27admin:
27 email: 'admin6@example.com' 28 email: 'admin6@example.com'
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}`
diff --git a/support/docker/production/config/production.yaml b/support/docker/production/config/production.yaml
index 4eeca4110..a32cf1a89 100644
--- a/support/docker/production/config/production.yaml
+++ b/support/docker/production/config/production.yaml
@@ -54,6 +54,11 @@ storage:
54 captions: '../data/captions/' 54 captions: '../data/captions/'
55 cache: '../data/cache/' 55 cache: '../data/cache/'
56 plugins: '../data/plugins/' 56 plugins: '../data/plugins/'
57 # Overridable client files : logo.svg, favicon.png and icons/*.png (PWA) in client/dist/assets/images
58 # Could contain for example assets/images/favicon.png
59 # If the file exists, peertube will serve it
60 # If not, peertube will fallback to the default fil
61 client_overrides: '../data/client-overrides/'
57 62
58log: 63log:
59 level: 'info' # debug/info/warning/error 64 level: 'info' # debug/info/warning/error