Browse Source

feat(file-sharing): Adds file sharing options. (#2829)

* feat(file-sharing): Adds file sharing options.

* squash: Adds missing jsdocs.

* squash: Updates docs for a backend change.

* squash: exports type IFileMetadata.
master
Дамян Минков 5 months ago
parent
commit
d1c4b9fa2b
No account linked to committer's email address

+ 9
- 0
JitsiConference.js View File

4315
         return this.room?.getBreakoutRooms();
4315
         return this.room?.getBreakoutRooms();
4316
     }
4316
     }
4317
 
4317
 
4318
+    /**
4319
+     * Returns the file sharing manager object.
4320
+     *
4321
+     * @returns {Object} the file sharing manager.
4322
+     */
4323
+    getFileSharing() {
4324
+        return this.room?.getFileSharing();
4325
+    }
4326
+
4318
     /**
4327
     /**
4319
      * Returns the metadata handler object.
4328
      * Returns the metadata handler object.
4320
      *
4329
      *

+ 8
- 0
JitsiConferenceEventManager.js View File

402
         this.chatRoomForwarder.forward(XMPPEvents.BREAKOUT_ROOMS_UPDATED,
402
         this.chatRoomForwarder.forward(XMPPEvents.BREAKOUT_ROOMS_UPDATED,
403
             JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED);
403
             JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED);
404
 
404
 
405
+        // File sharing
406
+        this.chatRoomForwarder.forward(XMPPEvents.FILE_SHARING_FILES_RECEIVED,
407
+            JitsiConferenceEvents.FILE_SHARING_FILES_RECEIVED);
408
+        this.chatRoomForwarder.forward(XMPPEvents.FILE_SHARING_FILE_ADDED,
409
+            JitsiConferenceEvents.FILE_SHARING_FILE_ADDED);
410
+        this.chatRoomForwarder.forward(XMPPEvents.FILE_SHARING_FILE_REMOVED,
411
+            JitsiConferenceEvents.FILE_SHARING_FILE_REMOVED);
412
+
405
         // Room metadata.
413
         // Room metadata.
406
         chatRoom.addListener(XMPPEvents.ROOM_METADATA_UPDATED, metadata => {
414
         chatRoom.addListener(XMPPEvents.ROOM_METADATA_UPDATED, metadata => {
407
             if (metadata.startMuted) {
415
             if (metadata.startMuted) {

+ 9
- 0
JitsiConferenceEvents.spec.ts View File

31
         ENCODE_TIME_STATS_RECEIVED,
31
         ENCODE_TIME_STATS_RECEIVED,
32
         ENDPOINT_MESSAGE_RECEIVED,
32
         ENDPOINT_MESSAGE_RECEIVED,
33
         ENDPOINT_STATS_RECEIVED,
33
         ENDPOINT_STATS_RECEIVED,
34
+        FILE_SHARING_FILES_RECEIVED,
35
+        FILE_SHARING_FILE_ADDED,
36
+        FILE_SHARING_FILE_REMOVED,
34
         JVB121_STATUS,
37
         JVB121_STATUS,
35
         KICKED,
38
         KICKED,
36
         PARTICIPANT_KICKED,
39
         PARTICIPANT_KICKED,
117
         expect( DTMF_SUPPORT_CHANGED ).toBe( 'conference.dtmfSupportChanged' );
120
         expect( DTMF_SUPPORT_CHANGED ).toBe( 'conference.dtmfSupportChanged' );
118
         expect( ENDPOINT_MESSAGE_RECEIVED ).toBe( 'conference.endpoint_message_received' );
121
         expect( ENDPOINT_MESSAGE_RECEIVED ).toBe( 'conference.endpoint_message_received' );
119
         expect( ENDPOINT_STATS_RECEIVED ).toBe( 'conference.endpoint_stats_received' );
122
         expect( ENDPOINT_STATS_RECEIVED ).toBe( 'conference.endpoint_stats_received' );
123
+        expect( FILE_SHARING_FILES_RECEIVED ).toBe( 'conference.file_sharing.files_received' );
124
+        expect( FILE_SHARING_FILE_ADDED ).toBe( 'conference.file_sharing.file_added' );
125
+        expect( FILE_SHARING_FILE_REMOVED ).toBe( 'conference.file_sharing.file_removed' );
120
         expect( JVB121_STATUS ).toBe( 'conference.jvb121Status' );
126
         expect( JVB121_STATUS ).toBe( 'conference.jvb121Status' );
121
         expect( KICKED ).toBe( 'conference.kicked' );
127
         expect( KICKED ).toBe( 'conference.kicked' );
122
         expect( PARTICIPANT_KICKED ).toBe( 'conference.participant_kicked' );
128
         expect( PARTICIPANT_KICKED ).toBe( 'conference.participant_kicked' );
201
         expect( JitsiConferenceEvents.ENCODE_TIME_STATS_RECEIVED ).toBe( 'conference.encode_time_stats_received' );
207
         expect( JitsiConferenceEvents.ENCODE_TIME_STATS_RECEIVED ).toBe( 'conference.encode_time_stats_received' );
202
         expect( JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED ).toBe( 'conference.endpoint_message_received' );
208
         expect( JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED ).toBe( 'conference.endpoint_message_received' );
203
         expect( JitsiConferenceEvents.ENDPOINT_STATS_RECEIVED ).toBe( 'conference.endpoint_stats_received' );
209
         expect( JitsiConferenceEvents.ENDPOINT_STATS_RECEIVED ).toBe( 'conference.endpoint_stats_received' );
210
+        expect( JitsiConferenceEvents.FILE_SHARING_FILES_RECEIVED ).toBe( 'conference.file_sharing.files_received' );
211
+        expect( JitsiConferenceEvents.FILE_SHARING_FILE_ADDED ).toBe( 'conference.file_sharing.file_added' );
212
+        expect( JitsiConferenceEvents.FILE_SHARING_FILE_REMOVED ).toBe( 'conference.file_sharing.file_removed' );
204
         expect( JitsiConferenceEvents.JVB121_STATUS ).toBe( 'conference.jvb121Status' );
213
         expect( JitsiConferenceEvents.JVB121_STATUS ).toBe( 'conference.jvb121Status' );
205
         expect( JitsiConferenceEvents.KICKED ).toBe( 'conference.kicked' );
214
         expect( JitsiConferenceEvents.KICKED ).toBe( 'conference.kicked' );
206
         expect( JitsiConferenceEvents.PARTICIPANT_KICKED ).toBe( 'conference.participant_kicked' );
215
         expect( JitsiConferenceEvents.PARTICIPANT_KICKED ).toBe( 'conference.participant_kicked' );

+ 22
- 0
JitsiConferenceEvents.ts View File

201
      */
201
      */
202
     ENDPOINT_STATS_RECEIVED = 'conference.endpoint_stats_received',
202
     ENDPOINT_STATS_RECEIVED = 'conference.endpoint_stats_received',
203
 
203
 
204
+    /**
205
+     * Event emitted when a list file is received in the conference. This event is fired when a participant joins
206
+     * and the file list is sent to them.
207
+     * @param {Map<String, IFileMetadata>} files - The map of files received in the conference with key the file ID.
208
+     */
209
+    FILE_SHARING_FILES_RECEIVED = 'conference.file_sharing.files_received',
210
+
211
+    /**
212
+     * Event emitted when a file is added to the conference.
213
+     * @param {IFileMetadata} file - The file object containing metadata about the file.
214
+     */
215
+    FILE_SHARING_FILE_ADDED = 'conference.file_sharing.file_added',
216
+
217
+    /**
218
+     * Event emitted when a file is removed from the conference.
219
+     * @param {String} fileId - The ID of the file that was removed.
220
+     */
221
+    FILE_SHARING_FILE_REMOVED = 'conference.file_sharing.file_removed',
222
+
204
     /**
223
     /**
205
      * The forwarded sources set is changed.
224
      * The forwarded sources set is changed.
206
      *
225
      *
549
 export const ENCODE_TIME_STATS_RECEIVED = JitsiConferenceEvents.ENCODE_TIME_STATS_RECEIVED;
568
 export const ENCODE_TIME_STATS_RECEIVED = JitsiConferenceEvents.ENCODE_TIME_STATS_RECEIVED;
550
 export const ENDPOINT_MESSAGE_RECEIVED = JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED;
569
 export const ENDPOINT_MESSAGE_RECEIVED = JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED;
551
 export const ENDPOINT_STATS_RECEIVED = JitsiConferenceEvents.ENDPOINT_STATS_RECEIVED;
570
 export const ENDPOINT_STATS_RECEIVED = JitsiConferenceEvents.ENDPOINT_STATS_RECEIVED;
571
+export const FILE_SHARING_FILES_RECEIVED = JitsiConferenceEvents.FILE_SHARING_FILES_RECEIVED;
572
+export const FILE_SHARING_FILE_ADDED = JitsiConferenceEvents.FILE_SHARING_FILE_ADDED;
573
+export const FILE_SHARING_FILE_REMOVED = JitsiConferenceEvents.FILE_SHARING_FILE_REMOVED;
552
 export const FORWARDED_SOURCES_CHANGED = JitsiConferenceEvents.FORWARDED_SOURCES_CHANGED;
574
 export const FORWARDED_SOURCES_CHANGED = JitsiConferenceEvents.FORWARDED_SOURCES_CHANGED;
553
 export const JVB121_STATUS = JitsiConferenceEvents.JVB121_STATUS;
575
 export const JVB121_STATUS = JitsiConferenceEvents.JVB121_STATUS;
554
 export const KICKED = JitsiConferenceEvents.KICKED;
576
 export const KICKED = JitsiConferenceEvents.KICKED;

+ 5
- 6
modules/xmpp/AVModeration.ts View File

27
  * The AVModeration logic.
27
  * The AVModeration logic.
28
  */
28
  */
29
 export default class AVModeration {
29
 export default class AVModeration {
30
-
31
-    /**
32
-     * Constructs AV moderation room.
33
-     *
34
-     * @param {ChatRoom} room the main room.
35
-     */
36
     private _xmpp: XMPP;
30
     private _xmpp: XMPP;
37
     private _mainRoom: ChatRoom;
31
     private _mainRoom: ChatRoom;
38
     private _moderationEnabledByType: IModerationEnabledByType;
32
     private _moderationEnabledByType: IModerationEnabledByType;
39
     private _whitelistAudio: string[];
33
     private _whitelistAudio: string[];
40
     private _whitelistVideo: string[];
34
     private _whitelistVideo: string[];
41
 
35
 
36
+    /**
37
+     * Constructs AV moderation room.
38
+     *
39
+     * @param {ChatRoom} room the main room.
40
+     */
42
     constructor(room: ChatRoom) {
41
     constructor(room: ChatRoom) {
43
         this._xmpp = room.xmpp;
42
         this._xmpp = room.xmpp;
44
 
43
 

+ 10
- 0
modules/xmpp/ChatRoom.js View File

18
 
18
 
19
 import AVModeration from './AVModeration';
19
 import AVModeration from './AVModeration';
20
 import BreakoutRooms from './BreakoutRooms';
20
 import BreakoutRooms from './BreakoutRooms';
21
+import FileSharing from './FileSharing';
21
 import Lobby from './Lobby';
22
 import Lobby from './Lobby';
22
 import RoomMetadata from './RoomMetadata';
23
 import RoomMetadata from './RoomMetadata';
23
 import XmppConnection from './XmppConnection';
24
 import XmppConnection from './XmppConnection';
195
         }
196
         }
196
         this.avModeration = new AVModeration(this);
197
         this.avModeration = new AVModeration(this);
197
         this.breakoutRooms = new BreakoutRooms(this);
198
         this.breakoutRooms = new BreakoutRooms(this);
199
+        this.fileSharing = new FileSharing(this);
198
         this.roomMetadata = new RoomMetadata(this);
200
         this.roomMetadata = new RoomMetadata(this);
199
         this.initPresenceMap(options);
201
         this.initPresenceMap(options);
200
         this.lastPresences = {};
202
         this.lastPresences = {};
1881
         return this.breakoutRooms;
1883
         return this.breakoutRooms;
1882
     }
1884
     }
1883
 
1885
 
1886
+    /**
1887
+     * @returns {FileSharing}
1888
+     */
1889
+    getFileSharing() {
1890
+        return this.fileSharing;
1891
+    }
1892
+
1884
     /**
1893
     /**
1885
      * @returns {RoomMetadata}
1894
      * @returns {RoomMetadata}
1886
      */
1895
      */
2011
     leave(reason) {
2020
     leave(reason) {
2012
         this.avModeration.dispose();
2021
         this.avModeration.dispose();
2013
         this.breakoutRooms.dispose();
2022
         this.breakoutRooms.dispose();
2023
+        this.fileSharing.dispose();
2014
         this.roomMetadata.dispose();
2024
         this.roomMetadata.dispose();
2015
 
2025
 
2016
         const promises = [];
2026
         const promises = [];

+ 168
- 0
modules/xmpp/FileSharing.ts View File

1
+import { XMPPEvents } from '../../service/xmpp/XMPPEvents';
2
+
3
+import ChatRoom from './ChatRoom';
4
+import XMPP from './xmpp';
5
+
6
+export const IDENTITY_TYPE = 'file-sharing';
7
+
8
+/**
9
+ * The file metadata used in file sharing.
10
+ * Fields like `authorParticipantId`, `authorParticipantJid`, and `authorParticipantName` and `conferenceFullName` will
11
+ * be set by the backend, so passing them is optional.
12
+ */
13
+export type IFileMetadata = {
14
+    /**
15
+     * The ID of the participant who uploaded the file.
16
+     */
17
+    authorParticipantId?: string;
18
+
19
+    /**
20
+     * The connection JID of the participant who uploaded the file.
21
+     */
22
+    authorParticipantJid?: string;
23
+
24
+    /**
25
+     * The name of the participant who uploaded the file.
26
+     */
27
+    authorParticipantName?: string;
28
+
29
+    /**
30
+     * The jid of the conference where the file was uploaded.
31
+     */
32
+    conferenceFullName?: string;
33
+
34
+    /**
35
+     * The unique ID of the file.
36
+     */
37
+    fileId: string;
38
+
39
+    /**
40
+     * The name of the file.
41
+     */
42
+    fileName: string;
43
+
44
+    /**
45
+     * The size of the file in bytes.
46
+     */
47
+    fileSize: number;
48
+
49
+    /**
50
+     * The file type (file extension).
51
+     */
52
+    fileType: string;
53
+
54
+    /**
55
+     * The time when it was uploaded.
56
+     */
57
+    timestamp: number;
58
+};
59
+
60
+/**
61
+ * The FileSharing logic.
62
+ */
63
+export default class FileSharing {
64
+    private _mainRoom: ChatRoom;
65
+    private _xmpp: XMPP;
66
+
67
+    /**
68
+     * Constructs File sharing manager for a room.
69
+     *
70
+     * @param {ChatRoom} room the main room.
71
+     */
72
+    constructor(room: ChatRoom) {
73
+        this._mainRoom = room;
74
+        this._xmpp = room.xmpp;
75
+
76
+        this._handleMessages = this._handleMessages.bind(this);
77
+        this._mainRoom.xmpp.addListener(XMPPEvents.FILE_SHARING_EVENT, this._handleMessages);
78
+    }
79
+
80
+    /**
81
+     * Stops listening for events.
82
+     */
83
+    dispose() {
84
+        this._mainRoom.xmpp.removeListener(XMPPEvents.FILE_SHARING_EVENT, this._handleMessages);
85
+    }
86
+
87
+    /**
88
+     * Whether AV moderation is supported on backend.
89
+     *
90
+     * @returns {boolean} whether AV moderation is supported on backend.
91
+     */
92
+    isSupported() {
93
+        return Boolean(this._xmpp.fileSharingComponentAddress);
94
+    }
95
+
96
+    /**
97
+     * Returns the file sharing identity type (service name).
98
+     *
99
+     * @returns {string} the file sharing service name.
100
+     */
101
+    getIdentityType() {
102
+        return IDENTITY_TYPE;
103
+    }
104
+
105
+    /**
106
+     * Adds a file to the file sharing component after the file has been uploaded.
107
+     * @param metadata - The metadata of the file to be added.
108
+     */
109
+    addFile(metadata: IFileMetadata) {
110
+        const message = {
111
+            type: 'add',
112
+            xmlns: 'http://jitsi.org/jitmeet'
113
+        };
114
+
115
+        this._sendMessage(message, metadata);
116
+    }
117
+
118
+    /**
119
+     * Removes a file from the file sharing component after the file was deleted.
120
+     * @param fileId - The file ID of the file to be removed.
121
+     */
122
+    removeFile(fileId: string) {
123
+        const message = {
124
+            type: 'remove',
125
+            fileId,
126
+            xmlns: 'http://jitsi.org/jitmeet'
127
+        };
128
+
129
+        this._sendMessage(message);
130
+    }
131
+
132
+    /**
133
+     * Helper to send a file sharing message to the component.
134
+     *
135
+     * @param {Object} message - Command that needs to be sent.
136
+     * @param {Object} content - The content to add to the element created if any.
137
+     */
138
+    _sendMessage(message: object, content?: object) {
139
+        const msg = $msg({ to: this._xmpp.fileSharingComponentAddress });
140
+
141
+        msg.c(IDENTITY_TYPE, message, content ? JSON.stringify(content) : undefined).up();
142
+
143
+        this._xmpp.connection.send(msg);
144
+    }
145
+
146
+    /**
147
+     * Handles a message for file sharing.
148
+     *
149
+     * @param {object} payload - Arbitrary data.
150
+     */
151
+    _handleMessages(payload) {
152
+        switch (payload.event) {
153
+        case 'add':
154
+
155
+            this._mainRoom.eventEmitter.emit(XMPPEvents.FILE_SHARING_FILE_ADDED, payload.file);
156
+
157
+            break;
158
+        case 'remove': {
159
+            this._mainRoom.eventEmitter.emit(XMPPEvents.FILE_SHARING_FILE_REMOVED, payload.fileId);
160
+            break;
161
+        }
162
+        case 'list': {
163
+            this._mainRoom.eventEmitter.emit(XMPPEvents.FILE_SHARING_FILES_RECEIVED, payload.files);
164
+            break;
165
+        }
166
+        }
167
+    }
168
+}

+ 8
- 0
modules/xmpp/xmpp.js View File

16
 import RandomUtil from '../util/RandomUtil';
16
 import RandomUtil from '../util/RandomUtil';
17
 
17
 
18
 import Caps, { parseDiscoInfo } from './Caps';
18
 import Caps, { parseDiscoInfo } from './Caps';
19
+import { IDENTITY_TYPE as FILE_SHARING_IDENTITY_TYPE } from './FileSharing';
19
 import XmppConnection from './XmppConnection';
20
 import XmppConnection from './XmppConnection';
20
 import Moderator from './moderator';
21
 import Moderator from './moderator';
21
 import './strophe.disco';
22
 import './strophe.disco';
525
                 }
526
                 }
526
             }
527
             }
527
 
528
 
529
+            if (identity.type === FILE_SHARING_IDENTITY_TYPE) {
530
+                this.fileSharingComponentAddress = identity.name;
531
+                this._components.push(this.fileSharingComponentAddress);
532
+            }
533
+
528
             if (identity.type === 'room_metadata') {
534
             if (identity.type === 'room_metadata') {
529
                 this.roomMetadataComponentAddress = identity.name;
535
                 this.roomMetadataComponentAddress = identity.name;
530
                 this._components.push(this.roomMetadataComponentAddress);
536
                 this._components.push(this.roomMetadataComponentAddress);
1090
             this.eventEmitter.emit(XMPPEvents.AV_MODERATION_RECEIVED, parsedJson);
1096
             this.eventEmitter.emit(XMPPEvents.AV_MODERATION_RECEIVED, parsedJson);
1091
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'breakout_rooms') {
1097
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'breakout_rooms') {
1092
             this.eventEmitter.emit(XMPPEvents.BREAKOUT_ROOMS_EVENT, parsedJson);
1098
             this.eventEmitter.emit(XMPPEvents.BREAKOUT_ROOMS_EVENT, parsedJson);
1099
+        } else if (parsedJson[JITSI_MEET_MUC_TYPE] === FILE_SHARING_IDENTITY_TYPE) {
1100
+            this.eventEmitter.emit(XMPPEvents.FILE_SHARING_EVENT, parsedJson);
1093
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'room_metadata') {
1101
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'room_metadata') {
1094
             this.eventEmitter.emit(XMPPEvents.ROOM_METADATA_EVENT, parsedJson);
1102
             this.eventEmitter.emit(XMPPEvents.ROOM_METADATA_EVENT, parsedJson);
1095
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'visitors') {
1103
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'visitors') {

+ 4
- 0
service/xmpp/XMPPEvents.spec.ts View File

32
         expect( XMPPEvents.EMUC_ROOM_ADDED ).toBe( 'xmpp.emuc_room_added' );
32
         expect( XMPPEvents.EMUC_ROOM_ADDED ).toBe( 'xmpp.emuc_room_added' );
33
         expect( XMPPEvents.EMUC_ROOM_REMOVED ).toBe( 'xmpp.emuc_room_removed' );
33
         expect( XMPPEvents.EMUC_ROOM_REMOVED ).toBe( 'xmpp.emuc_room_removed' );
34
         expect( XMPPEvents.ETHERPAD ).toBe( 'xmpp.etherpad' );
34
         expect( XMPPEvents.ETHERPAD ).toBe( 'xmpp.etherpad' );
35
+        expect( XMPPEvents.FILE_SHARING_EVENT ).toBe( 'xmpp.files-sharing.event' );
36
+        expect( XMPPEvents.FILE_SHARING_FILES_RECEIVED ).toBe( 'xmpp.files-sharing.list' );
37
+        expect( XMPPEvents.FILE_SHARING_FILE_ADDED ).toBe( 'xmpp.files-sharing.add' );
38
+        expect( XMPPEvents.FILE_SHARING_FILE_REMOVED ).toBe( 'xmpp.files-sharing.remove' );
35
         expect( XMPPEvents.FOCUS_DISCONNECTED ).toBe( 'xmpp.focus_disconnected' );
39
         expect( XMPPEvents.FOCUS_DISCONNECTED ).toBe( 'xmpp.focus_disconnected' );
36
         expect( XMPPEvents.FOCUS_LEFT ).toBe( 'xmpp.focus_left' );
40
         expect( XMPPEvents.FOCUS_LEFT ).toBe( 'xmpp.focus_left' );
37
         expect( XMPPEvents.GRACEFUL_SHUTDOWN ).toBe( 'xmpp.graceful_shutdown' );
41
         expect( XMPPEvents.GRACEFUL_SHUTDOWN ).toBe( 'xmpp.graceful_shutdown' );

+ 20
- 0
service/xmpp/XMPPEvents.ts View File

130
 
130
 
131
     ETHERPAD = 'xmpp.etherpad',
131
     ETHERPAD = 'xmpp.etherpad',
132
 
132
 
133
+    /**
134
+     * Event fired when we receive a message for files sharing.
135
+     */
136
+    FILE_SHARING_EVENT = 'xmpp.files-sharing.event',
137
+
138
+    /**
139
+     * Event emitted when a list file is received in the conference.
140
+     */
141
+    FILE_SHARING_FILES_RECEIVED = 'xmpp.files-sharing.list',
142
+
143
+    /**
144
+     * Event emitted when a file is added to the conference.
145
+     */
146
+    FILE_SHARING_FILE_ADDED = 'xmpp.files-sharing.add',
147
+
148
+    /**
149
+     * Event emitted when a file is removed from the conference.
150
+     */
151
+    FILE_SHARING_FILE_REMOVED = 'xmpp.files-sharing.remove',
152
+
133
     FOCUS_DISCONNECTED = 'xmpp.focus_disconnected',
153
     FOCUS_DISCONNECTED = 'xmpp.focus_disconnected',
134
 
154
 
135
     FOCUS_LEFT = 'xmpp.focus_left',
155
     FOCUS_LEFT = 'xmpp.focus_left',

Loading…
Cancel
Save