diff options
Diffstat (limited to 'server/controllers/client.ts')
-rw-r--r-- | server/controllers/client.ts | 236 |
1 files changed, 0 insertions, 236 deletions
diff --git a/server/controllers/client.ts b/server/controllers/client.ts deleted file mode 100644 index 2d0c49904..000000000 --- a/server/controllers/client.ts +++ /dev/null | |||
@@ -1,236 +0,0 @@ | |||
1 | import express from 'express' | ||
2 | import { constants, promises as fs } from 'fs' | ||
3 | import { readFile } from 'fs-extra' | ||
4 | import { join } from 'path' | ||
5 | import { logger } from '@server/helpers/logger' | ||
6 | import { CONFIG } from '@server/initializers/config' | ||
7 | import { Hooks } from '@server/lib/plugins/hooks' | ||
8 | import { root } from '@shared/core-utils' | ||
9 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' | ||
10 | import { HttpStatusCode } from '@shared/models' | ||
11 | import { STATIC_MAX_AGE } from '../initializers/constants' | ||
12 | import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' | ||
13 | import { asyncMiddleware, buildRateLimiter, embedCSP } from '../middlewares' | ||
14 | |||
15 | const clientsRouter = express.Router() | ||
16 | |||
17 | const clientsRateLimiter = buildRateLimiter({ | ||
18 | windowMs: CONFIG.RATES_LIMIT.CLIENT.WINDOW_MS, | ||
19 | max: CONFIG.RATES_LIMIT.CLIENT.MAX | ||
20 | }) | ||
21 | |||
22 | const distPath = join(root(), 'client', 'dist') | ||
23 | const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html') | ||
24 | |||
25 | // Special route that add OpenGraph and oEmbed tags | ||
26 | // Do not use a template engine for a so little thing | ||
27 | clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ], | ||
28 | clientsRateLimiter, | ||
29 | asyncMiddleware(generateWatchPlaylistHtmlPage) | ||
30 | ) | ||
31 | |||
32 | clientsRouter.use([ '/w/:id', '/videos/watch/:id' ], | ||
33 | clientsRateLimiter, | ||
34 | asyncMiddleware(generateWatchHtmlPage) | ||
35 | ) | ||
36 | |||
37 | clientsRouter.use([ '/accounts/:nameWithHost', '/a/:nameWithHost' ], | ||
38 | clientsRateLimiter, | ||
39 | asyncMiddleware(generateAccountHtmlPage) | ||
40 | ) | ||
41 | |||
42 | clientsRouter.use([ '/video-channels/:nameWithHost', '/c/:nameWithHost' ], | ||
43 | clientsRateLimiter, | ||
44 | asyncMiddleware(generateVideoChannelHtmlPage) | ||
45 | ) | ||
46 | |||
47 | clientsRouter.use('/@:nameWithHost', | ||
48 | clientsRateLimiter, | ||
49 | asyncMiddleware(generateActorHtmlPage) | ||
50 | ) | ||
51 | |||
52 | const embedMiddlewares = [ | ||
53 | clientsRateLimiter, | ||
54 | |||
55 | CONFIG.CSP.ENABLED | ||
56 | ? embedCSP | ||
57 | : (req: express.Request, res: express.Response, next: express.NextFunction) => next(), | ||
58 | |||
59 | // Set headers | ||
60 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
61 | res.removeHeader('X-Frame-Options') | ||
62 | |||
63 | // Don't cache HTML file since it's an index to the immutable JS/CSS files | ||
64 | res.setHeader('Cache-Control', 'public, max-age=0') | ||
65 | |||
66 | next() | ||
67 | }, | ||
68 | |||
69 | asyncMiddleware(generateEmbedHtmlPage) | ||
70 | ] | ||
71 | |||
72 | clientsRouter.use('/videos/embed', ...embedMiddlewares) | ||
73 | clientsRouter.use('/video-playlists/embed', ...embedMiddlewares) | ||
74 | |||
75 | const testEmbedController = (req: express.Request, res: express.Response) => res.sendFile(testEmbedPath) | ||
76 | |||
77 | clientsRouter.use('/videos/test-embed', clientsRateLimiter, testEmbedController) | ||
78 | clientsRouter.use('/video-playlists/test-embed', clientsRateLimiter, testEmbedController) | ||
79 | |||
80 | // Dynamic PWA manifest | ||
81 | clientsRouter.get('/manifest.webmanifest', clientsRateLimiter, asyncMiddleware(generateManifest)) | ||
82 | |||
83 | // Static client overrides | ||
84 | // Must be consistent with static client overrides redirections in /support/nginx/peertube | ||
85 | const staticClientOverrides = [ | ||
86 | 'assets/images/logo.svg', | ||
87 | 'assets/images/favicon.png', | ||
88 | 'assets/images/icons/icon-36x36.png', | ||
89 | 'assets/images/icons/icon-48x48.png', | ||
90 | 'assets/images/icons/icon-72x72.png', | ||
91 | 'assets/images/icons/icon-96x96.png', | ||
92 | 'assets/images/icons/icon-144x144.png', | ||
93 | 'assets/images/icons/icon-192x192.png', | ||
94 | 'assets/images/icons/icon-512x512.png', | ||
95 | 'assets/images/default-playlist.jpg', | ||
96 | 'assets/images/default-avatar-account.png', | ||
97 | 'assets/images/default-avatar-account-48x48.png', | ||
98 | 'assets/images/default-avatar-video-channel.png', | ||
99 | 'assets/images/default-avatar-video-channel-48x48.png' | ||
100 | ] | ||
101 | |||
102 | for (const staticClientOverride of staticClientOverrides) { | ||
103 | const overridePhysicalPath = join(CONFIG.STORAGE.CLIENT_OVERRIDES_DIR, staticClientOverride) | ||
104 | clientsRouter.use(`/client/${staticClientOverride}`, asyncMiddleware(serveClientOverride(overridePhysicalPath))) | ||
105 | } | ||
106 | |||
107 | clientsRouter.use('/client/locales/:locale/:file.json', serveServerTranslations) | ||
108 | clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE.CLIENT })) | ||
109 | |||
110 | // 404 for static files not found | ||
111 | clientsRouter.use('/client/*', (req: express.Request, res: express.Response) => { | ||
112 | res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
113 | }) | ||
114 | |||
115 | // Always serve index client page (the client is a single page application, let it handle routing) | ||
116 | // Try to provide the right language index.html | ||
117 | clientsRouter.use('/(:language)?', | ||
118 | clientsRateLimiter, | ||
119 | asyncMiddleware(serveIndexHTML) | ||
120 | ) | ||
121 | |||
122 | // --------------------------------------------------------------------------- | ||
123 | |||
124 | export { | ||
125 | clientsRouter | ||
126 | } | ||
127 | |||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | function serveServerTranslations (req: express.Request, res: express.Response) { | ||
131 | const locale = req.params.locale | ||
132 | const file = req.params.file | ||
133 | |||
134 | if (is18nLocale(locale) && LOCALE_FILES.includes(file)) { | ||
135 | const completeLocale = getCompleteLocale(locale) | ||
136 | const completeFileLocale = buildFileLocale(completeLocale) | ||
137 | |||
138 | const path = join(__dirname, `../../../client/dist/locale/${file}.${completeFileLocale}.json`) | ||
139 | return res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER }) | ||
140 | } | ||
141 | |||
142 | return res.status(HttpStatusCode.NOT_FOUND_404).end() | ||
143 | } | ||
144 | |||
145 | async function generateEmbedHtmlPage (req: express.Request, res: express.Response) { | ||
146 | const hookName = req.originalUrl.startsWith('/video-playlists/') | ||
147 | ? 'filter:html.embed.video-playlist.allowed.result' | ||
148 | : 'filter:html.embed.video.allowed.result' | ||
149 | |||
150 | const allowParameters = { req } | ||
151 | |||
152 | const allowedResult = await Hooks.wrapFun( | ||
153 | isEmbedAllowed, | ||
154 | allowParameters, | ||
155 | hookName | ||
156 | ) | ||
157 | |||
158 | if (!allowedResult || allowedResult.allowed !== true) { | ||
159 | logger.info('Embed is not allowed.', { allowedResult }) | ||
160 | |||
161 | return sendHTML(allowedResult?.html || '', res) | ||
162 | } | ||
163 | |||
164 | const html = await ClientHtml.getEmbedHTML() | ||
165 | |||
166 | return sendHTML(html, res) | ||
167 | } | ||
168 | |||
169 | async function generateWatchHtmlPage (req: express.Request, res: express.Response) { | ||
170 | // Thread link is '/w/:videoId;threadId=:threadId' | ||
171 | // So to get the videoId we need to remove the last part | ||
172 | let videoId = req.params.id + '' | ||
173 | |||
174 | const threadIdIndex = videoId.indexOf(';threadId') | ||
175 | if (threadIdIndex !== -1) videoId = videoId.substring(0, threadIdIndex) | ||
176 | |||
177 | const html = await ClientHtml.getWatchHTMLPage(videoId, req, res) | ||
178 | |||
179 | return sendHTML(html, res, true) | ||
180 | } | ||
181 | |||
182 | async function generateWatchPlaylistHtmlPage (req: express.Request, res: express.Response) { | ||
183 | const html = await ClientHtml.getWatchPlaylistHTMLPage(req.params.id + '', req, res) | ||
184 | |||
185 | return sendHTML(html, res, true) | ||
186 | } | ||
187 | |||
188 | async function generateAccountHtmlPage (req: express.Request, res: express.Response) { | ||
189 | const html = await ClientHtml.getAccountHTMLPage(req.params.nameWithHost, req, res) | ||
190 | |||
191 | return sendHTML(html, res, true) | ||
192 | } | ||
193 | |||
194 | async function generateVideoChannelHtmlPage (req: express.Request, res: express.Response) { | ||
195 | const html = await ClientHtml.getVideoChannelHTMLPage(req.params.nameWithHost, req, res) | ||
196 | |||
197 | return sendHTML(html, res, true) | ||
198 | } | ||
199 | |||
200 | async function generateActorHtmlPage (req: express.Request, res: express.Response) { | ||
201 | const html = await ClientHtml.getActorHTMLPage(req.params.nameWithHost, req, res) | ||
202 | |||
203 | return sendHTML(html, res, true) | ||
204 | } | ||
205 | |||
206 | async function generateManifest (req: express.Request, res: express.Response) { | ||
207 | const manifestPhysicalPath = join(root(), 'client', 'dist', 'manifest.webmanifest') | ||
208 | const manifestJson = await readFile(manifestPhysicalPath, 'utf8') | ||
209 | const manifest = JSON.parse(manifestJson) | ||
210 | |||
211 | manifest.name = CONFIG.INSTANCE.NAME | ||
212 | manifest.short_name = CONFIG.INSTANCE.NAME | ||
213 | manifest.description = CONFIG.INSTANCE.SHORT_DESCRIPTION | ||
214 | |||
215 | res.json(manifest) | ||
216 | } | ||
217 | |||
218 | function serveClientOverride (path: string) { | ||
219 | return async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
220 | try { | ||
221 | await fs.access(path, constants.F_OK) | ||
222 | // Serve override client | ||
223 | res.sendFile(path, { maxAge: STATIC_MAX_AGE.SERVER }) | ||
224 | } catch { | ||
225 | // Serve dist client | ||
226 | next() | ||
227 | } | ||
228 | } | ||
229 | } | ||
230 | |||
231 | type AllowedResult = { allowed: boolean, html?: string } | ||
232 | function isEmbedAllowed (_object: { | ||
233 | req: express.Request | ||
234 | }): AllowedResult { | ||
235 | return { allowed: true } | ||
236 | } | ||