Skip to content
This repository has been archived by the owner on Dec 14, 2024. It is now read-only.

Commit

Permalink
Merge pull request #8 from life360/miguel/CCE-6006-apns2-upgrade-to-v…
Browse files Browse the repository at this point in the history
…0.23.0

* Add iOS 15 payload additions (sideshow#185)

* Add iOS 15 payload additions

- Add interruption-level to payload
 interruption-level options:
    - passive
    - active (default if none is passed to apns)
    - time-sensitive
    - critical (requires Apple entitlement)

- Add relevance-score to payload
relevance-score is a number between 0 and 1
The highest score gets featured in the notification summary.

* Update readme re iOS 15 features

* Fix documentation typo

Note that at the time of writing [Apple docs](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification#2943360) have a typo, showing `time-senstive` as opposed to `time-sensitive`.

Testing has shown that the correct spelling `time-sensitive` does indeed work.

* Update builder.go

Alphabetically order keys in struct as per @Singwai suggestion.

* Allow relevance-score to be set to zero.

* Updated to single InterruptionLevel function

Co-authored-by: Chris Haines <[email protected]>

* Go modules support (sideshow#181)

* Go Modules Support

* Replace Travis with Github Actions (sideshow#190)

* Replace Travis with Github Actions

* Fix context timeout error

* Add Github workflow badge

* Add coverage to actions

* Update jwt library (sideshow#191)

- Resolves sideshow#186
- Resolves sideshow#187
- Resolves sideshow#189

* Use NewReader instead of NewBuffer (sideshow#193)

* Add location push type (sideshow#194)

* Add location push type
* Fix Typo
* Add InvalidPushType reason error code

* Use type switch with assignment syntax (sideshow#196)

* Use POST http constant (sideshow#203)

* Use if type conditional (sideshow#198)

In that case if type is much simpler and look better.

* Simplify FromPemBytes conditional (sideshow#197)

- Simplify logic. strings.HasSuffix can check both for suffix and for equality

* Use NewRequestWithContext instead of nil checking (sideshow#200)

* Use appropriate type cast functions (sideshow#199)

* Use appropriate type cast functions
* avoid fmt usage

* Fix double pointer (sideshow#195)

* Refactor request/response variable names (sideshow#205)

r *Request is more consistent with n *Notification

* Feature/updated http2 transport (sideshow#209)

* Update http2 transport
* Add ReadIdleTimeout for ping frames
* Update defaults for TCP Keepalive

* Revert "CF-153: Updating Log to print issuedAt (#7)"

This reverts commit 69ea756.

* Revert "CF-133: Adding PushTypeLocation (#6)"

This reverts commit 3668879.

* Reverting Using int64 for timestamp (8fac21d)

* Changing module name

* gitignore idea directory

---------

Co-authored-by: Neil Morton <[email protected]>
Co-authored-by: Chris Haines <[email protected]>
Co-authored-by: jbendotnet <[email protected]>
Co-authored-by: Adam Jones <[email protected]>
Co-authored-by: Mikhail Faraponov <[email protected]>
  • Loading branch information
6 people authored Jul 18, 2023
2 parents 69ea756 + 937c735 commit 408c8aa
Show file tree
Hide file tree
Showing 226 changed files with 97,197 additions and 10,223 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: tests

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go: [1.16.x, 1.17.x, 1.18.x]
steps:
- uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go }}

- name: Install goveralls
run: go install github.com/mattn/goveralls@latest

- name: Build
run: go build -v ./...

- name: Run tests
run: go test -race -covermode=atomic -coverprofile=covprofile -v ./...

- name: Update coverage
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: goveralls -coverprofile=covprofile -service=github
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ _testmain.go
/*.cer
/*.p8

.DS_Store
.DS_Store

.idea
29 changes: 0 additions & 29 deletions .travis.yml

This file was deleted.

6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

APNS/2 is a go package designed for simple, flexible and fast Apple Push Notifications on iOS, OSX and Safari using the new HTTP/2 Push provider API.

[![Build Status](https://travis-ci.org/sideshow/apns2.svg?branch=master)](https://travis-ci.org/sideshow/apns2) [![Coverage Status](https://coveralls.io/repos/sideshow/apns2/badge.svg?branch=master&service=github)](https://coveralls.io/github/sideshow/apns2?branch=master) [![GoDoc](https://godoc.org/github.com/sideshow/apns2?status.svg)](https://godoc.org/github.com/sideshow/apns2)
[![Build Status](https://github.com/sideshow/apns2/actions/workflows/tests.yml/badge.svg)](https://github.com/sideshow/apns2/actions/workflows/tests.yml) [![Coverage Status](https://coveralls.io/repos/sideshow/apns2/badge.svg?branch=master&service=github)](https://coveralls.io/github/sideshow/apns2?branch=master) [![GoDoc](https://godoc.org/github.com/sideshow/apns2?status.svg)](https://godoc.org/github.com/sideshow/apns2)

## Features

Expand All @@ -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 features interruptionLevel and relevanceScore
- Supports persistent connections to APNs
- Supports VoIP/PushKit notifications (iOS 8 and later)
- Modular & easy to use
Expand All @@ -26,6 +27,7 @@ go get -u github.com/sideshow/apns2
```

If you are running the test suite you will also need to install testify:

```sh
go get -u github.com/stretchr/testify
```
Expand Down Expand Up @@ -59,7 +61,7 @@ func main() {
// client := apns2.NewClient(cert).Development()
// For apps published to the app store or installed as an ad-hoc distribution use Production()

client := apns2.NewClient(cert).Production()
client := apns2.NewClient(cert).Production()
res, err := client.Push(notification)

if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions apns2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"os"
"strings"

"github.com/sideshow/apns2"
"github.com/sideshow/apns2/certificate"
"github.com/life360/apns2"
"github.com/life360/apns2/certificate"
"gopkg.in/alecthomas/kingpin.v2"
)

Expand Down
2 changes: 1 addition & 1 deletion certificate/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func FromPemBytes(bytes []byte, password string) (tls.Certificate, error) {
if block.Type == "CERTIFICATE" {
cert.Certificate = append(cert.Certificate, block.Bytes)
}
if block.Type == "PRIVATE KEY" || strings.HasSuffix(block.Type, "PRIVATE KEY") {
if strings.HasSuffix(block.Type, "PRIVATE KEY") {
key, err := unencryptPrivateKey(block, password)
if err != nil {
return tls.Certificate{}, err
Expand Down
2 changes: 1 addition & 1 deletion certificate/certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"io/ioutil"
"testing"

"github.com/sideshow/apns2/certificate"
"github.com/life360/apns2/certificate"
"github.com/stretchr/testify/assert"
)

Expand Down
63 changes: 33 additions & 30 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"strconv"
"time"

"github.com/life360/apns2/token"
Expand All @@ -28,16 +28,24 @@ const (
var DefaultHost = HostDevelopment

var (
// TLSDialTimeout is the maximum amount of time a dial will wait for a connect
// to complete.
TLSDialTimeout = 20 * time.Second
// HTTPClientTimeout specifies a time limit for requests made by the
// HTTPClient. The timeout includes connection time, any redirects,
// and reading the response body.
HTTPClientTimeout = 60 * time.Second

// ReadIdleTimeout is the timeout after which a health check using a ping
// frame will be carried out if no frame is received on the connection. If
// zero, no health check is performed.
ReadIdleTimeout = 15 * time.Second

// TCPKeepAlive specifies the keep-alive period for an active network
// connection. If zero, keep-alives are not enabled.
TCPKeepAlive = 60 * time.Second
// connection. If zero, keep-alive probes are sent with a default value
// (currently 15 seconds)
TCPKeepAlive = 15 * time.Second

// TLSDialTimeout is the maximum amount of time a dial will wait for a connect
// to complete.
TLSDialTimeout = 20 * time.Second
)

// DialTLS is the default dial function for creating TLS connections for
Expand Down Expand Up @@ -90,6 +98,7 @@ func NewClient(certificate tls.Certificate) *Client {
transport := &http2.Transport{
TLSClientConfig: tlsConfig,
DialTLS: DialTLS,
ReadIdleTimeout: ReadIdleTimeout,
}
return &Client{
HTTPClient: &http.Client{
Expand All @@ -111,7 +120,8 @@ func NewClient(certificate tls.Certificate) *Client {
// connection and disconnection as a denial-of-service attack.
func NewTokenClient(token *token.Token) *Client {
transport := &http2.Transport{
DialTLS: DialTLS,
DialTLS: DialTLS,
ReadIdleTimeout: ReadIdleTimeout,
}
return &Client{
Token: token,
Expand Down Expand Up @@ -143,7 +153,7 @@ func (c *Client) Production() *Client {
//
// Use PushWithContext if you need better cancellation and timeout control.
func (c *Client) Push(n *Notification) (*Response, error) {
return c.PushWithContext(nil, n)
return c.PushWithContext(context.Background(), n)
}

// PushWithContext sends a Notification to the APNs gateway. Context carries a
Expand All @@ -161,33 +171,33 @@ func (c *Client) PushWithContext(ctx Context, n *Notification) (*Response, error
return nil, err
}

url := fmt.Sprintf("%v/3/device/%v", c.Host, n.DeviceToken)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
url := c.Host + "/3/device/" + n.DeviceToken
request, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
if err != nil {
return nil, err
}

if c.Token != nil {
c.setTokenHeader(req)
c.setTokenHeader(request)
}

setHeaders(req, n)
setHeaders(request, n)

httpRes, err := c.requestWithContext(ctx, req)
response, err := c.HTTPClient.Do(request)
if err != nil {
return nil, err
}
defer httpRes.Body.Close()
defer response.Body.Close()

response := &Response{}
response.StatusCode = httpRes.StatusCode
response.ApnsID = httpRes.Header.Get("apns-id")
r := &Response{}
r.StatusCode = response.StatusCode
r.ApnsID = response.Header.Get("apns-id")

decoder := json.NewDecoder(httpRes.Body)
if err := decoder.Decode(&response); err != nil && err != io.EOF {
decoder := json.NewDecoder(response.Body)
if err := decoder.Decode(r); err != nil && err != io.EOF {
return &Response{}, err
}
return response, nil
return r, nil
}

// CloseIdleConnections closes any underlying connections which were previously
Expand All @@ -199,7 +209,7 @@ func (c *Client) CloseIdleConnections() {

func (c *Client) setTokenHeader(r *http.Request) {
bearer := c.Token.GenerateIfExpired()
r.Header.Set("authorization", fmt.Sprintf("bearer %v", bearer))
r.Header.Set("authorization", "bearer "+bearer)
}

func setHeaders(r *http.Request, n *Notification) {
Expand All @@ -214,10 +224,10 @@ func setHeaders(r *http.Request, n *Notification) {
r.Header.Set("apns-collapse-id", n.CollapseID)
}
if n.Priority > 0 {
r.Header.Set("apns-priority", fmt.Sprintf("%v", n.Priority))
r.Header.Set("apns-priority", strconv.Itoa(n.Priority))
}
if !n.Expiration.IsZero() {
r.Header.Set("apns-expiration", fmt.Sprintf("%v", n.Expiration.Unix()))
r.Header.Set("apns-expiration", strconv.FormatInt(n.Expiration.Unix(), 10))
}
if n.PushType != "" {
r.Header.Set("apns-push-type", string(n.PushType))
Expand All @@ -226,10 +236,3 @@ func setHeaders(r *http.Request, n *Notification) {
}

}

func (c *Client) requestWithContext(ctx Context, req *http.Request) (*http.Response, error) {
if ctx != nil {
req = req.WithContext(ctx)
}
return c.HTTPClient.Do(req)
}
4 changes: 2 additions & 2 deletions client_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"testing"
"time"

"github.com/sideshow/apns2"
"github.com/sideshow/apns2/certificate"
"github.com/life360/apns2"
"github.com/life360/apns2/certificate"
"github.com/stretchr/testify/assert"
)

Expand Down
39 changes: 34 additions & 5 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"net"
Expand All @@ -17,9 +18,9 @@ import (

"golang.org/x/net/http2"

apns "github.com/sideshow/apns2"
"github.com/sideshow/apns2/certificate"
"github.com/sideshow/apns2/token"
apns "github.com/life360/apns2"
"github.com/life360/apns2/certificate"
"github.com/life360/apns2/token"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -139,8 +140,10 @@ func TestDialTLSTimeout(t *testing.T) {
if _, e = dialTLS("tcp", address, nil); e == nil {
t.Fatal("Dial completed successfully")
}
if !strings.Contains(e.Error(), "timed out") {
t.Errorf("resulting error not a timeout: %s", e)
// Go 1.7.x and later will return a context deadline exceeded error
// Previous versions will return a time out
if !strings.Contains(e.Error(), "timed out") && !errors.Is(e, context.DeadlineExceeded) {
t.Errorf("Unexpected error: %s", e)
}
}

Expand Down Expand Up @@ -208,6 +211,21 @@ func TestClientPushWithContext(t *testing.T) {
assert.Equal(t, res.ApnsID, apnsID)
}

func TestClientPushWithNilContext(t *testing.T) {
n := mockNotification()
var apnsID = "02ABC856-EF8D-4E49-8F15-7B8A61D978D6"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("apns-id", apnsID)
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

res, err := mockClient(server.URL).PushWithContext(nil, n)
assert.EqualError(t, err, "net/http: nil Context")
assert.Nil(t, res)
}

func TestHeaders(t *testing.T) {
n := mockNotification()
n.ApnsID = "84DB694F-464F-49BD-960A-D6DB028335C9"
Expand Down Expand Up @@ -249,6 +267,17 @@ func TestPushTypeBackgroundHeader(t *testing.T) {
assert.NoError(t, err)
}

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

func TestPushTypeVOIPHeader(t *testing.T) {
n := mockNotification()
n.PushType = apns.PushTypeVOIP
Expand Down
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/life360/apns2

go 1.15

require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 // indirect
github.com/golang-jwt/jwt/v4 v4.4.1
github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20170512130425-ab89591268e0
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)
Loading

0 comments on commit 408c8aa

Please sign in to comment.