Log in with external Peertube account (#348):
* For anonymous users: new "log in using an external account" dialog, with the "remote Peertube account" options * ConverseJS: using global vars for custom localized string (injected using Webpack)
This commit is contained in:
parent
c55fabc972
commit
8fc8e3032b
@ -14,6 +14,8 @@ TODO: https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/48
|
||||
* #143: User colors: implementing [XEP-0392](https://xmpp.org/extensions/xep-0392.html) to have random colors on users nicknames
|
||||
* #330: Chat does no more use an iframe to display the chat besides the videos.
|
||||
* #330: Fullscreen chat: now uses a custom page (in other words: when opening the chat in a new tab, you will have the Peertube menu).
|
||||
* For anonymous users: new "log in using an external account" dialog, with following options:
|
||||
* remote Peertube account
|
||||
|
||||
### Minor changes and fixes
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const YAML = require('yaml')
|
||||
const locKeys = require('./loc.keys.js')
|
||||
|
||||
/**
|
||||
* This script will patch ConverseJS .po files, to add custom strings.
|
||||
@ -11,9 +12,7 @@ const livechatDir = path.resolve(__dirname, '..', 'languages')
|
||||
const converseDir = path.resolve(__dirname, '..', 'build', 'conversejs', 'src', 'i18n')
|
||||
|
||||
// Labels to import:
|
||||
const labels = loadLabels([
|
||||
'slow_mode_info'
|
||||
])
|
||||
const labels = loadLabels(locKeys)
|
||||
|
||||
function loadLabels (keys) {
|
||||
const labels = {}
|
||||
|
@ -79,6 +79,7 @@ rm -rf "$converse_build_dir/custom/"
|
||||
echo "Adding the custom files..."
|
||||
cp -r "$src_dir/custom/" "$converse_build_dir/custom/"
|
||||
mv "$converse_build_dir/custom/webpack.livechat.js" "$converse_build_dir/"
|
||||
cp "$src_dir/loc.keys.js" "$converse_build_dir/"
|
||||
|
||||
echo "Patching i18n files to add custom labels..."
|
||||
/bin/env node conversejs/build-conversejs-patch-i18n.js
|
||||
|
117
conversejs/custom/livechat-external-login-content.js
Normal file
117
conversejs/custom/livechat-external-login-content.js
Normal file
@ -0,0 +1,117 @@
|
||||
import { api } from '@converse/headless/core.js'
|
||||
import { CustomElement } from 'shared/components/element.js'
|
||||
import { tplExternalLoginModal } from 'templates/livechat-external-login-modal.js'
|
||||
import { __ } from 'i18n'
|
||||
|
||||
export default class LivechatExternalLoginContentElement extends CustomElement {
|
||||
static get properties () {
|
||||
return {
|
||||
remote_peertube_state: { type: String, attribute: false },
|
||||
remote_peertube_alert_message: { type: String, attribute: false },
|
||||
remote_peertube_try_anyway_url: { type: String, attribute: false }
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
super()
|
||||
this.remote_peertube_state = 'init'
|
||||
}
|
||||
|
||||
render () {
|
||||
return tplExternalLoginModal(this, {
|
||||
remote_peertube_state: this.remote_peertube_state,
|
||||
remote_peertube_alert_message: this.remote_peertube_alert_message,
|
||||
remote_peertube_try_anyway_url: this.remote_peertube_try_anyway_url
|
||||
})
|
||||
}
|
||||
|
||||
onKeyUp (_ev) {
|
||||
if (this.remote_peertube_state !== 'init') {
|
||||
this.remote_peertube_state = 'init'
|
||||
this.remote_peertube_alert_message = ''
|
||||
this.clearAlert()
|
||||
}
|
||||
}
|
||||
|
||||
async openRemotePeertube (ev) {
|
||||
ev.preventDefault()
|
||||
this.clearAlert()
|
||||
|
||||
const remotePeertubeUrl = ev.target.peertube_url.value.trim()
|
||||
if (!remotePeertubeUrl) { return }
|
||||
|
||||
this.remote_peertube_state = 'loading'
|
||||
|
||||
try {
|
||||
// Calling Peertube API to check if livechat plugin is available.
|
||||
// In the meantime, this will also check that the URL exists, and is a Peertube instance
|
||||
// (or something with similar API result... as the user typed the url, we assume there is no security risk here).
|
||||
const configApiUrl = new URL('/api/v1/config', remotePeertubeUrl)
|
||||
const config = await (await fetch(configApiUrl.toString())).json()
|
||||
if (!config || typeof config !== 'object') {
|
||||
throw new Error('Invalid config API result')
|
||||
}
|
||||
if (!('plugin' in config) || !('registered' in config.plugin) || !Array.isArray(config.plugin.registered)) {
|
||||
throw new Error('No registered plugin in config API result')
|
||||
}
|
||||
if (!config.plugin.registered.find(p => p.npmName === 'peertube-plugin-livechat')) {
|
||||
console.error('Plugin livechat not available on remote instance')
|
||||
this.remote_peertube_state = 'error'
|
||||
// eslint-disable-next-line no-undef
|
||||
this.remote_peertube_alert_message = __(LOC_login_remote_peertube_no_livechat)
|
||||
return
|
||||
}
|
||||
// Note: we do not check if the livechat plugin disables federation (neither on current or remote instance).
|
||||
// We assume this is not a standard use case, and we don't want to add to much use cases.
|
||||
|
||||
// Now we must search the current video on the remote instance, to be sure it federates, and to get the url.
|
||||
// Note: url search can be disabled on remote instance for non logged in users...
|
||||
// As we are not authenticated on remote here, there are chances that the search wont return anything.
|
||||
// As a fallback, we will launch another search with the video UUID.
|
||||
// And if no result neither, we will just propose to open using the lazy-load page.
|
||||
const videoUrl = api.settings.get('livechat_peertube_video_original_url')
|
||||
const videoUUID = api.settings.get('livechat_peertube_video_uuid')
|
||||
for (const search of [videoUrl, videoUUID]) {
|
||||
if (!search) { continue }
|
||||
// searching first on federation network, then on vidiverse (this could be disabled)
|
||||
for (const searchTarget of ['local', 'search-index']) {
|
||||
const searchAPIUrl = new URL('/api/v1/search/videos', remotePeertubeUrl)
|
||||
searchAPIUrl.searchParams.append('start', '0')
|
||||
searchAPIUrl.searchParams.append('count', 1)
|
||||
searchAPIUrl.searchParams.append('search', search)
|
||||
searchAPIUrl.searchParams.append('searchTarget', searchTarget)
|
||||
const videos = await (await fetch(searchAPIUrl.toString())).json()
|
||||
if (videos && Array.isArray(videos.data) && videos.data.length > 0 && videos.data[0].uuid) {
|
||||
console.log('Video found, opening on remote instance')
|
||||
this.remote_peertube_state = 'ok'
|
||||
window.location.href = new URL(
|
||||
'/videos/watch/' + encodeURIComponent(videos.data[0].uuid), remotePeertubeUrl
|
||||
).toString()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Video not found on remote instance')
|
||||
this.remote_peertube_state = 'error'
|
||||
// eslint-disable-next-line no-undef
|
||||
this.remote_peertube_alert_message = __(LOC_login_remote_peertube_video_not_found)
|
||||
this.remote_peertube_try_anyway_url = new URL(
|
||||
'/search/lazy-load-video;url=' + encodeURIComponent(videoUrl),
|
||||
remotePeertubeUrl
|
||||
).toString()
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.remote_peertube_state = 'error'
|
||||
// eslint-disable-next-line no-undef
|
||||
this.remote_peertube_alert_message = __(LOC_login_remote_peertube_url_invalid)
|
||||
}
|
||||
}
|
||||
|
||||
clearAlert () {
|
||||
this.remote_peertube_alert_message = ''
|
||||
this.remote_peertube_try_anyway_url = ''
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-livechat-external-login-content', LivechatExternalLoginContentElement)
|
20
conversejs/custom/shared/modals/livechat-external-login.js
Normal file
20
conversejs/custom/shared/modals/livechat-external-login.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { __ } from 'i18n'
|
||||
import BaseModal from 'plugins/modal/modal.js'
|
||||
import { api } from '@converse/headless/core'
|
||||
import { html } from 'lit'
|
||||
import 'livechat-external-login-content.js'
|
||||
|
||||
class ExternalLoginModal extends BaseModal {
|
||||
remotePeertubeError = ''
|
||||
|
||||
renderModal () {
|
||||
return html`<converse-livechat-external-login-content></converse-livechat-external-login-content>`
|
||||
}
|
||||
|
||||
getModalTitle () {
|
||||
// eslint-disable-next-line no-undef
|
||||
return __(LOC_login_using_external_account)
|
||||
}
|
||||
}
|
||||
|
||||
api.elements.define('converse-livechat-external-login', ExternalLoginModal)
|
@ -43,30 +43,39 @@ body.livechat-readonly.livechat-noscroll {
|
||||
}
|
||||
|
||||
// Viewer mode
|
||||
.livechat-viewer-mode-nick {
|
||||
.livechat-viewer-mode-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body[livechat-viewer-mode="on"] {
|
||||
.livechat-viewer-mode-nick {
|
||||
display: initial;
|
||||
form {
|
||||
display: flex !important;
|
||||
flex-flow: row wrap !important;
|
||||
padding-bottom: 0.5em !important;
|
||||
border-top: 1px solid var(--chatroom-head-bg-color) !important;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
|
||||
form {
|
||||
display: flex !important;
|
||||
flex-flow: row wrap !important;
|
||||
padding-bottom: 0.5em !important;
|
||||
border-top: var(--chatroom-separator-border-bottom) !important;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
|
||||
label {
|
||||
color: var(--text-color); // fix converseJs css that breaks this label color.
|
||||
}
|
||||
label {
|
||||
color: var(--text-color); // fix converseJs css that breaks this label color.
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 0;
|
||||
background-color: var(--chatroom-head-bg-color);
|
||||
}
|
||||
|
||||
.livechat-viewer-mode-external-login {
|
||||
padding: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
body[livechat-viewer-mode="on"] {
|
||||
.livechat-viewer-mode-content {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
converse-muc-bottom-panel {
|
||||
>:not(.livechat-viewer-mode-nick) {
|
||||
>:not(.livechat-viewer-mode-content) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
56
conversejs/custom/templates/livechat-external-login-modal.js
Normal file
56
conversejs/custom/templates/livechat-external-login-modal.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { __ } from 'i18n'
|
||||
import { html } from 'lit'
|
||||
|
||||
export const tplExternalLoginModal = (el, o) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nRemotePeertube = __(LOC_login_remote_peertube)
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nRemotePeertubeUrl = __(LOC_login_remote_peertube_url)
|
||||
const i18nRemotePeertubeOpen = __('OK')
|
||||
return html`<div class="modal-body livechat-external-login-modal">
|
||||
<form class="converse-form chatroom-form" @submit=${(ev) => el.openRemotePeertube(ev)}>
|
||||
<label>
|
||||
${i18nRemotePeertube}
|
||||
<input
|
||||
type="url"
|
||||
placeholder="${i18nRemotePeertubeUrl}"
|
||||
class="form-control ${o.remote_peertube_alert_message ? 'is-invalid' : ''}"
|
||||
name="peertube_url"
|
||||
?disabled=${o.remote_peertube_state === 'loading'}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
value="${i18nRemotePeertubeOpen}"
|
||||
@keyup=${el.onKeyUp}
|
||||
?disabled=${o.remote_peertube_state === 'loading'}
|
||||
/>
|
||||
${
|
||||
o.remote_peertube_state !== 'loading'
|
||||
? ''
|
||||
: html`<small class="form-text text-muted">${
|
||||
// eslint-disable-next-line no-undef
|
||||
__(LOC_login_remote_peertube_searching)
|
||||
}</small>`
|
||||
}
|
||||
${!o.remote_peertube_alert_message
|
||||
? ''
|
||||
: html`<div class="invalid-feedback d-block">${o.remote_peertube_alert_message}</div>`
|
||||
}
|
||||
${!o.remote_peertube_try_anyway_url
|
||||
? ''
|
||||
: html`<div class="form-text">
|
||||
${
|
||||
// eslint-disable-next-line no-undef
|
||||
__(LOC_login_remote_peertube_video_not_found_try_anyway)
|
||||
}
|
||||
<button class="btn btn-primary" onclick="window.location.href='${o.remote_peertube_try_anyway_url}'">${
|
||||
// eslint-disable-next-line no-undef
|
||||
__(LOC_login_remote_peertube_video_not_found_try_anyway_button)
|
||||
}</button>
|
||||
</div>`
|
||||
}
|
||||
</fieldset>
|
||||
</form></div>`
|
||||
}
|
@ -3,6 +3,7 @@ import { _converse, api } from '@converse/headless/core'
|
||||
import { html } from 'lit'
|
||||
import tplMucBottomPanel from '../../src/plugins/muc-views/templates/muc-bottom-panel.js'
|
||||
import { CustomElement } from 'shared/components/element.js'
|
||||
import 'shared/modals/livechat-external-login.js'
|
||||
|
||||
async function setNickname (ev, model) {
|
||||
ev.preventDefault()
|
||||
@ -54,7 +55,8 @@ class SlowMode extends CustomElement {
|
||||
return html`<div class="livechat-slow-mode-info-box">
|
||||
<converse-icon class="fa fa-info-circle" size="1.2em"></converse-icon>
|
||||
${__(
|
||||
'Slow mode is enabled, users can send a message every %1$s seconds.',
|
||||
// eslint-disable-next-line no-undef
|
||||
LOC_slow_mode_info,
|
||||
this.model.config.get('slow_mode_duration')
|
||||
)}
|
||||
<i class="livechat-hide-slow-mode-info-box" @click=${this.closeSlowModeInfoBox}>
|
||||
@ -82,14 +84,15 @@ export default (o) => {
|
||||
const i18nNickname = __('Nickname')
|
||||
const i18nJoin = __('Enter groupchat')
|
||||
const i18nHeading = __('Choose a nickname to enter')
|
||||
// eslint-disable-next-line no-undef
|
||||
const i18nExternalLogin = __(LOC_login_using_external_account)
|
||||
return html`
|
||||
<div class="livechat-viewer-mode-nick chatroom-form-container"
|
||||
@submit=${ev => setNickname(ev, model)}>
|
||||
<form class="converse-form chatroom-form">
|
||||
<div class="livechat-viewer-mode-content chatroom-form-container">
|
||||
<form class="converse-form chatroom-form" @submit=${ev => setNickname(ev, model)}>
|
||||
<label>${i18nHeading}</label>
|
||||
<fieldset class="form-group">
|
||||
<input type="text"
|
||||
required="required"
|
||||
required
|
||||
name="nick"
|
||||
value=""
|
||||
class="form-control"
|
||||
@ -99,6 +102,21 @@ export default (o) => {
|
||||
<input type="submit" class="btn btn-primary" name="join" value="${i18nJoin}"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
${
|
||||
// If we open a room with forcetype, there is no current video... So just disabling external login
|
||||
// (in such case, we should be logged in as admin/moderator...)
|
||||
!api.settings.get('livechat_peertube_video_original_url')
|
||||
? ''
|
||||
: html`
|
||||
<hr>
|
||||
<div class="livechat-viewer-mode-external-login">
|
||||
<button class="btn btn-primary" @click=${ev => {
|
||||
ev.preventDefault()
|
||||
api.modal.show('converse-livechat-external-login')
|
||||
}}>${i18nExternalLogin}</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
${tplSlowMode(o)}
|
||||
${tplMucBottomPanel(o)}`
|
||||
|
@ -1,18 +1,54 @@
|
||||
const prod = require('./webpack/webpack.build.js')
|
||||
const { merge } = require('webpack-merge')
|
||||
const webpack = require('webpack')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const locKeys = require('./loc.keys.js')
|
||||
|
||||
function loadLocs () {
|
||||
// Loading english strings, so we can inject them as constants.
|
||||
const refFile = path.resolve(__dirname, '..', '..', 'dist', 'languages', 'en.reference.json')
|
||||
if (!fs.existsSync(refFile)) {
|
||||
throw new Error('Missing english reference file, please run "npm run build:languages" before building ConverseJS')
|
||||
}
|
||||
const english = require(refFile)
|
||||
|
||||
const r = {}
|
||||
for (const key of locKeys) {
|
||||
if (!(key in english) || (typeof english[key] !== 'string')) {
|
||||
throw new Error('Missing english string key=' + key)
|
||||
}
|
||||
r['LOC_' + key] = JSON.stringify(english[key])
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
module.exports = merge(prod, {
|
||||
entry: path.resolve(__dirname, 'custom/entry.js'),
|
||||
output: {
|
||||
filename: 'converse.min.js'
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin(loadLocs())
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js'],
|
||||
alias: {
|
||||
'./templates/muc-bottom-panel.js': path.resolve('custom/templates/muc-bottom-panel.js'),
|
||||
'../../templates/background_logo.js$': path.resolve(__dirname, 'custom/templates/background_logo.js'),
|
||||
'shared/styles/index.scss$': path.resolve(__dirname, 'custom/shared/styles/livechat.scss')
|
||||
'shared/styles/index.scss$': path.resolve(__dirname, 'custom/shared/styles/livechat.scss'),
|
||||
'shared/modals/livechat-external-login.js': path.resolve(
|
||||
__dirname,
|
||||
'custom/shared/modals/livechat-external-login.js'
|
||||
),
|
||||
'templates/livechat-external-login-modal.js': path.resolve(
|
||||
__dirname,
|
||||
'custom/templates/livechat-external-login-modal.js'
|
||||
),
|
||||
'livechat-external-login-content.js': path.resolve(
|
||||
__dirname,
|
||||
'custom/livechat-external-login-content.js'
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -8,7 +8,10 @@ import type { AuthentInfos } from './auth'
|
||||
* @returns default parameters to provide to ConverseJS.
|
||||
*/
|
||||
function defaultConverseParams (
|
||||
{ forceReadonly, theme, assetsPath, room, forceDefaultHideMucParticipants, autofocus }: InitConverseJSParams
|
||||
{
|
||||
forceReadonly, theme, assetsPath, room, forceDefaultHideMucParticipants, autofocus,
|
||||
peertubeVideoOriginalUrl, peertubeVideoUUID
|
||||
}: InitConverseJSParams
|
||||
): any {
|
||||
const mucShowInfoMessages = forceReadonly
|
||||
? [
|
||||
@ -87,7 +90,10 @@ function defaultConverseParams (
|
||||
colorize_username: true,
|
||||
|
||||
// This is a specific settings, that is used in ConverseJS customization, to force avatars loading in readonly mode.
|
||||
livechat_load_all_vcards: !!forceReadonly
|
||||
livechat_load_all_vcards: !!forceReadonly,
|
||||
|
||||
livechat_peertube_video_original_url: peertubeVideoOriginalUrl,
|
||||
livechat_peertube_video_uuid: peertubeVideoUUID
|
||||
}
|
||||
|
||||
// TODO: params.clear_messages_on_reconnection = true when muc_mam will be available.
|
||||
|
@ -6,7 +6,9 @@ export const livechatViewerModePlugin = {
|
||||
const _converse = this._converse
|
||||
|
||||
_converse.api.settings.extend({
|
||||
livechat_enable_viewer_mode: false
|
||||
livechat_enable_viewer_mode: false,
|
||||
livechat_peertube_video_original_url: undefined,
|
||||
livechat_peertube_video_uuid: undefined
|
||||
})
|
||||
|
||||
const originalGetDefaultMUCNickname = _converse.getDefaultMUCNickname
|
||||
|
19
conversejs/loc.keys.js
Normal file
19
conversejs/loc.keys.js
Normal file
@ -0,0 +1,19 @@
|
||||
/** Localization keys to inject in ConverseJS:
|
||||
* these keys are used to:
|
||||
* - inject needed localization strings in ConverseJS language files
|
||||
* - defined global variable using Webpack, to retrieve the english key to pass to the ConverseJS localization function
|
||||
*/
|
||||
const locKeys = [
|
||||
'slow_mode_info',
|
||||
'login_using_external_account',
|
||||
'login_remote_peertube',
|
||||
'login_remote_peertube_searching',
|
||||
'login_remote_peertube_url',
|
||||
'login_remote_peertube_url_invalid',
|
||||
'login_remote_peertube_no_livechat',
|
||||
'login_remote_peertube_video_not_found',
|
||||
'login_remote_peertube_video_not_found_try_anyway',
|
||||
'login_remote_peertube_video_not_found_try_anyway_button'
|
||||
]
|
||||
|
||||
module.exports = locKeys
|
@ -392,3 +392,13 @@ invalid_value: "Invalid value."
|
||||
slow_mode_info: "Slow mode is enabled, users can send a message every %1$s seconds."
|
||||
|
||||
chatroom_not_accessible: "This chatroom does not exist, or is not accessible to you."
|
||||
|
||||
login_using_external_account: "Log in using an external account"
|
||||
login_remote_peertube: "Log in using an account on another Peertube instance:"
|
||||
login_remote_peertube_url: "Your Peertube instance URL"
|
||||
login_remote_peertube_searching: "Searching the video on the Peertube instance..."
|
||||
login_remote_peertube_url_invalid: "Invalid Peertube URL."
|
||||
login_remote_peertube_no_livechat: "The livechat plugin is not installed on this Peertube instance."
|
||||
login_remote_peertube_video_not_found: "This video is not available on this Peertube instance."
|
||||
login_remote_peertube_video_not_found_try_anyway: "In some cases, the video can still be retrieved if you connect to the remote instance."
|
||||
login_remote_peertube_video_not_found_try_anyway_button: "Try anyway to open the video on the Peertube instance"
|
||||
|
@ -77,6 +77,8 @@ async function getConverseJSParams (
|
||||
} = connectionInfos
|
||||
|
||||
return {
|
||||
peertubeVideoOriginalUrl: roomInfos.video?.url,
|
||||
peertubeVideoUUID: roomInfos.video?.uuid,
|
||||
staticBaseUrl,
|
||||
assetsPath: staticBaseUrl + 'conversejs/',
|
||||
isRemoteChat: !!(roomInfos.video?.remote),
|
||||
|
@ -1,6 +1,8 @@
|
||||
type ConverseJSTheme = 'peertube' | 'default' | 'concord'
|
||||
|
||||
interface InitConverseJSParams {
|
||||
peertubeVideoOriginalUrl?: string
|
||||
peertubeVideoUUID?: string
|
||||
isRemoteChat: boolean
|
||||
localAnonymousJID: string | null
|
||||
remoteAnonymousJID: string | null
|
||||
|
Loading…
x
Reference in New Issue
Block a user