From 6b0c26e4c4d4ce4d00f1fcadd083f959b3e7bb68 Mon Sep 17 00:00:00 2001 From: Stephen Hand Date: Fri, 16 Jan 2026 13:18:03 +0000 Subject: [PATCH] Return 404 when participant not found in conferencing endpoints --- .../src/conference/getParticipant.ts | 31 ++++++++++++++----- .../src/conference/removeParticipant.ts | 31 ++++++++++++++----- .../src/conference/updateParticipant.ts | 23 ++++++++++++-- 3 files changed, 68 insertions(+), 17 deletions(-) diff --git a/lambdas/account-scoped/src/conference/getParticipant.ts b/lambdas/account-scoped/src/conference/getParticipant.ts index c8ee471504..144a445c22 100644 --- a/lambdas/account-scoped/src/conference/getParticipant.ts +++ b/lambdas/account-scoped/src/conference/getParticipant.ts @@ -17,7 +17,8 @@ import { AccountScopedHandler } from '../httpTypes'; import { newMissingParameterResult } from '../httpErrors'; import { getTwilioClient } from '@tech-matters/twilio-configuration'; -import { newOk } from '../Result'; +import { newErr, newOk } from '../Result'; +import type RestException from 'twilio/lib/base/RestException'; export type Body = { callSid: string; @@ -34,10 +35,26 @@ export const getParticipantHandler: AccountScopedHandler = async ( if (!callSid) return newMissingParameterResult('callSid'); if (!conferenceSid) return newMissingParameterResult('conferenceSid'); const client = await getTwilioClient(accountSid); - const participant = await client.conferences - .get(conferenceSid) - .participants.get(callSid) - .fetch(); - - return newOk({ participant }); + try { + const participant = await client.conferences + .get(conferenceSid) + .participants.get(callSid) + .fetch(); + return newOk({ participant }); + } catch (error) { + const restError = error as RestException; + if (restError.status === 404) { + const message = `Participant with call sid ${callSid} not found on ${accountSid}/${conferenceSid}`; + // Often errors of this type are thrown but the recording appears to pause at the correct point. + console.warn(message, error); + return newErr({ + message, + error: { + cause: restError, + statusCode: 404, + }, + }); + } + throw error; + } }; diff --git a/lambdas/account-scoped/src/conference/removeParticipant.ts b/lambdas/account-scoped/src/conference/removeParticipant.ts index ac3185565f..e59db9cfc9 100644 --- a/lambdas/account-scoped/src/conference/removeParticipant.ts +++ b/lambdas/account-scoped/src/conference/removeParticipant.ts @@ -16,8 +16,9 @@ import { AccountScopedHandler } from '../httpTypes'; import { getTwilioClient } from '@tech-matters/twilio-configuration'; -import { newOk } from '../Result'; +import { newErr, newOk } from '../Result'; import { newMissingParameterResult } from '../httpErrors'; +import type RestException from 'twilio/lib/base/RestException'; export type Body = { callSid: string; @@ -35,10 +36,26 @@ export const removeParticipantHandler: AccountScopedHandler = async ( if (!conferenceSid) return newMissingParameterResult('conferenceSid'); const client = await getTwilioClient(accountSid); - const participantRemoved = await client - .conferences(conferenceSid) - .participants(callSid) - .remove(); - - return newOk({ message: `Participant removed: ${participantRemoved}` }); + try { + const participantRemoved = await client + .conferences(conferenceSid) + .participants(callSid) + .remove(); + return newOk({ message: `Participant removed: ${participantRemoved}` }); + } catch (error) { + const restError = error as RestException; + if (restError.status === 404) { + const message = `Participant with call sid ${callSid} not found on ${accountSid}/${conferenceSid}`; + // Often errors of this type are thrown but the recording appears to pause at the correct point. + console.warn(message, error); + return newErr({ + message, + error: { + cause: restError, + statusCode: 404, + }, + }); + } + throw error; + } }; diff --git a/lambdas/account-scoped/src/conference/updateParticipant.ts b/lambdas/account-scoped/src/conference/updateParticipant.ts index 367dcd8099..301996b1f1 100644 --- a/lambdas/account-scoped/src/conference/updateParticipant.ts +++ b/lambdas/account-scoped/src/conference/updateParticipant.ts @@ -17,7 +17,8 @@ import { AccountScopedHandler } from '../httpTypes'; import { newMissingParameterResult } from '../httpErrors'; import { getTwilioClient } from '@tech-matters/twilio-configuration'; -import { newOk } from '../Result'; +import { newErr, newOk } from '../Result'; +import type RestException from 'twilio/lib/base/RestException'; const validUpdates = ['endConferenceOnExit', 'hold', 'muted'] as const; @@ -56,8 +57,24 @@ export const updateParticipantHandler: AccountScopedHandler = async ( .conferences(conferenceSid) .participants(callSid) .fetch(); - - await participant.update(parsedUpdates); + try { + await participant.update(parsedUpdates); + } catch (error) { + const restError = error as RestException; + if (restError.status === 404) { + const message = `Participant with call sid ${callSid} not found on ${accountSid}/${conferenceSid}`; + // Often errors of this type are thrown but the recording appears to pause at the correct point. + console.warn(message, error); + return newErr({ + message, + error: { + cause: restError, + statusCode: 404, + }, + }); + } + throw error; + } return newOk({ message: `Participant updated: ${updates}` }); };