backend: Implement recording token generation and update room preferences handling
This commit is contained in:
parent
5a7185caa3
commit
576b1f7d98
@ -7,9 +7,10 @@ const INTERNAL_CONFIG = {
|
|||||||
API_BASE_PATH_V1: '/meet/api/v1',
|
API_BASE_PATH_V1: '/meet/api/v1',
|
||||||
|
|
||||||
// Cookie names
|
// Cookie names
|
||||||
PARTICIPANT_TOKEN_COOKIE_NAME: 'OvMeetParticipantToken',
|
|
||||||
ACCESS_TOKEN_COOKIE_NAME: 'OvMeetAccessToken',
|
ACCESS_TOKEN_COOKIE_NAME: 'OvMeetAccessToken',
|
||||||
REFRESH_TOKEN_COOKIE_NAME: 'OvMeetRefreshToken',
|
REFRESH_TOKEN_COOKIE_NAME: 'OvMeetRefreshToken',
|
||||||
|
PARTICIPANT_TOKEN_COOKIE_NAME: 'OvMeetParticipantToken',
|
||||||
|
RECORDING_TOKEN_COOKIE_NAME: 'OvMeetRecordingToken',
|
||||||
|
|
||||||
// Headers for API requests
|
// Headers for API requests
|
||||||
API_KEY_HEADER: 'x-api-key',
|
API_KEY_HEADER: 'x-api-key',
|
||||||
|
|||||||
@ -2,8 +2,10 @@ import { MeetRoomFilters, MeetRoomOptions, MeetRoomRoleAndPermissions, Participa
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { container } from '../config/index.js';
|
import { container } from '../config/index.js';
|
||||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||||
|
import { MEET_RECORDING_TOKEN_EXPIRATION } from '../environment.js';
|
||||||
import { OpenViduMeetError } from '../models/error.model.js';
|
import { OpenViduMeetError } from '../models/error.model.js';
|
||||||
import { LoggerService, ParticipantService, RoomService } from '../services/index.js';
|
import { LoggerService, ParticipantService, RoomService } from '../services/index.js';
|
||||||
|
import { getCookieOptions } from '../utils/cookie-utils.js';
|
||||||
|
|
||||||
export const createRoom = async (req: Request, res: Response) => {
|
export const createRoom = async (req: Request, res: Response) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
@ -122,6 +124,46 @@ export const bulkDeleteRooms = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateRoomPreferences = async (req: Request, res: Response) => {
|
||||||
|
const logger = container.get(LoggerService);
|
||||||
|
const roomService = container.get(RoomService);
|
||||||
|
const roomPreferences = req.body;
|
||||||
|
const { roomId } = req.params;
|
||||||
|
|
||||||
|
logger.verbose(`Updating room preferences`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const room = await roomService.updateMeetRoomPreferences(roomId, roomPreferences);
|
||||||
|
return res.status(200).json(room);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error saving room preferences: ${error}`);
|
||||||
|
handleError(res, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateRecordingToken = async (req: Request, res: Response) => {
|
||||||
|
const logger = container.get(LoggerService);
|
||||||
|
const roomService = container.get(RoomService);
|
||||||
|
const { roomId } = req.params;
|
||||||
|
const { secret } = req.body;
|
||||||
|
|
||||||
|
logger.verbose(`Generating recording token for room '${roomId}'`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = await roomService.generateRecordingToken(roomId, secret);
|
||||||
|
|
||||||
|
res.cookie(
|
||||||
|
INTERNAL_CONFIG.RECORDING_TOKEN_COOKIE_NAME,
|
||||||
|
token,
|
||||||
|
getCookieOptions('/', MEET_RECORDING_TOKEN_EXPIRATION)
|
||||||
|
);
|
||||||
|
return res.status(200).json({ token });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error generating recording token for room '${roomId}'`);
|
||||||
|
handleError(res, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getRoomRolesAndPermissions = async (req: Request, res: Response) => {
|
export const getRoomRolesAndPermissions = async (req: Request, res: Response) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const roomService = container.get(RoomService);
|
const roomService = container.get(RoomService);
|
||||||
@ -177,23 +219,6 @@ export const getRoomRoleAndPermissions = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateRoomPreferences = async (req: Request, res: Response) => {
|
|
||||||
const logger = container.get(LoggerService);
|
|
||||||
const roomService = container.get(RoomService);
|
|
||||||
const roomPreferences = req.body;
|
|
||||||
const { roomId } = req.params;
|
|
||||||
|
|
||||||
logger.verbose(`Updating room preferences`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const room = await roomService.updateMeetRoomPreferences(roomId, roomPreferences);
|
|
||||||
return res.status(200).json(room);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error saving room preferences: ${error}`);
|
|
||||||
handleError(res, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleError = (res: Response, error: OpenViduMeetError | unknown) => {
|
const handleError = (res: Response, error: OpenViduMeetError | unknown) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
logger.error(String(error));
|
logger.error(String(error));
|
||||||
|
|||||||
@ -18,19 +18,25 @@ dotenv.config(envPath ? { path: envPath } : {});
|
|||||||
export const {
|
export const {
|
||||||
SERVER_PORT = 6080,
|
SERVER_PORT = 6080,
|
||||||
SERVER_CORS_ORIGIN = '*',
|
SERVER_CORS_ORIGIN = '*',
|
||||||
|
MEET_LOG_LEVEL = 'info',
|
||||||
MEET_NAME_ID = 'openviduMeet',
|
MEET_NAME_ID = 'openviduMeet',
|
||||||
|
|
||||||
|
// Authentication configuration
|
||||||
MEET_API_KEY = 'meet-api-key',
|
MEET_API_KEY = 'meet-api-key',
|
||||||
MEET_USER = 'user',
|
MEET_USER = 'user',
|
||||||
MEET_SECRET = 'user',
|
MEET_SECRET = 'user',
|
||||||
MEET_ADMIN_USER = 'admin',
|
MEET_ADMIN_USER = 'admin',
|
||||||
MEET_ADMIN_SECRET = 'admin',
|
MEET_ADMIN_SECRET = 'admin',
|
||||||
MEET_PARTICIPANT_TOKEN_EXPIRATION = '6h',
|
|
||||||
|
// Token expiration times
|
||||||
MEET_ACCESS_TOKEN_EXPIRATION = '2h',
|
MEET_ACCESS_TOKEN_EXPIRATION = '2h',
|
||||||
MEET_REFRESH_TOKEN_EXPIRATION = '1d',
|
MEET_REFRESH_TOKEN_EXPIRATION = '1d',
|
||||||
MEET_PREFERENCES_STORAGE_MODE = 's3',
|
MEET_PARTICIPANT_TOKEN_EXPIRATION = '6h',
|
||||||
|
MEET_RECORDING_TOKEN_EXPIRATION = '2h',
|
||||||
|
|
||||||
|
// Webhook configuration
|
||||||
MEET_WEBHOOK_ENABLED = 'false',
|
MEET_WEBHOOK_ENABLED = 'false',
|
||||||
MEET_WEBHOOK_URL = 'http://localhost:5080/webhook',
|
MEET_WEBHOOK_URL = 'http://localhost:5080/webhook',
|
||||||
MEET_LOG_LEVEL = 'info',
|
|
||||||
|
|
||||||
// LiveKit configuration
|
// LiveKit configuration
|
||||||
LIVEKIT_URL = 'ws://localhost:7880',
|
LIVEKIT_URL = 'ws://localhost:7880',
|
||||||
@ -38,6 +44,8 @@ export const {
|
|||||||
LIVEKIT_API_KEY = 'devkey',
|
LIVEKIT_API_KEY = 'devkey',
|
||||||
LIVEKIT_API_SECRET = 'secret',
|
LIVEKIT_API_SECRET = 'secret',
|
||||||
|
|
||||||
|
MEET_PREFERENCES_STORAGE_MODE = 's3',
|
||||||
|
|
||||||
// S3 configuration
|
// S3 configuration
|
||||||
MEET_S3_BUCKET = 'openvidu',
|
MEET_S3_BUCKET = 'openvidu',
|
||||||
MEET_S3_SUBBUCKET = 'openvidu-meet',
|
MEET_S3_SUBBUCKET = 'openvidu-meet',
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
MeetChatPreferences,
|
MeetChatPreferences,
|
||||||
MeetRoomOptions,
|
MeetRecordingAccess,
|
||||||
MeetRecordingPreferences,
|
MeetRecordingPreferences,
|
||||||
|
MeetRoomFilters,
|
||||||
|
MeetRoomOptions,
|
||||||
MeetRoomPreferences,
|
MeetRoomPreferences,
|
||||||
MeetVirtualBackgroundPreferences,
|
MeetVirtualBackgroundPreferences
|
||||||
MeetRoomFilters
|
|
||||||
} from '@typings-ce';
|
} from '@typings-ce';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import { z } from 'zod';
|
|
||||||
import ms from 'ms';
|
import ms from 'ms';
|
||||||
|
import { z } from 'zod';
|
||||||
import INTERNAL_CONFIG from '../../config/internal-config.js';
|
import INTERNAL_CONFIG from '../../config/internal-config.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,8 +56,16 @@ const validForceQueryParam = () =>
|
|||||||
}, z.boolean())
|
}, z.boolean())
|
||||||
.default(false);
|
.default(false);
|
||||||
|
|
||||||
|
const RecordingAccessSchema: z.ZodType<MeetRecordingAccess> = z.enum([
|
||||||
|
MeetRecordingAccess.ADMIN,
|
||||||
|
MeetRecordingAccess.ADMIN_MODERATOR,
|
||||||
|
MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER,
|
||||||
|
MeetRecordingAccess.PUBLIC
|
||||||
|
]);
|
||||||
|
|
||||||
const RecordingPreferencesSchema: z.ZodType<MeetRecordingPreferences> = z.object({
|
const RecordingPreferencesSchema: z.ZodType<MeetRecordingPreferences> = z.object({
|
||||||
enabled: z.boolean()
|
enabled: z.boolean(),
|
||||||
|
allowAccessTo: RecordingAccessSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
const ChatPreferencesSchema: z.ZodType<MeetChatPreferences> = z.object({
|
const ChatPreferencesSchema: z.ZodType<MeetChatPreferences> = z.object({
|
||||||
@ -89,7 +98,7 @@ const RoomRequestOptionsSchema: z.ZodType<MeetRoomOptions> = z.object({
|
|||||||
.optional()
|
.optional()
|
||||||
.default(''),
|
.default(''),
|
||||||
preferences: RoomPreferencesSchema.optional().default({
|
preferences: RoomPreferencesSchema.optional().default({
|
||||||
recordingPreferences: { enabled: true },
|
recordingPreferences: { enabled: true, allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER },
|
||||||
chatPreferences: { enabled: true },
|
chatPreferences: { enabled: true },
|
||||||
virtualBackgroundPreferences: { enabled: true }
|
virtualBackgroundPreferences: { enabled: true }
|
||||||
})
|
})
|
||||||
@ -154,6 +163,10 @@ const BulkDeleteRoomsSchema = z.object({
|
|||||||
force: validForceQueryParam()
|
force: validForceQueryParam()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const RecordingTokenRequestSchema = z.object({
|
||||||
|
secret: z.string().nonempty('Secret is required')
|
||||||
|
});
|
||||||
|
|
||||||
export const withValidRoomOptions = (req: Request, res: Response, next: NextFunction) => {
|
export const withValidRoomOptions = (req: Request, res: Response, next: NextFunction) => {
|
||||||
const { success, error, data } = RoomRequestOptionsSchema.safeParse(req.body);
|
const { success, error, data } = RoomRequestOptionsSchema.safeParse(req.body);
|
||||||
|
|
||||||
@ -235,6 +248,17 @@ export const withValidRoomDeleteRequest = (req: Request, res: Response, next: Ne
|
|||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const withValidRoomSecret = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const { success, error, data } = RecordingTokenRequestSchema.safeParse(req.body);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return rejectRequest(res, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.body = data;
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
const rejectRequest = (res: Response, error: z.ZodError) => {
|
const rejectRequest = (res: Response, error: z.ZodError) => {
|
||||||
const errors = error.errors.map((error) => ({
|
const errors = error.errors.map((error) => ({
|
||||||
field: error.path.join('.'),
|
field: error.path.join('.'),
|
||||||
|
|||||||
@ -113,6 +113,10 @@ export const errorRoomNotFound = (roomId: string): OpenViduMeetError => {
|
|||||||
return new OpenViduMeetError('Room Error', `The room '${roomId}' does not exist`, 404);
|
return new OpenViduMeetError('Room Error', `The room '${roomId}' does not exist`, 404);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const errorRoomNotFoundOrEmptyRecordings = (roomId: string): OpenViduMeetError => {
|
||||||
|
return new OpenViduMeetError('Room Error', `The room '${roomId}' does not exist or has no recordings`, 404);
|
||||||
|
};
|
||||||
|
|
||||||
export const errorInvalidRoomSecret = (roomId: string, secret: string): OpenViduMeetError => {
|
export const errorInvalidRoomSecret = (roomId: string, secret: string): OpenViduMeetError => {
|
||||||
return new OpenViduMeetError('Room Error', `The secret '${secret}' is not recognized for room '${roomId}'`, 400);
|
return new OpenViduMeetError('Room Error', `The secret '${secret}' is not recognized for room '${roomId}'`, 400);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,7 +14,8 @@ import {
|
|||||||
withValidRoomFiltersRequest,
|
withValidRoomFiltersRequest,
|
||||||
withValidRoomId,
|
withValidRoomId,
|
||||||
withValidRoomOptions,
|
withValidRoomOptions,
|
||||||
withValidRoomPreferences
|
withValidRoomPreferences,
|
||||||
|
withValidRoomSecret
|
||||||
} from '../middlewares/index.js';
|
} from '../middlewares/index.js';
|
||||||
|
|
||||||
export const roomRouter = Router();
|
export const roomRouter = Router();
|
||||||
@ -63,6 +64,13 @@ internalRoomRouter.put(
|
|||||||
roomCtrl.updateRoomPreferences
|
roomCtrl.updateRoomPreferences
|
||||||
);
|
);
|
||||||
|
|
||||||
|
internalRoomRouter.post(
|
||||||
|
'/:roomId/recording-token',
|
||||||
|
configureCreateRoomAuth,
|
||||||
|
withValidRoomSecret,
|
||||||
|
roomCtrl.generateRecordingToken
|
||||||
|
);
|
||||||
|
|
||||||
// Roles and permissions
|
// Roles and permissions
|
||||||
internalRoomRouter.get('/:roomId/roles', roomCtrl.getRoomRolesAndPermissions);
|
internalRoomRouter.get('/:roomId/roles', roomCtrl.getRoomRolesAndPermissions);
|
||||||
internalRoomRouter.get('/:roomId/roles/:secret', roomCtrl.getRoomRoleAndPermissions);
|
internalRoomRouter.get('/:roomId/roles/:secret', roomCtrl.getRoomRoleAndPermissions);
|
||||||
|
|||||||
@ -1,19 +1,29 @@
|
|||||||
import { uid as secureUid } from 'uid/secure';
|
import {
|
||||||
import { inject, injectable } from '../config/dependency-injector.config.js';
|
MeetRecordingAccess,
|
||||||
|
MeetRoom,
|
||||||
|
MeetRoomFilters,
|
||||||
|
MeetRoomOptions,
|
||||||
|
MeetRoomPreferences,
|
||||||
|
ParticipantRole,
|
||||||
|
RecordingPermissions
|
||||||
|
} from '@typings-ce';
|
||||||
|
import { inject, injectable } from 'inversify';
|
||||||
import { CreateOptions, Room, SendDataOptions } from 'livekit-server-sdk';
|
import { CreateOptions, Room, SendDataOptions } from 'livekit-server-sdk';
|
||||||
import { LoggerService } from './logger.service.js';
|
import { uid as secureUid } from 'uid/secure';
|
||||||
import { LiveKitService } from './livekit.service.js';
|
|
||||||
import { MeetStorageService } from './storage/storage.service.js';
|
|
||||||
import { MeetRoom, MeetRoomFilters, MeetRoomOptions, MeetRoomPreferences, ParticipantRole } from '@typings-ce';
|
|
||||||
import { MeetRoomHelper } from '../helpers/room.helper.js';
|
|
||||||
import { SystemEventService } from './system-event.service.js';
|
|
||||||
import { IScheduledTask, TaskSchedulerService } from './task-scheduler.service.js';
|
|
||||||
import { errorInvalidRoomSecret, internalError } from '../models/error.model.js';
|
|
||||||
import { OpenViduComponentsAdapterHelper } from '../helpers/index.js';
|
|
||||||
import { uid } from 'uid/single';
|
import { uid } from 'uid/single';
|
||||||
import { MEET_NAME_ID } from '../environment.js';
|
|
||||||
import { UtilsHelper } from '../helpers/utils.helper.js';
|
|
||||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||||
|
import { MEET_NAME_ID } from '../environment.js';
|
||||||
|
import { MeetRoomHelper, OpenViduComponentsAdapterHelper, UtilsHelper } from '../helpers/index.js';
|
||||||
|
import { errorInvalidRoomSecret, errorRoomNotFoundOrEmptyRecordings, internalError } from '../models/error.model.js';
|
||||||
|
import {
|
||||||
|
IScheduledTask,
|
||||||
|
LiveKitService,
|
||||||
|
LoggerService,
|
||||||
|
MeetStorageService,
|
||||||
|
SystemEventService,
|
||||||
|
TaskSchedulerService,
|
||||||
|
TokenService
|
||||||
|
} from './index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for managing OpenVidu Meet rooms.
|
* Service for managing OpenVidu Meet rooms.
|
||||||
@ -28,7 +38,8 @@ export class RoomService {
|
|||||||
@inject(MeetStorageService) protected storageService: MeetStorageService,
|
@inject(MeetStorageService) protected storageService: MeetStorageService,
|
||||||
@inject(LiveKitService) protected livekitService: LiveKitService,
|
@inject(LiveKitService) protected livekitService: LiveKitService,
|
||||||
@inject(SystemEventService) protected systemEventService: SystemEventService,
|
@inject(SystemEventService) protected systemEventService: SystemEventService,
|
||||||
@inject(TaskSchedulerService) protected taskSchedulerService: TaskSchedulerService
|
@inject(TaskSchedulerService) protected taskSchedulerService: TaskSchedulerService,
|
||||||
|
@inject(TokenService) protected tokenService: TokenService
|
||||||
) {
|
) {
|
||||||
const roomGarbageCollectorTask: IScheduledTask = {
|
const roomGarbageCollectorTask: IScheduledTask = {
|
||||||
name: 'roomGarbageCollector',
|
name: 'roomGarbageCollector',
|
||||||
@ -235,6 +246,10 @@ export class RoomService {
|
|||||||
*/
|
*/
|
||||||
async getRoomRoleBySecret(roomId: string, secret: string): Promise<ParticipantRole> {
|
async getRoomRoleBySecret(roomId: string, secret: string): Promise<ParticipantRole> {
|
||||||
const room = await this.getMeetRoom(roomId);
|
const room = await this.getMeetRoom(roomId);
|
||||||
|
return this.getRoomRoleBySecretFromRoom(room, secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getRoomRoleBySecretFromRoom(room: MeetRoom, secret: string): ParticipantRole {
|
||||||
const { moderatorSecret, publisherSecret } = MeetRoomHelper.extractSecretsFromRoom(room);
|
const { moderatorSecret, publisherSecret } = MeetRoomHelper.extractSecretsFromRoom(room);
|
||||||
|
|
||||||
switch (secret) {
|
switch (secret) {
|
||||||
@ -243,10 +258,53 @@ export class RoomService {
|
|||||||
case publisherSecret:
|
case publisherSecret:
|
||||||
return ParticipantRole.PUBLISHER;
|
return ParticipantRole.PUBLISHER;
|
||||||
default:
|
default:
|
||||||
throw errorInvalidRoomSecret(roomId, secret);
|
throw errorInvalidRoomSecret(room.roomId, secret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a token with recording permissions for a specific room.
|
||||||
|
*
|
||||||
|
* @param roomId - The unique identifier of the room for which the recording token is being generated.
|
||||||
|
* @param secret - The secret associated with the room, used to determine the user's role.
|
||||||
|
* @returns A promise that resolves to the generated recording token as a string.
|
||||||
|
* @throws An error if the room with the given `roomId` is not found.
|
||||||
|
*/
|
||||||
|
async generateRecordingToken(roomId: string, secret: string): Promise<string> {
|
||||||
|
const room = await this.storageService.getArchivedRoomMetadata(roomId);
|
||||||
|
|
||||||
|
if (!room) {
|
||||||
|
// If the room is not found, it means that there are no recordings for that room or the room doesn't exist
|
||||||
|
throw errorRoomNotFoundOrEmptyRecordings(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = this.getRoomRoleBySecretFromRoom(room as MeetRoom, secret);
|
||||||
|
const permissions = this.getRecordingPermissions(room, role);
|
||||||
|
return await this.tokenService.generateRecordingToken(roomId, role, permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getRecordingPermissions(room: Partial<MeetRoom>, role: ParticipantRole): RecordingPermissions {
|
||||||
|
const recordingAccess = room.preferences!.recordingPreferences.allowAccessTo;
|
||||||
|
|
||||||
|
// A participant can delete recordings if they are a moderator and the recording access is not set to admin
|
||||||
|
const canDeleteRecordings = role === ParticipantRole.MODERATOR && recordingAccess !== MeetRecordingAccess.ADMIN;
|
||||||
|
|
||||||
|
/* A participant can retrieve recordings if
|
||||||
|
- they can delete recordings
|
||||||
|
- the recording access is public
|
||||||
|
- they are a publisher and the recording access includes publishers
|
||||||
|
*/
|
||||||
|
const canRetrieveRecordings =
|
||||||
|
canDeleteRecordings ||
|
||||||
|
recordingAccess === MeetRecordingAccess.PUBLIC ||
|
||||||
|
(role === ParticipantRole.PUBLISHER && recordingAccess === MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER);
|
||||||
|
|
||||||
|
return {
|
||||||
|
canRetrieveRecordings,
|
||||||
|
canDeleteRecordings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async sendRoomStatusSignalToOpenViduComponents(roomId: string, participantSid: string) {
|
async sendRoomStatusSignalToOpenViduComponents(roomId: string, participantSid: string) {
|
||||||
// Check if recording is started in the room
|
// Check if recording is started in the room
|
||||||
const activeEgressArray = await this.livekitService.getActiveEgress(roomId);
|
const activeEgressArray = await this.livekitService.getActiveEgress(roomId);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ParticipantOptions, ParticipantPermissions, ParticipantRole, User } from '@typings-ce';
|
import { ParticipantOptions, ParticipantPermissions, ParticipantRole, RecordingPermissions, User } from '@typings-ce';
|
||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { AccessToken, AccessTokenOptions, ClaimGrants, TokenVerifier, VideoGrant } from 'livekit-server-sdk';
|
import { AccessToken, AccessTokenOptions, ClaimGrants, TokenVerifier, VideoGrant } from 'livekit-server-sdk';
|
||||||
import {
|
import {
|
||||||
@ -7,6 +7,7 @@ import {
|
|||||||
LIVEKIT_URL,
|
LIVEKIT_URL,
|
||||||
MEET_ACCESS_TOKEN_EXPIRATION,
|
MEET_ACCESS_TOKEN_EXPIRATION,
|
||||||
MEET_PARTICIPANT_TOKEN_EXPIRATION,
|
MEET_PARTICIPANT_TOKEN_EXPIRATION,
|
||||||
|
MEET_RECORDING_TOKEN_EXPIRATION,
|
||||||
MEET_REFRESH_TOKEN_EXPIRATION
|
MEET_REFRESH_TOKEN_EXPIRATION
|
||||||
} from '../environment.js';
|
} from '../environment.js';
|
||||||
import { LoggerService } from './index.js';
|
import { LoggerService } from './index.js';
|
||||||
@ -58,6 +59,25 @@ export class TokenService {
|
|||||||
return await this.generateJwtToken(tokenOptions, permissions.livekit);
|
return await this.generateJwtToken(tokenOptions, permissions.livekit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async generateRecordingToken(
|
||||||
|
roomId: string,
|
||||||
|
role: ParticipantRole,
|
||||||
|
permissions: RecordingPermissions
|
||||||
|
): Promise<string> {
|
||||||
|
this.logger.info(`Generating recording token for room ${roomId}`);
|
||||||
|
const tokenOptions: AccessTokenOptions = {
|
||||||
|
ttl: MEET_RECORDING_TOKEN_EXPIRATION,
|
||||||
|
metadata: JSON.stringify({
|
||||||
|
role,
|
||||||
|
recordingPermissions: permissions
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const grants: VideoGrant = {
|
||||||
|
room: roomId
|
||||||
|
};
|
||||||
|
return await this.generateJwtToken(tokenOptions, grants);
|
||||||
|
}
|
||||||
|
|
||||||
private async generateJwtToken(tokenOptions: AccessTokenOptions, grants?: VideoGrant): Promise<string> {
|
private async generateJwtToken(tokenOptions: AccessTokenOptions, grants?: VideoGrant): Promise<string> {
|
||||||
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, tokenOptions);
|
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, tokenOptions);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user