feat(ios): Support iOS 12 Critical Alerts (#367)

fix https://github.com/appleboy/gorush/issues/366
This commit is contained in:
Bo-Yi Wu 2018-08-28 11:02:13 +08:00 committed by GitHub
parent 880752d88c
commit 0c89fd1d81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 140 additions and 43 deletions

View File

@ -50,16 +50,16 @@ type RequestPush struct {
// PushNotification is single notification request // PushNotification is single notification request
type PushNotification struct { type PushNotification struct {
// Common // Common
Tokens []string `json:"tokens" binding:"required"` Tokens []string `json:"tokens" binding:"required"`
Platform int `json:"platform" binding:"required"` Platform int `json:"platform" binding:"required"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Title string `json:"title,omitempty"` Title string `json:"title,omitempty"`
Priority string `json:"priority,omitempty"` Priority string `json:"priority,omitempty"`
ContentAvailable bool `json:"content_available,omitempty"` ContentAvailable bool `json:"content_available,omitempty"`
MutableContent bool `json:"mutable_content,omitempty"` MutableContent bool `json:"mutable_content,omitempty"`
Sound string `json:"sound,omitempty"` Sound interface{} `json:"sound,omitempty"`
Data D `json:"data,omitempty"` Data D `json:"data,omitempty"`
Retry int `json:"retry,omitempty"` Retry int `json:"retry,omitempty"`
wg *sync.WaitGroup wg *sync.WaitGroup
log *[]LogPushEntry log *[]LogPushEntry
@ -86,6 +86,8 @@ type PushNotification struct {
Alert Alert `json:"alert,omitempty"` Alert Alert `json:"alert,omitempty"`
Production bool `json:"production,omitempty"` Production bool `json:"production,omitempty"`
Development bool `json:"development,omitempty"` Development bool `json:"development,omitempty"`
SoundName string `json:"name,omitempty"`
SoundVolume float32 `json:"volume,omitempty"`
} }
// WaitDone decrements the WaitGroup counter. // WaitDone decrements the WaitGroup counter.

View File

@ -14,6 +14,13 @@ import (
"github.com/sideshow/apns2/token" "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. // InitAPNSClient use for initialize APNs Client.
func InitAPNSClient() error { func InitAPNSClient() error {
if PushConf.Ios.Enabled { if PushConf.Ios.Enabled {
@ -183,8 +190,16 @@ func GetIOSNotification(req PushNotification) *apns2.Notification {
payload.MutableContent() payload.MutableContent()
} }
if len(req.Sound) > 0 { if _, ok := req.Sound.(Sound); ok {
payload.Sound(req.Sound) payload.Sound(&req.Sound)
}
if len(req.SoundName) > 0 {
payload.SoundName(req.SoundName)
}
if req.SoundVolume > 0 {
payload.SoundVolume(req.SoundVolume)
} }
if req.ContentAvailable { if req.ContentAvailable {

View File

@ -56,13 +56,17 @@ func TestIOSNotificationStructure(t *testing.T) {
expectBadge := 0 expectBadge := 0
message := "Welcome notification Server" message := "Welcome notification Server"
req := PushNotification{ req := PushNotification{
ApnsID: test, ApnsID: test,
Topic: test, Topic: test,
Expiration: time.Now().Unix(), Expiration: time.Now().Unix(),
Priority: "normal", Priority: "normal",
Message: message, Message: message,
Badge: &expectBadge, Badge: &expectBadge,
Sound: test, Sound: Sound{
Critical: 1,
Name: test,
Volume: 1.0,
},
ContentAvailable: true, ContentAvailable: true,
Data: D{ Data: D{
"key1": "test", "key1": "test",
@ -78,13 +82,14 @@ func TestIOSNotificationStructure(t *testing.T) {
data := []byte(string(dump)) data := []byte(string(dump))
if err := json.Unmarshal(data, &dat); err != nil { if err := json.Unmarshal(data, &dat); err != nil {
log.Println(err)
panic(err) panic(err)
} }
alert, _ := jsonparser.GetString(data, "aps", "alert") alert, _ := jsonparser.GetString(data, "aps", "alert")
badge, _ := jsonparser.GetInt(data, "aps", "badge") 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") contentAvailable, _ := jsonparser.GetInt(data, "aps", "content-available")
category, _ := jsonparser.GetString(data, "aps", "category") category, _ := jsonparser.GetString(data, "aps", "category")
key1 := dat["key1"].(interface{}) key1 := dat["key1"].(interface{})
@ -99,7 +104,9 @@ func TestIOSNotificationStructure(t *testing.T) {
assert.Equal(t, message, alert) assert.Equal(t, message, alert)
assert.Equal(t, expectBadge, int(badge)) assert.Equal(t, expectBadge, int(badge))
assert.Equal(t, expectBadge, *req.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, 1, int(contentAvailable))
assert.Equal(t, "test", key1) assert.Equal(t, "test", key1)
assert.Equal(t, 2, int(key2.(float64))) assert.Equal(t, 2, int(key2.(float64)))
@ -108,6 +115,48 @@ func TestIOSNotificationStructure(t *testing.T) {
assert.Contains(t, urlArgs, "b") 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 payloads aps dictionary must not contain the alert, sound, or badge keys. // Silent Notification which payloads aps dictionary must not contain the alert, sound, or badge keys.
// ref: https://goo.gl/m9xyqG // ref: https://goo.gl/m9xyqG
func TestSendZeroValueForBadgeKey(t *testing.T) { func TestSendZeroValueForBadgeKey(t *testing.T) {

View File

@ -70,8 +70,8 @@ func GetAndroidNotification(req PushNotification) *fcm.Message {
notification.Notification.Title = req.Title notification.Notification.Title = req.Title
} }
if len(req.Sound) > 0 { if v, ok := req.Sound.(string); ok && len(v) > 0 {
notification.Notification.Sound = req.Sound notification.Notification.Sound = v
} }
return notification return notification

View File

@ -1,7 +1,5 @@
# APNS/2 # 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. 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.
[![Build Status](https://travis-ci.org/sideshow/apns2.svg?branch=master)](https://travis-ci.org/sideshow/apns2) [![Coverage Status](https://coveralls.io/repos/sideshow/apns2/badge.svg?branch=master&service=github)](https://coveralls.io/github/sideshow/apns2?branch=master) [![GoDoc](https://godoc.org/github.com/sideshow/apns2?status.svg)](https://godoc.org/github.com/sideshow/apns2) [![Build Status](https://travis-ci.org/sideshow/apns2.svg?branch=master)](https://travis-ci.org/sideshow/apns2) [![Coverage Status](https://coveralls.io/repos/sideshow/apns2/badge.svg?branch=master&service=github)](https://coveralls.io/github/sideshow/apns2?branch=master) [![GoDoc](https://godoc.org/github.com/sideshow/apns2?status.svg)](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 ```go
// {"aps":{"alert":"hello","badge":1},"key":"val"} // {"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 notification.Payload = payload
client.Push(notification) client.Push(notification)
@ -156,7 +154,7 @@ if res.Sent() {
## Context & Timeouts ## 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 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 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 push timeouts. See the [Google post](https://blog.golang.org/context) for more

View File

@ -133,13 +133,13 @@ func (c *Client) Production() *Client {
// indicating whether the notification was accepted or rejected by the APNs // indicating whether the notification was accepted or rejected by the APNs
// gateway, or an error if something goes wrong. // 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) { func (c *Client) Push(n *Notification) (*Response, error) {
return c.PushWithContext(nil, n) return c.PushWithContext(nil, n)
} }
// PushWithContext sends a Notification to the APNs gateway. Context carries a // 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 // requests when the context timeout is exceeded. Context can be nil, for
// backwards compatibility. // backwards compatibility.
// //

View File

@ -9,7 +9,7 @@ import (
"golang.org/x/net/context/ctxhttp" "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. // API boundaries.
// //
// Context's methods may be called by multiple goroutines simultaneously. // Context's methods may be called by multiple goroutines simultaneously.

View File

@ -7,7 +7,7 @@ import (
"net/http" "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. // API boundaries.
// //
// Context's methods may be called by multiple goroutines simultaneously. // Context's methods may be called by multiple goroutines simultaneously.

View File

@ -16,7 +16,7 @@ type aps struct {
Category string `json:"category,omitempty"` Category string `json:"category,omitempty"`
ContentAvailable int `json:"content-available,omitempty"` ContentAvailable int `json:"content-available,omitempty"`
MutableContent int `json:"mutable-content,omitempty"` MutableContent int `json:"mutable-content,omitempty"`
Sound string `json:"sound,omitempty"` Sound interface{} `json:"sound,omitempty"`
ThreadID string `json:"thread-id,omitempty"` ThreadID string `json:"thread-id,omitempty"`
URLArgs []string `json:"url-args,omitempty"` URLArgs []string `json:"url-args,omitempty"`
} }
@ -34,6 +34,12 @@ type alert struct {
TitleLocKey string `json:"title-loc-key,omitempty"` 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 // NewPayload returns a new Payload struct
func NewPayload() *Payload { func NewPayload() *Payload {
return &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. // This will play a sound from the app bundle, or the default sound otherwise.
// //
// {"aps":{"sound":sound}} // {"aps":{"sound":sound}}
func (p *Payload) Sound(sound string) *Payload { func (p *Payload) Sound(sound interface{}) *Payload {
p.aps().Sound = sound p.aps().Sound = sound
return p return p
} }
@ -274,6 +280,26 @@ func (p *Payload) URLArgs(urlArgs []string) *Payload {
return p 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 // MarshalJSON returns the JSON encoded version of the Payload
func (p *Payload) MarshalJSON() ([]byte, error) { func (p *Payload) MarshalJSON() ([]byte, error) {
return json.Marshal(p.content) return json.Marshal(p.content)
@ -289,3 +315,10 @@ func (a *aps) alert() *alert {
} }
return a.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)
}

View File

@ -110,7 +110,7 @@ const (
// surrounding the rejection. // surrounding the rejection.
type Response struct { 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. // 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 // For a list of other possible status codes, see table 6-4 in the Apple Local
// and Remote Notification Programming Guide. // and Remote Notification Programming Guide.

16
vendor/vendor.json vendored
View File

@ -441,12 +441,12 @@
"revisionTime": "2017-02-16T22:32:56Z" "revisionTime": "2017-02-16T22:32:56Z"
}, },
{ {
"checksumSHA1": "guPIkygGyVn+VPS1hh63f+4IJYg=", "checksumSHA1": "DL5vX9aOefzIAxMDdaQBtgRdR0Y=",
"path": "github.com/sideshow/apns2", "path": "github.com/sideshow/apns2",
"revision": "a3ce9c6f95f63dab4ead29da86534dd7af95271a", "revision": "3c5d4af1700ed9111ecb16a9a99a92a00c8b2f27",
"revisionTime": "2017-09-26T09:37:56Z", "revisionTime": "2018-08-27T05:51:07Z",
"version": "v0.13", "version": "master",
"versionExact": "v0.13" "versionExact": "master"
}, },
{ {
"checksumSHA1": "EHOwwdWPJGt1vNVeJxyRDRHBhl8=", "checksumSHA1": "EHOwwdWPJGt1vNVeJxyRDRHBhl8=",
@ -455,10 +455,10 @@
"revisionTime": "2018-04-13T21:53:35Z" "revisionTime": "2018-04-13T21:53:35Z"
}, },
{ {
"checksumSHA1": "9Lxz0w+q8pqp/Do/kNa8hLoQeY0=", "checksumSHA1": "36bPwB1aiQCxfmdwfgYmI89I9nI=",
"path": "github.com/sideshow/apns2/payload", "path": "github.com/sideshow/apns2/payload",
"revision": "c6554aff77e6e5580dec977c8c33cc238f329ab0", "revision": "3c5d4af1700ed9111ecb16a9a99a92a00c8b2f27",
"revisionTime": "2018-04-13T21:53:35Z" "revisionTime": "2018-08-27T05:51:07Z"
}, },
{ {
"checksumSHA1": "nkQ/1JoIY4jh8XlI8LClfFVux9U=", "checksumSHA1": "nkQ/1JoIY4jh8XlI8LClfFVux9U=",