Merged 2.0 and 1.1 breakouts, chat, and captions
This commit is contained in:
parent
aa39361360
commit
47d9b53cc2
@ -1 +1,14 @@
|
||||
export default new Mongo.Collection('breakouts');
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Breakouts = new Mongo.Collection('breakouts2x');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// types of queries for the breakouts:
|
||||
// 1. breakoutId ( handleJoinUrl, roomStarted, clearBreakouts )
|
||||
// 2. parentMeetingId ( updateTimeRemaining )
|
||||
|
||||
Breakouts._ensureIndex({ breakoutId: 1 });
|
||||
Breakouts._ensureIndex({ parentMeetingId: 1 });
|
||||
}
|
||||
|
||||
export default Breakouts;
|
||||
|
@ -1,13 +1,10 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
|
||||
import handleCreateBreakout from './handlers/createBreakout';
|
||||
import handleBreakoutStarted from './handlers/breakoutStarted';
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import handleBreakoutJoinURL from './handlers/breakoutJoinURL';
|
||||
import handleBreakoutStarted from './handlers/breakoutStarted';
|
||||
import handleUpdateTimeRemaining from './handlers/updateTimeRemaining';
|
||||
import handleBreakoutClosed from './handlers/breakoutClosed';
|
||||
|
||||
RedisPubSub.on('CreateBreakoutRoomRequest', handleCreateBreakout);
|
||||
RedisPubSub.on('BreakoutRoomStarted', handleBreakoutStarted);
|
||||
RedisPubSub.on('BreakoutRoomJoinURL', handleBreakoutJoinURL);
|
||||
RedisPubSub.on('BreakoutRoomsTimeRemainingUpdate', handleUpdateTimeRemaining);
|
||||
RedisPubSub.on('BreakoutRoomClosed', handleBreakoutClosed);
|
||||
RedisPubSub.on('BreakoutRoomStartedEvtMsg', handleBreakoutStarted);
|
||||
RedisPubSub.on('BreakoutRoomJoinURLEvtMsg', handleBreakoutJoinURL);
|
||||
RedisPubSub.on('BreakoutRoomsTimeRemainingUpdateEvtMsg', handleUpdateTimeRemaining);
|
||||
RedisPubSub.on('BreakoutRoomEndedEvtMsg', handleBreakoutClosed);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import clearBreakouts from '../modifiers/clearBreakouts';
|
||||
import { check } from 'meteor/check';
|
||||
import clearBreakouts from '../modifiers/clearBreakouts';
|
||||
|
||||
export default function handleBreakoutClosed({ payload }) {
|
||||
const meetingId = payload.meetingId;
|
||||
export default function handleBreakoutClosed({ body }) {
|
||||
const { breakoutId } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(breakoutId, String);
|
||||
|
||||
return clearBreakouts(meetingId);
|
||||
return clearBreakouts(breakoutId);
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Breakouts from '/imports/api/1.1/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
import xml2js from 'xml2js';
|
||||
import url from 'url';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
|
||||
const xmlParser = new xml2js.Parser();
|
||||
|
||||
const getUrlParams = (urlToParse) => {
|
||||
@ -12,48 +12,48 @@ const getUrlParams = (urlToParse) => {
|
||||
return parsedUrl.query;
|
||||
};
|
||||
|
||||
export default function handleBreakoutJoinURL({ payload }) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CLIENT_HTML = 'HTML5';
|
||||
|
||||
export default function handleBreakoutJoinURL({ body }) {
|
||||
const {
|
||||
noRedirectJoinURL,
|
||||
} = payload;
|
||||
userId,
|
||||
breakoutId,
|
||||
} = body;
|
||||
|
||||
check(noRedirectJoinURL, String);
|
||||
|
||||
const urlParams = getUrlParams(noRedirectJoinURL);
|
||||
|
||||
const selector = {
|
||||
externalMeetingId: urlParams.meetingID,
|
||||
breakoutId,
|
||||
};
|
||||
|
||||
let breakout = Breakouts.findOne(selector);
|
||||
|
||||
const res = Meteor.http.call('get', noRedirectJoinURL);
|
||||
|
||||
xmlParser.parseString(res.content, (err, parsedXML) => {
|
||||
if (err) {
|
||||
return Logger.error(`An Error occured when parsing xml response for: ${noRedirectJoinURL}`);
|
||||
}
|
||||
|
||||
breakout = Breakouts.findOne(selector);
|
||||
const breakout = Breakouts.findOne(selector);
|
||||
|
||||
const { response } = parsedXML;
|
||||
const users = breakout.users;
|
||||
|
||||
const user = {
|
||||
userId: payload.userId,
|
||||
userId,
|
||||
urlParams: {
|
||||
meetingId: response.meeting_id[0],
|
||||
userId: response.user_id[0],
|
||||
authToken: response.auth_token[0],
|
||||
sessionToken: response.session_token[0],
|
||||
},
|
||||
};
|
||||
|
||||
const userExists = users.find(u => user.userId === u.userId);
|
||||
|
||||
if (userExists) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const modifier = {
|
||||
@ -62,9 +62,9 @@ export default function handleBreakoutJoinURL({ payload }) {
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding breakout to collection: ${err}`);
|
||||
const cb = (cbErr, numChanged) => {
|
||||
if (cbErr) {
|
||||
return Logger.error(`Adding breakout to collection: ${cbErr}`);
|
||||
}
|
||||
|
||||
const {
|
||||
|
@ -1,38 +1,41 @@
|
||||
import Breakouts from '/imports/api/1.1/breakouts';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import flat from 'flat';
|
||||
|
||||
export default function handleBreakoutRoomStarted({ payload }) {
|
||||
export default function handleBreakoutRoomStarted({ body }, meetingId) {
|
||||
const {
|
||||
meetingId,
|
||||
timeRemaining,
|
||||
externalMeetingId,
|
||||
} = payload;
|
||||
parentMeetingId,
|
||||
breakout,
|
||||
} = body;
|
||||
|
||||
const { breakoutId } = breakout;
|
||||
|
||||
const timeRemaining = 15;
|
||||
|
||||
check(meetingId, String);
|
||||
|
||||
const selector = {
|
||||
breakoutMeetingId: meetingId,
|
||||
breakoutId,
|
||||
};
|
||||
|
||||
modifier = {
|
||||
$set: {
|
||||
users: [],
|
||||
timeRemaining: Number(timeRemaining),
|
||||
externalMeetingId,
|
||||
},
|
||||
const modifier = {
|
||||
$set: Object.assign(
|
||||
{ users: [] },
|
||||
{ timeRemaining: Number(timeRemaining) },
|
||||
{ parentMeetingId },
|
||||
flat(breakout),
|
||||
),
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
const cb = (err) => {
|
||||
if (err) {
|
||||
return Logger.error(`updating breakout: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info('Updated timeRemaining and externalMeetingId ' +
|
||||
`for breakout id=${meetingId}`);
|
||||
}
|
||||
return Logger.info('Updated timeRemaining and externalMeetingId ' +
|
||||
`for breakout id=${breakoutId}`);
|
||||
};
|
||||
|
||||
return Breakouts.update(selector, modifier, cb);
|
||||
return Breakouts.upsert(selector, modifier, cb);
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
import Breakouts from '/imports/api/1.1/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import addBreakout from '../modifiers/addBreakout';
|
||||
|
||||
export default function handleCreateBreakout({ payload }) {
|
||||
const { breakoutMeetingId } = payload;
|
||||
|
||||
check(breakoutMeetingId, String);
|
||||
|
||||
return addBreakout(payload);
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
import Breakouts from '/imports/api/1.1/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
|
||||
export default function handleUpdateTimeRemaining({ payload }) {
|
||||
export default function handleUpdateTimeRemaining({ body }, meetingId) {
|
||||
const {
|
||||
meetingId,
|
||||
timeRemaining,
|
||||
} = payload;
|
||||
} = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(timeRemaining, Number);
|
||||
@ -25,15 +24,13 @@ export default function handleUpdateTimeRemaining({ payload }) {
|
||||
multi: true,
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
const cb = (err) => {
|
||||
if (err) {
|
||||
return Logger.error(`Updating breakouts: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.info('Updated breakout time remaining for breakouts ' +
|
||||
`where parentMeetingId=${meetingId}`);
|
||||
}
|
||||
return Logger.info('Updated breakout time remaining for breakouts ' +
|
||||
`where parentMeetingId=${meetingId}`);
|
||||
};
|
||||
|
||||
return Breakouts.update(selector, modifier, options, cb);
|
||||
|
@ -1,42 +0,0 @@
|
||||
import Breakouts from '/imports/api/1.1/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default function addBreakout(payload) {
|
||||
const {
|
||||
breakoutMeetingId,
|
||||
parentMeetingId,
|
||||
name,
|
||||
} = payload;
|
||||
|
||||
check(breakoutMeetingId, String);
|
||||
check(parentMeetingId, String);
|
||||
check(name, String);
|
||||
|
||||
const selector = { breakoutMeetingId };
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
breakoutMeetingId,
|
||||
parentMeetingId,
|
||||
name,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding breakout to collection: ${err}`);
|
||||
}
|
||||
|
||||
const {
|
||||
insertedId,
|
||||
} = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.info(`Added breakout id=${breakoutMeetingId}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Upserted breakout id=${breakoutMeetingId}`);
|
||||
};
|
||||
|
||||
return Breakouts.upsert(selector, modifier, cb);
|
||||
}
|
@ -1,19 +1,13 @@
|
||||
import Breakouts from '/imports/api/1.1/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import removeMeeting from '/imports/api/1.1/meetings/server/modifiers/removeMeeting';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
|
||||
export default function clearBreakouts(meetingId) {
|
||||
if (meetingId) {
|
||||
export default function clearBreakouts(breakoutId) {
|
||||
if (breakoutId) {
|
||||
const selector = {
|
||||
breakoutMeetingId: meetingId,
|
||||
breakoutId,
|
||||
};
|
||||
|
||||
const cb = () => {
|
||||
Logger.info(`Cleared Breakouts (${meetingId})`);
|
||||
removeMeeting(meetingId);
|
||||
};
|
||||
|
||||
return Breakouts.remove(selector, cb);
|
||||
return Breakouts.remove(selector);
|
||||
}
|
||||
|
||||
return Breakouts.remove({}, Logger.info('Cleared Breakouts (all)'));
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Breakouts from '/imports/api/1.1/breakouts';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
function breakouts(credentials) {
|
||||
const {
|
||||
@ -8,9 +9,12 @@ function breakouts(credentials) {
|
||||
requesterUserId,
|
||||
} = credentials;
|
||||
|
||||
Logger.info(`Publishing Breakouts2x for ${meetingId} ${requesterUserId}`);
|
||||
|
||||
return Breakouts.find({
|
||||
$or: [
|
||||
{ breakoutMeetingId: meetingId },
|
||||
{ breakoutId: meetingId },
|
||||
{ meetingId },
|
||||
{
|
||||
users: {
|
||||
$elemMatch: { userId: requesterUserId },
|
||||
@ -25,4 +29,4 @@ function publish(...args) {
|
||||
return mapToAcl('subscriptions.breakouts', boundBreakouts)(args);
|
||||
}
|
||||
|
||||
Meteor.publish('breakouts', publish);
|
||||
Meteor.publish('breakouts2x', publish);
|
||||
|
@ -1 +1,14 @@
|
||||
export default new Mongo.Collection('captions');
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Captions = new Mongo.Collection('captions2x');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// types of queries for the captions:
|
||||
// 1. meetingId, locale, 'captionHistory.index' (History)
|
||||
// 2. meetingId, locale (Owner update, Caption update, addCaption)
|
||||
// 3. meetingId ( clear Captions)
|
||||
|
||||
Captions._ensureIndex({ meetingId: 1, locale: 1 });
|
||||
}
|
||||
|
||||
export default Captions;
|
||||
|
@ -1,8 +1,9 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import handleCaptionHistory from './handlers/captionHistory';
|
||||
import handleCaptionUpdate from './handlers/captionUpdate';
|
||||
import handleCaptionOwnerUpdate from './handlers/captionOwnerUpdate';
|
||||
|
||||
RedisPubSub.on('send_caption_history_reply_message', handleCaptionHistory);
|
||||
RedisPubSub.on('edit_caption_history_message', handleCaptionUpdate);
|
||||
RedisPubSub.on('update_caption_owner_message', handleCaptionOwnerUpdate);
|
||||
// TODO
|
||||
RedisPubSub.on('SendCaptionHistoryRespMsg', handleCaptionHistory);
|
||||
RedisPubSub.on('EditCaptionHistoryEvtMsg', handleCaptionUpdate);
|
||||
RedisPubSub.on('UpdateCaptionOwnerEvtMsg', handleCaptionOwnerUpdate);
|
||||
|
@ -1,65 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import Captions from '/imports/api/1.1/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionHistory({ payload }) {
|
||||
if (!inReplyToHTML5Client({ payload })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SERVER_CONFIG = Meteor.settings.app;
|
||||
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
|
||||
|
||||
const meetingId = payload.meeting_id;
|
||||
const locale = payload.locale;
|
||||
const captionHistory = payload.caption_history;
|
||||
|
||||
check(meetingId, String);
|
||||
check(captionHistory, Object);
|
||||
|
||||
const captionsAdded = [];
|
||||
_.each(captionHistory, (caption, locale) => {
|
||||
const ownerId = caption[0];
|
||||
let captions = caption[1].slice(0);
|
||||
const chunks = [];
|
||||
|
||||
if (captions.length === 0) {
|
||||
chunks.push('');
|
||||
} else {
|
||||
while (captions.length > 0) {
|
||||
if (captions.length > CAPTION_CHUNK_LENGTH) {
|
||||
chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
|
||||
captions = captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
} else {
|
||||
chunks.push(captions);
|
||||
captions = captions.slice(captions.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectorToRemove = {
|
||||
meetingId,
|
||||
locale,
|
||||
'captionHistory.index': { $gt: (chunks.length - 1) },
|
||||
};
|
||||
|
||||
Captions.remove(selectorToRemove);
|
||||
|
||||
chunks.forEach((captions, index) => {
|
||||
const captionHistoryObject = {
|
||||
locale,
|
||||
ownerId,
|
||||
captions,
|
||||
index,
|
||||
next: (index < chunks.length - 1) ? index + 1 : undefined,
|
||||
};
|
||||
|
||||
captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
|
||||
});
|
||||
});
|
||||
|
||||
return captionsAdded;
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import Captions from '/imports/api/1.1/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionOwnerUpdate({ payload }) {
|
||||
const meetingId = payload.meeting_id;
|
||||
const locale = payload.locale;
|
||||
const ownerId = payload.owner_id;
|
||||
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
check(ownerId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
locale,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'captionHistory.ownerId': ownerId,
|
||||
},
|
||||
};
|
||||
|
||||
const Caption = Captions.findOne(selector);
|
||||
|
||||
if (!Caption) {
|
||||
const captionHistory = {
|
||||
ownerId,
|
||||
captions: '',
|
||||
index: 0,
|
||||
next: null,
|
||||
};
|
||||
|
||||
return addCaption(meetingId, locale, captionHistory);
|
||||
}
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Updating captions owner: ${err}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.verbose(`Update caption owner locale=${locale} meeting=${meetingId}`);
|
||||
}
|
||||
};
|
||||
|
||||
return Captions.update(selector, modifier, { multi: true }, cb);
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
import Captions from '/imports/api/1.1/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionUpdate({ payload }) {
|
||||
const SERVER_CONFIG = Meteor.settings.app;
|
||||
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
|
||||
|
||||
const meetingId = payload.meeting_id;
|
||||
const locale = payload.locale;
|
||||
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
|
||||
const captionsObjects = Captions.find({
|
||||
meetingId,
|
||||
locale,
|
||||
}, {
|
||||
sort: {
|
||||
locale: 1,
|
||||
'captionHistory.index': 1,
|
||||
},
|
||||
}).fetch();
|
||||
|
||||
const objectsToUpdate = [];
|
||||
if (captionsObjects != null) {
|
||||
let startIndex;
|
||||
let endIndex;
|
||||
let length = 0;
|
||||
let current = captionsObjects[0];
|
||||
|
||||
// looking for a start index and end index
|
||||
// (end index only for the case when they are in the same block)
|
||||
while (current != null) {
|
||||
length += current.captionHistory.captions.length;
|
||||
|
||||
// if length is bigger than start index - we found our start index
|
||||
if (length >= payload.start_index && startIndex == undefined) {
|
||||
// check if it's a new character somewhere in the middle of captions text
|
||||
if (length - 1 >= payload.start_index) {
|
||||
startIndex = payload.start_index - (length - current.captionHistory.captions.length);
|
||||
|
||||
// check to see if the end_index is in the same object as start_index
|
||||
if (length - 1 >= payload.end_index) {
|
||||
endIndex = payload.end_index - (length - current.captionHistory.captions.length);
|
||||
const _captions = current.captionHistory.captions;
|
||||
current.captionHistory.captions = _captions.slice(0, startIndex) +
|
||||
payload.text +
|
||||
_captions.slice(endIndex);
|
||||
objectsToUpdate.push(current);
|
||||
break;
|
||||
|
||||
// end index is not in the same object as start_index, we will find it later
|
||||
} else {
|
||||
current.captionHistory.captions = current.captionHistory.captions.slice(0, startIndex) +
|
||||
payload.text;
|
||||
objectsToUpdate.push(current);
|
||||
break;
|
||||
}
|
||||
|
||||
// separate case for appending new characters to the very end of the string
|
||||
} else if (current.captionHistory.next == null &&
|
||||
length == payload.start_index &&
|
||||
length == payload.start_index) {
|
||||
startIndex = 1;
|
||||
endIndex = 1;
|
||||
current.captionHistory.captions += payload.text;
|
||||
objectsToUpdate.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
current = captionsObjects[current.captionHistory.next];
|
||||
}
|
||||
|
||||
// looking for end index here if it wasn't in the same object as start index
|
||||
if (startIndex != undefined && endIndex == undefined) {
|
||||
current = captionsObjects[current.captionHistory.next];
|
||||
while (current != null) {
|
||||
length += current.captionHistory.captions.length;
|
||||
|
||||
// check to see if the end_index is in the current object
|
||||
if (length - 1 >= payload.end_index) {
|
||||
endIndex = payload.end_index - (length - current.captionHistory.captions.length);
|
||||
current.captionHistory.captions = current.captionHistory.captions.slice(endIndex);
|
||||
objectsToUpdate.push(current);
|
||||
|
||||
break;
|
||||
|
||||
// if end_index wasn't in the current object, that means this whole object was deleted
|
||||
// initializing string to ''
|
||||
} else {
|
||||
current.captionHistory.captions = '';
|
||||
objectsToUpdate.push(current);
|
||||
}
|
||||
|
||||
current = captionsObjects[current.captionHistory.next];
|
||||
}
|
||||
}
|
||||
|
||||
// looking for the strings which exceed the limit and split them into multiple objects
|
||||
let maxIndex = captionsObjects.length - 1;
|
||||
for (i = 0; i < objectsToUpdate.length; i++) {
|
||||
if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
|
||||
// string is too large. Check if the next object exists and if it can
|
||||
// accomodate the part of the string that exceeds the limits
|
||||
const _nextIndex = objectsToUpdate[i].captionHistory.next;
|
||||
if (_nextIndex != null &&
|
||||
captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
|
||||
const extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
|
||||
// could assign it directly, but our linter complained
|
||||
let _captions = objectsToUpdate[i].captionHistory.captions;
|
||||
_captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
|
||||
objectsToUpdate[i].captionHistory.captions = _captions;
|
||||
|
||||
// check to see if the next object was added to objectsToUpdate array
|
||||
if (objectsToUpdate[i + 1] != null &&
|
||||
objectsToUpdate[i].captionHistory.next == objectsToUpdate[i + 1].captionHistory.index) {
|
||||
objectsToUpdate[i + 1].captionHistory.captions = extraString +
|
||||
objectsToUpdate[i + 1].captionHistory.captions;
|
||||
|
||||
// next object wasn't added to objectsToUpdate array, adding it from captionsObjects array.
|
||||
} else {
|
||||
const nextObj = captionsObjects[objectsToUpdate[i].captionHistory.next];
|
||||
nextObj.captionHistory.captions = extraString + nextObj.captionHistory.captions;
|
||||
objectsToUpdate.push(nextObj);
|
||||
}
|
||||
|
||||
// next object was full already, so we create another and insert it in between them
|
||||
} else {
|
||||
// need to take a current object out of the objectsToUpdate and add it back after
|
||||
// every other object, so that Captions collection could be updated in a proper order
|
||||
const tempObj = objectsToUpdate.splice(i, 1);
|
||||
let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
|
||||
tempObj[0].captionHistory.captions =
|
||||
tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
|
||||
|
||||
maxIndex += 1;
|
||||
const tempIndex = tempObj[0].captionHistory.next;
|
||||
tempObj[0].captionHistory.next = maxIndex;
|
||||
|
||||
while (extraString.length != 0) {
|
||||
const entry = {
|
||||
meetingId,
|
||||
locale,
|
||||
captionHistory: {
|
||||
locale,
|
||||
ownerId: tempObj[0].captionHistory.ownerId,
|
||||
captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
|
||||
index: maxIndex,
|
||||
next: null,
|
||||
},
|
||||
};
|
||||
maxIndex += 1;
|
||||
extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
|
||||
if (extraString.length > 0) {
|
||||
entry.captionHistory.next = maxIndex;
|
||||
} else {
|
||||
entry.captionHistory.next = tempIndex;
|
||||
}
|
||||
|
||||
objectsToUpdate.push(entry);
|
||||
}
|
||||
|
||||
objectsToUpdate.push(tempObj[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const captionsAdded = [];
|
||||
objectsToUpdate.forEach((entry) => {
|
||||
const { _id, meetingId, locale, captionHistory } = entry;
|
||||
captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
|
||||
});
|
||||
|
||||
return captionsAdded;
|
||||
}
|
@ -1,17 +1,26 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Captions from '/imports/api/1.1/captions';
|
||||
import { Match, check } from 'meteor/check';
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function addCaption(meetingId, locale, captionHistory, id = false) {
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
check(captionHistory, Object);
|
||||
|
||||
check(captionHistory, {
|
||||
ownerId: String,
|
||||
index: Number,
|
||||
captions: String,
|
||||
locale: Match.Maybe(String),
|
||||
localeCode: Match.Maybe(String),
|
||||
next: Match.OneOf(Number, undefined, null),
|
||||
});
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
locale,
|
||||
};
|
||||
|
||||
|
||||
if (id) {
|
||||
selector._id = id;
|
||||
} else {
|
||||
@ -22,27 +31,21 @@ export default function addCaption(meetingId, locale, captionHistory, id = false
|
||||
$set: {
|
||||
meetingId,
|
||||
locale,
|
||||
'captionHistory.locale': locale,
|
||||
'captionHistory.ownerId': captionHistory.ownerId,
|
||||
'captionHistory.captions': captionHistory.captions,
|
||||
'captionHistory.next': captionHistory.next,
|
||||
'captionHistory.index': captionHistory.index,
|
||||
captionHistory,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding caption to collection: ${err}`);
|
||||
return Logger.error(`Adding caption2x to collection: ${err}`);
|
||||
}
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.verbose(`Added caption locale=${locale} meeting=${meetingId}`);
|
||||
return Logger.verbose(`Added caption2x locale=${locale} meeting=${meetingId}`);
|
||||
}
|
||||
|
||||
if (numChanged) {
|
||||
return Logger.verbose(`Upserted caption locale=${locale} meeting=${meetingId}`);
|
||||
}
|
||||
return Logger.verbose(`Upserted caption2x locale=${locale} meeting=${meetingId}`);
|
||||
};
|
||||
|
||||
return Captions.upsert(selector, modifier, cb);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import Captions from '/imports/api/1.1/captions';
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearCaptions(meetingId) {
|
||||
if (meetingId) {
|
||||
return Captions.remove({ meetingId }, Logger.info(`Cleared Captions (${meetingId})`));
|
||||
return Captions.remove({ meetingId }, Logger.info(`Cleared Captions2x (${meetingId})`));
|
||||
}
|
||||
|
||||
return Captions.remove({}, Logger.info('Cleared Captions (all)'));
|
||||
return Captions.remove({}, Logger.info('Cleared Captions2x (all)'));
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Captions from '/imports/api/1.1/captions';
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
@ -11,7 +11,7 @@ function captions(credentials) {
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
Logger.verbose(`Publishing Captions for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
Logger.verbose(`Publishing Captions2x for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Captions.find({ meetingId });
|
||||
}
|
||||
@ -21,4 +21,4 @@ function publish(...args) {
|
||||
return mapToAcl('subscriptions.captions', boundCaptions)(args);
|
||||
}
|
||||
|
||||
Meteor.publish('captions', publish);
|
||||
Meteor.publish('captions2x', publish);
|
||||
|
@ -1 +1,19 @@
|
||||
export default new Mongo.Collection('chat');
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Chat = new Mongo.Collection('chat2x');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// types of queries for the chat:
|
||||
// 1. meetingId, toUsername (publishers)
|
||||
// 2. meetingId, fromUserId (publishers)
|
||||
// 3. meetingId, toUserId (publishers)
|
||||
// 4. meetingId, fromTime, fromUserId, toUserId (addChat)
|
||||
// 5. meetingId (clearChat)
|
||||
// 6. meetingId, fromUserId, toUserId (clearSystemMessages)
|
||||
|
||||
Chat._ensureIndex({ meetingId: 1, toUsername: 1 });
|
||||
Chat._ensureIndex({ meetingId: 1, fromUserId: 1 });
|
||||
Chat._ensureIndex({ meetingId: 1, toUserId: 1 });
|
||||
}
|
||||
|
||||
export default Chat;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import handleChatMessage from './handlers/chatMessage';
|
||||
import handleChatHistory from './handlers/chatHistory';
|
||||
import handleChatPublicHistoryClear from './handlers/chatPublicHistoryClear';
|
||||
|
||||
RedisPubSub.on('get_chat_history_reply', handleChatHistory);
|
||||
RedisPubSub.on('send_public_chat_message', handleChatMessage);
|
||||
RedisPubSub.on('send_private_chat_message', handleChatMessage);
|
||||
RedisPubSub.on('GetChatHistoryRespMsg', handleChatHistory);
|
||||
RedisPubSub.on('SendPublicMessageEvtMsg', handleChatMessage);
|
||||
RedisPubSub.on('SendPrivateMessageEvtMsg', handleChatMessage);
|
||||
RedisPubSub.on('ClearPublicChatHistoryEvtMsg', handleChatPublicHistoryClear);
|
||||
|
@ -1,22 +1,15 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import { inReplyToHTML5Client } from '/imports/api/common/server/helpers';
|
||||
import addChat from '../modifiers/addChat';
|
||||
|
||||
export default function handleChatHistory({ payload }) {
|
||||
if (!inReplyToHTML5Client({ payload })) {
|
||||
return;
|
||||
}
|
||||
|
||||
const meetingId = payload.meeting_id;
|
||||
const chatHistory = payload.chat_history || [];
|
||||
export default function handleChatHistory({ body }, meetingId) {
|
||||
const { history } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(chatHistory, Array);
|
||||
check(history, Array);
|
||||
|
||||
const chatsAdded = [];
|
||||
|
||||
chatHistory.forEach((message) => {
|
||||
history.forEach((message) => {
|
||||
chatsAdded.push(addChat(meetingId, message));
|
||||
});
|
||||
|
||||
|
@ -1,17 +1,11 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import addChat from '../modifiers/addChat';
|
||||
|
||||
export default function handleChatMessage({ payload, header }) {
|
||||
const message = payload.message;
|
||||
const meetingId = payload.meeting_id;
|
||||
export default function handleChatMessage({ body }, meetingId) {
|
||||
const { message } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(message, Object);
|
||||
|
||||
// use current_time instead of message.from_time so that the
|
||||
// chats from Flash and HTML5 have uniform times
|
||||
message.from_time = +(header.current_time);
|
||||
|
||||
return addChat(meetingId, message);
|
||||
}
|
||||
|
@ -1,3 +1,9 @@
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import sendChat from './methods/sendChat';
|
||||
import clearPublicChatHistory from './methods/clearPublicChatHistory';
|
||||
|
||||
Meteor.methods({});
|
||||
Meteor.methods(mapToAcl(['methods.sendChat', 'methods.clearPublicChatHistory'], {
|
||||
sendChat,
|
||||
clearPublicChatHistory,
|
||||
}));
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import RegexWebUrl from '/imports/utils/regex-weburl';
|
||||
|
||||
const HTML_SAFE_MAP = {
|
||||
@ -11,30 +10,28 @@ const HTML_SAFE_MAP = {
|
||||
"'": ''',
|
||||
};
|
||||
|
||||
const PUBLIC_CHAT_TYPE = 'PUBLIC_CHAT';
|
||||
|
||||
const parseMessage = (message) => {
|
||||
message = message || '';
|
||||
message = message.trim();
|
||||
let parsedMessage = message || '';
|
||||
parsedMessage = parsedMessage.trim();
|
||||
|
||||
// Replace <br/> with \n\r
|
||||
message = message.replace(/<br\s*[\/]?>/gi, '\n\r');
|
||||
parsedMessage = parsedMessage.replace(/<br\s*[\\/]?>/gi, '\n\r');
|
||||
|
||||
// Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
|
||||
message = message.replace(/[<>'"]/g, c => HTML_SAFE_MAP[c]);
|
||||
parsedMessage = parsedMessage.replace(/[<>'"]/g, c => HTML_SAFE_MAP[c]);
|
||||
|
||||
// Replace flash links to flash valid ones
|
||||
message = message.replace(RegexWebUrl, "<a href='event:$&'><u>$&</u></a>");
|
||||
parsedMessage = parsedMessage.replace(RegexWebUrl, "<a href='event:$&'><u>$&</u></a>");
|
||||
|
||||
return message;
|
||||
return parsedMessage;
|
||||
};
|
||||
|
||||
export default function sendChat(credentials, message) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toBBBApps.chat;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
|
||||
const TO_PUBLIC_CHAT = CHAT_CONFIG.public_username;
|
||||
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
@ -43,21 +40,15 @@ export default function sendChat(credentials, message) {
|
||||
check(requesterToken, String);
|
||||
check(message, Object);
|
||||
|
||||
let actionName = message.to_userid === requesterUserId ? 'chatSelf' : 'chatPrivate';
|
||||
let eventName = 'send_private_chat_message';
|
||||
let eventName = 'SendPrivateMessagePubMsg';
|
||||
|
||||
message.message = parseMessage(message.message);
|
||||
const parsedMessage = message;
|
||||
|
||||
if (message.chat_type === PUBLIC_CHAT_TYPE) {
|
||||
eventName = 'send_public_chat_message';
|
||||
actionName = 'chatPublic';
|
||||
parsedMessage.message = parseMessage(message.message);
|
||||
|
||||
if (message.toUsername === TO_PUBLIC_CHAT) {
|
||||
eventName = 'SendPublicMessagePubMsg';
|
||||
}
|
||||
|
||||
const payload = {
|
||||
message,
|
||||
meeting_id: meetingId,
|
||||
requester_id: message.from_userid,
|
||||
};
|
||||
|
||||
return RedisPubSub.publish(CHANNEL, eventName, payload);
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, eventName, meetingId, requesterUserId, { message: parsedMessage });
|
||||
}
|
||||
|
@ -1,56 +1,61 @@
|
||||
import Chat from '/imports/api/1.1/chat';
|
||||
import flat from 'flat';
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import { BREAK_LINE } from '/imports/utils/lineEndings.js';
|
||||
import { Match, check } from 'meteor/check';
|
||||
import { BREAK_LINE } from '/imports/utils/lineEndings';
|
||||
|
||||
const parseMessage = (message) => {
|
||||
message = message || '';
|
||||
let parsedMessage = message || '';
|
||||
|
||||
// Replace \r and \n to <br/>
|
||||
message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, `$1${BREAK_LINE}$2`);
|
||||
parsedMessage = parsedMessage.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="');
|
||||
parsedMessage = parsedMessage.split('<a href=\'event:').join('<a target="_blank" href=\'');
|
||||
parsedMessage = parsedMessage.split('<a href="event:').join('<a target="_blank" href="');
|
||||
|
||||
return message;
|
||||
return parsedMessage;
|
||||
};
|
||||
|
||||
export default function addChat(meetingId, message) {
|
||||
// manually convert time from 1.408645053653E12 to 1408645053653 if necessary
|
||||
// (this is the time_from that the Flash client outputs)
|
||||
message.from_time = +(message.from_time.toString().split('.').join('').split('E')[0]);
|
||||
message.message = parseMessage(message.message);
|
||||
const chatType = (userName) => {
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
|
||||
const fromUserId = message.from_userid;
|
||||
const toUserId = message.to_userid;
|
||||
const typeByUser = {
|
||||
[CHAT_CONFIG.system_username]: CHAT_CONFIG.type_system,
|
||||
[CHAT_CONFIG.public_username]: CHAT_CONFIG.type_public,
|
||||
};
|
||||
|
||||
check(fromUserId, String);
|
||||
check(toUserId, String);
|
||||
return userName in typeByUser ? typeByUser[userName] : CHAT_CONFIG.type_private;
|
||||
};
|
||||
|
||||
export default function addChat(meetingId, chat) {
|
||||
check(chat, {
|
||||
message: String,
|
||||
fromColor: String,
|
||||
toUserId: String,
|
||||
toUsername: String,
|
||||
fromUserId: String,
|
||||
fromUsername: Match.Maybe(String),
|
||||
fromTime: Number,
|
||||
fromTimezoneOffset: Match.Maybe(Number),
|
||||
});
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
'message.from_time': message.from_time,
|
||||
'message.from_userid': message.from_userid,
|
||||
'message.to_userid': message.to_userid,
|
||||
fromTime: chat.fromTime,
|
||||
fromUserId: chat.fromUserId,
|
||||
toUserId: chat.toUserId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
message: {
|
||||
chat_type: message.chat_type,
|
||||
message: message.message,
|
||||
to_username: message.to_username,
|
||||
from_tz_offset: message.from_tz_offset,
|
||||
from_color: message.from_color,
|
||||
to_userid: message.to_userid,
|
||||
from_userid: message.from_userid,
|
||||
from_time: message.from_time,
|
||||
from_username: message.from_username,
|
||||
from_lang: message.from_lang,
|
||||
$set: Object.assign(
|
||||
flat(chat, { safe: true }),
|
||||
{
|
||||
meetingId,
|
||||
message: parseMessage(chat.message),
|
||||
type: chatType(chat.toUsername),
|
||||
},
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
@ -59,11 +64,13 @@ export default function addChat(meetingId, message) {
|
||||
}
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
const to = chat.toUsername || 'PUBLIC';
|
||||
|
||||
if (insertedId) {
|
||||
const to = message.to_username || 'PUBLIC';
|
||||
return Logger.info(`Added chat id=${insertedId} from=${message.from_username} to=${to}`);
|
||||
return Logger.info(`Added chat from=${chat.fromUsername} to=${to} time=${chat.fromTime}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Upserted chat from=${chat.fromUsername} to=${to} time=${chat.fromTime}`);
|
||||
};
|
||||
|
||||
return Chat.upsert(selector, modifier, cb);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Chat from '/imports/api/1.1/chat';
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearChats(meetingId) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Chat from '/imports/api/1.1/chat';
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import { BREAK_LINE } from '/imports/utils/lineEndings.js';
|
||||
|
||||
/**
|
||||
* Remove any system message from the user with userId.
|
||||
@ -17,8 +16,8 @@ export default function clearUserSystemMessages(meetingId, userId) {
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
'message.from_userid': CHAT_CONFIG.type_system,
|
||||
'message.to_userid': userId,
|
||||
fromUserId: CHAT_CONFIG.type_system,
|
||||
toUserId: userId,
|
||||
};
|
||||
|
||||
return Chat.remove(selector, Logger.info(`Removing system messages from: (${userId})`));
|
||||
|
@ -1,32 +1,31 @@
|
||||
import Chat from '/imports/api/1.1/chat';
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
|
||||
function chat(credentials) {
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_TYPE = CHAT_CONFIG.type_public;
|
||||
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_USERNAME = CHAT_CONFIG.public_username;
|
||||
|
||||
Logger.info(`Publishing chat for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Chat.find({
|
||||
$or: [
|
||||
{
|
||||
'message.chat_type': PUBLIC_CHAT_TYPE,
|
||||
toUsername: PUBLIC_CHAT_USERNAME,
|
||||
meetingId,
|
||||
}, {
|
||||
'message.from_userid': requesterUserId,
|
||||
fromUserId: requesterUserId,
|
||||
meetingId,
|
||||
}, {
|
||||
'message.to_userid': requesterUserId,
|
||||
toUserId: requesterUserId,
|
||||
meetingId,
|
||||
},
|
||||
],
|
||||
@ -38,4 +37,4 @@ function publish(...args) {
|
||||
return mapToAcl('subscriptions.chat', boundChat)(args);
|
||||
}
|
||||
|
||||
Meteor.publish('chat', publish);
|
||||
Meteor.publish('chat2x', publish);
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Breakouts = new Mongo.Collection('breakouts2x');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// types of queries for the breakouts:
|
||||
// 1. breakoutId ( handleJoinUrl, roomStarted, clearBreakouts )
|
||||
// 2. parentMeetingId ( updateTimeRemaining )
|
||||
|
||||
Breakouts._ensureIndex({ breakoutId: 1 });
|
||||
Breakouts._ensureIndex({ parentMeetingId: 1 });
|
||||
}
|
||||
|
||||
export default Breakouts;
|
@ -1,10 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import handleBreakoutJoinURL from './handlers/breakoutJoinURL';
|
||||
import handleBreakoutStarted from './handlers/breakoutStarted';
|
||||
import handleUpdateTimeRemaining from './handlers/updateTimeRemaining';
|
||||
import handleBreakoutClosed from './handlers/breakoutClosed';
|
||||
|
||||
RedisPubSub.on('BreakoutRoomStartedEvtMsg', handleBreakoutStarted);
|
||||
RedisPubSub.on('BreakoutRoomJoinURLEvtMsg', handleBreakoutJoinURL);
|
||||
RedisPubSub.on('BreakoutRoomsTimeRemainingUpdateEvtMsg', handleUpdateTimeRemaining);
|
||||
RedisPubSub.on('BreakoutRoomEndedEvtMsg', handleBreakoutClosed);
|
@ -1,10 +0,0 @@
|
||||
import { check } from 'meteor/check';
|
||||
import clearBreakouts from '../modifiers/clearBreakouts';
|
||||
|
||||
export default function handleBreakoutClosed({ body }) {
|
||||
const { breakoutId } = body;
|
||||
|
||||
check(breakoutId, String);
|
||||
|
||||
return clearBreakouts(breakoutId);
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
import xml2js from 'xml2js';
|
||||
import url from 'url';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
|
||||
const xmlParser = new xml2js.Parser();
|
||||
|
||||
const getUrlParams = (urlToParse) => {
|
||||
const options = { parseQueryString: true };
|
||||
const parsedUrl = url.parse(urlToParse, options);
|
||||
return parsedUrl.query;
|
||||
};
|
||||
|
||||
|
||||
export default function handleBreakoutJoinURL({ body }) {
|
||||
const {
|
||||
noRedirectJoinURL,
|
||||
userId,
|
||||
breakoutId,
|
||||
} = body;
|
||||
|
||||
check(noRedirectJoinURL, String);
|
||||
|
||||
const urlParams = getUrlParams(noRedirectJoinURL);
|
||||
|
||||
const selector = {
|
||||
breakoutId,
|
||||
};
|
||||
|
||||
const res = Meteor.http.call('get', noRedirectJoinURL);
|
||||
|
||||
xmlParser.parseString(res.content, (err, parsedXML) => {
|
||||
if (err) {
|
||||
return Logger.error(`An Error occured when parsing xml response for: ${noRedirectJoinURL}`);
|
||||
}
|
||||
|
||||
const breakout = Breakouts.findOne(selector);
|
||||
|
||||
const { response } = parsedXML;
|
||||
const users = breakout.users;
|
||||
|
||||
const user = {
|
||||
userId,
|
||||
urlParams: {
|
||||
meetingId: response.meeting_id[0],
|
||||
userId: response.user_id[0],
|
||||
authToken: response.auth_token[0],
|
||||
sessionToken: response.session_token[0],
|
||||
},
|
||||
};
|
||||
|
||||
const userExists = users.find(u => user.userId === u.userId);
|
||||
|
||||
if (userExists) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const modifier = {
|
||||
$push: {
|
||||
users: user,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (cbErr, numChanged) => {
|
||||
if (cbErr) {
|
||||
return Logger.error(`Adding breakout to collection: ${cbErr}`);
|
||||
}
|
||||
|
||||
const {
|
||||
insertedId,
|
||||
} = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.info(`Added breakout id=${urlParams.meetingID}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Upserted breakout id=${urlParams.meetingID}`);
|
||||
};
|
||||
|
||||
return Breakouts.upsert(selector, modifier, cb);
|
||||
});
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import flat from 'flat';
|
||||
|
||||
export default function handleBreakoutRoomStarted({ body }, meetingId) {
|
||||
const {
|
||||
parentMeetingId,
|
||||
breakout,
|
||||
} = body;
|
||||
|
||||
const { breakoutId } = breakout;
|
||||
|
||||
const timeRemaining = 15;
|
||||
|
||||
check(meetingId, String);
|
||||
|
||||
const selector = {
|
||||
breakoutId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: Object.assign(
|
||||
{ users: [] },
|
||||
{ timeRemaining: Number(timeRemaining) },
|
||||
{ parentMeetingId },
|
||||
flat(breakout),
|
||||
),
|
||||
};
|
||||
|
||||
const cb = (err) => {
|
||||
if (err) {
|
||||
return Logger.error(`updating breakout: ${err}`);
|
||||
}
|
||||
|
||||
return Logger.info('Updated timeRemaining and externalMeetingId ' +
|
||||
`for breakout id=${breakoutId}`);
|
||||
};
|
||||
|
||||
return Breakouts.upsert(selector, modifier, cb);
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
|
||||
export default function handleUpdateTimeRemaining({ body }, meetingId) {
|
||||
const {
|
||||
timeRemaining,
|
||||
} = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(timeRemaining, Number);
|
||||
|
||||
const selector = {
|
||||
parentMeetingId: meetingId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
timeRemaining,
|
||||
},
|
||||
};
|
||||
|
||||
const options = {
|
||||
multi: true,
|
||||
};
|
||||
|
||||
const cb = (err) => {
|
||||
if (err) {
|
||||
return Logger.error(`Updating breakouts: ${err}`);
|
||||
}
|
||||
|
||||
return Logger.info('Updated breakout time remaining for breakouts ' +
|
||||
`where parentMeetingId=${meetingId}`);
|
||||
};
|
||||
|
||||
return Breakouts.update(selector, modifier, options, cb);
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import './eventHandlers';
|
||||
import './methods';
|
||||
import './publishers';
|
@ -1,3 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.methods({});
|
@ -1,14 +0,0 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
|
||||
export default function clearBreakouts(breakoutId) {
|
||||
if (breakoutId) {
|
||||
const selector = {
|
||||
breakoutId,
|
||||
};
|
||||
|
||||
return Breakouts.remove(selector);
|
||||
}
|
||||
|
||||
return Breakouts.remove({}, Logger.info('Cleared Breakouts (all)'));
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
import Breakouts from '/imports/api/2.0/breakouts';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
function breakouts(credentials) {
|
||||
const {
|
||||
meetingId,
|
||||
requesterUserId,
|
||||
} = credentials;
|
||||
|
||||
Logger.info(`Publishing Breakouts2x for ${meetingId} ${requesterUserId}`);
|
||||
|
||||
return Breakouts.find({
|
||||
$or: [
|
||||
{ breakoutId: meetingId },
|
||||
{ meetingId },
|
||||
{
|
||||
users: {
|
||||
$elemMatch: { userId: requesterUserId },
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function publish(...args) {
|
||||
const boundBreakouts = breakouts.bind(this);
|
||||
return mapToAcl('subscriptions.breakouts', boundBreakouts)(args);
|
||||
}
|
||||
|
||||
Meteor.publish('breakouts2x', publish);
|
@ -1,14 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Captions = new Mongo.Collection('captions2x');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// types of queries for the captions:
|
||||
// 1. meetingId, locale, 'captionHistory.index' (History)
|
||||
// 2. meetingId, locale (Owner update, Caption update, addCaption)
|
||||
// 3. meetingId ( clear Captions)
|
||||
|
||||
Captions._ensureIndex({ meetingId: 1, locale: 1 });
|
||||
}
|
||||
|
||||
export default Captions;
|
@ -1,9 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import handleCaptionHistory from './handlers/captionHistory';
|
||||
import handleCaptionUpdate from './handlers/captionUpdate';
|
||||
import handleCaptionOwnerUpdate from './handlers/captionOwnerUpdate';
|
||||
|
||||
// TODO
|
||||
RedisPubSub.on('SendCaptionHistoryRespMsg', handleCaptionHistory);
|
||||
RedisPubSub.on('EditCaptionHistoryEvtMsg', handleCaptionUpdate);
|
||||
RedisPubSub.on('UpdateCaptionOwnerEvtMsg', handleCaptionOwnerUpdate);
|
@ -1,57 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import { check } from 'meteor/check';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionHistory({ body }, meetingId) {
|
||||
const SERVER_CONFIG = Meteor.settings.app;
|
||||
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
|
||||
|
||||
const captionHistory = body.history;
|
||||
|
||||
check(meetingId, String);
|
||||
check(captionHistory, Object);
|
||||
|
||||
const captionsAdded = [];
|
||||
_.each(captionHistory, (caption, locale) => {
|
||||
const ownerId = caption[0];
|
||||
let captions = caption[1].slice(0);
|
||||
const chunks = [];
|
||||
|
||||
if (captions.length === 0) {
|
||||
chunks.push('');
|
||||
} else {
|
||||
while (captions.length > 0) {
|
||||
if (captions.length > CAPTION_CHUNK_LENGTH) {
|
||||
chunks.push(captions.slice(0, CAPTION_CHUNK_LENGTH));
|
||||
captions = captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
} else {
|
||||
chunks.push(captions);
|
||||
captions = captions.slice(captions.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectorToRemove = {
|
||||
meetingId,
|
||||
locale,
|
||||
'captionHistory.index': { $gt: (chunks.length - 1) },
|
||||
};
|
||||
|
||||
Captions.remove(selectorToRemove);
|
||||
|
||||
chunks.forEach((chunkCaptions, index) => {
|
||||
const captionHistoryObject = {
|
||||
locale,
|
||||
ownerId,
|
||||
chunkCaptions,
|
||||
index,
|
||||
next: (index < chunks.length - 1) ? index + 1 : undefined,
|
||||
};
|
||||
|
||||
captionsAdded.push(addCaption(meetingId, locale, captionHistoryObject));
|
||||
});
|
||||
});
|
||||
|
||||
return captionsAdded;
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionOwnerUpdate({ body }, meetingId) {
|
||||
const { ownerId, locale } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
check(ownerId, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
locale,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
'captionHistory.ownerId': ownerId,
|
||||
},
|
||||
};
|
||||
|
||||
const Caption = Captions.findOne(selector);
|
||||
|
||||
if (!Caption) {
|
||||
const captionHistory = {
|
||||
ownerId,
|
||||
captions: '',
|
||||
index: 0,
|
||||
next: null,
|
||||
};
|
||||
|
||||
return addCaption(meetingId, locale, captionHistory);
|
||||
}
|
||||
|
||||
const cb = (err) => {
|
||||
if (err) {
|
||||
return Logger.error(`Updating captions owner: ${err}`);
|
||||
}
|
||||
|
||||
return Logger.verbose(`Update caption owner locale=${locale} meeting=${meetingId}`);
|
||||
};
|
||||
|
||||
return Captions.update(selector, modifier, { multi: true }, cb);
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import { check } from 'meteor/check';
|
||||
import addCaption from '../modifiers/addCaption';
|
||||
|
||||
export default function handleCaptionUpdate({ body }, meetingId) {
|
||||
const SERVER_CONFIG = Meteor.settings.app;
|
||||
const CAPTION_CHUNK_LENGTH = SERVER_CONFIG.captionsChunkLength || 1000;
|
||||
|
||||
const { locale } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
|
||||
const captionsObjects = Captions.find({
|
||||
meetingId,
|
||||
locale,
|
||||
}, {
|
||||
sort: {
|
||||
locale: 1,
|
||||
'captionHistory.index': 1,
|
||||
},
|
||||
}).fetch();
|
||||
|
||||
const objectsToUpdate = [];
|
||||
if (captionsObjects != null) {
|
||||
let startIndex;
|
||||
let endIndex;
|
||||
let length = 0;
|
||||
let current = captionsObjects[0];
|
||||
|
||||
// looking for a start index and end index
|
||||
// (end index only for the case when they are in the same block)
|
||||
while (current != null) {
|
||||
length += current.captionHistory.captions.length;
|
||||
|
||||
// if length is bigger than start index - we found our start index
|
||||
if (length >= body.startIndex && startIndex == undefined) {
|
||||
// check if it's a new character somewhere in the middle of captions text
|
||||
if (length - 1 >= body.startIndex) {
|
||||
startIndex = body.startIndex - (length - current.captionHistory.captions.length);
|
||||
|
||||
// check to see if the endIndex is in the same object as startIndex
|
||||
if (length - 1 >= body.endIndex) {
|
||||
endIndex = body.endIndex - (length - current.captionHistory.captions.length);
|
||||
const _captions = current.captionHistory.captions;
|
||||
current.captionHistory.captions = _captions.slice(0, startIndex) +
|
||||
body.text +
|
||||
_captions.slice(endIndex);
|
||||
objectsToUpdate.push(current);
|
||||
break;
|
||||
|
||||
// end index is not in the same object as startIndex, we will find it later
|
||||
} else {
|
||||
current.captionHistory.captions = current.captionHistory.captions.slice(0, startIndex) +
|
||||
body.text;
|
||||
objectsToUpdate.push(current);
|
||||
break;
|
||||
}
|
||||
|
||||
// separate case for appending new characters to the very end of the string
|
||||
} else if (current.captionHistory.next == null &&
|
||||
length == body.startIndex &&
|
||||
length == body.startIndex) {
|
||||
startIndex = 1;
|
||||
endIndex = 1;
|
||||
current.captionHistory.captions += body.text;
|
||||
objectsToUpdate.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
current = captionsObjects[current.captionHistory.next];
|
||||
}
|
||||
|
||||
// looking for end index here if it wasn't in the same object as start index
|
||||
if (startIndex != undefined && endIndex == undefined) {
|
||||
current = captionsObjects[current.captionHistory.next];
|
||||
while (current != null) {
|
||||
length += current.captionHistory.captions.length;
|
||||
|
||||
// check to see if the endIndex is in the current object
|
||||
if (length - 1 >= body.endIndex) {
|
||||
endIndex = body.endIndex - (length - current.captionHistory.captions.length);
|
||||
current.captionHistory.captions = current.captionHistory.captions.slice(endIndex);
|
||||
objectsToUpdate.push(current);
|
||||
|
||||
break;
|
||||
|
||||
// if endIndex wasn't in the current object, that means this whole object was deleted
|
||||
// initializing string to ''
|
||||
} else {
|
||||
current.captionHistory.captions = '';
|
||||
objectsToUpdate.push(current);
|
||||
}
|
||||
|
||||
current = captionsObjects[current.captionHistory.next];
|
||||
}
|
||||
}
|
||||
|
||||
// looking for the strings which exceed the limit and split them into multiple objects
|
||||
let maxIndex = captionsObjects.length - 1;
|
||||
for (let i = 0; i < objectsToUpdate.length; i++) {
|
||||
if (objectsToUpdate[i].captionHistory.captions.length > CAPTION_CHUNK_LENGTH) {
|
||||
// string is too large. Check if the next object exists and if it can
|
||||
// accomodate the part of the string that exceeds the limits
|
||||
const _nextIndex = objectsToUpdate[i].captionHistory.next;
|
||||
if (_nextIndex != null &&
|
||||
captionsObjects[_nextIndex].captionHistory.captions.length < CAPTION_CHUNK_LENGTH) {
|
||||
const extraString = objectsToUpdate[i].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
|
||||
// could assign it directly, but our linter complained
|
||||
let _captions = objectsToUpdate[i].captionHistory.captions;
|
||||
_captions = _captions.slice(0, CAPTION_CHUNK_LENGTH);
|
||||
objectsToUpdate[i].captionHistory.captions = _captions;
|
||||
|
||||
// check to see if the next object was added to objectsToUpdate array
|
||||
if (objectsToUpdate[i + 1] != null &&
|
||||
objectsToUpdate[i].captionHistory.next == objectsToUpdate[i + 1].captionHistory.index) {
|
||||
objectsToUpdate[i + 1].captionHistory.captions = extraString +
|
||||
objectsToUpdate[i + 1].captionHistory.captions;
|
||||
|
||||
// next object wasn't added to objectsToUpdate array, adding it from captionsObjects array.
|
||||
} else {
|
||||
const nextObj = captionsObjects[objectsToUpdate[i].captionHistory.next];
|
||||
nextObj.captionHistory.captions = extraString + nextObj.captionHistory.captions;
|
||||
objectsToUpdate.push(nextObj);
|
||||
}
|
||||
|
||||
// next object was full already, so we create another and insert it in between them
|
||||
} else {
|
||||
// need to take a current object out of the objectsToUpdate and add it back after
|
||||
// every other object, so that Captions collection could be updated in a proper order
|
||||
const tempObj = objectsToUpdate.splice(i, 1);
|
||||
let extraString = tempObj[0].captionHistory.captions.slice(CAPTION_CHUNK_LENGTH);
|
||||
|
||||
tempObj[0].captionHistory.captions =
|
||||
tempObj[0].captionHistory.captions.slice(0, CAPTION_CHUNK_LENGTH);
|
||||
|
||||
maxIndex += 1;
|
||||
const tempIndex = tempObj[0].captionHistory.next;
|
||||
tempObj[0].captionHistory.next = maxIndex;
|
||||
|
||||
while (extraString.length != 0) {
|
||||
const entry = {
|
||||
meetingId,
|
||||
locale,
|
||||
captionHistory: {
|
||||
locale,
|
||||
ownerId: tempObj[0].captionHistory.ownerId,
|
||||
captions: extraString.slice(0, CAPTION_CHUNK_LENGTH),
|
||||
index: maxIndex,
|
||||
next: null,
|
||||
},
|
||||
};
|
||||
maxIndex += 1;
|
||||
extraString = extraString.slice(CAPTION_CHUNK_LENGTH);
|
||||
if (extraString.length > 0) {
|
||||
entry.captionHistory.next = maxIndex;
|
||||
} else {
|
||||
entry.captionHistory.next = tempIndex;
|
||||
}
|
||||
|
||||
objectsToUpdate.push(entry);
|
||||
}
|
||||
|
||||
objectsToUpdate.push(tempObj[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const captionsAdded = [];
|
||||
objectsToUpdate.forEach((entry) => {
|
||||
const { _id, meetingId, locale, captionHistory } = entry;
|
||||
captionsAdded.push(addCaption(meetingId, locale, captionHistory, _id));
|
||||
});
|
||||
|
||||
return captionsAdded;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import './eventHandlers';
|
||||
import './methods';
|
||||
import './publishers';
|
@ -1,4 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.methods({
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
import { Match, check } from 'meteor/check';
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function addCaption(meetingId, locale, captionHistory, id = false) {
|
||||
check(meetingId, String);
|
||||
check(locale, String);
|
||||
|
||||
check(captionHistory, {
|
||||
ownerId: String,
|
||||
index: Number,
|
||||
captions: String,
|
||||
locale: Match.Maybe(String),
|
||||
localeCode: Match.Maybe(String),
|
||||
next: Match.OneOf(Number, undefined, null),
|
||||
});
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
locale,
|
||||
};
|
||||
|
||||
|
||||
if (id) {
|
||||
selector._id = id;
|
||||
} else {
|
||||
selector['captionHistory.index'] = captionHistory.index;
|
||||
}
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
meetingId,
|
||||
locale,
|
||||
captionHistory,
|
||||
},
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding caption2x to collection: ${err}`);
|
||||
}
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
if (insertedId) {
|
||||
return Logger.verbose(`Added caption2x locale=${locale} meeting=${meetingId}`);
|
||||
}
|
||||
|
||||
return Logger.verbose(`Upserted caption2x locale=${locale} meeting=${meetingId}`);
|
||||
};
|
||||
|
||||
return Captions.upsert(selector, modifier, cb);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearCaptions(meetingId) {
|
||||
if (meetingId) {
|
||||
return Captions.remove({ meetingId }, Logger.info(`Cleared Captions2x (${meetingId})`));
|
||||
}
|
||||
|
||||
return Captions.remove({}, Logger.info('Cleared Captions2x (all)'));
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import Captions from '/imports/api/2.0/captions';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
|
||||
function captions(credentials) {
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
Logger.verbose(`Publishing Captions2x for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Captions.find({ meetingId });
|
||||
}
|
||||
|
||||
function publish(...args) {
|
||||
const boundCaptions = captions.bind(this);
|
||||
return mapToAcl('subscriptions.captions', boundCaptions)(args);
|
||||
}
|
||||
|
||||
Meteor.publish('captions2x', publish);
|
@ -1,19 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const Chat = new Mongo.Collection('chat2x');
|
||||
|
||||
if (Meteor.isServer) {
|
||||
// types of queries for the chat:
|
||||
// 1. meetingId, toUsername (publishers)
|
||||
// 2. meetingId, fromUserId (publishers)
|
||||
// 3. meetingId, toUserId (publishers)
|
||||
// 4. meetingId, fromTime, fromUserId, toUserId (addChat)
|
||||
// 5. meetingId (clearChat)
|
||||
// 6. meetingId, fromUserId, toUserId (clearSystemMessages)
|
||||
|
||||
Chat._ensureIndex({ meetingId: 1, toUsername: 1 });
|
||||
Chat._ensureIndex({ meetingId: 1, fromUserId: 1 });
|
||||
Chat._ensureIndex({ meetingId: 1, toUserId: 1 });
|
||||
}
|
||||
|
||||
export default Chat;
|
@ -1,9 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import handleChatMessage from './handlers/chatMessage';
|
||||
import handleChatHistory from './handlers/chatHistory';
|
||||
import handleChatPublicHistoryClear from './handlers/chatPublicHistoryClear';
|
||||
|
||||
RedisPubSub.on('GetChatHistoryRespMsg', handleChatHistory);
|
||||
RedisPubSub.on('SendPublicMessageEvtMsg', handleChatMessage);
|
||||
RedisPubSub.on('SendPrivateMessageEvtMsg', handleChatMessage);
|
||||
RedisPubSub.on('ClearPublicChatHistoryEvtMsg', handleChatPublicHistoryClear);
|
@ -1,17 +0,0 @@
|
||||
import { check } from 'meteor/check';
|
||||
import addChat from '../modifiers/addChat';
|
||||
|
||||
export default function handleChatHistory({ body }, meetingId) {
|
||||
const { history } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(history, Array);
|
||||
|
||||
const chatsAdded = [];
|
||||
|
||||
history.forEach((message) => {
|
||||
chatsAdded.push(addChat(meetingId, message));
|
||||
});
|
||||
|
||||
return chatsAdded;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import { check } from 'meteor/check';
|
||||
import addChat from '../modifiers/addChat';
|
||||
|
||||
export default function handleChatMessage({ body }, meetingId) {
|
||||
const { message } = body;
|
||||
|
||||
check(meetingId, String);
|
||||
check(message, Object);
|
||||
|
||||
return addChat(meetingId, message);
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import './eventHandlers';
|
||||
import './methods';
|
||||
import './publishers';
|
@ -1,9 +0,0 @@
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import sendChat from './methods/sendChat';
|
||||
import clearPublicChatHistory from './methods/clearPublicChatHistory';
|
||||
|
||||
Meteor.methods(mapToAcl(['methods.sendChat', 'methods.clearPublicChatHistory'], {
|
||||
sendChat,
|
||||
clearPublicChatHistory,
|
||||
}));
|
@ -1,54 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis2x';
|
||||
import RegexWebUrl from '/imports/utils/regex-weburl';
|
||||
|
||||
const HTML_SAFE_MAP = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
};
|
||||
|
||||
const parseMessage = (message) => {
|
||||
let parsedMessage = message || '';
|
||||
parsedMessage = parsedMessage.trim();
|
||||
|
||||
// Replace <br/> with \n\r
|
||||
parsedMessage = parsedMessage.replace(/<br\s*[\\/]?>/gi, '\n\r');
|
||||
|
||||
// Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
|
||||
parsedMessage = parsedMessage.replace(/[<>'"]/g, c => HTML_SAFE_MAP[c]);
|
||||
|
||||
// Replace flash links to flash valid ones
|
||||
parsedMessage = parsedMessage.replace(RegexWebUrl, "<a href='event:$&'><u>$&</u></a>");
|
||||
|
||||
return parsedMessage;
|
||||
};
|
||||
|
||||
export default function sendChat(credentials, message) {
|
||||
const REDIS_CONFIG = Meteor.settings.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const TO_PUBLIC_CHAT = CHAT_CONFIG.public_username;
|
||||
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
check(message, Object);
|
||||
|
||||
let eventName = 'SendPrivateMessagePubMsg';
|
||||
|
||||
const parsedMessage = message;
|
||||
|
||||
parsedMessage.message = parseMessage(message.message);
|
||||
|
||||
if (message.toUsername === TO_PUBLIC_CHAT) {
|
||||
eventName = 'SendPublicMessagePubMsg';
|
||||
}
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, eventName, meetingId, requesterUserId, { message: parsedMessage });
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
import flat from 'flat';
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { Match, check } from 'meteor/check';
|
||||
import { BREAK_LINE } from '/imports/utils/lineEndings';
|
||||
|
||||
const parseMessage = (message) => {
|
||||
let parsedMessage = message || '';
|
||||
|
||||
// Replace \r and \n to <br/>
|
||||
parsedMessage = parsedMessage.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, `$1${BREAK_LINE}$2`);
|
||||
|
||||
// Replace flash links to html valid ones
|
||||
parsedMessage = parsedMessage.split('<a href=\'event:').join('<a target="_blank" href=\'');
|
||||
parsedMessage = parsedMessage.split('<a href="event:').join('<a target="_blank" href="');
|
||||
|
||||
return parsedMessage;
|
||||
};
|
||||
|
||||
const chatType = (userName) => {
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
|
||||
const typeByUser = {
|
||||
[CHAT_CONFIG.system_username]: CHAT_CONFIG.type_system,
|
||||
[CHAT_CONFIG.public_username]: CHAT_CONFIG.type_public,
|
||||
};
|
||||
|
||||
return userName in typeByUser ? typeByUser[userName] : CHAT_CONFIG.type_private;
|
||||
};
|
||||
|
||||
export default function addChat(meetingId, chat) {
|
||||
check(chat, {
|
||||
message: String,
|
||||
fromColor: String,
|
||||
toUserId: String,
|
||||
toUsername: String,
|
||||
fromUserId: String,
|
||||
fromUsername: Match.Maybe(String),
|
||||
fromTime: Number,
|
||||
fromTimezoneOffset: Match.Maybe(Number),
|
||||
});
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
fromTime: chat.fromTime,
|
||||
fromUserId: chat.fromUserId,
|
||||
toUserId: chat.toUserId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: Object.assign(
|
||||
flat(chat, { safe: true }),
|
||||
{
|
||||
meetingId,
|
||||
message: parseMessage(chat.message),
|
||||
type: chatType(chat.toUsername),
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
const cb = (err, numChanged) => {
|
||||
if (err) {
|
||||
return Logger.error(`Adding chat to collection: ${err}`);
|
||||
}
|
||||
|
||||
const { insertedId } = numChanged;
|
||||
const to = chat.toUsername || 'PUBLIC';
|
||||
|
||||
if (insertedId) {
|
||||
return Logger.info(`Added chat from=${chat.fromUsername} to=${to} time=${chat.fromTime}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Upserted chat from=${chat.fromUsername} to=${to} time=${chat.fromTime}`);
|
||||
};
|
||||
|
||||
return Chat.upsert(selector, modifier, cb);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearChats(meetingId) {
|
||||
if (meetingId) {
|
||||
return Chat.remove({ meetingId }, Logger.info(`Cleared Chats (${meetingId})`));
|
||||
}
|
||||
|
||||
return Chat.remove({}, Logger.info('Cleared Chats (all)'));
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
/**
|
||||
* Remove any system message from the user with userId.
|
||||
*
|
||||
* @param {string} meetingId
|
||||
* @param {string} userId
|
||||
*/
|
||||
export default function clearUserSystemMessages(meetingId, userId) {
|
||||
check(meetingId, String);
|
||||
check(userId, String);
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
fromUserId: CHAT_CONFIG.type_system,
|
||||
toUserId: userId,
|
||||
};
|
||||
|
||||
return Chat.remove(selector, Logger.info(`Removing system messages from: (${userId})`));
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import Chat from '/imports/api/2.0/chat';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import mapToAcl from '/imports/startup/mapToAcl';
|
||||
|
||||
function chat(credentials) {
|
||||
const { meetingId, requesterUserId, requesterToken } = credentials;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(requesterToken, String);
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_USERNAME = CHAT_CONFIG.public_username;
|
||||
|
||||
Logger.info(`Publishing chat for ${meetingId} ${requesterUserId} ${requesterToken}`);
|
||||
|
||||
return Chat.find({
|
||||
$or: [
|
||||
{
|
||||
toUsername: PUBLIC_CHAT_USERNAME,
|
||||
meetingId,
|
||||
}, {
|
||||
fromUserId: requesterUserId,
|
||||
meetingId,
|
||||
}, {
|
||||
toUserId: requesterUserId,
|
||||
meetingId,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function publish(...args) {
|
||||
const boundChat = chat.bind(this);
|
||||
return mapToAcl('subscriptions.chat', boundChat)(args);
|
||||
}
|
||||
|
||||
Meteor.publish('chat2x', publish);
|
Loading…
Reference in New Issue
Block a user