apns

package module
v0.0.0-...-1678368 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2016 License: MIT Imports: 20 Imported by: 0

README

apns

Utilities for Apple Push Notification and Feedback Services.

GoDoc

Installation

go get github.com/anachronistic/apns

Documentation

Usage

Creating pns and payloads manually
package main

import (
  "fmt"
  apns "github.com/anachronistic/apns"
)

func main() {
  payload := apns.NewPayload()
  payload.Alert = "Hello, world!"
  payload.Badge = 42
  payload.Sound = "bingbong.aiff"

  pn := apns.NewPushNotification()
  pn.AddPayload(payload)

  alert, _ := pn.PayloadString()
  fmt.Println(alert)
}
Returns
{
  "aps": {
    "alert": "Hello, world!",
    "badge": 42,
    "sound": "bingbong.aiff"
  }
}
Using an alert dictionary for complex payloads
package main

import (
  "fmt"
  apns "github.com/anachronistic/apns"
)

func main() {
  args := make([]string, 1)
  args[0] = "localized args"

  dict := apns.NewAlertDictionary()
  dict.Body = "Alice wants Bob to join in the fun!"
  dict.ActionLocKey = "Play a Game!"
  dict.LocKey = "localized key"
  dict.LocArgs = args
  dict.LaunchImage = "image.jpg"

  payload := apns.NewPayload()
  payload.Alert = dict
  payload.Badge = 42
  payload.Sound = "bingbong.aiff"

  pn := apns.NewPushNotification()
  pn.AddPayload(payload)

  alert, _ := pn.PayloadString()
  fmt.Println(alert)
}
Returns
{
  "aps": {
    "alert": {
      "body": "Alice wants Bob to join in the fun!",
      "action-loc-key": "Play a Game!",
      "loc-key": "localized key",
      "loc-args": [
        "localized args"
      ],
      "launch-image": "image.jpg"
    },
    "badge": 42,
    "sound": "bingbong.aiff"
  }
}
Setting custom properties
package main

import (
  "fmt"
  apns "github.com/anachronistic/apns"
)

func main() {
  payload := apns.NewPayload()
  payload.Alert = "Hello, world!"
  payload.Badge = 42
  payload.Sound = "bingbong.aiff"

  pn := apns.NewPushNotification()
  pn.AddPayload(payload)

  pn.Set("foo", "bar")
  pn.Set("doctor", "who?")
  pn.Set("the_ultimate_answer", 42)

  alert, _ := pn.PayloadString()
  fmt.Println(alert)
}
Returns
{
  "aps": {
    "alert": "Hello, world!",
    "badge": 42,
    "sound": "bingbong.aiff"
  },
  "doctor": "who?",
  "foo": "bar",
  "the_ultimate_answer": 42
}
Sending a notification
package main

import (
  "fmt"
  apns "github.com/anachronistic/apns"
)

func main() {
  payload := apns.NewPayload()
  payload.Alert = "Hello, world!"
  payload.Badge = 42
  payload.Sound = "bingbong.aiff"

  pn := apns.NewPushNotification()
  pn.DeviceToken = "YOUR_DEVICE_TOKEN_HERE"
  pn.AddPayload(payload)

  client := apns.NewClient("gateway.sandbox.push.apple.com:2195", "YOUR_CERT_PEM", "YOUR_KEY_NOENC_PEM")
  resp := client.Send(pn)

  alert, _ := pn.PayloadString()
  fmt.Println("  Alert:", alert)
  fmt.Println("Success:", resp.Success)
  fmt.Println("  Error:", resp.Error)
}
Returns
  Alert: {"aps":{"alert":"Hello, world!","badge":42,"sound":"bingbong.aiff"}}
Success: true
  Error: <nil>
Checking the feedback service
package main

import (
  "fmt"
  apns "github.com/anachronistic/apns"
  "os"
)

func main() {
  fmt.Println("- connecting to check for deactivated tokens (maximum read timeout =", apns.FeedbackTimeoutSeconds, "seconds)")

  client := apns.NewClient("feedback.sandbox.push.apple.com:2196", "YOUR_CERT_PEM", "YOUR_KEY_NOENC_PEM")
  go client.ListenForFeedback()

  for {
    select {
    case resp := <-apns.FeedbackChannel:
      fmt.Println("- recv'd:", resp.DeviceToken)
    case <-apns.ShutdownChannel:
      fmt.Println("- nothing returned from the feedback service")
      os.Exit(1)
    }
  }
}
Returns
- connecting to check for deactivated tokens (maximum read timeout = 5 seconds)
- nothing returned from the feedback service
exit status 1

Your output will differ if the service returns device tokens.

- recv'd: DEVICE_TOKEN_HERE
...etc.

Documentation

Index

Constants

View Source
const (
	// CodeSuccess represents that everything's ok
	CodeSuccess statusCode = 200

	// CodeBadRequest represents that the request was not completeled
	// successfully
	CodeBadRequest statusCode = 400

	// CodeCertificateError represents an error while validating the certificate
	// used to communicate with the service.
	CodeCertificateError statusCode = 403

	// CodeBadMethod represents an error for using the wrong method, in this
	// case it means something other than POST was used.
	CodeBadMethod statusCode = 405

	// CodeInactiveDeviceToken represents a code that indicates that the device
	// toekn should no longer be used.
	CodeInactiveDeviceToken statusCode = 410

	// CodePayloadTooLarge represents an error that states the Payload was too
	// big to be used.
	CodePayloadTooLarge statusCode = 413

	// CodeTooManyRequestsForDeviceToken represents an error that indicates that
	// too many push requests have been sent to the same device within too short
	// of a time frame.
	CodeTooManyRequestsForDeviceToken statusCode = 429

	// CodeInternalServerError represents an error that indicates an internal
	// server error has ocurred.
	CodeInternalServerError statusCode = 500

	// CodeServerShutdown represents a status that indicates the server will be
	// shutting down, and so the connection should be closed.
	CodeServerShutdown statusCode = 503
)
View Source
const FeedbackTimeoutSeconds = 5

FeedbackTimeoutSeconds specifies how long to wait for a response from Apple's APNs service before returning a response. This is done as Apple will not send a response on their Binary Provider API if everything is successful. Ultimately this means that a response is not guaranteed.

View Source
const IdentifierUbound = 9999

IdentifierUbound represents the upper bound, or an error identifier.

Every push notification gets a pseudo-unique identifier; this establishes the upper boundary for it. Apple will return this identifier if there is an issue sending your notification.

View Source
const MaxPayloadSizeBytes = 2048

MaxPayloadSizeBytes represents the maximum size of a payload, up to 2KB

View Source
const TimeoutSeconds = 5

TimeoutSeconds is the maximum number of seconds we're willing to wait for a response from the Apple Push Notification Service.

Variables

View Source
var (
	APPLE_PUSH_RESPONSES     = ApplePushResponses
	FEEDBACK_TIMEOUT_SECONDS = FeedbackTimeoutSeconds
	IDENTIFIER_UBOUND        = IdentifierUbound
	MAX_PAYLOAD_SIZE_BYTES   = MaxPayloadSizeBytes
	TIMEOUT_SECONDS          = TimeoutSeconds
)

These variables map old identifiers to their current format.

View Source
var ApplePushResponses = map[AppleResponseCode]string{
	RespNoError:            "No errors encountered",
	RespProcessingError:    "An error occurred with processing",
	RespMissingDeviceToken: "The push notification was missing the device token, unable to deliver",
	RespMissingTopic:       "The push notification was missing the topic",
	RespMissingPayload:     "The push notification was missing the payload, nothing to send",
	RespInvalidTokenSize:   "The token was not a valid size for delivery",
	RespInvalidTopicSize:   "The topic was not a valid size for delivery",
	RespInvalidPayloadSize: "The payload was not a valid size for delivery, likely too large",
	RespInvalidToken:       "the token provided was invalid, should be removed",
	RespShutdown:           "The server shutdown the connection.  This most-likely happend for maintenance",
	RespUnknown:            "An unknown error occurred",
}

ApplePushResponses represents bindings of the AppleResponseCode to various messages to use for an error.

This enumerates the response codes that Apple defines for push notification attempts.

View Source
var DebugAPN = false

DebugAPN is a flag that determins whether or not logging should be output, or not

View Source
var ErrMaxRetriesExceeded = errors.New("Tried to send the push, and failed beyond the maximum retry count")

ErrMaxRetriesExceeded represents an error that states that the push was attempted to be sent, and was unsuccessful up to the alloted retry count

View Source
var FeedbackChannel = make(chan (*FeedbackResponse))

FeedbackChannel will receive individual responses from Apple.

View Source
var ShutdownChannel = make(chan bool)

ShutdownChannel is a signal channel the informs us there's nothing to read. It will receive a true, if that is the case.

View Source
var UseHTTP2API = true

UseHTTP2API informs BareClient and NewClient which communication API to use

Functions

func StartMockFeedbackServer

func StartMockFeedbackServer(certFile, keyFile string)

StartMockFeedbackServer spins up a simple stand-in for the Apple feedback service that can be used for testing purposes. Doesn't handle many errors, etc. Just for the sake of having something "live" to hit.

Types

type APIReason

type APIReason string

APIReason represents the potential reasons returned for an API Request result

const (
	// ReasonPayloadEmpty represents a reason for error due to a payload empty
	ReasonPayloadEmpty APIReason = "PayloadEmpty"

	// ReasonPayloadTooLarge represents a resason that the payload exceeded the
	// maximum size of 4096 bytes
	ReasonPayloadTooLarge APIReason = "PayloadTooLarge"

	// ReasonBadTopic represents a resason that the topic was either not
	// specified
	ReasonBadTopic APIReason = "BadTopic"

	//ReasonTopicDisallowed represents a reason that the specified topic is not
	//allowed
	ReasonTopicDisallowed APIReason = "TopicDisallowed"

	// ReasonBadMessageID represents a reason that the message id specified is
	// bad
	ReasonBadMessageID APIReason = "BadMessageId"

	// ReasonBadExpirationDate represents a reason that the specified expiration
	// date is not a good one.
	ReasonBadExpirationDate APIReason = "BadExpirationDate"

	// ReasonBadPriority represents a reason that specifies that the priority
	// specified was not valid given the rest of the notification, or just an
	// invalid number
	ReasonBadPriority APIReason = "BadPriority"

	// ReasonMissingDeviceToken represents a reason that specifies that the
	// device token was not specified, and therefore no push could be delivered
	ReasonMissingDeviceToken APIReason = "MissingDeviceToken"

	// ReasonBadDeviceToken represents a reason that specifies that the device
	// Toekn is not a valid one.
	ReasonBadDeviceToken APIReason = "BadDeviceToken"

	// ReasonDeviceTokenNotForTopic represents a reason that specifies that the
	// specified device token hasn't subscribed for push subscriptions for the
	// given topic.
	ReasonDeviceTokenNotForTopic APIReason = "DeviceTokenNotForTopic"

	// ReasonUnregistered represetns a reason that specifies that the device
	// token has unregistered for notifications for the given topic.
	ReasonUnregistered APIReason = "Unregistered"

	// ReasonDuplicateHeaders represents a reason that specifies that one or
	// more headers were repeated.
	ReasonDuplicateHeaders APIReason = "DuplicateHeaders"

	// ReasonBadCertificateEnvironment represents a reason that indicates that
	// the certificate supplied does not match the request environment.
	ReasonBadCertificateEnvironment APIReason = "BadCertificateEnvironment"

	// ReasonBadCertificate represents a reason that the certificate specified
	// is not a valid certificate.
	ReasonBadCertificate APIReason = "BadCertificate"

	// ReasonForbidden represents a reason that specifies that the action
	// requested is not valid.
	ReasonForbidden APIReason = "Forbidden"

	// ReasonBadPath represents a reason that specifies that the path specified
	// is not a good one.
	ReasonBadPath APIReason = "BadPath"

	// ReasonMethodNotAllowed represents a reason that specifies that the
	// request method is not allowed.  The only one allowed, currently is POST.
	ReasonMethodNotAllowed APIReason = "MethodNotAllowed"

	// ReasonTooManyRequests represents a reason that indicates that too many
	// requests have been made to the same device token.
	ReasonTooManyRequests APIReason = "TooManyRequests"

	// ReasonIdleTimeout represents a reason that the given connection has timed
	// out after being idle for too long.
	ReasonIdleTimeout APIReason = "IdleTimeout"

	// ReasonShutdown represents a reason that the APN service is shutting down
	// and the connections are to be terminated.
	ReasonShutdown APIReason = "Shutdown"

	// ReasonInternalServerError represents a reason that indicates that the
	// APN service encountered an internal error while processing your request.
	ReasonInternalServerError APIReason = "InternalServerError"

	// ReasonServiceUnavailable represents a reason that indicates that the
	// APN Service is unavailable.
	ReasonServiceUnavailable APIReason = "ServiceUnavailable"

	// ReasonMissingTopic represents a reason that indicates that the topic is
	// missing from the Notification, and cannot be inferred from the
	// certificate.
	ReasonMissingTopic APIReason = "MissingTopic"
)

func (APIReason) Error

func (a APIReason) Error() string

type APIResponse

type APIResponse struct {

	// Reason represents the reason, this will be populated with the reason for
	// the failure
	Reason APIReason `json:"reason"`

	// Timestamp is used for status code 410, and indicates the last time the
	// specified device token was valid for the topic.  This is a unix timestamp
	// represented in milli seconds
	Timestamp int64 `json:"timestamp,omitempty"`
}

APIResponse represents a potential resposne from the APN services's HTTP2 APNs Provider API

func (APIResponse) String

func (r APIResponse) String() string

func (APIResponse) ToTime

func (r APIResponse) ToTime() time.Time

ToTime will take the underlying timestamp and convert it to to a timestamp, in Unix time since Epoc 0.

type AlertDictionary

type AlertDictionary struct {
	Title        string   `json:"title,omitempty"`
	Body         string   `json:"body,omitempty"`
	TitleLocKey  string   `json:"title-loc-key,omitempty"`
	TitleLocArgs []string `json:"title-loc-args,omitempty"`
	ActionLocKey string   `json:"action-loc-key,omitempty"`
	LocKey       string   `json:"loc-key,omitempty"`
	LocArgs      []string `json:"loc-args,omitempty"`
	LaunchImage  string   `json:"launch-image,omitempty"`
}

AlertDictionary is a more complex notification payload.

From the APN docs: "Use the ... alert dictionary in general only if you absolutely need to." The AlertDictionary is suitable for specific localization needs.

func NewAlertDictionary

func NewAlertDictionary() *AlertDictionary

NewAlertDictionary creates and returns an AlertDictionary structure.

type AppleResponseCode

type AppleResponseCode byte

AppleResponseCode represents the various different response codes that can be returned by Apple's APN service Binary Provider API.

const (
	// RespNoError represents a non-error
	RespNoError AppleResponseCode = iota

	// RespProcessingError represents an error in processing the request. This
	// is likely an internal error on Apple's APN service part.
	RespProcessingError

	// RespMissingDeviceToken represents an error that indicates that no device
	// token was specified.
	RespMissingDeviceToken

	// RespMissingTopic represents an error that indicates that no topic was
	// specified within the push request.
	RespMissingTopic

	// RespMissingPayload represents an error that indicates that no payload
	// was contained within the push request.  In general this means there was
	// not anything to be pushed to the device.
	RespMissingPayload

	// RespInvalidTokenSize represents an error that indicates a token had an
	// invalid length
	RespInvalidTokenSize

	// RespInvalidTopicSize represents an error that indicates that a topic had
	// an invalid length
	RespInvalidTopicSize

	// RespInvalidPayloadSize represents an erro that indicates that a palyload
	// had an invalid length
	RespInvalidPayloadSize

	// RespInvalidToken represents an error that indicates that the specified
	// token was no valid for a push subscription.  If encountered, you should
	// remove the push subscription token from use.
	RespInvalidToken

	// RespShutdown represents an error that indicates the Apple's APN service
	// has closed this connection, likely used for maintenance.  This does not
	// neccessarily indicate an error with the push itself.
	RespShutdown AppleResponseCode = 10

	// RespUnknown represents an error that indicates an unknown cause.
	RespUnknown AppleResponseCode = 255
)

The following constants represent response codes potentially returned by Apples APNs Binary Provider API.

func (AppleResponseCode) Error

func (arc AppleResponseCode) Error() string

type Client

type Client interface {

	// Send will initiate a send of the specified Push Notification, and return
	// a response
	Send(pn *PushNotification) (resp *PushNotificationResponse)
}

Client is an APNS client.

func BareClient

func BareClient(gateway, certificateBase64, keyBase64 string) (c Client, err error)

BareClient will create a new Client that will use the base64 certificate and key to conduct it's transations

func BareGatewayClient

func BareGatewayClient(gateway, certificateBase64, keyBase64 string) (c Client)

BareGatewayClient can be used to set the contents of your certificate and key blocks manually.

func BareHTTP2Client

func BareHTTP2Client(gateway, certificateBase64, keyBase64 string) (c Client, err error)

BareHTTP2Client can be used to set the contents of your certificate and key blocks manually.

func NewClient

func NewClient(gateway, certificateFile, keyFile string) (c Client, err error)

NewClient will create a new Client that will use the certificate and key at the specifies for it's transactions

func NewGatewayClient

func NewGatewayClient(gateway, certificateFile, keyFile string) (c Client, err error)

NewGatewayClient assumes you'll be passing in paths that point to your certificate and key.

func NewHTTP2Client

func NewHTTP2Client(gateway, certificateFile, keyFile string) (c Client, err error)

NewHTTP2Client assumes you'll be passing in paths that point to your certificate and key.

type FeedbackResponse

type FeedbackResponse struct {
	Timestamp   uint32
	DeviceToken string
}

FeedbackResponse represents a device token that Apple has indicated should not be sent to in the future.

func NewFeedbackResponse

func NewFeedbackResponse() (resp *FeedbackResponse)

NewFeedbackResponse creates and returns a FeedbackResponse structure.

type GatewayClient

type GatewayClient struct {
	// contains filtered or unexported fields
}

GatewayClient 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.

func (*GatewayClient) ListenForFeedback

func (client *GatewayClient) ListenForFeedback() (err error)

ListenForFeedback connects to the Apple Feedback Service and checks for device tokens.

Feedback consists of device tokens that should not be sent to in the future; Apple *does* monitor that you respect this so you should be checking it ;)

func (*GatewayClient) Send

func (client *GatewayClient) Send(pn *PushNotification) (resp *PushNotificationResponse)

Send connects to the APN service and sends your push notification. Remember that if the submission is successful, Apple won't reply.

func (*GatewayClient) SetMaxConns

func (client *GatewayClient) SetMaxConns(m int)

SetMaxConns will set the maximum number of connections usable within the connection pool

func (*GatewayClient) SetMaxIdleConns

func (client *GatewayClient) SetMaxIdleConns(m int)

SetMaxIdleConns will set the maximum number of Idle Connections to remain open at a given time

type HTTP2Client

type HTTP2Client struct {
	// contains filtered or unexported fields
}

HTTP2Client 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.

func (*HTTP2Client) Send

func (client *HTTP2Client) Send(pn *PushNotification) (resp *PushNotificationResponse)

Send implements Client

type MockClient

type MockClient struct {
	mock.Mock
}

MockClient implements Client

func (*MockClient) Send

Send implements Client

func (*MockClient) SetMaxConns

func (*MockClient) SetMaxConns(m int)

SetMaxConns implements Client

func (*MockClient) SetMaxIdleConns

func (*MockClient) SetMaxIdleConns(m int)

SetMaxIdleConns implements Client

type Payload

type Payload struct {
	Alert            interface{} `json:"alert,omitempty"`
	Badge            int         `json:"badge,omitempty"`
	Sound            string      `json:"sound,omitempty"`
	ContentAvailable int         `json:"content-available,omitempty"`
	Category         string      `json:"category,omitempty"`
}

Payload contains the notification data for your request.

Alert is an interface here because it supports either a string or a dictionary, represented within by an AlertDictionary struct.

func NewPayload

func NewPayload() *Payload

NewPayload creates and returns a Payload structure.

type PushNotification

type PushNotification struct {
	UUID        string
	Identifier  int32
	Expiry      uint32
	DeviceToken string

	Priority uint8
	// contains filtered or unexported fields
}

PushNotification is the wrapper for the Payload. The length fields are computed in ToBytes() and aren't represented here.

func NewPushNotification

func NewPushNotification() (pn *PushNotification)

NewPushNotification creates and returns a PushNotification structure. It also initializes the pseudo-random identifier.

func (*PushNotification) AddPayload

func (pn *PushNotification) AddPayload(p *Payload)

AddPayload sets the "aps" payload section of the request. It also has a hack described within to deal with specific zero values.

func (*PushNotification) Get

func (pn *PushNotification) Get(key string) interface{}

Get returns the value of a payload key, if it exists.

func (*PushNotification) PayloadJSON

func (pn *PushNotification) PayloadJSON() ([]byte, error)

PayloadJSON returns the current payload in JSON format.

func (*PushNotification) PayloadString

func (pn *PushNotification) PayloadString() (string, error)

PayloadString returns the current payload in string format.

func (*PushNotification) Set

func (pn *PushNotification) Set(key string, value interface{})

Set defines the value of a payload key.

func (*PushNotification) ToBytes

func (pn *PushNotification) ToBytes() ([]byte, error)

ToBytes returns a byte array of the complete PushNotification struct. This array is what should be transmitted to the APN Service.

type PushNotificationResponse

type PushNotificationResponse struct {
	Success       bool
	AppleResponse AppleResponseCode
	Error         error
}

PushNotificationResponse details what Apple had to say, if anything.

func NewPushNotificationResponse

func NewPushNotificationResponse() (resp *PushNotificationResponse)

NewPushNotificationResponse creates and returns a new PushNotificationResponse structure; it defaults to being unsuccessful at first.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL