Skip to content

Commit 79519b7

Browse files
authored
Live Activity support added (revised and expanded) (#219)
* 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)
1 parent 79fa4db commit 79519b7

File tree

4 files changed

+172
-11
lines changed

4 files changed

+172
-11
lines changed

client_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,17 @@ func TestPushTypeMDMHeader(t *testing.T) {
322322
assert.NoError(t, err)
323323
}
324324

325+
func TestPushTypeLiveActivityHeader(t *testing.T) {
326+
n := mockNotification()
327+
n.PushType = apns.PushTypeLiveActivity
328+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
329+
assert.Equal(t, "liveactivity", r.Header.Get("apns-push-type"))
330+
}))
331+
defer server.Close()
332+
_, err := mockClient(server.URL).Push(n)
333+
assert.NoError(t, err)
334+
}
335+
325336
func TestAuthorizationHeader(t *testing.T) {
326337
n := mockNotification()
327338
token := mockToken()

notification.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ const (
6363
// contact the MDM server. If you set this push type, you must use the topic
6464
// from the UID attribute in the subject of your MDM push certificate.
6565
PushTypeMDM EPushType = "mdm"
66+
67+
// PushTypeLiveActivity is used for Live Activities that display various
68+
// real-time information. If you set this push type, the topic field must
69+
// use your app’s bundle ID with `push-type.liveactivity` appended to the end.
70+
// The live activity push supports only token-based authentication. This
71+
// push type is recommended for iOS. It is not available on macOS, tvOS,
72+
// watchOS and iPadOS.
73+
PushTypeLiveActivity EPushType = "liveactivity"
6674
)
6775

6876
const (

payload/builder.go

Lines changed: 92 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,41 @@ const (
2323
InterruptionLevelCritical EInterruptionLevel = "critical"
2424
)
2525

26+
// LiveActivityEvent defines the value for the payload aps event
27+
type ELiveActivityEvent string
28+
29+
const (
30+
// LiveActivityEventUpdate is used to update an live activity.
31+
LiveActivityEventUpdate ELiveActivityEvent = "update"
32+
33+
// LiveActivityEventEnd is used to end an live activity.
34+
LiveActivityEventEnd ELiveActivityEvent = "end"
35+
)
36+
2637
// Payload represents a notification which holds the content that will be
2738
// marshalled as JSON.
2839
type Payload struct {
2940
content map[string]interface{}
3041
}
3142

3243
type aps struct {
33-
Alert interface{} `json:"alert,omitempty"`
34-
Badge interface{} `json:"badge,omitempty"`
35-
Category string `json:"category,omitempty"`
36-
ContentAvailable int `json:"content-available,omitempty"`
37-
InterruptionLevel EInterruptionLevel `json:"interruption-level,omitempty"`
38-
MutableContent int `json:"mutable-content,omitempty"`
39-
RelevanceScore interface{} `json:"relevance-score,omitempty"`
40-
Sound interface{} `json:"sound,omitempty"`
41-
ThreadID string `json:"thread-id,omitempty"`
42-
URLArgs []string `json:"url-args,omitempty"`
44+
Alert interface{} `json:"alert,omitempty"`
45+
Badge interface{} `json:"badge,omitempty"`
46+
Category string `json:"category,omitempty"`
47+
ContentAvailable int `json:"content-available,omitempty"`
48+
InterruptionLevel EInterruptionLevel `json:"interruption-level,omitempty"`
49+
MutableContent int `json:"mutable-content,omitempty"`
50+
RelevanceScore interface{} `json:"relevance-score,omitempty"`
51+
Sound interface{} `json:"sound,omitempty"`
52+
ThreadID string `json:"thread-id,omitempty"`
53+
URLArgs []string `json:"url-args,omitempty"`
54+
ContentState map[string]interface{} `json:"content-state,omitempty"`
55+
DismissalDate int64 `json:"dismissal-date,omitempty"`
56+
StaleDate int64 `json:"stale-date,omitempty"`
57+
Event ELiveActivityEvent `json:"event,omitempty"`
58+
Timestamp int64 `json:"timestamp,omitempty"`
59+
AttributesType string `json:"attributes-type,omitempty"`
60+
Attributes map[string]interface{} `json:"attributes,omitempty"`
4361
}
4462

4563
type alert struct {
@@ -81,6 +99,69 @@ func (p *Payload) Alert(alert interface{}) *Payload {
8199
return p
82100
}
83101

102+
// SetContentState sets the aps content-state on the payload.
103+
// This will update content-state of live activity widget.
104+
//
105+
// {"aps":{"content-state": {} }}`
106+
func (p *Payload) SetContentState(contentState map[string]interface{}) *Payload {
107+
p.aps().ContentState = contentState
108+
return p
109+
}
110+
111+
// SetDismissalDate sets the aps dismissal-date on the payload.
112+
// This will remove the live activity from the user's UI at the given timestamp.
113+
//
114+
// {"aps":{"dismissal-date": DismissalDate }}`
115+
func (p *Payload) SetDismissalDate(dismissalDate int64) *Payload {
116+
p.aps().DismissalDate = dismissalDate
117+
return p
118+
}
119+
120+
// SetStaleDate sets the aps stale-date on the payload.
121+
// This will mark this live activity update as outdated at the given timestamp.
122+
//
123+
// {"aps":{"stale-date": StaleDate }}`
124+
func (p *Payload) SetStaleDate(staleDate int64) *Payload {
125+
p.aps().StaleDate = staleDate
126+
return p
127+
}
128+
129+
// SetEvent sets the aps event type on the payload.
130+
// This can either be `LiveActivityEventUpdate` or `LiveActivityEventEnd`
131+
//
132+
// {"aps":{"event": Event }}`
133+
func (p *Payload) SetEvent(event ELiveActivityEvent) *Payload {
134+
p.aps().Event = event
135+
return p
136+
}
137+
138+
// SetTimestamp sets the aps timestamp on the payload.
139+
// This will let live activity know when to update the stuff.
140+
//
141+
// {"aps":{"timestamp": Timestamp }}`
142+
func (p *Payload) SetTimestamp(timestamp int64) *Payload {
143+
p.aps().Timestamp = timestamp
144+
return p
145+
}
146+
147+
// SetAttributesType sets the aps attributes-type field on the payload.
148+
// This is used for push-to-start live activities
149+
//
150+
// {"aps":{"attributes-type": attributesType }}`
151+
func (p *Payload) SetAttributesType(attributesType string) *Payload {
152+
p.aps().AttributesType = attributesType
153+
return p
154+
}
155+
156+
// SetAttributes sets the aps attributes field on the payload.
157+
// This is used for push-to-start live activities
158+
//
159+
// {"aps":{"attributes": attributes }}`
160+
func (p *Payload) SetAttributes(attributes map[string]interface{}) *Payload {
161+
p.aps().Attributes = attributes
162+
return p
163+
}
164+
84165
// Badge sets the aps badge on the payload.
85166
// This will display a numeric badge on the app icon.
86167
//
@@ -218,7 +299,7 @@ func (p *Payload) AlertLaunchImage(image string) *Payload {
218299
// specifiers in loc-key. See Localized Formatted Strings in Apple
219300
// documentation for more information.
220301
//
221-
// {"aps":{"alert":{"loc-args":args}}}
302+
// {"aps":{"alert":{"loc-args":args}}}
222303
func (p *Payload) AlertLocArgs(args []string) *Payload {
223304
p.aps().alert().LocArgs = args
224305
return p

payload/builder_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package payload_test
33
import (
44
"encoding/json"
55
"testing"
6+
"time"
67

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

150+
func TestContentState(t *testing.T) {
151+
payload := NewPayload().SetContentState(map[string]interface{}{"my_int": 13, "my_string": "foo"})
152+
b, _ := json.Marshal(payload)
153+
assert.Equal(t, `{"aps":{"content-state":{"my_int":13,"my_string":"foo"}}}`, string(b))
154+
}
155+
156+
func TestDismissalDate(t *testing.T) {
157+
timestamp := time.Date(2023, 1, 27, 12, 14, 00, 00, time.UTC).Unix()
158+
payload := NewPayload().SetDismissalDate(timestamp)
159+
b, _ := json.Marshal(payload)
160+
assert.Equal(t, `{"aps":{"dismissal-date":1674821640}}`, string(b))
161+
}
162+
163+
func TestStaleDate(t *testing.T) {
164+
timestamp := time.Date(2023, 1, 27, 12, 14, 00, 00, time.UTC).Unix()
165+
payload := NewPayload().SetStaleDate(timestamp)
166+
b, _ := json.Marshal(payload)
167+
assert.Equal(t, `{"aps":{"stale-date":1674821640}}`, string(b))
168+
}
169+
170+
func TestEventEnd(t *testing.T) {
171+
payload := NewPayload().SetEvent(LiveActivityEventEnd)
172+
b, _ := json.Marshal(payload)
173+
assert.Equal(t, `{"aps":{"event":"end"}}`, string(b))
174+
}
175+
176+
func TestEventUpdate(t *testing.T) {
177+
payload := NewPayload().SetEvent(LiveActivityEventUpdate)
178+
b, _ := json.Marshal(payload)
179+
assert.Equal(t, `{"aps":{"event":"update"}}`, string(b))
180+
}
181+
182+
func TestTimestamp(t *testing.T) {
183+
timestamp := time.Date(2023, 1, 27, 12, 14, 00, 00, time.UTC).Unix()
184+
payload := NewPayload().SetTimestamp(timestamp)
185+
b, _ := json.Marshal(payload)
186+
assert.Equal(t, `{"aps":{"timestamp":1674821640}}`, string(b))
187+
}
188+
189+
func TestAttributesType(t *testing.T) {
190+
attributesType := "AdventureAttributes"
191+
payload := NewPayload().SetAttributesType(attributesType)
192+
b, _ := json.Marshal(payload)
193+
assert.Equal(t, `{"aps":{"attributes-type":"AdventureAttributes"}}`, string(b))
194+
}
195+
196+
func TestAttributes(t *testing.T) {
197+
attributes := map[string]interface{}{
198+
"currentHealthLevel": 100,
199+
"eventDescription": "Adventure has begun!",
200+
}
201+
payload := NewPayload().SetAttributes(attributes)
202+
b, _ := json.Marshal(payload)
203+
assert.Equal(
204+
t,
205+
`{"aps":{"attributes":{"currentHealthLevel":100,"eventDescription":"Adventure has begun!"}}}`,
206+
string(b),
207+
)
208+
}
209+
149210
func TestMdm(t *testing.T) {
150211
payload := NewPayload().Mdm("996ac527-9993-4a0a-8528-60b2b3c2f52b")
151212
b, _ := json.Marshal(payload)

0 commit comments

Comments
 (0)