X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Fclient-html.ts;h=8530cbcc288b06b95dde9c3ea6c9ed05f6a9f23a;hb=5f3505ba78bc05fc61aeca44d95f9a954934c0fc;hp=adc3d712e98eda336e10e5f81a4b7cfbac98a9c4;hpb=0882c8e6509b2a4ea48f6c48ecb2aa4aa371500a;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index adc3d712e..8530cbcc2 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts @@ -2,19 +2,21 @@ import express from 'express' import { readFile } from 'fs-extra' import { join } from 'path' import validator from 'validator' +import { isTestOrDevInstance } from '@server/helpers/core-utils' import { toCompleteUUID } from '@server/helpers/custom-validators/misc' +import { mdToOneLinePlainText } from '@server/helpers/markdown' +import { ActorImageModel } from '@server/models/actor/actor-image' +import { root } from '@shared/core-utils' import { escapeHTML } from '@shared/core-utils/renderer' +import { sha256 } from '@shared/extra-utils' import { HTMLServerConfig } from '@shared/models' import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' import { HttpStatusCode } from '../../shared/models/http/http-error-codes' import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' -import { isTestInstance, sha256 } from '../helpers/core-utils' import { logger } from '../helpers/logger' -import { mdToPlainText } from '../helpers/markdown' import { CONFIG } from '../initializers/config' import { ACCEPT_HEADERS, - ACTOR_IMAGES_SIZE, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, FILES_CONTENT_HASH, @@ -22,11 +24,13 @@ import { WEBSERVER } from '../initializers/constants' import { AccountModel } from '../models/account/account' -import { getActivityStreamDuration } from '../models/video/formatter/video-format-utils' import { VideoModel } from '../models/video/video' import { VideoChannelModel } from '../models/video/video-channel' import { VideoPlaylistModel } from '../models/video/video-playlist' -import { MAccountActor, MChannelActor } from '../types/models' +import { MAccountActor, MChannelActor, MVideo, MVideoPlaylist } from '../types/models' +import { getActivityStreamDuration } from './activitypub/activity' +import { getBiggestActorImage } from './actor-image' +import { Hooks } from './plugins/hooks' import { ServerConfigManager } from './server-config-manager' type Tags = { @@ -61,6 +65,11 @@ type Tags = { } } +type HookContext = { + video?: MVideo + playlist?: MVideoPlaylist +} + class ClientHtml { private static htmlCache: { [path: string]: string } = {} @@ -101,7 +110,7 @@ class ClientHtml { res.status(HttpStatusCode.NOT_FOUND_404) return html } - const description = mdToPlainText(video.description) + const description = mdToOneLinePlainText(video.description) let customHtml = ClientHtml.addTitleTag(html, video.name) customHtml = ClientHtml.addDescriptionTag(customHtml, description) @@ -126,18 +135,19 @@ class ClientHtml { const twitterCard = CONFIG.SERVICES.TWITTER.WHITELISTED ? 'player' : 'summary_large_image' const schemaType = 'VideoObject' - customHtml = ClientHtml.addTags(customHtml, { + customHtml = await ClientHtml.addTags(customHtml, { url, originUrl, escapedSiteName: escapeHTML(siteName), escapedTitle: escapeHTML(title), escapedDescription: escapeHTML(description), + disallowIndexation: video.privacy !== VideoPrivacy.PUBLIC, image, embed, ogType, twitterCard, schemaType - }) + }, { video }) return customHtml } @@ -162,7 +172,7 @@ class ClientHtml { return html } - const description = mdToPlainText(videoPlaylist.description) + const description = mdToOneLinePlainText(videoPlaylist.description) let customHtml = ClientHtml.addTitleTag(html, videoPlaylist.name) customHtml = ClientHtml.addDescriptionTag(customHtml, description) @@ -189,19 +199,20 @@ class ClientHtml { const twitterCard = CONFIG.SERVICES.TWITTER.WHITELISTED ? 'player' : 'summary' const schemaType = 'ItemList' - customHtml = ClientHtml.addTags(customHtml, { + customHtml = await ClientHtml.addTags(customHtml, { url, originUrl, escapedSiteName: escapeHTML(siteName), escapedTitle: escapeHTML(title), escapedDescription: escapeHTML(description), + disallowIndexation: videoPlaylist.privacy !== VideoPlaylistPrivacy.PUBLIC, embed, image, list, ogType, twitterCard, schemaType - }) + }, { playlist: videoPlaylist }) return customHtml } @@ -228,7 +239,10 @@ class ClientHtml { static async getEmbedHTML () { const path = ClientHtml.getEmbedPath() - if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] + // Disable HTML cache in dev mode because webpack can regenerate JS files + if (!isTestOrDevInstance() && ClientHtml.htmlCache[path]) { + return ClientHtml.htmlCache[path] + } const buffer = await readFile(path) const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() @@ -261,7 +275,7 @@ class ClientHtml { return ClientHtml.getIndexHTML(req, res) } - const description = mdToPlainText(entity.description) + const description = mdToOneLinePlainText(entity.description) let customHtml = ClientHtml.addTitleTag(html, entity.getDisplayName()) customHtml = ClientHtml.addDescriptionTag(customHtml, description) @@ -271,17 +285,18 @@ class ClientHtml { const siteName = CONFIG.INSTANCE.NAME const title = entity.getDisplayName() + const avatar = getBiggestActorImage(entity.Actor.Avatars) const image = { - url: entity.Actor.getAvatarUrl(), - width: ACTOR_IMAGES_SIZE.AVATARS.width, - height: ACTOR_IMAGES_SIZE.AVATARS.height + url: ActorImageModel.getImageUrl(avatar), + width: avatar?.width, + height: avatar?.height } const ogType = 'website' const twitterCard = 'summary' const schemaType = 'ProfilePage' - customHtml = ClientHtml.addTags(customHtml, { + customHtml = await ClientHtml.addTags(customHtml, { url, originUrl, escapedTitle: escapeHTML(title), @@ -292,14 +307,14 @@ class ClientHtml { twitterCard, schemaType, disallowIndexation: !entity.Actor.isOwned() - }) + }, {}) return customHtml } private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { const path = ClientHtml.getIndexPath(req, res, paramLang) - if (!isTestInstance() && ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] + if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] const buffer = await readFile(path) const serverConfig = await ServerConfigManager.Instance.getHTMLServerConfig() @@ -343,11 +358,11 @@ class ClientHtml { { cookie: req.cookies?.clientLanguage, paramLang, acceptLanguage: req.headers['accept-language'] } ) - return join(__dirname, '../../../client/dist/' + buildFileLocale(lang) + '/index.html') + return join(root(), 'client', 'dist', buildFileLocale(lang), 'index.html') } private static getEmbedPath () { - return join(__dirname, '../../../client/dist/standalone/videos/embed.html') + return join(root(), 'client', 'dist', 'standalone', 'videos', 'embed.html') } private static addManifestContentHash (htmlStringPage: string) { @@ -460,7 +475,7 @@ class ClientHtml { return metaTags } - private static generateSchemaTags (tags: Tags) { + private static async generateSchemaTags (tags: Tags, context: HookContext) { const schema = { '@context': 'http://schema.org', '@type': tags.schemaType, @@ -480,20 +495,19 @@ class ClientHtml { schema['uploadDate'] = tags.embed.createdAt if (tags.embed.duration) schema['duration'] = tags.embed.duration - if (tags.embed.views) schema['iterationCount'] = tags.embed.views schema['thumbnailUrl'] = tags.image.url schema['contentUrl'] = tags.url } - return schema + return Hooks.wrapObject(schema, 'filter:html.client.json-ld.result', context) } - private static addTags (htmlStringPage: string, tagsValues: Tags) { + private static async addTags (htmlStringPage: string, tagsValues: Tags, context: HookContext) { const openGraphMetaTags = this.generateOpenGraphMetaTags(tagsValues) const standardMetaTags = this.generateStandardMetaTags(tagsValues) const twitterCardMetaTags = this.generateTwitterCardMetaTags(tagsValues) - const schemaTags = this.generateSchemaTags(tagsValues) + const schemaTags = await this.generateSchemaTags(tagsValues, context) const { url, escapedTitle, embed, originUrl, disallowIndexation } = tagsValues @@ -567,7 +581,7 @@ async function serveIndexHTML (req: express.Request, res: express.Response) { await generateHTMLPage(req, res, req.params.language) return } catch (err) { - logger.error('Cannot generate HTML page.', err) + logger.error('Cannot generate HTML page.', { err }) return res.status(HttpStatusCode.INTERNAL_SERVER_ERROR_500).end() } }