summaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/anachronistic/apns/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/github.com/anachronistic/apns/client.go')
-rw-r--r--Godeps/_workspace/src/github.com/anachronistic/apns/client.go167
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
+}