Chat Federation: fetch remote server information when missing.
This commit is contained in:
		| @ -1,6 +1,8 @@ | |||||||
| import type { RegisterServerOptions } from '@peertube/peertube-types' | import type { RegisterServerOptions } from '@peertube/peertube-types' | ||||||
|  | import { hasRemoteServerInfos, storeRemoteServerInfos } from './storage' | ||||||
| import { getBaseRouterRoute } from '../helpers' | import { getBaseRouterRoute } from '../helpers' | ||||||
| import { canonicalizePluginUri } from '../uri/canonicalize' | import { canonicalizePluginUri } from '../uri/canonicalize' | ||||||
|  | import { sanitizePeertubeLiveChatServerInfos } from './sanitize' | ||||||
| import { URL } from 'url' | import { URL } from 'url' | ||||||
| const got = require('got') | const got = require('got') | ||||||
| 
 | 
 | ||||||
| @ -33,13 +35,18 @@ const got = require('got') | |||||||
|  * @param remoteInstanceUrl remote instance url to check (as readed in the request header) |  * @param remoteInstanceUrl remote instance url to check (as readed in the request header) | ||||||
|  * @returns true if the remote instance is ok |  * @returns true if the remote instance is ok | ||||||
|  */ |  */ | ||||||
| async function remoteServerInfos ( | async function fetchMissingRemoteServerInfos ( | ||||||
|   options: RegisterServerOptions, |   options: RegisterServerOptions, | ||||||
|   remoteInstanceUrl: string |   remoteInstanceUrl: string | ||||||
| ): Promise<boolean> { | ): Promise<void> { | ||||||
|   const logger = options.peertubeHelpers.logger |   const logger = options.peertubeHelpers.logger | ||||||
|   logger.debug(`remoteServerInfos: checking if we have remote server infos for host ${remoteInstanceUrl}.`) |   logger.debug(`remoteServerInfos: checking if we have remote server infos for host ${remoteInstanceUrl}.`) | ||||||
| 
 | 
 | ||||||
|  |   // FIXME: add a max age.
 | ||||||
|  |   if (await hasRemoteServerInfos(options, remoteInstanceUrl)) { | ||||||
|  |     return | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   let url: string |   let url: string | ||||||
|   try { |   try { | ||||||
|     const u = new URL(remoteInstanceUrl) |     const u = new URL(remoteInstanceUrl) | ||||||
| @ -53,7 +60,7 @@ async function remoteServerInfos ( | |||||||
|     }) |     }) | ||||||
|   } catch (_err) { |   } catch (_err) { | ||||||
|     logger.info('remoteServerInfos: Invalid remote instance url provided: ' + remoteInstanceUrl) |     logger.info('remoteServerInfos: Invalid remote instance url provided: ' + remoteInstanceUrl) | ||||||
|     return false |     return | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   try { |   try { | ||||||
| @ -66,18 +73,18 @@ async function remoteServerInfos ( | |||||||
| 
 | 
 | ||||||
|     if (!response) { |     if (!response) { | ||||||
|       logger.info('remoteServerInfos: Invalid remote server options') |       logger.info('remoteServerInfos: Invalid remote server options') | ||||||
|       return false |       return | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // FIXME/TODO
 |     const serverInfos = sanitizePeertubeLiveChatServerInfos(options, response) | ||||||
| 
 |     if (serverInfos) { | ||||||
|     return true |       await storeRemoteServerInfos(options, serverInfos) | ||||||
|  |     } | ||||||
|   } catch (_err) { |   } catch (_err) { | ||||||
|     logger.info('remoteServerInfos: Can\'t get remote instance informations using url ' + url) |     logger.info('remoteServerInfos: Can\'t get remote instance informations using url ' + url) | ||||||
|     return false |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export { | export { | ||||||
|   remoteServerInfos |   fetchMissingRemoteServerInfos | ||||||
| } | } | ||||||
| @ -20,7 +20,9 @@ async function readIncomingAPVideo ( | |||||||
|  |  | ||||||
|   await storeVideoLiveChatInfos(options, video, peertubeLiveChat) |   await storeVideoLiveChatInfos(options, video, peertubeLiveChat) | ||||||
|   if (video.remote) { |   if (video.remote) { | ||||||
|     await storeRemoteServerInfos(options, peertubeLiveChat) |     if (peertubeLiveChat !== false && peertubeLiveChat.xmppserver) { | ||||||
|  |       await storeRemoteServerInfos(options, peertubeLiveChat.xmppserver) | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import type { RegisterServerOptions } from '@peertube/peertube-types' | import type { RegisterServerOptions } from '@peertube/peertube-types' | ||||||
| import type { LiveChatJSONLDAttributeV1 } from './types' | import type { LiveChatJSONLDAttributeV1, PeertubeXMPPServerInfos } from './types' | ||||||
| import { URL } from 'url' | import { URL } from 'url' | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @ -28,7 +28,24 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo | |||||||
|   if (!chatInfos.xmppserver || (typeof chatInfos.xmppserver !== 'object')) { |   if (!chatInfos.xmppserver || (typeof chatInfos.xmppserver !== 'object')) { | ||||||
|     return false |     return false | ||||||
|   } |   } | ||||||
|   const xmppserver = chatInfos.xmppserver |   const xmppserver = sanitizePeertubeLiveChatServerInfos(options, chatInfos.xmppserver) | ||||||
|  |   if (!xmppserver) { return false } | ||||||
|  |  | ||||||
|  |   const r: LiveChatJSONLDAttributeV1 = { | ||||||
|  |     type: chatInfos.type, | ||||||
|  |     jid: chatInfos.jid, | ||||||
|  |     xmppserver | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   return r | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function sanitizePeertubeLiveChatServerInfos ( | ||||||
|  |   options: RegisterServerOptions, xmppserver: any | ||||||
|  | ): PeertubeXMPPServerInfos | false { | ||||||
|  |   if (!xmppserver || (typeof xmppserver !== 'object')) { | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  |  | ||||||
|   if ((typeof xmppserver.host) !== 'string') { return false } |   if ((typeof xmppserver.host) !== 'string') { return false } | ||||||
|   const host = _validateHost(xmppserver.host) |   const host = _validateHost(xmppserver.host) | ||||||
| @ -37,20 +54,16 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo | |||||||
|   const muc = _validateHost(xmppserver.muc) |   const muc = _validateHost(xmppserver.muc) | ||||||
|   if (!muc) { return false } |   if (!muc) { return false } | ||||||
|  |  | ||||||
|   const r: LiveChatJSONLDAttributeV1 = { |   const r: PeertubeXMPPServerInfos = { | ||||||
|     type: chatInfos.type, |     host, | ||||||
|     jid: chatInfos.jid, |     muc | ||||||
|     xmppserver: { |  | ||||||
|       host, |  | ||||||
|       muc |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (xmppserver.directs2s) { |   if (xmppserver.directs2s) { | ||||||
|     if ((typeof xmppserver.directs2s) === 'object') { |     if ((typeof xmppserver.directs2s) === 'object') { | ||||||
|       const port = xmppserver.directs2s.port |       const port = xmppserver.directs2s.port | ||||||
|       if ((typeof port === 'string') && /^\d+$/.test(port)) { |       if ((typeof port === 'string') && /^\d+$/.test(port)) { | ||||||
|         r.xmppserver.directs2s = { |         r.directs2s = { | ||||||
|           port |           port | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @ -63,7 +76,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo | |||||||
|         noSearchParams: true, |         noSearchParams: true, | ||||||
|         protocol: 'ws.' |         protocol: 'ws.' | ||||||
|       })) { |       })) { | ||||||
|         r.xmppserver.websockets2s = { |         r.websockets2s = { | ||||||
|           url |           url | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @ -72,7 +85,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo | |||||||
|   if (xmppserver.anonymous) { |   if (xmppserver.anonymous) { | ||||||
|     const virtualhost = _validateHost(xmppserver.anonymous.virtualhost) |     const virtualhost = _validateHost(xmppserver.anonymous.virtualhost) | ||||||
|     if (virtualhost) { |     if (virtualhost) { | ||||||
|       r.xmppserver.anonymous = { |       r.anonymous = { | ||||||
|         virtualhost |         virtualhost | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @ -81,7 +94,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo | |||||||
|         noSearchParams: true, |         noSearchParams: true, | ||||||
|         protocol: 'http.' |         protocol: 'http.' | ||||||
|       })) { |       })) { | ||||||
|         r.xmppserver.anonymous.bosh = bosh |         r.anonymous.bosh = bosh | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       const websocket = xmppserver.anonymous.websocket |       const websocket = xmppserver.anonymous.websocket | ||||||
| @ -89,7 +102,7 @@ function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfo | |||||||
|         noSearchParams: true, |         noSearchParams: true, | ||||||
|         protocol: 'ws.' |         protocol: 'ws.' | ||||||
|       })) { |       })) { | ||||||
|         r.xmppserver.anonymous.websocket = websocket |         r.anonymous.websocket = websocket | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -206,6 +219,23 @@ function _sanitizePeertubeLiveChatInfosV0 (options: RegisterServerOptions, chatI | |||||||
|   return r |   return r | ||||||
| } | } | ||||||
|  |  | ||||||
| export { | function sanitizeXMPPHost (options: RegisterServerOptions, host: any): false | string { | ||||||
|   sanitizePeertubeLiveChatInfos |   return _validateHost(host) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function sanitizeXMPPHostFromInstanceUrl (_options: RegisterServerOptions, s: any): false | string { | ||||||
|  |   try { | ||||||
|  |     if (typeof s !== 'string') { return false } | ||||||
|  |     const url = new URL(s) | ||||||
|  |     return url.hostname | ||||||
|  |   } catch (_err) { | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export { | ||||||
|  |   sanitizePeertubeLiveChatInfos, | ||||||
|  |   sanitizePeertubeLiveChatServerInfos, | ||||||
|  |   sanitizeXMPPHost, | ||||||
|  |   sanitizeXMPPHostFromInstanceUrl | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import type { RegisterServerOptions, MVideoFullLight, MVideoAP, Video, MVideoThumbnail } from '@peertube/peertube-types' | import type { RegisterServerOptions, MVideoFullLight, MVideoAP, Video, MVideoThumbnail } from '@peertube/peertube-types' | ||||||
| import type { LiveChatJSONLDAttribute, LiveChatJSONLDAttributeV1 } from './types' | import type { LiveChatJSONLDAttribute, LiveChatJSONLDAttributeV1, PeertubeXMPPServerInfos } from './types' | ||||||
| import { sanitizePeertubeLiveChatInfos } from './sanitize' | import { sanitizePeertubeLiveChatInfos, sanitizeXMPPHostFromInstanceUrl } from './sanitize' | ||||||
| import { URL } from 'url' | import { URL } from 'url' | ||||||
| import * as fs from 'fs' | import * as fs from 'fs' | ||||||
| import * as path from 'path' | import * as path from 'path' | ||||||
| @ -111,21 +111,18 @@ async function getVideoLiveChatInfos ( | |||||||
|  * kind of urls. |  * kind of urls. | ||||||
|  * |  * | ||||||
|  * @param options server optiosn |  * @param options server optiosn | ||||||
|  * @param liveChatInfos livechat stored data |  * @param xmppserver remote server informations | ||||||
|  */ |  */ | ||||||
| async function storeRemoteServerInfos ( | async function storeRemoteServerInfos ( | ||||||
|   options: RegisterServerOptions, |   options: RegisterServerOptions, | ||||||
|   liveChatInfos: LiveChatJSONLDAttributeV1 |   xmppserver: PeertubeXMPPServerInfos | ||||||
| ): Promise<void> { | ): Promise<void> { | ||||||
|   if (!liveChatInfos) { return } |  | ||||||
|   if (!liveChatInfos.xmppserver) { return } |  | ||||||
|  |  | ||||||
|   const logger = options.peertubeHelpers.logger |   const logger = options.peertubeHelpers.logger | ||||||
|  |  | ||||||
|   const mainHost = liveChatInfos.xmppserver.host |   const mainHost = xmppserver.host | ||||||
|   const hosts = [ |   const hosts = [ | ||||||
|     liveChatInfos.xmppserver.host, |     xmppserver.host, | ||||||
|     liveChatInfos.xmppserver.muc |     xmppserver.muc | ||||||
|   ] |   ] | ||||||
|  |  | ||||||
|   for (const host of hosts) { |   for (const host of hosts) { | ||||||
| @ -144,27 +141,55 @@ async function storeRemoteServerInfos ( | |||||||
|     ) |     ) | ||||||
|     const s2sFilePath = path.resolve(dir, 's2s') |     const s2sFilePath = path.resolve(dir, 's2s') | ||||||
|     const wsS2SFilePath = path.resolve(dir, 'ws-s2s') |     const wsS2SFilePath = path.resolve(dir, 'ws-s2s') | ||||||
|  |     const timestampFilePath = path.resolve(dir, 'last-update') | ||||||
|  |  | ||||||
|     if (liveChatInfos.xmppserver.directs2s?.port) { |     if (xmppserver.directs2s?.port) { | ||||||
|       await _store(options, s2sFilePath, { |       await _store(options, s2sFilePath, { | ||||||
|         host: mainHost, |         host: mainHost, | ||||||
|         port: liveChatInfos.xmppserver.directs2s.port |         port: xmppserver.directs2s.port | ||||||
|       }) |       }) | ||||||
|     } else { |     } else { | ||||||
|       await _del(options, s2sFilePath) |       await _del(options, s2sFilePath) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (liveChatInfos.xmppserver.websockets2s?.url) { |     if (xmppserver.websockets2s?.url) { | ||||||
|       await _store(options, wsS2SFilePath, { |       await _store(options, wsS2SFilePath, { | ||||||
|         host: mainHost, |         host: mainHost, | ||||||
|         url: liveChatInfos.xmppserver.websockets2s.url |         url: xmppserver.websockets2s.url | ||||||
|       }) |       }) | ||||||
|     } else { |     } else { | ||||||
|       await _del(options, wsS2SFilePath) |       await _del(options, wsS2SFilePath) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     await _store(options, timestampFilePath, { | ||||||
|  |       timestamp: (new Date()).getTime() | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Indicate if we have the remote hosts informations. | ||||||
|  |  * @param options server options | ||||||
|  |  * @param host host domain | ||||||
|  |  */ | ||||||
|  | async function hasRemoteServerInfos (options: RegisterServerOptions, hostParam: any): Promise<boolean> { | ||||||
|  |   const host = sanitizeXMPPHostFromInstanceUrl(options, hostParam) | ||||||
|  |   if (!host) { | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  |   if (host.includes('..')) { | ||||||
|  |     options.peertubeHelpers.logger.error(`Host seems not correct, contains ..: ${host}`) | ||||||
|  |     return false | ||||||
|  |   } | ||||||
|  |   const filePath = path.resolve( | ||||||
|  |     options.peertubeHelpers.plugin.getDataDirectoryPath(), | ||||||
|  |     'serverInfos', | ||||||
|  |     host, | ||||||
|  |     'last-update' | ||||||
|  |   ) | ||||||
|  |   return fs.existsSync(filePath) | ||||||
|  | } | ||||||
|  |  | ||||||
| async function _getFilePath ( | async function _getFilePath ( | ||||||
|   options: RegisterServerOptions, |   options: RegisterServerOptions, | ||||||
|   remote: boolean, |   remote: boolean, | ||||||
| @ -269,6 +294,7 @@ function getRemoteServerInfosDir (options: RegisterServerOptions): string { | |||||||
| export { | export { | ||||||
|   storeVideoLiveChatInfos, |   storeVideoLiveChatInfos, | ||||||
|   storeRemoteServerInfos, |   storeRemoteServerInfos, | ||||||
|  |   hasRemoteServerInfos, | ||||||
|   getVideoLiveChatInfos, |   getVideoLiveChatInfos, | ||||||
|   getRemoteServerInfosDir |   getRemoteServerInfosDir | ||||||
| } | } | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import { getProsodyDomain } from '../prosody/config/domain' | |||||||
| import { fillVideoCustomFields } from '../custom-fields' | import { fillVideoCustomFields } from '../custom-fields' | ||||||
| import { getChannelInfosById } from '../database/channel' | import { getChannelInfosById } from '../database/channel' | ||||||
| import { ensureProsodyRunning } from '../prosody/ctl' | import { ensureProsodyRunning } from '../prosody/ctl' | ||||||
|  | import { serverBuildInfos } from '../federation/outgoing' | ||||||
| import { isDebugMode } from '../debug' | import { isDebugMode } from '../debug' | ||||||
|  |  | ||||||
| // See here for description: https://modules.prosody.im/mod_muc_http_defaults.html | // See here for description: https://modules.prosody.im/mod_muc_http_defaults.html | ||||||
| @ -224,14 +225,13 @@ async function initApiRouter (options: RegisterServerOptions): Promise<Router> { | |||||||
|     } |     } | ||||||
|   )) |   )) | ||||||
|  |  | ||||||
|   // router.get('/federation_server_infos', asyncMiddleware( |   router.get('/federation_server_infos', asyncMiddleware( | ||||||
|   //   async (req: Request, res: Response, _next: NextFunction) => { |     async (req: Request, res: Response, _next: NextFunction) => { | ||||||
|   //     logger.info('federation_server_infos api call') |       logger.info('federation_server_infos api call') | ||||||
|   //     // TODO/FIXME: return server infos. |       const result = await serverBuildInfos(options) | ||||||
|   //     // TODO/FIXME: store these informations on the other side. |       res.json(result) | ||||||
|   //     res.json({ ok: true }) |     } | ||||||
|   //   } |   )) | ||||||
|   // )) |  | ||||||
|  |  | ||||||
|   if (isDebugMode(options)) { |   if (isDebugMode(options)) { | ||||||
|     // Only add this route if the debug mode is enabled at time of the server launch. |     // Only add this route if the debug mode is enabled at time of the server launch. | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ import { LiveChatJSONLDAttributeV1 } from '../federation/types' | |||||||
| import { | import { | ||||||
|   anonymousConnectionInfos, compatibleRemoteAuthenticatedConnectionEnabled |   anonymousConnectionInfos, compatibleRemoteAuthenticatedConnectionEnabled | ||||||
| } from '../federation/connection-infos' | } from '../federation/connection-infos' | ||||||
| // import { remoteServerInfos } from '../federation/remote-infos' | import { fetchMissingRemoteServerInfos } from '../federation/fetch-infos' | ||||||
| import * as path from 'path' | import * as path from 'path' | ||||||
| const got = require('got') | const got = require('got') | ||||||
|  |  | ||||||
| @ -290,11 +290,11 @@ async function initWebchatRouter (options: RegisterServerOptionsV5): Promise<Rou | |||||||
|           // If the incomming request is from a remote Peertube instance, we must ensure that we know |           // 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). |           // how to connect to it using Websocket S2S (for the dialback mecanism). | ||||||
|           const remoteInstanceUrl = request.headers['peertube-livechat-ws-s2s-instance-url'] |           const remoteInstanceUrl = request.headers['peertube-livechat-ws-s2s-instance-url'] | ||||||
|           if (remoteInstanceUrl && (typeof remoteInstanceUrl !== 'string')) { |           if (remoteInstanceUrl && (typeof remoteInstanceUrl === 'string')) { | ||||||
|             // Note: remoteServerInfos will store the information, |             // Note: fetchMissingRemoteServerInfos will store the information, | ||||||
|             // so that the Prosody mod_s2s_peertubelivechat module can access them. |             // so that the Prosody mod_s2s_peertubelivechat module can access them. | ||||||
|             // TODO |             // We dont need to read the result. | ||||||
|             // await remoteServerInfos(options, remoteInstanceUrl) |             await fetchMissingRemoteServerInfos(options, remoteInstanceUrl) | ||||||
|           } |           } | ||||||
|           currentS2SWebsocketProxy.ws(request, socket, head) |           currentS2SWebsocketProxy.ws(request, socket, head) | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user