odoo18/addons/website_jitsi/static/src/js/chat_room.js

267 lines
11 KiB
JavaScript

/** @odoo-module **/
import publicWidget from "@web/legacy/js/public/public_widget";
import { renderToElement } from "@web/core/utils/render";
import { utils as uiUtils } from "@web/core/ui/ui_service";
import { rpc } from "@web/core/network/rpc";
publicWidget.registry.ChatRoom = publicWidget.Widget.extend({
selector: '.o_wjitsi_room_widget',
events: {
'click .o_wjitsi_room_link': '_onChatRoomClick',
},
/**
* Manage the chat room (Jitsi), update the participant count...
*
* The widget takes some options
* - 'room-name', the name of the Jitsi room
* - 'chat-room-id', the ID of the `chat.room` record
* - 'auto-open', the chat room will be automatically opened when the page is loaded
* - 'check-full', check if the chat room is full before joining
* - 'attach-to', a JQuery selector of the element on which we will add the Jitsi
* iframe. If nothing is specified, it will open a modal instead.
* - 'default-username': the username to use in the chat room
* - 'jitsi-server': the domain name of the Jitsi server to use
*/
start: async function () {
await this._super.apply(this, arguments);
this.roomName = this.$el.data('room-name');
this.chatRoomId = parseInt(this.$el.data('chat-room-id'));
// automatically open the current room
this.autoOpen = parseInt(this.$el.data('auto-open') || 0);
// before joining, perform a RPC call to verify that the chat room is not full
this.checkFull = parseInt(this.$el.data('check-full') || 0);
// query selector of the element on which we attach the Jitsi iframe
// if not defined, the widget will pop in a modal instead
this.attachTo = this.$el.data('attach-to') || false;
// default username for jitsi
this.defaultUsername = this.$el.data('default-username') || false;
this.jitsiServer = this.$el.data('jitsi-server') || 'meet.jit.si';
this.maxCapacity = parseInt(this.$el.data('max-capacity')) || Infinity;
if (this.autoOpen) {
await this._onChatRoomClick();
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Click on a chat room to join it.
*
* @private
*/
_onChatRoomClick: async function () {
if (this.checkFull) {
// maybe we didn't refresh the page for a while and so we might join a room
// which is full, so we perform a RPC call to verify that we can really join
let isChatRoomFull = await rpc('/jitsi/is_full', { room_name: this.roomName });
if (isChatRoomFull) {
window.location.reload();
return;
}
}
if (await this._openMobileApplication(this.roomName)) {
// we opened the mobile application
return;
}
await this._loadJisti();
if (this.attachTo) {
// attach the Jitsi iframe on the given parent node
let $parentNode = $(this.attachTo);
$parentNode.find("iframe").trigger("empty");
$parentNode.empty();
await this._joinJitsiRoom($parentNode);
} else {
// create a model and append the Jitsi iframe in it
let $jitsiModal = $(renderToElement('chat_room_modal', {}));
$("body").append($jitsiModal);
$jitsiModal.modal('show');
let jitsiRoom = await this._joinJitsiRoom($jitsiModal.find('.modal-body'));
// close the modal when hanging up
jitsiRoom.addEventListener('videoConferenceLeft', async () => {
$('.o_wjitsi_room_modal').modal('hide');
});
// when the modal is closed, delete the Jitsi room object and clear the DOM
$jitsiModal.on('hidden.bs.modal', async () => {
jitsiRoom.dispose();
$(".o_wjitsi_room_modal").remove();
});
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Jitsi do not provide an REST API to get the number of participant in a room.
* The only way to get the number of participant is to be in the room and to use
* the Javascript API. So, to update the participant count on the server side,
* the participant have to send the count in RPC...
*
* When leaving a room, the event "participantLeft" is called for the current user
* once per participant in the room (like if all other participants were leaving the
* room and then the current user himself).
*
* "participantLeft" is called only one time for the other participant who are still
* in the room.
*
* We can not ask the user who is leaving the room to update the participant count
* because user might close their browser tab without hanging up (and so without
* triggering the event "videoConferenceLeft"). So, we wait for a moment (because the
* event "participantLeft" is called many time for the participant who is leaving)
* and the first participant send the new participant count (so we avoid spamming the
* server with HTTP requests).
*
* We use "setTimout" to send maximum one HTTP request per interval, even if multiple
* participants join/leave at the same time in the defined interval.
*
* Update on the 29 June 2020
*
* @private
* @param {jQuery} $jitsiModal, jQuery modal element in which we add the Jitsi room
* @returns {JitsiRoom} the newly created Jitsi room
*/
_joinJitsiRoom: async function ($parentNode) {
let jitsiRoom = await this._createJitsiRoom(this.roomName, $parentNode);
if (this.defaultUsername) {
jitsiRoom.executeCommand("displayName", this.defaultUsername);
}
let timeoutCall = null;
const updateParticipantCount = (joined) => {
this.allParticipantIds = Object.keys(jitsiRoom._participants).sort();
// if we reached the maximum capacity, update immediately the participant count
const timeoutTime = this.allParticipantIds.length >= this.maxCapacity ? 0 : 2000;
// we clear the old timeout to be sure to call it only once each 2 seconds
// (so if 2 participants join/leave in this interval, we will perform only
// one HTTP request for both).
clearTimeout(timeoutCall);
timeoutCall = setTimeout(() => {
this.allParticipantIds = Object.keys(jitsiRoom._participants).sort();
if (this.participantId === this.allParticipantIds[0]) {
// only the first participant of the room send the new participant
// count so we avoid to send to many HTTP requests
this._updateParticipantCount(this.allParticipantIds.length, joined);
}
}, timeoutTime);
};
jitsiRoom.addEventListener('participantJoined', () => updateParticipantCount(true));
jitsiRoom.addEventListener('participantLeft', () => updateParticipantCount(false));
// update the participant count when joining the room
jitsiRoom.addEventListener('videoConferenceJoined', async (event) => {
this.participantId = event.id;
updateParticipantCount(true);
$('.o_wjitsi_chat_room_loading').addClass('d-none');
// recheck if the room is not full
if (this.checkFull && this.allParticipantIds.length > this.maxCapacity) {
clearTimeout(timeoutCall);
jitsiRoom.executeCommand('hangup');
window.location.reload();
}
});
// update the participant count when using the "Leave" button
jitsiRoom.addEventListener('videoConferenceLeft', async (event) => {
this.allParticipantIds = Object.keys(jitsiRoom._participants)
if (!this.allParticipantIds.length) {
// bypass the checks and timer of updateParticipantCount
this._updateParticipantCount(this.allParticipantIds.length, false);
}
});
return jitsiRoom;
},
/**
* Perform an HTTP request to update the participant count on the server side.
*
* @private
* @param {integer} count, current number of participant in the room
* @param {boolean} joined, true if someone joined the room
*/
_updateParticipantCount: async function (count, joined) {
await rpc('/jitsi/update_status', {
room_name: this.roomName,
participant_count: count,
joined: joined,
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Redirect on the Jitsi mobile application if we are on mobile.
*
* @private
* @param {string} roomName
* @returns {boolean} true is we were redirected to the mobile application
*/
_openMobileApplication: async function (roomName) {
if (uiUtils.isSmall()) {
// we are on mobile, open the room in the application
window.location = `intent://${this.jitsiServer}/${encodeURIComponent(roomName)}#Intent;scheme=org.jitsi.meet;package=org.jitsi.meet;end`;
return true;
}
return false;
},
/**
* Create a Jitsi room on the given DOM element.
*
* @private
* @param {string} roomName
* @param {jQuery} $parentNode
* @returns {JitsiRoom} the newly created Jitsi room
*/
_createJitsiRoom: async function (roomName, $parentNode) {
await this._loadJisti();
const options = {
roomName: roomName,
width: "100%",
height: "100%",
parentNode: $parentNode[0],
configOverwrite: {disableDeepLinking: true},
};
return new window.JitsiMeetExternalAPI(this.jitsiServer, options);
},
/**
* Load the Jitsi external library if necessary.
*
* @private
*/
_loadJisti: async function () {
if (!window.JitsiMeetExternalAPI) {
await $.ajax({
url: `https://${this.jitsiServer}/external_api.js`,
dataType: "script",
});
}
},
});
export default publicWidget.registry.ChatRoom;