diff --git a/conversejs/lib/plugins/livechat-specific.ts b/conversejs/lib/plugins/livechat-specific.ts
index b50d254e..ef62eaeb 100644
--- a/conversejs/lib/plugins/livechat-specific.ts
+++ b/conversejs/lib/plugins/livechat-specific.ts
@@ -2,6 +2,13 @@
//
// SPDX-License-Identifier: AGPL-3.0-only
+import { customizeHeading } from './livechat-specific/heading'
+import { customizeToolbar } from './livechat-specific/toolbar'
+import { initReconnectionStuff } from './livechat-specific/reconnection'
+import { chatRoomOverrides } from './livechat-specific/chatroom'
+import { chatRoomMessageOverrides } from './livechat-specific/chatroom-message'
+import { chatRoomOccupantsOverrides } from './livechat-specific/chatroom-occupants'
+
export const livechatSpecificsPlugin = {
dependencies: ['converse-muc', 'converse-muc-views'],
initialize: function (this: any) {
@@ -14,166 +21,15 @@ export const livechatSpecificsPlugin = {
livechat_specific_is_anonymous: false
})
- _converse.api.listen.on('getHeadingButtons', (view: any, buttons: any[]) => {
- if (view.model.get('type') !== _converse.constants.CHATROOMS_TYPE) {
- // only on MUC.
- return buttons
- }
-
- if (_converse.api.settings.get('livechat_specific_external_authent')) {
- // Adding a logout button
- buttons.push({
- i18n_text: _converse.__('Log out'),
- handler: async (ev: Event) => {
- ev.preventDefault()
- ev.stopPropagation()
-
- const messages = [_converse.__('Are you sure you want to leave this groupchat?')]
- const result = await _converse.api.confirm(_converse.__('Confirm'), messages)
- if (!result) { return }
-
- // Deleting access token in sessionStorage.
- window.sessionStorage.removeItem('peertube-plugin-livechat-external-auth-oidc-token')
-
- const reconnectMode = _converse.api.settings.get('livechat_external_auth_reconnect_mode')
- if (reconnectMode === 'button-close-open') {
- const button = document.getElementsByClassName('peertube-plugin-livechat-button-close')[0]
- if ((button as HTMLAnchorElement).click) { (button as HTMLAnchorElement).click() }
- return
- }
-
- window.location.reload()
- },
- a_class: 'close-chatbox-button',
- icon_class: 'fa-sign-out-alt',
- name: 'signout'
- })
- }
-
- return buttons
- })
-
- _converse.api.listen.on('getToolbarButtons', (toolbarEl: any, buttons: any[]) => {
- // We will replace the toggle occupant button, to change its appearance.
- // First, we must find it. We search from the end, because usually it is the last one.
- let toggleOccupantButton: any
- for (const button of buttons.reverse()) {
- if (button.strings?.find((s: string) => s.includes('toggle_occupants'))) { // searching the classname
- console.debug('[livechatSpecificsPlugin] found the toggle occupants button', button)
- toggleOccupantButton = button
- break
- }
- }
- if (!toggleOccupantButton) {
- console.debug('[livechatSpecificsPlugin] Did not found the toggle occupants button')
- return buttons
- }
-
- buttons = buttons.filter(b => b !== toggleOccupantButton)
- // Replacing by the new button...
- // Note: we don't need to test conditions, we know the button was here.
- const i18nHideOccupants = _converse.__('Hide participants')
- const i18nShowOccupants = _converse.__('Show participants')
- const html = window.converse.env.html
- const icon = toolbarEl.hidden_occupants
- ? html`
-
-
- `
- : html`
-
-
- `
- buttons.push(html`
- `
- )
- return buttons
- })
-
- // Overriding the MUCHeading custom element, to customize the destroyMUC function:
- const MUCHeading = _converse.api.elements.registry['converse-muc-heading']
- if (MUCHeading) {
- class MUCHeadingOverloaded extends MUCHeading {
- async destroy (ev: Event): Promise {
- ev.preventDefault()
- await destroyMUC(_converse, this.model) // here we call a custom version of destroyMUC
- }
- }
- _converse.api.elements.define('converse-muc-heading', MUCHeadingOverloaded)
- }
+ customizeHeading(this)
+ customizeToolbar(this)
_converse.api.listen.on('chatRoomViewInitialized', function (this: any, _model: any): void {
// Remove the spinner if present...
document.getElementById('livechat-loading-spinner')?.remove()
})
- // Adding a method on window.converse, so we can close the chat on navigation-end event
- // (when chatIncludeMode is peertube-*)
- window.converse.livechatDisconnect = function livechatDisconnect () {
- if (_converse.api.connection.connected()) {
- console.log('[livechatSpecificsPlugin] disconnecting converseJS...')
- _converse.api.user.logout()
- }
- }
-
- // To reconnect ConverseJS when joining another room (or the same one),
- // we store the relevant closure function:
- window.reconnectConverse = function reconnectConverse (params: any): void {
- console.log('[livechatSpecificsPlugin] reconnecting converseJS...')
-
- // The new room to join:
- _converse.api.settings.set('auto_join_rooms', params.auto_join_rooms)
- _converse.api.settings.set('notify_all_room_messages', params.notify_all_room_messages)
-
- // update connection parameters (in case the user logged in after the first chat)
- for (const k of [
- 'bosh_service_url', 'websocket_url',
- 'authentication', 'nickname', 'muc_nickname_from_jid', 'auto_login', 'jid', 'password', 'keepalive'
- ]) {
- _converse.api.settings.set(k, params[k])
- }
-
- // update other settings
- for (const k of [
- 'hide_muc_participants',
- 'livechat_enable_viewer_mode',
- 'livechat_external_auth_oidc_buttons',
- 'livechat_external_auth_reconnect_mode',
- 'livechat_mini_muc_head',
- 'livechat_specific_external_authent',
- 'livechat_task_app_enabled',
- 'livechat_task_app_restore',
- 'livechat_custom_emojis_url',
- 'emoji_categories'
- ]) {
- _converse.api.settings.set(k, params[k])
- }
-
- // We also unload emojis, in case there are custom emojis.
- window.converse.emojis = {
- initialized: false,
- initialized_promise: getOpenPromise()
- }
-
- // Then login.
- _converse.api.user.login()
- }
+ initReconnectionStuff(this)
if (window.location.protocol === 'http:') {
// We are probably on a dev instance, so we will add _converse in window:
@@ -181,99 +37,8 @@ export const livechatSpecificsPlugin = {
}
},
overrides: {
- ChatRoom: {
- getActionInfoMessage: function getActionInfoMessage (this: any, code: string, nick: string, actor: any): any {
- if (code === '303') {
- // When there is numerous anonymous users joining at the same time,
- // they can all change their nicknames at the same time, generating a log of action messages.
- // To mitigate this, will don't display nickname changes if the previous nick is something like
- // 'Anonymous 12345'.
- if (/^Anonymous \d+$/.test(nick)) {
- // To avoid displaying the message, we just have to return an empty one
- // (createInfoMessage will ignore if !data.message).
- return null
- }
- }
- return this.__super__.getActionInfoMessage(code, nick, actor)
- },
- canPostMessages: function canPostMessages (this: any) {
- // ConverseJS does not handle properly the visitor role in unmoderated rooms.
- // See https://github.com/conversejs/converse.js/issues/3428 for more info.
- // FIXME: if #3428 is fixed, remove this override.
- return this.isEntered() && this.getOwnRole() !== 'visitor'
- }
- },
- ChatRoomMessage: {
- /* By default, ConverseJS groups messages from the same users for a 10 minutes period.
- * This make no sense in a livechat room. So we override isFollowup to ignore. */
- isFollowup: function isFollowup () { return false }
- },
- ChatRoomOccupants: {
- comparator: function (this: any, occupant1: any, occupant2: any): Number {
- // Overriding Occupants comparators, to display anonymous users at the end of the list.
- const nick1: string = occupant1.getDisplayName()
- const nick2: string = occupant2.getDisplayName()
- const b1 = nick1.startsWith('Anonymous ')
- const b2 = nick2.startsWith('Anonymous ')
- if (b1 === b2) {
- // Both startswith anonymous, or non of it: fallback to the standard comparator.
- return this.__super__.comparator(occupant1, occupant2)
- }
- // Else: Anonymous always last.
- return b1 ? 1 : -1
- }
- }
- }
-}
-
-// FIXME: this function is copied from @converse. Should not do so.
-function getOpenPromise (): any {
- const wrapper: any = {
- isResolved: false,
- isPending: true,
- isRejected: false
- }
- const promise: any = new Promise((resolve, reject) => {
- wrapper.resolve = resolve
- wrapper.reject = reject
- })
- Object.assign(promise, wrapper)
- promise.then(
- function (v: any) {
- promise.isResolved = true
- promise.isPending = false
- promise.isRejected = false
- return v
- },
- function (e: any) {
- promise.isResolved = false
- promise.isPending = false
- promise.isRejected = true
- throw (e)
- }
- )
- return promise
-}
-
-async function destroyMUC (_converse: any, model: any): Promise {
- const __ = _converse.__
- const messages = [__('Are you sure you want to destroy this groupchat?')]
- // Note: challenge and newjid make no sens for peertube-plugin-livechat,
- // we remove them comparing to the original function.
- let fields = [
- {
- name: 'reason',
- label: __('Optional reason for destroying this groupchat'),
- placeholder: __('Reason'),
- value: undefined
- }
- ]
- try {
- fields = await _converse.api.confirm(__('Confirm'), messages, fields)
- const reason = fields.filter(f => f.name === 'reason').pop()?.value
- const newjid = undefined
- return model.sendDestroyIQ(reason, newjid).then(() => model.close())
- } catch (e) {
- console.error(e)
+ ChatRoom: chatRoomOverrides(),
+ ChatRoomMessage: chatRoomMessageOverrides(),
+ ChatRoomOccupants: chatRoomOccupantsOverrides()
}
}
diff --git a/conversejs/lib/plugins/livechat-specific/chatroom-message.ts b/conversejs/lib/plugins/livechat-specific/chatroom-message.ts
new file mode 100644
index 00000000..c34ddff7
--- /dev/null
+++ b/conversejs/lib/plugins/livechat-specific/chatroom-message.ts
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+export function chatRoomMessageOverrides (): {[key: string]: Function} {
+ return {
+ /* By default, ConverseJS groups messages from the same users for a 10 minutes period.
+ * This make no sense in a livechat room. So we override isFollowup to ignore. */
+ isFollowup: function isFollowup () { return false }
+ }
+}
diff --git a/conversejs/lib/plugins/livechat-specific/chatroom-occupants.ts b/conversejs/lib/plugins/livechat-specific/chatroom-occupants.ts
new file mode 100644
index 00000000..51685231
--- /dev/null
+++ b/conversejs/lib/plugins/livechat-specific/chatroom-occupants.ts
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+export function chatRoomOccupantsOverrides (): {[key: string]: Function} {
+ return {
+ comparator: function (this: any, occupant1: any, occupant2: any): Number {
+ // Overriding Occupants comparators, to display anonymous users at the end of the list.
+ const nick1: string = occupant1.getDisplayName()
+ const nick2: string = occupant2.getDisplayName()
+ const b1 = nick1.startsWith('Anonymous ')
+ const b2 = nick2.startsWith('Anonymous ')
+ if (b1 === b2) {
+ // Both startswith anonymous, or non of it: fallback to the standard comparator.
+ return this.__super__.comparator(occupant1, occupant2)
+ }
+ // Else: Anonymous always last.
+ return b1 ? 1 : -1
+ }
+ }
+}
diff --git a/conversejs/lib/plugins/livechat-specific/chatroom.ts b/conversejs/lib/plugins/livechat-specific/chatroom.ts
new file mode 100644
index 00000000..b41871ab
--- /dev/null
+++ b/conversejs/lib/plugins/livechat-specific/chatroom.ts
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+export function chatRoomOverrides (): {[key: string]: Function} {
+ return {
+ getActionInfoMessage: function getActionInfoMessage (this: any, code: string, nick: string, actor: any): any {
+ if (code === '303') {
+ // When there is numerous anonymous users joining at the same time,
+ // they can all change their nicknames at the same time, generating a log of action messages.
+ // To mitigate this, will don't display nickname changes if the previous nick is something like
+ // 'Anonymous 12345'.
+ if (/^Anonymous \d+$/.test(nick)) {
+ // To avoid displaying the message, we just have to return an empty one
+ // (createInfoMessage will ignore if !data.message).
+ return null
+ }
+ }
+ return this.__super__.getActionInfoMessage(code, nick, actor)
+ },
+ canPostMessages: function canPostMessages (this: any) {
+ // ConverseJS does not handle properly the visitor role in unmoderated rooms.
+ // See https://github.com/conversejs/converse.js/issues/3428 for more info.
+ // FIXME: if #3428 is fixed, remove this override.
+ return this.isEntered() && this.getOwnRole() !== 'visitor'
+ }
+ }
+}
diff --git a/conversejs/lib/plugins/livechat-specific/heading.ts b/conversejs/lib/plugins/livechat-specific/heading.ts
new file mode 100644
index 00000000..38170d14
--- /dev/null
+++ b/conversejs/lib/plugins/livechat-specific/heading.ts
@@ -0,0 +1,75 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { destroyMUC } from './utils'
+
+/**
+ * Do some customization on MUCHeading:
+ * * adds a logout button for users that are authenticated with an external account
+ * * change the destroyMUC handler
+ *
+ * @param plugin The plugin object
+ */
+export function customizeHeading (plugin: any): void {
+ const _converse = plugin._converse
+ _converse.api.listen.on('getHeadingButtons', getHeadingButtons.bind(plugin))
+ overrideMUCHeadingElement(_converse)
+}
+
+function getHeadingButtons (this: any, view: any, buttons: any[]): any {
+ const _converse = this._converse
+
+ if (view.model.get('type') !== _converse.constants.CHATROOMS_TYPE) {
+ // only on MUC.
+ return buttons
+ }
+
+ if (_converse.api.settings.get('livechat_specific_external_authent')) {
+ // Adding a logout button
+ buttons.push({
+ i18n_text: _converse.__('Log out'),
+ handler: async (ev: Event) => {
+ ev.preventDefault()
+ ev.stopPropagation()
+
+ const messages = [_converse.__('Are you sure you want to leave this groupchat?')]
+ const result = await _converse.api.confirm(_converse.__('Confirm'), messages)
+ if (!result) { return }
+
+ // Deleting access token in sessionStorage.
+ window.sessionStorage.removeItem('peertube-plugin-livechat-external-auth-oidc-token')
+
+ const reconnectMode = _converse.api.settings.get('livechat_external_auth_reconnect_mode')
+ if (reconnectMode === 'button-close-open') {
+ const button = document.getElementsByClassName('peertube-plugin-livechat-button-close')[0]
+ if ((button as HTMLAnchorElement).click) { (button as HTMLAnchorElement).click() }
+ return
+ }
+
+ window.location.reload()
+ },
+ a_class: 'close-chatbox-button',
+ icon_class: 'fa-sign-out-alt',
+ name: 'signout'
+ })
+ }
+
+ return buttons
+}
+
+/**
+ * Override the MUCHeading custom element, to customize the destroyMUC function.
+ */
+function overrideMUCHeadingElement (_converse: any): void {
+ const MUCHeading = _converse.api.elements.registry['converse-muc-heading']
+ if (MUCHeading) {
+ class MUCHeadingOverloaded extends MUCHeading {
+ async destroy (ev: Event): Promise {
+ ev.preventDefault()
+ await destroyMUC(_converse, this.model) // here we call a custom version of destroyMUC
+ }
+ }
+ _converse.api.elements.define('converse-muc-heading', MUCHeadingOverloaded)
+ }
+}
diff --git a/conversejs/lib/plugins/livechat-specific/reconnection.ts b/conversejs/lib/plugins/livechat-specific/reconnection.ts
new file mode 100644
index 00000000..b75bf488
--- /dev/null
+++ b/conversejs/lib/plugins/livechat-specific/reconnection.ts
@@ -0,0 +1,66 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import { getOpenPromise } from './utils'
+
+/**
+ * Initialiaze some function on `window` that will be used for the reconnection process.
+ *
+ * @param plugin The plugin object
+ */
+export function initReconnectionStuff (plugin: any): void {
+ const _converse = plugin._converse
+
+ // Adding a method on window.converse, so we can close the chat on navigation-end event
+ // (when chatIncludeMode is peertube-*)
+ window.converse.livechatDisconnect = function livechatDisconnect () {
+ if (_converse.api.connection.connected()) {
+ console.log('[livechatSpecificsPlugin] disconnecting converseJS...')
+ _converse.api.user.logout()
+ }
+ }
+
+ // To reconnect ConverseJS when joining another room (or the same one),
+ // we store the relevant closure function:
+ window.reconnectConverse = function reconnectConverse (params: any): void {
+ console.log('[livechatSpecificsPlugin] reconnecting converseJS...')
+
+ // The new room to join:
+ _converse.api.settings.set('auto_join_rooms', params.auto_join_rooms)
+ _converse.api.settings.set('notify_all_room_messages', params.notify_all_room_messages)
+
+ // update connection parameters (in case the user logged in after the first chat)
+ for (const k of [
+ 'bosh_service_url', 'websocket_url',
+ 'authentication', 'nickname', 'muc_nickname_from_jid', 'auto_login', 'jid', 'password', 'keepalive'
+ ]) {
+ _converse.api.settings.set(k, params[k])
+ }
+
+ // update other settings
+ for (const k of [
+ 'hide_muc_participants',
+ 'livechat_enable_viewer_mode',
+ 'livechat_external_auth_oidc_buttons',
+ 'livechat_external_auth_reconnect_mode',
+ 'livechat_mini_muc_head',
+ 'livechat_specific_external_authent',
+ 'livechat_task_app_enabled',
+ 'livechat_task_app_restore',
+ 'livechat_custom_emojis_url',
+ 'emoji_categories'
+ ]) {
+ _converse.api.settings.set(k, params[k])
+ }
+
+ // We also unload emojis, in case there are custom emojis.
+ window.converse.emojis = {
+ initialized: false,
+ initialized_promise: getOpenPromise()
+ }
+
+ // Then login.
+ _converse.api.user.login()
+ }
+}
diff --git a/conversejs/lib/plugins/livechat-specific/toolbar.ts b/conversejs/lib/plugins/livechat-specific/toolbar.ts
new file mode 100644
index 00000000..bbe4a6d0
--- /dev/null
+++ b/conversejs/lib/plugins/livechat-specific/toolbar.ts
@@ -0,0 +1,69 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ * Do some customization on the toolbar:
+ * * change the appearance of the toggle occupants button
+ *
+ * @param plugin The plugin object
+ */
+export function customizeToolbar (plugin: any): void {
+ const _converse = plugin._converse
+ _converse.api.listen.on('getToolbarButtons', getToolbarButtons.bind(plugin))
+}
+
+function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): any {
+ const _converse = this._converse
+
+ // We will replace the toggle occupant button, to change its appearance.
+ // First, we must find it. We search from the end, because usually it is the last one.
+ let toggleOccupantButton: any
+ for (const button of buttons.reverse()) {
+ if (button.strings?.find((s: string) => s.includes('toggle_occupants'))) { // searching the classname
+ console.debug('[livechatSpecificsPlugin] found the toggle occupants button', button)
+ toggleOccupantButton = button
+ break
+ }
+ }
+ if (!toggleOccupantButton) {
+ console.debug('[livechatSpecificsPlugin] Did not found the toggle occupants button')
+ return buttons
+ }
+
+ buttons = buttons.filter(b => b !== toggleOccupantButton)
+ // Replacing by the new button...
+ // Note: we don't need to test conditions, we know the button was here.
+ const i18nHideOccupants = _converse.__('Hide participants')
+ const i18nShowOccupants = _converse.__('Show participants')
+ const html = window.converse.env.html
+ const icon = toolbarEl.hidden_occupants
+ ? html`
+
+
+ `
+ : html`
+
+
+ `
+ buttons.push(html`
+ `
+ )
+ return buttons
+}
diff --git a/conversejs/lib/plugins/livechat-specific/utils.ts b/conversejs/lib/plugins/livechat-specific/utils.ts
new file mode 100644
index 00000000..14644041
--- /dev/null
+++ b/conversejs/lib/plugins/livechat-specific/utils.ts
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2024 John Livingston
+//
+// SPDX-License-Identifier: AGPL-3.0-only
+
+// FIXME: this function is copied from @converse. Should not do so.
+export function getOpenPromise (): any {
+ const wrapper: any = {
+ isResolved: false,
+ isPending: true,
+ isRejected: false
+ }
+ const promise: any = new Promise((resolve, reject) => {
+ wrapper.resolve = resolve
+ wrapper.reject = reject
+ })
+ Object.assign(promise, wrapper)
+ promise.then(
+ function (v: any) {
+ promise.isResolved = true
+ promise.isPending = false
+ promise.isRejected = false
+ return v
+ },
+ function (e: any) {
+ promise.isResolved = false
+ promise.isPending = false
+ promise.isRejected = true
+ throw (e)
+ }
+ )
+ return promise
+}
+
+export async function destroyMUC (_converse: any, model: any): Promise {
+ const __ = _converse.__
+ const messages = [__('Are you sure you want to destroy this groupchat?')]
+ // Note: challenge and newjid make no sens for peertube-plugin-livechat,
+ // we remove them comparing to the original function.
+ let fields = [
+ {
+ name: 'reason',
+ label: __('Optional reason for destroying this groupchat'),
+ placeholder: __('Reason'),
+ value: undefined
+ }
+ ]
+ try {
+ fields = await _converse.api.confirm(__('Confirm'), messages, fields)
+ const reason = fields.filter(f => f.name === 'reason').pop()?.value
+ const newjid = undefined
+ return model.sendDestroyIQ(reason, newjid).then(() => model.close())
+ } catch (e) {
+ console.error(e)
+ }
+}