Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 39 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ The Request body must have a notifications array. The following is a parameter t
| expiration | int | expiration for notification | - | only iOS |
| apns_id | string | A canonical UUID that identifies the notification | - | only iOS |
| collapse_id | string | An identifier you use to coalesce multiple notifications into a single notification for the user | - | only iOS |
| push_type | string | The type of the notification. The value of this header is alert or background. | - | only iOS |
| push_type | string | APNs push type header value (any valid APNs push type; e.g. `alert`, `background`, `liveactivity`). | - | only iOS; use `liveactivity` for Live Activities |
| badge | int | badge count | - | only iOS |
| category | string | the UIMutableUserNotificationCategory object | - | only iOS |
| alert | string array | payload of a iOS message | - | only iOS. See the [detail](#ios-alert-payload) |
Expand All @@ -694,10 +694,13 @@ The Request body must have a notifications array. The following is a parameter t
| volume | float32 | sets the volume value on the aps sound dictionary. | - | only iOS |
| interruption_level | string | defines the interruption level for the push notification. | - | only iOS(15.0+) |
| content-state | string array | dynamic and custom content for live-activity notification. | - | only iOS(16.1+) |
| timestamp | int | the UNIX time when sending the remote notification that updates or ends a Live Activity | - | only iOS(16.1+) |
| event | string | describes whether you update or end an ongoing Live Activity | - | only iOS(16.1+) |
| timestamp | int | the UNIX time when sending the remote notification that starts, updates or ends a Live Activity | - | only iOS(16.1+) |
| event | string | `start`, `update`, or `end` for a Live Activity | - | only iOS(16.1+); `start` requires iOS 17.2+ |
| stale-date | int | the date which a Live Activity becomes stale, or out of date | - | only iOS(16.1+) |
| dismissal-date | int | the UNIX time -timestamp- which a Live Activity will end and will be removed | - | only iOS(16.1+) |
| attributes-type | string | the ActivityAttributes type name used to start a Live Activity | - | only iOS(17.2+); required for `event: start` |
| attributes | object | fixed ActivityAttributes content used to start a Live Activity | - | only iOS(17.2+); required for `event: start` |
| relevance-score | float | relevance score used to order Live Activities / choose Dynamic Island presentation | - | only iOS(16.1+) |

### iOS alert payload

Expand Down Expand Up @@ -857,6 +860,39 @@ Support send notification from different environment. See the detail of [issue](
}
```

Send a Live Activity push-to-start notification (requires a push-to-start token and `push_type` / topic for Live Activities):

```json
{
"notifications": [
{
"tokens": ["push_to_start_token"],
"platform": 1,
"push_type": "liveactivity",
"topic": "com.example.app.push-type.liveactivity",
"event": "start",
"timestamp": 1685952000,
"content-state": {
"homeScore": 0,
"awayScore": 0,
"minute": 1,
"period": "1H"
},
"attributes-type": "FootballMatchAttributes",
"attributes": {
"homeTeam": "Team 1",
"awayTeam": "Team 2",
"league": "Premier League"
},
"alert": {
"title": "Team 1 vs Team 2",
"body": "Kick-off! The match has started."
}
}
]
}
```

### Android Example

Send normal notification.
Expand Down
13 changes: 8 additions & 5 deletions notify/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,14 @@ type PushNotification struct {

// live-activity support
// ref: https://apple.co/3MLe2DB
ContentState D `json:"content-state,omitempty"`
StaleDate int64 `json:"stale-date,omitempty"`
DismissalDate int64 `json:"dismissal-date"`
Event string `json:"event,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
ContentState D `json:"content-state,omitempty"`
StaleDate int64 `json:"stale-date,omitempty"`
DismissalDate int64 `json:"dismissal-date"`
Event string `json:"event,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
AttributesType string `json:"attributes-type,omitempty"`
Attributes D `json:"attributes,omitempty"`
RelevanceScore *float64 `json:"relevance-score,omitempty"`
}

// Bytes for queue message
Expand Down
9 changes: 9 additions & 0 deletions notify/notification_apns.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,15 @@ func setLiveActivityFields(p *payload.Payload, req *PushNotification) {
if req.Timestamp > 0 {
p.SetTimestamp(req.Timestamp)
}
if len(req.AttributesType) > 0 {
p.SetAttributesType(req.AttributesType)
}
if len(req.Attributes) > 0 {
p.SetAttributes(req.Attributes)
}
if req.RelevanceScore != nil {
p.RelevanceScore(float32(*req.RelevanceScore))
}
}

func iosAlertDictionary(
Expand Down
68 changes: 63 additions & 5 deletions notify/notification_apns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -960,15 +960,23 @@ func TestSetLiveActivityFields(t *testing.T) {
staleDate := time.Now().Unix()
dismissalDate := staleDate + 100
timestamp := time.Now().Unix()
relevanceScore := 100.0

req := &PushNotification{
ContentState: D{
"score": 10,
"homeScore": 1,
"awayScore": 0,
},
StaleDate: staleDate,
DismissalDate: dismissalDate,
Event: "update",
Timestamp: timestamp,
StaleDate: staleDate,
DismissalDate: dismissalDate,
Event: "update",
Timestamp: timestamp,
AttributesType: "FootballMatchAttributes",
Attributes: D{
"homeTeam": "Team 1",
"awayTeam": "Team 2",
},
RelevanceScore: &relevanceScore,
}

notification := GetIOSNotification(req)
Expand All @@ -978,10 +986,60 @@ func TestSetLiveActivityFields(t *testing.T) {
gotStaleDate, _ := jsonparser.GetInt(data, "aps", "stale-date")
gotEvent, _ := jsonparser.GetString(data, "aps", "event")
gotTimestamp, _ := jsonparser.GetInt(data, "aps", "timestamp")
gotAttributesType, _ := jsonparser.GetString(data, "aps", "attributes-type")
gotHomeTeam, _ := jsonparser.GetString(data, "aps", "attributes", "homeTeam")
gotRelevanceScore, _ := jsonparser.GetFloat(data, "aps", "relevance-score")

assert.Equal(t, staleDate, gotStaleDate)
assert.Equal(t, "update", gotEvent)
assert.Equal(t, timestamp, gotTimestamp)
assert.Equal(t, "FootballMatchAttributes", gotAttributesType)
assert.Equal(t, "Team 1", gotHomeTeam)
assert.InDelta(t, relevanceScore, gotRelevanceScore, 0.01)
}

func TestLiveActivityPushToStart(t *testing.T) {
timestamp := time.Now().Unix()

req := &PushNotification{
PushType: "liveactivity",
Topic: "com.example.app.push-type.liveactivity",
Event: "start",
Timestamp: timestamp,
ContentState: D{
"homeScore": 0,
"awayScore": 0,
"minute": 1,
"period": "1H",
},
AttributesType: "FootballMatchAttributes",
Attributes: D{
"homeTeam": "Team 1",
"awayTeam": "Team 2",
"league": "Premier League",
},
Alert: Alert{
Title: "Team 1 vs Team 2",
Body: "Kick-off! The match has started.",
},
}

notification := GetIOSNotification(req)
assert.Equal(t, apns2.EPushType("liveactivity"), notification.PushType)
assert.Equal(t, "com.example.app.push-type.liveactivity", notification.Topic)

dump, err := json.Marshal(notification.Payload)
assert.NoError(t, err)

gotEvent, _ := jsonparser.GetString(dump, "aps", "event")
gotAttributesType, _ := jsonparser.GetString(dump, "aps", "attributes-type")
gotHomeTeam, _ := jsonparser.GetString(dump, "aps", "attributes", "homeTeam")
gotTitle, _ := jsonparser.GetString(dump, "aps", "alert", "title")

assert.Equal(t, "start", gotEvent)
assert.Equal(t, "FootballMatchAttributes", gotAttributesType)
assert.Equal(t, "Team 1", gotHomeTeam)
assert.Equal(t, "Team 1 vs Team 2", gotTitle)
}

func TestSetNotificationPriority(t *testing.T) {
Expand Down
Loading