backend: enhance webhook handling with locking mechanism and error handling
This commit is contained in:
parent
bbd3c274c4
commit
dc268a436c
@ -9,10 +9,16 @@ export const lkWebhookHandler = async (req: Request, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
const lkWebhookService = container.get(LivekitWebhookService);
|
const lkWebhookService = container.get(LivekitWebhookService);
|
||||||
|
|
||||||
const webhookEvent: WebhookEvent = await lkWebhookService.getEventFromWebhook(
|
const webhookEvent: WebhookEvent | undefined = await lkWebhookService.getEventFromWebhook(
|
||||||
req.body,
|
req.body,
|
||||||
req.get('Authorization')!
|
req.get('Authorization')!
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!webhookEvent) {
|
||||||
|
logger.debug(`Webhook processing skipped: May another instance is processing it`);
|
||||||
|
return res.status(200).send();
|
||||||
|
}
|
||||||
|
|
||||||
const { event: eventType, egressInfo, room, participant } = webhookEvent;
|
const { event: eventType, egressInfo, room, participant } = webhookEvent;
|
||||||
|
|
||||||
const belongsToOpenViduMeet = await lkWebhookService.webhookEventBelongsToOpenViduMeet(webhookEvent);
|
const belongsToOpenViduMeet = await lkWebhookService.webhookEventBelongsToOpenViduMeet(webhookEvent);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { WebhookEvent } from 'livekit-server-sdk';
|
||||||
import { RedisLockName, RedisLockPrefix } from '../models/redis.model.js';
|
import { RedisLockName, RedisLockPrefix } from '../models/redis.model.js';
|
||||||
|
|
||||||
export class MeetLock {
|
export class MeetLock {
|
||||||
@ -36,4 +37,12 @@ export class MeetLock {
|
|||||||
static getGlobalPreferencesLock(): string {
|
static getGlobalPreferencesLock(): string {
|
||||||
return `${RedisLockPrefix.BASE}${RedisLockName.GLOBAL_PREFERENCES}`;
|
return `${RedisLockPrefix.BASE}${RedisLockName.GLOBAL_PREFERENCES}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getWebhookLock(webhookEvent: WebhookEvent) {
|
||||||
|
if (!webhookEvent || !webhookEvent.event) {
|
||||||
|
throw new Error('event must be a non-empty string');
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${RedisLockPrefix.BASE}${RedisLockName.WEBHOOK}_${webhookEvent.event}_${webhookEvent.id}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,5 +21,6 @@ export const enum RedisLockName {
|
|||||||
ROOM_GARBAGE_COLLECTOR = 'room_garbage_collector',
|
ROOM_GARBAGE_COLLECTOR = 'room_garbage_collector',
|
||||||
RECORDING_ACTIVE = 'recording_active',
|
RECORDING_ACTIVE = 'recording_active',
|
||||||
SCHEDULED_TASK = 'scheduled_task',
|
SCHEDULED_TASK = 'scheduled_task',
|
||||||
GLOBAL_PREFERENCES = 'global_preferences'
|
GLOBAL_PREFERENCES = 'global_preferences',
|
||||||
|
WEBHOOK = 'webhook'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { MeetRecordingInfo, MeetRecordingStatus } from '@typings-ce';
|
|||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { EgressInfo, ParticipantInfo, Room, WebhookEvent, WebhookReceiver } from 'livekit-server-sdk';
|
import { EgressInfo, ParticipantInfo, Room, WebhookEvent, WebhookReceiver } from 'livekit-server-sdk';
|
||||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from '../environment.js';
|
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET } from '../environment.js';
|
||||||
import { MeetRoomHelper, RecordingHelper } from '../helpers/index.js';
|
import { MeetLock, MeetRoomHelper, RecordingHelper } from '../helpers/index.js';
|
||||||
import { DistributedEventType } from '../models/distributed-event.model.js';
|
import { DistributedEventType } from '../models/distributed-event.model.js';
|
||||||
import {
|
import {
|
||||||
LiveKitService,
|
LiveKitService,
|
||||||
@ -15,6 +15,7 @@ import {
|
|||||||
DistributedEventService
|
DistributedEventService
|
||||||
} from './index.js';
|
} from './index.js';
|
||||||
import { FrontendEventService } from './frontend-event.service.js';
|
import { FrontendEventService } from './frontend-event.service.js';
|
||||||
|
import ms from 'ms';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LivekitWebhookService {
|
export class LivekitWebhookService {
|
||||||
@ -39,9 +40,14 @@ export class LivekitWebhookService {
|
|||||||
* @param auth - The authentication token for verifying the webhook request.
|
* @param auth - The authentication token for verifying the webhook request.
|
||||||
* @returns The WebhookEvent extracted from the request body.
|
* @returns The WebhookEvent extracted from the request body.
|
||||||
*/
|
*/
|
||||||
async getEventFromWebhook(body: string, auth?: string): Promise<WebhookEvent> {
|
async getEventFromWebhook(body: string, auth?: string): Promise<WebhookEvent | undefined> {
|
||||||
try {
|
try {
|
||||||
return await this.webhookReceiver.receive(body, auth);
|
const webhookEvent = await this.webhookReceiver.receive(body, auth);
|
||||||
|
const lock = await this.mutexService.acquire(MeetLock.getWebhookLock(webhookEvent), ms('5s'));
|
||||||
|
|
||||||
|
if (!lock) return undefined;
|
||||||
|
|
||||||
|
return webhookEvent;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error('Error receiving webhook event', error);
|
this.logger.error('Error receiving webhook event', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@ -20,9 +20,12 @@ export class MutexService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Acquires a lock for the specified resource.
|
* Acquires a lock for the specified resource.
|
||||||
|
* This method uses the Redlock library to acquire a distributed lock on a resource identified by the key.
|
||||||
|
* The request will return null if the lock cannot be acquired.
|
||||||
|
*
|
||||||
* @param key The resource to acquire a lock for.
|
* @param key The resource to acquire a lock for.
|
||||||
* @param ttl The time-to-live (TTL) for the lock in milliseconds. Defaults to the TTL value of the MutexService.
|
* @param ttl The time-to-live (TTL) for the lock in milliseconds. Defaults to the TTL value of the MutexService.
|
||||||
* @returns A Promise that resolves to the acquired Lock object.
|
* @returns A Promise that resolves to the acquired Lock object. If the lock cannot be acquired, it resolves to null.
|
||||||
*/
|
*/
|
||||||
async acquire(key: string, ttl: number = this.TTL_MS): Promise<Lock | null> {
|
async acquire(key: string, ttl: number = this.TTL_MS): Promise<Lock | null> {
|
||||||
const registryKey = MeetLock.getRegistryLock(key);
|
const registryKey = MeetLock.getRegistryLock(key);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user