Skip to content

Commit

Permalink
Live Activity support added (revised and expanded) (#219)
Browse files Browse the repository at this point in the history
* Merge pull request #6 from braze-inc/CH-4323
* [CH-4323] Add Live Activity Support
* [CH-5411] Add support for Attributes/AttributesType for push-to-start live activities (#7)
  • Loading branch information
froodian authored Aug 8, 2024
1 parent 79fa4db commit 79519b7
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 11 deletions.
11 changes: 11 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,17 @@ func TestPushTypeMDMHeader(t *testing.T) {
assert.NoError(t, err)
}

func TestPushTypeLiveActivityHeader(t *testing.T) {
n := mockNotification()
n.PushType = apns.PushTypeLiveActivity
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "liveactivity", r.Header.Get("apns-push-type"))
}))
defer server.Close()
_, err := mockClient(server.URL).Push(n)
assert.NoError(t, err)
}

func TestAuthorizationHeader(t *testing.T) {
n := mockNotification()
token := mockToken()
Expand Down
8 changes: 8 additions & 0 deletions notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ const (
// contact the MDM server. If you set this push type, you must use the topic
// from the UID attribute in the subject of your MDM push certificate.
PushTypeMDM EPushType = "mdm"

// PushTypeLiveActivity is used for Live Activities that display various
// real-time information. If you set this push type, the topic field must
// use your app’s bundle ID with `push-type.liveactivity` appended to the end.
// The live activity push supports only token-based authentication. This
// push type is recommended for iOS. It is not available on macOS, tvOS,
// watchOS and iPadOS.
PushTypeLiveActivity EPushType = "liveactivity"
)

const (
Expand Down
103 changes: 92 additions & 11 deletions payload/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,41 @@ const (
InterruptionLevelCritical EInterruptionLevel = "critical"
)

// LiveActivityEvent defines the value for the payload aps event
type ELiveActivityEvent string

const (
// LiveActivityEventUpdate is used to update an live activity.
LiveActivityEventUpdate ELiveActivityEvent = "update"

// LiveActivityEventEnd is used to end an live activity.
LiveActivityEventEnd ELiveActivityEvent = "end"
)

// Payload represents a notification which holds the content that will be
// marshalled as JSON.
type Payload struct {
content map[string]interface{}
}

type aps struct {
Alert interface{} `json:"alert,omitempty"`
Badge interface{} `json:"badge,omitempty"`
Category string `json:"category,omitempty"`
ContentAvailable int `json:"content-available,omitempty"`
InterruptionLevel EInterruptionLevel `json:"interruption-level,omitempty"`
MutableContent int `json:"mutable-content,omitempty"`
RelevanceScore interface{} `json:"relevance-score,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 EInterruptionLevel `json:"interruption-level,omitempty"`
MutableContent int `json:"mutable-content,omitempty"`
RelevanceScore interface{} `json:"relevance-score,omitempty"`
Sound interface{} `json:"sound,omitempty"`
ThreadID string `json:"thread-id,omitempty"`
URLArgs []string `json:"url-args,omitempty"`
ContentState map[string]interface{} `json:"content-state,omitempty"`
DismissalDate int64 `json:"dismissal-date,omitempty"`
StaleDate int64 `json:"stale-date,omitempty"`
Event ELiveActivityEvent `json:"event,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
AttributesType string `json:"attributes-type,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
}

type alert struct {
Expand Down Expand Up @@ -81,6 +99,69 @@ func (p *Payload) Alert(alert interface{}) *Payload {
return p
}

// SetContentState sets the aps content-state on the payload.
// This will update content-state of live activity widget.
//
// {"aps":{"content-state": {} }}`
func (p *Payload) SetContentState(contentState map[string]interface{}) *Payload {
p.aps().ContentState = contentState
return p
}

// SetDismissalDate sets the aps dismissal-date on the payload.
// This will remove the live activity from the user's UI at the given timestamp.
//
// {"aps":{"dismissal-date": DismissalDate }}`
func (p *Payload) SetDismissalDate(dismissalDate int64) *Payload {
p.aps().DismissalDate = dismissalDate
return p
}

// SetStaleDate sets the aps stale-date on the payload.
// This will mark this live activity update as outdated at the given timestamp.
//
// {"aps":{"stale-date": StaleDate }}`
func (p *Payload) SetStaleDate(staleDate int64) *Payload {
p.aps().StaleDate = staleDate
return p
}

// SetEvent sets the aps event type on the payload.
// This can either be `LiveActivityEventUpdate` or `LiveActivityEventEnd`
//
// {"aps":{"event": Event }}`
func (p *Payload) SetEvent(event ELiveActivityEvent) *Payload {
p.aps().Event = event
return p
}

// SetTimestamp sets the aps timestamp on the payload.
// This will let live activity know when to update the stuff.
//
// {"aps":{"timestamp": Timestamp }}`
func (p *Payload) SetTimestamp(timestamp int64) *Payload {
p.aps().Timestamp = timestamp
return p
}

// SetAttributesType sets the aps attributes-type field on the payload.
// This is used for push-to-start live activities
//
// {"aps":{"attributes-type": attributesType }}`
func (p *Payload) SetAttributesType(attributesType string) *Payload {
p.aps().AttributesType = attributesType
return p
}

// SetAttributes sets the aps attributes field on the payload.
// This is used for push-to-start live activities
//
// {"aps":{"attributes": attributes }}`
func (p *Payload) SetAttributes(attributes map[string]interface{}) *Payload {
p.aps().Attributes = attributes
return p
}

// Badge sets the aps badge on the payload.
// This will display a numeric badge on the app icon.
//
Expand Down Expand Up @@ -218,7 +299,7 @@ func (p *Payload) AlertLaunchImage(image string) *Payload {
// specifiers in loc-key. See Localized Formatted Strings in Apple
// documentation for more information.
//
// {"aps":{"alert":{"loc-args":args}}}
// {"aps":{"alert":{"loc-args":args}}}
func (p *Payload) AlertLocArgs(args []string) *Payload {
p.aps().alert().LocArgs = args
return p
Expand Down
61 changes: 61 additions & 0 deletions payload/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package payload_test
import (
"encoding/json"
"testing"
"time"

. "github.com/sideshow/apns2/payload"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -146,6 +147,66 @@ func TestCategory(t *testing.T) {
assert.Equal(t, `{"aps":{"category":"NEW_MESSAGE_CATEGORY"}}`, string(b))
}

func TestContentState(t *testing.T) {
payload := NewPayload().SetContentState(map[string]interface{}{"my_int": 13, "my_string": "foo"})
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"content-state":{"my_int":13,"my_string":"foo"}}}`, string(b))
}

func TestDismissalDate(t *testing.T) {
timestamp := time.Date(2023, 1, 27, 12, 14, 00, 00, time.UTC).Unix()
payload := NewPayload().SetDismissalDate(timestamp)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"dismissal-date":1674821640}}`, string(b))
}

func TestStaleDate(t *testing.T) {
timestamp := time.Date(2023, 1, 27, 12, 14, 00, 00, time.UTC).Unix()
payload := NewPayload().SetStaleDate(timestamp)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"stale-date":1674821640}}`, string(b))
}

func TestEventEnd(t *testing.T) {
payload := NewPayload().SetEvent(LiveActivityEventEnd)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"event":"end"}}`, string(b))
}

func TestEventUpdate(t *testing.T) {
payload := NewPayload().SetEvent(LiveActivityEventUpdate)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"event":"update"}}`, string(b))
}

func TestTimestamp(t *testing.T) {
timestamp := time.Date(2023, 1, 27, 12, 14, 00, 00, time.UTC).Unix()
payload := NewPayload().SetTimestamp(timestamp)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"timestamp":1674821640}}`, string(b))
}

func TestAttributesType(t *testing.T) {
attributesType := "AdventureAttributes"
payload := NewPayload().SetAttributesType(attributesType)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"attributes-type":"AdventureAttributes"}}`, string(b))
}

func TestAttributes(t *testing.T) {
attributes := map[string]interface{}{
"currentHealthLevel": 100,
"eventDescription": "Adventure has begun!",
}
payload := NewPayload().SetAttributes(attributes)
b, _ := json.Marshal(payload)
assert.Equal(
t,
`{"aps":{"attributes":{"currentHealthLevel":100,"eventDescription":"Adventure has begun!"}}}`,
string(b),
)
}

func TestMdm(t *testing.T) {
payload := NewPayload().Mdm("996ac527-9993-4a0a-8528-60b2b3c2f52b")
b, _ := json.Marshal(payload)
Expand Down

0 comments on commit 79519b7

Please sign in to comment.