diff options
Diffstat (limited to 'Godeps/_workspace/src/github.com/anachronistic/apns/client.go')
-rw-r--r-- | Godeps/_workspace/src/github.com/anachronistic/apns/client.go | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/anachronistic/apns/client.go b/Godeps/_workspace/src/github.com/anachronistic/apns/client.go new file mode 100644 index 000000000..3fc079a87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/anachronistic/apns/client.go @@ -0,0 +1,167 @@ +package apns + +import ( + "crypto/tls" + "errors" + "net" + "strings" + "time" +) + +var _ APNSClient = &Client{} + +// APNSClient is an APNS client. +type APNSClient interface { + ConnectAndWrite(resp *PushNotificationResponse, payload []byte) (err error) + Send(pn *PushNotification) (resp *PushNotificationResponse) +} + +// Client contains the fields necessary to communicate +// with Apple, such as the gateway to use and your +// certificate contents. +// +// You'll need to provide your own CertificateFile +// and KeyFile to send notifications. Ideally, you'll +// just set the CertificateFile and KeyFile fields to +// a location on drive where the certs can be loaded, +// but if you prefer you can use the CertificateBase64 +// and KeyBase64 fields to store the actual contents. +type Client struct { + Gateway string + CertificateFile string + CertificateBase64 string + KeyFile string + KeyBase64 string +} + +// BareClient can be used to set the contents of your +// certificate and key blocks manually. +func BareClient(gateway, certificateBase64, keyBase64 string) (c *Client) { + c = new(Client) + c.Gateway = gateway + c.CertificateBase64 = certificateBase64 + c.KeyBase64 = keyBase64 + return +} + +// NewClient assumes you'll be passing in paths that +// point to your certificate and key. +func NewClient(gateway, certificateFile, keyFile string) (c *Client) { + c = new(Client) + c.Gateway = gateway + c.CertificateFile = certificateFile + c.KeyFile = keyFile + return +} + +// Send connects to the APN service and sends your push notification. +// Remember that if the submission is successful, Apple won't reply. +func (client *Client) Send(pn *PushNotification) (resp *PushNotificationResponse) { + resp = new(PushNotificationResponse) + + payload, err := pn.ToBytes() + if err != nil { + resp.Success = false + resp.Error = err + return + } + + err = client.ConnectAndWrite(resp, payload) + if err != nil { + resp.Success = false + resp.Error = err + return + } + + resp.Success = true + resp.Error = nil + + return +} + +// ConnectAndWrite establishes the connection to Apple and handles the +// transmission of your push notification, as well as waiting for a reply. +// +// In lieu of a timeout (which would be available in Go 1.1) +// we use a timeout channel pattern instead. We start two goroutines, +// one of which just sleeps for TimeoutSeconds seconds, while the other +// waits for a response from the Apple servers. +// +// Whichever channel puts data on first is the "winner". As such, it's +// possible to get a false positive if Apple takes a long time to respond. +// It's probably not a deal-breaker, but something to be aware of. +func (client *Client) ConnectAndWrite(resp *PushNotificationResponse, payload []byte) (err error) { + var cert tls.Certificate + + if len(client.CertificateBase64) == 0 && len(client.KeyBase64) == 0 { + // The user did not specify raw block contents, so check the filesystem. + cert, err = tls.LoadX509KeyPair(client.CertificateFile, client.KeyFile) + } else { + // The user provided the raw block contents, so use that. + cert, err = tls.X509KeyPair([]byte(client.CertificateBase64), []byte(client.KeyBase64)) + } + + if err != nil { + return err + } + + gatewayParts := strings.Split(client.Gateway, ":") + conf := &tls.Config{ + Certificates: []tls.Certificate{cert}, + ServerName: gatewayParts[0], + } + + conn, err := net.Dial("tcp", client.Gateway) + if err != nil { + return err + } + defer conn.Close() + + tlsConn := tls.Client(conn, conf) + err = tlsConn.Handshake() + if err != nil { + return err + } + defer tlsConn.Close() + + _, err = tlsConn.Write(payload) + if err != nil { + return err + } + + // Create one channel that will serve to handle + // timeouts when the notification succeeds. + timeoutChannel := make(chan bool, 1) + go func() { + time.Sleep(time.Second * TimeoutSeconds) + timeoutChannel <- true + }() + + // This channel will contain the binary response + // from Apple in the event of a failure. + responseChannel := make(chan []byte, 1) + go func() { + buffer := make([]byte, 6, 6) + tlsConn.Read(buffer) + responseChannel <- buffer + }() + + // First one back wins! + // The data structure for an APN response is as follows: + // + // command -> 1 byte + // status -> 1 byte + // identifier -> 4 bytes + // + // The first byte will always be set to 8. + select { + case r := <-responseChannel: + resp.Success = false + resp.AppleResponse = ApplePushResponses[r[1]] + err = errors.New(resp.AppleResponse) + case <-timeoutChannel: + resp.Success = true + } + + return err +} |