Chat Federation: refactoring ActivityPub data:

The data format used by plugin v6.3.0 was not well suited.
Here comes a new data format, with S2S informations.
The plugin can automatically upgrade old format.
It also continues to provide the old format, so than remote instance
that did not update the plugin will still work.
This commit is contained in:
John Livingston
2023-05-24 15:09:56 +02:00
parent 4f9534dc11
commit 6ed69d2c2f
10 changed files with 547 additions and 212 deletions

View File

@ -1,73 +1,99 @@
import type { LiveChatJSONLDInfos, LiveChatJSONLDAttribute } from './types'
import type { RegisterServerOptions } from '@peertube/peertube-types'
import type { LiveChatJSONLDAttributeV1 } from './types'
import { URL } from 'url'
function sanitizePeertubeLiveChatInfos (chatInfos: any): LiveChatJSONLDAttribute {
/**
* Use this function for incoming remote informations.
* It will sanitize them, by checking everything is ok.
* It can also migrate from old format to the new one.
*
* This function can be used when informations are incoming,
* or when reading stored information (to automatically migrate them)
*
* @param chatInfos remote chat informations
* @returns a sanitized version of the remote chat informations
*/
function sanitizePeertubeLiveChatInfos (options: RegisterServerOptions, chatInfos: any): LiveChatJSONLDAttributeV1 {
if (chatInfos === false) { return false }
if (typeof chatInfos !== 'object') { return false }
if (chatInfos.type !== 'xmpp') { return false }
if ((typeof chatInfos.jid) !== 'string') { return false }
if (!Array.isArray(chatInfos.links)) { return false }
const r: LiveChatJSONLDInfos = {
if (!('xmppserver' in chatInfos)) {
// V0 format, migrating on the fly to v1.
return _sanitizePeertubeLiveChatInfosV0(options, chatInfos)
}
if (!chatInfos.xmppserver || (typeof chatInfos.xmppserver !== 'object')) {
return false
}
const xmppserver = chatInfos.xmppserver
if ((typeof xmppserver.host) !== 'string') { return false }
const host = _validateHost(xmppserver.host)
if (!host) { return false }
if ((typeof xmppserver.muc) !== 'string') { return false }
const muc = _validateHost(xmppserver.muc)
if (!muc) { return false }
const r: LiveChatJSONLDAttributeV1 = {
type: chatInfos.type,
jid: chatInfos.jid,
links: []
}
for (const link of chatInfos.links) {
if ((typeof link) !== 'object') { continue }
if (['xmpp-bosh-anonymous', 'xmpp-websocket-anonymous'].includes(link.type)) {
if ((typeof link.jid) !== 'string') { continue }
if ((typeof link.url) !== 'string') { continue }
if (
!_validUrl(link.url, {
noSearchParams: true,
protocol: link.type === 'xmpp-websocket-anonymous' ? 'ws.' : 'http.'
})
) {
continue
}
r.links.push({
type: link.type,
jid: link.jid,
url: link.url
})
}
if (link.type === 'xmpp-s2s') {
if (!/^\d+$/.test(link.port)) {
continue
}
const host = _validateHost(link.host)
if (!host) {
continue
}
r.links.push({
type: link.type,
host,
port: link.port
})
}
if (link.type === 'xmpp-peertube-livechat-ws-s2s') {
if ((typeof link.url) !== 'string') { continue }
if (
!_validUrl(link.url, {
noSearchParams: true,
protocol: 'ws.'
})
) {
continue
}
r.links.push({
type: link.type,
url: link.url
})
xmppserver: {
host,
muc
}
}
if (xmppserver.directs2s) {
if ((typeof xmppserver.directs2s) === 'object') {
const port = xmppserver.directs2s.port
if ((typeof port === 'string') && /^\d+$/.test(port)) {
r.xmppserver.directs2s = {
port
}
}
}
}
if (xmppserver.websockets2s) {
if ((typeof xmppserver.websockets2s) === 'object') {
const url = xmppserver.websockets2s.url
if ((typeof url === 'string') && _validUrl(url, {
noSearchParams: true,
protocol: 'ws.'
})) {
r.xmppserver.websockets2s = {
url
}
}
}
}
if (xmppserver.anonymous) {
const virtualhost = _validateHost(xmppserver.anonymous.virtualhost)
if (virtualhost) {
r.xmppserver.anonymous = {
virtualhost
}
const bosh = xmppserver.anonymous.bosh
if ((typeof bosh === 'string') && _validUrl(bosh, {
noSearchParams: true,
protocol: 'http.'
})) {
r.xmppserver.anonymous.bosh = bosh
}
const websocket = xmppserver.anonymous.websocket
if ((typeof websocket === 'string') && _validUrl(websocket, {
noSearchParams: true,
protocol: 'ws.'
})) {
r.xmppserver.anonymous.websocket = websocket
}
}
}
return r
}
@ -107,8 +133,9 @@ function _validUrl (s: string, constraints: URLConstraints): boolean {
return true
}
function _validateHost (s: string): false | string {
function _validateHost (s: any): false | string {
try {
if (typeof s !== 'string') { return false }
if (s.includes('/')) { return false }
const url = new URL('http://' + s)
return url.hostname
@ -117,6 +144,68 @@ function _validateHost (s: string): false | string {
}
}
function _sanitizePeertubeLiveChatInfosV0 (options: RegisterServerOptions, chatInfos: any): LiveChatJSONLDAttributeV1 {
const logger = options.peertubeHelpers.logger
logger.debug('We are have to migrate data from the old JSONLD format')
if (chatInfos === false) { return false }
if (typeof chatInfos !== 'object') { return false }
if (chatInfos.type !== 'xmpp') { return false }
if ((typeof chatInfos.jid) !== 'string') { return false }
// no link? invalid! dropping all.
if (!Array.isArray(chatInfos.links)) { return false }
const muc = _validateHost(chatInfos.jid.split('@')[1])
if (!muc) { return false }
if (!muc.startsWith('room.')) {
logger.error('We expected old format host to begin with "room.". Discarding.')
return false
}
const host = _validateHost(muc.replace(/^room\./, ''))
if (!host) { return false }
const r: LiveChatJSONLDAttributeV1 = {
type: chatInfos.type,
jid: chatInfos.jid,
xmppserver: {
host,
muc
}
}
for (const link of chatInfos.links) {
if ((typeof link) !== 'object') { continue }
if (['xmpp-bosh-anonymous', 'xmpp-websocket-anonymous'].includes(link.type)) {
if ((typeof link.jid) !== 'string') { continue }
if ((typeof link.url) !== 'string') { continue }
if (
!_validUrl(link.url, {
noSearchParams: true,
protocol: link.type === 'xmpp-websocket-anonymous' ? 'ws.' : 'http.'
})
) {
continue
}
if (!r.xmppserver.anonymous) {
r.xmppserver.anonymous = {
virtualhost: link.jid
}
}
if (link.type === 'xmpp-bosh-anonymous') {
r.xmppserver.anonymous.bosh = link.url
} else if (link.type === 'xmpp-websocket-anonymous') {
r.xmppserver.anonymous.websocket = link.url
}
}
}
return r
}
export {
sanitizePeertubeLiveChatInfos
}