diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90419e10..f8fb88fd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,10 @@
TODO: replace commit_id by a tag in build-conversejs
+### New features
+
* #143: User colors: implementing [XEP-0392](https://xmpp.org/extensions/xep-0392.html) to have random colors on users nicknames
+* Fullscreen chat: now uses a custom page (in other words: when opening the chat in a new tab, you will have the Peertube menu).
## 8.4.0
diff --git a/client/admin-plugin-client-plugin.ts b/client/admin-plugin-client-plugin.ts
index db3d1349..56682db4 100644
--- a/client/admin-plugin-client-plugin.ts
+++ b/client/admin-plugin-client-plugin.ts
@@ -1,22 +1,14 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import type { Video } from '@peertube/peertube-types'
import type { ProsodyListRoomsResult } from 'shared/lib/types'
+import { getBaseRoute } from './utils/uri'
interface ActionPluginSettingsParams {
npmName: string
}
-function register ({ registerHook, registerSettingsScript, peertubeHelpers }: RegisterClientOptions): void {
- function getBaseRoute (): string {
- // NB: this will come with Peertube > 3.2.1 (3.3.0?)
- if (peertubeHelpers.getBaseRouterRoute) {
- return peertubeHelpers.getBaseRouterRoute()
- }
- // We are guessing the route with the correct plugin version with this trick:
- const staticBase = peertubeHelpers.getBaseStaticRoute()
- // we can't use '/plugins/livechat/router', because the loaded html page needs correct relative paths.
- return staticBase.replace(/\/static.*$/, '/router')
- }
+function register (clientOptions: RegisterClientOptions): void {
+ const { registerHook, registerSettingsScript, peertubeHelpers } = clientOptions
registerHook({
target: 'action:admin-plugin-settings.init',
@@ -30,7 +22,7 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
diagButtons.forEach(diagButton => {
if (diagButton.hasAttribute('href')) { return }
// TODO: use a modal instead of a target=_blank
- diagButton.setAttribute('href', getBaseRoute() + '/settings/diagnostic')
+ diagButton.setAttribute('href', getBaseRoute(clientOptions) + '/settings/diagnostic')
diagButton.setAttribute('target', '_blank')
})
console.log('[peertube-plugin-livechat] Initializing prosody-list-rooms button')
@@ -71,7 +63,7 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
container.textContent = '...'
listRoomsButton.after(container)
- const response = await fetch(getBaseRoute() + '/webchat/prosody-list-rooms', {
+ const response = await fetch(getBaseRoute(clientOptions) + '/webchat/prosody-list-rooms', {
method: 'GET',
headers: peertubeHelpers.getAuthHeader()
})
@@ -168,7 +160,8 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
// Here we have a channel chat room
// The backend should have added informations here
// (because the Peertube API can't work with channelId...)
- const href = getBaseRoute() + '/webchat/room/' + encodeURIComponent(localpart) + '?forcetype=1'
+ const href = getBaseRoute(clientOptions) +
+ '/webchat/room/' + encodeURIComponent(localpart) + '?forcetype=1'
if (room.channel?.name) {
aEl.href = href // here we know that the channel still exists, so we can open the webchat.
const aVideoEl = document.createElement('a')
@@ -183,7 +176,8 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
} else if (/^[a-zA-A0-9-]+$/.test(localpart)) {
// localpart must be a video uuid.
const uuid = localpart
- const href = getBaseRoute() + '/webchat/room/' + encodeURIComponent(uuid) + '?forcetype=1'
+ const href = getBaseRoute(clientOptions) +
+ '/webchat/room/' + encodeURIComponent(uuid) + '?forcetype=1'
const p = fetch('/api/v1/videos/' + uuid, {
method: 'GET',
headers: peertubeHelpers.getAuthHeader()
diff --git a/client/common/configuration/register.ts b/client/common/configuration/register.ts
index d4c7f9b7..1d77316f 100644
--- a/client/common/configuration/register.ts
+++ b/client/common/configuration/register.ts
@@ -1,6 +1,9 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
+import type { InitConverseJSParams } from 'shared/lib/types'
import { renderConfigurationHome } from './templates/home'
import { renderConfigurationChannel } from './templates/channel'
+import { getBaseRoute } from '../../utils/uri'
+import { loadConverseJS } from '../../utils/conversejs'
/**
* Registers stuff related to the user's configuration pages.
@@ -12,6 +15,42 @@ async function registerConfiguration (clientOptions: RegisterClientOptions): Pro
const settings = await peertubeHelpers.getSettings()
if (settings['disable-channel-configuration']) { return }
+ registerClientRoute({
+ route: 'livechat/room',
+ onMount: async ({ rootEl }) => {
+ try {
+ const urlParams = new URLSearchParams(window.location.search)
+ const roomKey = urlParams.get('room')
+ if (!roomKey) {
+ throw new Error('missing room parameter')
+ }
+
+ const response = await fetch(
+ getBaseRoute(clientOptions) + '/api/configuration/room/' + encodeURIComponent(roomKey),
+ {
+ method: 'GET',
+ headers: peertubeHelpers.getAuthHeader()
+ }
+ )
+ if (!response.ok) {
+ throw new Error('Can\'t get channel configuration options.')
+ }
+
+ const converseJSParams: InitConverseJSParams = await (response).json()
+ await loadConverseJS(converseJSParams)
+
+ rootEl.innerHTML = `
+
+
`
+
+ window.initConverse(converseJSParams)
+ } catch (err) {
+ console.error('[peertube-plugin-livechat] ' + (err as string))
+ rootEl.innerText = await peertubeHelpers.translate(LOC_NOT_FOUND)
+ }
+ }
+ })
+
registerClientRoute({
route: 'livechat/configuration',
onMount: async ({ rootEl }) => {
diff --git a/client/common/configuration/templates/logic/channel.ts b/client/common/configuration/templates/logic/channel.ts
index 34ffecb8..5c98ece9 100644
--- a/client/common/configuration/templates/logic/channel.ts
+++ b/client/common/configuration/templates/logic/channel.ts
@@ -1,6 +1,6 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import type { ChannelConfiguration, ChannelConfigurationOptions } from 'shared/lib/types'
-import { getBaseRoute } from '../../../../videowatch/uri'
+import { getBaseRoute } from '../../../../utils/uri'
/**
* Returns the data that can be feed into the template view
diff --git a/client/utils/conversejs.ts b/client/utils/conversejs.ts
new file mode 100644
index 00000000..328233a0
--- /dev/null
+++ b/client/utils/conversejs.ts
@@ -0,0 +1,58 @@
+import type { InitConverseJSParams } from 'shared/lib/types'
+
+// declare global {
+// interface Window {
+// converse?: {
+// initialize: (args: any) => void
+// plugins: {
+// add: (name: string, plugin: any) => void
+// }
+// }
+// }
+// }
+
+declare global {
+ interface Window {
+ converse?: any
+ initConverse: Function
+ }
+}
+
+async function loadCSS (url: string): Promise
{
+ return new Promise((resolve, reject) => {
+ const css = document.createElement('link')
+ css.onerror = () => reject(new URIError(`CSS ${url} didn't load correctly.`))
+ css.onload = () => resolve()
+ css.setAttribute('type', 'text/css')
+ css.setAttribute('rel', 'stylesheet')
+ css.setAttribute('href', url)
+ document.head.appendChild(css)
+ })
+}
+
+async function loadScript (url: string): Promise {
+ return new Promise((resolve, reject) => {
+ const script = document.createElement('script')
+ script.onerror = () => reject(new URIError(`Script ${url} didn't load correctly.`))
+ script.onload = () => resolve()
+ script.async = true
+ script.src = url
+ document.head.appendChild(script)
+ })
+}
+
+async function loadConverseJS (converseJSParams: InitConverseJSParams): Promise {
+ if (!window.converse) {
+ await Promise.all([
+ loadCSS(converseJSParams.staticBaseUrl + 'conversejs/converse.min.css'),
+ loadScript(converseJSParams.staticBaseUrl + 'conversejs/converse.min.js')
+ ])
+ }
+ if (!window.initConverse) {
+ await loadScript(converseJSParams.staticBaseUrl + 'static/builtin.js')
+ }
+}
+
+export {
+ loadConverseJS
+}
diff --git a/client/utils/uri.ts b/client/utils/uri.ts
new file mode 100644
index 00000000..e39b73b1
--- /dev/null
+++ b/client/utils/uri.ts
@@ -0,0 +1,18 @@
+import type { RegisterClientOptions } from '@peertube/peertube-types/client'
+
+function getBaseRoute ({ peertubeHelpers }: RegisterClientOptions, permanent: boolean = false): string {
+ if (permanent) {
+ return '/plugins/livechat/router'
+ }
+ // NB: this will come with Peertube > 3.2.1 (3.3.0?)
+ if (peertubeHelpers.getBaseRouterRoute) {
+ return peertubeHelpers.getBaseRouterRoute()
+ }
+ // We are guessing the route with the correct plugin version with this trick:
+ const staticBase = peertubeHelpers.getBaseStaticRoute()
+ return staticBase.replace(/\/static.*$/, '/router')
+}
+
+export {
+ getBaseRoute
+}
diff --git a/client/videowatch/uri.ts b/client/videowatch/uri.ts
index ff1c565b..6f15895d 100644
--- a/client/videowatch/uri.ts
+++ b/client/videowatch/uri.ts
@@ -1,6 +1,7 @@
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
import type { Video } from '@peertube/peertube-types'
import { AutoColors, isAutoColorsAvailable } from 'shared/lib/autocolors'
+import { getBaseRoute } from '../utils/uri'
import { logger } from './logger'
import { computeAutoColors } from './colors'
@@ -11,19 +12,6 @@ interface UriOptions {
permanent?: boolean
}
-function getBaseRoute ({ peertubeHelpers }: RegisterClientOptions, permanent: boolean = false): string {
- if (permanent) {
- return '/plugins/livechat/router'
- }
- // NB: this will come with Peertube > 3.2.1 (3.3.0?)
- if (peertubeHelpers.getBaseRouterRoute) {
- return peertubeHelpers.getBaseRouterRoute()
- }
- // We are guessing the route with the correct plugin version with this trick:
- const staticBase = peertubeHelpers.getBaseStaticRoute()
- return staticBase.replace(/\/static.*$/, '/router')
-}
-
function getIframeUri (
registerOptions: RegisterClientOptions, settings: any, video: Video, uriOptions: UriOptions = {}
): string | null {
@@ -98,7 +86,6 @@ export type {
UriOptions
}
export {
- getBaseRoute,
getIframeUri,
getXMPPAddr
}
diff --git a/server/lib/conversejs/params.ts b/server/lib/conversejs/params.ts
index 57a7d896..e3dfaae7 100644
--- a/server/lib/conversejs/params.ts
+++ b/server/lib/conversejs/params.ts
@@ -1,5 +1,5 @@
import type { RegisterServerOptions, MVideoThumbnail, SettingEntries } from '@peertube/peertube-types'
-import type { ConverseJSTheme, InitConverseJSParams } from '../../../shared/lib/types'
+import type { ConverseJSTheme, InitConverseJSParams, InitConverseJSParamsError } from '../../../shared/lib/types'
import type { RegisterServerOptionsV5 } from '../helpers'
import type { LiveChatJSONLDAttributeV1 } from '../federation/types'
import { getChannelInfosById, getChannelNameById } from '../database/channel'
@@ -11,12 +11,6 @@ import { getBaseRouterRoute, getBaseStaticRoute } from '../helpers'
import { getProsodyDomain } from '../prosody/config/domain'
import { getBoshUri, getWSUri } from '../uri/webchat'
-interface InitConverseJSParamsError {
- isError: true
- code: 404 | 403 | 500
- message: string
-}
-
interface GetConverseJSParamsParams {
readonly?: boolean | 'noscroll'
transparent?: boolean
diff --git a/server/lib/routers/api/configuration.ts b/server/lib/routers/api/configuration.ts
index e42e72b1..9440c007 100644
--- a/server/lib/routers/api/configuration.ts
+++ b/server/lib/routers/api/configuration.ts
@@ -10,10 +10,24 @@ import {
storeChannelConfigurationOptions
} from '../../configuration/channel/storage'
import { sanitizeChannelConfigurationOptions } from '../../configuration/channel/sanitize'
+import { getConverseJSParams } from '../../../lib/conversejs/params'
async function initConfigurationApiRouter (options: RegisterServerOptions, router: Router): Promise {
const logger = options.peertubeHelpers.logger
+ router.get('/configuration/room/:roomKey', asyncMiddleware(
+ async (req: Request, res: Response, _next: NextFunction): Promise => {
+ const roomKey = req.params.roomKey
+ const initConverseJSParam = await getConverseJSParams(options, roomKey, {})
+ if (('isError' in initConverseJSParam) && initConverseJSParam.isError) {
+ res.sendStatus(initConverseJSParam.code)
+ return
+ }
+ res.status(200)
+ res.json(initConverseJSParam)
+ }
+ ))
+
router.get('/configuration/channel/:channelId', asyncMiddleware([
checkConfigurationEnabledMiddleware(options),
getCheckConfigurationChannelMiddleware(options),
diff --git a/shared/lib/types.ts b/shared/lib/types.ts
index f694d1c9..ed5d439d 100644
--- a/shared/lib/types.ts
+++ b/shared/lib/types.ts
@@ -21,6 +21,12 @@ interface InitConverseJSParams {
forceDefaultHideMucParticipants?: boolean
}
+interface InitConverseJSParamsError {
+ isError: true
+ code: 404 | 403 | 500
+ message: string
+}
+
interface ProsodyListRoomsResultError {
ok: false
error: string
@@ -87,6 +93,7 @@ interface ChannelConfiguration {
export type {
ConverseJSTheme,
InitConverseJSParams,
+ InitConverseJSParamsError,
ProsodyListRoomsResult,
ProsodyListRoomsResultRoom,
ChannelInfos,