Merge pull request #3684 from oswaldoacauan/refactor-api-users
[HTML5] Refactor API Users
This commit is contained in:
commit
d83c391997
@ -2,7 +2,7 @@ import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import handleLockingMic from '/imports/api/users/server/modifiers/handleLockingMic';
|
||||
import lockAllViewersMic from '/imports/api/users/server/modifiers/lockAllViewersMic';
|
||||
|
||||
export default function handlePermissionSettingsChange({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
@ -41,7 +41,7 @@ export default function handlePermissionSettingsChange({ payload }) {
|
||||
}
|
||||
|
||||
if (permissions.disableMic) {
|
||||
handleLockingMic(meetingId, permissions);
|
||||
lockAllViewersMic(meetingId);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
|
@ -2,7 +2,7 @@ import Meetings from '/imports/api/chat';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import removeMeeting from './removeMeeting';
|
||||
|
||||
import { clearUsersCollection } from '/imports/api/users/server/modifiers/clearUsersCollection';
|
||||
import clearUsers from '/imports/api/users/server/modifiers/clearUsers';
|
||||
import clearChats from '/imports/api/chat/server/modifiers/clearChats';
|
||||
import clearBreakouts from '/imports/api/breakouts/server/modifiers/clearBreakouts';
|
||||
import clearShapes from '/imports/api/shapes/server/modifiers/clearShapes';
|
||||
@ -22,7 +22,7 @@ export default function clearMeetings() {
|
||||
clearPolls();
|
||||
clearShapes();
|
||||
clearSlides();
|
||||
clearUsersCollection();
|
||||
clearUsers();
|
||||
|
||||
return Logger.info('Cleared Meetings (all)');
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ import { check } from 'meteor/check';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import { clearUsersCollection } from '/imports/api/users/server/modifiers/clearUsersCollection';
|
||||
import clearUsers from '/imports/api/users/server/modifiers/clearUsers';
|
||||
import clearChats from '/imports/api/chat/server/modifiers/clearChats';
|
||||
import clearShapes from '/imports/api/shapes/server/modifiers/clearShapes';
|
||||
import clearSlides from '/imports/api/slides/server/modifiers/clearSlides';
|
||||
@ -31,7 +31,7 @@ export default function removeMeeting(meetingId) {
|
||||
clearPolls(meetingId);
|
||||
clearShapes(meetingId);
|
||||
clearSlides(meetingId);
|
||||
clearUsersCollection(meetingId);
|
||||
clearUsers(meetingId);
|
||||
|
||||
return Logger.info(`Removed meeting id=${meetingId}`);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
// TODO: This file should be a `service.js` somewhere in the /ui folder
|
||||
import Users from '/imports/api/users';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader } from '/imports/api/common/server/helpers';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
|
||||
export default function getStun(credentials) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
const eventName = 'send_stun_turn_info_request_message';
|
||||
|
||||
let message = {
|
||||
payload: {
|
||||
meeting_id: meetingId,
|
||||
requester_id: requesterUserId,
|
||||
},
|
||||
};
|
||||
|
||||
message = appendMessageHeader(eventName, message);
|
||||
return publish(REDIS_CONFIG.channels.fromBBBUsers, message);
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
import { eventEmitter } from '/imports/startup/server';
|
||||
import { updateVoiceUser } from '/imports/api/users/server/modifiers/updateVoiceUser';
|
||||
|
||||
eventEmitter.on('user_left_voice_message', function (arg) {
|
||||
handleVoiceEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('user_joined_voice_message', function (arg) {
|
||||
handleVoiceEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('user_voice_talking_message', function (arg) {
|
||||
handleVoiceEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('user_voice_muted_message', function (arg) {
|
||||
handleVoiceEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('user_listening_only', function (arg) {
|
||||
const voiceUserObj = {
|
||||
web_userid: arg.payload.userid,
|
||||
listen_only: arg.payload.listen_only,
|
||||
};
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
return updateVoiceUser(meetingId, voiceUserObj, arg.callback);
|
||||
});
|
||||
|
||||
const handleVoiceEvent = function (arg) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const voiceUser = arg.payload.user.voiceUser;
|
||||
const voiceUserObj = {
|
||||
web_userid: voiceUser.web_userid,
|
||||
listen_only: arg.payload.listen_only,
|
||||
talking: voiceUser.talking,
|
||||
joined: voiceUser.joined,
|
||||
locked: voiceUser.locked,
|
||||
muted: voiceUser.muted,
|
||||
};
|
||||
return updateVoiceUser(meetingId, voiceUserObj, arg.callback);
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import handleGetUsers from './handlers/getUsers';
|
||||
import handleRemoveUser from './handlers/removeUser';
|
||||
import handlePresenterAssigned from './handlers/presenterAssigned';
|
||||
import handleEmojiStatus from './handlers/emojiStatus';
|
||||
import handleLockedStatusChange from './handlers/lockedStatusChange';
|
||||
import handleUserJoined from './handlers/userJoined';
|
||||
import handleValidateAuthToken from './handlers/validateAuthToken';
|
||||
import handleVoiceUpdate from './handlers/voiceUpdate';
|
||||
import handleListeningOnly from './handlers/listeningOnly';
|
||||
|
||||
RedisPubSub.on('validate_auth_token_reply', handleValidateAuthToken);
|
||||
RedisPubSub.on('get_users_reply', handleGetUsers);
|
||||
RedisPubSub.on('user_joined_message', handleUserJoined);
|
||||
RedisPubSub.on('user_eject_from_meeting', handleRemoveUser);
|
||||
RedisPubSub.on('disconnect_user_message', handleRemoveUser);
|
||||
RedisPubSub.on('user_left_message', handleRemoveUser);
|
||||
RedisPubSub.on('presenter_assigned_message', handlePresenterAssigned);
|
||||
RedisPubSub.on('user_emoji_status_message', handleEmojiStatus);
|
||||
RedisPubSub.on('user_locked_message', handleLockedStatusChange);
|
||||
RedisPubSub.on('user_unlocked_message', handleLockedStatusChange);
|
||||
RedisPubSub.on('user_left_voice_message', handleVoiceUpdate);
|
||||
RedisPubSub.on('user_joined_voice_message', handleVoiceUpdate);
|
||||
RedisPubSub.on('user_voice_talking_message', handleVoiceUpdate);
|
||||
RedisPubSub.on('user_voice_muted_message', handleVoiceUpdate);
|
||||
RedisPubSub.on('user_listening_only', handleListeningOnly);
|
@ -0,0 +1,37 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function handleEmojiStatus({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const userId = payload.userid;
|
||||
const status = payload.emoji_status;
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(status, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.set_emoji_time': (new Date()).getTime(),
|
||||
'user.emoji_status': status,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Assigning user emoji status: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Assigned user emoji status '${status}' id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
import { check } from 'meteor/check';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
import addUser from '../modifiers/addUser';
|
||||
import removeUser from '../modifiers/removeUser';
|
||||
|
||||
export default function handleGetUsers({ payload }) {
|
||||
if (!inReplyToHTML5Client({ payload })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const meetingId = payload.meeting_id;
|
||||
const users = payload.users;
|
||||
|
||||
check(meetingId, String);
|
||||
check(users, Array);
|
||||
|
||||
const usersIds = users.map(m => m.userid);
|
||||
|
||||
const usersToRemove = Users.find({
|
||||
meetingId,
|
||||
userId: { $nin: usersIds },
|
||||
}).fetch();
|
||||
|
||||
usersToRemove.forEach(user => removeUser(meetingId, user.userId));
|
||||
|
||||
let usersAdded = [];
|
||||
users.forEach(user => {
|
||||
usersAdded.push(addUser(meetingId, user));
|
||||
});
|
||||
|
||||
return usersAdded;
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function handleListeningOnly({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const userId = payload.userid;
|
||||
const listenOnly = payload.listen_only;
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(listenOnly, Boolean);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.listenOnly': listenOnly,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Assigning user listen only status: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Assigned listen only status '${listenOnly}' user=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
@ -0,0 +1,58 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
import muteToggle from '../methods/muteToggle';
|
||||
|
||||
export default function handleLockedStatusChange({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const userId = payload.userid;
|
||||
const isLocked = arg.payload.locked;
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(isLocked, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const User = Users.findOne(selector);
|
||||
if (!User) {
|
||||
throw new Meteor.Error(
|
||||
'user-not-found', `You need a valid user to be able to set presenter`);
|
||||
}
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.locked': isLocked,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Assigning user locked status: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
if (User.user.role === 'VIEWER'
|
||||
&& !User.user.listenOnly
|
||||
&& !User.user.voiceUser.muted
|
||||
&& User.user.voiceUser.joined
|
||||
&& isLocked) {
|
||||
|
||||
const credentials = {
|
||||
meetingId,
|
||||
requesterUserId: userId,
|
||||
};
|
||||
|
||||
muteToggle(credentials, userId, true);
|
||||
}
|
||||
|
||||
return Logger.info(`Assigned locked status '${isLocked ? 'locked' : 'unlocked'}' id=${newPresenterId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
@ -0,0 +1,61 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function handlePresenterAssigned({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const newPresenterId = payload.new_presenter_id;
|
||||
|
||||
check(meetingId, String);
|
||||
check(newPresenterId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId: newPresenterId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.presenter': true,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Assigning user as presenter: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
unassignCurrentPresenter(meetingId, newPresenterId);
|
||||
return Logger.info(`Assigned user as presenter id=${newPresenterId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
||||
|
||||
const unassignCurrentPresenter = (meetingId, newPresenterId) => {
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId: { $ne: newPresenterId },
|
||||
'user.presenter': true,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.presenter': false,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Unassigning current presenter from collection: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Unassign current presenter meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
import removeUser from '../modifiers/removeUser';
|
||||
|
||||
export default function handleRemoveUser({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const userId = payload.userid || payload.user.userid;
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
|
||||
return removeUser(meetingId, userId);
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import addUser from '../modifiers/addUser';
|
||||
|
||||
export default function handleUserJoined({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const user = payload.user;
|
||||
|
||||
check(meetingId, String);
|
||||
check(user, Object);
|
||||
|
||||
return addUser(meetingId, user);
|
||||
};
|
@ -0,0 +1,66 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
import addChat from '/imports/api/chat/server/modifiers/addChat';
|
||||
|
||||
export default function handleValidateAuthToken({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const userId = payload.userid;
|
||||
const validStatus = payload.valid;
|
||||
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(validStatus, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
validated: validStatus,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Validating auth token: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
if (validStatus) {
|
||||
addWelcomeChatMessage(meetingId, userId);
|
||||
}
|
||||
|
||||
return Logger.info(`Validated auth token as '${validStatus}' user=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
||||
|
||||
const addWelcomeChatMessage = (meetingId, userId) => {
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
|
||||
const Meeting = Meetings.findOne({ meetingId });
|
||||
|
||||
let welcomeMessage = APP_CONFIG.defaultWelcomeMessage
|
||||
.concat(APP_CONFIG.defaultWelcomeMessageFooter)
|
||||
.replace(/%%CONFNAME%%/, Meeting.meetingName);
|
||||
|
||||
const message = {
|
||||
chat_type: CHAT_CONFIG.type_system,
|
||||
message: welcomeMessage,
|
||||
from_color: '0x3399FF',
|
||||
to_userid: userId,
|
||||
from_userid: CHAT_CONFIG.type_system,
|
||||
from_username: '',
|
||||
from_time: (new Date()).getTime(),
|
||||
};
|
||||
|
||||
return addChat(meetingId, message);
|
||||
};
|
@ -0,0 +1,20 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import updateVoiceUser from '../modifiers/updateVoiceUser';
|
||||
|
||||
export default function handleVoiceUpdate({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const user = payload.user;
|
||||
|
||||
check(meetingId, String);
|
||||
check(user, Object);
|
||||
|
||||
const voiceUser = user.voiceUser;
|
||||
check(voiceUser, Object);
|
||||
|
||||
const userId = voiceUser.web_userid;
|
||||
check(userId, String);
|
||||
|
||||
return updateVoiceUser(meetingId, userId, voiceUser);
|
||||
};
|
3
bigbluebutton-html5/imports/api/users/server/index.js
Normal file
3
bigbluebutton-html5/imports/api/users/server/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import './eventHandlers';
|
||||
import './methods';
|
||||
import './publishers';
|
17
bigbluebutton-html5/imports/api/users/server/methods.js
Normal file
17
bigbluebutton-html5/imports/api/users/server/methods.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import kickUser from './methods/kickUser';
|
||||
import listenOnlyToggle from './methods/listenOnlyToggle';
|
||||
import userLogout from './methods/userLogout';
|
||||
import assignPresenter from './methods/assignPresenter';
|
||||
import muteToggle from './methods/muteToggle';
|
||||
import setEmojiStatus from './methods/setEmojiStatus';
|
||||
|
||||
Meteor.methods({
|
||||
kickUser,
|
||||
listenOnlyToggle,
|
||||
userLogout,
|
||||
assignPresenter,
|
||||
setEmojiStatus,
|
||||
muteUser: (...args) => muteToggle(...args, true),
|
||||
unmuteUser: (...args) => muteToggle(...args, false),
|
||||
});
|
42
bigbluebutton-html5/imports/api/users/server/methods/assignPresenter.js
Executable file
42
bigbluebutton-html5/imports/api/users/server/methods/assignPresenter.js
Executable file
@ -0,0 +1,42 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function assignPresenter(credentials, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.users;
|
||||
const EVENT_NAME = 'assign_presenter_request_message';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
if (!isAllowedTo('setPresenter', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', `You are not allowed to setPresenter`);
|
||||
}
|
||||
|
||||
const User = Users.findOne({
|
||||
meetingId,
|
||||
userId,
|
||||
});
|
||||
if (!User) {
|
||||
throw new Meteor.Error(
|
||||
'user-not-found', `You need a valid user to be able to set presenter`);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
new_presenter_id: userId,
|
||||
new_presenter_name: User.user.name,
|
||||
meeting_id: meetingId,
|
||||
assigned_by: requesterUserId,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${userId}' setted as presenter by '${requesterUserId}' from meeting '${meetingId}'`);
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
};
|
@ -1,25 +1,31 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader, publish } from '/imports/api/common/server/helpers';
|
||||
|
||||
Meteor.methods({
|
||||
//meetingId: the meeting where the user is
|
||||
//toKickUserId: the userid of the user to kick
|
||||
//requesterUserId: the userid of the user that wants to kick
|
||||
//authToken: the authToken of the user that wants to kick
|
||||
kickUser(credentials, toKickUserId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
let message;
|
||||
if (isAllowedTo('kickUser', credentials)) {
|
||||
message = {
|
||||
payload: {
|
||||
userid: toKickUserId,
|
||||
ejected_by: requesterUserId,
|
||||
meeting_id: meetingId,
|
||||
},
|
||||
};
|
||||
message = appendMessageHeader('eject_user_from_meeting_request_message', message);
|
||||
return publish(REDIS_CONFIG.channels.toBBBApps.users, message);
|
||||
}
|
||||
},
|
||||
});
|
||||
export default function kickUser(credentials, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.users;
|
||||
const EVENT_NAME = 'eject_user_from_meeting_request_message';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
if (!isAllowedTo('kickUser', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', `You are not allowed to kickUser`);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
userid: userId,
|
||||
ejected_by: requesterUserId,
|
||||
meeting_id: meetingId,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${userId}' was kicked by '${requesterUserId}' from meeting '${meetingId}'`);
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
};
|
||||
|
@ -1,70 +0,0 @@
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader, publish } from '/imports/api/common/server/helpers';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
Meteor.methods({
|
||||
// meetingId: the meetingId of the meeting the user is in
|
||||
// toSetUserId: the userId of the user joining
|
||||
// requesterUserId: the userId of the requester
|
||||
// requesterToken: the authToken of the requester
|
||||
listenOnlyRequestToggle(credentials, isJoining) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
let username;
|
||||
let voiceConf;
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
const meetingObject = Meetings.findOne({
|
||||
meetingId: meetingId,
|
||||
});
|
||||
const userObject = Users.findOne({
|
||||
meetingId: meetingId,
|
||||
userId: requesterUserId,
|
||||
});
|
||||
|
||||
if (meetingObject != null) {
|
||||
voiceConf = meetingObject.voiceConf;
|
||||
}
|
||||
|
||||
if (userObject != null) {
|
||||
username = userObject.user.name;
|
||||
}
|
||||
|
||||
if (isJoining) {
|
||||
if (isAllowedTo('joinListenOnly', credentials)) {
|
||||
let message = {
|
||||
payload: {
|
||||
userid: requesterUserId,
|
||||
meeting_id: meetingId,
|
||||
voice_conf: voiceConf,
|
||||
name: username,
|
||||
},
|
||||
};
|
||||
message = appendMessageHeader('user_connected_to_global_audio', message);
|
||||
logger.info(
|
||||
`publishing a user listenOnly toggleRequest ${isJoining} ` +
|
||||
`request for ${requesterUserId}`
|
||||
);
|
||||
publish(REDIS_CONFIG.channels.toBBBApps.meeting, message);
|
||||
}
|
||||
} else {
|
||||
if (isAllowedTo('leaveListenOnly', credentials)) {
|
||||
let message = {
|
||||
payload: {
|
||||
userid: requesterUserId,
|
||||
meeting_id: meetingId,
|
||||
voice_conf: voiceConf,
|
||||
name: username,
|
||||
},
|
||||
};
|
||||
message = appendMessageHeader('user_disconnected_from_global_audio', message);
|
||||
logger.info(
|
||||
`publishing a user listenOnly toggleRequest ${isJoining} ` +
|
||||
`request for ${requesterUserId}`
|
||||
);
|
||||
publish(REDIS_CONFIG.channels.toBBBApps.meeting, message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
});
|
60
bigbluebutton-html5/imports/api/users/server/methods/listenOnlyToggle.js
Executable file
60
bigbluebutton-html5/imports/api/users/server/methods/listenOnlyToggle.js
Executable file
@ -0,0 +1,60 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function listenOnlyToggle(credentials, isJoining = true) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.meeting;
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(isJoining, Boolean);
|
||||
|
||||
if (isJoining) {
|
||||
let EVENT_NAME = 'user_connected_to_global_audio';
|
||||
if (!isAllowedTo('joinListenOnly', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', `You are not allowed to joinListenOnly`);
|
||||
}
|
||||
} else {
|
||||
let EVENT_NAME = 'user_disconnected_from_global_audio';
|
||||
if (!isAllowedTo('leaveListenOnly', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', `You are not allowed to leaveListenOnly`);
|
||||
}
|
||||
}
|
||||
|
||||
const Metting = Meetings.findOne({ meetingId });
|
||||
if (!Metting) {
|
||||
throw new Meteor.Error(
|
||||
'metting-not-found', `You need a valid meeting to be able to toggle audio`);
|
||||
}
|
||||
|
||||
check(Metting.voiceConf, String);
|
||||
|
||||
const User = Users.findOne({
|
||||
meetingId,
|
||||
userId: requesterUserId,
|
||||
});
|
||||
if (!User) {
|
||||
throw new Meteor.Error(
|
||||
'user-not-found', `You need a valid user to be able to toggle audio`);
|
||||
}
|
||||
|
||||
check(User.user.name, String);
|
||||
|
||||
let payload = {
|
||||
userid: requesterUserId,
|
||||
meeting_id: meetingId,
|
||||
voice_conf: Metting.voiceConf,
|
||||
name: User.user.name,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${requesterUserId}' ${isJoining ? 'joined' : 'left'} global audio from meeting '${meetingId}'`);
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
};
|
38
bigbluebutton-html5/imports/api/users/server/methods/muteToggle.js
Executable file
38
bigbluebutton-html5/imports/api/users/server/methods/muteToggle.js
Executable file
@ -0,0 +1,38 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
|
||||
export default function muteToggle(credentials, userId, isMuted = true) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.users;
|
||||
const EVENT_NAME = 'mute_user_request_message';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
let action = userId === requesterUserId ? 'muteSelf' : 'muteOther';
|
||||
|
||||
if (!isMuted) {
|
||||
action = `un${action}`;
|
||||
}
|
||||
|
||||
if (!isAllowedTo(action, credentials)) {
|
||||
throw new Meteor.Error('not-allowed', `You are not allowed to ${action}`);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
user_id: userId,
|
||||
meeting_id: meetingId,
|
||||
mute: isMuted,
|
||||
requester_id: requesterUserId,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${userId}' was ${!isMuted ? 'un' : ''}muted by '${requesterUserId}' from meeting '${meetingId}'`);
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader } from '/imports/api/common/server/helpers';
|
||||
import { updateVoiceUser } from '/imports/api/users/server/modifiers/updateVoiceUser';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
Meteor.methods({
|
||||
// meetingId: the meetingId of the meeting the user[s] is in
|
||||
// toMuteUserId: the userId of the user to be muted
|
||||
// requesterUserId: the userId of the requester
|
||||
// requesterToken: the authToken of the requester
|
||||
muteUser(credentials, toMuteUserId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
const action = function () {
|
||||
if (toMuteUserId === requesterUserId) {
|
||||
return 'muteSelf';
|
||||
} else {
|
||||
return 'muteOther';
|
||||
}
|
||||
};
|
||||
|
||||
if (isAllowedTo(action(), credentials)) {
|
||||
let message = {
|
||||
payload: {
|
||||
user_id: toMuteUserId,
|
||||
meeting_id: meetingId,
|
||||
mute: true,
|
||||
requester_id: requesterUserId,
|
||||
},
|
||||
};
|
||||
message = appendMessageHeader('mute_user_request_message', message);
|
||||
logger.info(`publishing a user mute request for ${toMuteUserId}`);
|
||||
publish(REDIS_CONFIG.channels.toBBBApps.users, message);
|
||||
updateVoiceUser(meetingId, {
|
||||
web_userid: toMuteUserId,
|
||||
talking: false,
|
||||
muted: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function requestStunTurn(meetingId, requesterUserId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.fromBBBUsers;
|
||||
const EVENT_NAME = 'send_stun_turn_info_request_message';
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
let payload = {
|
||||
meeting_id: meetingId,
|
||||
requester_id: requesterUserId,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${requesterUserId}' requested stun/turn from meeting '${meetingId}'`);
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
};
|
31
bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js
Executable file
31
bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js
Executable file
@ -0,0 +1,31 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
|
||||
export default function setEmojiStatus(credentials, userId, status) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.users;
|
||||
const EVENT_NAME = 'user_emoji_status_message';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
if (!isAllowedTo('setEmojiStatus', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', `You are not allowed to setEmojiStatus`);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
emoji_status: status,
|
||||
userid: userId,
|
||||
meeting_id: meetingId,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${userId}' emoji status updated to '${status}' by '${requesterUserId}' from meeting '${meetingId}'`);
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
};
|
@ -1,32 +0,0 @@
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader } from '/imports/api/common/server/helpers';
|
||||
|
||||
Meteor.methods({
|
||||
//meetingId: the meeting where the user is
|
||||
//newPresenterId: the userid of the new presenter
|
||||
//requesterUserId: the userid of the user that wants to change the presenter
|
||||
//newPresenterName: user name of the new presenter
|
||||
//authToken: the authToken of the user that wants to kick
|
||||
setUserPresenter(
|
||||
credentials,
|
||||
newPresenterId,
|
||||
newPresenterName) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
let message;
|
||||
if (isAllowedTo('setPresenter', credentials)) {
|
||||
message = {
|
||||
payload: {
|
||||
new_presenter_id: newPresenterId,
|
||||
new_presenter_name: newPresenterName,
|
||||
meeting_id: meetingId,
|
||||
assigned_by: requesterUserId,
|
||||
},
|
||||
};
|
||||
|
||||
message = appendMessageHeader('assign_presenter_request_message', message);
|
||||
return publish(REDIS_CONFIG.channels.toBBBApps.users, message);
|
||||
}
|
||||
},
|
||||
});
|
@ -1,42 +0,0 @@
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader } from '/imports/api/common/server/helpers';
|
||||
import { updateVoiceUser } from '/imports/api/users/server/modifiers/updateVoiceUser';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
Meteor.methods({
|
||||
// meetingId: the meetingId of the meeting the user[s] is in
|
||||
// toMuteUserId: the userId of the user to be unmuted
|
||||
// requesterUserId: the userId of the requester
|
||||
// requesterToken: the authToken of the requester
|
||||
unmuteUser(credentials, toMuteUserId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
const action = function () {
|
||||
if (toMuteUserId === requesterUserId) {
|
||||
return 'unmuteSelf';
|
||||
} else {
|
||||
return 'unmuteOther';
|
||||
}
|
||||
};
|
||||
|
||||
if (isAllowedTo(action(), credentials)) {
|
||||
let message = {
|
||||
payload: {
|
||||
user_id: toMuteUserId,
|
||||
meeting_id: meetingId,
|
||||
mute: false,
|
||||
requester_id: requesterUserId,
|
||||
},
|
||||
};
|
||||
message = appendMessageHeader('mute_user_request_message', message);
|
||||
logger.info(`publishing a user unmute request for ${toMuteUserId}`);
|
||||
publish(REDIS_CONFIG.channels.toBBBApps.users, message);
|
||||
updateVoiceUser(meetingId, {
|
||||
web_userid: toMuteUserId,
|
||||
talking: false,
|
||||
muted: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
@ -0,0 +1,48 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
import setConnectionStatus from '../modifiers/setConnectionStatus';
|
||||
import listenOnlyToggle from './listenOnlyToggle';
|
||||
|
||||
const OFFLINE_CONNECTION_STATUS = 'offline';
|
||||
|
||||
export default function userLeaving(credentials, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.users;
|
||||
const EVENT_NAME = 'user_leaving_request';
|
||||
|
||||
const { meetingId, requesterUserId } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(userId, String);
|
||||
|
||||
const User = Users.findOne({
|
||||
meetingId,
|
||||
userId,
|
||||
});
|
||||
if (!User) {
|
||||
throw new Meteor.Error(
|
||||
'user-not-found', `You need a valid user to be able to toggle audio`);
|
||||
}
|
||||
|
||||
if (User.user.connection_status === OFFLINE_CONNECTION_STATUS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (User.user.listenOnly) {
|
||||
listenOnlyToggle(credentials, false);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
meeting_id: meetingId,
|
||||
userid: userId,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${requesterUserId}' left meeting '${meetingId}'`);
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload);
|
||||
};
|
@ -1,13 +1,14 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { requestUserLeaving } from '/imports/api/users/server/modifiers/requestUserLeaving';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
Meteor.methods({
|
||||
userLogout(credentials) {
|
||||
if (isAllowedTo('logoutSelf', credentials)) {
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
logger.info(`a user is logging out from ${meetingId}:${requesterUserId}`);
|
||||
return requestUserLeaving(meetingId, requesterUserId);
|
||||
}
|
||||
},
|
||||
});
|
||||
import userLeaving from './userLeaving';
|
||||
|
||||
export default function userLogout(credentials) {
|
||||
if (!isAllowedTo('logoutSelf', credentials)) {
|
||||
throw new Meteor.Error('not-allowed', `You are not allowed to logoutSelf`);
|
||||
}
|
||||
|
||||
const { requesterUserId } = credentials;
|
||||
|
||||
return userLeaving(credentials, requesterUserId);
|
||||
};
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { appendMessageHeader } from '/imports/api/common/server/helpers';
|
||||
|
||||
Meteor.methods({
|
||||
userSetEmoji(credentials, toRaiseUserId, status) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
let message;
|
||||
if (isAllowedTo('setEmojiStatus', credentials)) {
|
||||
message = {
|
||||
payload: {
|
||||
emoji_status: status,
|
||||
userid: toRaiseUserId,
|
||||
meeting_id: meetingId,
|
||||
},
|
||||
};
|
||||
|
||||
message = appendMessageHeader('user_emoji_status_message', message);
|
||||
|
||||
// publish to pubsub
|
||||
publish(REDIS_CONFIG.channels.toBBBApps.users, message);
|
||||
}
|
||||
},
|
||||
});
|
@ -1,34 +1,48 @@
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import { createDummyUser } from '/imports/api/users/server/modifiers/createDummyUser';
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
Meteor.methods({
|
||||
// Construct and send a message to bbb-web to validate the user
|
||||
validateAuthToken(credentials) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
logger.info('sending a validate_auth_token with', {
|
||||
userid: requesterUserId,
|
||||
authToken: requesterToken,
|
||||
meetingid: meetingId,
|
||||
});
|
||||
let message = {
|
||||
payload: {
|
||||
auth_token: requesterToken,
|
||||
userid: requesterUserId,
|
||||
meeting_id: meetingId,
|
||||
},
|
||||
header: {
|
||||
timestamp: new Date().getTime(),
|
||||
reply_to: `${meetingId}/${requesterUserId}`,
|
||||
name: 'validate_auth_token',
|
||||
},
|
||||
};
|
||||
if ((requesterToken != null) && (requesterUserId != null) && (meetingId != null)) {
|
||||
createDummyUser(meetingId, requesterUserId, requesterToken);
|
||||
return publish(REDIS_CONFIG.channels.toBBBApps.meeting, message);
|
||||
} else {
|
||||
return logger.info('did not have enough information to send a validate_auth_token message');
|
||||
}
|
||||
},
|
||||
});
|
||||
import createDummyUser from '../modifiers/createDummyUser';
|
||||
import setConnectionStatus from '../modifiers/setConnectionStatus';
|
||||
|
||||
const ONLINE_CONNECTION_STATUS = 'online';
|
||||
|
||||
export default function validateAuthToken(credentials) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.meeting;
|
||||
const EVENT_NAME = 'validate_auth_token';
|
||||
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
const User = Users.findOne({
|
||||
meetingId,
|
||||
userId: requesterUserId,
|
||||
});
|
||||
|
||||
if (!User) {
|
||||
createDummyUser(meetingId, requesterUserId, requesterToken);
|
||||
} else if (User.validated) {
|
||||
setConnectionStatus(meetingId, requesterUserId, ONLINE_CONNECTION_STATUS);
|
||||
}
|
||||
|
||||
let payload = {
|
||||
auth_token: requesterToken,
|
||||
userid: requesterUserId,
|
||||
meeting_id: meetingId,
|
||||
};
|
||||
|
||||
const header = {
|
||||
reply_to: `${meetingId}/${requesterUserId}`,
|
||||
};
|
||||
|
||||
Logger.verbose(`User '${requesterUserId}' is trying to validate auth token for meeting '${meetingId}'`);
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, EVENT_NAME, payload, header);
|
||||
};
|
||||
|
@ -0,0 +1,71 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
import requestStunTurn from '../methods/requestStunTurn';
|
||||
|
||||
export default function addUser(meetingId, user) {
|
||||
check(user, Object);
|
||||
check(meetingId, String);
|
||||
|
||||
const userId = user.userid;
|
||||
check(userId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
userId,
|
||||
'user.connection_status': 'online',
|
||||
'user.userid': userId,
|
||||
'user.extern_userid': user.extern_userid,
|
||||
'user.role': user.role,
|
||||
'user.name': user.name,
|
||||
'user._sort_name': user.name.trim().toLowerCase(),
|
||||
'user.avatarURL': user.avatarURL,
|
||||
'user.set_emoji_time': user.set_emoji_time || (new Date()).getTime(),
|
||||
'user.time_of_joining': (new Date()).getTime(),
|
||||
'user.emoji_status': user.emoji_status,
|
||||
'user.webcam_stream': user.webcam_stream,
|
||||
'user.presenter': user.presenter,
|
||||
'user.locked': user.locked,
|
||||
'user.phone_user': user.phone_user,
|
||||
'user.listenOnly': user.listenOnly,
|
||||
'user.has_stream': user.has_stream,
|
||||
'user.voiceUser.web_userid': user.voiceUser.web_userid,
|
||||
'user.voiceUser.callernum': user.voiceUser.callernum,
|
||||
'user.voiceUser.userid': user.voiceUser.userid,
|
||||
'user.voiceUser.talking': user.voiceUser.talking,
|
||||
'user.voiceUser.joined': user.voiceUser.joined,
|
||||
'user.voiceUser.callername': user.voiceUser.callername,
|
||||
'user.voiceUser.locked': user.voiceUser.locked,
|
||||
'user.voiceUser.muted': user.voiceUser.muted,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding user to collection: ${err}`);
|
||||
}
|
||||
|
||||
// TODO: Do we really need to request the stun/turn everytime?
|
||||
requestStunTurn(meetingId, userId);
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.info(`Added user id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Upserted user id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.upsert(selector, modifier, cb);
|
||||
};
|
10
bigbluebutton-html5/imports/api/users/server/modifiers/clearUsers.js
Executable file
10
bigbluebutton-html5/imports/api/users/server/modifiers/clearUsers.js
Executable file
@ -0,0 +1,10 @@
|
||||
import Users from '/imports/api/users';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearUsers(meetingId) {
|
||||
if (meetingId) {
|
||||
return Users.remove({ meetingId }, Logger.info(`Cleared Users (${meetingId})`));
|
||||
}
|
||||
|
||||
return Users.remove({}, Logger.info('Cleared Users (all)'));
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import Users from '/imports/api/users';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
// called on server start and on meeting end
|
||||
export function clearUsersCollection() {
|
||||
const meetingId = arguments[0];
|
||||
if (meetingId != null) {
|
||||
return Users.remove({
|
||||
meetingId: meetingId,
|
||||
}, err => {
|
||||
if (err != null) {
|
||||
return logger.error(`_error ${JSON.stringify(err)} while removing users from ${meetingId}`);
|
||||
} else {
|
||||
return logger.info(`_cleared Users Collection (meetingId: ${meetingId})!`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Users.remove({}, err => {
|
||||
if (err != null) {
|
||||
return logger.error(`_error ${JSON.stringify(err)} while removing users from all meetings`);
|
||||
} else {
|
||||
return logger.info('_cleared Users Collection (all meetings)!');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -1,24 +1,33 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
export function createDummyUser(meetingId, userId, authToken) {
|
||||
if (Users.findOne({
|
||||
userId: userId,
|
||||
meetingId: meetingId,
|
||||
authToken: authToken,
|
||||
}) != null) {
|
||||
const msg = `html5 user userId:[${userId}] from [${meetingId}] tried to revalidate token`;
|
||||
return logger.info(msg);
|
||||
} else {
|
||||
return Users.insert({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
authToken: authToken,
|
||||
clientType: 'HTML5',
|
||||
validated: false, //will be validated on validate_auth_token_reply
|
||||
}, (err, id) => {
|
||||
const res = Users.find({ meetingId: meetingId, }).count();
|
||||
return logger.info(`_added a dummy html5 user userId=[${userId}] Users.size is now ${res}`);
|
||||
});
|
||||
export default function createDummyUser(meetingId, userId, authToken) {
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(authToken, String);
|
||||
|
||||
const User = Users.findOne({ meetingId, userId });
|
||||
if (User) {
|
||||
throw new Meteor.Error('existing-user', `Tried to create a dummy user for an existing user`);
|
||||
}
|
||||
|
||||
const doc = {
|
||||
meetingId,
|
||||
userId,
|
||||
authToken,
|
||||
clientType: 'HTML5',
|
||||
validated: false,
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Creating dummy user to collection: ${err}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Created dummy user id=${userId} token=${authToken} meeting=${meetingId}`);
|
||||
};
|
||||
|
||||
return Users.insert(doc, cb);
|
||||
};
|
||||
|
@ -1,212 +0,0 @@
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import { eventEmitter } from '/imports/startup/server';
|
||||
import { userJoined } from './userJoined';
|
||||
import { setUserLockedStatus } from './setUserLockedStatus';
|
||||
import { markUserOffline } from './markUserOffline';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
import Users from '../..';
|
||||
|
||||
eventEmitter.on('user_eject_from_meeting', function (arg) {
|
||||
handleRemoveUserEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('disconnect_user_message', function (arg) {
|
||||
handleRemoveUserEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('user_left_message', function (arg) {
|
||||
handleRemoveUserEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('validate_auth_token_reply', function (arg) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const userId = arg.payload.userid;
|
||||
const user = Users.findOne({
|
||||
userId: userId,
|
||||
meetingId: meetingId,
|
||||
});
|
||||
const validStatus = arg.payload.valid;
|
||||
|
||||
// if the user already exists in the db
|
||||
if (user != null && user.clientType === 'HTML5') {
|
||||
//if the html5 client user was validated successfully, add a flag
|
||||
return Users.update({
|
||||
userId: userId,
|
||||
meetingId: meetingId,
|
||||
}, {
|
||||
$set: {
|
||||
validated: validStatus,
|
||||
},
|
||||
}, (err, numChanged) => {
|
||||
if (numChanged.insertedId != null) {
|
||||
let funct = function (cbk) {
|
||||
let user = Users.findOne({
|
||||
userId: userId,
|
||||
meetingId: meetingId,
|
||||
});
|
||||
let val;
|
||||
if (user != null) {
|
||||
val = user.validated;
|
||||
}
|
||||
|
||||
logger.info(`user.validated for ${userId} in meeting ${meetingId} just became ${val}`);
|
||||
return cbk();
|
||||
};
|
||||
|
||||
return funct(arg.callback);
|
||||
} else {
|
||||
return arg.callback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.info('a non-html5 user got validate_auth_token_reply.');
|
||||
return arg.callback();
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on('user_joined_message', function (arg) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const payload = arg.payload;
|
||||
let userObj = payload.user;
|
||||
let dbUser = Users.findOne({
|
||||
userId: userObj.userid,
|
||||
meetingId: meetingId,
|
||||
});
|
||||
|
||||
// On attempting reconnection of Flash clients (in voiceBridge) we receive
|
||||
// an extra user_joined_message. Ignore it as it will add an extra user
|
||||
// in the user list, creating discrepancy with the list in the Flash client
|
||||
if (dbUser != null && dbUser.user != null && dbUser.user.connection_status === 'offline') {
|
||||
if (payload.user != null && payload.user.phone_user) {
|
||||
logger.error('offline AND phone user');
|
||||
return arg.callback(); //return without joining the user
|
||||
}
|
||||
} else {
|
||||
if (dbUser != null && dbUser.clientType === 'HTML5') {
|
||||
// typically html5 users will be in
|
||||
// the db [as a dummy user] before the joining message
|
||||
let status = dbUser.validated;
|
||||
logger.info(`in user_joined_message the validStatus of the user was ${status}`);
|
||||
userObj.timeOfJoining = arg.header.current_time;
|
||||
}
|
||||
|
||||
return userJoined(meetingId, userObj, arg.callback);
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
eventEmitter.on('get_users_reply', function (arg) {
|
||||
if (inReplyToHTML5Client(arg)) {
|
||||
let users = arg.payload.users;
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
|
||||
//TODO make the serialization be split per meeting. This will allow us to
|
||||
// use N threads vs 1 and we'll take advantage of Mongo's concurrency tricks
|
||||
|
||||
// Processing the users recursively with a callback to notify us,
|
||||
// ensuring that we update the users collection serially
|
||||
let processUser = function () {
|
||||
let user = users.pop();
|
||||
if (user != null) {
|
||||
user.timeOfJoining = arg.header.current_time;
|
||||
if (user.emoji_status !== 'none' && typeof user.emoji_status === 'string') {
|
||||
user.set_emoji_time = new Date();
|
||||
return userJoined(meetingId, user, processUser);
|
||||
} else {
|
||||
return userJoined(meetingId, user, processUser);
|
||||
}
|
||||
} else {
|
||||
return arg.callback(); // all meeting arrays (if any) have been processed
|
||||
}
|
||||
};
|
||||
|
||||
return processUser();
|
||||
} else {
|
||||
arg.callback();
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.on('user_emoji_status_message', function (arg) {
|
||||
const userId = arg.payload.userid;
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const emojiStatus = arg.payload.emoji_status;
|
||||
if (userId != null && meetingId != null) {
|
||||
const setEmojiTime = new Date();
|
||||
Users.update({
|
||||
'user.userid': userId,
|
||||
}, {
|
||||
$set: {
|
||||
'user.set_emoji_time': setEmojiTime,
|
||||
'user.emoji_status': emojiStatus,
|
||||
},
|
||||
}, (err, numUpdated) => logger.info(`Updating emoji numUpdated=${numUpdated}, err=${err}`));
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
eventEmitter.on('user_locked_message', function (arg) {
|
||||
handleLockEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('user_unlocked_message', function (arg) {
|
||||
handleLockEvent(arg);
|
||||
});
|
||||
|
||||
eventEmitter.on('presenter_assigned_message', function (arg) {
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
const newPresenterId = arg.payload.new_presenter_id;
|
||||
if (newPresenterId != null) {
|
||||
// reset the previous presenter
|
||||
Users.update({
|
||||
'user.presenter': true,
|
||||
meetingId: meetingId,
|
||||
}, {
|
||||
$set: {
|
||||
'user.presenter': false,
|
||||
},
|
||||
}, (err, numUpdated) => logger.info(
|
||||
` Updating old presenter numUpdated=${numUpdated}, ` +
|
||||
` err=${err}`
|
||||
)
|
||||
);
|
||||
|
||||
// set the new presenter
|
||||
Users.update({
|
||||
'user.userid': newPresenterId,
|
||||
meetingId: meetingId,
|
||||
}, {
|
||||
$set: {
|
||||
'user.presenter': true,
|
||||
},
|
||||
}, (err, numUpdated) => logger.info(
|
||||
` Updating new presenter numUpdated=${numUpdated}, ` +
|
||||
`err=${err}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return arg.callback();
|
||||
});
|
||||
|
||||
const handleRemoveUserEvent = function (arg) {
|
||||
const { payload, callback } = arg;
|
||||
if ((payload.userid || payload.user.userid) &&
|
||||
payload.meeting_id) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const userId = payload.userid || payload.user.userid;
|
||||
return markUserOffline(meetingId, userId, callback);
|
||||
} else {
|
||||
logger.info('could not perform handleRemoveUserEvent');
|
||||
return callback();
|
||||
}
|
||||
};
|
||||
|
||||
const handleLockEvent = function (arg) {
|
||||
const userId = arg.payload.userid;
|
||||
const isLocked = arg.payload.locked;
|
||||
const meetingId = arg.payload.meeting_id;
|
||||
setUserLockedStatus(meetingId, userId, isLocked);
|
||||
return arg.callback();
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
// when new lock settings including disableMic are set,
|
||||
// all viewers that are in the audio bridge with a mic should be muted and locked
|
||||
export default function handleLockingMic(meetingId, newSettings) {
|
||||
// send mute requests for the viewer users joined with mic
|
||||
let userObject;
|
||||
let results = [];
|
||||
const userObjects = Users.find({
|
||||
meetingId: meetingId,
|
||||
'user.role': 'VIEWER',
|
||||
'user.listenOnly': false,
|
||||
'user.locked': true,
|
||||
'user.voiceUser.joined': true,
|
||||
'user.voiceUser.muted': false,
|
||||
}).fetch();
|
||||
const _userObjectsLength = userObjects.length;
|
||||
|
||||
for (let i = 0; i < _userObjectsLength; i++) {
|
||||
userObject = userObjects[i];
|
||||
results.push(Meteor.call('muteUser', meetingId, userObject.userId, userObject.userId,
|
||||
userObject.authToken, true)); //true for muted
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
24
bigbluebutton-html5/imports/api/users/server/modifiers/lockAllViewersMic.js
Executable file
24
bigbluebutton-html5/imports/api/users/server/modifiers/lockAllViewersMic.js
Executable file
@ -0,0 +1,24 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function lockAllViewersMic(meetingId) {
|
||||
const selector = {
|
||||
meetingId,
|
||||
'user.role': 'VIEWER',
|
||||
'user.listenOnly': false,
|
||||
'user.locked': true,
|
||||
'user.voiceUser.joined': true,
|
||||
'user.voiceUser.muted': false,
|
||||
};
|
||||
|
||||
const usersToMute = Users.find(selector).fetch();
|
||||
|
||||
usersToMute.forEach(user =>
|
||||
muteToggle({
|
||||
meetingId,
|
||||
requesterUserId: user.userId,
|
||||
}, userId, true)
|
||||
);
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
import Users from '/imports/api/users';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
// Only callable from server
|
||||
// Received information from BBB-Apps that a user left
|
||||
// Need to update the collection
|
||||
// params: meetingid, userid as defined in BBB-Apps
|
||||
// callback
|
||||
export function markUserOffline(meetingId, userId, callback) {
|
||||
// mark the user as offline. remove from the collection on meeting_end #TODO
|
||||
let user = Users.findOne({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
});
|
||||
|
||||
if (user != null && user.clientType === 'HTML5') {
|
||||
logger.info(`marking html5 user [${userId}] as offline in meeting[${meetingId}]`);
|
||||
return Users.update({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
}, {
|
||||
$set: {
|
||||
'user.connection_status': 'offline',
|
||||
'user.voiceUser.talking': false,
|
||||
'user.voiceUser.joined': false,
|
||||
'user.voiceUser.muted': false,
|
||||
'user.time_of_joining': 0,
|
||||
'user.listenOnly': false, //TODO make this user: {}
|
||||
},
|
||||
}, (err, numChanged) => {
|
||||
let funct;
|
||||
if (err != null) {
|
||||
logger.error(
|
||||
`_unsucc update (mark as offline) of user ${user.user.name} ` +
|
||||
`${userId} err=${JSON.stringify(err)}`
|
||||
);
|
||||
return callback();
|
||||
} else {
|
||||
funct = function (cbk) {
|
||||
logger.info(
|
||||
`_marking as offline html5 user ${user.user.name} ` +
|
||||
`${userId} numChanged=${numChanged}`
|
||||
);
|
||||
return cbk();
|
||||
};
|
||||
|
||||
return funct(callback);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Users.remove({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
}, (err, numDeletions) => {
|
||||
let funct;
|
||||
if (err != null) {
|
||||
logger.error(
|
||||
`_unsucc deletion of user ${user != null ? user.user.name : void 0} ` +
|
||||
`${userId} err=${JSON.stringify(err)}`
|
||||
);
|
||||
return callback();
|
||||
} else {
|
||||
funct = function (cbk) {
|
||||
logger.info(
|
||||
`_deleting info for user ${user != null ? user.user.name : void 0} ` +
|
||||
`${userId} numDeletions=${numDeletions}`
|
||||
);
|
||||
return cbk();
|
||||
};
|
||||
|
||||
return funct(callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,56 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/slides';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import setConnectionStatus from './setConnectionStatus';
|
||||
|
||||
const CLIENT_TYPE_HTML = 'HTML5';
|
||||
|
||||
export default function removeUser(meetingId, userId) {
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const User = Users.findOne(selector);
|
||||
|
||||
if (User && User.clientType !== CLIENT_TYPE_HTML) {
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Removing user from collection: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Removed user id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.remove(selector, cb);
|
||||
}
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.connection_status': 'offline',
|
||||
'user.voiceUser.talking': false,
|
||||
'user.voiceUser.joined': false,
|
||||
'user.voiceUser.muted': false,
|
||||
'user.time_of_joining': 0,
|
||||
'user.listenOnly': false,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Removing user from collection: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Removed ${CLIENT_TYPE_HTML} user id=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
@ -1,63 +0,0 @@
|
||||
import { publish } from '/imports/api/common/server/helpers';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import Users from '/imports/api/users';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
// Corresponds to a valid action on the HTML clientside
|
||||
// After authorization, publish a user_leaving_request in redis
|
||||
// params: meetingid, userid as defined in BBB-App
|
||||
export function requestUserLeaving(meetingId, userId) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
let voiceConf;
|
||||
const userObject = Users.findOne({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
});
|
||||
const meetingObject = Meetings.findOne({
|
||||
meetingId: meetingId,
|
||||
});
|
||||
|
||||
if (meetingObject != null) {
|
||||
voiceConf = meetingObject.voiceConf;
|
||||
}
|
||||
|
||||
if ((userObject != null) && (voiceConf != null) && (userId != null) && (meetingId != null)) {
|
||||
let lOnly = false;
|
||||
if (userObject.hasOwnProperty('user') && userObject.user.hasOwnProperty('listenOnly')) {
|
||||
lOnly = userObject.user.listenOnly;
|
||||
}
|
||||
|
||||
// end listenOnly audio for the departing user
|
||||
if (lOnly) {
|
||||
const listenOnlyMessage = {
|
||||
payload: {
|
||||
userid: userId,
|
||||
meeting_id: meetingId,
|
||||
voice_conf: voiceConf,
|
||||
name: userObject.user.name,
|
||||
},
|
||||
header: {
|
||||
timestamp: new Date().getTime(),
|
||||
name: 'user_disconnected_from_global_audio',
|
||||
},
|
||||
};
|
||||
publish(REDIS_CONFIG.channels.toBBBApps.meeting, listenOnlyMessage);
|
||||
}
|
||||
|
||||
// remove user from meeting
|
||||
const message = {
|
||||
payload: {
|
||||
meeting_id: meetingId,
|
||||
userid: userId,
|
||||
},
|
||||
header: {
|
||||
timestamp: new Date().getTime(),
|
||||
name: 'user_leaving_request',
|
||||
},
|
||||
};
|
||||
logger.info(`sending a user_leaving_request for ${meetingId}:${userId}`);
|
||||
return publish(REDIS_CONFIG.channels.toBBBApps.users, message);
|
||||
} else {
|
||||
return logger.info('did not have enough information to send a user_leaving_request');
|
||||
}
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/users';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
const VALID_CONNECTION_STATUS = ['online', 'offline'];
|
||||
|
||||
export default function setConnectionStatus(meetingId, userId, status = 'online') {
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
check(status, String);
|
||||
|
||||
if (!VALID_CONNECTION_STATUS.includes(status)) {
|
||||
throw new Meteor.Error('invalid-connection-status',
|
||||
`Invalid connection status, received ${status} expecting ${VALID_CONNECTION_STATUS.join()}`);
|
||||
}
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.connection_status': status,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Updating connection status user=${userId}: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info(`Updated connection status user=${userId} status=${status} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
import Users from '/imports/api/users';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
|
||||
// change the locked status of a user (lock settings)
|
||||
export function setUserLockedStatus(meetingId, userId, isLocked) {
|
||||
let userObject;
|
||||
userObject = Users.findOne({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
});
|
||||
if (userObject != null) {
|
||||
Users.update({
|
||||
userId: userId,
|
||||
meetingId: meetingId,
|
||||
}, {
|
||||
$set: {
|
||||
'user.locked': isLocked,
|
||||
},
|
||||
}, (err, numChanged) => {
|
||||
if (err != null) {
|
||||
return logger.error(`_error ${err} while updating user ${userId} with lock settings`);
|
||||
} else {
|
||||
return logger.info(
|
||||
`_setting user locked status for:[${userId}] ` +
|
||||
`from [${meetingId}] locked=${isLocked}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// if the user is sharing audio, he should be muted upon locking involving disableMic
|
||||
if (userObject.user.role === 'VIEWER' && !userObject.user.listenOnly &&
|
||||
userObject.user.voiceUser.joined && !userObject.user.voiceUser.muted && isLocked) {
|
||||
// TODO why are we doing Meteor.call here?! Anton
|
||||
return Meteor.call('muteUser', meetingId, userObject.userId, userObject.userId,
|
||||
userObject.authToken, true); //true for muted
|
||||
}
|
||||
} else {
|
||||
let tempMsg = '(unsuccessful-no such user) setting user locked status for userid:';
|
||||
return logger.error(`${tempMsg}[${userId}] from [${meetingId}] locked=${isLocked}`);
|
||||
}
|
||||
};
|
@ -1,47 +1,35 @@
|
||||
import Users from '/imports/api/users';
|
||||
import { check } from 'meteor/check';
|
||||
import Users from '/imports/api/slides';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
//update a voiceUser - a helper method
|
||||
export function updateVoiceUser(meetingId, voiceUserObject, callback = () => {}) {
|
||||
export default function updateVoiceUser(meetingId, userId, voiceUser) {
|
||||
check(meetingId, String);
|
||||
check(voiceUserObject, Object);
|
||||
|
||||
const webUserId = voiceUserObject.web_userid;
|
||||
|
||||
check(webUserId, String);
|
||||
|
||||
let userObject = Users.findOne({
|
||||
userId: webUserId,
|
||||
});
|
||||
|
||||
Logger.debug(`user ${userObject.userId} vu=${JSON.stringify(voiceUserObject)}`);
|
||||
|
||||
check(userObject, Object);
|
||||
check(userId, String);
|
||||
check(voiceUser, Object);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId: webUserId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'user.voiceUser.talking': voiceUserObject.talking || false,
|
||||
'user.voiceUser.joined': voiceUserObject.joined || false,
|
||||
'user.voiceUser.locked': voiceUserObject.locked || false,
|
||||
'user.voiceUser.muted': voiceUserObject.muted || false,
|
||||
'user.listenOnly': voiceUserObject.listen_only || false,
|
||||
'user.voiceUser.talking': voiceUser.talking,
|
||||
'user.voiceUser.joined': voiceUser.joined,
|
||||
'user.voiceUser.locked': voiceUser.locked,
|
||||
'user.voiceUser.muted': voiceUser.muted,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
Logger.error(
|
||||
`failed to update voiceUser ${JSON.stringify(voiceUserObject)} err=${JSON.stringify(err)}`
|
||||
);
|
||||
return Logger.error(`Updating voice user=${userId}: ${err}`);
|
||||
}
|
||||
|
||||
return callback();
|
||||
if (numChanged) {
|
||||
return Logger.verbose(`Updated voice user=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
Users.update(selector, modifier, cb);
|
||||
return Users.update(selector, modifier, cb);
|
||||
};
|
||||
|
@ -1,189 +0,0 @@
|
||||
import Chat from '/imports/api/chat';
|
||||
import Users from '/imports/api/users';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import { BREAK_LINE } from '/imports/utils/lineEndings.js';
|
||||
import getStun from '/imports/api/phone/server/getStun';
|
||||
|
||||
const parseMessage = (message) => {
|
||||
message = message || '';
|
||||
|
||||
// Replace \r and \n to <br/>
|
||||
message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + BREAK_LINE + '$2');
|
||||
|
||||
// Replace flash links to html valid ones
|
||||
message = message.split(`<a href='event:`).join(`<a target="_blank" href='`);
|
||||
message = message.split(`<a href="event:`).join(`<a target="_blank" href="`);
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
export function userJoined(meetingId, user, callback) {
|
||||
const APP_CONFIG = Meteor.settings.public.app;
|
||||
let welcomeMessage;
|
||||
const userId = user.userid;
|
||||
const userObject = Users.findOne({
|
||||
userId: user.userid,
|
||||
meetingId: meetingId,
|
||||
});
|
||||
|
||||
// the collection already contains an entry for this user
|
||||
// because the user is reconnecting OR
|
||||
// in the case of an html5 client user we added a dummy user on
|
||||
// register_user_message (to save authToken)
|
||||
if (userObject != null && userObject.authToken != null) {
|
||||
getStun({
|
||||
meetingId: meetingId,
|
||||
requesterUserId: userId,
|
||||
});
|
||||
|
||||
Users.update({
|
||||
userId: user.userid,
|
||||
meetingId: meetingId,
|
||||
}, {
|
||||
$set: {
|
||||
user: {
|
||||
userid: user.userid,
|
||||
presenter: user.presenter,
|
||||
name: user.name,
|
||||
_sort_name: user.name.toLowerCase(),
|
||||
phone_user: user.phone_user,
|
||||
set_emoji_time: user.set_emoji_time,
|
||||
emoji_status: user.emoji_status,
|
||||
has_stream: user.has_stream,
|
||||
role: user.role,
|
||||
listenOnly: user.listenOnly,
|
||||
extern_userid: user.extern_userid,
|
||||
locked: user.locked,
|
||||
time_of_joining: user.timeOfJoining,
|
||||
connection_status: 'online', // TODO consider other default value
|
||||
voiceUser: {
|
||||
web_userid: user.voiceUser.web_userid,
|
||||
callernum: user.voiceUser.callernum,
|
||||
userid: user.voiceUser.userid,
|
||||
talking: user.voiceUser.talking,
|
||||
joined: user.voiceUser.joined,
|
||||
callername: user.voiceUser.callername,
|
||||
locked: user.voiceUser.locked,
|
||||
muted: user.voiceUser.muted,
|
||||
},
|
||||
webcam_stream: user.webcam_stream,
|
||||
},
|
||||
},
|
||||
}, err => {
|
||||
let funct;
|
||||
if (err != null) {
|
||||
logger.error(`_error ${err} when trying to insert user ${userId}`);
|
||||
return callback();
|
||||
} else {
|
||||
funct = function (cbk) {
|
||||
logger.info(
|
||||
`_(case1) UPDATING USER ${user.userid}, ` +
|
||||
`authToken= ${userObject.authToken}, ` +
|
||||
`locked=${user.locked}, username=${user.name}`
|
||||
);
|
||||
return cbk();
|
||||
};
|
||||
|
||||
return funct(callback);
|
||||
}
|
||||
});
|
||||
const meetingObject = Meetings.findOne({
|
||||
meetingId: meetingId,
|
||||
});
|
||||
if (meetingObject != null) {
|
||||
welcomeMessage = APP_CONFIG.defaultWelcomeMessage.replace(/%%CONFNAME%%/,
|
||||
meetingObject.meetingName);
|
||||
}
|
||||
|
||||
welcomeMessage = welcomeMessage + APP_CONFIG.defaultWelcomeMessageFooter;
|
||||
|
||||
// add the welcome message if it's not there already OR update time_of_joining
|
||||
return Chat.upsert({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
'message.chat_type': 'SYSTEM_MESSAGE',
|
||||
'message.to_userid': userId,
|
||||
}, {
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
message: {
|
||||
chat_type: 'SYSTEM_MESSAGE',
|
||||
message: parseMessage(welcomeMessage),
|
||||
from_color: '0x3399FF',
|
||||
to_userid: userId,
|
||||
from_userid: 'SYSTEM_MESSAGE',
|
||||
from_username: '',
|
||||
from_time: (user != null && user.timeOfJoining != null) ? +(user.timeOfJoining) : 0,
|
||||
},
|
||||
}, err => {
|
||||
if (err != null) {
|
||||
return logger.error(`_error ${err} when trying to insert welcome message for ${userId}`);
|
||||
} else {
|
||||
return logger.info(`_added/updated a system message in chat for user ${userId}`);
|
||||
}
|
||||
|
||||
// note that we already called callback() when updating the user. Adding
|
||||
// the welcome message in the chat is not as vital and we can afford to
|
||||
// complete it when possible, without blocking the serial event messages processing
|
||||
});
|
||||
} else {
|
||||
// logger.info "NOTE: got user_joined_message #{user.name} #{user.userid}"
|
||||
getStun({
|
||||
meetingId: meetingId,
|
||||
requesterUserId: userId,
|
||||
});
|
||||
|
||||
return Users.upsert({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
}, {
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
user: {
|
||||
userid: user.userid,
|
||||
presenter: user.presenter,
|
||||
name: user.name,
|
||||
_sort_name: user.name.toLowerCase(),
|
||||
phone_user: user.phone_user,
|
||||
emoji_status: user.emoji_status,
|
||||
set_emoji_time: user.set_emoji_time,
|
||||
has_stream: user.has_stream,
|
||||
role: user.role,
|
||||
listenOnly: user.listenOnly,
|
||||
extern_userid: user.extern_userid,
|
||||
locked: user.locked,
|
||||
time_of_joining: user.timeOfJoining,
|
||||
connection_status: '',
|
||||
voiceUser: {
|
||||
web_userid: user.voiceUser.web_userid,
|
||||
callernum: user.voiceUser.callernum,
|
||||
userid: user.voiceUser.userid,
|
||||
talking: user.voiceUser.talking,
|
||||
joined: user.voiceUser.joined,
|
||||
callername: user.voiceUser.callername,
|
||||
locked: user.voiceUser.locked,
|
||||
muted: user.voiceUser.muted,
|
||||
},
|
||||
webcam_stream: user.webcam_stream,
|
||||
},
|
||||
}, (err, numChanged) => {
|
||||
let funct;
|
||||
if (numChanged.insertedId != null) {
|
||||
funct = function (cbk) {
|
||||
logger.info(
|
||||
`_joining user (case2) userid=[${userId}]:${user.name}. Users.size is now ` +
|
||||
`${Users.find({
|
||||
meetingId: meetingId,
|
||||
}).count()}`
|
||||
);
|
||||
return cbk();
|
||||
};
|
||||
|
||||
return funct(callback);
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
@ -1,77 +0,0 @@
|
||||
import Users from '/imports/api/users';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
import { logger } from '/imports/startup/server/logger';
|
||||
import { requestUserLeaving } from '/imports/api/users/server/modifiers/requestUserLeaving';
|
||||
|
||||
// Publish only the online users that are in the particular meetingId
|
||||
// Also contains reconnection and connection_status info
|
||||
Meteor.publish('users', function (credentials) {
|
||||
const meetingId = credentials.meetingId;
|
||||
const userid = credentials.requesterUserId;
|
||||
const authToken = credentials.requesterToken;
|
||||
|
||||
logger.info(`attempt publishing users for ${meetingId}, ${userid}, ${authToken}`);
|
||||
const userObject = Users.findOne({
|
||||
userId: userid,
|
||||
meetingId: meetingId,
|
||||
});
|
||||
|
||||
if (!!userObject && !!userObject.user) {
|
||||
let username = 'UNKNOWN';
|
||||
if (isAllowedTo('subscribeUsers', credentials)) {
|
||||
logger.info(`${userid} was allowed to subscribe to 'users'`);
|
||||
username = userObject.user.name;
|
||||
|
||||
// offline -> online
|
||||
if (userObject.user.connection_status !== 'online') {
|
||||
Meteor.call('validateAuthToken', credentials);
|
||||
setConnectionStatus(meetingId, userid, 'online');
|
||||
}
|
||||
|
||||
this._session.socket.on('close', Meteor.bindEnvironment((function (_this) {
|
||||
return function () {
|
||||
logger.info(`a user lost connection: session.id=${_this._session.id}` +
|
||||
` userId = ${userid}, username=${username}, meeting=${meetingId}`);
|
||||
setConnectionStatus(meetingId, userid, 'offline');
|
||||
return requestUserLeaving(meetingId, userid);
|
||||
};
|
||||
})(this)));
|
||||
|
||||
return getUsers(meetingId);
|
||||
} else {
|
||||
logger.warn('was not authorized to subscribe to users');
|
||||
return this.error(new Meteor.Error(402, 'User was not authorized to subscribe to users'));
|
||||
}
|
||||
} else { //subscribing before the user was added to the collection
|
||||
Meteor.call('validateAuthToken', credentials);
|
||||
logger.info(`Sending validateAuthTokenthere for user ${userid} in ${meetingId}.`);
|
||||
return getUsers(meetingId);
|
||||
}
|
||||
});
|
||||
|
||||
const getUsers = function (meetingId) {
|
||||
//publish the users which are not offline
|
||||
return Users.find({
|
||||
meetingId: meetingId,
|
||||
'user.connection_status': {
|
||||
$in: ['online', ''],
|
||||
},
|
||||
}, {
|
||||
fields: {
|
||||
authToken: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const setConnectionStatus = function (meetingId, userId, statusStr) {
|
||||
Users.update({
|
||||
meetingId: meetingId,
|
||||
userId: userId,
|
||||
}, {
|
||||
$set: {
|
||||
'user.connection_status': statusStr,
|
||||
},
|
||||
}, (err, numChanged) => {
|
||||
logger.info(`User ${userId} in ${meetingId} goes ${statusStr}`);
|
||||
});
|
||||
};
|
44
bigbluebutton-html5/imports/api/users/server/publishers.js
Normal file
44
bigbluebutton-html5/imports/api/users/server/publishers.js
Normal file
@ -0,0 +1,44 @@
|
||||
import Users from '/imports/api/users';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { isAllowedTo } from '/imports/startup/server/userPermissions';
|
||||
|
||||
import userLeaving from './methods/userLeaving';
|
||||
import validateAuthToken from './methods/validateAuthToken';
|
||||
|
||||
Meteor.publish('users', function (credentials) {
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
validateAuthToken(credentials);
|
||||
|
||||
// TODO(auth): We need to fix the Authentication flow to enable ACL
|
||||
// if (!isAllowedTo('subscribeUsers', credentials)) {
|
||||
// this.error(new Meteor.Error(402, "The user was not authorized to subscribe for 'Users'"));
|
||||
// }
|
||||
|
||||
this.onStop(() => {
|
||||
userLeaving(credentials, requesterUserId);
|
||||
});
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
'user.connection_status': {
|
||||
$in: ['online', ''],
|
||||
},
|
||||
};
|
||||
|
||||
const options = {
|
||||
fields: {
|
||||
authToken: false,
|
||||
},
|
||||
};
|
||||
|
||||
Logger.info(`Publishing Users for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Users.find(selector, options);
|
||||
});
|
@ -8,6 +8,8 @@ const presenter = {
|
||||
//poll
|
||||
subscribePoll: true,
|
||||
subscribeAnswers: true,
|
||||
|
||||
setPresenter: true,
|
||||
};
|
||||
|
||||
// holds the values for whether the moderator user is allowed to perform an action (true)
|
||||
|
@ -22,7 +22,7 @@ let getEmojiData = () => {
|
||||
|
||||
// Below doesn't even need to receieve credentials
|
||||
const setEmoji = (toRaiseUserId, status) => {
|
||||
callServer('userSetEmoji', toRaiseUserId, status);
|
||||
callServer('setEmojiStatus', toRaiseUserId, status);
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@ -219,12 +219,12 @@ const userActions = {
|
||||
},
|
||||
clearStatus: {
|
||||
label: 'Clear Status',
|
||||
handler: user => callServer('userSetEmoji', user.id, 'none'),
|
||||
handler: user => callServer('setEmojiStatus', user.id, 'none'),
|
||||
icon: 'clear-status',
|
||||
},
|
||||
setPresenter: {
|
||||
label: 'Make Presenter',
|
||||
handler: user => callServer('setUserPresenter', user.id, user.name),
|
||||
handler: user => callServer('assignPresenter', user.id),
|
||||
icon: 'presentation',
|
||||
},
|
||||
kick: {
|
||||
|
@ -1,49 +1,19 @@
|
||||
import '/imports/startup/server';
|
||||
import '/imports/api/chat/server';
|
||||
|
||||
import '/imports/api/cursor/server';
|
||||
|
||||
import '/imports/api/deskshare/server/publications';
|
||||
import '/imports/api/deskshare/server/modifiers/clearDeskshareCollection';
|
||||
import '/imports/api/deskshare/server/modifiers/handleDeskShareChange';
|
||||
import '/imports/api/deskshare/server/modifiers/handleIncomingDeskshareMessage';
|
||||
import '/imports/api/deskshare/server/modifiers/eventHandlers';
|
||||
|
||||
import '/imports/api/meetings/server';
|
||||
|
||||
import '/imports/api/phone/server/modifiers/eventHandlers';
|
||||
|
||||
import '/imports/api/polls/server';
|
||||
|
||||
import '/imports/api/breakouts/server';
|
||||
|
||||
import '/imports/api/presentations/server';
|
||||
|
||||
import '/imports/api/shapes/server';
|
||||
|
||||
import '/imports/api/slides/server';
|
||||
|
||||
import '/imports/api/captions/server';
|
||||
|
||||
import '/imports/api/users/server/publications';
|
||||
import '/imports/api/users/server/methods/kickUser';
|
||||
import '/imports/api/users/server/methods/listenOnlyRequestToggle';
|
||||
import '/imports/api/users/server/methods/muteUser';
|
||||
import '/imports/api/users/server/methods/setUserPresenter';
|
||||
import '/imports/api/users/server/methods/unmuteUser';
|
||||
import '/imports/api/users/server/methods/userLogout';
|
||||
import '/imports/api/users/server/methods/userSetEmoji';
|
||||
import '/imports/api/users/server/methods/validateAuthToken';
|
||||
import '/imports/api/users/server/modifiers/clearUsersCollection';
|
||||
import '/imports/api/users/server/modifiers/createDummyUser';
|
||||
import '/imports/api/users/server/modifiers/handleLockingMic';
|
||||
import '/imports/api/users/server/modifiers/markUserOffline';
|
||||
import '/imports/api/users/server/modifiers/requestUserLeaving';
|
||||
import '/imports/api/users/server/modifiers/setUserLockedStatus';
|
||||
import '/imports/api/users/server/modifiers/updateVoiceUser';
|
||||
import '/imports/api/users/server/modifiers/userJoined';
|
||||
import '/imports/api/users/server/modifiers/eventHandlers';
|
||||
|
||||
import '/imports/api/users/server';
|
||||
import '/imports/api/common/server/helpers';
|
||||
import '/imports/startup/server/logger';
|
||||
import '/imports/startup/server/userPermissions';
|
||||
|
Loading…
Reference in New Issue
Block a user