Add plugin static files cache
authorChocobozzz <me@florianbigard.com>
Tue, 23 Jul 2019 07:48:48 +0000 (09:48 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Wed, 24 Jul 2019 08:58:16 +0000 (10:58 +0200)
server/controllers/api/videos/blacklist.ts
server/controllers/plugins.ts
server/helpers/utils.ts
server/lib/client-html.ts
server/lib/plugins/plugin-manager.ts
shared/models/plugins/server-hook.model.ts

index 27dcfb76112010428f05ac70bd249f5096e849b7..0ec518e0d629505908e06572483ce6ccd299cda1 100644 (file)
@@ -100,13 +100,13 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
   return res.type('json').status(204).end()
 }
 
-async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function listBlacklist (req: express.Request, res: express.Response) {
   const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type)
 
-  return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total))
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) {
   const videoBlacklist = res.locals.videoBlacklist
   const video = res.locals.video
 
index f255d13e840ec3b279ca7b06583534cf853b3186..f5285ba3ae3ae9967d533f73332f547e1c535327 100644 (file)
@@ -5,6 +5,12 @@ import { RegisteredPlugin } from '../lib/plugins/plugin-manager'
 import { servePluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
 import { serveThemeCSSValidator } from '../middlewares/validators/themes'
 import { PluginType } from '../../shared/models/plugins/plugin.type'
+import { isTestInstance } from '../helpers/core-utils'
+
+const sendFileOptions = {
+  maxAge: '30 days',
+  immutable: !isTestInstance()
+}
 
 const pluginsRouter = express.Router()
 
@@ -46,7 +52,12 @@ export {
 // ---------------------------------------------------------------------------
 
 function servePluginGlobalCSS (req: express.Request, res: express.Response) {
-  return res.sendFile(PLUGIN_GLOBAL_CSS_PATH)
+  // Only cache requests that have a ?hash=... query param
+  const globalCSSOptions = req.query.hash
+    ? sendFileOptions
+    : {}
+
+  return res.sendFile(PLUGIN_GLOBAL_CSS_PATH, globalCSSOptions)
 }
 
 function servePluginStaticDirectory (req: express.Request, res: express.Response) {
@@ -61,7 +72,7 @@ function servePluginStaticDirectory (req: express.Request, res: express.Response
   }
 
   const filepath = file.join('/')
-  return res.sendFile(join(plugin.path, staticPath, filepath))
+  return res.sendFile(join(plugin.path, staticPath, filepath), sendFileOptions)
 }
 
 function servePluginClientScripts (req: express.Request, res: express.Response) {
@@ -73,7 +84,7 @@ function servePluginClientScripts (req: express.Request, res: express.Response)
     return res.sendStatus(404)
   }
 
-  return res.sendFile(join(plugin.path, staticEndpoint))
+  return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
 }
 
 function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
@@ -84,5 +95,5 @@ function serveThemeCSSDirectory (req: express.Request, res: express.Response) {
     return res.sendStatus(404)
   }
 
-  return res.sendFile(join(plugin.path, staticEndpoint))
+  return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions)
 }
index 94ceb15e0cbe560b5817431c63b6f9796569e99a..1464b147728e0cefbac7c7c4b6c97b24d7676ca3 100644 (file)
@@ -19,18 +19,14 @@ async function generateRandomString (size: number) {
   return raw.toString('hex')
 }
 
-interface FormattableToJSON { toFormattedJSON (args?: any) }
-function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number, formattedArg?: any) {
-  const formattedObjects: U[] = []
-
-  objects.forEach(object => {
-    formattedObjects.push(object.toFormattedJSON(formattedArg))
-  })
+interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V }
+function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) {
+  const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg))
 
   return {
     total: objectsTotal,
     data: formattedObjects
-  } as ResultList<U>
+  } as ResultList<V>
 }
 
 const getServerActor = memoizee(async function () {
index ccc963514ac46acde0bda562ac93ff06d4895460..1e78972205c5e65d588e4e7ad315a91af1cc49ea 100644 (file)
@@ -1,8 +1,8 @@
 import * as express from 'express'
 import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
-import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, WEBSERVER } from '../initializers/constants'
+import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, PLUGIN_GLOBAL_CSS_PATH, WEBSERVER } from '../initializers/constants'
 import { join } from 'path'
-import { escapeHTML } from '../helpers/core-utils'
+import { escapeHTML, sha256 } from '../helpers/core-utils'
 import { VideoModel } from '../models/video/video'
 import * as validator from 'validator'
 import { VideoPrivacy } from '../../shared/models/videos'
@@ -92,7 +92,7 @@ export class ClientHtml {
     let html = buffer.toString()
 
     html = ClientHtml.addCustomCSS(html)
-    html = ClientHtml.addPluginCSS(html)
+    html = await ClientHtml.addAsyncPluginCSS(html)
 
     ClientHtml.htmlCache[ path ] = html
 
@@ -144,8 +144,12 @@ export class ClientHtml {
     return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag)
   }
 
-  private static addPluginCSS (htmlStringPage: string) {
-    const linkTag = `<link rel="stylesheet" href="/plugins/global.css" />`
+  private static async addAsyncPluginCSS (htmlStringPage: string) {
+    const globalCSSContent = await readFile(PLUGIN_GLOBAL_CSS_PATH)
+    if (!globalCSSContent) return htmlStringPage
+
+    const fileHash = sha256(globalCSSContent)
+    const linkTag = `<link rel="stylesheet" href="/plugins/global.css?hash=${fileHash}" />`
 
     return htmlStringPage.replace('</head>', linkTag + '</head>')
   }
index cfe63e50d1a681dc1d94028382572a88a0b15cdd..c0b49c7c709c55be68d1ea6334de8261092c3079 100644 (file)
@@ -17,6 +17,7 @@ import { ServerHook, ServerHookName, serverHookObject } from '../../../shared/mo
 import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks'
 import { RegisterOptions } from '../../typings/plugins/register-options.model'
 import { PluginLibrary } from '../../typings/plugins'
+import { ClientHtml } from '../client-html'
 
 export interface RegisteredPlugin {
   npmName: string
@@ -323,6 +324,8 @@ export class PluginManager implements ServerHook {
     for (const cssPath of cssRelativePaths) {
       await this.concatFiles(join(pluginPath, cssPath), PLUGIN_GLOBAL_CSS_PATH)
     }
+
+    ClientHtml.invalidCache()
   }
 
   private concatFiles (input: string, output: string) {
index f53e0ce597d5cbd9010943d6054b2319b910cade..fc4c511603736ff65a3ab832c22c4776a69f435c 100644 (file)
@@ -1,3 +1,5 @@
+// {hookType}:{api?}.{location}.{subLocation?}.{actionType}.{target}
+
 export const serverFilterHookObject = {
   'filter:api.videos.list.params': true,
   'filter:api.videos.list.result': true,