-
Notifications
You must be signed in to change notification settings - Fork 12
/
client.go
127 lines (106 loc) · 3.05 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
package fcm
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httputil"
)
const (
endpointFormat = "https://fcm.googleapis.com/v1/projects/%s/messages:send"
)
// Client abstracts the interaction between the application server and the
// FCM server via HTTP protocol. The developer must obtain a service account
// private key in JSON and the Firebase project id from the Firebase console and pass it to the `Client`
// so that it can perform authorized requests on the application server's behalf.
type Client struct {
// https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send
projectID string
endpoint string
client *http.Client
tokenProvider *tokenProvider
}
// NewClient creates new Firebase Cloud Messaging Client based on a json service account file credentials file.
func NewClient(projectID string, credentialsLocation string, opts ...Option) (*Client, error) {
tp, err := newTokenProvider(credentialsLocation)
if err != nil {
return nil, err
}
c := &Client{
endpoint: fmt.Sprintf(endpointFormat, projectID),
client: http.DefaultClient,
tokenProvider: tp,
}
for _, o := range opts {
if err := o(c); err != nil {
return nil, err
}
}
return c, nil
}
// Send sends a message to the FCM server.
func (c *Client) Send(req *SendRequest) (*Message, error) {
// validate
if err := req.Message.Validate(); err != nil {
return nil, err
}
// marshal message
data, err := json.Marshal(req)
if err != nil {
return nil, err
}
return c.send(data)
}
// send sends a request.
func (c *Client) send(data []byte) (*Message, error) {
// create request
req, err := http.NewRequest("POST", c.endpoint, bytes.NewBuffer(data))
if err != nil {
return nil, err
}
// get bearer token
token, err := c.tokenProvider.token()
if err != nil {
return nil, err
}
// add headers
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
req.Header.Add("Content-Type", "application/json")
// execute request
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
requestBytes, _ := httputil.DumpRequest(req, true)
responseBytes, _ := httputil.DumpResponse(resp, true)
if resp.StatusCode >= http.StatusInternalServerError {
return nil, HttpError{
RequestDump: string(requestBytes),
ResponseDump: string(responseBytes),
Err: fmt.Errorf(fmt.Sprintf("%d error: %s", resp.StatusCode, resp.Status)),
}
}
return nil, HttpError{
RequestDump: string(requestBytes),
ResponseDump: string(responseBytes),
Err: fmt.Errorf("%d error: %s", resp.StatusCode, resp.Status),
}
}
// build return
response := new(Message)
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return nil, err
}
return response, nil
}
// HttpError contains the dump of the request and response for debugging purposes.
type HttpError struct {
RequestDump string
ResponseDump string
Err error
}
func (fcmError HttpError) Error() string {
return fcmError.Err.Error()
}