Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
PushNotificationConfiguration.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Text;
5using System.Threading.Tasks;
6using Waher.Content;
10using Waher.Events;
20using Waher.Script;
22
24{
30 {
31 private static PushNotificationConfiguration instance = null;
32 private static FirebaseClient firebaseClient = null;
33
34 private string firebaseServiceAccountJson = string.Empty;
35 private string firebaseApiKey = string.Empty;
36 private string firebaseAuthDomain = string.Empty;
37 private string firebaseProjectId = string.Empty;
38 private string firebaseStorageBucket = string.Empty;
39 private string firebaseMessagingSenderId = string.Empty;
40 private string firebaseAppId = string.Empty;
41 private string firebaseWebPushPublicKey = string.Empty;
42
43 private DateTime firebaseServiceAccountJsonUploaded = DateTime.MinValue;
44 private bool useFirebase = false;
45
46 private HttpResource firebaseWebPushJavascript = null;
47 private HttpResource testFirebaseConnection = null;
48 private HttpResource testFirebaseNotification = null;
49
53 [DefaultValueStringEmpty]
55 {
56 get => this.firebaseServiceAccountJson;
57 set => this.firebaseServiceAccountJson = value;
58 }
59
63 [DefaultValueStringEmpty]
64 public string FirebaseApiKey
65 {
66 get => this.firebaseApiKey;
67 set => this.firebaseApiKey = value;
68 }
69
73 [DefaultValueStringEmpty]
74 public string FirebaseAuthDomain
75 {
76 get => this.firebaseAuthDomain;
77 set => this.firebaseAuthDomain = value;
78 }
79
83 [DefaultValueStringEmpty]
84 public string FirebaseProjectId
85 {
86 get => this.firebaseProjectId;
87 set => this.firebaseProjectId = value;
88 }
89
93 [DefaultValueStringEmpty]
95 {
96 get => this.firebaseStorageBucket;
97 set => this.firebaseStorageBucket = value;
98 }
99
103 [DefaultValueStringEmpty]
105 {
106 get => this.firebaseMessagingSenderId;
107 set => this.firebaseMessagingSenderId = value;
108 }
109
113 [DefaultValueStringEmpty]
114 public string FirebaseAppId
115 {
116 get => this.firebaseAppId;
117 set => this.firebaseAppId = value;
118 }
119
124 {
125 get => this.firebaseWebPushPublicKey;
126 set => this.firebaseWebPushPublicKey = value;
127 }
128
132 [DefaultValueDateTimeMinValue]
134 {
135 get => this.firebaseServiceAccountJsonUploaded;
136 set => this.firebaseServiceAccountJsonUploaded = value;
137 }
138
142 [DefaultValue(false)]
143 public bool UseFirebase
144 {
145 get => this.useFirebase;
146 set => this.useFirebase = value;
147 }
148
152 public static FirebaseClient FirebaseClient => firebaseClient;
153
157 public static PushNotificationConfiguration Instance => instance;
158
162 public override string Resource => "/Settings/PushNotification.md";
163
167 public override int Priority => 460;
168
174 public override Task<string> Title(Language Language)
175 {
176 return Language.GetStringAsync(typeof(XmppServerModule), 41, "Push Notification");
177 }
178
182 public override async Task ConfigureSystem()
183 {
184 firebaseClient?.Dispose();
185 firebaseClient = null;
186
187 if (this.useFirebase && !string.IsNullOrEmpty(this.firebaseServiceAccountJson))
188 firebaseClient = await GetClient(this.firebaseServiceAccountJson);
189 }
190
191 private static async Task<FirebaseClient> GetClient(string ServiceAccountJson)
192 {
194 {
195 ISniffer Sniffer = new XmlFileSniffer(Gateway.AppDataFolder + "Firebase" + Path.DirectorySeparatorChar +
196 "Firebase Log %YEAR%-%MONTH%-%DAY%T%HOUR%.xml",
197 Gateway.AppDataFolder + "Transforms" + Path.DirectorySeparatorChar + "SnifferXmlToHtml.xslt",
198 7, BinaryPresentationMethod.ByteCount);
199
200 return await FirebaseClient.CreateAsync(ServiceAccountJson, false, Sniffer);
201 }
202 else
203 return await FirebaseClient.CreateAsync(ServiceAccountJson, false);
204 }
205
210 public override void SetStaticInstance(ISystemConfiguration Configuration)
211 {
212 instance = Configuration as PushNotificationConfiguration;
213 }
214
219 public override Task InitSetup(HttpServer WebServer)
220 {
221 this.firebaseWebPushJavascript = WebServer.Register("/Settings/FirebaseWebPush.js",
222 this.FirebaseWebPushJs, true, false, false);
223 this.testFirebaseConnection = WebServer.Register("/Settings/TestFirebaseConnection",
224 null, this.TestFirebaseConnection, true, false, true);
225 this.testFirebaseNotification = WebServer.Register("/Settings/TestFirebaseNotification",
226 null, this.TestFirebaseNotification, true, false, true);
227
228 return base.InitSetup(WebServer);
229 }
230
235 public override Task UnregisterSetup(HttpServer WebServer)
236 {
237 WebServer.Unregister(this.testFirebaseConnection);
238 WebServer.Unregister(this.testFirebaseNotification);
239
240 return base.UnregisterSetup(WebServer);
241 }
242
246 protected override string ConfigPrivilege => "Admin.Communication.PushNotification";
247
248 private async Task FirebaseWebPushJs(HttpRequest Request, HttpResponse Response)
249 {
250 Response.StatusCode = 200;
251 Response.ContentType = JavaScriptCodec.DefaultContentType;
252
253 StringBuilder sb = new StringBuilder();
254
255 sb.AppendLine("import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.12.4/firebase-app.js';");
256 sb.AppendLine("import { getMessaging, getToken } from 'https://www.gstatic.com/firebasejs/10.12.4/firebase-messaging.js';");
257 sb.AppendLine();
258 sb.AppendLine("const firebaseConfig =");
259 sb.AppendLine("{");
260 sb.Append("\tapiKey: '");
261 sb.Append(this.firebaseApiKey);
262 sb.AppendLine("',");
263 sb.Append("\tauthDomain: '");
264 sb.Append(this.firebaseAuthDomain);
265 sb.AppendLine("',");
266 sb.Append("\tprojectId: '");
267 sb.Append(this.firebaseProjectId);
268 sb.AppendLine("',");
269 sb.Append("\tstorageBucket: '");
270 sb.Append(this.firebaseStorageBucket);
271 sb.AppendLine("',");
272 sb.Append("\tmessagingSenderId: '");
273 sb.Append(this.firebaseMessagingSenderId);
274 sb.AppendLine("',");
275 sb.Append("\tappId: '");
276 sb.Append(this.firebaseAppId);
277 sb.AppendLine("'");
278 sb.AppendLine("};");
279 sb.AppendLine();
280 sb.AppendLine("const app = initializeApp(firebaseConfig);");
281 sb.AppendLine("const messaging = getMessaging(app);");
282 sb.AppendLine();
283 sb.AppendLine("window.GetFirebaseToken = function GetFirebaseToken()");
284 sb.AppendLine("{");
285 sb.AppendLine("\tgetToken(messaging, { vapidKey: '" + this.firebaseWebPushPublicKey + "'}).then(");
286 sb.AppendLine("\t\t(token) =>");
287 sb.AppendLine("\t\t{");
288 sb.AppendLine("\t\t\tif (token)");
289 sb.AppendLine("\t\t\t\tFirebaseTokenReceived(token);");
290 sb.AppendLine("\t\t\telse");
291 sb.AppendLine("\t\t\t\tFirebaseTokenRejected();");
292 sb.AppendLine("\t\t}).catch((error) => FirebaseTokenFailure(error));");
293 sb.AppendLine("};");
294
295 await Response.Write(sb.ToString());
296 }
297
298 private async Task TestFirebaseConnection(HttpRequest Request, HttpResponse Response)
299 {
300 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
301
302 if (!Request.HasData)
303 throw new BadRequestException();
304
305 object Obj = await Request.DecodeDataAsync();
306 if (!(Obj is Dictionary<string, object> Form) ||
307 !Form.TryGetValue("useFirebase", out Obj) || !(Obj is bool UseFirebase))
308 {
309 throw new BadRequestException();
310 }
311
312 if (!Form.TryGetValue("firebaseWebConfig", out Obj) ||
313 !(Obj is string FirebaseWebConfigJson) ||
314 string.IsNullOrEmpty(FirebaseWebConfigJson))
315 {
316 FirebaseWebConfigJson = null;
317 }
318
319 if (!Form.TryGetValue("firebaseWebPushPublicKey", out Obj) ||
320 !(Obj is string FirebaseWebPushPublicKey))
321 {
322 throw new BadRequestException();
323 }
324
325 this.firebaseWebPushPublicKey = FirebaseWebPushPublicKey;
326
327 if (!Form.TryGetValue("serviceAccountJson", out Obj) ||
328 !(Obj is string ServiceAccountJson) ||
329 string.IsNullOrEmpty(ServiceAccountJson))
330 {
331 ServiceAccountJson = this.firebaseServiceAccountJson;
332 }
333
334 if (Form.TryGetValue("firebaseMessagingSwJsContents", out Obj) &&
335 Obj is string FirebaseMessagingSwJsContents)
336 {
337 await DomainSettings.SetSettingAsync(Request, Path.Combine(Gateway.RootFolder, "firebase-messaging-sw.js"),
338 FirebaseMessagingSwJsContents);
339 }
340
341 string TabID = Request.Header["X-TabID"];
342 if (string.IsNullOrEmpty(TabID))
343 throw new BadRequestException();
344
345 bool Ok = await this.Test(UseFirebase, FirebaseWebConfigJson, ServiceAccountJson, TabID);
346
347 string TimestampHtml = await MarkdownToHtml.ToHtml("JSON file uploaded: **{{Config.FirebaseServiceAccountJsonUploaded}}**.",
348 new Variables()
349 {
350 { "Config", this }
351 });
352
353 Response.StatusCode = 200;
354 await Response.Return(new Dictionary<string, object>()
355 {
356 { "ok", Ok },
357 { "timestampHtml", TimestampHtml }
358 });
359 }
360
361 private async Task<bool> Test(bool UseFirebase, string FirebaseWebConfigJson, string ServiceAccountJson, params string[] TabIDs)
362 {
363 try
364 {
365 this.useFirebase = UseFirebase;
366
367 if (this.firebaseServiceAccountJson != ServiceAccountJson)
368 {
369 this.firebaseServiceAccountJson = ServiceAccountJson;
370 this.firebaseServiceAccountJsonUploaded = DateTime.Now;
371 }
372
373 if (!string.IsNullOrEmpty(FirebaseWebConfigJson))
374 {
375 if (!(JSON.Parse(FirebaseWebConfigJson) is Dictionary<string, object> FirebaseWebConfig))
376 throw new Exception("Invalid Web Configuration JSON object.");
377
378 foreach (KeyValuePair<string, object> P in FirebaseWebConfig)
379 {
380 if (!(P.Value is string Value))
381 throw new Exception("Web Configuration object values must be strings.");
382
383 switch (P.Key)
384 {
385 case "apiKey":
386 this.firebaseApiKey = Value;
387 break;
388
389 case "authDomain":
390 this.firebaseAuthDomain = Value;
391 break;
392
393 case "projectId":
394 this.firebaseProjectId = Value;
395 break;
396
397 case "storageBucket":
398 this.firebaseStorageBucket = Value;
399 break;
400
401 case "messagingSenderId":
402 this.firebaseMessagingSenderId = Value;
403 break;
404
405 case "appId":
406 this.firebaseAppId = Value;
407 break;
408
409 default:
410 throw new Exception("Unrecognized property: " + P.Key);
411 }
412 }
413 }
414
415 await Database.Update(this);
416
417 if (UseFirebase)
418 {
419 FirebaseClient Client = null;
420
421 try
422 {
423 Client = await GetClient(ServiceAccountJson);
424
425 if (Client.ProjectId != this.firebaseProjectId)
426 throw new Exception("Inconsistency between mobile phone and web Project IDs");
427
428 await ClientEvents.PushEvent(TabIDs, "ConnectionSuccessful", string.Empty, false, "User");
429
430 firebaseClient?.Dispose();
431 firebaseClient = Client;
432 Client = null;
433
434 return true;
435 }
436 finally
437 {
438 Client?.Dispose();
439 }
440 }
441 else
442 {
443 firebaseClient?.Dispose();
444 firebaseClient = null;
445 }
446 }
447 catch (Exception ex)
448 {
449 Log.Exception(ex);
450 await ClientEvents.PushEvent(TabIDs, "ConnectionError", string.Empty, false, "User");
451 await ClientEvents.PushEvent(TabIDs, "ShowStatus", ex.Message, false, "User");
452 }
453
454 return false;
455 }
456
457 private async Task TestFirebaseNotification(HttpRequest Request, HttpResponse Response)
458 {
459 Gateway.AssertUserAuthenticated(Request, this.ConfigPrivilege);
460
461 if (!Request.HasData)
462 throw new BadRequestException();
463
464 object Obj = await Request.DecodeDataAsync();
465 if (!(Obj is Dictionary<string, object> Form) ||
466 !Form.TryGetValue("token", out Obj) || !(Obj is string Token) ||
467 !Form.TryGetValue("title", out Obj) || !(Obj is string Title) ||
468 !Form.TryGetValue("body", out Obj) || !(Obj is string Body))
469 {
470 throw new BadRequestException();
471 }
472
473 using (FirebaseClient Client = await GetClient(this.firebaseServiceAccountJson))
474 {
475 NotificationResponse FirebaseResponse = await Client.SendNotification(Token, new NotificationMessage()
476 {
477 Title = Title,
478 Body = Body
479 });
480
481 Response.StatusCode = 200;
482 Response.ContentType = JsonCodec.DefaultContentType;
483
484 await Response.Write(JSON.Encode(new Dictionary<string, object>()
485 {
486 { "ok", FirebaseResponse.Ok },
487 { "errorMessage", FirebaseResponse.ErrorMessage }
488 }, false));
489 }
490 }
491
496 public override Task<bool> SimplifiedConfiguration()
497 {
498 return Task.FromResult(true);
499 }
500
506 internal static async void PushNotification(PushNotificationToken Token, object Content)
507 {
508 try
509 {
510 switch (Token.Service)
511 {
512 case PushMessagingService.Firebase:
513 Dictionary<string, object> Data = null;
515 NotificationMessage Message;
516
517 switch (Token.ClientType)
518 {
519 case ClientType.Android:
520 Message = new NotificationMessageAndroid();
521 break;
522
523 case ClientType.iOS:
524 Message = new NotificationMessageApple();
525 break;
526
527 case ClientType.Web:
528 Message = new NotificationMessageWeb();
529 break;
530
531 case ClientType.Other:
532 default:
533 Message = new NotificationMessage();
534 break;
535 }
536
537 if (Content is Dictionary<string, object> JsonObject)
538 {
539 foreach (KeyValuePair<string, object> P in JsonObject)
540 SetProperty(Message, Options, ref Data, P.Key, P.Value);
541 }
542 else if (Content is Dictionary<string, IElement> ScriptObject)
543 {
544 foreach (KeyValuePair<string, IElement> P in ScriptObject)
545 SetProperty(Message, Options, ref Data, P.Key, P.Value.AssociatedObjectValue);
546 }
547 else
548 Message.Body = Content?.ToString();
549
550 FirebaseClient Client = firebaseClient;
551 if (!(Client is null))
552 await Client.SendNotification(Token.Token, Message, Options, Data);
553 break;
554 }
555 }
556 catch (Exception ex)
557 {
558 Log.Exception(ex);
559 }
560 }
561
562 private static void SetProperty(NotificationMessage Message, NotificationOptions Options, ref Dictionary<string, object> Data,
563 string Key, object Value)
564 {
565 if (!Options.TrySetProperty(Key, Value) && !Message.TrySetProperty(Key, Value))
566 {
567 if (Data is null)
568 Data = new Dictionary<string, object>();
569
570 if (Key == "data" && Value is Dictionary<string, object> DataItems)
571 {
572 foreach (KeyValuePair<string, object> P in DataItems)
573 Data[P.Key] = P.Value;
574 }
575 else
576 Data[Key] = Value;
577 }
578 }
579
583 public const string BROKER_FIREBASE_USE = nameof(BROKER_FIREBASE_USE);
584
588 public const string BROKER_FIREBASE_SERVICE_JSON = nameof(BROKER_FIREBASE_SERVICE_JSON);
589
593 public const string BROKER_FIREBASE_API_KEY = nameof(BROKER_FIREBASE_API_KEY);
594
598 public const string BROKER_FIREBASE_AUTH_DOMAIN = nameof(BROKER_FIREBASE_AUTH_DOMAIN);
599
603 public const string BROKER_FIREBASE_PROJECT_ID = nameof(BROKER_FIREBASE_PROJECT_ID);
604
608 public const string BROKER_FIREBASE_STORAGE_BUCKET = nameof(BROKER_FIREBASE_STORAGE_BUCKET);
609
613 public const string BROKER_FIREBASE_MESSAGING_SENDER_ID = nameof(BROKER_FIREBASE_MESSAGING_SENDER_ID);
614
618 public const string BROKER_FIREBASE_APP_ID = nameof(BROKER_FIREBASE_APP_ID);
619
624 public override async Task<bool> EnvironmentConfiguration()
625 {
626 if (!this.TryGetEnvironmentVariable(BROKER_FIREBASE_USE, false, out this.useFirebase))
627 return false;
628
629 if (!this.useFirebase)
630 return true;
631
632 if (!this.TryGetEnvironmentVariable(BROKER_FIREBASE_SERVICE_JSON, true, out string FileName) ||
633 !File.Exists(FileName))
634 {
635 return false;
636 }
637
638 try
639 {
640 string Json = await Resources.ReadAllTextAsync(FileName);
641 FirebaseClient Client = await GetClient(Json);
642
643 this.firebaseServiceAccountJson = Json;
644 this.firebaseServiceAccountJsonUploaded = DateTime.Now;
645
646 if (!this.TryGetEnvironmentVariable(BROKER_FIREBASE_API_KEY, true, out this.firebaseApiKey) ||
647 !this.TryGetEnvironmentVariable(BROKER_FIREBASE_AUTH_DOMAIN, true, out this.firebaseAuthDomain) ||
648 !this.TryGetEnvironmentVariable(BROKER_FIREBASE_PROJECT_ID, true, out this.firebaseProjectId) ||
649 !this.TryGetEnvironmentVariable(BROKER_FIREBASE_STORAGE_BUCKET, true, out this.firebaseStorageBucket) ||
650 !this.TryGetEnvironmentVariable(BROKER_FIREBASE_MESSAGING_SENDER_ID, true, out this.firebaseMessagingSenderId) ||
651 !this.TryGetEnvironmentVariable(BROKER_FIREBASE_APP_ID, true, out this.firebaseAppId))
652 {
653 return false;
654 }
655
656 return Client.ProjectId == this.firebaseProjectId;
657 }
658 catch (Exception ex)
659 {
660 Log.Exception(ex, FileName);
661 return false;
662 }
663 }
664
665}
666 }
const string DefaultContentType
application/javascript
Helps with common JSON-related tasks.
Definition: JSON.cs:14
static object Parse(string Json)
Parses a JSON string.
Definition: JSON.cs:43
static string Encode(string s)
Encodes a string for inclusion in JSON.
Definition: JSON.cs:507
const string DefaultContentType
application/json
Definition: JsonCodec.cs:19
static Task< string > ToHtml(string Markdown)
Converts a Markdown snippet to a HTML snippet.
Static class managing loading of resources stored as embedded resources or in content files.
Definition: Resources.cs:15
static async Task< string > ReadAllTextAsync(string FileName)
Reads a text file asynchronously.
Definition: Resources.cs:205
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
The ClientEvents class allows applications to push information asynchronously to web clients connecte...
Definition: ClientEvents.cs:51
static Task< int > PushEvent(string[] TabIDs, string Type, object Data)
Puses an event to a set of Tabs, given their Tab IDs.
static Task< bool > SetSettingAsync(IHostReference HostRef, string Key, string Value)
Sets a setting that may vary depending on domain.
Static class managing the runtime environment of the IoT Gateway.
Definition: Gateway.cs:126
static IUser AssertUserAuthenticated(HttpRequest Request, string Privilege)
Makes sure a request is being made from a session with a successful user login.
Definition: Gateway.cs:3041
static string AppDataFolder
Application data folder.
Definition: Gateway.cs:2369
static string RootFolder
Web root folder.
Definition: Gateway.cs:2379
Abstract base class for multi-step system configurations.
static XmppConfiguration Instance
Current instance of configuration.
bool Sniffer
If communication should be sniffed.
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repe...
static async Task< FirebaseClient > CreateAsync(string GoogleServiceAccountJson, bool Test, params ISniffer[] Sniffers)
Creates an HTTP-based Firebase client.
Task< NotificationResponse > SendNotification(string To, NotificationMessage Message)
Sends a push notification
string ProjectId
Project ID, as from Service Account JSON.
virtual bool TrySetProperty(string Name, object Value)
Tries to set a property value.
bool TrySetProperty(string Name, object Value)
Tries to set a property value.
Firebase response to sending a notification message.
Represents an HTTP request.
Definition: HttpRequest.cs:18
HttpRequestHeader Header
Request header.
Definition: HttpRequest.cs:134
bool HasData
If the request has data.
Definition: HttpRequest.cs:74
async Task< object > DecodeDataAsync()
Decodes data sent in request.
Definition: HttpRequest.cs:95
Base class for all HTTP resources.
Definition: HttpResource.cs:23
Represets a response of an HTTP client request.
Definition: HttpResponse.cs:21
async Task Return(object Object)
Returns an object to the client. This method can only be called once per response,...
async Task Write(byte[] Data)
Returns binary data in the response.
Implements an HTTP server.
Definition: HttpServer.cs:36
HttpResource Register(HttpResource Resource)
Registers a resource with the server.
Definition: HttpServer.cs:1287
bool Unregister(HttpResource Resource)
Unregisters a resource from the server.
Definition: HttpServer.cs:1438
Outputs sniffed data to an XML file.
PushMessagingService Service
Service used for push notification
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
Contains information about a language.
Definition: Language.cs:17
Task< string > GetStringAsync(Type Type, int Id, string Default)
Gets the string value of a string ID. If no such string exists, a string is created with the default ...
Definition: Language.cs:209
Collection of variables.
Definition: Variables.cs:25
Provides the user configuration options regarding use of Push Notification to reach offline clients.
override string Resource
Resource to be redirected to, to perform the configuration.
override void SetStaticInstance(ISystemConfiguration Configuration)
Sets the static instance of the configuration.
string FirebaseAuthDomain
Firebase Authentication Domain (Web Push)
override string ConfigPrivilege
Minimum required privilege for a user to be allowed to change the configuration defined by the class.
override async Task< bool > EnvironmentConfiguration()
Environment configuration by configuring values available in environment variables.
override Task< bool > SimplifiedConfiguration()
Simplified configuration by configuring simple default values.
string FirebaseWebPushPublicKey
Firebase public Web Push (VAPID) key.
override int Priority
Priority of the setting. Configurations are sorted in ascending order.
static PushNotificationConfiguration Instance
Current instance of configuration.
override Task< string > Title(Language Language)
Gets a title for the system configuration.
override async Task ConfigureSystem()
Is called during startup to configure the system.
bool UseFirebase
If Firebase is to be used to push notifications to offline clients.
override Task InitSetup(HttpServer WebServer)
Initializes the setup object.
string FirebaseMessagingSenderId
Firebase Messaging Sender ID (Web Push)
override Task UnregisterSetup(HttpServer WebServer)
Unregisters the setup object.
DateTime FirebaseServiceAccountJsonUploaded
When Firebase Service Account JSON was uploaded
Service Module hosting the XMPP broker and its components.
Interface for system configurations. The gateway will scan all module for system configuration classe...
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:11
BinaryPresentationMethod
How binary data is to be presented.
ClientType
Type of client requesting notification.
Definition: ClientType.cs:7