apns

package module
Version: v0.0.0-...-cd33876 Latest Latest
Warning

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

Go to latest
Published: Feb 5, 2020 License: MIT Imports: 18 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 FeedbackTimeoutSeconds = 5

Wait at most this many seconds for feedback data from Apple.

View Source
const IdentifierUbound = 9999

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

Your total notification payload cannot exceed 2 KB.

View Source
const TimeoutSeconds = 5

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[uint8]string{
	0:   "NO_ERRORS",
	1:   "PROCESSING_ERROR",
	2:   "MISSING_DEVICE_TOKEN",
	3:   "MISSING_TOPIC",
	4:   "MISSING_PAYLOAD",
	5:   "INVALID_TOKEN_SIZE",
	6:   "INVALID_TOPIC_SIZE",
	7:   "INVALID_PAYLOAD_SIZE",
	8:   "INVALID_TOKEN",
	10:  "SHUTDOWN",
	255: "UNKNOWN",
}

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

View Source
var Direct = direct{}

Direct used when proxy is used to connect to APNS server

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

FeedbackChannel will receive individual responses from Apple.

View Source
var ShutdownChannel = make(chan bool)

If there's nothing to read, ShutdownChannel gets a true.

Functions

func FromEnvironment

func FromEnvironment() proxy.Dialer

func FromURL

func FromURL(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error)

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 APNSClient

type APNSClient interface {
	ConnectAndWrite(resp *PushNotificationResponse, payload []byte) (err error)
	Send(pn *PushNotification) (resp *PushNotificationResponse)
}

APNSClient is an APNS client.

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 Client

type Client struct {
	Gateway           string
	CertificateFile   string
	CertificateBase64 string
	KeyFile           string
	KeyBase64         string
	ProxyURL          string
}

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.

func BareClient

func BareClient(gateway, certificateBase64, keyBase64, proxyURL string) (c *Client)

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

func NewClient

func NewClient(gateway, certificateFile, keyFile, proxyURL string) (c *Client)

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

func (*Client) ConnectAndWrite

func (client *Client) ConnectAndWrite(resp *PushNotificationResponse, payload []byte) (err error)

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) ListenForFeedback

func (client *Client) 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 (*Client) Send

func (client *Client) 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.

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 MockClient

type MockClient struct {
	mock.Mock
}

func (*MockClient) ConnectAndWrite

func (m *MockClient) ConnectAndWrite(resp *PushNotificationResponse, payload []byte) (err error)

func (*MockClient) Send

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 {
	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 string
	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
t or T : Toggle theme light dark auto
y or Y : Canonical URL