-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathclient.go
146 lines (137 loc) · 5.3 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package apns
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"golang.org/x/net/http2"
)
// Timeout contains the maximum waiting time connection to the APNS server.
var Timeout = 15 * time.Second
// Client supports APNs Provider API.
//
// The APNs provider API lets you send remote notifications to your app on iOS,
// tvOS, and macOS devices, and to Apple Watch via iOS. The API is based on the
// HTTP/2 network protocol. Each interaction starts with a POST request,
// containing a JSON payload, that you send from your provider server to APNs.
// APNs then forwards the notification to your app on a specific user device.
//
// The first step in sending a remote notification is to establish a connection
// with the appropriate APNs server Host:
// Development server: api.development.push.apple.com:443
// Production server: api.push.apple.com:443
//
// Note: You can alternatively use port 2197 when communicating with APNs. You
// might do this, for example, to allow APNs traffic through your firewall but
// to block other HTTPS traffic.
//
// The APNs server allows multiple concurrent streams for each connection. The
// exact number of streams is based on the authentication method used (i.e.
// provider certificate or token) and the server load, so do not assume a
// specific number of streams. When you connect to APNs without a provider
// certificate, only one stream is allowed on the connection until you send a
// push message with valid token.
//
// It is recommended to close all existing connections to APNs and open new
// connections when existing certificate or the key used to sign provider tokens
// is revoked.
type Client struct {
Host string // http URL
ci *CertificateInfo // certificate
token *ProviderToken // provider token
httpСlient *http.Client // http client for push
}
func newClient(certificate *tls.Certificate, pt *ProviderToken) *Client {
client := &Client{
Host: "https://api.push.apple.com",
httpСlient: &http.Client{Timeout: Timeout},
}
if pt != nil {
client.token = pt
}
if certificate != nil {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{*certificate}}}
if err := http2.ConfigureTransport(transport); err != nil {
panic(err) // HTTP/2 initialization error
}
client.httpСlient.Transport = transport
client.ci = GetCertificateInfo(certificate)
if !client.ci.Production {
client.Host = "https://api.development.push.apple.com"
}
}
return client
}
func New(certificate tls.Certificate) *Client {
return newClient(&certificate, nil)
}
// NewWithToken returns an initialized Client with JSON Web Token (JWT)
// authentication support.
func NewWithToken(pt *ProviderToken) *Client {
return newClient(nil, pt)
}
// Push send push notification to APNS API.
//
// The APNs Provider API consists of a request and a response that you configure
// and send using an HTTP/2 POST command. You use the request to send a push
// notification to the APNs server and use the response to determine the results
// of that request.
//
// Response from APNs:
// - The apns-id value from the request. If no value was included in the
// request, the server creates a new UUID and returns it in this header.
// - :status - the HTTP status code.
// - reason - the error indicating the reason for the failure. The error code
// is specified as a string.
// - timestamp - if the value in the :status header is 410, the value of this
// key is the last time at which APNs confirmed that the device token was no
// longer valid for the topic. Stop pushing notifications until the device
// registers a token with a later timestamp with your provider.
func (c *Client) Push(notification Notification) (id string, err error) {
req, err := notification.request(c.Host)
if err != nil {
return "", err
}
req.Header.Set("user-agent", "mdigger-apns/3.1")
// add default certificate topic
if notification.Topic == "" && c.ci != nil && len(c.ci.Topics) > 0 {
// If your certificate includes multiple topics, you must specify a
// value for this header.
req.Header.Set("apns-topic", c.ci.BundleID)
}
if c.token != nil {
// The provider token that authorizes APNs to send push notifications
// for the specified topics. The token is in Base64URL-encoded JWT
// format, specified as bearer <provider token>.
// When the provider certificate is used to establish a connection, this
// request header is ignored.
if token, err := c.token.JWT(); err == nil {
req.Header.Set("authorization", fmt.Sprintf("bearer %s", token))
}
}
resp, err := c.httpСlient.Do(req)
if err, ok := err.(*url.Error); ok {
// If APNs decides to terminate an established HTTP/2 connection, it
// sends a GOAWAY frame. The GOAWAY frame includes JSON data in its
// payload with a reason key, whose value indicates the reason for the
// connection termination.
if err, ok := err.Err.(http2.GoAwayError); ok {
return "", parseError(0, strings.NewReader(err.DebugData))
}
}
if err != nil {
return "", err
}
// For a successful request, the body of the response is empty. On failure,
// the response body contains a JSON dictionary.
defer resp.Body.Close()
id = resp.Header.Get("apns-id")
if resp.StatusCode == http.StatusOK {
return id, nil
}
return id, parseError(resp.StatusCode, resp.Body)
}