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
|
// 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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 payload’s aps dictionary must not contain the alert, sound, or badge keys.
|
// Silent Notification which payload’s 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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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=",
|
||||||
|
|
Loading…
Reference in New Issue