Skip to content

Commit

Permalink
[BREAK] VideoConference (#25570)
Browse files Browse the repository at this point in the history
Co-authored-by: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com>
Co-authored-by: Tasso Evangelista <2263066+tassoevan@users.noreply.github.com>
Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com>
  • Loading branch information
4 people authored Jul 1, 2022
1 parent 1bb284d commit 60638b9
Show file tree
Hide file tree
Showing 185 changed files with 5,710 additions and 2,230 deletions.
1 change: 1 addition & 0 deletions apps/meteor/.mocharc.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ module.exports = {
'tests/end-to-end/api/*.js',
'tests/end-to-end/api/*.ts',
'tests/end-to-end/apps/*.js',
'tests/end-to-end/apps/*.ts',
],
};
183 changes: 170 additions & 13 deletions apps/meteor/app/api/server/v1/videoConference.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,182 @@
import { Meteor } from 'meteor/meteor';
import type { VideoConference } from '@rocket.chat/core-typings';
import {
isVideoConfStartProps,
isVideoConfJoinProps,
isVideoConfCancelProps,
isVideoConfInfoProps,
isVideoConfListProps,
} from '@rocket.chat/rest-typings';

import { Rooms } from '../../../models/server';
import { API } from '../api';
import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
import { VideoConf } from '../../../../server/sdk';
import { videoConfProviders } from '../../../../server/lib/videoConfProviders';
import { availabilityErrors } from '../../../../lib/videoConference/constants';

API.v1.addRoute(
'video-conference/jitsi.update-timeout',
{ authRequired: true },
'video-conference.start',
{ authRequired: true, validateParams: isVideoConfStartProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 60000 } },
{
post() {
const { roomId, joiningNow = true } = this.bodyParams;
if (!roomId) {
return API.v1.failure('The "roomId" parameter is required!');
async post() {
const { roomId, title, allowRinging } = this.bodyParams;
const { userId } = this;
if (!userId || !(await canAccessRoomIdAsync(roomId, userId))) {
return API.v1.failure('invalid-params');
}

const room = Rooms.findOneById(roomId, { fields: { _id: 1 } });
if (!room) {
return API.v1.failure('Room does not exist!');
try {
const providerName = videoConfProviders.getActiveProvider();

if (!providerName) {
throw new Error(availabilityErrors.NOT_ACTIVE);
}

return API.v1.success({
data: {
...(await VideoConf.start(userId, roomId, { title, allowRinging: Boolean(allowRinging) })),
providerName,
},
});
} catch (e) {
return API.v1.failure(await VideoConf.diagnoseProvider(userId, roomId));
}
},
},
);

API.v1.addRoute(
'video-conference.join',
{ authOrAnonRequired: true, validateParams: isVideoConfJoinProps, rateLimiterOptions: { numRequestsAllowed: 2, intervalTimeInMS: 5000 } },
{
async post() {
const { callId, state } = this.bodyParams;
const { userId } = this;

const call = await VideoConf.get(callId);
if (!call) {
return API.v1.failure('invalid-params');
}

if (!(await canAccessRoomIdAsync(call.rid, userId))) {
return API.v1.failure('invalid-params');
}

let url: string | undefined;

try {
url = await VideoConf.join(userId, callId, {
...(state?.cam !== undefined ? { cam: state.cam } : {}),
...(state?.mic !== undefined ? { mic: state.mic } : {}),
});
} catch (e) {
if (userId) {
return API.v1.failure(await VideoConf.diagnoseProvider(userId, call.rid, call.providerName));
}
}

if (!url) {
return API.v1.failure('failed-to-get-url');
}

const jitsiTimeout = Meteor.runAsUser(this.userId, () => Meteor.call('jitsi:updateTimeout', roomId, Boolean(joiningNow)));
return API.v1.success({ jitsiTimeout });
return API.v1.success({
url,
providerName: call.providerName,
});
},
},
);

API.v1.addRoute(
'video-conference.cancel',
{ authRequired: true, validateParams: isVideoConfCancelProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 60000 } },
{
async post() {
const { callId } = this.bodyParams;
const { userId } = this;

const call = await VideoConf.get(callId);
if (!call) {
return API.v1.failure('invalid-params');
}

if (!userId || !(await canAccessRoomIdAsync(call.rid, userId))) {
return API.v1.failure('invalid-params');
}

await VideoConf.cancel(userId, callId);
return API.v1.success();
},
},
);

API.v1.addRoute(
'video-conference.info',
{ authRequired: true, validateParams: isVideoConfInfoProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 1000 } },
{
async get() {
const { callId } = this.queryParams;
const { userId } = this;

const call = await VideoConf.get(callId);
if (!call) {
return API.v1.failure('invalid-params');
}

if (!userId || !(await canAccessRoomIdAsync(call.rid, userId))) {
return API.v1.failure('invalid-params');
}

const capabilities = await VideoConf.listProviderCapabilities(call.providerName);

return API.v1.success({
...(call as VideoConference),
capabilities,
});
},
},
);

API.v1.addRoute(
'video-conference.list',
{ authRequired: true, validateParams: isVideoConfListProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 1000 } },
{
async get() {
const { roomId } = this.queryParams;
const { userId } = this;

const { offset, count } = this.getPaginationItems();

if (!userId || !(await canAccessRoomIdAsync(roomId, userId))) {
return API.v1.failure('invalid-params');
}

const data = await VideoConf.list(roomId, { offset, count });

return API.v1.success(data);
},
},
);

API.v1.addRoute(
'video-conference.providers',
{ authRequired: true, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 1000 } },
{
async get() {
const data = await VideoConf.listProviders();

return API.v1.success({ data });
},
},
);

API.v1.addRoute(
'video-conference.capabilities',
{ authRequired: true, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 1000 } },
{
async get() {
const data = await VideoConf.listCapabilities();

return API.v1.success(data);
},
},
);
6 changes: 6 additions & 0 deletions apps/meteor/app/apps/server/bridges/bridges.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AppLivechatBridge } from './livechat';
import { AppUploadBridge } from './uploads';
import { UiInteractionBridge } from './uiInteraction';
import { AppSchedulerBridge } from './scheduler';
import { AppVideoConferenceBridge } from './videoConferences';

export class RealAppBridges extends AppBridges {
constructor(orch) {
Expand All @@ -41,6 +42,7 @@ export class RealAppBridges extends AppBridges {
this._uiInteractionBridge = new UiInteractionBridge(orch);
this._schedulerBridge = new AppSchedulerBridge(orch);
this._cloudWorkspaceBridge = new AppCloudBridge(orch);
this._videoConfBridge = new AppVideoConferenceBridge(orch);
}

getCommandBridge() {
Expand Down Expand Up @@ -114,4 +116,8 @@ export class RealAppBridges extends AppBridges {
getCloudWorkspaceBridge() {
return this._cloudWorkspaceBridge;
}

getVideoConferenceBridge() {
return this._videoConfBridge;
}
}
70 changes: 70 additions & 0 deletions apps/meteor/app/apps/server/bridges/videoConferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { VideoConferenceBridge } from '@rocket.chat/apps-engine/server/bridges/VideoConferenceBridge';
import { AppVideoConference, VideoConference } from '@rocket.chat/apps-engine/definition/videoConferences';
import { IVideoConfProvider } from '@rocket.chat/apps-engine/definition/videoConfProviders';

import { VideoConf } from '../../../../server/sdk';
import { AppServerOrchestrator } from '../orchestrator';
import { videoConfProviders } from '../../../../server/lib/videoConfProviders';
import type { AppVideoConferencesConverter } from '../converters/videoConferences';

export class AppVideoConferenceBridge extends VideoConferenceBridge {
// eslint-disable-next-line no-empty-function
constructor(private readonly orch: AppServerOrchestrator) {
super();
}

protected async getById(callId: string, appId: string): Promise<VideoConference> {
this.orch.debugLog(`The App ${appId} is getting the video conference byId: "${callId}"`);

return this.orch.getConverters()?.get('videoConferences').convertById(callId);
}

protected async create(call: AppVideoConference, appId: string): Promise<string> {
this.orch.debugLog(`The App ${appId} is creating a video conference.`);

return (
await VideoConf.create({
type: 'videoconference',
...call,
})
).callId;
}

protected async update(call: VideoConference, appId: string): Promise<void> {
this.orch.debugLog(`The App ${appId} is updating a video conference.`);

const oldData = call._id && (await VideoConf.getUnfiltered(call._id));
if (!oldData) {
throw new Error('A video conference must exist to update.');
}

const data = (this.orch.getConverters()?.get('videoConferences') as AppVideoConferencesConverter).convertAppVideoConference(call);
await VideoConf.setProviderData(call._id, data.providerData);

for (const { _id, ts } of data.users) {
if (oldData.users.find((user) => user._id === _id)) {
continue;
}

VideoConf.addUser(call._id, _id, ts);
}

if (data.endedBy && data.endedBy._id !== oldData.endedBy?._id) {
await VideoConf.setEndedBy(call._id, data.endedBy._id);
} else if (data.endedAt) {
await VideoConf.setEndedAt(call._id, data.endedAt);
}

if (data.status > oldData.status) {
await VideoConf.setStatus(call._id, data.status);
}
}

protected async registerProvider(info: IVideoConfProvider): Promise<void> {
videoConfProviders.registerProvider(info.name, info.capabilities || {});
}

protected async unRegisterProvider(info: IVideoConfProvider): Promise<void> {
videoConfProviders.unRegisterProvider(info.name);
}
}
3 changes: 2 additions & 1 deletion apps/meteor/app/apps/server/converters/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { AppMessagesConverter } from './messages';
import { AppRoomsConverter } from './rooms';
import { AppSettingsConverter } from './settings';
import { AppUsersConverter } from './users';
import { AppVideoConferencesConverter } from './videoConferences';

export { AppMessagesConverter, AppRoomsConverter, AppSettingsConverter, AppUsersConverter };
export { AppMessagesConverter, AppRoomsConverter, AppSettingsConverter, AppUsersConverter, AppVideoConferencesConverter };
36 changes: 36 additions & 0 deletions apps/meteor/app/apps/server/converters/videoConferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { VideoConference } from '@rocket.chat/apps-engine/definition/videoConferences';
import type { IVideoConference } from '@rocket.chat/core-typings';

import { VideoConf } from '../../../../server/sdk';
import type { AppServerOrchestrator } from '../orchestrator';

export class AppVideoConferencesConverter {
// @ts-ignore
private orch: AppServerOrchestrator;

constructor(orch: AppServerOrchestrator) {
this.orch = orch;
}

async convertById(callId: string): Promise<VideoConference | undefined> {
const call = await VideoConf.getUnfiltered(callId);

return this.convertVideoConference(call);
}

convertVideoConference(call: IVideoConference | null): VideoConference | undefined {
if (!call) {
return;
}

return {
...call,
} as VideoConference;
}

convertAppVideoConference(call: VideoConference): IVideoConference {
return {
...call,
} as IVideoConference;
}
}
9 changes: 8 additions & 1 deletion apps/meteor/app/apps/server/orchestrator.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import { AppsLogsModel, AppsModel, AppsPersistenceModel } from '../../models/ser
import { settings, settingsRegistry } from '../../settings/server';
import { RealAppBridges } from './bridges';
import { AppMethods, AppServerNotifier, AppsRestApi, AppUIKitInteractionApi } from './communication';
import { AppMessagesConverter, AppRoomsConverter, AppSettingsConverter, AppUsersConverter } from './converters';
import {
AppMessagesConverter,
AppRoomsConverter,
AppSettingsConverter,
AppUsersConverter,
AppVideoConferencesConverter,
} from './converters';
import { AppDepartmentsConverter } from './converters/departments';
import { AppUploadsConverter } from './converters/uploads';
import { AppVisitorsConverter } from './converters/visitors';
Expand Down Expand Up @@ -54,6 +60,7 @@ export class AppServerOrchestrator {
this._converters.set('visitors', new AppVisitorsConverter(this));
this._converters.set('departments', new AppDepartmentsConverter(this));
this._converters.set('uploads', new AppUploadsConverter(this));
this._converters.set('videoConferences', new AppVideoConferencesConverter(this));

this._bridges = new RealAppBridges(this);

Expand Down
Loading

0 comments on commit 60638b9

Please sign in to comment.