Skip to content
Open
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
11 changes: 10 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,13 @@ func (c *Client) PushWithContext(ctx Context, n *Notification) (*Response, error
return nil, err
}

url := c.Host + "/3/device/" + n.DeviceToken
url := c.Host
if n.ChannelID != "" {
url = url + "/4/broadcasts/apps/" + n.Topic
} else {
url = url + "/3/device/" + n.DeviceToken
}

request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
if err != nil {
return nil, err
Expand Down Expand Up @@ -224,6 +230,9 @@ func setHeaders(r *http.Request, n *Notification) {
if n.CollapseID != "" {
r.Header.Set("apns-collapse-id", n.CollapseID)
}
if n.ChannelID != "" {
r.Header.Set("apns-channel-id", n.ChannelID)
}
if n.Priority > 0 {
r.Header.Set("apns-priority", strconv.Itoa(n.Priority))
}
Expand Down
24 changes: 24 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,18 @@ func TestURL(t *testing.T) {
assert.NoError(t, err)
}

func TestBroadcastURL(t *testing.T) {
n := mockNotification()
n.ChannelID = "channel123"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "POST", r.Method)
assert.Equal(t, fmt.Sprintf("/4/broadcasts/apps/%s", n.Topic), r.URL.String())
}))
defer server.Close()
_, err := mockClient(server.URL).Push(n)
assert.NoError(t, err)
}

func TestDefaultHeaders(t *testing.T) {
n := mockNotification()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -169,6 +181,7 @@ func TestDefaultHeaders(t *testing.T) {
assert.Equal(t, "", r.Header.Get("apns-priority"))
assert.Equal(t, "", r.Header.Get("apns-topic"))
assert.Equal(t, "", r.Header.Get("apns-expiration"))
assert.Equal(t, "", r.Header.Get("apns-channel-id"))
assert.Equal(t, "", r.Header.Get("thread-id"))
assert.Equal(t, "alert", r.Header.Get("apns-push-type"))
}))
Expand Down Expand Up @@ -264,6 +277,17 @@ func TestExpirationHeader(t *testing.T) {
assert.NoError(t, err)
}

func TestChannelHeader(t *testing.T) {
n := mockNotification()
n.ChannelID = "channel123"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "channel123", r.Header.Get("apns-channel-id"))
}))
defer server.Close()
_, err := mockClient(server.URL).Push(n)
assert.NoError(t, err)
}

func TestPushTypeAlertHeader(t *testing.T) {
n := mockNotification()
n.PushType = apns.PushTypeAlert
Expand Down
5 changes: 5 additions & 0 deletions notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ type Notification struct {
// device.
DeviceToken string

// A base64-encoded string that identifies which channel to publish a
// payload to. This string is generated by APNs, using either the channel
// management API or the CloudKit console.
ChannelID string

// The topic of the remote notification, which is typically the bundle ID
// for your app. The certificate you create in the Apple Developer Member
// Center must include the capability for this topic. If your certificate
Expand Down
23 changes: 23 additions & 0 deletions payload/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const (
type ELiveActivityEvent string

const (
// LiveActivityEventStart is used to start an live activity.
LiveActivityEventStart ELiveActivityEvent = "start"

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

Expand Down Expand Up @@ -58,6 +61,8 @@ type aps struct {
Timestamp int64 `json:"timestamp,omitempty"`
AttributesType string `json:"attributes-type,omitempty"`
Attributes map[string]interface{} `json:"attributes,omitempty"`
InputPushChannel string `json:"input-push-channel,omitempty"`
InputPushToken int `json:"input-push-token,omitempty"`
}

type alert struct {
Expand Down Expand Up @@ -164,6 +169,24 @@ func (p *Payload) SetAttributes(attributes map[string]interface{}) *Payload {
return p
}

// SetInputPushChannel sets the aps input-push-channel field on the payload.
// This is used for push-to-start live activities for channels.
//
// {"aps":{"input-push-channel": channelID }}`
func (p *Payload) SetInputPushChannel(channelID string) *Payload {
p.aps().InputPushChannel = channelID
return p
}

// SetInputPushToken sets the aps input-push-token field on the payload.
// This is used for push-to-start live activities for channels.,
//
// {"aps":{"input-push-token": channelID }}`
func (p *Payload) SetInputPushToken(token int) *Payload {
p.aps().InputPushToken = token
return p
}

// Badge sets the aps badge on the payload.
// This will display a numeric badge on the app icon.
//
Expand Down
13 changes: 13 additions & 0 deletions payload/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,19 @@ func TestAttributes(t *testing.T) {
)
}

func TestInputPushChannel(t *testing.T) {
channelID := "channelid123"
payload := NewPayload().SetInputPushChannel(channelID)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"input-push-channel":"channelid123"}}`, string(b))
}

func TestInputPushToken(t *testing.T) {
payload := NewPayload().SetInputPushToken(1)
b, _ := json.Marshal(payload)
assert.Equal(t, `{"aps":{"input-push-token":1}}`, string(b))
}

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