feat(ios): Support iOS 12 Critical Alerts (#367)
fix https://github.com/appleboy/gorush/issues/366
This commit is contained in:
		
							parent
							
								
									880752d88c
								
							
						
					
					
						commit
						0c89fd1d81
					
				| 
						 | 
				
			
			@ -50,16 +50,16 @@ type RequestPush struct {
 | 
			
		|||
// PushNotification is single notification request
 | 
			
		||||
type PushNotification struct {
 | 
			
		||||
	// Common
 | 
			
		||||
	Tokens           []string `json:"tokens" binding:"required"`
 | 
			
		||||
	Platform         int      `json:"platform" binding:"required"`
 | 
			
		||||
	Message          string   `json:"message,omitempty"`
 | 
			
		||||
	Title            string   `json:"title,omitempty"`
 | 
			
		||||
	Priority         string   `json:"priority,omitempty"`
 | 
			
		||||
	ContentAvailable bool     `json:"content_available,omitempty"`
 | 
			
		||||
	MutableContent   bool     `json:"mutable_content,omitempty"`
 | 
			
		||||
	Sound            string   `json:"sound,omitempty"`
 | 
			
		||||
	Data             D        `json:"data,omitempty"`
 | 
			
		||||
	Retry            int      `json:"retry,omitempty"`
 | 
			
		||||
	Tokens           []string    `json:"tokens" binding:"required"`
 | 
			
		||||
	Platform         int         `json:"platform" binding:"required"`
 | 
			
		||||
	Message          string      `json:"message,omitempty"`
 | 
			
		||||
	Title            string      `json:"title,omitempty"`
 | 
			
		||||
	Priority         string      `json:"priority,omitempty"`
 | 
			
		||||
	ContentAvailable bool        `json:"content_available,omitempty"`
 | 
			
		||||
	MutableContent   bool        `json:"mutable_content,omitempty"`
 | 
			
		||||
	Sound            interface{} `json:"sound,omitempty"`
 | 
			
		||||
	Data             D           `json:"data,omitempty"`
 | 
			
		||||
	Retry            int         `json:"retry,omitempty"`
 | 
			
		||||
	wg               *sync.WaitGroup
 | 
			
		||||
	log              *[]LogPushEntry
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -86,6 +86,8 @@ type PushNotification struct {
 | 
			
		|||
	Alert       Alert    `json:"alert,omitempty"`
 | 
			
		||||
	Production  bool     `json:"production,omitempty"`
 | 
			
		||||
	Development bool     `json:"development,omitempty"`
 | 
			
		||||
	SoundName   string   `json:"name,omitempty"`
 | 
			
		||||
	SoundVolume float32  `json:"volume,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WaitDone decrements the WaitGroup counter.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,6 +14,13 @@ import (
 | 
			
		|||
	"github.com/sideshow/apns2/token"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Sound sets the aps sound on the payload.
 | 
			
		||||
type Sound struct {
 | 
			
		||||
	Critical int     `json:"critical,omitempty"`
 | 
			
		||||
	Name     string  `json:"name,omitempty"`
 | 
			
		||||
	Volume   float32 `json:"volume,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InitAPNSClient use for initialize APNs Client.
 | 
			
		||||
func InitAPNSClient() error {
 | 
			
		||||
	if PushConf.Ios.Enabled {
 | 
			
		||||
| 
						 | 
				
			
			@ -183,8 +190,16 @@ func GetIOSNotification(req PushNotification) *apns2.Notification {
 | 
			
		|||
		payload.MutableContent()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(req.Sound) > 0 {
 | 
			
		||||
		payload.Sound(req.Sound)
 | 
			
		||||
	if _, ok := req.Sound.(Sound); ok {
 | 
			
		||||
		payload.Sound(&req.Sound)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(req.SoundName) > 0 {
 | 
			
		||||
		payload.SoundName(req.SoundName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.SoundVolume > 0 {
 | 
			
		||||
		payload.SoundVolume(req.SoundVolume)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.ContentAvailable {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,13 +56,17 @@ func TestIOSNotificationStructure(t *testing.T) {
 | 
			
		|||
	expectBadge := 0
 | 
			
		||||
	message := "Welcome notification Server"
 | 
			
		||||
	req := PushNotification{
 | 
			
		||||
		ApnsID:           test,
 | 
			
		||||
		Topic:            test,
 | 
			
		||||
		Expiration:       time.Now().Unix(),
 | 
			
		||||
		Priority:         "normal",
 | 
			
		||||
		Message:          message,
 | 
			
		||||
		Badge:            &expectBadge,
 | 
			
		||||
		Sound:            test,
 | 
			
		||||
		ApnsID:     test,
 | 
			
		||||
		Topic:      test,
 | 
			
		||||
		Expiration: time.Now().Unix(),
 | 
			
		||||
		Priority:   "normal",
 | 
			
		||||
		Message:    message,
 | 
			
		||||
		Badge:      &expectBadge,
 | 
			
		||||
		Sound: Sound{
 | 
			
		||||
			Critical: 1,
 | 
			
		||||
			Name:     test,
 | 
			
		||||
			Volume:   1.0,
 | 
			
		||||
		},
 | 
			
		||||
		ContentAvailable: true,
 | 
			
		||||
		Data: D{
 | 
			
		||||
			"key1": "test",
 | 
			
		||||
| 
						 | 
				
			
			@ -78,13 +82,14 @@ func TestIOSNotificationStructure(t *testing.T) {
 | 
			
		|||
	data := []byte(string(dump))
 | 
			
		||||
 | 
			
		||||
	if err := json.Unmarshal(data, &dat); err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	alert, _ := jsonparser.GetString(data, "aps", "alert")
 | 
			
		||||
	badge, _ := jsonparser.GetInt(data, "aps", "badge")
 | 
			
		||||
	sound, _ := jsonparser.GetString(data, "aps", "sound")
 | 
			
		||||
	soundName, _ := jsonparser.GetString(data, "aps", "sound", "name")
 | 
			
		||||
	soundCritical, _ := jsonparser.GetInt(data, "aps", "sound", "critical")
 | 
			
		||||
	soundVolume, _ := jsonparser.GetFloat(data, "aps", "sound", "volume")
 | 
			
		||||
	contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available")
 | 
			
		||||
	category, _ := jsonparser.GetString(data, "aps", "category")
 | 
			
		||||
	key1 := dat["key1"].(interface{})
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +104,9 @@ func TestIOSNotificationStructure(t *testing.T) {
 | 
			
		|||
	assert.Equal(t, message, alert)
 | 
			
		||||
	assert.Equal(t, expectBadge, int(badge))
 | 
			
		||||
	assert.Equal(t, expectBadge, *req.Badge)
 | 
			
		||||
	assert.Equal(t, test, sound)
 | 
			
		||||
	assert.Equal(t, test, soundName)
 | 
			
		||||
	assert.Equal(t, 1.0, soundVolume)
 | 
			
		||||
	assert.Equal(t, int64(1), soundCritical)
 | 
			
		||||
	assert.Equal(t, 1, int(contentAvailable))
 | 
			
		||||
	assert.Equal(t, "test", key1)
 | 
			
		||||
	assert.Equal(t, 2, int(key2.(float64)))
 | 
			
		||||
| 
						 | 
				
			
			@ -108,6 +115,48 @@ func TestIOSNotificationStructure(t *testing.T) {
 | 
			
		|||
	assert.Contains(t, urlArgs, "b")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIOSSoundAndVolume(t *testing.T) {
 | 
			
		||||
	var dat map[string]interface{}
 | 
			
		||||
 | 
			
		||||
	test := "test"
 | 
			
		||||
	message := "Welcome notification Server"
 | 
			
		||||
	req := PushNotification{
 | 
			
		||||
		ApnsID:   test,
 | 
			
		||||
		Topic:    test,
 | 
			
		||||
		Priority: "normal",
 | 
			
		||||
		Message:  message,
 | 
			
		||||
		Sound: Sound{
 | 
			
		||||
			Critical: 2,
 | 
			
		||||
			Name:     test,
 | 
			
		||||
			Volume:   1.0,
 | 
			
		||||
		},
 | 
			
		||||
		SoundName:   "foo",
 | 
			
		||||
		SoundVolume: 2.0,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	notification := GetIOSNotification(req)
 | 
			
		||||
 | 
			
		||||
	dump, _ := json.Marshal(notification.Payload)
 | 
			
		||||
	data := []byte(string(dump))
 | 
			
		||||
 | 
			
		||||
	if err := json.Unmarshal(data, &dat); err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	alert, _ := jsonparser.GetString(data, "aps", "alert")
 | 
			
		||||
	soundName, _ := jsonparser.GetString(data, "aps", "sound", "name")
 | 
			
		||||
	soundCritical, _ := jsonparser.GetInt(data, "aps", "sound", "critical")
 | 
			
		||||
	soundVolume, _ := jsonparser.GetFloat(data, "aps", "sound", "volume")
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, test, notification.ApnsID)
 | 
			
		||||
	assert.Equal(t, test, notification.Topic)
 | 
			
		||||
	assert.Equal(t, ApnsPriorityLow, notification.Priority)
 | 
			
		||||
	assert.Equal(t, message, alert)
 | 
			
		||||
	assert.Equal(t, "foo", soundName)
 | 
			
		||||
	assert.Equal(t, 2.0, soundVolume)
 | 
			
		||||
	assert.Equal(t, int64(1), soundCritical)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Silent Notification which payload’s aps dictionary must not contain the alert, sound, or badge keys.
 | 
			
		||||
// ref: https://goo.gl/m9xyqG
 | 
			
		||||
func TestSendZeroValueForBadgeKey(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,8 +70,8 @@ func GetAndroidNotification(req PushNotification) *fcm.Message {
 | 
			
		|||
		notification.Notification.Title = req.Title
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(req.Sound) > 0 {
 | 
			
		||||
		notification.Notification.Sound = req.Sound
 | 
			
		||||
	if v, ok := req.Sound.(string); ok && len(v) > 0 {
 | 
			
		||||
		notification.Notification.Sound = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return notification
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,5 @@
 | 
			
		|||
# APNS/2
 | 
			
		||||
 | 
			
		||||
NOTE: This is an experimental branch for the purpose of testing the new token based authentication
 | 
			
		||||
 | 
			
		||||
APNS/2 is a go package designed for simple, flexible and fast Apple Push Notifications on iOS, OSX and Safari using the new HTTP/2 Push provider API.
 | 
			
		||||
 | 
			
		||||
[](https://travis-ci.org/sideshow/apns2)  [](https://coveralls.io/github/sideshow/apns2?branch=master)  [](https://godoc.org/github.com/sideshow/apns2)
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +121,7 @@ You can use raw bytes for the `notification.Payload` as above, or you can use th
 | 
			
		|||
```go
 | 
			
		||||
// {"aps":{"alert":"hello","badge":1},"key":"val"}
 | 
			
		||||
 | 
			
		||||
payload := apns2.NewPayload().Alert("hello").Badge(1).Custom("key", "val")
 | 
			
		||||
payload := payload.NewPayload().Alert("hello").Badge(1).Custom("key", "val")
 | 
			
		||||
 | 
			
		||||
notification.Payload = payload
 | 
			
		||||
client.Push(notification)
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +154,7 @@ if res.Sent() {
 | 
			
		|||
 | 
			
		||||
## Context & Timeouts
 | 
			
		||||
 | 
			
		||||
For better control over request cancelations and timeouts APNS/2 supports
 | 
			
		||||
For better control over request cancellations and timeouts APNS/2 supports
 | 
			
		||||
contexts. Using a context can be helpful if you want to cancel all pushes when
 | 
			
		||||
the parent process is cancelled, or need finer grained control over individual
 | 
			
		||||
push timeouts. See the [Google post](https://blog.golang.org/context) for more
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,13 +133,13 @@ func (c *Client) Production() *Client {
 | 
			
		|||
// indicating whether the notification was accepted or rejected by the APNs
 | 
			
		||||
// gateway, or an error if something goes wrong.
 | 
			
		||||
//
 | 
			
		||||
// Use PushWithContext if you need better cancelation and timeout control.
 | 
			
		||||
// Use PushWithContext if you need better cancellation and timeout control.
 | 
			
		||||
func (c *Client) Push(n *Notification) (*Response, error) {
 | 
			
		||||
	return c.PushWithContext(nil, n)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PushWithContext sends a Notification to the APNs gateway. Context carries a
 | 
			
		||||
// deadline and a cancelation signal and allows you to close long running
 | 
			
		||||
// deadline and a cancellation signal and allows you to close long running
 | 
			
		||||
// requests when the context timeout is exceeded. Context can be nil, for
 | 
			
		||||
// backwards compatibility.
 | 
			
		||||
//
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ import (
 | 
			
		|||
	"golang.org/x/net/context/ctxhttp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// A Context carries a deadline, a cancelation signal, and other values across
 | 
			
		||||
// A Context carries a deadline, a cancellation signal, and other values across
 | 
			
		||||
// API boundaries.
 | 
			
		||||
//
 | 
			
		||||
// Context's methods may be called by multiple goroutines simultaneously.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ import (
 | 
			
		|||
	"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// A Context carries a deadline, a cancelation signal, and other values across
 | 
			
		||||
// A Context carries a deadline, a cancellation signal, and other values across
 | 
			
		||||
// API boundaries.
 | 
			
		||||
//
 | 
			
		||||
// Context's methods may be called by multiple goroutines simultaneously.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ type aps struct {
 | 
			
		|||
	Category         string      `json:"category,omitempty"`
 | 
			
		||||
	ContentAvailable int         `json:"content-available,omitempty"`
 | 
			
		||||
	MutableContent   int         `json:"mutable-content,omitempty"`
 | 
			
		||||
	Sound            string      `json:"sound,omitempty"`
 | 
			
		||||
	Sound            interface{} `json:"sound,omitempty"`
 | 
			
		||||
	ThreadID         string      `json:"thread-id,omitempty"`
 | 
			
		||||
	URLArgs          []string    `json:"url-args,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +34,12 @@ type alert struct {
 | 
			
		|||
	TitleLocKey  string   `json:"title-loc-key,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type sound struct {
 | 
			
		||||
	Critical int     `json:"critical,omitempty"`
 | 
			
		||||
	Name     string  `json:"name,omitempty"`
 | 
			
		||||
	Volume   float32 `json:"volume,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewPayload returns a new Payload struct
 | 
			
		||||
func NewPayload() *Payload {
 | 
			
		||||
	return &Payload{
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +90,7 @@ func (p *Payload) UnsetBadge() *Payload {
 | 
			
		|||
// This will play a sound from the app bundle, or the default sound otherwise.
 | 
			
		||||
//
 | 
			
		||||
//	{"aps":{"sound":sound}}
 | 
			
		||||
func (p *Payload) Sound(sound string) *Payload {
 | 
			
		||||
func (p *Payload) Sound(sound interface{}) *Payload {
 | 
			
		||||
	p.aps().Sound = sound
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -274,6 +280,26 @@ func (p *Payload) URLArgs(urlArgs []string) *Payload {
 | 
			
		|||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SoundName sets the name value on the aps sound dictionary.
 | 
			
		||||
// This function makes the notification a critical alert, which should be pre-approved by Apple.
 | 
			
		||||
// See: https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/
 | 
			
		||||
//
 | 
			
		||||
// {"aps":{"sound":{"critical":1,"name":name,"volume":1.0}}}
 | 
			
		||||
func (p *Payload) SoundName(name string) *Payload {
 | 
			
		||||
	p.aps().sound().Name = name
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SoundVolume sets the volume value on the aps sound dictionary.
 | 
			
		||||
// This function makes the notification a critical alert, which should be pre-approved by Apple.
 | 
			
		||||
// See: https://developer.apple.com/contact/request/notifications-critical-alerts-entitlement/
 | 
			
		||||
//
 | 
			
		||||
// {"aps":{"sound":{"critical":1,"name":"default","volume":volume}}}
 | 
			
		||||
func (p *Payload) SoundVolume(volume float32) *Payload {
 | 
			
		||||
	p.aps().sound().Volume = volume
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MarshalJSON returns the JSON encoded version of the Payload
 | 
			
		||||
func (p *Payload) MarshalJSON() ([]byte, error) {
 | 
			
		||||
	return json.Marshal(p.content)
 | 
			
		||||
| 
						 | 
				
			
			@ -289,3 +315,10 @@ func (a *aps) alert() *alert {
 | 
			
		|||
	}
 | 
			
		||||
	return a.Alert.(*alert)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *aps) sound() *sound {
 | 
			
		||||
	if _, ok := a.Sound.(*sound); !ok {
 | 
			
		||||
		a.Sound = &sound{Critical: 1, Name: "default", Volume: 1.0}
 | 
			
		||||
	}
 | 
			
		||||
	return a.Sound.(*sound)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -110,7 +110,7 @@ const (
 | 
			
		|||
// surrounding the rejection.
 | 
			
		||||
type Response struct {
 | 
			
		||||
 | 
			
		||||
	// The HTTP status code retuened by APNs.
 | 
			
		||||
	// The HTTP status code returned by APNs.
 | 
			
		||||
	// A 200 value indicates that the notification was successfully sent.
 | 
			
		||||
	// For a list of other possible status codes, see table 6-4 in the Apple Local
 | 
			
		||||
	// and Remote Notification Programming Guide.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -441,12 +441,12 @@
 | 
			
		|||
			"revisionTime": "2017-02-16T22:32:56Z"
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"checksumSHA1": "guPIkygGyVn+VPS1hh63f+4IJYg=",
 | 
			
		||||
			"checksumSHA1": "DL5vX9aOefzIAxMDdaQBtgRdR0Y=",
 | 
			
		||||
			"path": "github.com/sideshow/apns2",
 | 
			
		||||
			"revision": "a3ce9c6f95f63dab4ead29da86534dd7af95271a",
 | 
			
		||||
			"revisionTime": "2017-09-26T09:37:56Z",
 | 
			
		||||
			"version": "v0.13",
 | 
			
		||||
			"versionExact": "v0.13"
 | 
			
		||||
			"revision": "3c5d4af1700ed9111ecb16a9a99a92a00c8b2f27",
 | 
			
		||||
			"revisionTime": "2018-08-27T05:51:07Z",
 | 
			
		||||
			"version": "master",
 | 
			
		||||
			"versionExact": "master"
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"checksumSHA1": "EHOwwdWPJGt1vNVeJxyRDRHBhl8=",
 | 
			
		||||
| 
						 | 
				
			
			@ -455,10 +455,10 @@
 | 
			
		|||
			"revisionTime": "2018-04-13T21:53:35Z"
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"checksumSHA1": "9Lxz0w+q8pqp/Do/kNa8hLoQeY0=",
 | 
			
		||||
			"checksumSHA1": "36bPwB1aiQCxfmdwfgYmI89I9nI=",
 | 
			
		||||
			"path": "github.com/sideshow/apns2/payload",
 | 
			
		||||
			"revision": "c6554aff77e6e5580dec977c8c33cc238f329ab0",
 | 
			
		||||
			"revisionTime": "2018-04-13T21:53:35Z"
 | 
			
		||||
			"revision": "3c5d4af1700ed9111ecb16a9a99a92a00c8b2f27",
 | 
			
		||||
			"revisionTime": "2018-08-27T05:51:07Z"
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			"checksumSHA1": "nkQ/1JoIY4jh8XlI8LClfFVux9U=",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue