Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Framasoft
PeerTube
PeerTube
Commits
e032aec9
Verified
Commit
e032aec9
authored
Jul 18, 2018
by
Chocobozzz
Browse files
Render CSS/title/description tags on server side
parent
1d94c154
Changes
10
Hide whitespace changes
Inline
Side-by-side
client/src/app/app.component.ts
View file @
e032aec9
...
...
@@ -4,6 +4,7 @@ import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router'
import
{
AuthService
,
RedirectService
,
ServerService
}
from
'
@app/core
'
import
{
is18nPath
}
from
'
../../../shared/models/i18n
'
import
{
ScreenService
}
from
'
@app/shared/misc/screen.service
'
import
{
skip
}
from
'
rxjs/operators
'
@
Component
({
selector
:
'
my-app
'
,
...
...
@@ -89,25 +90,36 @@ export class AppComponent implements OnInit {
}
)
// Inject JS
this
.
serverService
.
configLoaded
.
subscribe
(()
=>
{
const
config
=
this
.
serverService
.
getConfig
()
.
subscribe
(()
=>
{
const
config
=
this
.
serverService
.
getConfig
()
if
(
config
.
instance
.
customizations
.
javascript
)
{
try
{
// tslint:disable:no-eval
eval
(
config
.
instance
.
customizations
.
javascript
)
}
catch
(
err
)
{
console
.
error
(
'
Cannot eval custom JavaScript.
'
,
err
)
}
}
})
// We test customCSS if the admin removed the css
if
(
this
.
customCSS
||
config
.
instance
.
customizations
.
css
)
{
const
styleTag
=
'
<style>
'
+
config
.
instance
.
customizations
.
css
+
'
</style>
'
this
.
customCSS
=
this
.
domSanitizer
.
bypassSecurityTrustHtml
(
styleTag
)
}
// Inject CSS if modified (admin config settings)
this
.
serverService
.
configLoaded
.
pipe
(
skip
(
1
))
// We only want to subscribe to reloads, because the CSS is already injected by the server
.
subscribe
(()
=>
{
const
headStyle
=
document
.
querySelector
(
'
style.custom-css-style
'
)
if
(
headStyle
)
headStyle
.
parentNode
.
removeChild
(
headStyle
)
if
(
config
.
instance
.
customizations
.
javascript
)
{
try
{
//
tslint:disable:no-eval
eval
(
config
.
instance
.
customizations
.
javascript
)
}
c
atch
(
err
)
{
console
.
error
(
'
Cannot eval custom JavaScript.
'
,
err
)
const
config
=
this
.
serverService
.
getConfig
()
//
We test customCSS if the admin removed the css
if
(
this
.
customCSS
||
config
.
instance
.
customizations
.
css
)
{
c
onst
styleTag
=
'
<style>
'
+
config
.
instance
.
customizations
.
css
+
'
</style>
'
this
.
customCSS
=
this
.
domSanitizer
.
bypassSecurityTrustHtml
(
styleTag
)
}
}
})
})
}
isUserLoggedIn
()
{
...
...
client/src/index.html
View file @
e032aec9
<!DOCTYPE html>
<html>
<head>
<title>
PeerTube
</title>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1"
>
<meta
name=
"description"
content=
"PeerTube, a decentralized video streaming platform using P2P (BitTorrent) directly in the web browser"
/>
<meta
name=
"theme-color"
content=
"#fff"
/>
<!-- Web Manifest file -->
<link
rel=
"manifest"
href=
"/manifest.json"
>
<!-- The following comment is used by the server to prerender OpenGraph and oEmbed tags -->
<!-- open graph and oembed tags -->
<!-- Do not remove it! -->
<!-- /!\ The following comment is used by the server to prerender some tags /!\ -->
<!-- title tag -->
<!-- description tag -->
<!-- custom css tag -->
<!-- open graph and oembed tags -->
<!-- /!\ Do not remove it /!\ -->
<link
rel=
"icon"
type=
"image/png"
href=
"/client/assets/images/favicon.png"
/>
...
...
scripts/build/client.sh
View file @
e032aec9
...
...
@@ -10,16 +10,19 @@ defaultLanguage="en_US"
npm run ng build
--
--output-path
"dist/
$defaultLanguage
/"
--deploy-url
"/client/
$defaultLanguage
/"
--prod
--stats-json
mv
"./dist/
$defaultLanguage
/assets"
"./dist"
# Supported languages
languages
=(
"fr_FR"
"eu_ES"
"ca_ES"
"cs_CZ"
"eo"
)
for
lang
in
"
${
languages
[@]
}
"
;
do
npm run ng build
--
--prod
--i18n-file
"./src/locale/target/angular_
$lang
.xml"
--i18n-format
xlf
--i18n-locale
"
$lang
"
\
--output-path
"dist/
$lang
/"
--deploy-url
"/client/
$lang
/"
# Do no duplicate assets
rm
-r
"./dist/
$lang
/assets"
done
# Don't build other languages if --light arg is provided
if
[
-z
${
1
+x
}
]
||
[
"
$1
"
!=
"--light"
]
;
then
# Supported languages
languages
=(
"fr_FR"
"eu_ES"
"ca_ES"
"cs_CZ"
"eo"
)
for
lang
in
"
${
languages
[@]
}
"
;
do
npm run ng build
--
--prod
--i18n-file
"./src/locale/target/angular_
$lang
.xml"
--i18n-format
xlf
--i18n-locale
"
$lang
"
\
--output-path
"dist/
$lang
/"
--deploy-url
"/client/
$lang
/"
# Do no duplicate assets
rm
-r
"./dist/
$lang
/assets"
done
fi
NODE_ENV
=
production npm run webpack
--
--config
webpack/webpack.video-embed.js
--mode
production
--json
>
"./dist/embed-stats.json"
...
...
server/controllers/api/config.ts
View file @
e032aec9
...
...
@@ -8,6 +8,7 @@ import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/util
import
{
CONFIG
,
CONSTRAINTS_FIELDS
,
reloadConfig
}
from
'
../../initializers
'
import
{
asyncMiddleware
,
authenticate
,
ensureUserHasRight
}
from
'
../../middlewares
'
import
{
customConfigUpdateValidator
}
from
'
../../middlewares/validators/config
'
import
{
ClientHtml
}
from
'
../../lib/client-html
'
const
packageJSON
=
require
(
'
../../../../package.json
'
)
const
configRouter
=
express
.
Router
()
...
...
@@ -119,6 +120,7 @@ async function deleteCustomConfig (req: express.Request, res: express.Response,
await
unlinkPromise
(
CONFIG
.
CUSTOM_FILE
)
reloadConfig
()
ClientHtml
.
invalidCache
()
const
data
=
customConfig
()
...
...
@@ -145,6 +147,7 @@ async function updateCustomConfig (req: express.Request, res: express.Response,
await
writeFilePromise
(
CONFIG
.
CUSTOM_FILE
,
JSON
.
stringify
(
toUpdateJSON
,
undefined
,
2
))
reloadConfig
()
ClientHtml
.
invalidCache
()
const
data
=
customConfig
()
return
res
.
json
(
data
).
end
()
...
...
server/controllers/client.ts
View file @
e032aec9
import
*
as
Bluebird
from
'
bluebird
'
import
*
as
express
from
'
express
'
import
*
as
helmet
from
'
helmet
'
import
{
join
}
from
'
path
'
import
*
as
validator
from
'
validator
'
import
{
escapeHTML
,
readFileBufferPromise
,
root
}
from
'
../helpers/core-utils
'
import
{
ACCEPT_HEADERS
,
CONFIG
,
EMBED_SIZE
,
OPENGRAPH_AND_OEMBED_COMMENT
,
STATIC_MAX_AGE
,
STATIC_PATHS
}
from
'
../initializers
'
import
{
root
}
from
'
../helpers/core-utils
'
import
{
ACCEPT_HEADERS
,
STATIC_MAX_AGE
}
from
'
../initializers
'
import
{
asyncMiddleware
}
from
'
../middlewares
'
import
{
VideoModel
}
from
'
../models/video/video
'
import
{
VideoPrivacy
}
from
'
../../shared/models/videos
'
import
{
buildFileLocale
,
getCompleteLocale
,
getDefaultLocale
,
is18nLocale
,
LOCALE_FILES
,
POSSIBLE_LOCALES
}
from
'
../../shared/models/i18n/i18n
'
import
{
buildFileLocale
,
getCompleteLocale
,
is18nLocale
,
LOCALE_FILES
}
from
'
../../shared/models/i18n/i18n
'
import
{
ClientHtml
}
from
'
../lib/client-html
'
const
clientsRouter
=
express
.
Router
()
...
...
@@ -79,7 +68,7 @@ clientsRouter.use('/client/*', (req: express.Request, res: express.Response, nex
// Try to provide the right language index.html
clientsRouter
.
use
(
'
/(:language)?
'
,
function
(
req
,
res
)
{
if
(
req
.
accepts
(
ACCEPT_HEADERS
)
===
'
html
'
)
{
return
res
.
sendFile
(
getIndexPath
(
req
,
res
,
req
.
params
.
language
)
)
return
generateHTMLPage
(
req
,
res
,
req
.
params
.
language
)
}
return
res
.
status
(
404
).
end
()
...
...
@@ -93,131 +82,20 @@ export {
// ---------------------------------------------------------------------------
function
ge
tIndexPath
(
req
:
express
.
Request
,
res
:
express
.
Response
,
paramLang
?:
string
)
{
let
lang
:
string
async
function
ge
nerateHTMLPage
(
req
:
express
.
Request
,
res
:
express
.
Response
,
paramLang
?:
string
)
{
const
html
=
await
ClientHtml
.
getIndexHTML
(
req
,
res
)
// Check param lang validity
if
(
paramLang
&&
is18nLocale
(
paramLang
))
{
lang
=
paramLang
// Save locale in cookies
res
.
cookie
(
'
clientLanguage
'
,
lang
,
{
secure
:
CONFIG
.
WEBSERVER
.
SCHEME
===
'
https
'
,
sameSite
:
true
,
maxAge
:
1000
*
3600
*
24
*
90
// 3 months
})
}
else
if
(
req
.
cookies
.
clientLanguage
&&
is18nLocale
(
req
.
cookies
.
clientLanguage
))
{
lang
=
req
.
cookies
.
clientLanguage
}
else
{
lang
=
req
.
acceptsLanguages
(
POSSIBLE_LOCALES
)
||
getDefaultLocale
()
}
return
join
(
__dirname
,
'
../../../client/dist/
'
+
buildFileLocale
(
lang
)
+
'
/index.html
'
)
return
sendHTML
(
html
,
res
)
}
function
addOpenGraphAndOEmbedTags
(
htmlStringPage
:
string
,
video
:
VideoModel
)
{
const
previewUrl
=
CONFIG
.
WEBSERVER
.
URL
+
STATIC_PATHS
.
PREVIEWS
+
video
.
getPreviewName
()
const
videoUrl
=
CONFIG
.
WEBSERVER
.
URL
+
'
/videos/watch/
'
+
video
.
uuid
const
videoNameEscaped
=
escapeHTML
(
video
.
name
)
const
videoDescriptionEscaped
=
escapeHTML
(
video
.
description
)
const
embedUrl
=
CONFIG
.
WEBSERVER
.
URL
+
video
.
getEmbedStaticPath
()
const
openGraphMetaTags
=
{
'
og:type
'
:
'
video
'
,
'
og:title
'
:
videoNameEscaped
,
'
og:image
'
:
previewUrl
,
'
og:url
'
:
videoUrl
,
'
og:description
'
:
videoDescriptionEscaped
,
'
og:video:url
'
:
embedUrl
,
'
og:video:secure_url
'
:
embedUrl
,
'
og:video:type
'
:
'
text/html
'
,
'
og:video:width
'
:
EMBED_SIZE
.
width
,
'
og:video:height
'
:
EMBED_SIZE
.
height
,
'
name
'
:
videoNameEscaped
,
'
description
'
:
videoDescriptionEscaped
,
'
image
'
:
previewUrl
,
'
twitter:card
'
:
CONFIG
.
SERVICES
.
TWITTER
.
WHITELISTED
?
'
player
'
:
'
summary_large_image
'
,
'
twitter:site
'
:
CONFIG
.
SERVICES
.
TWITTER
.
USERNAME
,
'
twitter:title
'
:
videoNameEscaped
,
'
twitter:description
'
:
videoDescriptionEscaped
,
'
twitter:image
'
:
previewUrl
,
'
twitter:player
'
:
embedUrl
,
'
twitter:player:width
'
:
EMBED_SIZE
.
width
,
'
twitter:player:height
'
:
EMBED_SIZE
.
height
}
const
oembedLinkTags
=
[
{
type
:
'
application/json+oembed
'
,
href
:
CONFIG
.
WEBSERVER
.
URL
+
'
/services/oembed?url=
'
+
encodeURIComponent
(
videoUrl
),
title
:
videoNameEscaped
}
]
const
schemaTags
=
{
'
@context
'
:
'
http://schema.org
'
,
'
@type
'
:
'
VideoObject
'
,
name
:
videoNameEscaped
,
description
:
videoDescriptionEscaped
,
thumbnailUrl
:
previewUrl
,
uploadDate
:
video
.
createdAt
.
toISOString
(),
duration
:
video
.
getActivityStreamDuration
(),
contentUrl
:
videoUrl
,
embedUrl
:
embedUrl
,
interactionCount
:
video
.
views
}
let
tagsString
=
''
// Opengraph
Object
.
keys
(
openGraphMetaTags
).
forEach
(
tagName
=>
{
const
tagValue
=
openGraphMetaTags
[
tagName
]
async
function
generateWatchHtmlPage
(
req
:
express
.
Request
,
res
:
express
.
Response
)
{
const
html
=
await
ClientHtml
.
getWatchHTMLPage
(
req
.
params
.
id
+
''
,
req
,
res
)
tagsString
+=
`<meta property="
${
tagName
}
" content="
${
tagValue
}
" />`
})
// OEmbed
for
(
const
oembedLinkTag
of
oembedLinkTags
)
{
tagsString
+=
`<link rel="alternate" type="
${
oembedLinkTag
.
type
}
" href="
${
oembedLinkTag
.
href
}
" title="
${
oembedLinkTag
.
title
}
" />`
}
// Schema.org
tagsString
+=
`<script type="application/ld+json">
${
JSON
.
stringify
(
schemaTags
)}
</script>`
// SEO
tagsString
+=
`<link rel="canonical" href="
${
videoUrl
}
" />`
return
htmlStringPage
.
replace
(
OPENGRAPH_AND_OEMBED_COMMENT
,
tagsString
)
return
sendHTML
(
html
,
res
)
}
async
function
generateWatchHtmlPage
(
req
:
express
.
Request
,
res
:
express
.
Response
,
next
:
express
.
NextFunction
)
{
const
videoId
=
''
+
req
.
params
.
id
let
videoPromise
:
Bluebird
<
VideoModel
>
// Let Angular application handle errors
if
(
validator
.
isUUID
(
videoId
,
4
))
{
videoPromise
=
VideoModel
.
loadByUUIDAndPopulateAccountAndServerAndTags
(
videoId
)
}
else
if
(
validator
.
isInt
(
videoId
))
{
videoPromise
=
VideoModel
.
loadAndPopulateAccountAndServerAndTags
(
+
videoId
)
}
else
{
return
res
.
sendFile
(
getIndexPath
(
req
,
res
))
}
let
[
file
,
video
]
=
await
Promise
.
all
([
readFileBufferPromise
(
getIndexPath
(
req
,
res
)),
videoPromise
])
const
html
=
file
.
toString
()
// Let Angular application handle errors
if
(
!
video
||
video
.
privacy
===
VideoPrivacy
.
PRIVATE
)
return
res
.
sendFile
(
getIndexPath
(
req
,
res
))
function
sendHTML
(
html
:
string
,
res
:
express
.
Response
)
{
res
.
set
(
'
Content-Type
'
,
'
text/html; charset=UTF-8
'
)
const
htmlStringPageWithTags
=
addOpenGraphAndOEmbedTags
(
html
,
video
)
res
.
set
(
'
Content-Type
'
,
'
text/html; charset=UTF-8
'
).
send
(
htmlStringPageWithTags
)
return
res
.
send
(
html
)
}
server/initializers/constants.ts
View file @
e032aec9
...
...
@@ -466,7 +466,12 @@ const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENT
// ---------------------------------------------------------------------------
const
OPENGRAPH_AND_OEMBED_COMMENT
=
'
<!-- open graph and oembed tags -->
'
const
CUSTOM_HTML_TAG_COMMENTS
=
{
TITLE
:
'
<!-- title tag -->
'
,
DESCRIPTION
:
'
<!-- description tag -->
'
,
CUSTOM_CSS
:
'
<!-- custom css tag -->
'
,
OPENGRAPH_AND_OEMBED
:
'
<!-- open graph and oembed tags -->
'
}
// ---------------------------------------------------------------------------
...
...
@@ -528,7 +533,7 @@ export {
JOB_ATTEMPTS
,
LAST_MIGRATION_VERSION
,
OAUTH_LIFETIME
,
OPENGRAPH_AND_OEMBED
_COMMENT
,
CUSTOM_HTML_TAG
_COMMENT
S
,
BROADCAST_CONCURRENCY
,
PAGINATION_COUNT_DEFAULT
,
ACTOR_FOLLOW_SCORE
,
...
...
server/lib/client-html.ts
0 → 100644
View file @
e032aec9
import
*
as
express
from
'
express
'
import
*
as
Bluebird
from
'
bluebird
'
import
{
buildFileLocale
,
getDefaultLocale
,
is18nLocale
,
POSSIBLE_LOCALES
}
from
'
../../shared/models/i18n/i18n
'
import
{
CONFIG
,
EMBED_SIZE
,
CUSTOM_HTML_TAG_COMMENTS
,
STATIC_PATHS
}
from
'
../initializers
'
import
{
join
}
from
'
path
'
import
{
escapeHTML
,
readFileBufferPromise
}
from
'
../helpers/core-utils
'
import
{
VideoModel
}
from
'
../models/video/video
'
import
*
as
validator
from
'
validator
'
import
{
VideoPrivacy
}
from
'
../../shared/models/videos
'
export
class
ClientHtml
{
private
static
htmlCache
:
{
[
path
:
string
]:
string
}
=
{}
static
invalidCache
()
{
ClientHtml
.
htmlCache
=
{}
}
static
async
getIndexHTML
(
req
:
express
.
Request
,
res
:
express
.
Response
,
paramLang
?:
string
)
{
const
path
=
ClientHtml
.
getIndexPath
(
req
,
res
,
paramLang
)
if
(
ClientHtml
.
htmlCache
[
path
])
return
ClientHtml
.
htmlCache
[
path
]
const
buffer
=
await
readFileBufferPromise
(
path
)
let
html
=
buffer
.
toString
()
html
=
ClientHtml
.
addTitleTag
(
html
)
html
=
ClientHtml
.
addDescriptionTag
(
html
)
html
=
ClientHtml
.
addCustomCSS
(
html
)
ClientHtml
.
htmlCache
[
path
]
=
html
return
html
}
static
async
getWatchHTMLPage
(
videoId
:
string
,
req
:
express
.
Request
,
res
:
express
.
Response
)
{
let
videoPromise
:
Bluebird
<
VideoModel
>
// Let Angular application handle errors
if
(
validator
.
isUUID
(
videoId
,
4
))
{
videoPromise
=
VideoModel
.
loadByUUIDAndPopulateAccountAndServerAndTags
(
videoId
)
}
else
if
(
validator
.
isInt
(
videoId
))
{
videoPromise
=
VideoModel
.
loadAndPopulateAccountAndServerAndTags
(
+
videoId
)
}
else
{
return
ClientHtml
.
getIndexHTML
(
req
,
res
)
}
const
[
html
,
video
]
=
await
Promise
.
all
([
ClientHtml
.
getIndexHTML
(
req
,
res
),
videoPromise
])
// Let Angular application handle errors
if
(
!
video
||
video
.
privacy
===
VideoPrivacy
.
PRIVATE
)
{
return
ClientHtml
.
getIndexHTML
(
req
,
res
)
}
return
ClientHtml
.
addOpenGraphAndOEmbedTags
(
html
,
video
)
}
private
static
getIndexPath
(
req
:
express
.
Request
,
res
:
express
.
Response
,
paramLang
?:
string
)
{
let
lang
:
string
// Check param lang validity
if
(
paramLang
&&
is18nLocale
(
paramLang
))
{
lang
=
paramLang
// Save locale in cookies
res
.
cookie
(
'
clientLanguage
'
,
lang
,
{
secure
:
CONFIG
.
WEBSERVER
.
SCHEME
===
'
https
'
,
sameSite
:
true
,
maxAge
:
1000
*
3600
*
24
*
90
// 3 months
})
}
else
if
(
req
.
cookies
.
clientLanguage
&&
is18nLocale
(
req
.
cookies
.
clientLanguage
))
{
lang
=
req
.
cookies
.
clientLanguage
}
else
{
lang
=
req
.
acceptsLanguages
(
POSSIBLE_LOCALES
)
||
getDefaultLocale
()
}
return
join
(
__dirname
,
'
../../../client/dist/
'
+
buildFileLocale
(
lang
)
+
'
/index.html
'
)
}
private
static
addTitleTag
(
htmlStringPage
:
string
)
{
const
titleTag
=
'
<title>
'
+
CONFIG
.
INSTANCE
.
NAME
+
'
</title>
'
return
htmlStringPage
.
replace
(
CUSTOM_HTML_TAG_COMMENTS
.
TITLE
,
titleTag
)
}
private
static
addDescriptionTag
(
htmlStringPage
:
string
)
{
const
descriptionTag
=
`<meta name="description" content="
${
CONFIG
.
INSTANCE
.
SHORT_DESCRIPTION
}
" />`
return
htmlStringPage
.
replace
(
CUSTOM_HTML_TAG_COMMENTS
.
DESCRIPTION
,
descriptionTag
)
}
private
static
addCustomCSS
(
htmlStringPage
:
string
)
{
const
styleTag
=
'
<style class="custom-css-style">
'
+
CONFIG
.
INSTANCE
.
CUSTOMIZATIONS
.
CSS
+
'
</style>
'
return
htmlStringPage
.
replace
(
CUSTOM_HTML_TAG_COMMENTS
.
CUSTOM_CSS
,
styleTag
)
}
private
static
addOpenGraphAndOEmbedTags
(
htmlStringPage
:
string
,
video
:
VideoModel
)
{
const
previewUrl
=
CONFIG
.
WEBSERVER
.
URL
+
STATIC_PATHS
.
PREVIEWS
+
video
.
getPreviewName
()
const
videoUrl
=
CONFIG
.
WEBSERVER
.
URL
+
'
/videos/watch/
'
+
video
.
uuid
const
videoNameEscaped
=
escapeHTML
(
video
.
name
)
const
videoDescriptionEscaped
=
escapeHTML
(
video
.
description
)
const
embedUrl
=
CONFIG
.
WEBSERVER
.
URL
+
video
.
getEmbedStaticPath
()
const
openGraphMetaTags
=
{
'
og:type
'
:
'
video
'
,
'
og:title
'
:
videoNameEscaped
,
'
og:image
'
:
previewUrl
,
'
og:url
'
:
videoUrl
,
'
og:description
'
:
videoDescriptionEscaped
,
'
og:video:url
'
:
embedUrl
,
'
og:video:secure_url
'
:
embedUrl
,
'
og:video:type
'
:
'
text/html
'
,
'
og:video:width
'
:
EMBED_SIZE
.
width
,
'
og:video:height
'
:
EMBED_SIZE
.
height
,
'
name
'
:
videoNameEscaped
,
'
description
'
:
videoDescriptionEscaped
,
'
image
'
:
previewUrl
,
'
twitter:card
'
:
CONFIG
.
SERVICES
.
TWITTER
.
WHITELISTED
?
'
player
'
:
'
summary_large_image
'
,
'
twitter:site
'
:
CONFIG
.
SERVICES
.
TWITTER
.
USERNAME
,
'
twitter:title
'
:
videoNameEscaped
,
'
twitter:description
'
:
videoDescriptionEscaped
,
'
twitter:image
'
:
previewUrl
,
'
twitter:player
'
:
embedUrl
,
'
twitter:player:width
'
:
EMBED_SIZE
.
width
,
'
twitter:player:height
'
:
EMBED_SIZE
.
height
}
const
oembedLinkTags
=
[
{
type
:
'
application/json+oembed
'
,
href
:
CONFIG
.
WEBSERVER
.
URL
+
'
/services/oembed?url=
'
+
encodeURIComponent
(
videoUrl
),
title
:
videoNameEscaped
}
]
const
schemaTags
=
{
'
@context
'
:
'
http://schema.org
'
,
'
@type
'
:
'
VideoObject
'
,
name
:
videoNameEscaped
,
description
:
videoDescriptionEscaped
,
thumbnailUrl
:
previewUrl
,
uploadDate
:
video
.
createdAt
.
toISOString
(),
duration
:
video
.
getActivityStreamDuration
(),
contentUrl
:
videoUrl
,
embedUrl
:
embedUrl
,
interactionCount
:
video
.
views
}
let
tagsString
=
''
// Opengraph
Object
.
keys
(
openGraphMetaTags
).
forEach
(
tagName
=>
{
const
tagValue
=
openGraphMetaTags
[
tagName
]
tagsString
+=
`<meta property="
${
tagName
}
" content="
${
tagValue
}
" />`
})
// OEmbed
for
(
const
oembedLinkTag
of
oembedLinkTags
)
{
tagsString
+=
`<link rel="alternate" type="
${
oembedLinkTag
.
type
}
" href="
${
oembedLinkTag
.
href
}
" title="
${
oembedLinkTag
.
title
}
" />`
}
// Schema.org
tagsString
+=
`<script type="application/ld+json">
${
JSON
.
stringify
(
schemaTags
)}
</script>`
// SEO
tagsString
+=
`<link rel="canonical" href="
${
videoUrl
}
" />`
return
htmlStringPage
.
replace
(
CUSTOM_HTML_TAG_COMMENTS
.
OPENGRAPH_AND_OEMBED
,
tagsString
)
}
}
server/tests/api/server/config.ts
View file @
e032aec9
...
...
@@ -4,7 +4,7 @@ import 'mocha'
import
*
as
chai
from
'
chai
'
import
{
About
}
from
'
../../../../shared/models/server/about.model
'
import
{
CustomConfig
}
from
'
../../../../shared/models/server/custom-config.model
'
import
{
deleteCustomConfig
,
getAbout
,
killallServers
,
reRunServer
}
from
'
../../utils
'
import
{
deleteCustomConfig
,
getAbout
,
killallServers
,
makeHTMLRequest
,
reRunServer
}
from
'
../../utils
'
const
expect
=
chai
.
expect
import
{
...
...
@@ -69,6 +69,12 @@ function checkUpdatedConfig (data: CustomConfig) {
expect
(
data
.
transcoding
.
resolutions
[
'
1080p
'
]).
to
.
be
.
false
}
function
checkIndexTags
(
html
:
string
,
title
:
string
,
description
:
string
,
css
:
string
)
{
expect
(
html
).
to
.
contain
(
'
<title>
'
+
title
+
'
</title>
'
)
expect
(
html
).
to
.
contain
(
'
<meta name="description" content="
'
+
description
+
'
" />
'
)
expect
(
html
).
to
.
contain
(
'
<style class="custom-css-style">
'
+
css
+
'
</style>
'
)
}
describe
(
'
Test config
'
,
function
()
{
let
server
=
null
...
...
@@ -109,6 +115,14 @@ describe('Test config', function () {
checkInitialConfig
(
data
)
})
it
(
'
Should have valid index html tags (title, description...)
'
,
async
function
()
{
const
res
=
await
makeHTMLRequest
(
server
.
url
,
'
/videos/trending
'
)
const
description
=
'
PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser
'
+
'
with WebTorrent and Angular.
'
checkIndexTags
(
res
.
text
,
'
PeerTube
'
,
description
,
''
)
})
it
(
'
Should update the customized configuration
'
,
async
function
()
{
const
newCustomConfig
:
CustomConfig
=
{
instance
:
{
...
...
@@ -167,6 +181,12 @@ describe('Test config', function () {
checkUpdatedConfig
(
data
)
})
it
(
'
Should have valid index html updated tags (title, description...)
'
,
async
function
()
{
const
res
=
await
makeHTMLRequest
(
server
.
url
,
'
/videos/trending
'
)
checkIndexTags
(
res
.
text
,
'
PeerTube updated
'
,
'
my short description
'
,
'
body { background-color: red; }
'
)
})
it
(
'
Should have the configuration updated after a restart
'
,
async
function
()
{
this
.
timeout
(
10000
)
...
...
@@ -178,6 +198,10 @@ describe('Test config', function () {
const
data
=
res
.
body
checkUpdatedConfig
(
data
)
// Check HTML too
const
resHtml
=
await
makeHTMLRequest
(
server
.
url
,
'
/videos/trending
'
)
checkIndexTags
(
resHtml
.
text
,
'
PeerTube updated
'
,
'
my short description
'
,
'
body { background-color: red; }
'
)
})
it
(
'
Should fetch the about information
'
,
async
function
()
{
...
...
server/tests/real-world/populate-database.ts
View file @
e032aec9
...
...
@@ -19,6 +19,12 @@ start()
// ----------------------------------------------------------------------------
async
function
start
()
{
await
flushTests
()
console
.
log
(
'
Flushed tests.
'
)
const
server
=
await
runServer
(
6
)
process
.
on
(
'
exit
'
,
async
()
=>
{
killallServers
([
server
])
return
...
...
@@ -26,11 +32,6 @@ async function start () {
process
.
on
(
'
SIGINT
'
,
goodbye
)