@flashphoner/sfusdk 1.0.1-36 → 1.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Gruntfile.js +13 -0
- package/README.md +1 -1
- package/docTemplate/README.md +1 -1
- package/package.json +13 -5
- package/src/examples/player/player.js +3 -0
- package/src/examples/two-way-streaming/two-way-streaming.js +88 -59
- package/src/sdk/constants.js +30 -2
- package/src/sdk/messaging.js +22 -11
- package/src/sdk/promise.js +13 -3
- package/src/sdk/sfu-extended.js +149 -19
- package/src/tests/sdk/sfu-extended.test.js +151 -0
package/Gruntfile.js
CHANGED
|
@@ -50,6 +50,18 @@ module.exports = function(grunt) {
|
|
|
50
50
|
release: [
|
|
51
51
|
'release'
|
|
52
52
|
]
|
|
53
|
+
},
|
|
54
|
+
run: {
|
|
55
|
+
options: {
|
|
56
|
+
// ...
|
|
57
|
+
},
|
|
58
|
+
test: {
|
|
59
|
+
cmd: 'npm',
|
|
60
|
+
args: [
|
|
61
|
+
'run',
|
|
62
|
+
'test'
|
|
63
|
+
]
|
|
64
|
+
}
|
|
53
65
|
}
|
|
54
66
|
});
|
|
55
67
|
|
|
@@ -57,6 +69,7 @@ module.exports = function(grunt) {
|
|
|
57
69
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
|
58
70
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
|
59
71
|
grunt.loadNpmTasks('grunt-jsdoc');
|
|
72
|
+
grunt.loadNpmTasks('grunt-run');
|
|
60
73
|
grunt.registerTask('build', [
|
|
61
74
|
'clean:build',
|
|
62
75
|
'copy',
|
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ cp -r out/* /var/www/html/flashphoner-sfu-test
|
|
|
87
87
|
|
|
88
88
|
[SFU functions description](https://docs.flashphoner.com/display/WCS52EN/SFU+functions+with+Simulcast)
|
|
89
89
|
[SFU SDK documentation](https://docs.flashphoner.com/display/SS1E/SFU+SDK+1.0+-+EN)
|
|
90
|
-
[SFU
|
|
90
|
+
[SFU examples description](https://docs.flashphoner.com/display/SS1E/SFU+SDK+Examples)
|
|
91
91
|
[API documentation](http://flashphoner.com/docs/api/WCS5/client/sfu-sdk/latest)
|
|
92
92
|
|
|
93
93
|
## Known issues
|
package/docTemplate/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flashphoner/sfusdk",
|
|
3
3
|
"description": "Official Flashphoner WebCallServer SFU SDK package",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.41",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "jest --runInBand"
|
|
7
|
+
},
|
|
5
8
|
"dependencies": {
|
|
6
9
|
"bootstrap": "^4.6.0",
|
|
7
10
|
"datatables.net": "^1.10.24",
|
|
@@ -10,15 +13,17 @@
|
|
|
10
13
|
"jquery-ui": "^1.12.1",
|
|
11
14
|
"popper.js": "^1.16.1",
|
|
12
15
|
"sdp-transform": "^2.14.0",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
16
|
+
"uuid": "^8.3.0",
|
|
17
|
+
"webrtc-adapter": "^7.2.6"
|
|
15
18
|
},
|
|
16
19
|
"devDependencies": {
|
|
17
20
|
"grunt": "^1.0.1",
|
|
18
21
|
"grunt-browserify": "^5.0.0",
|
|
19
22
|
"grunt-contrib-clean": "^1.0.0",
|
|
20
23
|
"grunt-contrib-copy": "^1.0.0",
|
|
21
|
-
"grunt-jsdoc": "^2.4.0"
|
|
24
|
+
"grunt-jsdoc": "^2.4.0",
|
|
25
|
+
"grunt-run": "^0.8.1",
|
|
26
|
+
"jest": "^27.4.7"
|
|
22
27
|
},
|
|
23
28
|
"keywords": [
|
|
24
29
|
"Flashphoner",
|
|
@@ -26,5 +31,8 @@
|
|
|
26
31
|
"SFU SDK"
|
|
27
32
|
],
|
|
28
33
|
"author": "Flashphoner",
|
|
29
|
-
"license": "MIT"
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"jest" : {
|
|
36
|
+
"testEnvironment": "jsdom"
|
|
37
|
+
}
|
|
30
38
|
}
|
|
@@ -100,6 +100,9 @@ const connect = function(state) {
|
|
|
100
100
|
roomConfig.url = $("#url").val();
|
|
101
101
|
roomConfig.roomName = $("#roomName").val();
|
|
102
102
|
roomConfig.nickname = $("#" + state.inputId()).val();
|
|
103
|
+
// clean state display items
|
|
104
|
+
setStatus(state.statusId(), "");
|
|
105
|
+
setStatus(state.errInfoId(), "");
|
|
103
106
|
// connect to server and create a room if not
|
|
104
107
|
const session = sfu.createRoom(roomConfig);
|
|
105
108
|
session.on(constants.SFU_EVENT.CONNECTED, function(room) {
|
|
@@ -56,16 +56,37 @@ const CurrentState = function(prefix) {
|
|
|
56
56
|
pc: null,
|
|
57
57
|
session: null,
|
|
58
58
|
room: null,
|
|
59
|
+
timer: null,
|
|
59
60
|
set: function(pc, session, room) {
|
|
60
61
|
state.pc = pc;
|
|
61
62
|
state.session = session;
|
|
62
63
|
state.room = room;
|
|
63
64
|
},
|
|
64
65
|
clear: function() {
|
|
66
|
+
state.stopWaiting();
|
|
65
67
|
state.room = null;
|
|
66
68
|
state.session = null;
|
|
67
69
|
state.pc = null;
|
|
68
70
|
},
|
|
71
|
+
waitFor: function(div, timeout) {
|
|
72
|
+
state.stopWaiting();
|
|
73
|
+
state.timer = setTimeout(function () {
|
|
74
|
+
if (div.innerHTML !== "") {
|
|
75
|
+
// Enable stop button
|
|
76
|
+
$("#" + state.buttonId()).prop('disabled', false);
|
|
77
|
+
}
|
|
78
|
+
else if (state.isConnected()) {
|
|
79
|
+
setStatus(state.errInfoId(), "No media capturing started in " + timeout + " ms, stopping", "red");
|
|
80
|
+
onStopClick(state);
|
|
81
|
+
}
|
|
82
|
+
}, timeout);
|
|
83
|
+
},
|
|
84
|
+
stopWaiting: function() {
|
|
85
|
+
if (state.timer) {
|
|
86
|
+
clearTimeout(state.timer);
|
|
87
|
+
state.timer = null;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
69
90
|
buttonId: function() {
|
|
70
91
|
return state.prefix + "Btn";
|
|
71
92
|
},
|
|
@@ -86,6 +107,12 @@ const CurrentState = function(prefix) {
|
|
|
86
107
|
},
|
|
87
108
|
is: function(value) {
|
|
88
109
|
return (prefix === value);
|
|
110
|
+
},
|
|
111
|
+
isActive: function() {
|
|
112
|
+
return (state.room && state.pc);
|
|
113
|
+
},
|
|
114
|
+
isConnected: function() {
|
|
115
|
+
return (state.session && state.session.state() == constants.SFU_STATE.CONNECTED);
|
|
89
116
|
}
|
|
90
117
|
};
|
|
91
118
|
return state;
|
|
@@ -165,14 +192,12 @@ const onConnected = function(state) {
|
|
|
165
192
|
// Add errors displaying
|
|
166
193
|
state.room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
|
|
167
194
|
setStatus(state.errInfoId(), e, "red");
|
|
195
|
+
stopStreaming(state);
|
|
168
196
|
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
|
|
169
197
|
setStatus(state.errInfoId(), e.operation + " failed: " + e.error, "red");
|
|
198
|
+
stopStreaming(state);
|
|
170
199
|
});
|
|
171
|
-
|
|
172
|
-
publishStreams(state);
|
|
173
|
-
} else if (state.is(PLAY)) {
|
|
174
|
-
playStreams(state);
|
|
175
|
-
}
|
|
200
|
+
startStreaming(state);
|
|
176
201
|
}
|
|
177
202
|
|
|
178
203
|
const onDisconnected = function(state) {
|
|
@@ -204,65 +229,67 @@ const onStartClick = function(state) {
|
|
|
204
229
|
|
|
205
230
|
const onStopClick = function(state) {
|
|
206
231
|
$("#" + state.buttonId()).prop('disabled', true);
|
|
232
|
+
stopStreaming(state);
|
|
233
|
+
if (state.isConnected()) {
|
|
234
|
+
state.session.disconnect();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const startStreaming = function(state) {
|
|
207
239
|
if (state.is(PUBLISH)) {
|
|
208
|
-
|
|
240
|
+
publishStreams(state);
|
|
209
241
|
} else if (state.is(PLAY)) {
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
state.session.disconnect();
|
|
242
|
+
playStreams(state);
|
|
243
|
+
}
|
|
213
244
|
}
|
|
214
245
|
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
let streams = await getVideoStreams(mainConfig);
|
|
222
|
-
let audioStreams = await getAudioStreams(mainConfig);
|
|
223
|
-
//combine local video streams with audio streams
|
|
224
|
-
streams.push.apply(streams, audioStreams);
|
|
225
|
-
let config = {};
|
|
226
|
-
//add our local streams to the room (to PeerConnection)
|
|
227
|
-
streams.forEach(function (s) {
|
|
228
|
-
//add local stream to local display
|
|
229
|
-
localDisplay.add(s.stream.id, $("#" + state.inputId()).val(), s.stream);
|
|
230
|
-
//add each track to PeerConnection
|
|
231
|
-
s.stream.getTracks().forEach((track) => {
|
|
232
|
-
if (s.source === "screen") {
|
|
233
|
-
config[track.id] = s.source;
|
|
234
|
-
}
|
|
235
|
-
addTrackToPeerConnection(state.pc, s.stream, track, s.encodings);
|
|
236
|
-
subscribeTrackToEndedEvent(state.room, track, state.pc);
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
state.room.join(config);
|
|
240
|
-
// TODO: Use room state or promises to detect if publishing started to enable stop button
|
|
241
|
-
timerId = waitFor(document.getElementById("localVideo"), 3000, state);
|
|
242
|
-
} catch(e) {
|
|
243
|
-
console.error("Failed to capture streams: " + e);
|
|
244
|
-
setStatus(state.errInfoId(), e.name, "red");
|
|
245
|
-
if (timerId) {
|
|
246
|
-
clearTimeout(timerId);
|
|
247
|
-
timerId = null;
|
|
248
|
-
}
|
|
249
|
-
onStopClick(state);
|
|
246
|
+
const stopStreaming = function(state) {
|
|
247
|
+
state.stopWaiting();
|
|
248
|
+
if (state.is(PUBLISH)) {
|
|
249
|
+
unPublishStreams(state);
|
|
250
|
+
} else if (state.is(PLAY)) {
|
|
251
|
+
stopStreams(state);
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
255
|
+
const publishStreams = async function(state) {
|
|
256
|
+
if (state.isConnected()) {
|
|
257
|
+
//create local display item to show local streams
|
|
258
|
+
localDisplay = initLocalDisplay(document.getElementById("localVideo"));
|
|
259
|
+
try {
|
|
260
|
+
//get configured local video streams
|
|
261
|
+
let streams = await getVideoStreams(mainConfig);
|
|
262
|
+
let audioStreams = await getAudioStreams(mainConfig);
|
|
263
|
+
if (state.isConnected() && state.isActive()) {
|
|
264
|
+
//combine local video streams with audio streams
|
|
265
|
+
streams.push.apply(streams, audioStreams);
|
|
266
|
+
let config = {};
|
|
267
|
+
//add our local streams to the room (to PeerConnection)
|
|
268
|
+
streams.forEach(function (s) {
|
|
269
|
+
//add local stream to local display
|
|
270
|
+
localDisplay.add(s.stream.id, $("#" + state.inputId()).val(), s.stream);
|
|
271
|
+
//add each track to PeerConnection
|
|
272
|
+
s.stream.getTracks().forEach((track) => {
|
|
273
|
+
if (s.source === "screen") {
|
|
274
|
+
config[track.id] = s.source;
|
|
275
|
+
}
|
|
276
|
+
addTrackToPeerConnection(state.pc, s.stream, track, s.encodings);
|
|
277
|
+
subscribeTrackToEndedEvent(state.room, track, state.pc);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
state.room.join(config);
|
|
281
|
+
// TODO: Use room state or promises to detect if publishing started to enable stop button
|
|
282
|
+
state.waitFor(document.getElementById("localVideo"), 3000);
|
|
283
|
+
}
|
|
284
|
+
} catch(e) {
|
|
285
|
+
console.error("Failed to capture streams: " + e);
|
|
286
|
+
setStatus(state.errInfoId(), e.name, "red");
|
|
287
|
+
state.stopWaiting();
|
|
288
|
+
if (state.isConnected()) {
|
|
289
|
+
onStopClick(state);
|
|
290
|
+
}
|
|
263
291
|
}
|
|
264
|
-
}
|
|
265
|
-
return timerId;
|
|
292
|
+
}
|
|
266
293
|
}
|
|
267
294
|
|
|
268
295
|
const unPublishStreams = function(state) {
|
|
@@ -272,9 +299,11 @@ const unPublishStreams = function(state) {
|
|
|
272
299
|
}
|
|
273
300
|
|
|
274
301
|
const playStreams = function(state) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
302
|
+
if (state.isConnected() && state.isActive()) {
|
|
303
|
+
//create remote display item to show remote streams
|
|
304
|
+
remoteDisplay = initRemoteDisplay(document.getElementById("remoteVideo"), state.room, state.pc);
|
|
305
|
+
state.room.join();
|
|
306
|
+
}
|
|
278
307
|
$("#" + state.buttonId()).prop('disabled', false);
|
|
279
308
|
}
|
|
280
309
|
|
package/src/sdk/constants.js
CHANGED
|
@@ -56,6 +56,14 @@ const constants = Object.freeze({
|
|
|
56
56
|
*/
|
|
57
57
|
USER_LIST: "USER_LIST",
|
|
58
58
|
|
|
59
|
+
//TODO(naz): documentation
|
|
60
|
+
USER_CHATS: "USER_CHATS",
|
|
61
|
+
CHAT_LOADED: "CHAT_LOADED",
|
|
62
|
+
NEW_CHAT: "NEW_CHAT",
|
|
63
|
+
CHAT_DELETED: "CHAT_DELETED",
|
|
64
|
+
CHAT_UPDATED: "CHAT_UPDATED",
|
|
65
|
+
MESSAGE_STATE: "MESSAGE_STATE",
|
|
66
|
+
|
|
59
67
|
/**
|
|
60
68
|
* @typedef {Object} UserCalendar
|
|
61
69
|
* @property events {UserCalendarEvents} object with events
|
|
@@ -400,6 +408,11 @@ const constants = Object.freeze({
|
|
|
400
408
|
MESSAGE_STATE: "SFU_MESSAGE_STATE",
|
|
401
409
|
USER_LIST: "SFU_USER_LIST",
|
|
402
410
|
USER_CALENDAR: "SFU_USER_CALENDAR",
|
|
411
|
+
USER_CHATS: "SFU_USER_CHATS",
|
|
412
|
+
CHAT_LOADED: "SFU_CHAT_LOADED",
|
|
413
|
+
NEW_CHAT: "SFU_NEW_CHAT",
|
|
414
|
+
CHAT_DELETED: "SFU_CHAT_DELETED",
|
|
415
|
+
CHAT_UPDATED: "SFU_UPDATE_CHAT",
|
|
403
416
|
GET_USER_LIST: "getUserList",
|
|
404
417
|
GET_USER_CALENDAR: "getUserCalendar",
|
|
405
418
|
ADD_CALENDAR_EVENT: "addCalendarEvent",
|
|
@@ -411,7 +424,14 @@ const constants = Object.freeze({
|
|
|
411
424
|
UNSUBSCRIBE_FROM_WAITING_PARTICIPANT: "unsubscribeFromWaitingParticipant",
|
|
412
425
|
MOVE_TO_WAITING_ROOM: "moveToWaitingRoom",
|
|
413
426
|
CONFIGURE_WAITING_ROOM: "configureWaitingRoom",
|
|
414
|
-
TRACK_CONTENT_HEADER: "a=content:"
|
|
427
|
+
TRACK_CONTENT_HEADER: "a=content:",
|
|
428
|
+
GET_USER_CHATS: "getUserChats",
|
|
429
|
+
LOAD_CHAT: "loadChat",
|
|
430
|
+
CREATE_CHAT: "createChat",
|
|
431
|
+
DELETE_CHAT: "deleteChat",
|
|
432
|
+
RENAME_CHAT: "renameChat",
|
|
433
|
+
ADD_MEMBER_TO_CHAT: "addMemberToChat",
|
|
434
|
+
REMOVE_MEMBER_FROM_CHAT: "removeMemberFromChat"
|
|
415
435
|
}),
|
|
416
436
|
/**
|
|
417
437
|
* @namespace FlashphonerSFUExtended.SFU_OPERATIONS
|
|
@@ -430,7 +450,15 @@ const constants = Object.freeze({
|
|
|
430
450
|
ASSIGN_ROLE: "ASSIGN_ROLE",
|
|
431
451
|
SUBSCRIBE_TO_WAITING_PARTICIPANT: "SUBSCRIBE_TO_WAITING_PARTICIPANT",
|
|
432
452
|
UNSUBSCRIBE_FROM_WAITING_PARTICIPANT: "UNSUBSCRIBE_FROM_WAITING_PARTICIPANT",
|
|
433
|
-
MOVE_TO_WAITING_ROOM: "MOVE_TO_WAITING_ROOM"
|
|
453
|
+
MOVE_TO_WAITING_ROOM: "MOVE_TO_WAITING_ROOM",
|
|
454
|
+
CREATE_CHAT: "CREATE_CHAT",
|
|
455
|
+
DELETE_CHAT: "DELETE_CHAT",
|
|
456
|
+
RENAME_CHAT: "RENAME_CHAT",
|
|
457
|
+
LOAD_CHAT: "LOAD_CHAT",
|
|
458
|
+
ADD_MEMBER_TO_CHAT: "ADD_MEMBER_TO_CHAT",
|
|
459
|
+
REMOVE_MEMBER_FROM_CHAT: "REMOVE_MEMBER_FROM_CHAT",
|
|
460
|
+
UPDATE_CHAT_PREFERENCES: "UPDATE_CHAT_PREFERENCES",
|
|
461
|
+
GET_USER_CHATS: "GET_USER_CHATS"
|
|
434
462
|
}),
|
|
435
463
|
/**
|
|
436
464
|
* @namespace FlashphonerSFU.SFU_PARTICIPANT_ROLE
|
package/src/sdk/messaging.js
CHANGED
|
@@ -6,20 +6,31 @@ const create = function(options) {
|
|
|
6
6
|
|
|
7
7
|
/*
|
|
8
8
|
* @param {String} msg.to Recipient's id
|
|
9
|
+
* @param {String} msg.chatId Indicates chat this message belongs to
|
|
9
10
|
* @param {String} msg.body Message body
|
|
10
11
|
*/
|
|
11
12
|
const sendMessage = function(msg) {
|
|
12
13
|
return new Promise(function(resolve, reject) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
//validate
|
|
15
|
+
if (!msg) {
|
|
16
|
+
reject(new Error("Can't send null message"));
|
|
17
|
+
} else if (!msg.body || msg.body === "") {
|
|
18
|
+
reject(new Error("Can't send message without a body"));
|
|
19
|
+
} else if (!msg.chatId || msg.chatId === "") {
|
|
20
|
+
reject(new Error("Can't send message without a chatId"));
|
|
21
|
+
} else {
|
|
22
|
+
const message = {
|
|
23
|
+
id: uuidv4(),
|
|
24
|
+
to: msg.to,
|
|
25
|
+
body: msg.body,
|
|
26
|
+
chatId: msg.chatId
|
|
27
|
+
};
|
|
28
|
+
connection.send("sendMessage", message);
|
|
29
|
+
pendingMessages[message.id] = {
|
|
30
|
+
resolve: resolve,
|
|
31
|
+
reject: reject
|
|
32
|
+
};
|
|
33
|
+
}
|
|
23
34
|
});
|
|
24
35
|
};
|
|
25
36
|
|
|
@@ -28,7 +39,7 @@ const create = function(options) {
|
|
|
28
39
|
if (promise) {
|
|
29
40
|
delete pendingMessages[msg[0].status.id];
|
|
30
41
|
if (msg[0].status.delivered) {
|
|
31
|
-
promise.resolve();
|
|
42
|
+
promise.resolve(msg[0].status);
|
|
32
43
|
} else {
|
|
33
44
|
promise.reject();
|
|
34
45
|
}
|
package/src/sdk/promise.js
CHANGED
|
@@ -8,11 +8,21 @@ function add(id, resolve, reject) {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function resolve(id, args) {
|
|
11
|
-
getAndRemovePromise(id)
|
|
11
|
+
const promise = getAndRemovePromise(id);
|
|
12
|
+
if (!promise) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
promise.resolve(args);
|
|
16
|
+
return true;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
function reject(id, args) {
|
|
15
|
-
getAndRemovePromise(id)
|
|
20
|
+
const promise = getAndRemovePromise(id);
|
|
21
|
+
if (!promise) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
promise.reject(args);
|
|
25
|
+
return true;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
function promised(id) {
|
|
@@ -22,7 +32,7 @@ function promised(id) {
|
|
|
22
32
|
function getAndRemovePromise(id) {
|
|
23
33
|
const promise = promises[id];
|
|
24
34
|
if (!promise) {
|
|
25
|
-
|
|
35
|
+
return;
|
|
26
36
|
}
|
|
27
37
|
delete promises[id];
|
|
28
38
|
return promise;
|
package/src/sdk/sfu-extended.js
CHANGED
|
@@ -22,7 +22,6 @@ let state = SFU_STATE.NEW;
|
|
|
22
22
|
let connectionConfig;
|
|
23
23
|
let user;
|
|
24
24
|
let im;
|
|
25
|
-
let pendingUserList = [];
|
|
26
25
|
|
|
27
26
|
function setupConnection(connection) {
|
|
28
27
|
connection.onMessage = function(name, msg){
|
|
@@ -33,15 +32,28 @@ function setupConnection(connection) {
|
|
|
33
32
|
notify(SFU_EVENT.MESSAGE, msg[0].message);
|
|
34
33
|
} else if (msg[0].type === SFU_INTERNAL.MESSAGE_STATE) {
|
|
35
34
|
im.onMessageState(msg);
|
|
35
|
+
notify(SFU_EVENT.MESSAGE_STATE, msg[0].status);
|
|
36
36
|
} else if (msg[0].type === SFU_INTERNAL.USER_LIST) {
|
|
37
|
-
|
|
38
|
-
const promise = pendingUserList.pop();
|
|
39
|
-
promise.resolve(msg[0].list);
|
|
40
|
-
}
|
|
37
|
+
promises.resolve(msg[0].internalMessageId, msg[0].list);
|
|
41
38
|
notify(SFU_EVENT.USER_LIST, msg[0].list);
|
|
42
39
|
} else if (msg[0].type === SFU_INTERNAL.USER_CALENDAR) {
|
|
43
40
|
promises.resolve(msg[0].internalMessageId, msg[0].calendar);
|
|
44
41
|
notify(SFU_EVENT.USER_CALENDAR, msg[0].calendar);
|
|
42
|
+
} else if (msg[0].type === SFU_INTERNAL.NEW_CHAT) {
|
|
43
|
+
if(!promises.resolve(msg[0].internalMessageId, msg[0].info)) {
|
|
44
|
+
notify(SFU_EVENT.NEW_CHAT, msg[0].info);
|
|
45
|
+
}
|
|
46
|
+
} else if (msg[0].type === SFU_INTERNAL.CHAT_DELETED) {
|
|
47
|
+
notify(SFU_EVENT.CHAT_DELETED, msg[0].info);
|
|
48
|
+
} else if (msg[0].type === SFU_INTERNAL.CHAT_UPDATED) {
|
|
49
|
+
promises.resolve(msg[0].internalMessageId, msg[0].info);
|
|
50
|
+
notify(SFU_EVENT.CHAT_UPDATED, msg[0].info);
|
|
51
|
+
} else if (msg[0].type === SFU_INTERNAL.USER_CHATS) {
|
|
52
|
+
promises.resolve(msg[0].internalMessageId, msg[0].chats);
|
|
53
|
+
notify(SFU_EVENT.USER_CHATS, msg[0].chats);
|
|
54
|
+
} else if (msg[0].type === SFU_INTERNAL.CHAT_LOADED) {
|
|
55
|
+
promises.resolve(msg[0].internalMessageId, msg[0].chat);
|
|
56
|
+
notify(SFU_EVENT.CHAT_LOADED, msg[0].chat);
|
|
45
57
|
} else if (msg[0].type === constants.SFU_ROOM_EVENT.OPERATION_FAILED && promises.promised(msg[0].internalMessageId)) {
|
|
46
58
|
promises.reject(msg[0].internalMessageId, msg[0]);
|
|
47
59
|
} else if (msg[0].type === constants.SFU_EVENT.ACK && promises.promised(msg[0].internalMessageId)) {
|
|
@@ -128,7 +140,8 @@ const connect = function(options) {
|
|
|
128
140
|
/**
|
|
129
141
|
* Send message
|
|
130
142
|
* @param {Object} msg Message
|
|
131
|
-
* @param {String} msg.to Recipient's id
|
|
143
|
+
* @param {String} msg.to Recipient's id (deprecated, use chatId instead)
|
|
144
|
+
* @param {String} msg.chatId Indicates chat this message belongs to
|
|
132
145
|
* @param {String} msg.body Message body
|
|
133
146
|
* @returns {Promise} Promise will resolve upon message delivery and reject if delivery was unsuccessful
|
|
134
147
|
* @throws {Error} error if api isn't connected
|
|
@@ -144,26 +157,18 @@ const sendMessage = function(msg) {
|
|
|
144
157
|
/**
|
|
145
158
|
* Fetch available user list from server
|
|
146
159
|
* @returns {Promise<Array<FlashphonerSFUExtended.UserListEntry>>}
|
|
147
|
-
* @throws {Error} error if api isn't connected
|
|
148
160
|
* @memberOf FlashphonerSFUExtended
|
|
149
161
|
|
|
150
162
|
*/
|
|
151
163
|
const getUserList = function() {
|
|
152
|
-
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
153
|
-
throw new Error("Can't get user list while in " + state + " state");
|
|
154
|
-
}
|
|
155
164
|
return new Promise(function (resolve, reject){
|
|
156
165
|
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
157
166
|
reject(new Error("Can't get user list while in " + state + " state"));
|
|
158
167
|
return;
|
|
159
168
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
} else {
|
|
164
|
-
pendingUserList.push({resolve: resolve, reject: reject});
|
|
165
|
-
}
|
|
166
|
-
connection.send(constants.SFU_INTERNAL_API.GET_USER_LIST);
|
|
169
|
+
const id = uuidv4();
|
|
170
|
+
promises.add(id, resolve, reject);
|
|
171
|
+
connection.send(constants.SFU_INTERNAL_API.GET_USER_LIST, {internalMessageId: id});
|
|
167
172
|
});
|
|
168
173
|
};
|
|
169
174
|
|
|
@@ -229,6 +234,126 @@ const removeCalendarEvent = function(event) {
|
|
|
229
234
|
});
|
|
230
235
|
};
|
|
231
236
|
|
|
237
|
+
/**
|
|
238
|
+
* Fetch available chats from server
|
|
239
|
+
* @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
|
|
240
|
+
* @memberOf FlashphonerSFUExtended
|
|
241
|
+
*/
|
|
242
|
+
const getUserChats = function() {
|
|
243
|
+
return new Promise(function (resolve, reject){
|
|
244
|
+
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
245
|
+
reject(new Error("Can't get user chats while in " + state + " state"));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const id = uuidv4();
|
|
249
|
+
promises.add(id, resolve, reject);
|
|
250
|
+
connection.send(constants.SFU_INTERNAL_API.GET_USER_CHATS, {internalMessageId: id});
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Fetch chat data from server
|
|
256
|
+
* @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
|
|
257
|
+
* @memberOf FlashphonerSFUExtended
|
|
258
|
+
*/
|
|
259
|
+
const loadChat = function(chat) {
|
|
260
|
+
return new Promise(function (resolve, reject){
|
|
261
|
+
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
262
|
+
reject(new Error("Can't load chats while in " + state + " state"));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const id = uuidv4();
|
|
266
|
+
promises.add(id, resolve, reject);
|
|
267
|
+
connection.send(constants.SFU_INTERNAL_API.LOAD_CHAT, {id: chat.id, internalMessageId: id});
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Create chat
|
|
273
|
+
* @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
|
|
274
|
+
* @memberOf FlashphonerSFUExtended
|
|
275
|
+
*/
|
|
276
|
+
const createChat = function(chat) {
|
|
277
|
+
return new Promise(function (resolve, reject){
|
|
278
|
+
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
279
|
+
reject(new Error("Can't create chats while in " + state + " state"));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const id = uuidv4();
|
|
283
|
+
promises.add(id, resolve, reject);
|
|
284
|
+
connection.send(constants.SFU_INTERNAL_API.CREATE_CHAT, {id: chat.id, name: chat.name, members: chat.members, internalMessageId: id});
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Delete chat
|
|
290
|
+
* @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
|
|
291
|
+
* @memberOf FlashphonerSFUExtended
|
|
292
|
+
*/
|
|
293
|
+
const deleteChat = function(chat) {
|
|
294
|
+
return new Promise(function (resolve, reject){
|
|
295
|
+
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
296
|
+
reject(new Error("Can't delete chats while in " + state + " state"));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const id = uuidv4();
|
|
300
|
+
promises.add(id, resolve, reject);
|
|
301
|
+
connection.send(constants.SFU_INTERNAL_API.DELETE_CHAT, {id: chat.id, internalMessageId: id});
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Rename chat
|
|
307
|
+
* @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
|
|
308
|
+
* @memberOf FlashphonerSFUExtended
|
|
309
|
+
*/
|
|
310
|
+
const renameChat = function(chat) {
|
|
311
|
+
return new Promise(function (resolve, reject){
|
|
312
|
+
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
313
|
+
reject(new Error("Can't rename chats while in " + state + " state"));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const id = uuidv4();
|
|
317
|
+
promises.add(id, resolve, reject);
|
|
318
|
+
connection.send(constants.SFU_INTERNAL_API.RENAME_CHAT, {id: chat.id, name: chat.name, internalMessageId: id});
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Add member to chat
|
|
324
|
+
* @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
|
|
325
|
+
* @memberOf FlashphonerSFUExtended
|
|
326
|
+
*/
|
|
327
|
+
const addMemberToChat = function(chat) {
|
|
328
|
+
return new Promise(function (resolve, reject){
|
|
329
|
+
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
330
|
+
reject(new Error("Can't add member to chat while in " + state + " state"));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const id = uuidv4();
|
|
334
|
+
promises.add(id, resolve, reject);
|
|
335
|
+
connection.send(constants.SFU_INTERNAL_API.ADD_MEMBER_TO_CHAT, {id: chat.id, member: chat.member, internalMessageId: id});
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Remove member from chat
|
|
341
|
+
* @returns {Promise<Array<FlashphonerSFUExtended.UserCalendar>>}
|
|
342
|
+
* @memberOf FlashphonerSFUExtended
|
|
343
|
+
*/
|
|
344
|
+
const removeMemberFromChat = function(chat) {
|
|
345
|
+
return new Promise(function (resolve, reject){
|
|
346
|
+
if (state !== SFU_STATE.AUTHENTICATED) {
|
|
347
|
+
reject(new Error("Can't remove member to chat while in " + state + " state"));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const id = uuidv4();
|
|
351
|
+
promises.add(id, resolve, reject);
|
|
352
|
+
connection.send(constants.SFU_INTERNAL_API.REMOVE_MEMBER_FROM_CHAT, {id: chat.id, member: chat.member, internalMessageId: id});
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
|
|
232
357
|
/**
|
|
233
358
|
* Create room
|
|
234
359
|
*
|
|
@@ -326,8 +451,6 @@ const disconnect = function() {
|
|
|
326
451
|
value.leaveRoom();
|
|
327
452
|
}
|
|
328
453
|
user = undefined;
|
|
329
|
-
pendingUserList = [];
|
|
330
|
-
pendingUserCalendar = [];
|
|
331
454
|
connection.close();
|
|
332
455
|
connection = ws.createConnection();
|
|
333
456
|
state = SFU_STATE.DISCONNECTED;
|
|
@@ -341,6 +464,13 @@ sfu.getUserList = getUserList;
|
|
|
341
464
|
sfu.getUserCalendar = getUserCalendar;
|
|
342
465
|
sfu.addCalendarEvent = addCalendarEvent;
|
|
343
466
|
sfu.removeCalendarEvent = removeCalendarEvent;
|
|
467
|
+
sfu.getUserChats = getUserChats;
|
|
468
|
+
sfu.loadChat = loadChat;
|
|
469
|
+
sfu.createChat = createChat;
|
|
470
|
+
sfu.deleteChat = deleteChat;
|
|
471
|
+
sfu.renameChat = renameChat;
|
|
472
|
+
sfu.addMemberToChat = addMemberToChat;
|
|
473
|
+
sfu.removeMemberFromChat = removeMemberFromChat;
|
|
344
474
|
sfu.room = room;
|
|
345
475
|
|
|
346
476
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const sfu = require("../../sdk/sfu-extended");
|
|
2
|
+
const constants = require("../../sdk/constants");
|
|
3
|
+
|
|
4
|
+
const SERVER = "ws://127.0.0.1:8080";
|
|
5
|
+
const USER = "bob@flashphoner.com";
|
|
6
|
+
const PASSWD = "123456";
|
|
7
|
+
const CHAT_ID = "test-chat-id";
|
|
8
|
+
const CHAT_NAME = "test-chat";
|
|
9
|
+
const CHAT_NAME2 = "test-chat2";
|
|
10
|
+
const USER2 = "alice@flashphoner.com";
|
|
11
|
+
const MESSAGE_BODY = "This is a test message";
|
|
12
|
+
const MEMBERS = [
|
|
13
|
+
"bob@flashphoner.com",
|
|
14
|
+
"alice@flashphoner.com",
|
|
15
|
+
"kiri@flashphoner.com"
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
beforeAll( async () => {
|
|
19
|
+
await sfu.connect({
|
|
20
|
+
url: SERVER,
|
|
21
|
+
username: USER,
|
|
22
|
+
password: PASSWD
|
|
23
|
+
});
|
|
24
|
+
const chats = await sfu.getUserChats();
|
|
25
|
+
if (chats) {
|
|
26
|
+
Object.entries(chats).map(async ([key, value]) => {
|
|
27
|
+
await sfu.deleteChat({id: value.id});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterAll(() => {
|
|
33
|
+
return sfu.disconnect();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
describe("connection", () => {
|
|
38
|
+
test("should connect to server", () => {
|
|
39
|
+
expect(sfu.state()).toBe(constants.SFU_STATE.AUTHENTICATED);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("contacts", () => {
|
|
44
|
+
test("should load user list", () => {
|
|
45
|
+
return sfu.getUserList();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("calendar", () => {
|
|
50
|
+
test("should load calendar", () => {
|
|
51
|
+
return sfu.getUserCalendar().then((calendar) => {
|
|
52
|
+
expect(calendar).toHaveProperty("events");
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("chat", () => {
|
|
58
|
+
test("should create new chat", () => {
|
|
59
|
+
return sfu.createChat({
|
|
60
|
+
id: CHAT_ID,
|
|
61
|
+
name: CHAT_NAME
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
test("should load chats", () => {
|
|
65
|
+
return sfu.getUserChats().then((chats) => {
|
|
66
|
+
expect(chats).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
test("should add new member", () => {
|
|
70
|
+
return sfu.addMemberToChat({
|
|
71
|
+
id: CHAT_ID,
|
|
72
|
+
member: USER2
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
test("should send a message", () => {
|
|
76
|
+
return sfu.sendMessage({
|
|
77
|
+
chatId: CHAT_ID,
|
|
78
|
+
body: MESSAGE_BODY
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
test("should load chat", () => {
|
|
82
|
+
return sfu.loadChat({
|
|
83
|
+
id: CHAT_ID
|
|
84
|
+
}).then((chat) => {
|
|
85
|
+
expect(chat.id).toBe(CHAT_ID);
|
|
86
|
+
expect(chat.name).toBe(CHAT_NAME);
|
|
87
|
+
expect(chat.members).toContain(USER);
|
|
88
|
+
expect(Array.isArray(chat.messages)).toBe(true);
|
|
89
|
+
expect(chat.messages.length).toBeGreaterThan(0);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
test("should remove member", () => {
|
|
93
|
+
return sfu.removeMemberFromChat({
|
|
94
|
+
id: CHAT_ID,
|
|
95
|
+
member: USER2
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
test("should rename chat", () => {
|
|
99
|
+
return sfu.renameChat({
|
|
100
|
+
id: CHAT_ID,
|
|
101
|
+
name: CHAT_NAME2
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
test.skip("should receive message", (done) => {
|
|
105
|
+
sfu.on(constants.SFU_EVENT.MESSAGE, (msg) => {
|
|
106
|
+
done();
|
|
107
|
+
})
|
|
108
|
+
sfu.sendMessage({
|
|
109
|
+
chatId: CHAT_ID,
|
|
110
|
+
body: MESSAGE_BODY
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
test("should receive ACK message", (done) => {
|
|
114
|
+
sfu.on(constants.SFU_EVENT.MESSAGE_STATE, (state) => {
|
|
115
|
+
done();
|
|
116
|
+
})
|
|
117
|
+
sfu.sendMessage({
|
|
118
|
+
chatId: CHAT_ID,
|
|
119
|
+
body: MESSAGE_BODY
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
test("should create new chat without id", () => {
|
|
123
|
+
return sfu.createChat({
|
|
124
|
+
name: CHAT_NAME
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
test("should create new chat with member list", () => {
|
|
128
|
+
return sfu.createChat({
|
|
129
|
+
name: CHAT_NAME,
|
|
130
|
+
members: MEMBERS
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
test("should create new chat without name based on members", async () => {
|
|
134
|
+
const chat = await sfu.createChat({
|
|
135
|
+
members: MEMBERS
|
|
136
|
+
});
|
|
137
|
+
expect(chat.name).toBeTruthy();
|
|
138
|
+
});
|
|
139
|
+
test("should delete chat", () => {
|
|
140
|
+
return sfu.deleteChat({
|
|
141
|
+
id: CHAT_ID
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
test("should change name after member was added", async () => {
|
|
145
|
+
const chat = await sfu.createChat({});
|
|
146
|
+
expect(chat.name).toBeTruthy();
|
|
147
|
+
const name1 = chat.name;
|
|
148
|
+
const sameChat = await sfu.addMemberToChat({id: chat.id, member: USER2});
|
|
149
|
+
expect(sameChat.name !== name1).toBeTruthy();
|
|
150
|
+
});
|
|
151
|
+
});
|