XMPP client connection WIP:

Adding an option in the «share chat» dialog to display the XMPP room
address.
This commit is contained in:
John Livingston 2023-04-10 18:21:32 +02:00 committed by John Livingston
parent 4d9d9d39b0
commit 4a28991497
14 changed files with 191 additions and 47 deletions

View File

@ -109,7 +109,7 @@ table.peertube-plugin-livechat-prosody-list-rooms td {
.peertube-plugin-livechat-shareurl-modal { .peertube-plugin-livechat-shareurl-modal {
& > * { & > * {
margin-top: 30px; margin-top: 10px;
} }
.livechat-shareurl-copy { .livechat-shareurl-copy {
@ -126,8 +126,10 @@ table.peertube-plugin-livechat-prosody-list-rooms td {
} }
} }
.livechat-shareurl-custom { .livechat-shareurl-web-options,
input[type="checkbox"] { .livechat-shareurl-xmpp-options {
input[type="checkbox"],
input[type="radio"] {
margin-right: 20px; margin-right: 20px;
} }
@ -135,18 +137,32 @@ table.peertube-plugin-livechat-prosody-list-rooms td {
display: block; display: block;
} }
.livechat-shareurl-custom-readonly-options { .livechat-shareurl-web-options-readonly {
margin-left: 40px; margin-left: 40px;
& > * { & > * {
display: block; display: block;
} }
&.livechat-shareurl-custom-readonly-disabled { &.livechat-shareurl-web-options-readonly-disabled {
label { label {
color: var(--gray-dark); color: var(--gray-dark);
} }
} }
} }
} }
.livechat-shareurl-protocol {
display: flex;
flex-flow: row wrap;
column-gap: 30px;
input[type="radio"] {
margin-right: 10px;
}
}
.livechat-shareurl-tips {
min-height: 60px;
}
} }

View File

@ -1,7 +1,7 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client' import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import type { Video } from '@peertube/peertube-types' import type { Video } from '@peertube/peertube-types'
import { logger } from './logger' import { logger } from './logger'
import { getIframeUri, UriOptions } from './uri' import { getIframeUri, getXMPPUri, UriOptions } from './uri'
import { isAutoColorsAvailable } from 'shared/lib/autocolors' import { isAutoColorsAvailable } from 'shared/lib/autocolors'
interface ShareForm { interface ShareForm {
@ -14,6 +14,10 @@ interface ShareForm {
readonlyOptions: HTMLElement readonlyOptions: HTMLElement
autoColors?: HTMLInputElement autoColors?: HTMLInputElement
generateIframe: HTMLInputElement generateIframe: HTMLInputElement
divTips: HTMLElement
radioProtocolWeb?: HTMLInputElement
radioProtocolXMPP?: HTMLInputElement
divWebOptions: HTMLDivElement
} }
async function shareChatUrl (registerOptions: RegisterClientOptions, settings: any, video: Video): Promise<void> { async function shareChatUrl (registerOptions: RegisterClientOptions, settings: any, video: Video): Promise<void> {
@ -21,10 +25,13 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
const [ const [
labelShare, labelShare,
labelWeb,
labelXMPP,
labelXMPPTips,
labelReadonly, labelReadonly,
labelWithscroll, labelWithscroll,
labelTransparent, labelTransparent,
tipsOBS, labelOBSTips,
labelCopy, labelCopy,
labelCopied, labelCopied,
labelError, labelError,
@ -34,6 +41,10 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
labelChatFor labelChatFor
] = await Promise.all([ ] = await Promise.all([
peertubeHelpers.translate('Share chat link'), peertubeHelpers.translate('Share chat link'),
peertubeHelpers.translate('Web'),
peertubeHelpers.translate('Connect using XMPP'),
// eslint-disable-next-line max-len
peertubeHelpers.translate('You can connect to the room using an external XMPP account, and your favorite XMPP client.'),
peertubeHelpers.translate('Read-only'), peertubeHelpers.translate('Read-only'),
peertubeHelpers.translate('Show the scrollbar'), peertubeHelpers.translate('Show the scrollbar'),
peertubeHelpers.translate('Transparent background (for stream integration, with OBS for example)'), peertubeHelpers.translate('Transparent background (for stream integration, with OBS for example)'),
@ -79,24 +90,52 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
divShareString.append(openButton) divShareString.append(openButton)
container.append(divShareString) container.append(divShareString)
let radioProtocolWeb
let radioProtocolXMPP
if (settings['prosody-room-allow-s2s']) {
const protocolContainer = document.createElement('div')
protocolContainer.classList.add('livechat-shareurl-protocol')
radioProtocolWeb = document.createElement('input')
radioProtocolWeb.setAttribute('type', 'radio')
radioProtocolWeb.setAttribute('value', 'web')
radioProtocolWeb.setAttribute('name', 'protocol')
const radioProtocolWebLabel = document.createElement('label')
radioProtocolWebLabel.textContent = labelWeb
radioProtocolWebLabel.prepend(radioProtocolWeb)
protocolContainer.append(radioProtocolWebLabel)
radioProtocolXMPP = document.createElement('input')
radioProtocolXMPP.setAttribute('type', 'radio')
radioProtocolXMPP.setAttribute('value', 'xmpp')
radioProtocolXMPP.setAttribute('name', 'protocol')
const radioProtocolXMPPLabel = document.createElement('label')
radioProtocolXMPPLabel.textContent = labelXMPP
radioProtocolXMPPLabel.prepend(radioProtocolXMPP)
protocolContainer.append(radioProtocolXMPPLabel)
container.append(protocolContainer)
}
const divTips = document.createElement('div') const divTips = document.createElement('div')
divTips.textContent = tipsOBS divTips.textContent = ''
divTips.classList.add('livechat-shareurl-tips')
container.append(divTips) container.append(divTips)
const divCustom = document.createElement('div') const divWebOptions = document.createElement('div')
divCustom.classList.add('livechat-shareurl-custom') divWebOptions.classList.add('livechat-shareurl-web-options')
container.append(divCustom) container.append(divWebOptions)
const readonly = document.createElement('input') const readonly = document.createElement('input')
readonly.setAttribute('type', 'checkbox') readonly.setAttribute('type', 'checkbox')
const readonlyLabelEl = document.createElement('label') const readonlyLabelEl = document.createElement('label')
readonlyLabelEl.textContent = labelReadonly readonlyLabelEl.textContent = labelReadonly
readonlyLabelEl.prepend(readonly) readonlyLabelEl.prepend(readonly)
divCustom.append(readonlyLabelEl) divWebOptions.append(readonlyLabelEl)
const readonlyOptions = document.createElement('div') const readonlyOptions = document.createElement('div')
readonlyOptions.classList.add('livechat-shareurl-custom-readonly-options') readonlyOptions.classList.add('livechat-shareurl-web-options-readonly')
divCustom.append(readonlyOptions) divWebOptions.append(readonlyOptions)
const withscroll = document.createElement('input') const withscroll = document.createElement('input')
withscroll.setAttribute('type', 'checkbox') withscroll.setAttribute('type', 'checkbox')
@ -119,7 +158,7 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
autoColors = document.createElement('input') autoColors = document.createElement('input')
autoColors.setAttribute('type', 'checkbox') autoColors.setAttribute('type', 'checkbox')
label.prepend(autoColors) label.prepend(autoColors)
divCustom.append(label) divWebOptions.append(label)
} }
const generateIframe = document.createElement('input') const generateIframe = document.createElement('input')
@ -127,7 +166,18 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
const generateIframeLabelEl = document.createElement('label') const generateIframeLabelEl = document.createElement('label')
generateIframeLabelEl.textContent = labelGenerateIframe generateIframeLabelEl.textContent = labelGenerateIframe
generateIframeLabelEl.prepend(generateIframe) generateIframeLabelEl.prepend(generateIframe)
divCustom.append(generateIframeLabelEl) divWebOptions.append(generateIframeLabelEl)
if (radioProtocolWeb) {
radioProtocolWeb.onclick = () => {
renderContent(container)
}
}
if (radioProtocolXMPP) {
radioProtocolXMPP.onclick = () => {
renderContent(container)
}
}
readonly.onclick = () => { readonly.onclick = () => {
renderContent(container) renderContent(container)
@ -164,7 +214,7 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
openButton.onclick = () => { openButton.onclick = () => {
// Don't open the url if it is an iframe! // Don't open the url if it is an iframe!
if (shareString.value.startsWith('http')) { if (shareString.value.startsWith('http') || shareString.value.startsWith('xmpp')) {
window.open(shareString.value) window.open(shareString.value)
} }
} }
@ -178,7 +228,11 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
transparent, transparent,
readonlyOptions, readonlyOptions,
autoColors, autoColors,
generateIframe generateIframe,
radioProtocolWeb,
radioProtocolXMPP,
divWebOptions,
divTips
} }
restore(form) restore(form)
} }
@ -190,6 +244,16 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
ignoreAutoColors: form.autoColors ? !form.autoColors.checked : true, ignoreAutoColors: form.autoColors ? !form.autoColors.checked : true,
permanent: true permanent: true
} }
if (form.radioProtocolXMPP?.checked) {
// To minimize the height gap between the 2 modes,
// and prevent the dialog to resize and move too much,
// we use visibility instead of display
form.divTips.textContent = labelXMPPTips
form.divWebOptions.style.visibility = 'hidden'
} else {
form.divTips.textContent = labelOBSTips
form.divWebOptions.style.visibility = 'visible'
}
if (form.readonly.checked) { if (form.readonly.checked) {
if (form.withscroll.checked) { if (form.withscroll.checked) {
uriOptions.readonly = true uriOptions.readonly = true
@ -201,28 +265,35 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
} }
form.withscroll.disabled = false form.withscroll.disabled = false
form.transparent.disabled = false form.transparent.disabled = false
form.readonlyOptions.classList.remove('livechat-shareurl-custom-readonly-disabled') form.readonlyOptions.classList.remove('livechat-shareurl-web-options-readonly-disabled')
} else { } else {
form.withscroll.disabled = true form.withscroll.disabled = true
form.transparent.disabled = true form.transparent.disabled = true
form.readonlyOptions.classList.add('livechat-shareurl-custom-readonly-disabled') form.readonlyOptions.classList.add('livechat-shareurl-web-options-readonly-disabled')
} }
let shareStringValue = getIframeUri(registerOptions, settings, video, uriOptions) let shareStringValue
if (form.generateIframe.checked) { if (!form.radioProtocolXMPP?.checked) {
form.openButton.disabled = true shareStringValue = getIframeUri(registerOptions, settings, video, uriOptions)
if (shareStringValue) { if (form.generateIframe.checked) {
// To properly escape all attributes, we are constructing an HTMLIframeElement form.openButton.disabled = true
const iframe = document.createElement('iframe') if (shareStringValue) {
iframe.setAttribute('src', shareStringValue) // To properly escape all attributes, we are constructing an HTMLIframeElement
iframe.setAttribute('title', labelChatFor + ' ' + video.name) const iframe = document.createElement('iframe')
iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms') iframe.setAttribute('src', shareStringValue)
iframe.setAttribute('width', '560') iframe.setAttribute('title', labelChatFor + ' ' + video.name)
iframe.setAttribute('height', '315') iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms')
iframe.setAttribute('frameborder', '0') iframe.setAttribute('width', '560')
shareStringValue = iframe.outerHTML iframe.setAttribute('height', '315')
iframe.setAttribute('frameborder', '0')
shareStringValue = iframe.outerHTML
}
} else {
form.openButton.disabled = false
} }
} else { } else {
form.openButton.disabled = false // we must generate a XMPP room address
// form.openButton.disabled = true
shareStringValue = getXMPPUri(registerOptions, settings, video)
} }
form.shareString.setAttribute('value', shareStringValue ?? '') form.shareString.setAttribute('value', shareStringValue ?? '')
} }
@ -237,7 +308,8 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
withscroll: !!form.withscroll.checked, withscroll: !!form.withscroll.checked,
transparent: !!form.transparent.checked, transparent: !!form.transparent.checked,
autocolors: !!form.autoColors?.checked, autocolors: !!form.autoColors?.checked,
generateIframe: !!form.generateIframe.checked generateIframe: !!form.generateIframe.checked,
protocol: !form.radioProtocolWeb || form.radioProtocolWeb.checked ? 'web' : 'xmpp'
} }
window.localStorage.setItem('peertube-plugin-livechat-shareurl', JSON.stringify(v)) window.localStorage.setItem('peertube-plugin-livechat-shareurl', JSON.stringify(v))
} }
@ -263,6 +335,11 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
form.autoColors.checked = !!v.autocolors form.autoColors.checked = !!v.autocolors
} }
form.generateIframe.checked = !!v.generateIframe form.generateIframe.checked = !!v.generateIframe
if (form.radioProtocolXMPP && v.protocol === 'xmpp') {
form.radioProtocolXMPP.checked = true
} else if (form.radioProtocolWeb) {
form.radioProtocolWeb.checked = true
}
} catch (err) { } catch (err) {
logger.error(err as string) logger.error(err as string)
} }

View File

@ -66,10 +66,31 @@ function getIframeUri (
return iframeUriStr return iframeUriStr
} }
function getXMPPUri (
registerOptions: RegisterClientOptions, settings: any, video: Video
): string | null {
// returns something like xmpp:256896ac-199a-4dab-bb3a-4fd916140272@room.instance.tdl?join
if (!settings['prosody-room-allow-s2s']) {
return null
}
let uuid: string
if (settings['prosody-room-type'] === 'channel') {
uuid = 'channel.' + video.channel.id.toString()
} else {
uuid = video.uuid.toString()
}
const hostname = window.location.hostname
return 'xmpp:' + uuid + '@room.' + hostname + '?join'
}
export type { export type {
UriOptions UriOptions
} }
export { export {
getBaseRoute, getBaseRoute,
getIframeUri getIframeUri,
getXMPPUri
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": "Non trouvé", "Not found": "Non trouvé",
"Video": "Vidéo", "Video": "Vidéo",
"Channel": "Chaîne", "Channel": "Chaîne",
"Last activity": "Dernière activité" "Last activity": "Dernière activité",
"Web": "Web",
"Connect using XMPP": "Connexion avec un client XMPP",
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": "Vous pouvez vous connecter au salon en utilisant un compte XMPP externe, et votre client XMPP favori."
} }

View File

@ -21,5 +21,8 @@
"Not found": "Non trovato", "Not found": "Non trovato",
"Video": "Video", "Video": "Video",
"Channel": "Canale", "Channel": "Canale",
"Last activity": "Ultima attività" "Last activity": "Ultima attività",
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -21,5 +21,8 @@
"Not found": false, "Not found": false,
"Video": false, "Video": false,
"Channel": false, "Channel": false,
"Last activity": false "Last activity": false,
"Web": false,
"Connect using XMPP": false,
"You can connect to the room using an external XMPP account, and your favorite XMPP client.": false
} }

View File

@ -307,7 +307,7 @@ Please read
label: loc('prosody_room_allow_s2s_label'), label: loc('prosody_room_allow_s2s_label'),
type: 'input-checkbox', type: 'input-checkbox',
default: false, default: false,
private: true, private: false,
descriptionHTML: loc('prosody_room_allow_s2s_description') descriptionHTML: loc('prosody_room_allow_s2s_description')
}) })