From ae8531ea8101145d4adf2bdda57eee3666adc762 Mon Sep 17 00:00:00 2001 From: Rowan Seymour Date: Tue, 10 Aug 2021 14:56:41 -0500 Subject: [PATCH] Implement asynchronous AMD for Twilio IVR --- core/ivr/ivr.go | 7 ++++++- core/ivr/twiml/twiml.go | 18 +++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/core/ivr/ivr.go b/core/ivr/ivr.go index 3738497dd..e2e4fbda5 100644 --- a/core/ivr/ivr.go +++ b/core/ivr/ivr.go @@ -417,6 +417,11 @@ func ResumeIVRFlow( return WriteErrorResponse(ctx, rt.DB, client, conn, w, errors.Errorf("active session: %d does not match connection: %d", session.ID(), *session.ConnectionID())) } + // check connection is still marked as in progress + if conn.Status() != models.ConnectionStatusInProgress { + return WriteErrorResponse(ctx, rt.DB, client, conn, w, errors.Errorf("connection in invalid state: %s", conn.Status())) + } + // preprocess this request body, err := client.PreprocessResume(ctx, rt.DB, rt.RP, conn, r) if err != nil { @@ -590,7 +595,7 @@ func HandleIVRStatus(ctx context.Context, rt *runtime.Runtime, oa *models.OrgAss // no associated start? this is a permanent failure if conn.StartID() == models.NilStartID { conn.MarkFailed(ctx, rt.DB, time.Now()) - return client.WriteEmptyResponse(w, "status updated: F") + return client.WriteEmptyResponse(w, "no flow start found, status updated: F") } // on errors we need to look up the flow to know how long to wait before retrying diff --git a/core/ivr/twiml/twiml.go b/core/ivr/twiml/twiml.go index 577e672ab..f95ba7150 100644 --- a/core/ivr/twiml/twiml.go +++ b/core/ivr/twiml/twiml.go @@ -190,7 +190,7 @@ func (c *client) RequestCall(number urns.URN, callbackURL string, statusURL stri form.Set("StatusCallback", statusURL) form.Set("MachineDetection", "Enable") form.Set("AsyncAmd", "true") - form.Set("AsyncAmdStatusCallback", callbackURL) + form.Set("AsyncAmdStatusCallback", statusURL) sendURL := c.baseURL + strings.Replace(callPath, "{AccountSID}", c.accountSID, -1) @@ -289,14 +289,14 @@ func (c *client) ResumeForRequest(r *http.Request) (ivr.Resume, error) { // StatusForRequest returns the current call status for the passed in status (and optional duration if known) func (c *client) StatusForRequest(r *http.Request) (models.ConnectionStatus, int) { + // we re-use our status callback for AMD results which will have an AnsweredBy field but no CallStatus field answeredBy := r.Form.Get("AnsweredBy") - - switch answeredBy { - case "human", "unknown", "": - break - - default: - return models.ConnectionStatusErrored, 0 + if answeredBy != "" { + switch answeredBy { + case "machine_start", "fax": + return models.ConnectionStatusErrored, 0 + } + return models.ConnectionStatusInProgress, 0 } status := r.Form.Get("CallStatus") @@ -316,7 +316,7 @@ func (c *client) StatusForRequest(r *http.Request) (models.ConnectionStatus, int return models.ConnectionStatusErrored, 0 default: - logrus.WithField("call_status", status).Error("unknown call status in ivr callback") + logrus.WithField("call_status", status).Error("unknown call status in status callback") return models.ConnectionStatusFailed, 0 } }