Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
FirebaseClient.cs
1using System;
4using System.Text;
5using System.Threading.Tasks;
6using Waher.Content;
8using Waher.Events;
10using Waher.Script;
12
14{
24 public class FirebaseClient : CommunicationLayer, IDisposable
25 {
26 #region Construction
27
28 private static readonly Uri productionEndpoint = new Uri("https://fcm.googleapis.com/");
29 private const string scope = "https://www.googleapis.com/auth/firebase.messaging"; //"https://www.googleapis.com/auth/cloud-platform";
30
31 private readonly string projectId;
32 private readonly string privateKeyId;
33 private readonly string clientEmail;
34 private readonly string clientId;
35 private readonly string authUri;
36 private readonly string tokenUri;
37 private readonly string universeDomain;
38 private readonly bool test;
39 private readonly Uri sendNotificationUri;
40 private JwtFactory factory;
41 private string token = null;
42 private DateTime tokenTimestamp = DateTime.MinValue;
43
44 private FirebaseClient(string ProjectId, string PrivateKeyId, JwtFactory Factory, string ClientEMail, string ClientId,
45 string AuthUri, string TokenUri, string UniverseDomain, bool Test, params ISniffer[] Sniffers)
46 : base(false, Sniffers)
47 {
48 this.projectId = ProjectId;
49 this.privateKeyId = PrivateKeyId;
50 this.clientEmail = ClientEMail;
51 this.clientId = ClientId;
52 this.authUri = AuthUri;
53 this.tokenUri = TokenUri;
54 this.universeDomain = UniverseDomain;
55 this.factory = Factory;
56 this.test = Test;
57 this.sendNotificationUri = new Uri(productionEndpoint, "v1/projects/" + this.projectId + "/messages:send");
58 }
59
63 public string ProjectId => this.projectId;
64
68 public string PrivateKeyId => this.privateKeyId;
69
73 public string ClientEMail => this.clientEmail;
74
78 public string ClientId => this.clientId;
79
83 public string AuthUri => this.authUri;
84
88 public string TokenUri => this.tokenUri;
89
93 public string UniverseDomain => this.universeDomain;
94
98 public bool Test => this.test;
99
107 public static async Task<FirebaseClient> CreateAsync(string GoogleServiceAccountJson, bool Test, params ISniffer[] Sniffers)
108 {
109 if (!(JSON.Parse(GoogleServiceAccountJson) is Dictionary<string, object> Parsed) ||
110 !Parsed.TryGetValue("type", out object Obj) || !(Obj is string Type))
111 {
112 throw new ArgumentException("Invalid Google Service Account JSON file.", nameof(GoogleServiceAccountJson));
113 }
114
115 if (Type != "service_account")
116 throw new ArgumentException("File does not specify a Google Service account.", nameof(GoogleServiceAccountJson));
117
118 if (!Parsed.TryGetValue("project_id", out Obj) || !(Obj is string ProjectId) ||
119 !Parsed.TryGetValue("private_key_id", out Obj) || !(Obj is string PrivateKeyId) ||
120 !Parsed.TryGetValue("private_key", out Obj) || !(Obj is string PrivateKey) ||
121 !Parsed.TryGetValue("client_email", out Obj) || !(Obj is string ClientEmail) ||
122 !Parsed.TryGetValue("client_id", out Obj) || !(Obj is string ClientId) ||
123 !Parsed.TryGetValue("auth_uri", out Obj) || !(Obj is string AuthUri) ||
124 !Parsed.TryGetValue("token_uri", out Obj) || !(Obj is string TokenUri) ||
125 !Parsed.TryGetValue("universe_domain", out Obj) || !(Obj is string UniverseDomain))
126 {
127 throw new ArgumentException("Missing properties in JSON file.", nameof(GoogleServiceAccountJson));
128 }
129
130 JwtFactory Factory = (JwtFactory)await expCreateFactory.EvaluateAsync(new Variables() { { "PrivateKey", PrivateKey } });
131
132 // Note: Importing private keys from PEM file not available in .NET Standard. It is available from .NET 5 however,
133 // which is used by the executing container, which also publishes script functions for the purpose.
134
136 }
137
138 private static readonly Expression expCreateFactory = new Expression("CreateJwtFactory(RS256(RsaFromPem(PrivateKey)))");
139
143 public void Dispose()
144 {
145 this.factory?.Dispose();
146 this.factory = null;
147 }
148
149 #endregion
150
151 #region Authentication
152
156 public string BearerToken
157 {
158 get
159 {
160 DateTime Now = DateTime.Now;
161
162 if (this.token is null || Now.Subtract(this.tokenTimestamp).TotalMinutes >= 30)
163 {
164 int IssuedAt = (int)Math.Round(DateTime.UtcNow.Subtract(JSON.UnixEpoch).TotalSeconds);
165 int Expires = IssuedAt + 3600;
166
167 this.token = this.factory.Create(new KeyValuePair<string, object>[]
168 {
169 new KeyValuePair<string, object>("kid", this.privateKeyId),
170 },
171 new KeyValuePair<string, object>[]
172 {
173 new KeyValuePair<string, object>(JwtClaims.Issuer, this.clientEmail),
174 new KeyValuePair<string, object>(JwtClaims.Subject, this.clientEmail),
175 new KeyValuePair<string, object>(JwtClaims.Audience, productionEndpoint.OriginalString), // scope),
176 new KeyValuePair<string, object>(JwtClaims.IssueTime, IssuedAt),
177 new KeyValuePair<string, object>(JwtClaims.ExpirationTime, Expires)
178 });
179
180 this.tokenTimestamp = Now;
181 }
182
183 return this.token;
184 }
185 }
186
187 #endregion
188
189 #region Send Notification
190
197 public Task<NotificationResponse> SendNotification(string To, NotificationMessage Message)
198 {
199 return this.SendNotification(To, Message, null, null);
200 }
201
209 public Task<NotificationResponse> SendNotification(string To, NotificationMessage Message, NotificationOptions Options)
210 {
211 return this.SendNotification(To, Message, Options, null);
212 }
213
222 public async Task<NotificationResponse> SendNotification(string To, NotificationMessage Message, NotificationOptions Options, Dictionary<string, object> Data)
223 {
224 Dictionary<string, object> Msg = new Dictionary<string, object>()
225 {
226 { "token", To },
227 };
228 Dictionary<string, object> Request = new Dictionary<string, object>()
229 {
230 { "message", Msg }
231 };
232
233 if (this.test)
234 Request["validate_only"] = true;
235
236 Message.ExportProperties(Msg);
237 Options?.SetProperties(Msg, this);
238
239 if (!(Data is null) && Data.Count > 0)
240 {
241 Dictionary<string, object> ConvertedData = new Dictionary<string, object>();
242 foreach (KeyValuePair<string, object> Kvp in Data)
243 {
244 // If the value is a nested structure, JSON-encode,
245 // otherwise call ToString() to ensure a string value.
246 if (Kvp.Value is Dictionary<string, object> NestedObj)
247 {
248 ConvertedData[Kvp.Key] = JSON.Encode(NestedObj, false);
249 }
250 else if (Kvp.Value is IEnumerable Enumerable && !(Kvp.Value is string))
251 {
252 List<object> Items = new List<object>();
253 foreach (object Item in Enumerable)
254 Items.Add(Item);
255
256 ConvertedData[Kvp.Key] = JSON.Encode(Items.ToArray(), false);
257 }
258 else
259 {
260 ConvertedData[Kvp.Key] = Kvp.Value?.ToString() ?? string.Empty;
261 }
262 }
263 Msg["data"] = ConvertedData;
264 }
265
266 try
267 {
268 if (this.HasSniffers)
269 {
270 StringBuilder sb = new StringBuilder();
271
272 sb.Append("POST(");
273 sb.Append(this.sendNotificationUri.ToString());
274 sb.AppendLine(",");
275 sb.Append(" Authorization: Bearer ");
276 sb.Append(this.BearerToken);
277 sb.AppendLine(",");
278
279 string s = JSON.Encode(Request, true);
280 s = s.Replace("\t", " ");
281
282 string[] Rows = s.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
283
284 foreach (string Row in Rows)
285 {
286 sb.Append(" ");
287 sb.AppendLine(Row);
288 }
289
290 sb.Append(')');
291
292 this.TransmitText(sb.ToString());
293 }
294
295 ContentResponse Response = await InternetContent.PostAsync(this.sendNotificationUri, Request,
296 new KeyValuePair<string, string>("Authorization", "Bearer " + this.BearerToken));
297
298 if (Response.HasError)
299 {
300 if (Response.Error is WebException ex)
301 {
302 if (ex.Content is Dictionary<string, object> ErrorResponse &&
303 ErrorResponse.TryGetValue("error", out object Obj) &&
304 Obj is Dictionary<string, object> ErrorObject &&
305 ErrorObject.TryGetValue("message", out Obj) &&
306 Obj is string ErrorMessage)
307 {
308 this.Error(ErrorMessage);
309 return new NotificationResponse(ex, ErrorMessage);
310 }
311 else
312 {
313 this.Exception(ex);
314 Log.Exception(ex);
315
316 return new NotificationResponse(ex);
317 }
318 }
319 else
320 {
321 this.Exception(Response.Error);
322 Log.Exception(Response.Error);
323
324 return new NotificationResponse(Response.Error);
325 }
326 }
327 else
328 {
329 if (this.HasSniffers)
330 this.ReceiveText(JSON.Encode(Response.Decoded, true));
331
332 if (!(Response.Decoded is Dictionary<string, object> Obj))
333 {
334 Exception ex = new Exception("Invalid or unexpected JSON content returned.");
335 this.Exception(ex);
336 Log.Exception(ex);
337
338 return new NotificationResponse(ex);
339 }
340
341 return new NotificationResponse();
342 }
343 }
344 catch (Exception ex)
345 {
346 this.Exception(ex);
347 Log.Exception(ex);
348
349 return new NotificationResponse(ex);
350 }
351 }
352
353 #endregion
354
355 }
356}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:15
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:19
Contains information about a response to a content request.
bool HasError
If an error occurred.
object Decoded
Decoded object.
Exception Error
Error response.
Exception class for web exceptions.
Definition: WebException.cs:11
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
Helps with common JSON-related tasks.
Definition: JSON.cs:16
static object Parse(string Json)
Parses a JSON string.
Definition: JSON.cs:45
static readonly DateTime UnixEpoch
Unix Date and Time epoch, starting at 1970-01-01T00:00:00Z
Definition: JSON.cs:20
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:537
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1657
Simple base class for classes implementing communication protocols.
void TransmitText(string Text)
Called when text has been transmitted.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
bool HasSniffers
If there are sniffers registered on the object.
void Error(string Error)
Called to inform the viewer of an error state.
string ClientEMail
Client e-Mail address, as from Service Account JSON.
static async Task< FirebaseClient > CreateAsync(string GoogleServiceAccountJson, bool Test, params ISniffer[] Sniffers)
Creates an HTTP-based Firebase client.
string AuthUri
Authentication URI, as from Service Account JSON.
string PrivateKeyId
Private Key ID, as from Service Account JSON.
string ClientId
Client ID, as from Service Account JSON.
Task< NotificationResponse > SendNotification(string To, NotificationMessage Message)
Sends a push notification
Task< NotificationResponse > SendNotification(string To, NotificationMessage Message, NotificationOptions Options)
Sends a push notification
string UniverseDomain
Universe Domain, as from Service Account JSON.
string ProjectId
Project ID, as from Service Account JSON.
async Task< NotificationResponse > SendNotification(string To, NotificationMessage Message, NotificationOptions Options, Dictionary< string, object > Data)
Sends a push notification
bool Test
If notifications should only be validated, as from Service Account JSON.
string TokenUri
Token URI, as from Service Account JSON.
string BearerToken
Bearer JWT token to use for authentication.
virtual void ExportProperties(Dictionary< string, object > Message)
Prepares the object to send to Firebase.
void SetProperties(Dictionary< string, object > Message, FirebaseClient Client)
Prepares the object to send to Firebase.
Firebase response to sending a notification message.
Class managing a script expression.
Definition: Expression.cs:41
async Task< object > EvaluateAsync(Variables Variables)
Evaluates the expression, using the variables provided in the Variables collection....
Definition: Expression.cs:4461
Collection of variables.
Definition: Variables.cs:25
Static class containing predefined JWT claim names.
Definition: JwtClaims.cs:10
const string Issuer
Issuer of the JWT
Definition: JwtClaims.cs:14
const string Audience
Recipient for which the JWT is intended
Definition: JwtClaims.cs:24
const string IssueTime
Time at which the JWT was issued; can be used to determine age of the JWT
Definition: JwtClaims.cs:39
const string Subject
Subject of the JWT (the user)
Definition: JwtClaims.cs:19
const string ExpirationTime
Time after which the JWT expires
Definition: JwtClaims.cs:29
A factory that can create and validate JWT tokens.
Definition: JwtFactory.cs:66
string Create(params KeyValuePair< string, object >[] Claims)
Creates a new JWT token.
Definition: JwtFactory.cs:379
void Dispose()
IDisposable.Dispose
Definition: JwtFactory.cs:263
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10