package tmobile import ( "context" "time" "fiskerinc.com/modules/grpc/sms" "fiskerinc.com/modules/kafka" "fiskerinc.com/modules/logger" "github.com/pkg/errors" ) // I want to move the check sms loop into a less thread intensive action // This is done in the following way // Have a single/limit a couple look at all the messages we need to see if they are successfully sent // If they are successfully sent then we notify something else that will properly return the status // Currently the return is going to be the hardest part // Wrapper wraps the client with an auto-refresh and auth-token retry. type SMSClientWrapper interface { Start(ctx context.Context) SendSMS(ctx context.Context, req *SendSMSRequest) (out *SMSDetailsResponse, err error) SendSMSQueue(ctx context.Context, req *SendSMSRequest) (out *SMSQueueResponse, err error) HandleChangeRatePlan(context.Context, *ChangeRatePlanRequest) (*ChangeRatePlanResponse, error) HandleCustomAttributes(context.Context, *CustomAtributesRequest) (*CustomAtributesResponse, error) HandleGetProducts(context.Context, *sms.GetAvailableProductsRequest) (*sms.GetAvailableProductsResponse, error) HandleDeviceDetails(context.Context, *DeviceDetailsRequest) (*DeviceDetailsResponse, error) HandleChangeDeviceStatus(ctx context.Context, cda ChangeDeviceActivation) (err error) } type SMSClient struct { client TMobClienter queue *RunningQueue } func NewSMSClient(client TMobClienter, kafkaProducer kafka.ProducerInterface) *SMSClient { nc := &SMSClient{ client: client, } nc.queue = NewQueue(client, kafkaProducer) return nc } func (w *SMSClient) Start(ctx context.Context) { } // If the SMS is not delivered, out will be null, and this will need to be checked func (w *SMSClient) SendSMS(ctx context.Context, req *SendSMSRequest) (out *SMSDetailsResponse, err error) { logger.Debug().Msgf("Sending SMS: %+v", req) var smr *SendSMSResponse // Giving 3 tries for a gateway failure for x := 0; x < 3; x++ { smr, err = w.client.SendSMS(ctx, req) if err != nil { if errors.Is(err, ErrBadGatewayCode) { logger.Warn().Err(err).Msgf("ICCID %s gateway failure sending sms", req.ICCID) time.Sleep(time.Millisecond * 300) // 300 milliseconds seems to work best for me continue } if errors.Is(err, ErrAccessTokenExpired) { logger.Err(err).Msgf("Access token for sms expired, should never happen") } return nil, errors.WithMessagef(err, "failed to send SMS to ICCID: %s", req.ICCID) } break } if smr == nil { return nil, errors.WithMessagef(err, "failed to send SMS to ICCID: %s", req.ICCID) } expDur := time.Second * 5 logger.Debug().Msgf("SMS sent, expires in %d ms, id: %s currently %d", expDur.Milliseconds(), smr.SmsMessageID, time.Now().UnixMilli()) waitTill := time.After(expDur) checkTimer := time.NewTicker(time.Millisecond * 300) for { select { case <-waitTill: logger.Debug().Msgf("SMS with id %s not delivered after %d ms currently %d", smr.SmsMessageID, expDur.Milliseconds(), time.Now().UnixMilli()) return nil, ErrTimeoutSendingMessage // Lots of messages seem to hit here case <-checkTimer.C: // Doing the wait before the first check, increases the chance of a first check success out, err = w.client.Details(ctx, smr.SmsMessageID) if err != nil { // Ignoring and retrying a bad gateway request if errors.Is(err, ErrBadGatewayCode) { logger.Warn().Err(err).Msgf("Bad gateway request from t-mobileICCID %s, Message ID: %s", req.ICCID, smr.SmsMessageID) continue } // If we get this error, t-mobile has rejected our details request logger.Err(err).Msgf("failed to get sms details with smsMsgID: %s to ICCID: %s", smr.SmsMessageID, req.ICCID) return nil, errors.WithMessagef(err, "failed to get sms details with smsMsgID: %s to ICCID: %s", smr.SmsMessageID, req.ICCID) } if !req.await { return out, nil } switch out.Status { case Pending, CancelPending: logger.Debug().Msgf("MSG %s had Pending Status", smr.SmsMessageID) continue case Delivered: return out, nil default: logger.Warn().Msgf("Fell through to default of sms status id: %s status: %s", smr.SmsMessageID, out.Status) return out, errors.WithMessagef( ErrBadMsgStatus, "message with id %s failed with status %s", out.SmsMsgID, out.Status, ) } } } } func (w *SMSClient) SendSMSQueue(ctx context.Context, req *SendSMSRequest) (out *SMSQueueResponse, err error) { msgID, err := w.queue.SendSMS(ctx, req) out = &SMSQueueResponse{} out.SmsMsgID = msgID out.SentSuccessful = err == nil return } func (w *SMSClient) HandleChangeRatePlan(ctx context.Context, req *ChangeRatePlanRequest) (*ChangeRatePlanResponse, error) { return w.client.ChangeRatePlan(ctx, req) } func (w *SMSClient) HandleCustomAttributes(ctx context.Context, req *CustomAtributesRequest) (*CustomAtributesResponse, error) { return w.client.CustomAttributes(ctx, req) } func (w *SMSClient) HandleGetProducts(ctx context.Context, req *sms.GetAvailableProductsRequest) (*sms.GetAvailableProductsResponse, error) { return w.client.GetProducts(ctx, req) } func (w *SMSClient) HandleDeviceDetails(ctx context.Context, req *DeviceDetailsRequest) (*DeviceDetailsResponse, error) { return w.client.DeviceDetails(ctx, req) } func (w *SMSClient) HandleChangeDeviceStatus(ctx context.Context, cda ChangeDeviceActivation) (err error) { return w.client.ChangeDeviceStatus(ctx, cda) }