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

View File

@ -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 {

View File

@ -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 payloads aps dictionary must not contain the alert, sound, or badge keys.
// ref: https://goo.gl/m9xyqG
func TestSendZeroValueForBadgeKey(t *testing.T) {

View File

@ -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

View File

@ -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.
[![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
// {"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

View File

@ -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.
//

View File

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

View File

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

View File

@ -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)
}

View File

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

16
vendor/vendor.json vendored
View File

@ -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=",