From 4a28991497f929229835576afb9b0df2ac7bacab Mon Sep 17 00:00:00 2001 From: John Livingston Date: Mon, 10 Apr 2023 18:21:32 +0200 Subject: [PATCH] XMPP client connection WIP: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding an option in the «share chat» dialog to display the XMPP room address. --- assets/styles/style.scss | 26 +++++-- client/videowatch/share.ts | 137 +++++++++++++++++++++++++++++-------- client/videowatch/uri.ts | 23 ++++++- languages/ca.json | 5 +- languages/de.json | 5 +- languages/eo.json | 5 +- languages/es.json | 5 +- languages/eu.json | 5 +- languages/fr.json | 5 +- languages/it.json | 5 +- languages/ja.json | 5 +- languages/oc.json | 5 +- languages/pl.json | 5 +- server/lib/settings.ts | 2 +- 14 files changed, 191 insertions(+), 47 deletions(-) diff --git a/assets/styles/style.scss b/assets/styles/style.scss index f59d69c5..84e5d739 100644 --- a/assets/styles/style.scss +++ b/assets/styles/style.scss @@ -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; + } } diff --git a/client/videowatch/share.ts b/client/videowatch/share.ts index 0d930f67..8a9ba085 100644 --- a/client/videowatch/share.ts +++ b/client/videowatch/share.ts @@ -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 { @@ -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) } diff --git a/client/videowatch/uri.ts b/client/videowatch/uri.ts index 0196e358..3615dc53 100644 --- a/client/videowatch/uri.ts +++ b/client/videowatch/uri.ts @@ -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 } diff --git a/languages/ca.json b/languages/ca.json index 0e168c55..ab778f70 100644 --- a/languages/ca.json +++ b/languages/ca.json @@ -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 } diff --git a/languages/de.json b/languages/de.json index f9dd09a2..ad522ab6 100644 --- a/languages/de.json +++ b/languages/de.json @@ -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 } diff --git a/languages/eo.json b/languages/eo.json index 50d043d8..6852748b 100644 --- a/languages/eo.json +++ b/languages/eo.json @@ -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 } diff --git a/languages/es.json b/languages/es.json index a59723fd..96a94bf4 100644 --- a/languages/es.json +++ b/languages/es.json @@ -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 } diff --git a/languages/eu.json b/languages/eu.json index e1e84e47..a26c27f0 100644 --- a/languages/eu.json +++ b/languages/eu.json @@ -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 } diff --git a/languages/fr.json b/languages/fr.json index 99bee396..744adb8b 100644 --- a/languages/fr.json +++ b/languages/fr.json @@ -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." } diff --git a/languages/it.json b/languages/it.json index 6f38c860..16961c84 100644 --- a/languages/it.json +++ b/languages/it.json @@ -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 } diff --git a/languages/ja.json b/languages/ja.json index cd9edf38..f9bd8afa 100644 --- a/languages/ja.json +++ b/languages/ja.json @@ -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 } diff --git a/languages/oc.json b/languages/oc.json index e50d2e31..1e2d8b84 100644 --- a/languages/oc.json +++ b/languages/oc.json @@ -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 } diff --git a/languages/pl.json b/languages/pl.json index 0261be87..23bc819e 100644 --- a/languages/pl.json +++ b/languages/pl.json @@ -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 } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index bbfb473f..3fa65473 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -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') })