Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iOS 15 payload additions #185

Merged
merged 7 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ APNS/2 is a go package designed for simple, flexible and fast Apple Push Notific
- Works with go 1.7 and later
- Supports new Apple Token Based Authentication (JWT)
- Supports new iOS 10 features such as Collapse IDs, Subtitles and Mutable Notifications
- Supports new iOS 15 fetaures interruptionLevel and relevanceScore
- Supports persistent connections to APNs
- Supports VoIP/PushKit notifications (iOS 8 and later)
- Modular & easy to use
Expand Down
70 changes: 62 additions & 8 deletions payload/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ type Payload struct {
}

type aps struct {
Alert interface{} `json:"alert,omitempty"`
Badge interface{} `json:"badge,omitempty"`
Category string `json:"category,omitempty"`
ContentAvailable int `json:"content-available,omitempty"`
MutableContent int `json:"mutable-content,omitempty"`
Sound interface{} `json:"sound,omitempty"`
ThreadID string `json:"thread-id,omitempty"`
URLArgs []string `json:"url-args,omitempty"`
Alert interface{} `json:"alert,omitempty"`
Badge interface{} `json:"badge,omitempty"`
Category string `json:"category,omitempty"`
ContentAvailable int `json:"content-available,omitempty"`
InterruptionLevel string `json:"interruption-level,omitempty"`
MutableContent int `json:"mutable-content,omitempty"`
RelevanceScore float32 `json:"relevance-score,omitempty"`
Sound interface{} `json:"sound,omitempty"`
ThreadID string `json:"thread-id,omitempty"`
URLArgs []string `json:"url-args,omitempty"`
}

type alert struct {
Expand Down Expand Up @@ -324,6 +326,58 @@ func (p *Payload) SoundVolume(volume float32) *Payload {
return p
}

// InterruptionLevelPassive sets the aps interruption-level to "passive" on the payload.
// This is to indicate the importance and delivery timing of a notification.
// See: https://developer.apple.com/documentation/usernotifications/unnotificationinterruptionlevel/
//
// {"aps":{"interruption-level":passive}}
func (p *Payload) InterruptionLevelPassive() *Payload {
p.aps().InterruptionLevel = "passive"
return p
}

// InterruptionLevelActive sets the aps interruption-level to "active" on the payload.
// This is to indicate the importance and delivery timing of a notification.
// See: https://developer.apple.com/documentation/usernotifications/unnotificationinterruptionlevel/
//
// {"aps":{"interruption-level":active}}
func (p *Payload) InterruptionLevelActive() *Payload {
p.aps().InterruptionLevel = "active"
return p
}

// InterruptionLevelTimeSensitive sets the aps interruption-level to "time-sensitive" on the payload.
// This is to indicate the importance and delivery timing of a notification.
// See: https://developer.apple.com/documentation/usernotifications/unnotificationinterruptionlevel/
//
// {"aps":{"interruption-level":time-sensitive}}
func (p *Payload) InterruptionLevelTimeSensitive() *Payload {
p.aps().InterruptionLevel = "time-sensitive"
neilmorton marked this conversation as resolved.
Show resolved Hide resolved
return p
}

// InterruptionLevelCritical sets the aps interruption-level to "critical" on the payload.
// This is to indicate the importance and delivery timing of a notification.
// This interruption level requires an approved entitlement from Apple.
// See: https://developer.apple.com/documentation/usernotifications/unnotificationinterruptionlevel/
//
// {"aps":{"interruption-level":critical}}
func (p *Payload) InterruptionLevelCritical() *Payload {
p.aps().InterruptionLevel = "critical"
return p
}

Copy link
Contributor

@chrishaines chrishaines Sep 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use these four different functions for setting this value rather than a single setter (i.e. InterruptionLevel) and define a custom string type ala EPushType with the values as defined as constants?

Definitely appreciate the initiative in getting this out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used the four functions as I was following the way that individual functions were provided for badges eg. ZeroBadge etc.

I am happy to swap to a single setter (which was my original thought tbh).

Welcome your thoughts @chrishaines?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure but it seems to be that this is partially due to typing (it allows Badge to specifically take an int as an argument with UnsetBadge being used to set it to nil to remove it from the payload. I'm not sure why there is a separate ZeroBadge function rather than just using Badge(0) 🤷‍♂️ ).

I think that EPushType seems to be the closest precedent to handling an enumerated value in the codebase.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am inclined to swap it out then as you suggest (I have working code for that implementation ready to go).

Before I do though, can I ask, what is the reason for the E prefix in EPushType? Is there some specific naming convention? (I don't do a huge lot in Go).

Copy link
Contributor

@chrishaines chrishaines Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure where that came from either to be honest. Perhaps it's for Enum? That's really a complete guess.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that seems a sensible guess!
Just wondering if I should employ the same E prefix or not!

Copy link
Contributor

@chrishaines chrishaines Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that sounds reasonable. I would go for it.

Copy link
Contributor Author

@neilmorton neilmorton Sep 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have swapped out the InterruptionLevel into a single function. Thanks for your input @chrishaines. Please can you give it a once over?

// The relevance score, a number between 0 and 1,
// that the system uses to sort the notifications from your app.
// The highest score gets featured in the notification summary.
// See https://developer.apple.com/documentation/usernotifications/unnotificationcontent/3821031-relevancescore.
//
// {"aps":{"relevance-score":0.1}}
func (p *Payload) RelevanceScore(b float32) *Payload {
p.aps().RelevanceScore = b
return p
}
Copy link
Contributor

@chrishaines chrishaines Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't appear to allow setting a relevance-score of 0 because it will omitted when building the aps JSON. This will result in Apple using their default of 0.5. I think you can address this by either using the Badge precedent above (preferred) or by changing the type to a pointer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can make a PR for this if you'd like.

Copy link
Contributor Author

@neilmorton neilmorton Sep 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch @chrishaines on relevance-score.

Sure if you have time to do the PR for that, and I will try and get the other swapped out tomorrow or at the weekend.

Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here ya go!
neilmorton#1

Copy link
Contributor Author

@neilmorton neilmorton Sep 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @chrishaines! I have merged this.


// MarshalJSON returns the JSON encoded version of the Payload
func (p *Payload) MarshalJSON() ([]byte, error) {
return json.Marshal(p.content)
Expand Down
30 changes: 30 additions & 0 deletions payload/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,33 @@ func TestCombined(t *testing.T) {
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"alert":"hello","badge":1,"sound":"Default.caf"},"key":"val"}`, string(b))
}

func TestInterruptionLevelPassive(t *testing.T) {
payload := NewPayload().InterruptionLevelTimeSensitive().InterruptionLevelPassive()
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"interruption-level":"passive"}}`, string(b))
}

func TestInterruptionLevelActive(t *testing.T) {
payload := NewPayload().InterruptionLevelActive()
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"interruption-level":"active"}}`, string(b))
}

func TestInterruptionLevelTimeSensitive(t *testing.T) {
payload := NewPayload().InterruptionLevelTimeSensitive()
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"interruption-level":"time-sensitive"}}`, string(b))
}

func TestInterruptionLevelCritical(t *testing.T) {
payload := NewPayload().InterruptionLevelCritical()
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"interruption-level":"critical"}}`, string(b))
}

func TestRelevanceScore(t *testing.T) {
payload := NewPayload().RelevanceScore(0.1)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"relevance-score":0.1}}`, string(b))
}