Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

admin: allow Twilio messaging service SID as "from number" #1899

Merged
merged 37 commits into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
8cbbf5c
add messaging/sid support to mocktwilio
mastercactapus Aug 23, 2021
44d930c
inputTypes and helperText
dctalbot Sep 13, 2021
e9257e8
initial validation
KatieMSB Sep 13, 2021
03918af
refine validation
KatieMSB Sep 14, 2021
0455aad
update fields for sid support where needed
KatieMSB Sep 14, 2021
a18669c
server side validation
dctalbot Sep 14, 2021
984d3c1
refactor
dctalbot Sep 14, 2021
6616634
add plus sign for region code
dctalbot Sep 14, 2021
902532c
set type as tel for telelphone-only fields
dctalbot Sep 14, 2021
9dfbda4
merge latest
KatieMSB Sep 15, 2021
ecdad5a
refactor
dctalbot Sep 15, 2021
73cdf39
enforce format for telephone-only fields
dctalbot Sep 15, 2021
ef51f08
format displayName in backend
KatieMSB Sep 15, 2021
5b51f43
run generate
KatieMSB Sep 15, 2021
fb68e0f
Merge branch 'admin-toolbox-messaging-service-support' of github.com:…
KatieMSB Sep 15, 2021
4749dfc
run gofmt
KatieMSB Sep 15, 2021
b6096b7
fix: test debounced value for validation
dctalbot Sep 15, 2021
e9aa9e2
Merge branch 'admin-toolbox-messaging-service-support' of github.com:…
dctalbot Sep 15, 2021
583debc
refine regex pattern and handle special case
KatieMSB Sep 15, 2021
072528b
messenger sid -> twilio messaging service sid
KatieMSB Sep 15, 2021
e2f8fe7
fix config validation
KatieMSB Sep 16, 2021
59d0926
remove sid support for config From Number
KatieMSB Sep 16, 2021
f916ccf
undo changes to config display names
KatieMSB Sep 16, 2021
e20cd56
run gofmt
KatieMSB Sep 16, 2021
dd97346
fix formatting
KatieMSB Sep 16, 2021
86fce5b
Merge branch 'master' into admin-toolbox-messaging-service-support
dctalbot Sep 21, 2021
4a736ba
strip out non-alphanum chars
dctalbot Sep 22, 2021
9b19e97
update validation for from values
KatieMSB Sep 22, 2021
74a4e94
merge latest
KatieMSB Sep 22, 2021
dd6386c
sid -> twiliomessagesid
KatieMSB Sep 22, 2021
66654ec
update validation
KatieMSB Sep 22, 2021
1e8d4bb
don't assign From value until call/sms is processed
mastercactapus Sep 22, 2021
d299309
use single validation func
mastercactapus Sep 22, 2021
120b4e4
update validation func comment
mastercactapus Sep 22, 2021
fa679cd
don't require `type`
mastercactapus Sep 22, 2021
6c38638
swap field types for FromValue
mastercactapus Sep 22, 2021
0e27f2b
clarify default FromValueField mode
mastercactapus Sep 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type Config struct {

AccountSID string
AuthToken string `password:"true" info:"The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation."`
FromNumber string `public:"true" info:"The Twilio number to use for outgoing notifications."`
FromNumber string `displayName:"From Number or Messenger SID" public:"true" info:"The Twilio number or Messenger SID to use for outgoing notifications."`
dctalbot marked this conversation as resolved.
Show resolved Hide resolved

DisableTwoWaySMS bool `info:"Disables SMS reply codes for alert messages."`
SMSCarrierLookup bool `info:"Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply."`
Expand Down
50 changes: 40 additions & 10 deletions devtools/configparams/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"text/template"
Expand Down Expand Up @@ -39,7 +40,7 @@ import (
func MapConfigHints(cfg config.Hints) []ConfigHint {
return []ConfigHint{
{{- range .HintFields }}
{ID: {{quote .ID}}, Value: {{.Value}}},
{ID: {{quote .ID}}, DisplayName: {{quote .DisplayName}}, Value: {{.Value}}},
{{- end}}
}
}
Expand All @@ -48,7 +49,7 @@ func MapConfigHints(cfg config.Hints) []ConfigHint {
func MapConfigValues(cfg config.Config) []ConfigValue {
return []ConfigValue{
{{- range .ConfigFields }}
{ID: {{quote .ID}}, Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}},
{ID: {{quote .ID}},Type: {{.Type}}, DisplayName: {{quote .DisplayName}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}},
{{- end}}
}
}
Expand All @@ -58,7 +59,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue {
return []ConfigValue{
{{- range .ConfigFields }}
{{- if .Public}}
{ID: {{quote .ID}}, Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}},
{ID: {{quote .ID}}, Type: {{.Type}}, DisplayName: {{quote .DisplayName}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}},
{{- end}}
{{- end}}
}
Expand Down Expand Up @@ -129,8 +130,8 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi
`))

type field struct {
ID, Type, Desc, Value string
Public, Password bool
ID, DisplayName, Type, Desc, Value string
Public, Password bool
}

func main() {
Expand All @@ -155,8 +156,8 @@ package graphql2`)
ConfigFields []field
HintFields []field
}
input.ConfigFields = printType("", reflect.TypeOf(config.Config{}), "", false, false)
input.HintFields = printType("", reflect.TypeOf(config.Hints{}), "", false, false)
input.ConfigFields = printType("", reflect.TypeOf(config.Config{}), "", "", false, false)
input.HintFields = printType("", reflect.TypeOf(config.Hints{}), "", "", false, false)

err := tmpl.Execute(w, input)
if err != nil {
Expand All @@ -169,9 +170,9 @@ func printField(prefix string, f reflect.StructField) []field {
if f.Type.Kind() == reflect.Slice && f.Type.Elem().Kind() == reflect.Struct {
fPrefix = prefix + f.Name + "[]."
}
return printType(fPrefix, f.Type, f.Tag.Get("info"), f.Tag.Get("public") == "true", f.Tag.Get("password") == "true")
return printType(fPrefix, f.Type, f.Tag.Get("displayName"), f.Tag.Get("info"), f.Tag.Get("public") == "true", f.Tag.Get("password") == "true")
}
func printType(prefix string, v reflect.Type, details string, public, pass bool) []field {
func printType(prefix string, v reflect.Type, displayName string, info string, public, pass bool) []field {
var f []field
key := strings.TrimSuffix(prefix, ".")

Expand Down Expand Up @@ -207,6 +208,35 @@ func printType(prefix string, v reflect.Type, details string, public, pass bool)
panic(fmt.Sprintf("not implemented for type %T", v.Kind()))
}

f = append(f, field{ID: key, Type: typ, Desc: details, Value: value, Public: public, Password: pass})
f = append(f, field{ID: key, Type: typ, DisplayName: printDisplayName(key, displayName), Desc: info, Value: value, Public: public, Password: pass})
return f
}

func printDisplayName(key, displayName string) string {
// format reformats string with spaces, i.e. GoogleAnalyticsID -> Google Analytics ID
format := func(str string) string {
var formattedStr []string
// pattern groups words in string by capital letters
pattern := regexp.MustCompile("([A-Z]*)([A-Z][^A-Z]+|$)")
for _, subStr := range pattern.FindAllStringSubmatch(str, -1) {
if subStr[0] == "URLs" {
formattedStr = append(formattedStr, subStr[0])
continue
}
if subStr[1] != "" {
formattedStr = append(formattedStr, subStr[1])
}
if subStr[2] != "" {
formattedStr = append(formattedStr, subStr[2])
}
}
return strings.Join(formattedStr, " ")
}

if displayName == "" {
fieldKey := strings.Split(key, ".")
displayName = format(fieldKey[len(fieldKey)-1])
}

return displayName
}
42 changes: 42 additions & 0 deletions devtools/mocktwilio/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -47,6 +48,7 @@ type Server struct {

messages map[string]*SMS
calls map[string]*VoiceCall
msgSvc map[string][]string

mux *http.ServeMux

Expand All @@ -71,6 +73,7 @@ func NewServer(cfg Config) *Server {
mux: http.NewServeMux(),
messages: make(map[string]*SMS),
calls: make(map[string]*VoiceCall),
msgSvc: make(map[string][]string),
smsCh: make(chan *SMS),
smsInCh: make(chan *SMS),
callCh: make(chan *VoiceCall),
Expand Down Expand Up @@ -196,6 +199,45 @@ func (s *Server) SetCarrierInfo(number string, info twilio.CarrierInfo) {
s.carrierInfo[number] = info
}

// getFromNumber will return a random number from the messaging service if ID is a
// messaging SID, or the value itself otherwise.
func (s *Server) getFromNumber(id string) string {
if !strings.HasPrefix(id, "MG") {
return id
}

s.mx.Lock()
defer s.mx.Unlock()

// select a random number from the message service
if len(s.msgSvc[id]) == 0 {
return ""
}

return s.msgSvc[id][rand.Intn(len(s.msgSvc[id]))]
}

// NewMessagingService registers a new Messaging SID for the given numbers.
func (s *Server) NewMessagingService(smsURL, voiceURL string, numbers ...string) (string, error) {
err := validate.Many(
validate.URL("SMS URL", smsURL),
validate.URL("Voice URL", voiceURL),
)
if err != nil {
return "", err
}
svcID := s.id("MG")

s.mx.Lock()
defer s.mx.Unlock()
for _, num := range numbers {
s.callbacks["SMS:"+num] = smsURL
s.callbacks["VOICE:"+num] = voiceURL
}

return svcID, nil
}

// RegisterSMSCallback will set/update a callback URL for SMS calls made to the given number.
func (s *Server) RegisterSMSCallback(number, url string) error {
err := validate.URL("URL", url)
Expand Down
7 changes: 4 additions & 3 deletions devtools/mocktwilio/sms.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type SMS struct {
doneCh chan struct{}
}

func (s *Server) sendSMS(from, to, body, statusURL, destURL string) (*SMS, error) {
func (s *Server) sendSMS(fromValue, to, body, statusURL, destURL string) (*SMS, error) {
fromNumber := s.getFromNumber(fromValue)
if statusURL != "" {
err := validate.URL("StatusCallback", statusURL)
if err != nil {
Expand All @@ -39,7 +40,7 @@ func (s *Server) sendSMS(from, to, body, statusURL, destURL string) (*SMS, error
}
}
s.mx.RLock()
_, hasCallback := s.callbacks["SMS:"+from]
_, hasCallback := s.callbacks["SMS:"+fromNumber]
s.mx.RUnlock()

if !hasCallback {
Expand All @@ -63,7 +64,7 @@ func (s *Server) sendSMS(from, to, body, statusURL, destURL string) (*SMS, error
s: s,
msg: twilio.Message{
To: to,
From: from,
From: fromNumber,
Status: twilio.MessageStatusAccepted,
SID: s.id("SM"),
},
Expand Down
2 changes: 1 addition & 1 deletion devtools/mocktwilio/voicecall.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func (s *Server) serveNewCall(w http.ResponseWriter, req *http.Request) {
hangupCh: make(chan struct{}),
}

vc.call.From = req.FormValue("From")
vc.call.From = s.getFromNumber(req.FormValue("From"))
s.mx.RLock()
_, hasCallback := s.callbacks["VOICE:"+vc.call.From]
s.mx.RUnlock()
Expand Down
102 changes: 100 additions & 2 deletions graphql2/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading