Websocket for builtin Prosody. WIP
This commit is contained in:
parent
507da6e042
commit
6f8c7b8a93
@ -127,6 +127,13 @@ window.initConverse = async function initConverse ({
|
|||||||
body?.classList.add('livechat-transparent')
|
body?.classList.add('livechat-transparent')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (websocketServiceUrl?.startsWith('/')) {
|
||||||
|
websocketServiceUrl = new URL(
|
||||||
|
websocketServiceUrl,
|
||||||
|
(window.location.protocol === 'http:' ? 'ws://' : 'wss://') + window.location.host
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
|
||||||
const params: any = {
|
const params: any = {
|
||||||
assets_path: assetsPath,
|
assets_path: assetsPath,
|
||||||
|
|
||||||
|
@ -130,7 +130,7 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise<Prosod
|
|||||||
config.useAnonymous()
|
config.useAnonymous()
|
||||||
}
|
}
|
||||||
config.useHttpAuthentication(authApiUrl)
|
config.useHttpAuthentication(authApiUrl)
|
||||||
config.usePeertubeBosh(prosodyDomain, port)
|
config.usePeertubeBoshAndWebsocket(prosodyDomain, port)
|
||||||
config.useMucHttpDefault(roomApiUrl)
|
config.useMucHttpDefault(roomApiUrl)
|
||||||
|
|
||||||
if (enableC2s) {
|
if (enableC2s) {
|
||||||
|
@ -156,6 +156,7 @@ class ProsodyConfigContent {
|
|||||||
'uptime', // Report how long server has been running
|
'uptime', // Report how long server has been running
|
||||||
'ping', // Replies to XMPP pings with pongs
|
'ping', // Replies to XMPP pings with pongs
|
||||||
'bosh', // Enable BOSH clients, aka "Jabber over HTTP"
|
'bosh', // Enable BOSH clients, aka "Jabber over HTTP"
|
||||||
|
'websocket', // Enable Websocket clients
|
||||||
'posix' // POSIX functionality, sends server to background, enables syslog, etc.
|
'posix' // POSIX functionality, sends server to background, enables syslog, etc.
|
||||||
// 'pep', // Enables users to publish their avatar, mood, activity, playing music and more
|
// 'pep', // Enables users to publish their avatar, mood, activity, playing music and more
|
||||||
// 'vcard_legacy' // Conversion between legacy vCard and PEP Avatar, vcard
|
// 'vcard_legacy' // Conversion between legacy vCard and PEP Avatar, vcard
|
||||||
@ -200,7 +201,7 @@ class ProsodyConfigContent {
|
|||||||
this.authenticated.set('http_auth_url', url)
|
this.authenticated.set('http_auth_url', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
usePeertubeBosh (prosodyDomain: string, port: string): void {
|
usePeertubeBoshAndWebsocket (prosodyDomain: string, port: string): void {
|
||||||
this.global.set('c2s_require_encryption', false)
|
this.global.set('c2s_require_encryption', false)
|
||||||
this.global.set('interfaces', ['127.0.0.1', '::1'])
|
this.global.set('interfaces', ['127.0.0.1', '::1'])
|
||||||
this.global.set('c2s_ports', [])
|
this.global.set('c2s_ports', [])
|
||||||
@ -213,12 +214,14 @@ class ProsodyConfigContent {
|
|||||||
this.global.set('https_interfaces', ['127.0.0.1', '::1'])
|
this.global.set('https_interfaces', ['127.0.0.1', '::1'])
|
||||||
|
|
||||||
this.global.set('consider_bosh_secure', true)
|
this.global.set('consider_bosh_secure', true)
|
||||||
|
this.global.set('consider_websocket_secure', true)
|
||||||
|
|
||||||
if (this.anon) {
|
if (this.anon) {
|
||||||
this.anon.set('trusted_proxies', ['127.0.0.1', '::1'])
|
this.anon.set('trusted_proxies', ['127.0.0.1', '::1'])
|
||||||
this.anon.set('allow_anonymous_s2s', false)
|
this.anon.set('allow_anonymous_s2s', false)
|
||||||
this.anon.add('modules_enabled', 'http')
|
this.anon.add('modules_enabled', 'http')
|
||||||
this.anon.add('modules_enabled', 'bosh')
|
this.anon.add('modules_enabled', 'bosh')
|
||||||
|
this.anon.add('modules_enabled', 'websocket')
|
||||||
this.anon.set('http_host', prosodyDomain)
|
this.anon.set('http_host', prosodyDomain)
|
||||||
this.anon.set('http_external_url', 'http://' + prosodyDomain)
|
this.anon.set('http_external_url', 'http://' + prosodyDomain)
|
||||||
}
|
}
|
||||||
@ -232,6 +235,7 @@ class ProsodyConfigContent {
|
|||||||
this.authenticated.set('allow_anonymous_s2s', false)
|
this.authenticated.set('allow_anonymous_s2s', false)
|
||||||
this.authenticated.add('modules_enabled', 'http')
|
this.authenticated.add('modules_enabled', 'http')
|
||||||
this.authenticated.add('modules_enabled', 'bosh')
|
this.authenticated.add('modules_enabled', 'bosh')
|
||||||
|
this.authenticated.add('modules_enabled', 'websocket')
|
||||||
this.authenticated.set('http_host', prosodyDomain)
|
this.authenticated.set('http_host', prosodyDomain)
|
||||||
this.authenticated.set('http_external_url', 'http://' + prosodyDomain)
|
this.authenticated.set('http_external_url', 'http://' + prosodyDomain)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,12 @@ interface ProsodyProxyInfo {
|
|||||||
}
|
}
|
||||||
let currentProsodyProxyInfo: ProsodyProxyInfo | null = null
|
let currentProsodyProxyInfo: ProsodyProxyInfo | null = null
|
||||||
let currentHttpBindProxy: ReturnType<typeof createProxyServer> | null = null
|
let currentHttpBindProxy: ReturnType<typeof createProxyServer> | null = null
|
||||||
|
let currentWebsocketProxy: ReturnType<typeof createProxyServer> | null = null
|
||||||
|
interface CurrentWebsocketUpgradeEvent {
|
||||||
|
server: any
|
||||||
|
listener: Function
|
||||||
|
}
|
||||||
|
let currentWebsocketUpgradeEvent: CurrentWebsocketUpgradeEvent | null = null
|
||||||
|
|
||||||
async function initWebchatRouter (options: RegisterServerOptions): Promise<Router> {
|
async function initWebchatRouter (options: RegisterServerOptions): Promise<Router> {
|
||||||
const {
|
const {
|
||||||
@ -79,8 +85,9 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
|||||||
// which correspond to a path without the plugin version.
|
// which correspond to a path without the plugin version.
|
||||||
// We are doing this, so the path is predictible,
|
// We are doing this, so the path is predictible,
|
||||||
// and can be optimized in the nginx configuration (to bypass Peertube).
|
// and can be optimized in the nginx configuration (to bypass Peertube).
|
||||||
const boshUri = getBaseRouterCanonicalRoute(options) + 'webchat/http-bind'
|
const proxyBaseUri = getBaseRouterCanonicalRoute(options) + 'webchat/'
|
||||||
const wsUri = ''
|
const boshUri = proxyBaseUri + 'http-bind'
|
||||||
|
const wsUri = proxyBaseUri + 'xmpp-websocket'
|
||||||
authenticationUrl = options.peertubeHelpers.config.getWebserverUrl() +
|
authenticationUrl = options.peertubeHelpers.config.getWebserverUrl() +
|
||||||
getBaseRouterRoute(options) +
|
getBaseRouterRoute(options) +
|
||||||
'api/auth'
|
'api/auth'
|
||||||
@ -222,6 +229,21 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
router.all('/xmpp-websocket',
|
||||||
|
(req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
if (!currentWebsocketProxy) {
|
||||||
|
res.status(404)
|
||||||
|
res.send('Not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.url = 'xmpp-websocket'
|
||||||
|
currentWebsocketProxy.web(req, res)
|
||||||
|
} catch (err) {
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
router.get('/prosody-list-rooms', asyncMiddleware(
|
router.get('/prosody-list-rooms', asyncMiddleware(
|
||||||
async (req: Request, res: Response, _next: NextFunction) => {
|
async (req: Request, res: Response, _next: NextFunction) => {
|
||||||
@ -279,39 +301,6 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise<Route
|
|||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
// function changeHttpBindRoute (
|
|
||||||
// { peertubeHelpers }: RegisterServerOptions,
|
|
||||||
// prosodyHttpBindInfo: ProsodyHttpBindInfo | null
|
|
||||||
// ): void {
|
|
||||||
// const logger = peertubeHelpers.logger
|
|
||||||
// if (prosodyHttpBindInfo && !/^\d+$/.test(prosodyHttpBindInfo.port)) {
|
|
||||||
// logger.error(`Port '${prosodyHttpBindInfo.port}' is not valid. Replacing by null`)
|
|
||||||
// prosodyHttpBindInfo = null
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!prosodyHttpBindInfo) {
|
|
||||||
// logger.info('Changing http-bind port for null')
|
|
||||||
// currentProsodyHttpBindInfo = null
|
|
||||||
// httpBindRoute = (_req: Request, res: Response, _next: NextFunction) => {
|
|
||||||
// res.status(404)
|
|
||||||
// res.send('Not found')
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// logger.info('Changing http-bind port for ' + prosodyHttpBindInfo.port + ', on host ' + prosodyHttpBindInfo.host)
|
|
||||||
// const options: ProxyOptions = {
|
|
||||||
// https: false,
|
|
||||||
// proxyReqPathResolver: async (_req: Request): Promise<string> => {
|
|
||||||
// return '/http-bind' // should not be able to access anything else
|
|
||||||
// },
|
|
||||||
// // preserveHostHdr: true,
|
|
||||||
// parseReqBody: true // Note that setting this to false overrides reqAsBuffer and reqBodyEncoding below.
|
|
||||||
// // FIXME: should we remove cookies?
|
|
||||||
// }
|
|
||||||
// currentProsodyHttpBindInfo = prosodyHttpBindInfo
|
|
||||||
// httpBindRoute = proxy('http://localhost:' + prosodyHttpBindInfo.port, options)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
async function disableProxyRoute ({ peertubeHelpers }: RegisterServerOptions): Promise<void> {
|
async function disableProxyRoute ({ peertubeHelpers }: RegisterServerOptions): Promise<void> {
|
||||||
// Note: I tried to promisify the httpbind proxy closing (by waiting for the callback call).
|
// 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.
|
// But this seems to never happen, and stucked the plugin uninstallation.
|
||||||
@ -319,10 +308,19 @@ async function disableProxyRoute ({ peertubeHelpers }: RegisterServerOptions): P
|
|||||||
try {
|
try {
|
||||||
currentProsodyProxyInfo = null
|
currentProsodyProxyInfo = null
|
||||||
if (currentHttpBindProxy) {
|
if (currentHttpBindProxy) {
|
||||||
peertubeHelpers.logger.info('Closing the proxy...')
|
peertubeHelpers.logger.info('Closing the http bind proxy...')
|
||||||
currentHttpBindProxy.close()
|
currentHttpBindProxy.close()
|
||||||
currentHttpBindProxy = null
|
currentHttpBindProxy = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentWebsocketProxy) {
|
||||||
|
peertubeHelpers.logger.info('Closing the websocket proxy...')
|
||||||
|
currentWebsocketProxy.close()
|
||||||
|
currentWebsocketProxy = null
|
||||||
|
}
|
||||||
|
if (currentWebsocketUpgradeEvent) {
|
||||||
|
currentWebsocketUpgradeEvent.server.off('upgrade', currentWebsocketUpgradeEvent.listener)
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
peertubeHelpers.logger.error('Seems that the http bind proxy close has failed: ' + (err as string))
|
peertubeHelpers.logger.error('Seems that the http bind proxy close has failed: ' + (err as string))
|
||||||
}
|
}
|
||||||
@ -347,9 +345,9 @@ async function enableProxyRoute (
|
|||||||
currentHttpBindProxy.on('error', (err, req, res) => {
|
currentHttpBindProxy.on('error', (err, req, res) => {
|
||||||
// We must handle errors, otherwise Peertube server crashes!
|
// We must handle errors, otherwise Peertube server crashes!
|
||||||
logger.error(
|
logger.error(
|
||||||
'The proxy got an error ' +
|
'The http bind proxy got an error ' +
|
||||||
'(this can be normal if you updated/uninstalled the plugin, or shutdowned peertube while users were chatting): ' +
|
'(this can be normal if you updated/uninstalled the plugin, or shutdowned peertube while users were chatting): ' +
|
||||||
err.message
|
(err.message as string)
|
||||||
)
|
)
|
||||||
if ('writeHead' in res) {
|
if ('writeHead' in res) {
|
||||||
res.writeHead(500)
|
res.writeHead(500)
|
||||||
@ -359,6 +357,61 @@ async function enableProxyRoute (
|
|||||||
currentHttpBindProxy.on('close', () => {
|
currentHttpBindProxy.on('close', () => {
|
||||||
logger.info('Got a close event for the http bind proxy')
|
logger.info('Got a close event for the http bind proxy')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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): ' +
|
||||||
|
(err.message as string)
|
||||||
|
)
|
||||||
|
if ('writeHead' in res) {
|
||||||
|
res.writeHead(500)
|
||||||
|
}
|
||||||
|
res.end('')
|
||||||
|
})
|
||||||
|
currentWebsocketProxy.on('close', () => {
|
||||||
|
logger.info('Got a close event for the websocket proxy')
|
||||||
|
})
|
||||||
|
// Now, we need to attach an listener for the update event on the server...
|
||||||
|
// But we don't have access to the server.
|
||||||
|
// To get this, we will wait for the first request, and then get the server from there!
|
||||||
|
currentWebsocketProxy.once('proxyReq', function (_proxyReq, req, _res, _options) {
|
||||||
|
logger.info('Here is the first websocket proxy connection, we are binding the upgrade listener...')
|
||||||
|
/**
|
||||||
|
* Get the server object to subscribe to server events;
|
||||||
|
* 'upgrade' for websocket and 'close' for graceful shutdown
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
* req.socket: node >= 13
|
||||||
|
* req.connection: node < 13 (Remove this when node 12/13 support is dropped)
|
||||||
|
*
|
||||||
|
* This code was inspired by:
|
||||||
|
* https://github.com/chimurai/http-proxy-middleware, file /src/http-proxy-middleware.ts#L53
|
||||||
|
*/
|
||||||
|
const s: any = (req.socket ?? req.connection)
|
||||||
|
const server = 'server' in s ? s.server : null
|
||||||
|
if (currentWebsocketUpgradeEvent) {
|
||||||
|
currentWebsocketUpgradeEvent.server.off('upgrade', currentWebsocketUpgradeEvent.listener)
|
||||||
|
}
|
||||||
|
currentWebsocketUpgradeEvent = {
|
||||||
|
server,
|
||||||
|
listener: (req: any, socket: any, head: any) => {
|
||||||
|
// We are not the only websocket server! Peertube has its own. We must match the url.
|
||||||
|
if (/webchat\/xmpp-websocket/.test(req.url)) {
|
||||||
|
logger.info('Got an http upgrade event that match the correct path')
|
||||||
|
currentWebsocketProxy?.ws(req, socket, head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.on('upgrade', currentWebsocketUpgradeEvent.listener)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user