From 6f8c7b8a93c896bcbbe125a8eb629084371040be Mon Sep 17 00:00:00 2001 From: John Livingston Date: Wed, 24 Aug 2022 17:55:24 +0200 Subject: [PATCH] Websocket for builtin Prosody. WIP --- conversejs/builtin.ts | 7 ++ server/lib/prosody/config.ts | 2 +- server/lib/prosody/config/content.ts | 6 +- server/lib/routers/webchat.ts | 129 +++++++++++++++++++-------- 4 files changed, 104 insertions(+), 40 deletions(-) diff --git a/conversejs/builtin.ts b/conversejs/builtin.ts index 994b518d..07f2abdd 100644 --- a/conversejs/builtin.ts +++ b/conversejs/builtin.ts @@ -127,6 +127,13 @@ window.initConverse = async function initConverse ({ 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 = { assets_path: assetsPath, diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index 582172ac..d6e9454a 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -130,7 +130,7 @@ async function getProsodyConfig (options: RegisterServerOptions): Promise | null = null +let currentWebsocketProxy: ReturnType | null = null +interface CurrentWebsocketUpgradeEvent { + server: any + listener: Function +} +let currentWebsocketUpgradeEvent: CurrentWebsocketUpgradeEvent | null = null async function initWebchatRouter (options: RegisterServerOptions): Promise { const { @@ -79,8 +85,9 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise { + 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( async (req: Request, res: Response, _next: NextFunction) => { @@ -279,39 +301,6 @@ async function initWebchatRouter (options: RegisterServerOptions): Promise { -// 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 => { -// 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 { // 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. @@ -319,10 +308,19 @@ async function disableProxyRoute ({ peertubeHelpers }: RegisterServerOptions): P try { currentProsodyProxyInfo = null if (currentHttpBindProxy) { - peertubeHelpers.logger.info('Closing the proxy...') + peertubeHelpers.logger.info('Closing the http bind proxy...') currentHttpBindProxy.close() 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) { 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) => { // We must handle errors, otherwise Peertube server crashes! 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): ' + - err.message + (err.message as string) ) if ('writeHead' in res) { res.writeHead(500) @@ -359,6 +357,61 @@ async function enableProxyRoute ( currentHttpBindProxy.on('close', () => { 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 {