2023-04-21 14:56:48 +00:00
|
|
|
import type { RegisterServerOptions, MVideoThumbnail, SettingEntries } from '@peertube/peertube-types'
|
2022-08-23 18:34:03 +00:00
|
|
|
import type { Router, Request, Response, NextFunction } from 'express'
|
2021-11-18 10:08:12 +00:00
|
|
|
import type {
|
2022-10-10 16:08:20 +00:00
|
|
|
ProsodyListRoomsResult, ProsodyListRoomsResultRoom
|
2021-11-18 10:08:12 +00:00
|
|
|
} from '../../../shared/lib/types'
|
2022-08-23 18:34:03 +00:00
|
|
|
import { createProxyServer } from 'http-proxy'
|
2022-10-13 16:34:41 +00:00
|
|
|
import {
|
2023-04-19 17:07:08 +00:00
|
|
|
RegisterServerOptionsV5, getBaseRouterRoute, getBaseStaticRoute, isUserAdmin
|
2022-10-13 16:34:41 +00:00
|
|
|
} from '../helpers'
|
2021-05-03 18:37:23 +00:00
|
|
|
import { asyncMiddleware } from '../middlewares/async'
|
2021-05-06 11:31:55 +00:00
|
|
|
import { getProsodyDomain } from '../prosody/config/domain'
|
2021-06-12 01:52:45 +00:00
|
|
|
import { getAPIKey } from '../apikey'
|
2021-08-05 16:25:27 +00:00
|
|
|
import { getChannelInfosById, getChannelNameById } from '../database/channel'
|
2021-11-19 15:45:10 +00:00
|
|
|
import { isAutoColorsAvailable, areAutoColorsValid, AutoColors } from '../../../shared/lib/autocolors'
|
2023-04-19 17:07:08 +00:00
|
|
|
import { getBoshUri, getWSUri } from '../uri/webchat'
|
2023-04-21 14:56:48 +00:00
|
|
|
import { getVideoLiveChatInfos } from '../federation/storage'
|
2023-05-24 13:09:56 +00:00
|
|
|
import { LiveChatJSONLDAttributeV1 } from '../federation/types'
|
|
|
|
import {
|
|
|
|
anonymousConnectionInfos, compatibleRemoteAuthenticatedConnectionEnabled
|
|
|
|
} from '../federation/connection-infos'
|
2023-05-24 14:09:55 +00:00
|
|
|
import { fetchMissingRemoteServerInfos } from '../federation/fetch-infos'
|
2021-04-08 00:43:13 +00:00
|
|
|
import * as path from 'path'
|
2021-06-12 01:52:45 +00:00
|
|
|
const got = require('got')
|
2021-04-16 11:42:07 +00:00
|
|
|
|
2021-04-08 00:43:13 +00:00
|
|
|
const fs = require('fs').promises
|
2021-04-15 10:17:08 +00:00
|
|
|
|
2022-08-23 18:34:03 +00:00
|
|
|
interface ProsodyProxyInfo {
|
2021-07-20 00:52:58 +00:00
|
|
|
host: string
|
|
|
|
port: string
|
|
|
|
}
|
2022-08-23 18:34:03 +00:00
|
|
|
let currentProsodyProxyInfo: ProsodyProxyInfo | null = null
|
|
|
|
let currentHttpBindProxy: ReturnType<typeof createProxyServer> | null = null
|
2022-08-24 15:55:24 +00:00
|
|
|
let currentWebsocketProxy: ReturnType<typeof createProxyServer> | null = null
|
2023-05-19 10:52:52 +00:00
|
|
|
let currentS2SWebsocketProxy: ReturnType<typeof createProxyServer> | null = null
|
2021-04-15 10:17:08 +00:00
|
|
|
|
2022-10-13 16:34:41 +00:00
|
|
|
async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Router> {
|
2021-04-15 10:17:08 +00:00
|
|
|
const {
|
|
|
|
getRouter,
|
2022-10-13 16:34:41 +00:00
|
|
|
registerWebSocketRoute,
|
2021-04-15 10:17:08 +00:00
|
|
|
peertubeHelpers,
|
|
|
|
settingsManager
|
|
|
|
} = options
|
2021-04-08 00:43:13 +00:00
|
|
|
|
2021-04-09 17:29:44 +00:00
|
|
|
const converseJSIndex = await fs.readFile(path.resolve(__dirname, '../../conversejs/index.html'))
|
2021-04-08 00:43:13 +00:00
|
|
|
|
2021-04-15 10:17:08 +00:00
|
|
|
const router: Router = getRouter()
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
2021-08-05 16:25:27 +00:00
|
|
|
router.get('/room/:roomKey', asyncMiddleware(
|
2021-05-03 18:37:23 +00:00
|
|
|
async (req: Request, res: Response, _next: NextFunction): Promise<void> => {
|
2021-05-07 14:45:55 +00:00
|
|
|
res.removeHeader('X-Frame-Options') // this route can be opened in an iframe
|
|
|
|
|
2021-08-05 16:25:27 +00:00
|
|
|
const roomKey = req.params.roomKey
|
2021-04-08 00:43:13 +00:00
|
|
|
const settings = await settingsManager.getSettings([
|
2021-11-18 10:08:12 +00:00
|
|
|
'prosody-room-type',
|
2022-12-08 10:25:57 +00:00
|
|
|
'disable-websocket',
|
2023-04-21 14:56:48 +00:00
|
|
|
'converse-theme', 'converse-autocolors',
|
2023-05-04 17:14:23 +00:00
|
|
|
'federation-no-remote-chat',
|
|
|
|
'prosody-room-allow-s2s'
|
2021-04-08 00:43:13 +00:00
|
|
|
])
|
|
|
|
|
2022-01-07 18:20:28 +00:00
|
|
|
let autoViewerMode: boolean = false
|
2022-01-04 16:42:03 +00:00
|
|
|
let forceReadonly: 'true' | 'false' | 'noscroll' = 'false'
|
2021-11-18 10:08:12 +00:00
|
|
|
let converseJSTheme: string = settings['converse-theme'] as string
|
2022-01-16 16:50:11 +00:00
|
|
|
let transparent: boolean = false
|
2021-11-18 10:08:12 +00:00
|
|
|
if (!/^\w+$/.test(converseJSTheme)) {
|
|
|
|
converseJSTheme = 'peertube'
|
|
|
|
}
|
2022-10-13 16:34:41 +00:00
|
|
|
|
2023-01-11 17:05:18 +00:00
|
|
|
const authenticationUrl = options.peertubeHelpers.config.getWebserverUrl() +
|
2022-10-10 16:08:20 +00:00
|
|
|
getBaseRouterRoute(options) +
|
|
|
|
'api/auth'
|
|
|
|
if (req.query._readonly === 'true') {
|
|
|
|
forceReadonly = 'true'
|
|
|
|
} else if (req.query._readonly === 'noscroll') {
|
|
|
|
forceReadonly = 'noscroll'
|
2021-04-14 16:47:23 +00:00
|
|
|
} else {
|
2022-10-10 16:08:20 +00:00
|
|
|
autoViewerMode = true // auto join the chat in viewer mode, if not logged in
|
|
|
|
}
|
|
|
|
if (req.query._transparent === 'true') {
|
|
|
|
transparent = true
|
2021-04-08 00:43:13 +00:00
|
|
|
}
|
|
|
|
|
2021-08-05 16:25:27 +00:00
|
|
|
let video: MVideoThumbnail | undefined
|
|
|
|
let channelId: number
|
2023-05-24 13:09:56 +00:00
|
|
|
let remoteChatInfos: LiveChatJSONLDAttributeV1 | undefined
|
2021-08-05 16:25:27 +00:00
|
|
|
const channelMatches = roomKey.match(/^channel\.(\d+)$/)
|
|
|
|
if (channelMatches?.[1]) {
|
|
|
|
channelId = parseInt(channelMatches[1])
|
2022-10-10 16:08:20 +00:00
|
|
|
// Here we are on a channel room...
|
2021-08-05 16:25:27 +00:00
|
|
|
const channelInfos = await getChannelInfosById(options, channelId)
|
|
|
|
if (!channelInfos) {
|
|
|
|
throw new Error('Channel not found')
|
|
|
|
}
|
|
|
|
channelId = channelInfos.id
|
|
|
|
} else {
|
|
|
|
const uuid = roomKey // must be a video UUID.
|
|
|
|
video = await peertubeHelpers.videos.loadByIdOrUUID(uuid)
|
|
|
|
if (!video) {
|
2023-04-21 14:56:48 +00:00
|
|
|
res.status(404)
|
|
|
|
res.send('Not found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (video.remote) {
|
|
|
|
remoteChatInfos = settings['federation-no-remote-chat'] ? false : await getVideoLiveChatInfos(options, video)
|
|
|
|
if (!remoteChatInfos) {
|
|
|
|
res.status(404)
|
|
|
|
res.send('Not found')
|
|
|
|
return
|
|
|
|
}
|
2021-08-05 16:25:27 +00:00
|
|
|
}
|
|
|
|
channelId = video.channelId
|
2021-04-08 00:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let page = '' + (converseJSIndex as string)
|
2021-05-18 16:17:13 +00:00
|
|
|
const baseStaticUrl = getBaseStaticRoute(options)
|
|
|
|
page = page.replace(/{{BASE_STATIC_URL}}/g, baseStaticUrl)
|
2023-04-21 14:56:48 +00:00
|
|
|
|
2023-05-04 17:14:23 +00:00
|
|
|
const prosodyDomain = await getProsodyDomain(options)
|
|
|
|
const localAnonymousJID = 'anon.' + prosodyDomain
|
|
|
|
const localBoshUri = getBoshUri(options)
|
|
|
|
const localWsUri = settings['disable-websocket']
|
|
|
|
? ''
|
|
|
|
: (getWSUri(options) ?? '')
|
|
|
|
|
|
|
|
let remoteConnectionInfos: WCRemoteConnectionInfos | undefined
|
|
|
|
let roomJID: string
|
2023-04-21 14:56:48 +00:00
|
|
|
if (video?.remote) {
|
2023-05-24 14:55:03 +00:00
|
|
|
const canWebsocketS2S = !settings['federation-no-remote-chat'] && !settings['disable-websocket']
|
2023-05-24 13:09:56 +00:00
|
|
|
const canDirectS2S = !settings['federation-no-remote-chat'] && !!settings['prosody-room-allow-s2s']
|
|
|
|
remoteConnectionInfos = await _remoteConnectionInfos(remoteChatInfos ?? false, canWebsocketS2S, canDirectS2S)
|
2023-05-04 17:14:23 +00:00
|
|
|
if (!remoteConnectionInfos) {
|
|
|
|
res.status(404)
|
|
|
|
res.send('No compatible way to connect to remote chat')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
roomJID = remoteConnectionInfos.roomJID
|
2023-04-21 14:56:48 +00:00
|
|
|
} else {
|
2023-05-04 17:14:23 +00:00
|
|
|
roomJID = await _localRoomJID(
|
2023-04-21 14:56:48 +00:00
|
|
|
options,
|
|
|
|
settings,
|
2023-05-04 17:14:23 +00:00
|
|
|
prosodyDomain,
|
2023-04-21 14:56:48 +00:00
|
|
|
roomKey,
|
|
|
|
video,
|
|
|
|
channelId,
|
|
|
|
req.query.forcetype === '1'
|
|
|
|
)
|
2021-08-05 16:25:27 +00:00
|
|
|
}
|
2021-11-19 15:45:10 +00:00
|
|
|
|
2023-05-04 17:14:23 +00:00
|
|
|
page = page.replace(/{{IS_REMOTE_CHAT}}/g, video?.remote ? 'true' : 'false')
|
|
|
|
page = page.replace(/{{LOCAL_ANONYMOUS_JID}}/g, localAnonymousJID)
|
|
|
|
page = page.replace(/{{REMOTE_ANONYMOUS_JID}}/g, remoteConnectionInfos?.anonymous?.userJID ?? '')
|
2023-04-21 14:56:48 +00:00
|
|
|
|
2021-11-19 15:45:10 +00:00
|
|
|
let autocolorsStyles = ''
|
|
|
|
if (
|
|
|
|
settings['converse-autocolors'] &&
|
2022-10-10 16:08:20 +00:00
|
|
|
isAutoColorsAvailable(settings['converse-theme'] as string)
|
2021-11-19 15:45:10 +00:00
|
|
|
) {
|
|
|
|
peertubeHelpers.logger.debug('Trying to load AutoColors...')
|
|
|
|
const autocolors: AutoColors = {
|
|
|
|
mainForeground: req.query._ac_mainForeground?.toString() ?? '',
|
|
|
|
mainBackground: req.query._ac_mainBackground?.toString() ?? '',
|
|
|
|
greyForeground: req.query._ac_greyForeground?.toString() ?? '',
|
|
|
|
greyBackground: req.query._ac_greyBackground?.toString() ?? '',
|
|
|
|
menuForeground: req.query._ac_menuForeground?.toString() ?? '',
|
|
|
|
menuBackground: req.query._ac_menuBackground?.toString() ?? '',
|
|
|
|
inputForeground: req.query._ac_inputForeground?.toString() ?? '',
|
|
|
|
inputBackground: req.query._ac_inputBackground?.toString() ?? '',
|
|
|
|
buttonForeground: req.query._ac_buttonForeground?.toString() ?? '',
|
|
|
|
buttonBackground: req.query._ac_buttonBackground?.toString() ?? '',
|
|
|
|
link: req.query._ac_link?.toString() ?? '',
|
|
|
|
linkHover: req.query._ac_linkHover?.toString() ?? ''
|
|
|
|
}
|
2022-11-01 14:17:21 +00:00
|
|
|
if (!Object.values(autocolors).find(c => c !== '')) {
|
|
|
|
peertubeHelpers.logger.debug('All AutoColors are empty.')
|
2021-11-19 15:45:10 +00:00
|
|
|
} else {
|
2022-11-01 14:17:21 +00:00
|
|
|
const autoColorsTest = areAutoColorsValid(autocolors)
|
|
|
|
if (autoColorsTest === true) {
|
|
|
|
autocolorsStyles = `
|
|
|
|
<style>
|
|
|
|
:root {
|
|
|
|
--peertube-main-foreground: ${autocolors.mainForeground};
|
|
|
|
--peertube-main-background: ${autocolors.mainBackground};
|
|
|
|
--peertube-grey-foreground: ${autocolors.greyForeground};
|
|
|
|
--peertube-grey-background: ${autocolors.greyBackground};
|
|
|
|
--peertube-menu-foreground: ${autocolors.menuForeground};
|
|
|
|
--peertube-menu-background: ${autocolors.menuBackground};
|
|
|
|
--peertube-input-foreground: ${autocolors.inputForeground};
|
|
|
|
--peertube-input-background: ${autocolors.inputBackground};
|
|
|
|
--peertube-button-foreground: ${autocolors.buttonForeground};
|
|
|
|
--peertube-button-background: ${autocolors.buttonBackground};
|
|
|
|
--peertube-link: ${autocolors.link};
|
|
|
|
--peertube-link-hover: ${autocolors.linkHover};
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
`
|
|
|
|
} else {
|
|
|
|
peertubeHelpers.logger.error('Provided AutoColors are invalid.', autoColorsTest)
|
|
|
|
}
|
2021-11-19 15:45:10 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
peertubeHelpers.logger.debug('No AutoColors.')
|
|
|
|
}
|
2021-12-14 12:02:15 +00:00
|
|
|
|
2021-08-03 22:22:19 +00:00
|
|
|
// ... then inject it in the page.
|
2023-05-04 17:14:23 +00:00
|
|
|
page = page.replace(/{{ROOM}}/g, roomJID)
|
|
|
|
page = page.replace(/{{LOCAL_BOSH_SERVICE_URL}}/g, localBoshUri)
|
|
|
|
page = page.replace(/{{LOCAL_WS_SERVICE_URL}}/g, localWsUri ?? '')
|
|
|
|
page = page.replace(/{{REMOTE_BOSH_SERVICE_URL}}/g, remoteConnectionInfos?.anonymous?.boshUri ?? '')
|
|
|
|
page = page.replace(/{{REMOTE_WS_SERVICE_URL}}/g, remoteConnectionInfos?.anonymous?.wsUri ?? '')
|
|
|
|
page = page.replace(/{{REMOTE_ANONYMOUS_XMPP_SERVER}}/g, remoteConnectionInfos?.anonymous ? 'true' : 'false')
|
|
|
|
page = page.replace(
|
|
|
|
/{{REMOTE_AUTHENTICATED_XMPP_SERVER}}/g,
|
2023-05-19 10:52:52 +00:00
|
|
|
remoteConnectionInfos?.authenticated ? 'true' : 'false'
|
2023-05-04 17:14:23 +00:00
|
|
|
)
|
2021-05-04 11:00:44 +00:00
|
|
|
page = page.replace(/{{AUTHENTICATION_URL}}/g, authenticationUrl)
|
2022-01-07 18:20:28 +00:00
|
|
|
page = page.replace(/{{AUTOVIEWERMODE}}/g, autoViewerMode ? 'true' : 'false')
|
2021-11-18 10:08:12 +00:00
|
|
|
page = page.replace(/{{CONVERSEJS_THEME}}/g, converseJSTheme)
|
2021-11-19 15:45:10 +00:00
|
|
|
page = page.replace(/{{CONVERSEJS_AUTOCOLORS}}/g, autocolorsStyles)
|
2022-01-04 16:42:03 +00:00
|
|
|
page = page.replace(/{{FORCEREADONLY}}/g, forceReadonly)
|
2022-01-16 16:50:11 +00:00
|
|
|
page = page.replace(/{{TRANSPARENT}}/g, transparent ? 'true' : 'false')
|
2021-04-08 00:43:13 +00:00
|
|
|
|
|
|
|
res.status(200)
|
|
|
|
res.type('html')
|
|
|
|
res.send(page)
|
|
|
|
}
|
2021-05-03 18:37:23 +00:00
|
|
|
))
|
2021-04-15 10:17:08 +00:00
|
|
|
|
2022-08-24 09:03:29 +00:00
|
|
|
await disableProxyRoute(options)
|
2022-08-25 10:31:45 +00:00
|
|
|
router.post('/http-bind',
|
2021-04-16 11:44:24 +00:00
|
|
|
(req: Request, res: Response, next: NextFunction) => {
|
2022-08-23 18:34:03 +00:00
|
|
|
try {
|
|
|
|
if (!currentHttpBindProxy) {
|
|
|
|
res.status(404)
|
|
|
|
res.send('Not found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.url = 'http-bind'
|
|
|
|
currentHttpBindProxy.web(req, res)
|
|
|
|
} catch (err) {
|
|
|
|
next(err)
|
|
|
|
}
|
2021-04-16 11:44:24 +00:00
|
|
|
}
|
|
|
|
)
|
2023-04-21 16:49:15 +00:00
|
|
|
// We should also forward OPTIONS request, for CORS.
|
|
|
|
router.options('/http-bind',
|
|
|
|
(req: Request, res: Response, next: NextFunction) => {
|
|
|
|
try {
|
|
|
|
if (!currentHttpBindProxy) {
|
|
|
|
res.status(404)
|
|
|
|
res.send('Not found')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
req.url = 'http-bind'
|
|
|
|
currentHttpBindProxy.web(req, res)
|
|
|
|
} catch (err) {
|
|
|
|
next(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
2022-08-25 10:31:45 +00:00
|
|
|
|
2022-10-13 16:34:41 +00:00
|
|
|
// Peertube >=5.0.0: Adding the websocket route.
|
|
|
|
if (registerWebSocketRoute) {
|
|
|
|
registerWebSocketRoute({
|
|
|
|
route: '/xmpp-websocket',
|
|
|
|
handler: (request, socket, head) => {
|
2023-05-24 13:09:56 +00:00
|
|
|
try {
|
|
|
|
if (!currentWebsocketProxy) {
|
|
|
|
peertubeHelpers.logger.error('There is no current websocket proxy, should not get here.')
|
|
|
|
// no need to close the socket, Peertube will
|
|
|
|
// (see https://github.com/Chocobozzz/PeerTube/issues/5752#issuecomment-1510870894)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
currentWebsocketProxy.ws(request, socket, head)
|
|
|
|
} catch (err) {
|
|
|
|
peertubeHelpers.logger.error('Got an error when trying to connect to S2S', err)
|
2022-08-25 10:31:45 +00:00
|
|
|
}
|
2022-08-24 15:55:24 +00:00
|
|
|
}
|
2022-10-13 16:34:41 +00:00
|
|
|
})
|
2023-05-19 10:52:52 +00:00
|
|
|
|
|
|
|
registerWebSocketRoute({
|
|
|
|
route: '/xmpp-websocket-s2s',
|
2023-05-24 13:09:56 +00:00
|
|
|
handler: async (request, socket, head) => {
|
|
|
|
try {
|
|
|
|
if (!currentS2SWebsocketProxy) {
|
|
|
|
peertubeHelpers.logger.error('There is no current websocket s2s proxy, should not get here.')
|
|
|
|
// no need to close the socket, Peertube will
|
|
|
|
// (see https://github.com/Chocobozzz/PeerTube/issues/5752#issuecomment-1510870894)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// If the incomming request is from a remote Peertube instance, we must ensure that we know
|
|
|
|
// how to connect to it using Websocket S2S (for the dialback mecanism).
|
|
|
|
const remoteInstanceUrl = request.headers['peertube-livechat-ws-s2s-instance-url']
|
2023-05-24 14:09:55 +00:00
|
|
|
if (remoteInstanceUrl && (typeof remoteInstanceUrl === 'string')) {
|
|
|
|
// Note: fetchMissingRemoteServerInfos will store the information,
|
2023-05-24 13:09:56 +00:00
|
|
|
// so that the Prosody mod_s2s_peertubelivechat module can access them.
|
2023-05-24 14:09:55 +00:00
|
|
|
// We dont need to read the result.
|
|
|
|
await fetchMissingRemoteServerInfos(options, remoteInstanceUrl)
|
2023-05-24 13:09:56 +00:00
|
|
|
}
|
|
|
|
currentS2SWebsocketProxy.ws(request, socket, head)
|
|
|
|
} catch (err) {
|
|
|
|
peertubeHelpers.logger.error('Got an error when trying to connect to Websocket S2S', err)
|
2023-05-19 10:52:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2022-10-13 16:34:41 +00:00
|
|
|
}
|
2021-06-12 01:52:45 +00:00
|
|
|
|
|
|
|
router.get('/prosody-list-rooms', asyncMiddleware(
|
|
|
|
async (req: Request, res: Response, _next: NextFunction) => {
|
|
|
|
if (!res.locals.authenticated) {
|
|
|
|
res.sendStatus(403)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (!await isUserAdmin(options, res)) {
|
|
|
|
res.sendStatus(403)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-08-23 18:34:03 +00:00
|
|
|
if (!currentProsodyProxyInfo) {
|
2021-06-12 01:52:45 +00:00
|
|
|
throw new Error('It seems that prosody is not binded... Cant list rooms.')
|
|
|
|
}
|
2022-08-23 18:34:03 +00:00
|
|
|
const apiUrl = `http://localhost:${currentProsodyProxyInfo.port}/peertubelivechat_list_rooms/list-rooms`
|
2021-06-12 01:52:45 +00:00
|
|
|
peertubeHelpers.logger.debug('Calling list rooms API on url: ' + apiUrl)
|
|
|
|
const rooms = await got(apiUrl, {
|
|
|
|
method: 'GET',
|
|
|
|
headers: {
|
2021-07-20 00:52:58 +00:00
|
|
|
authorization: 'Bearer ' + await getAPIKey(options),
|
2022-08-23 18:34:03 +00:00
|
|
|
host: currentProsodyProxyInfo.host
|
2021-06-12 01:52:45 +00:00
|
|
|
},
|
|
|
|
responseType: 'json',
|
|
|
|
resolveBodyOnly: true
|
|
|
|
})
|
|
|
|
|
2021-08-05 16:25:27 +00:00
|
|
|
if (Array.isArray(rooms)) {
|
|
|
|
for (let i = 0; i < rooms.length; i++) {
|
|
|
|
const room: ProsodyListRoomsResultRoom = rooms[i]
|
|
|
|
const matches = room.localpart.match(/^channel\.(\d+)$/)
|
|
|
|
if (matches?.[1]) {
|
|
|
|
const channelId = parseInt(matches[1])
|
|
|
|
const channelInfos = await getChannelInfosById(options, channelId)
|
|
|
|
if (channelInfos) {
|
|
|
|
room.channel = {
|
|
|
|
id: channelInfos.id,
|
|
|
|
name: channelInfos.name,
|
|
|
|
displayName: channelInfos.displayName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-12 01:52:45 +00:00
|
|
|
res.status(200)
|
|
|
|
const r: ProsodyListRoomsResult = {
|
|
|
|
ok: true,
|
|
|
|
rooms: rooms
|
|
|
|
}
|
|
|
|
res.json(r)
|
|
|
|
}
|
|
|
|
))
|
|
|
|
|
2021-04-09 17:29:44 +00:00
|
|
|
return router
|
|
|
|
}
|
|
|
|
|
2022-08-24 09:03:29 +00:00
|
|
|
async function disableProxyRoute ({ peertubeHelpers }: RegisterServerOptions): Promise<void> {
|
2022-08-24 10:24:35 +00:00
|
|
|
// Note: I tried to promisify the httpbind proxy closing (by waiting for the callback call).
|
|
|
|
// But this seems to never happen, and stucked the plugin uninstallation.
|
|
|
|
// So I don't wait.
|
|
|
|
try {
|
|
|
|
currentProsodyProxyInfo = null
|
|
|
|
if (currentHttpBindProxy) {
|
2022-08-24 15:55:24 +00:00
|
|
|
peertubeHelpers.logger.info('Closing the http bind proxy...')
|
2022-08-24 10:24:35 +00:00
|
|
|
currentHttpBindProxy.close()
|
2022-08-24 09:03:29 +00:00
|
|
|
currentHttpBindProxy = null
|
|
|
|
}
|
2022-08-24 15:55:24 +00:00
|
|
|
|
|
|
|
if (currentWebsocketProxy) {
|
|
|
|
peertubeHelpers.logger.info('Closing the websocket proxy...')
|
|
|
|
currentWebsocketProxy.close()
|
|
|
|
currentWebsocketProxy = null
|
|
|
|
}
|
2023-05-19 10:52:52 +00:00
|
|
|
if (currentS2SWebsocketProxy) {
|
|
|
|
peertubeHelpers.logger.info('Closing the s2s websocket proxy...')
|
|
|
|
currentS2SWebsocketProxy.close()
|
|
|
|
currentS2SWebsocketProxy = null
|
|
|
|
}
|
2022-08-24 10:24:35 +00:00
|
|
|
} catch (err) {
|
|
|
|
peertubeHelpers.logger.error('Seems that the http bind proxy close has failed: ' + (err as string))
|
|
|
|
}
|
2022-08-23 18:34:03 +00:00
|
|
|
}
|
|
|
|
|
2022-08-24 09:03:29 +00:00
|
|
|
async function enableProxyRoute (
|
2021-07-20 00:52:58 +00:00
|
|
|
{ peertubeHelpers }: RegisterServerOptions,
|
2022-08-23 18:34:03 +00:00
|
|
|
prosodyProxyInfo: ProsodyProxyInfo
|
2022-08-24 09:03:29 +00:00
|
|
|
): Promise<void> {
|
2021-04-15 10:17:08 +00:00
|
|
|
const logger = peertubeHelpers.logger
|
2022-08-23 18:34:03 +00:00
|
|
|
if (!/^\d+$/.test(prosodyProxyInfo.port)) {
|
|
|
|
logger.error(`Port '${prosodyProxyInfo.port}' is not valid. Aborting.`)
|
|
|
|
return
|
2021-04-15 10:17:08 +00:00
|
|
|
}
|
2022-08-23 18:34:03 +00:00
|
|
|
currentProsodyProxyInfo = prosodyProxyInfo
|
2022-08-24 10:24:35 +00:00
|
|
|
|
|
|
|
logger.info('Creating a new http bind proxy')
|
2022-08-23 18:34:03 +00:00
|
|
|
currentHttpBindProxy = createProxyServer({
|
|
|
|
target: 'http://localhost:' + prosodyProxyInfo.port + '/http-bind',
|
|
|
|
ignorePath: true
|
|
|
|
})
|
2022-08-24 10:24:35 +00:00
|
|
|
currentHttpBindProxy.on('error', (err, req, res) => {
|
|
|
|
// We must handle errors, otherwise Peertube server crashes!
|
|
|
|
logger.error(
|
2022-08-24 15:55:24 +00:00
|
|
|
'The http bind proxy got an error ' +
|
2022-08-24 10:24:35 +00:00
|
|
|
'(this can be normal if you updated/uninstalled the plugin, or shutdowned peertube while users were chatting): ' +
|
2022-12-08 10:25:57 +00:00
|
|
|
err.message
|
2022-08-24 10:24:35 +00:00
|
|
|
)
|
|
|
|
if ('writeHead' in res) {
|
|
|
|
res.writeHead(500)
|
|
|
|
}
|
|
|
|
res.end('')
|
|
|
|
})
|
|
|
|
currentHttpBindProxy.on('close', () => {
|
|
|
|
logger.info('Got a close event for the http bind proxy')
|
|
|
|
})
|
2022-08-24 15:55:24 +00:00
|
|
|
|
|
|
|
logger.info('Creating a new websocket proxy')
|
|
|
|
currentWebsocketProxy = createProxyServer({
|
|
|
|
target: 'http://localhost:' + prosodyProxyInfo.port + '/xmpp-websocket',
|
|
|
|
ignorePath: true,
|
|
|
|
ws: true
|
|
|
|
})
|
|
|
|
currentWebsocketProxy.on('error', (err, req, res) => {
|
|
|
|
// We must handle errors, otherwise Peertube server crashes!
|
|
|
|
logger.error(
|
|
|
|
'The websocket proxy got an error ' +
|
|
|
|
'(this can be normal if you updated/uninstalled the plugin, or shutdowned peertube while users were chatting): ' +
|
2022-12-08 10:25:57 +00:00
|
|
|
err.message
|
2022-08-24 15:55:24 +00:00
|
|
|
)
|
|
|
|
if ('writeHead' in res) {
|
|
|
|
res.writeHead(500)
|
|
|
|
}
|
|
|
|
res.end('')
|
|
|
|
})
|
|
|
|
currentWebsocketProxy.on('close', () => {
|
|
|
|
logger.info('Got a close event for the websocket proxy')
|
|
|
|
})
|
2023-05-19 10:52:52 +00:00
|
|
|
|
|
|
|
logger.info('Creating a new s2s websocket proxy')
|
|
|
|
currentS2SWebsocketProxy = createProxyServer({
|
|
|
|
target: 'http://localhost:' + prosodyProxyInfo.port + '/xmpp-websocket-s2s',
|
|
|
|
ignorePath: true,
|
|
|
|
ws: true
|
|
|
|
})
|
|
|
|
currentS2SWebsocketProxy.on('error', (err, req, res) => {
|
|
|
|
// We must handle errors, otherwise Peertube server crashes!
|
|
|
|
logger.error(
|
|
|
|
'The s2s websocket proxy got an error ' +
|
|
|
|
'(this can be normal if you updated/uninstalled the plugin, or shutdowned peertube while users were chatting): ' +
|
|
|
|
err.message
|
|
|
|
)
|
|
|
|
if ('writeHead' in res) {
|
|
|
|
res.writeHead(500)
|
|
|
|
}
|
|
|
|
res.end('')
|
|
|
|
})
|
|
|
|
currentS2SWebsocketProxy.on('close', () => {
|
|
|
|
logger.info('Got a close event for the s2s websocket proxy')
|
|
|
|
})
|
2021-04-15 10:17:08 +00:00
|
|
|
}
|
|
|
|
|
2023-05-04 17:14:23 +00:00
|
|
|
interface WCRemoteConnectionInfos {
|
2023-04-21 14:56:48 +00:00
|
|
|
roomJID: string
|
2023-05-04 17:14:23 +00:00
|
|
|
anonymous?: {
|
|
|
|
userJID: string
|
|
|
|
boshUri: string
|
|
|
|
wsUri?: string
|
|
|
|
}
|
|
|
|
authenticated?: boolean
|
2023-04-21 14:56:48 +00:00
|
|
|
}
|
|
|
|
|
2023-05-24 13:09:56 +00:00
|
|
|
async function _remoteConnectionInfos (
|
|
|
|
remoteChatInfos: LiveChatJSONLDAttributeV1,
|
|
|
|
canWebsocketS2S: boolean,
|
|
|
|
canDirectS2S: boolean
|
|
|
|
): Promise<WCRemoteConnectionInfos> {
|
2023-04-21 14:56:48 +00:00
|
|
|
if (!remoteChatInfos) { throw new Error('Should have remote chat infos for remote videos') }
|
2023-05-04 17:14:23 +00:00
|
|
|
if (remoteChatInfos.type !== 'xmpp') { throw new Error('Should have remote xmpp chat infos for remote videos') }
|
|
|
|
const connectionInfos: WCRemoteConnectionInfos = {
|
|
|
|
roomJID: remoteChatInfos.jid
|
|
|
|
}
|
2023-05-24 13:09:56 +00:00
|
|
|
if (compatibleRemoteAuthenticatedConnectionEnabled(remoteChatInfos, canWebsocketS2S, canDirectS2S)) {
|
2023-05-04 17:14:23 +00:00
|
|
|
connectionInfos.authenticated = true
|
2023-04-21 14:56:48 +00:00
|
|
|
}
|
2023-05-04 17:14:23 +00:00
|
|
|
const anonymousCI = anonymousConnectionInfos(remoteChatInfos ?? false)
|
|
|
|
if (anonymousCI?.boshUri) {
|
|
|
|
connectionInfos.anonymous = {
|
|
|
|
userJID: anonymousCI.userJID,
|
|
|
|
boshUri: anonymousCI.boshUri,
|
|
|
|
wsUri: anonymousCI.wsUri
|
|
|
|
}
|
2023-04-21 14:56:48 +00:00
|
|
|
}
|
2023-05-04 17:14:23 +00:00
|
|
|
return connectionInfos
|
2023-04-21 14:56:48 +00:00
|
|
|
}
|
|
|
|
|
2023-05-04 17:14:23 +00:00
|
|
|
async function _localRoomJID (
|
2023-04-21 14:56:48 +00:00
|
|
|
options: RegisterServerOptions,
|
|
|
|
settings: SettingEntries,
|
2023-05-04 17:14:23 +00:00
|
|
|
prosodyDomain: string,
|
2023-04-21 14:56:48 +00:00
|
|
|
roomKey: string,
|
|
|
|
video: MVideoThumbnail | undefined,
|
|
|
|
channelId: number,
|
|
|
|
forceType: boolean
|
2023-05-04 17:14:23 +00:00
|
|
|
): Promise<string> {
|
2023-04-21 14:56:48 +00:00
|
|
|
// Computing the room name...
|
|
|
|
let room: string
|
|
|
|
if (forceType) {
|
|
|
|
// We come from the room list in the settings page.
|
|
|
|
// Here we don't read the prosody-room-type settings,
|
|
|
|
// but use the roomKey format.
|
|
|
|
// NB: there is no extra security. Any user can add this parameter.
|
|
|
|
// This is not an issue: the setting will be tested at the room creation.
|
|
|
|
// No room can be created in the wrong mode.
|
|
|
|
if (/^channel\.\d+$/.test(roomKey)) {
|
|
|
|
room = 'channel.{{CHANNEL_ID}}@room.' + prosodyDomain
|
|
|
|
} else {
|
|
|
|
room = '{{VIDEO_UUID}}@room.' + prosodyDomain
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (settings['prosody-room-type'] === 'channel') {
|
|
|
|
room = 'channel.{{CHANNEL_ID}}@room.' + prosodyDomain
|
|
|
|
} else {
|
|
|
|
room = '{{VIDEO_UUID}}@room.' + prosodyDomain
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (room.includes('{{VIDEO_UUID}}')) {
|
|
|
|
if (!video) {
|
|
|
|
throw new Error('Missing video')
|
|
|
|
}
|
|
|
|
room = room.replace(/{{VIDEO_UUID}}/g, video.uuid)
|
|
|
|
}
|
|
|
|
room = room.replace(/{{CHANNEL_ID}}/g, `${channelId}`)
|
|
|
|
if (room.includes('{{CHANNEL_NAME}}')) {
|
|
|
|
const channelName = await getChannelNameById(options, channelId)
|
|
|
|
if (channelName === null) {
|
|
|
|
throw new Error('Channel not found')
|
|
|
|
}
|
|
|
|
if (!/^[a-zA-Z0-9_.]+$/.test(channelName)) {
|
|
|
|
// FIXME: see if there is a response here https://github.com/Chocobozzz/PeerTube/issues/4301 for allowed chars
|
|
|
|
options.peertubeHelpers.logger.error(`Invalid channel name, contains unauthorized chars: '${channelName}'`)
|
|
|
|
throw new Error('Invalid channel name, contains unauthorized chars')
|
|
|
|
}
|
|
|
|
room = room.replace(/{{CHANNEL_NAME}}/g, channelName)
|
|
|
|
}
|
|
|
|
|
2023-05-04 17:14:23 +00:00
|
|
|
return room
|
2023-04-21 14:56:48 +00:00
|
|
|
}
|
|
|
|
|
2021-04-09 17:29:44 +00:00
|
|
|
export {
|
2021-04-15 10:17:08 +00:00
|
|
|
initWebchatRouter,
|
2022-08-23 18:34:03 +00:00
|
|
|
disableProxyRoute,
|
|
|
|
enableProxyRoute
|
2021-04-08 00:43:13 +00:00
|
|
|
}
|