f474c4282006c65ed0b8e2f772b56cfb5518e2ed
[oweals/peertube.git] / server / controllers / client.ts
1 import * as express from 'express'
2 import { join } from 'path'
3 import * as validator from 'validator'
4 import * as Bluebird from 'bluebird'
5
6 import { database as db } from '../initializers/database'
7 import {
8   CONFIG,
9   STATIC_PATHS,
10   STATIC_MAX_AGE,
11   OPENGRAPH_AND_OEMBED_COMMENT,
12   EMBED_SIZE
13 } from '../initializers'
14 import { root, readFileBufferPromise, escapeHTML } from '../helpers'
15 import { asyncMiddleware } from '../middlewares'
16 import { VideoInstance } from '../models'
17
18 const clientsRouter = express.Router()
19
20 const distPath = join(root(), 'client', 'dist')
21 const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images')
22 const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
23 const indexPath = join(distPath, 'index.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('/videos/watch/:id',
28   asyncMiddleware(generateWatchHtmlPage)
29 )
30
31 clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => {
32   res.sendFile(embedPath)
33 })
34
35 // Static HTML/CSS/JS client files
36 clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE }))
37 clientsRouter.use('/client/assets/images', express.static(assetsImagesPath, { maxAge: STATIC_MAX_AGE }))
38
39 // 404 for static files not found
40 clientsRouter.use('/client/*', (req: express.Request, res: express.Response, next: express.NextFunction) => {
41   res.sendStatus(404)
42 })
43
44 // ---------------------------------------------------------------------------
45
46 export {
47   clientsRouter
48 }
49
50 // ---------------------------------------------------------------------------
51
52 function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance) {
53   const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName()
54   const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
55
56   const videoNameEscaped = escapeHTML(video.name)
57   const videoDescriptionEscaped = escapeHTML(video.description)
58   const embedUrl = CONFIG.WEBSERVER.URL + video.getEmbedPath()
59
60   const openGraphMetaTags = {
61     'og:type': 'video',
62     'og:title': videoNameEscaped,
63     'og:image': previewUrl,
64     'og:url': videoUrl,
65     'og:description': videoDescriptionEscaped,
66
67     'og:video:url': embedUrl,
68     'og:video:secure_url': embedUrl,
69     'og:video:type': 'text/html',
70     'og:video:width': EMBED_SIZE.width,
71     'og:video:height': EMBED_SIZE.height,
72
73     'name': videoNameEscaped,
74     'description': videoDescriptionEscaped,
75     'image': previewUrl,
76
77     'twitter:card': 'summary_large_image',
78     'twitter:site': '@Chocobozzz',
79     'twitter:title': videoNameEscaped,
80     'twitter:description': videoDescriptionEscaped,
81     'twitter:image': previewUrl,
82     'twitter:player': embedUrl,
83     'twitter:player:width': EMBED_SIZE.width,
84     'twitter:player:height': EMBED_SIZE.height
85   }
86
87   const oembedLinkTags = [
88     {
89       type: 'application/json+oembed',
90       href: CONFIG.WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl),
91       title: videoNameEscaped
92     }
93   ]
94
95   let tagsString = ''
96   Object.keys(openGraphMetaTags).forEach(tagName => {
97     const tagValue = openGraphMetaTags[tagName]
98
99     tagsString += `<meta property="${tagName}" content="${tagValue}" />`
100   })
101
102   for (const oembedLinkTag of oembedLinkTags) {
103     tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />`
104   }
105
106   return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString)
107 }
108
109 async function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) {
110   const videoId = '' + req.params.id
111   let videoPromise: Bluebird<VideoInstance>
112
113   // Let Angular application handle errors
114   if (validator.isUUID(videoId, 4)) {
115     videoPromise = db.Video.loadByUUIDAndPopulateAccountAndServerAndTags(videoId)
116   } else if (validator.isInt(videoId)) {
117     videoPromise = db.Video.loadAndPopulateAccountAndServerAndTags(+videoId)
118   } else {
119     return res.sendFile(indexPath)
120   }
121
122   let [ file, video ] = await Promise.all([
123     readFileBufferPromise(indexPath),
124     videoPromise
125   ])
126
127   const html = file.toString()
128
129   // Let Angular application handle errors
130   if (!video) return res.sendFile(indexPath)
131
132   const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
133   res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
134 }