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 {
& > * {
margin-top: 30px;
margin-top: 10px;
}
.livechat-shareurl-copy {
@ -126,8 +126,10 @@ table.peertube-plugin-livechat-prosody-list-rooms td {
}
}
.livechat-shareurl-custom {
input[type="checkbox"] {
.livechat-shareurl-web-options,
.livechat-shareurl-xmpp-options {
input[type="checkbox"],
input[type="radio"] {
margin-right: 20px;
}
@ -135,18 +137,32 @@ table.peertube-plugin-livechat-prosody-list-rooms td {
display: block;
}
.livechat-shareurl-custom-readonly-options {
.livechat-shareurl-web-options-readonly {
margin-left: 40px;
& > * {
display: block;
}
&.livechat-shareurl-custom-readonly-disabled {
&.livechat-shareurl-web-options-readonly-disabled {
label {
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 { Video } from '@peertube/peertube-types'
import { logger } from './logger'
import { getIframeUri, UriOptions } from './uri'
import { getIframeUri, getXMPPUri, UriOptions } from './uri'
import { isAutoColorsAvailable } from 'shared/lib/autocolors'
interface ShareForm {
@ -14,6 +14,10 @@ interface ShareForm {
readonlyOptions: HTMLElement
autoColors?: HTMLInputElement
generateIframe: HTMLInputElement
divTips: HTMLElement
radioProtocolWeb?: HTMLInputElement
radioProtocolXMPP?: HTMLInputElement
divWebOptions: HTMLDivElement
}
async function shareChatUrl (registerOptions: RegisterClientOptions, settings: any, video: Video): Promise<void> {
@ -21,10 +25,13 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
const [
labelShare,
labelWeb,
labelXMPP,
labelXMPPTips,
labelReadonly,
labelWithscroll,
labelTransparent,
tipsOBS,
labelOBSTips,
labelCopy,
labelCopied,
labelError,
@ -34,6 +41,10 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
labelChatFor
] = await Promise.all([
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('Show the scrollbar'),
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)
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')
divTips.textContent = tipsOBS
divTips.textContent = ''
divTips.classList.add('livechat-shareurl-tips')
container.append(divTips)
const divCustom = document.createElement('div')
divCustom.classList.add('livechat-shareurl-custom')
container.append(divCustom)
const divWebOptions = document.createElement('div')
divWebOptions.classList.add('livechat-shareurl-web-options')
container.append(divWebOptions)
const readonly = document.createElement('input')
readonly.setAttribute('type', 'checkbox')
const readonlyLabelEl = document.createElement('label')
readonlyLabelEl.textContent = labelReadonly
readonlyLabelEl.prepend(readonly)
divCustom.append(readonlyLabelEl)
divWebOptions.append(readonlyLabelEl)
const readonlyOptions = document.createElement('div')
readonlyOptions.classList.add('livechat-shareurl-custom-readonly-options')
divCustom.append(readonlyOptions)
readonlyOptions.classList.add('livechat-shareurl-web-options-readonly')
divWebOptions.append(readonlyOptions)
const withscroll = document.createElement('input')
withscroll.setAttribute('type', 'checkbox')
@ -119,7 +158,7 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
autoColors = document.createElement('input')
autoColors.setAttribute('type', 'checkbox')
label.prepend(autoColors)
divCustom.append(label)
divWebOptions.append(label)
}
const generateIframe = document.createElement('input')
@ -127,7 +166,18 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
const generateIframeLabelEl = document.createElement('label')
generateIframeLabelEl.textContent = labelGenerateIframe
generateIframeLabelEl.prepend(generateIframe)
divCustom.append(generateIframeLabelEl)
divWebOptions.append(generateIframeLabelEl)
if (radioProtocolWeb) {
radioProtocolWeb.onclick = () => {
renderContent(container)
}
}
if (radioProtocolXMPP) {
radioProtocolXMPP.onclick = () => {
renderContent(container)
}
}
readonly.onclick = () => {
renderContent(container)
@ -164,7 +214,7 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
openButton.onclick = () => {
// 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)
}
}
@ -178,7 +228,11 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
transparent,
readonlyOptions,
autoColors,
generateIframe
generateIframe,
radioProtocolWeb,
radioProtocolXMPP,
divWebOptions,
divTips
}
restore(form)
}
@ -190,6 +244,16 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
ignoreAutoColors: form.autoColors ? !form.autoColors.checked : 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.withscroll.checked) {
uriOptions.readonly = true
@ -201,28 +265,35 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
}
form.withscroll.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 {
form.withscroll.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)
if (form.generateIframe.checked) {
form.openButton.disabled = true
if (shareStringValue) {
// To properly escape all attributes, we are constructing an HTMLIframeElement
const iframe = document.createElement('iframe')
iframe.setAttribute('src', shareStringValue)
iframe.setAttribute('title', labelChatFor + ' ' + video.name)
iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms')
iframe.setAttribute('width', '560')
iframe.setAttribute('height', '315')
iframe.setAttribute('frameborder', '0')
shareStringValue = iframe.outerHTML
let shareStringValue
if (!form.radioProtocolXMPP?.checked) {
shareStringValue = getIframeUri(registerOptions, settings, video, uriOptions)
if (form.generateIframe.checked) {
form.openButton.disabled = true
if (shareStringValue) {
// To properly escape all attributes, we are constructing an HTMLIframeElement
const iframe = document.createElement('iframe')
iframe.setAttribute('src', shareStringValue)
iframe.setAttribute('title', labelChatFor + ' ' + video.name)
iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms')
iframe.setAttribute('width', '560')
iframe.setAttribute('height', '315')
iframe.setAttribute('frameborder', '0')
shareStringValue = iframe.outerHTML
}
} else {
form.openButton.disabled = false
}
} 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 ?? '')
}
@ -237,7 +308,8 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
withscroll: !!form.withscroll.checked,
transparent: !!form.transparent.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))
}
@ -263,6 +335,11 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
form.autoColors.checked = !!v.autocolors
}
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) {
logger.error(err as string)
}

View File

@ -66,10 +66,31 @@ function getIframeUri (
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 {
UriOptions
}
export {
getBaseRoute,
getIframeUri
getIframeUri,
getXMPPUri
}

View File

@ -21,5 +21,8 @@
"Not found": false,
"Video": 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,
"Video": 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,
"Video": 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,
"Video": 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,
"Video": 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é",
"Video": "Vidéo",
"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",
"Video": "Video",
"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,
"Video": 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,
"Video": 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,
"Video": 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'),
type: 'input-checkbox',
default: false,
private: true,
private: false,
descriptionHTML: loc('prosody_room_allow_s2s_description')
})