-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathGcmServiceConnection.cs
More file actions
221 lines (178 loc) · 9.5 KB
/
GcmServiceConnection.cs
File metadata and controls
221 lines (178 loc) · 9.5 KB
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
using System;
using System.Threading.Tasks;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using System.Net;
using PushSharp.Core;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
namespace PushSharp.Google
{
public class GcmServiceConnectionFactory : IServiceConnectionFactory<GcmNotification>
{
public GcmServiceConnectionFactory (GcmConfiguration configuration)
{
Configuration = configuration;
}
public GcmConfiguration Configuration { get; private set; }
public IServiceConnection<GcmNotification> Create()
{
return new GcmServiceConnection (Configuration);
}
}
public class GcmServiceBroker : ServiceBroker<GcmNotification>
{
public GcmServiceBroker (GcmConfiguration configuration) : base (new GcmServiceConnectionFactory (configuration))
{
}
}
public class GcmServiceConnection : IServiceConnection<GcmNotification>
{
public GcmServiceConnection (GcmConfiguration configuration)
{
Configuration = configuration;
http = new HttpClient ();
http.DefaultRequestHeaders.UserAgent.Clear ();
http.DefaultRequestHeaders.UserAgent.Add (new ProductInfoHeaderValue ("PushSharp", "3.0"));
http.DefaultRequestHeaders.TryAddWithoutValidation ("Authorization", "key=" + Configuration.SenderAuthToken);
}
public GcmConfiguration Configuration { get; private set; }
readonly HttpClient http;
public async Task Send (GcmNotification notification)
{
var json = notification.GetJson ();
using (var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"))
using (var response = await http.PostAsync(Configuration.GcmUrl, content))
{
if (response.IsSuccessStatusCode) {
await processResponseOk(response, notification).ConfigureAwait(false);
} else {
await processResponseError(response, notification).ConfigureAwait(false);
}
}
}
async Task processResponseOk (HttpResponseMessage httpResponse, GcmNotification notification)
{
var multicastResult = new GcmMulticastResultException ();
var result = new GcmResponse () {
ResponseCode = GcmResponseCode.Ok,
OriginalNotification = notification
};
var str = await httpResponse.Content.ReadAsStringAsync ();
var json = JObject.Parse (str);
result.NumberOfCanonicalIds = json.Value<long> ("canonical_ids");
result.NumberOfFailures = json.Value<long> ("failure");
result.NumberOfSuccesses = json.Value<long> ("success");
var jsonResults = json ["results"] as JArray ?? new JArray ();
foreach (var r in jsonResults) {
var msgResult = new GcmMessageResult ();
msgResult.MessageId = r.Value<string> ("message_id");
msgResult.CanonicalRegistrationId = r.Value<string> ("registration_id");
msgResult.ResponseStatus = GcmResponseStatus.Ok;
if (!string.IsNullOrEmpty (msgResult.CanonicalRegistrationId))
msgResult.ResponseStatus = GcmResponseStatus.CanonicalRegistrationId;
else if (r ["error"] != null) {
var err = r.Value<string> ("error") ?? "";
msgResult.ResponseStatus = GetGcmResponseStatus (err);
}
result.Results.Add (msgResult);
}
int index = 0;
//Loop through every result in the response
// We will raise events for each individual result so that the consumer of the library
// can deal with individual registrationid's for the notification
foreach (var r in result.Results) {
var singleResultNotification = GcmNotification.ForSingleResult (result, index);
singleResultNotification.MessageId = r.MessageId;
if (r.ResponseStatus == GcmResponseStatus.Ok) { // Success
multicastResult.Succeeded.Add (singleResultNotification);
} else if (r.ResponseStatus == GcmResponseStatus.CanonicalRegistrationId) { //Need to swap reg id's
//Swap Registrations Id's
var newRegistrationId = r.CanonicalRegistrationId;
var oldRegistrationId = string.Empty;
if (singleResultNotification.RegistrationIds != null && singleResultNotification.RegistrationIds.Count > 0)
{
oldRegistrationId = singleResultNotification.RegistrationIds[0];
}
else if (!string.IsNullOrEmpty(singleResultNotification.To))
{
oldRegistrationId = singleResultNotification.To;
}
multicastResult.Failed.Add (singleResultNotification,
new DeviceSubscriptionExpiredException (singleResultNotification) {
OldSubscriptionId = oldRegistrationId,
NewSubscriptionId = newRegistrationId
});
} else if (r.ResponseStatus == GcmResponseStatus.Unavailable) { // Unavailable
multicastResult.Failed.Add (singleResultNotification, new GcmNotificationException (singleResultNotification, "Unavailable Response Status"));
} else if (r.ResponseStatus == GcmResponseStatus.NotRegistered) { //Bad registration Id
var oldRegistrationId = string.Empty;
if (singleResultNotification.RegistrationIds != null && singleResultNotification.RegistrationIds.Count > 0)
{
oldRegistrationId = singleResultNotification.RegistrationIds[0];
}
else if (!string.IsNullOrEmpty(singleResultNotification.To))
{
oldRegistrationId = singleResultNotification.To;
}
multicastResult.Failed.Add (singleResultNotification,
new DeviceSubscriptionExpiredException (singleResultNotification) {
OldSubscriptionId = oldRegistrationId });
} else {
multicastResult.Failed.Add (singleResultNotification, new GcmNotificationException (singleResultNotification, "Unknown Failure: " + r.ResponseStatus));
}
index++;
}
// If we only have 1 total result, it is not *multicast*,
if (multicastResult.Succeeded.Count + multicastResult.Failed.Count == 1) {
// If not multicast, and succeeded, don't throw any errors!
if (multicastResult.Succeeded.Count == 1)
return;
// Otherwise, throw the one single failure we must have
throw multicastResult.Failed.First ().Value;
}
// If we get here, we must have had a multicast message
// throw if we had any failures at all (otherwise all must be successful, so throw no error
if (multicastResult.Failed.Count > 0)
throw multicastResult;
}
async Task processResponseError (HttpResponseMessage httpResponse, GcmNotification notification)
{
string responseBody = null;
try {
responseBody = await httpResponse.Content.ReadAsStringAsync ().ConfigureAwait (false);
} catch { }
//401 bad auth token
if (httpResponse.StatusCode == HttpStatusCode.Unauthorized)
throw new UnauthorizedAccessException ("GCM Authorization Failed");
if (httpResponse.StatusCode == HttpStatusCode.BadRequest)
throw new GcmNotificationException (notification, "HTTP 400 Bad Request", responseBody);
if ((int)httpResponse.StatusCode >= 500 && (int)httpResponse.StatusCode < 600) {
//First try grabbing the retry-after header and parsing it.
var retryAfterHeader = httpResponse.Headers.RetryAfter;
if (retryAfterHeader != null && retryAfterHeader.Delta.HasValue) {
var retryAfter = retryAfterHeader.Delta.Value;
throw new RetryAfterException (notification, "GCM Requested Backoff", DateTime.UtcNow + retryAfter);
}
}
throw new GcmNotificationException (notification, "GCM HTTP Error: " + httpResponse.StatusCode, responseBody);
}
static GcmResponseStatus GetGcmResponseStatus (string str)
{
var enumType = typeof(GcmResponseStatus);
foreach (var name in Enum.GetNames (enumType)) {
var enumMemberAttribute = ((EnumMemberAttribute[])enumType.GetField (name).GetCustomAttributes (typeof(EnumMemberAttribute), true)).Single ();
if (enumMemberAttribute.Value.Equals (str, StringComparison.InvariantCultureIgnoreCase))
return (GcmResponseStatus)Enum.Parse (enumType, name);
}
//Default
return GcmResponseStatus.Error;
}
~GcmServiceConnection()
{
http.Dispose();
}
}
}