diff options
author | Kim <1877318+kimsible@users.noreply.github.com> | 2020-07-10 10:20:11 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-10 10:20:11 +0200 |
commit | caf2aaf4f9d38ad441a5562c3b8720f8779d6f78 (patch) | |
tree | 2a93cbfd2aee6089fd334f28dcb47c18a368a6fd /server | |
parent | 27647da17fe53ff24ed27ef8618bc244c0be6b26 (diff) | |
download | PeerTube-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.ts | 52 | ||||
-rw-r--r-- | server/initializers/config.ts | 3 | ||||
-rw-r--r-- | server/initializers/constants.ts | 17 | ||||
-rw-r--r-- | server/lib/client-html.ts | 17 |
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 @@ | |||
1 | import { constants, promises as fs } from 'fs' | ||
1 | import * as express from 'express' | 2 | import * as express from 'express' |
2 | import { join } from 'path' | 3 | import { join } from 'path' |
3 | import { root } from '../helpers/core-utils' | 4 | import { 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 | |||
43 | const staticClientFiles = [ | 43 | const staticClientFiles = [ |
44 | 'manifest.webmanifest', | ||
45 | 'ngsw-worker.js', | 44 | 'ngsw-worker.js', |
46 | 'ngsw.json' | 45 | 'ngsw.json' |
47 | ] | 46 | ] |
47 | |||
48 | for (const staticClientFile of staticClientFiles) { | 48 | for (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 | ||
57 | clientsRouter.get('/manifest.webmanifest', asyncMiddleware(generateManifest)) | ||
58 | |||
59 | // Static client overrides | ||
60 | const 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 | |||
72 | for (const staticClientOverride of staticClientOverrides) { | ||
73 | const overridePhysicalPath = join(CONFIG.STORAGE.CLIENT_OVERRIDES_DIR, staticClientOverride) | ||
74 | clientsRouter.use(`/client/${staticClientOverride}`, asyncMiddleware(serveClientOverride(overridePhysicalPath))) | ||
75 | } | ||
76 | |||
56 | clientsRouter.use('/client/locales/:locale/:file.json', serveServerTranslations) | 77 | clientsRouter.use('/client/locales/:locale/:file.json', serveServerTranslations) |
57 | clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE.CLIENT })) | 78 | clientsRouter.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 | |||
155 | async 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 | |||
167 | function 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 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { randomBytes } from 'crypto' | ||
2 | import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models' | 3 | import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models' |
3 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 4 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
4 | import { FollowState } from '../../shared/models/actors' | 5 | import { FollowState } from '../../shared/models/actors' |
@@ -710,6 +711,14 @@ registerConfigChangedHandler(() => { | |||
710 | 711 | ||
711 | // --------------------------------------------------------------------------- | 712 | // --------------------------------------------------------------------------- |
712 | 713 | ||
714 | const FILES_CONTENT_HASH = { | ||
715 | MANIFEST: generateContentHash(), | ||
716 | FAVICON: generateContentHash(), | ||
717 | LOGO: generateContentHash() | ||
718 | } | ||
719 | |||
720 | // --------------------------------------------------------------------------- | ||
721 | |||
713 | export { | 722 | export { |
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 | |||
910 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' | 2 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' |
3 | import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER } from '../initializers/constants' | 3 | import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER, FILES_CONTENT_HASH } from '../initializers/constants' |
4 | import { join } from 'path' | 4 | import { join } from 'path' |
5 | import { escapeHTML, sha256 } from '../helpers/core-utils' | 5 | import { escapeHTML, sha256 } from '../helpers/core-utils' |
6 | import { VideoModel } from '../models/video/video' | 6 | import { 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}` |