-
Notifications
You must be signed in to change notification settings - Fork 5
/
FCMPushNotificationService.cs
executable file
·192 lines (165 loc) · 6.89 KB
/
FCMPushNotificationService.cs
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
using ACB.FCMPushNotifications.Data;
using ACB.FCMPushNotifications.Models;
using ACB.FCMPushNotifications.Utils;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace ACB.FCMPushNotifications
{
/// <summary>
/// Service class to send push notifications using FCM.
/// </summary>
public class FCMPushNotificationService : IPushNotificationService
{
private HttpClient _Http { get; set; }
private NotifServerDbContext _db { get; set; }
private string FCMServerToken { get; set; }
/// <summary>
/// Constructor
/// </summary>
public FCMPushNotificationService(IOptions<PushNotificationServiceOptions> options,
NotifServerDbContext db)
{
if (string.IsNullOrWhiteSpace(options.Value.FCMServerToken))
{
throw new Exception("FCM Server Key is required");
}
FCMServerToken = options.Value.FCMServerToken;
_db = db;
_Http = new HttpClient
{
BaseAddress = new Uri("https://fcm.googleapis.com/fcm/")
};
_Http.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"key={FCMServerToken}");
}
/// <summary>
/// Send notification to users
/// </summary>
public async Task<List<NotificationResult>> NotifyAsync(NotificationRequest request)
{
var userTokensQuery = _db.UserDeviceTokens.Where(ut => request.UserIds.Contains(ut.UserId));
if (request.LimitByPlatform.HasValue)
{
userTokensQuery = userTokensQuery.Where(ut => ut.Platform == request.LimitByPlatform.Value);
}
var userTokens = userTokensQuery.ToList();
if (userTokens.Count == 0)
{
return new List<NotificationResult>();
}
var ttl = request.TimeToLive?.TotalSeconds ?? TimeSpan.FromDays(28).TotalSeconds;
var notification = new NotificationMessage
{
DryRun = request.DryRun,
RegistrationIds = userTokens.Select(r => r.Token).ToList(),
TimeToLive = Math.Max(0, ttl),
Notification = new NotificationPayload
{
Title = request.Title,
Body = request.Message
},
Data = request.Data
};
var jsonPayload = await Task.Run(() =>
JsonConvert.SerializeObject(
notification,
Formatting.None,
new JsonSerializerSettings
{
ContractResolver = new SnakeCasePropertyNameContractResolver()
}
)
);
var payload = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await _Http.PostAsync("send", payload);
var content = await response.Content.ReadAsStringAsync();
var json = await Task.Run(() => JsonConvert.DeserializeObject<FCMResponse>(content));
switch (response.StatusCode)
{
case HttpStatusCode.OK:
var tasks = new List<Task>();
var results = new List<NotificationResult>();
for (var i = 0; i < json.Results.Count; i++)
{
var result = json.Results[i];
var userToken = userTokens[i];
results.Add(new NotificationResult
{
UserId = userToken.UserId,
Success = !result.Error.HasValue,
Error = result.Error
});
if (!string.IsNullOrWhiteSpace(result.RegistrationId))
{
tasks.Add(UnregisterUserAsync(userToken.UserId, userToken.Token));
tasks.Add(
RegisterUserAsync(
userToken.UserId, result.RegistrationId, userToken.Platform
)
);
}
else if (result.Error.HasValue)
{
tasks.Add(HandleResponseError(userToken, result));
}
}
await Task.WhenAll(tasks.ToArray());
return results;
case (HttpStatusCode)400:
throw new Exception("Invalid JSON");
case (HttpStatusCode)401:
throw new Exception("Authentication Error");
default:
case (HttpStatusCode)500:
throw new Exception($"Internal Server Error: Multicast Id: {json?.MulticastId}");
}
}
private Task HandleResponseError(UserDeviceToken userToken, FCMResponse.Result result)
{
switch (result.Error)
{
case NotificationResultError.InvalidRegistration:
case NotificationResultError.NotRegistered:
return UnregisterUserAsync(userToken.UserId, userToken.Token);
}
return Task.CompletedTask;
}
/// <summary>
/// Save user device token
/// </summary>
public async Task RegisterUserAsync(string userId, string userToken, DevicePlatform platform)
{
var isDuplicate = _db.UserDeviceTokens.Any(ur => ur.UserId == userId
&& ur.Token == userToken);
if (!isDuplicate)
{
_db.UserDeviceTokens.Add(new UserDeviceToken
{
UserId = userId,
Token = userToken,
Platform = platform
});
await _db.SaveChangesAsync();
}
}
/// <summary>
/// Delete user device token
/// </summary>
public async Task UnregisterUserAsync(string userId, string userToken)
{
var userRegId = _db.UserDeviceTokens.FirstOrDefault(ur => ur.UserId == userId
&& ur.Token == userToken);
if (userRegId != null)
{
_db.UserDeviceTokens.Remove(userRegId);
await _db.SaveChangesAsync();
}
}
}
}