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