summaryrefslogtreecommitdiff
path: root/addons/website_jitsi/static/src/js/chat_room.js
blob: c704d1260dd20ea998ae65d03c15eada87be1b34 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
odoo.define('website_jitsi.chat_room', function (require) {
'use strict';

const config = require("web.config");
const core = require('web.core');
const Dialog = require('web.Dialog');
const publicWidget = require('web.public.widget');
const QWeb = core.qweb;
const _t = core._t;

publicWidget.registry.ChatRoom = publicWidget.Widget.extend({
    selector: '.o_wjitsi_room_widget',
    xmlDependencies: ['/website_jitsi/static/src/xml/chat_room_modal.xml'],
    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 this._rpc({
                route: '/jitsi/is_full',
                params: {
                    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 = $(QWeb.render('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 this._rpc({
            route: '/jitsi/update_status',
            params: {
                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 (config.device.isMobile) {
            // we are on mobile, open the room in the application
            window.location = `intent://${this.jitsiServer}/${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",
          });
      }
    },
});

return publicWidget.registry.ChatRoom;

});