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;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading.Tasks;
5using Waher.Content;
7using Waher.Events;
9using Waher.Script;
11
13{
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 { "notification", Message.GetNotificationObject() }
228 };
229 Dictionary<string, object> Request = new Dictionary<string, object>()
230 {
231 { "message", Msg }
232 };
233
234 if (this.test)
235 Request["validate_only"] = true;
236
237 Options?.SetProperties(Msg, this);
238 Message.ExportProperties(Msg);
239
240 if (!(Data is null) && Data.Count > 0)
241 {
242 Dictionary<string, object> Updated = new Dictionary<string, object>();
243
244 foreach (KeyValuePair<string, object> P in Data)
245 {
246 if (P.Value is Dictionary<string, object> NestedObj)
247 {
248 if (Updated is null)
249 Updated = new Dictionary<string, object>();
250
251 Updated[P.Key] = JSON.Encode(P.Value, false); // Nested data objects no longer supported by new Firebase API.
252 }
253 }
254
255 if (!(Updated is null))
256 {
257 foreach (KeyValuePair<string, object> P in Updated)
258 Data[P.Key] = P.Value;
259 }
260
261 Msg["data"] = Data;
262 }
263
264 try
265 {
266 if (this.HasSniffers)
267 {
268 StringBuilder sb = new StringBuilder();
269
270 sb.Append("POST(");
271 sb.Append(this.sendNotificationUri.ToString());
272 sb.AppendLine(",");
273 sb.Append(" Authorization: Bearer ");
274 sb.Append(this.BearerToken);
275 sb.AppendLine(",");
276
277 string s = JSON.Encode(Request, true);
278 s = s.Replace("\t", " ");
279
280 string[] Rows = s.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
281
282 foreach (string Row in Rows)
283 {
284 sb.Append(" ");
285 sb.AppendLine(Row);
286 }
287
288 sb.Append(')');
289
290 await this.TransmitText(sb.ToString());
291 }
292
293 object Response = await InternetContent.PostAsync(this.sendNotificationUri, Request,
294 new KeyValuePair<string, string>("Authorization", "Bearer " + this.BearerToken));
295
296 if (this.HasSniffers)
297 await this.ReceiveText(JSON.Encode(Response, true));
298
299 if (!(Response is Dictionary<string, object> Obj))
300 throw new Exception("Invalid or unexpected JSON content returned.");
301
302 return new NotificationResponse();
303 }
304 catch (WebException ex)
305 {
306 if (ex.Content is Dictionary<string, object> ErrorResponse &&
307 ErrorResponse.TryGetValue("error", out object Obj) &&
308 Obj is Dictionary<string, object> ErrorObject &&
309 ErrorObject.TryGetValue("message", out Obj) &&
310 Obj is string ErrorMessage)
311 {
312 await this.Error(ErrorMessage);
313 return new NotificationResponse(ex, ErrorMessage);
314 }
315 else
316 {
317 await this.Exception(ex);
318 Log.Exception(ex);
319
320 return new NotificationResponse(ex);
321 }
322 }
323 catch (Exception ex)
324 {
325 await this.Exception(ex);
326 Log.Exception(ex);
327
328 return new NotificationResponse(ex);
329 }
330 }
331
332 #endregion
333
334 }
335}
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:17
Exception class for web exceptions.
Definition: WebException.cs:11
object Content
Decoded content.
Definition: WebException.cs:55
Static class managing encoding and decoding of internet content.
static Task< object > 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:14
static object Parse(string Json)
Parses a JSON string.
Definition: JSON.cs:43
static readonly DateTime UnixEpoch
Unix Date and Time epoch, starting at 1970-01-01T00:00:00Z
Definition: JSON.cs:18
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
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:1647
Simple base class for classes implementing communication protocols.
Task Error(string Error)
Called to inform the viewer of an error state.
Task Exception(Exception Exception)
Called to inform the viewer of an exception state.
ISniffer[] Sniffers
Registered sniffers.
Task TransmitText(string Text)
Called when text has been transmitted.
bool HasSniffers
If there are sniffers registered on the object.
Task ReceiveText(string Text)
Called when text has been received.
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 Dictionary< string, object > GetNotificationObject()
Gets the notification object for a push notification.
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:39
async Task< object > EvaluateAsync(Variables Variables)
Evaluates the expression, using the variables provided in the Variables collection....
Definition: Expression.cs:4275
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:53
string Create(params KeyValuePair< string, object >[] Claims)
Creates a new JWT token.
Definition: JwtFactory.cs:248
void Dispose()
IDisposable.Dispose
Definition: JwtFactory.cs:160
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11