Преглед на файлове

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 месеца
родител
ревизия
d1c4b9fa2b
No account linked to committer's email address

+ 9
- 0
JitsiConference.js Целия файл

@@ -4315,6 +4315,15 @@ export default class JitsiConference {
4315 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 4328
      * Returns the metadata handler object.
4320 4329
      *

+ 8
- 0
JitsiConferenceEventManager.js Целия файл

@@ -402,6 +402,14 @@ export default class JitsiConferenceEventManager {
402 402
         this.chatRoomForwarder.forward(XMPPEvents.BREAKOUT_ROOMS_UPDATED,
403 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 413
         // Room metadata.
406 414
         chatRoom.addListener(XMPPEvents.ROOM_METADATA_UPDATED, metadata => {
407 415
             if (metadata.startMuted) {

+ 9
- 0
JitsiConferenceEvents.spec.ts Целия файл

@@ -31,6 +31,9 @@ describe( "/JitsiConferenceEvents members", () => {
31 31
         ENCODE_TIME_STATS_RECEIVED,
32 32
         ENDPOINT_MESSAGE_RECEIVED,
33 33
         ENDPOINT_STATS_RECEIVED,
34
+        FILE_SHARING_FILES_RECEIVED,
35
+        FILE_SHARING_FILE_ADDED,
36
+        FILE_SHARING_FILE_REMOVED,
34 37
         JVB121_STATUS,
35 38
         KICKED,
36 39
         PARTICIPANT_KICKED,
@@ -117,6 +120,9 @@ describe( "/JitsiConferenceEvents members", () => {
117 120
         expect( DTMF_SUPPORT_CHANGED ).toBe( 'conference.dtmfSupportChanged' );
118 121
         expect( ENDPOINT_MESSAGE_RECEIVED ).toBe( 'conference.endpoint_message_received' );
119 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 126
         expect( JVB121_STATUS ).toBe( 'conference.jvb121Status' );
121 127
         expect( KICKED ).toBe( 'conference.kicked' );
122 128
         expect( PARTICIPANT_KICKED ).toBe( 'conference.participant_kicked' );
@@ -201,6 +207,9 @@ describe( "/JitsiConferenceEvents members", () => {
201 207
         expect( JitsiConferenceEvents.ENCODE_TIME_STATS_RECEIVED ).toBe( 'conference.encode_time_stats_received' );
202 208
         expect( JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED ).toBe( 'conference.endpoint_message_received' );
203 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 213
         expect( JitsiConferenceEvents.JVB121_STATUS ).toBe( 'conference.jvb121Status' );
205 214
         expect( JitsiConferenceEvents.KICKED ).toBe( 'conference.kicked' );
206 215
         expect( JitsiConferenceEvents.PARTICIPANT_KICKED ).toBe( 'conference.participant_kicked' );

+ 22
- 0
JitsiConferenceEvents.ts Целия файл

@@ -201,6 +201,25 @@ export enum JitsiConferenceEvents {
201 201
      */
202 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 224
      * The forwarded sources set is changed.
206 225
      *
@@ -549,6 +568,9 @@ export const E2EE_VERIFICATION_READY = JitsiConferenceEvents.E2EE_VERIFICATION_R
549 568
 export const ENCODE_TIME_STATS_RECEIVED = JitsiConferenceEvents.ENCODE_TIME_STATS_RECEIVED;
550 569
 export const ENDPOINT_MESSAGE_RECEIVED = JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED;
551 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 574
 export const FORWARDED_SOURCES_CHANGED = JitsiConferenceEvents.FORWARDED_SOURCES_CHANGED;
553 575
 export const JVB121_STATUS = JitsiConferenceEvents.JVB121_STATUS;
554 576
 export const KICKED = JitsiConferenceEvents.KICKED;

+ 5
- 6
modules/xmpp/AVModeration.ts Целия файл

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

+ 10
- 0
modules/xmpp/ChatRoom.js Целия файл

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

+ 168
- 0
modules/xmpp/FileSharing.ts Целия файл

@@ -0,0 +1,168 @@
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 Целия файл

@@ -16,6 +16,7 @@ import Listenable from '../util/Listenable';
16 16
 import RandomUtil from '../util/RandomUtil';
17 17
 
18 18
 import Caps, { parseDiscoInfo } from './Caps';
19
+import { IDENTITY_TYPE as FILE_SHARING_IDENTITY_TYPE } from './FileSharing';
19 20
 import XmppConnection from './XmppConnection';
20 21
 import Moderator from './moderator';
21 22
 import './strophe.disco';
@@ -525,6 +526,11 @@ export default class XMPP extends Listenable {
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 534
             if (identity.type === 'room_metadata') {
529 535
                 this.roomMetadataComponentAddress = identity.name;
530 536
                 this._components.push(this.roomMetadataComponentAddress);
@@ -1090,6 +1096,8 @@ export default class XMPP extends Listenable {
1090 1096
             this.eventEmitter.emit(XMPPEvents.AV_MODERATION_RECEIVED, parsedJson);
1091 1097
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'breakout_rooms') {
1092 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 1101
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'room_metadata') {
1094 1102
             this.eventEmitter.emit(XMPPEvents.ROOM_METADATA_EVENT, parsedJson);
1095 1103
         } else if (parsedJson[JITSI_MEET_MUC_TYPE] === 'visitors') {

+ 4
- 0
service/xmpp/XMPPEvents.spec.ts Целия файл

@@ -32,6 +32,10 @@ describe( "/service/xmpp/XMPPEvents members", () => {
32 32
         expect( XMPPEvents.EMUC_ROOM_ADDED ).toBe( 'xmpp.emuc_room_added' );
33 33
         expect( XMPPEvents.EMUC_ROOM_REMOVED ).toBe( 'xmpp.emuc_room_removed' );
34 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 39
         expect( XMPPEvents.FOCUS_DISCONNECTED ).toBe( 'xmpp.focus_disconnected' );
36 40
         expect( XMPPEvents.FOCUS_LEFT ).toBe( 'xmpp.focus_left' );
37 41
         expect( XMPPEvents.GRACEFUL_SHUTDOWN ).toBe( 'xmpp.graceful_shutdown' );

+ 20
- 0
service/xmpp/XMPPEvents.ts Целия файл

@@ -130,6 +130,26 @@ export enum XMPPEvents {
130 130
 
131 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 153
     FOCUS_DISCONNECTED = 'xmpp.focus_disconnected',
134 154
 
135 155
     FOCUS_LEFT = 'xmpp.focus_left',

Loading…
Отказ
Запис