copilot-swe-agent[bot] a708352634 fix: WebhookPing csv tags + JSON payload column; DeviceChecks struct; fix Authenticate deadlock
Co-authored-by: coolaj86 <122831+coolaj86@users.noreply.github.com>
2026-03-02 06:56:04 +00:00

216 lines
8.2 KiB
Go

package androidsmsgateway
import (
"encoding/json"
"errors"
"time"
)
// WebhookEvent is an interface for all webhook event types.
type WebhookEvent interface {
GetEvent() string
}
// Webhook represents a webhook notification for an SMS sent event.
type Webhook struct {
DeviceID string `json:"deviceId"`
Event string `json:"event"`
ID string `json:"id"`
Payload json.RawMessage `json:"payload"`
WebhookID string `json:"webhookId"`
XSignature string `json:"x-signature"`
XTimestamp int64 `json:"x-timestamp"`
}
// WebhookSent represents a webhook notification for an SMS sent event.
type WebhookSent struct {
DeviceID string `json:"deviceId" csv:"deviceId"`
Event string `json:"event" csv:"event"`
ID string `json:"id" csv:"id"`
Payload WebhookSentPayload `json:"payload" csv:",inline"`
WebhookID string `json:"webhookId" csv:"webhookId"`
XSignature string `json:"x-signature" csv:"-"`
XTimestamp int64 `json:"x-timestamp" csv:"-"`
}
// GetEvent marks WebhookSent as part of the WebhookEvent interface.
func (w *WebhookSent) GetEvent() string {
return w.Event
}
// WebhookSentPayload contains details about the sent SMS.
type WebhookSentPayload struct {
MessageID string `json:"messageId" csv:"messageId"`
PartsCount int `json:"partsCount" csv:"partsCount"`
PhoneNumber string `json:"phoneNumber" csv:"phoneNumber"`
SentAt time.Time `json:"sentAt" csv:"sentAt"`
}
// WebhookDelivered represents a webhook notification for an SMS delivered event.
type WebhookDelivered struct {
DeviceID string `json:"deviceId"`
Event string `json:"event"`
ID string `json:"id"`
Payload WebhookDeliveredPayload `json:"payload"`
WebhookID string `json:"webhookId"`
XSignature string `json:"x-signature"`
XTimestamp int64 `json:"x-timestamp"`
}
// GetEvent marks WebhookDelivered as part of the WebhookEvent interface.
func (w *WebhookDelivered) GetEvent() string {
return w.Event
}
// WebhookDeliveredPayload contains details about the delivered SMS.
type WebhookDeliveredPayload struct {
DeliveredAt time.Time `json:"deliveredAt"`
MessageID string `json:"messageId"`
PhoneNumber string `json:"phoneNumber"`
}
// WebhookReceived represents a webhook notification for an SMS received event.
type WebhookReceived struct {
DeviceID string `json:"deviceId" csv:"deviceId"`
Event string `json:"event" csv:"event"`
ID string `json:"id" csv:"id"`
Payload WebhookReceivedPayload `json:"payload" csv:",inline"`
WebhookID string `json:"webhookId" csv:"webhookId"`
XSignature string `json:"x-signature" csv:"-"`
XTimestamp int64 `json:"x-timestamp" csv:"-"`
}
// GetEvent marks WebhookDelivered as part of the WebhookEvent interface.
func (w *WebhookReceived) GetEvent() string {
return w.Event
}
// WebhookReceivedPayload contains details about the received SMS.
type WebhookReceivedPayload struct {
Message string `json:"message" csv:"message"`
MessageID string `json:"messageId" csv:"messageId"`
PhoneNumber string `json:"phoneNumber" csv:"phoneNumber"`
ReceivedAt time.Time `json:"receivedAt" csv:"receivedAt"`
SimNumber int `json:"simNumber" csv:"simNumber"`
}
// WebhookPing represents a system:ping webhook event.
type WebhookPing struct {
DeviceID string `json:"deviceId" csv:"deviceId"`
Event string `json:"event" csv:"event"`
ID string `json:"id" csv:"id"`
Payload WebhookPingPayload `json:"payload" csv:"payload"`
WebhookID string `json:"webhookId" csv:"webhookId"`
PingedAt time.Time `json:"pingedAt,omitempty" csv:"pingedAt"`
XSignature string `json:"x-signature" csv:"-"`
XTimestamp int64 `json:"x-timestamp" csv:"-"`
}
// GetEvent marks WebhookPing as part of the WebhookEvent interface.
func (w *WebhookPing) GetEvent() string {
return w.Event
}
// WebhookPingPayload contains the health data reported by a system:ping event.
// MarshalText serialises the payload as a compact JSON string so that csvutil
// stores it as a single "payload" column rather than expanding every health check.
type WebhookPingPayload struct {
Health DeviceHealth `json:"health"`
}
// MarshalText implements encoding.TextMarshaler for CSV serialisation.
func (p WebhookPingPayload) MarshalText() ([]byte, error) {
return json.Marshal(p)
}
// MarshalJSON prevents MarshalText from being used during JSON serialisation,
// ensuring WebhookPingPayload is always encoded as a full JSON object.
func (p WebhookPingPayload) MarshalJSON() ([]byte, error) {
type alias WebhookPingPayload
return json.Marshal(alias(p))
}
// DeviceHealth is the top-level health object inside a system:ping payload.
// Named fields use colon json tags matching the API key names inside "checks".
type DeviceHealth struct {
Checks DeviceChecks `json:"checks"`
ReleaseID int `json:"releaseId"`
Status string `json:"status"`
Version string `json:"version"`
}
// DeviceChecks holds the individual health checks reported by the device.
// Go field names are camelCase; json tags carry the colon-delimited API key names.
type DeviceChecks struct {
BatteryCharging HealthCheck `json:"battery:charging"`
BatteryLevel HealthCheck `json:"battery:level"`
ConnectionCellular HealthCheck `json:"connection:cellular"`
ConnectionStatus HealthCheck `json:"connection:status"`
ConnectionTransport HealthCheck `json:"connection:transport"`
MessagesFailed HealthCheck `json:"messages:failed"`
}
// HealthCheck represents a single named health check result.
type HealthCheck struct {
Description string `json:"description"`
ObservedUnit string `json:"observedUnit"`
ObservedValue float64 `json:"observedValue"`
Status string `json:"status"`
}
// Decode decodes the raw Payload based on the Event field and returns the appropriate WebhookEvent.
func Decode(webhook *Webhook) (WebhookEvent, error) {
switch webhook.Event {
case "system:ping":
var ping WebhookPing
ping.DeviceID = webhook.DeviceID
ping.Event = webhook.Event
ping.ID = webhook.ID
ping.WebhookID = webhook.WebhookID
ping.XSignature = webhook.XSignature
ping.XTimestamp = webhook.XTimestamp
if err := json.Unmarshal(webhook.Payload, &ping.Payload); err != nil {
return nil, errors.New("failed to decode system:ping payload: " + err.Error())
}
return &ping, nil
case "sms:sent":
var sent WebhookSent
sent.DeviceID = webhook.DeviceID
sent.Event = webhook.Event
sent.ID = webhook.ID
sent.WebhookID = webhook.WebhookID
sent.XSignature = webhook.XSignature
sent.XTimestamp = webhook.XTimestamp
if err := json.Unmarshal(webhook.Payload, &sent.Payload); err != nil {
return nil, errors.New("failed to decode sms:sent payload: " + err.Error())
}
return &sent, nil
case "sms:delivered":
var delivered WebhookDelivered
delivered.DeviceID = webhook.DeviceID
delivered.Event = webhook.Event
delivered.ID = webhook.ID
delivered.WebhookID = webhook.WebhookID
delivered.XSignature = webhook.XSignature
delivered.XTimestamp = webhook.XTimestamp
if err := json.Unmarshal(webhook.Payload, &delivered.Payload); err != nil {
return nil, errors.New("failed to decode sms:delivered payload: " + err.Error())
}
return &delivered, nil
case "sms:received":
var received WebhookReceived
received.DeviceID = webhook.DeviceID
received.Event = webhook.Event
received.ID = webhook.ID
received.WebhookID = webhook.WebhookID
received.XSignature = webhook.XSignature
received.XTimestamp = webhook.XTimestamp
if err := json.Unmarshal(webhook.Payload, &received.Payload); err != nil {
return nil, errors.New("failed to decode sms:received payload: " + err.Error())
}
return &received, nil
default:
return nil, errors.New("unknown event type: " + webhook.Event)
}
}