From 789fa9dc2d599b4aae81d00504c163e61e4acf7a Mon Sep 17 00:00:00 2001 From: Philipp Burckhardt Date: Fri, 9 Jun 2023 20:17:19 -0400 Subject: [PATCH] feat: add support for stop signal and use do-while --- legacy/src/services/twilio-conversation.ts | 26 ++++++++++++++-- legacy/test/twilio-conversation.test.ts | 35 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/legacy/src/services/twilio-conversation.ts b/legacy/src/services/twilio-conversation.ts index 52d4301c..ec369142 100644 --- a/legacy/src/services/twilio-conversation.ts +++ b/legacy/src/services/twilio-conversation.ts @@ -116,6 +116,11 @@ export type TwilioSendAndWaitOptions = { * A function to validate the reply message. If the function returns `true`, the reply is considered valid and the function will return the message. If the function returns `false`, the reply is considered invalid and the function will continue to wait for a reply until the timeout is reached. */ validate?: (message: TwilioConversationMessage) => boolean + + /** + * A stop signal from an [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController), which can be used to abort retrying. More specifically, when `AbortController.abort(reason)` is called, the function will throw an error with the `reason` argument as the error message. + */ + stopSignal?: AbortSignal } /** @@ -242,14 +247,28 @@ export class TwilioConversationClient { recipientPhoneNumber, timeoutMs = DEFAULT_TWILIO_TIMEOUT_MS, intervalMs = DEFAULT_TWILIO_INTERVAL_MS, - validate = () => true + validate = () => true, + stopSignal }: TwilioSendAndWaitOptions) { + let aborted = false + stopSignal?.addEventListener( + 'abort', + () => { + aborted = true + }, + { once: true } + ) + const { sid: conversationSid } = await this.createConversation(name) await this.addParticipant({ conversationSid, recipientPhoneNumber }) await this.sendMessage({ conversationSid, text }) const start = Date.now() let nUserMessages = 0 - while (Date.now() - start < timeoutMs) { + do { + if (aborted) { + await this.deleteConversation(conversationSid) + throw new Error('Aborted waiting for reply') + } const response = await this.fetchMessages(conversationSid) if (response.messages.length > 1) { const candidates = response.messages.filter( @@ -269,7 +288,8 @@ export class TwilioConversationClient { nUserMessages = candidates.length } await sleep(intervalMs) - } + } while (Date.now() - start < timeoutMs) + await this.deleteConversation(conversationSid) throw new Error('Reached timeout waiting for reply') } diff --git a/legacy/test/twilio-conversation.test.ts b/legacy/test/twilio-conversation.test.ts index 55d0b41f..d22d0842 100644 --- a/legacy/test/twilio-conversation.test.ts +++ b/legacy/test/twilio-conversation.test.ts @@ -101,3 +101,38 @@ test('TwilioConversationClient.sendAndWaitForReply', async (t) => { } ) }) + +test('TwilioConversationClient.sendAndWaitForReply.stopSignal', async (t) => { + if ( + !process.env.TWILIO_ACCOUNT_SID || + !process.env.TWILIO_AUTH_TOKEN || + !process.env.TWILIO_TEST_PHONE_NUMBER + ) { + return t.pass() + } + + t.timeout(2 * 60 * 1000) + const client = new TwilioConversationClient() + + await t.throwsAsync( + async () => { + const controller = new AbortController() + const promise = client.sendAndWaitForReply({ + recipientPhoneNumber: process.env.TWILIO_TEST_PHONE_NUMBER as string, + text: 'Please confirm by replying with "yes" or "no".', + name: 'wait-for-reply-test', + validate: (message) => + ['yes', 'no'].includes(message.body.toLowerCase()), + timeoutMs: 10000, // 10 seconds + intervalMs: 5000, // 5 seconds + stopSignal: controller.signal + }) + controller.abort() + return promise + }, + { + instanceOf: Error, + message: 'Aborted waiting for reply' + } + ) +})