Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
XmppService.cs
1//#define DEBUG_XMPP_REMOTE
2#define DEBUG_XMPP_LOCAL
3//#define DEBUG_LOG_REMOTE
4//#define DEBUG_DB_REMOTE
5//#define DEBUG_NFC_REMOTE
6
8using System.ComponentModel;
9using System.Diagnostics;
10using System.Diagnostics.CodeAnalysis;
11using System.Globalization;
12using System.IO;
13using System.Linq;
14using System.Reflection;
15using System.Runtime.CompilerServices;
16using System.Text;
17using System.Xml;
18using CommunityToolkit.Mvvm.Messaging;
19using EDaler;
20using EDaler.Events;
21using EDaler.Uris;
44using NeuroFeatures;
47using Waher.Content;
51using Waher.Events;
86using Waher.Things;
88
90{
97 [Singleton]
98 internal sealed class XmppService : LoadableService, IXmppService, IDisposableAsync
99 {
100 //private bool isDisposed;
101 private XmppClient? xmppClient;
102 private ContractsClient? contractsClient;
103 private HttpFileUploadClient? fileUploadClient;
104 private ThingRegistryClient? thingRegistryClient;
105 private ProvisioningClient? provisioningClient;
106 private ControlClient? controlClient;
107 private SensorClient? sensorClient;
108 private ConcentratorClient? concentratorClient;
109 private EDalerClient? eDalerClient;
110 private NeuroFeaturesClient? neuroFeaturesClient;
111 private PushNotificationClient? pushNotificationClient;
112 private AbuseClient? abuseClient;
113 private PepClient? pepClient;
114 private HttpxClient? httpxClient;
115 private PubSubClient? pubSubClient;
116 private Timer? reconnectTimer;
117 private Timer? updatePasswordTimer;
118 private string? domainName;
119 private string? accountName;
120 private string? passwordHash;
121 private string? passwordHashMethod;
122 private bool xmppConnected = false;
123 private DateTime xmppLastStateChange = DateTime.MinValue;
124 private readonly InMemorySniffer? sniffer = new(250, "Connection In-memory sniffer.");
125 private bool isCreatingClient;
126 private EventFilter? xmppFilteredEventSink;
127 private string? token = null;
128 private DateTime tokenCreated = DateTime.MinValue;
129#if DEBUG_XMPP_REMOTE || DEBUG_LOG_REMOTE || DEBUG_DB_REMOTE || DEBUG_NFC_REMOTE
130 private const string debugRecipient = ""; // TODO: Set JID of recipient of debug messages.
131#endif
132#if DEBUG_XMPP_REMOTE || DEBUG_DB_REMOTE || DEBUG_NFC_REMOTE
133 private RemoteSniffer? debugSniffer = null;
134#endif
135#if DEBUG_LOG_REMOTE
136 private EventFilter? debugEventSink = null;
137#endif
138
139 #region Creation / Destruction
140
141 public XmppService()
142 {
143 }
144
145 private async Task CreateXmppClient()
146 {
147 if (this.isCreatingClient)
148 return;
149
150 try
151 {
152 this.isCreatingClient = true;
153
154 if (!this.XmppParametersCurrent() || this.XmppStale())
155 {
156 if (this.xmppClient is not null)
157 await this.DestroyXmppClient();
158
159 this.domainName = ServiceRef.TagProfile.Domain;
160 this.accountName = ServiceRef.TagProfile.Account;
161 this.passwordHash = ServiceRef.TagProfile.XmppPasswordHash;
162 this.passwordHashMethod = ServiceRef.TagProfile.XmppPasswordHashMethod;
163
164 string? HostName;
165 int PortNumber;
166 bool IsIpAddress;
167
169 {
170 HostName = this.domainName;
171 PortNumber = XmppCredentials.DefaultPort;
172 IsIpAddress = false;
173 }
174 else
175 {
176 (HostName, PortNumber, IsIpAddress) = await ServiceRef.NetworkService.LookupXmppHostnameAndPort(this.domainName!);
177
178 if (HostName == this.domainName && PortNumber == XmppCredentials.DefaultPort)
179 {
180 ServiceRef.TagProfile.SetDomain(this.domainName, true, ServiceRef.TagProfile.ApiKey ?? string.Empty,
181 ServiceRef.TagProfile.ApiSecret ?? string.Empty);
182 }
183 }
184
185 this.xmppLastStateChange = DateTime.Now;
186 this.xmppConnected = false;
187
188 Assembly AppAssembly = App.Current!.GetType().Assembly;
189
190 if (string.IsNullOrEmpty(this.passwordHashMethod))
191 {
192 this.xmppClient = new XmppClient(HostName, PortNumber, this.accountName, this.passwordHash,
193 Constants.LanguageCodes.Default, AppAssembly, this.sniffer);
194 }
195 else
196 {
197 this.xmppClient = new XmppClient(HostName, PortNumber, this.accountName, this.passwordHash, this.passwordHashMethod,
198 Constants.LanguageCodes.Default, AppAssembly, this.sniffer);
199 }
200#if DEBUG_XMPP_LOCAL
201 DebugSniffer LocalSniffer = new(BinaryPresentationMethod.Hexadecimal);
202 this.xmppClient.Add(LocalSniffer);
203#endif
204
205#if DEBUG_XMPP_REMOTE || DEBUG_LOG_REMOTE || DEBUG_DB_REMOTE
206 if (!string.IsNullOrEmpty(debugRecipient))
207 {
208#endif
209#if DEBUG_XMPP_REMOTE || DEBUG_DB_REMOTE || DEBUG_NFC_REMOTE
210 this.debugSniffer = new RemoteSniffer(debugRecipient, DateTime.MaxValue, this.xmppClient, this.xmppClient,
212#endif
213#if DEBUG_XMPP_REMOTE
214 this.xmppClient.Add(this.debugSniffer);
215#endif
216#if DEBUG_LOG_REMOTE
217 if (this.debugEventSink is not null)
218 {
219 Log.Unregister(this.debugEventSink);
220 this.debugEventSink?.Dispose();
221 this.debugEventSink = null;
222 }
223
224 this.debugEventSink = new EventFilter("Debug Event Filter",
225 new XmppEventSink("Debug Event Sink", this.xmppClient, debugRecipient, false),
226 EventType.Informational, (Event) =>
227 {
228 if (this.xmppClient is null || this.xmppClient.State != XmppState.Connected)
229 return false;
230
231 return string.IsNullOrEmpty(Event.StackTrace) || !Event.StackTrace.Contains("XmppEventSink");
232 });
233
234 Log.Register(this.debugEventSink);
235#endif
236#if DEBUG_DB_REMOTE
237 if (!Ledger.HasProvider)
238 {
239 XmlFileLedger XmlFileLedger = new(new RemoteLedgerWriter());
240 Ledger.Register(XmlFileLedger);
241
242 await XmlFileLedger.Start();
243
245 }
246#endif
247#if DEBUG_XMPP_REMOTE || DEBUG_LOG_REMOTE || DEBUG_DB_REMOTE
248 }
249#endif
250 this.xmppClient.DefaultRetryTimeout = 30000;
251 this.xmppClient.DefaultNrRetries = 0;
252 this.xmppClient.RequestRosterOnStartup = false;
253 this.xmppClient.TrustServer = !IsIpAddress;
254 this.xmppClient.AllowCramMD5 = false;
255 this.xmppClient.AllowDigestMD5 = false;
256 this.xmppClient.AllowPlain = false;
257 this.xmppClient.AllowEncryption = true;
258 this.xmppClient.AllowScramSHA1 = true;
259 this.xmppClient.AllowScramSHA256 = true;
260 this.xmppClient.AllowQuickLogin = true;
261
262 this.xmppClient.RequestRosterOnStartup = true;
263 this.xmppClient.OnStateChanged += this.XmppClient_StateChanged;
264 this.xmppClient.OnConnectionError += this.XmppClient_ConnectionError;
265 this.xmppClient.OnError += this.XmppClient_Error;
266 this.xmppClient.OnChatMessage += this.XmppClient_OnChatMessage;
267 this.xmppClient.OnNormalMessage += this.XmppClient_OnNormalMessage;
268 this.xmppClient.OnPresenceSubscribe += this.XmppClient_OnPresenceSubscribe;
269 this.xmppClient.OnPresenceUnsubscribed += this.XmppClient_OnPresenceUnsubscribed;
270 this.xmppClient.OnRosterItemAdded += this.XmppClient_OnRosterItemAdded;
271 this.xmppClient.OnRosterItemUpdated += this.XmppClient_OnRosterItemUpdated;
272 this.xmppClient.OnRosterItemRemoved += this.XmppClient_OnRosterItemRemoved;
273 this.xmppClient.OnPresence += this.XmppClient_OnPresence;
274
275 this.xmppClient.RegisterMessageHandler("Delivered", ContractsClient.NamespaceOnboarding, this.TransferIdDelivered, true);
276
277 this.xmppFilteredEventSink = new EventFilter("XMPP Event Filter",
278 new XmppEventSink("XMPP Event Sink", this.xmppClient, ServiceRef.TagProfile.LogJid, false),
279 EventType.Error);
280
281 // Add extensions before connecting
282
283 this.abuseClient = new AbuseClient(this.xmppClient);
284
285 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LegalJid))
286 {
287 this.contractsClient = new ContractsClient(this.xmppClient, ServiceRef.TagProfile.LegalJid);
288 this.RegisterContractsEventHandlers();
289
290 await this.contractsClient.LoadKeys(false);
291 }
292
293 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.HttpFileUploadJid) && (ServiceRef.TagProfile.HttpFileUploadMaxSize > 0))
295
296 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.RegistryJid))
297 this.thingRegistryClient = new ThingRegistryClient(this.xmppClient, ServiceRef.TagProfile.RegistryJid);
298
299 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.ProvisioningJid))
300 {
301 this.provisioningClient = new ProvisioningClient(this.xmppClient, ServiceRef.TagProfile.ProvisioningJid)
302 {
303 ManagePresenceSubscriptionRequests = false
304 };
305
306 this.provisioningClient.CanControlQuestion += this.ProvisioningClient_CanControlQuestion;
307 this.provisioningClient.CanReadQuestion += this.ProvisioningClient_CanReadQuestion;
308 this.provisioningClient.IsFriendQuestion += this.ProvisioningClient_IsFriendQuestion;
309 }
310
311 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.EDalerJid))
312 {
313 this.eDalerClient = new EDalerClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.EDalerJid);
314 this.RegisterEDalerEventHandlers(this.eDalerClient);
315 }
316
317 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.NeuroFeaturesJid))
318 {
319 this.neuroFeaturesClient = new NeuroFeaturesClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.NeuroFeaturesJid);
320 this.RegisterNeuroFeatureEventHandlers(this.neuroFeaturesClient);
321 }
322
324 this.pushNotificationClient = new PushNotificationClient(this.xmppClient);
325
326 this.sensorClient = new SensorClient(this.xmppClient);
327 this.controlClient = new ControlClient(this.xmppClient);
328 this.concentratorClient = new ConcentratorClient(this.xmppClient);
329
330 if (string.IsNullOrEmpty(ServiceRef.TagProfile.PubSubJid))
331 this.pepClient = new PepClient(this.xmppClient);
332 else
333 this.pepClient = new PepClient(this.xmppClient, ServiceRef.TagProfile.PubSubJid);
334 this.ReregisterPepEventHandlers(this.pepClient);
335
336 this.httpxClient = new HttpxClient(this.xmppClient, 8192);
337 Types.SetModuleParameter("XMPP", this.xmppClient); // Makes the XMPP Client the default XMPP client, when resolving HTTP over XMPP requests.
338
339 //if(this.pubSubClient is null && !string.IsNullOrEmpty(ServiceRef.TagProfile.PubSubJid))
340 // this.pubSubClient = new PubSubClient(this.xmppClient, ServiceRef.TagProfile.PubSubJid);
341
342 this.IsLoggedOut = false;
343 await this.xmppClient.Connect(IsIpAddress ? string.Empty : this.domainName);
344 this.RecreateReconnectTimer();
345
346 // Await connected state during registration or user initiated log in, but not otherwise.
348 {
349 if (!await this.WaitForConnectedState(Constants.Timeouts.XmppConnect))
350 {
351 ServiceRef.LogService.LogWarning("Connection to XMPP server failed.",
352 new KeyValuePair<string, object?>("Domain", this.domainName ?? string.Empty),
353 new KeyValuePair<string, object?>("Account", this.accountName ?? string.Empty),
354 new KeyValuePair<string, object?>("Timeout", Constants.Timeouts.XmppConnect));
355 }
356 }
357 }
358 }
359 finally
360 {
361 this.isCreatingClient = false;
362 }
363 }
364
365#if DEBUG_DB_REMOTE
366 private class RemoteLedgerWriter()
367 : TextWriter(CultureInfo.CurrentCulture)
368 {
369 private readonly StringBuilder sb = new();
370
371 public override Encoding Encoding => Encoding.Unicode;
372 public override void Flush() => this.FlushAsync().Wait();
373 public override Task FlushAsync(CancellationToken cancellationToken) => this.FlushAsync();
374
375 public override async Task FlushAsync()
376 {
377 try
378 {
379 string s = this.sb.ToString();
380 string s2 = s.TrimStart();
381 if (string.IsNullOrEmpty(s2))
382 return;
383
384 if (ServiceRef.XmppService is not XmppService Service)
385 return;
386
387 RemoteSniffer? Sniffer = Service.debugSniffer;
388 if (Sniffer is null)
389 return;
390
391 this.sb.Clear();
392
393 int i = s2.IndexOf('<');
394 if (i > 0)
395 s2 = s2[i..];
396
397 string[] Rows = s.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
398
399 if (s2.StartsWith("<New", StringComparison.OrdinalIgnoreCase))
400 {
401 foreach (string Row in Rows)
402 await Sniffer.TransmitText(Row);
403 }
404 else if (s2.StartsWith("<Update", StringComparison.OrdinalIgnoreCase))
405 {
406 foreach (string Row in Rows)
407 await Sniffer.ReceiveText(Row);
408 }
409 else if (s2.StartsWith("<Delete", StringComparison.OrdinalIgnoreCase))
410 {
411 foreach (string Row in Rows)
412 await Sniffer.Error(Row);
413 }
414 else if (s2.StartsWith("<Clear", StringComparison.OrdinalIgnoreCase))
415 {
416 foreach (string Row in Rows)
417 await Sniffer.Warning(Row);
418 }
419 else
420 {
421 foreach (string Row in Rows)
422 await Sniffer.Information(Row);
423 }
424 }
425 catch (Exception)
426 {
427 // Ignore
428 }
429 }
430
431 public override void Write(char value) => this.sb.Append(value);
432 public override void Write(char[]? buffer) => this.sb.Append(buffer);
433 public override void Write(char[] buffer, int index, int count) => this.sb.Append(new string(buffer, index, count));
434 public override void Write(ReadOnlySpan<char> buffer) => this.sb.Append(new string(buffer));
435 public override void Write(bool value) => this.sb.Append(value);
436 public override void Write(int value) => this.sb.Append(value);
437 public override void Write(uint value) => this.sb.Append(value);
438 public override void Write(long value) => this.sb.Append(value);
439 public override void Write(ulong value) => this.sb.Append(value);
440 public override void Write(float value) => this.sb.Append(value);
441 public override void Write(double value) => this.sb.Append(value);
442 public override void Write(decimal value) => this.sb.Append(value);
443 public override void Write(string? value) => this.sb.Append(value);
444 public override void Write(object? value) => this.sb.Append(value);
445 public override void Write(StringBuilder? value) => this.sb.Append(value);
446 public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) => this.sb.Append(string.Format(this.FormatProvider, format, arg0));
447 public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) => this.sb.Append(string.Format(this.FormatProvider, format, arg0, arg1));
448 public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) => this.sb.Append(string.Format(this.FormatProvider, format, arg0, arg1, arg2));
449 public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) => this.sb.Append(string.Format(this.FormatProvider, format, arg));
450 public override Task WriteAsync(char value) { this.sb.Append(value); return Task.CompletedTask; }
451 public override Task WriteAsync(string? value) { this.sb.Append(value); return Task.CompletedTask; }
452 public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) { this.sb.Append(value); return Task.CompletedTask; }
453 public override Task WriteAsync(char[] buffer, int index, int count) { this.sb.Append(new string(buffer, index, count)); return Task.CompletedTask; }
454 public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) { this.sb.Append(buffer); return Task.CompletedTask; }
455 public override void WriteLine() => this.sb.AppendLine(string.Empty);
456 public override void WriteLine(char value) => this.sb.AppendLine(value.ToString());
457 public override void WriteLine(char[]? buffer) => this.sb.AppendLine(new string(buffer));
458 public override void WriteLine(char[] buffer, int index, int count) => this.sb.AppendLine(new string(buffer, index, count));
459 public override void WriteLine(ReadOnlySpan<char> buffer) => this.sb.AppendLine(new string(buffer));
460 public override void WriteLine(bool value) => this.sb.AppendLine(value.ToString());
461 public override void WriteLine(int value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
462 public override void WriteLine(uint value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
463 public override void WriteLine(long value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
464 public override void WriteLine(ulong value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
465 public override void WriteLine(float value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
466 public override void WriteLine(double value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
467 public override void WriteLine(decimal value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
468 public override void WriteLine(string? value) => this.sb.AppendLine(value?.ToString());
469 public override void WriteLine(StringBuilder? value) => this.sb.AppendLine(value?.ToString());
470 public override void WriteLine(object? value) => this.sb.AppendLine(value?.ToString());
471 public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) => this.sb.AppendLine(string.Format(this.FormatProvider, format, arg0));
472 public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) => this.sb.AppendLine(string.Format(this.FormatProvider, format, arg0, arg1));
473 public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1, object? arg2) => this.sb.AppendLine(string.Format(this.FormatProvider, format, arg0, arg1, arg2));
474 public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) => this.sb.AppendLine(string.Format(this.FormatProvider, format, arg));
475 public override Task WriteLineAsync(char value) { this.sb.AppendLine(value.ToString()); return Task.CompletedTask; }
476 public override Task WriteLineAsync(string? value) { this.sb.AppendLine(value); return Task.CompletedTask; }
477 public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) { this.sb.AppendLine(value?.ToString()); return Task.CompletedTask; }
478 public override Task WriteLineAsync(char[] buffer, int index, int count) { this.sb.AppendLine(new string(buffer, index, count)); return Task.CompletedTask; }
479 public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) { this.sb.AppendLine(new string(buffer.Span)); return Task.CompletedTask; }
480 public override Task WriteLineAsync() { this.sb.AppendLine(string.Empty); return Task.CompletedTask; }
481 }
482#endif
483
484 private async Task DestroyXmppClient()
485 {
486 this.reconnectTimer?.Dispose();
487 this.reconnectTimer = null;
488
489 await this.OnConnectionStateChanged(XmppState.Offline);
490
491 if (this.xmppFilteredEventSink is not null)
492 {
493 ServiceRef.LogService.RemoveListener(this.xmppFilteredEventSink);
494 await this.xmppFilteredEventSink.SecondarySink.DisposeAsync();
495 await this.xmppFilteredEventSink.DisposeAsync();
496 this.xmppFilteredEventSink = null;
497 }
498
499 this.contractsClient?.Dispose();
500 this.contractsClient = null;
501
502 this.fileUploadClient?.Dispose();
503 this.fileUploadClient = null;
504
505 this.thingRegistryClient?.Dispose();
506 this.thingRegistryClient = null;
507
508 this.provisioningClient?.Dispose();
509 this.provisioningClient = null;
510
511 this.eDalerClient?.Dispose();
512 this.eDalerClient = null;
513
514 this.neuroFeaturesClient?.Dispose();
515 this.neuroFeaturesClient = null;
516
517 this.pushNotificationClient?.Dispose();
518 this.pushNotificationClient = null;
519
520 this.sensorClient?.Dispose();
521 this.sensorClient = null;
522
523 this.controlClient?.Dispose();
524 this.controlClient = null;
525
526 this.concentratorClient?.Dispose();
527 this.concentratorClient = null;
528
529 this.pepClient?.Dispose();
530 this.pepClient = null;
531
532 this.abuseClient?.Dispose();
533 this.abuseClient = null;
534
535#if DEBUG_XMPP_REMOTE || DEBUG_DB_REMOTE || DEBUG_NFC_REMOTE
536 this.debugSniffer = null;
537#endif
538#if DEBUG_LOG_REMOTE
539 if (this.debugEventSink is not null)
540 {
541 Log.Unregister(this.debugEventSink);
542 this.debugEventSink?.Dispose();
543 this.debugEventSink = null;
544 }
545#endif
546 if (this.xmppClient is not null)
547 {
548 await this.xmppClient.DisposeAsync();
549 this.xmppClient = null;
550 }
551 }
552
553 private bool XmppStale()
554 {
555 return this.xmppClient is null ||
556 this.xmppClient.State == XmppState.Offline ||
557 this.xmppClient.State == XmppState.Error ||
558 (this.xmppClient.State != XmppState.Connected && (DateTime.Now - this.xmppLastStateChange).TotalSeconds >= 10);
559 }
560
561 public ISniffer[] RemoteSniffers
562 {
563 get
564 {
565#if DEBUG_XMPP_REMOTE || DEBUG_DB_REMOTE || DEBUG_NFC_REMOTE
566 if (this.debugSniffer is null)
567 return [];
568 else
569 return [this.debugSniffer];
570
571#else
572 return [];
573#endif
574 }
575 }
576
577 private bool XmppParametersCurrent()
578 {
579 if (this.xmppClient is null)
580 return false;
581
582 if (this.domainName != ServiceRef.TagProfile.Domain)
583 return false;
584
585 if (this.accountName != ServiceRef.TagProfile.Account)
586 return false;
587
588 if (this.passwordHash != ServiceRef.TagProfile.XmppPasswordHash)
589 return false;
590
591 if (this.passwordHashMethod != ServiceRef.TagProfile.XmppPasswordHashMethod)
592 return false;
593
594 if (this.contractsClient?.ComponentAddress != ServiceRef.TagProfile.LegalJid)
595 return false;
596
597 if (this.fileUploadClient?.FileUploadJid != ServiceRef.TagProfile.HttpFileUploadJid)
598 return false;
599
600 if (this.thingRegistryClient?.ThingRegistryAddress != ServiceRef.TagProfile.RegistryJid)
601 return false;
602
603 if (this.provisioningClient?.ProvisioningServerAddress != ServiceRef.TagProfile.ProvisioningJid)
604 return false;
605
606 if (this.eDalerClient?.ComponentAddress != ServiceRef.TagProfile.EDalerJid)
607 return false;
608
609 if (this.neuroFeaturesClient?.ComponentAddress != ServiceRef.TagProfile.NeuroFeaturesJid)
610 return false;
611
612 if ((this.pushNotificationClient is null) ^ !ServiceRef.TagProfile.SupportsPushNotification)
613 return false;
614
615 return true;
616 }
617
618 private void RecreateReconnectTimer()
619 {
620 this.reconnectTimer?.Dispose();
621 this.reconnectTimer = new Timer(this.ReconnectTimer_Tick, null, Constants.Intervals.Reconnect, Constants.Intervals.Reconnect);
622 }
623
627 [Obsolete("Use the DisposeAsync method.")]
628 public void Dispose()
629 {
630 this.DisposeAsync().Wait();
631 }
632
636 public async Task DisposeAsync()
637 {
638 this.reconnectTimer?.Dispose();
639 this.reconnectTimer = null;
640
641 if (this.xmppFilteredEventSink is not null)
642 {
643 ServiceRef.LogService.RemoveListener(this.xmppFilteredEventSink);
644 await this.xmppFilteredEventSink.SecondarySink.DisposeAsync();
645 await this.xmppFilteredEventSink.DisposeAsync();
646 this.xmppFilteredEventSink = null;
647 }
648
649 this.contractsClient?.Dispose();
650 this.contractsClient = null;
651
652 this.fileUploadClient?.Dispose();
653 this.fileUploadClient = null;
654
655 this.thingRegistryClient?.Dispose();
656 this.thingRegistryClient = null;
657
658 this.provisioningClient?.Dispose();
659 this.provisioningClient = null;
660
661 this.eDalerClient?.Dispose();
662 this.eDalerClient = null;
663
664 this.neuroFeaturesClient?.Dispose();
665 this.neuroFeaturesClient = null;
666
667 this.pushNotificationClient?.Dispose();
668 this.pushNotificationClient = null;
669
670 this.sensorClient?.Dispose();
671 this.sensorClient = null;
672
673 this.controlClient?.Dispose();
674 this.controlClient = null;
675
676 this.concentratorClient?.Dispose();
677 this.concentratorClient = null;
678
679 this.pepClient?.Dispose();
680 this.pepClient = null;
681
682 this.abuseClient?.Dispose();
683 this.abuseClient = null;
684
685 if (this.xmppClient is not null)
686 {
687 await this.xmppClient.DisposeAsync();
688 this.xmppClient = null;
689 }
690
691 /*
692 this.Dispose(true);
693 GC.SuppressFinalize(this);
694 */
695 }
696
697 /*
701 protected virtual void Dispose(bool disposing)
702 {
703 if (this.isDisposed)
704 {
705 return;
706 }
707
708 if (disposing)
709 {
710 this.abuseClient.Dispose();
711 this.contractsClient.Dispose();
712 this.fileUploadClient.Dispose();
713 this.httpxClient.Dispose();
714 this.reconnectTimer.Dispose();
715 this.sniffer.Dispose();
716 this.xmppClient.Dispose();
717 this.xmppFilteredEventSink.SecondarySink.Dispose();
718 this.xmppFilteredEventSink.Dispose();
719 }
720
721 this.isDisposed = true;
722 }
723 */
724#endregion
725
726 #region Lifecycle
727
728 public async Task<bool> WaitForConnectedState(TimeSpan Timeout)
729 {
730 if (this.xmppClient is null)
731 {
732 DateTime Start = DateTime.Now;
733
734 while (this.xmppClient is null && DateTime.Now - Start < Timeout)
735 await Task.Delay(1000);
736
737 if (this.xmppClient is null)
738 return false;
739
740 Timeout -= DateTime.Now - Start;
741 }
742
743 if (this.xmppClient.State == XmppState.Connected)
744 return true;
745
746 if (Timeout < TimeSpan.Zero)
747 return false;
748
749 int i = await this.xmppClient.WaitStateAsync((int)Timeout.TotalMilliseconds, XmppState.Connected);
750 return i >= 0;
751 }
752
753 public override Task Load(bool IsResuming, CancellationToken CancellationToken)
754 {
755 if (this.BeginLoad(IsResuming, CancellationToken))
756 {
757 try
758 {
759 ServiceRef.TagProfile.StepChanged += this.TagProfile_StepChanged;
760 ServiceRef.TagProfile.Changed += this.TagProfile_Changed;
761
762 _ = this.CreateClientAsync();
763
764 this.EndLoad(true);
765 }
766 catch (Exception Ex)
767 {
768 Ex = Log.UnnestException(Ex);
769 ServiceRef.LogService.LogException(Ex, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
770 this.EndLoad(false);
771 }
772 }
773 return Task.CompletedTask;
774 }
775
776 private async Task CreateClientAsync()
777 {
778 try
779 {
780 if (ServiceRef.TagProfile.ShouldCreateClient() && !this.XmppParametersCurrent())
781 await this.CreateXmppClient();
782
783 if ((this.xmppClient is not null) &&
784 this.xmppClient.State == XmppState.Connected &&
786 {
787 // Don't await this one, just fire and forget, to improve startup time.
788 _ = this.xmppClient.SetPresenceAsync(Availability.Online);
789 }
790 }
791 catch (Exception ex)
792 {
793 ex = Log.UnnestException(ex);
794 ServiceRef.LogService.LogException(ex, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
795 }
796 }
797
798 public override Task Unload()
799 {
800 return this.Unload(false);
801 }
802
803 public Task UnloadFast()
804 {
805 return this.Unload(true);
806 }
807
808 private async Task Unload(bool fast)
809 {
810 if (this.BeginUnload())
811 {
812 try
813 {
814 ServiceRef.TagProfile.StepChanged -= this.TagProfile_StepChanged;
815 ServiceRef.TagProfile.Changed -= this.TagProfile_Changed;
816
817 this.reconnectTimer?.Dispose();
818 this.reconnectTimer = null;
819
820 if (this.xmppClient is not null)
821 {
822 this.xmppClient.CheckConnection = false;
823
824 if (!fast)
825 {
826 try
827 {
828 await Task.WhenAny(
829 this.xmppClient.SetPresenceAsync(Availability.Offline),
830 Task.Delay(1000) // Wait at most 1000 ms.
831 );
832 }
833 catch (Exception)
834 {
835 // Ignore
836 }
837 }
838 }
839
840 await this.DestroyXmppClient();
841 }
842 catch (Exception ex)
843 {
844 ServiceRef.LogService.LogException(ex, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
845 }
846
847 this.EndUnload();
848 }
849 }
850
851 private void TagProfile_StepChanged(object? Sender, EventArgs e)
852 {
853 if (!this.IsLoaded)
854 return;
855
856 Task ExecutionTask = Task.Run(async () =>
857 {
858 try
859 {
860 bool CreateXmppClient = ServiceRef.TagProfile.ShouldCreateClient();
861
862 if (CreateXmppClient && !this.XmppParametersCurrent())
863 await this.CreateXmppClient();
864 else if (!CreateXmppClient)
865 await this.DestroyXmppClient();
866 }
867 catch (Exception ex)
868 {
869 ServiceRef.LogService.LogException(ex);
870 }
871 });
872 }
873
874 private void TagProfile_Changed(object? Sender, PropertyChangedEventArgs e)
875 {
876 if (e.PropertyName == nameof(ITagProfile.Account))
877 this.TagProfile_StepChanged(Sender, new EventArgs());
878 }
879
880 private Task XmppClient_Error(object? _, Exception e)
881 {
882 this.LatestError = e.Message;
883 return Task.CompletedTask;
884 }
885
886 private Task XmppClient_ConnectionError(object? _, Exception e)
887 {
888 if (e is ObjectDisposedException)
889 this.LatestConnectionError = ServiceRef.Localizer[nameof(AppResources.UnableToConnect)];
891 {
892 this.reconnectTimer?.Dispose();
893 this.reconnectTimer = null;
894 this.LatestConnectionError = e.Message;
895 }
896 else
897 this.LatestConnectionError = e.Message;
898
899 return Task.CompletedTask;
900 }
901
902 private async Task XmppClient_StateChanged(object? Sender, XmppState NewState)
903 {
904 this.xmppLastStateChange = DateTime.Now;
905
906 switch (NewState)
907 {
908 case XmppState.Connecting:
909 this.LatestError = string.Empty;
910 this.LatestConnectionError = string.Empty;
911 break;
912
913 case XmppState.Connected:
914 this.LatestError = string.Empty;
915 this.LatestConnectionError = string.Empty;
916
917 this.xmppConnected = true;
918
919 this.RecreateReconnectTimer();
920
921 if (string.IsNullOrEmpty(ServiceRef.TagProfile.XmppPasswordHashMethod))
922 {
924 this.xmppClient?.PasswordHash ?? string.Empty,
925 this.xmppClient?.PasswordHashMethod ?? string.Empty);
926 }
927 if (ServiceRef.TagProfile.NeedsUpdating() && await this.DiscoverServices())
928 {
929 if (this.contractsClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LegalJid))
930 {
931 this.contractsClient = new ContractsClient(this.xmppClient, ServiceRef.TagProfile.LegalJid);
932 this.RegisterContractsEventHandlers();
933
934 if (!await this.contractsClient.LoadKeys(false))
935 {
936 this.contractsClient.Dispose();
937 this.contractsClient = null;
938 }
939 }
940
941 if (this.fileUploadClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.HttpFileUploadJid) && (ServiceRef.TagProfile.HttpFileUploadMaxSize > 0))
943
944 if (this.thingRegistryClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.RegistryJid))
945 this.thingRegistryClient = new ThingRegistryClient(this.xmppClient, ServiceRef.TagProfile.RegistryJid);
946
947 if (this.provisioningClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.RegistryJid))
948 {
949 this.provisioningClient = new ProvisioningClient(this.xmppClient, ServiceRef.TagProfile.ProvisioningJid)
950 {
951 ManagePresenceSubscriptionRequests = false
952 };
953
954 this.provisioningClient.CanControlQuestion += this.ProvisioningClient_CanControlQuestion;
955 this.provisioningClient.CanReadQuestion += this.ProvisioningClient_CanReadQuestion;
956 this.provisioningClient.IsFriendQuestion += this.ProvisioningClient_IsFriendQuestion;
957 }
958
959 if (this.eDalerClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.EDalerJid))
960 {
961 this.eDalerClient = new EDalerClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.EDalerJid);
962 this.RegisterEDalerEventHandlers(this.eDalerClient);
963 }
964
965 if (this.neuroFeaturesClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.NeuroFeaturesJid))
966 {
967 this.neuroFeaturesClient = new NeuroFeaturesClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.NeuroFeaturesJid);
968 this.RegisterNeuroFeatureEventHandlers(this.neuroFeaturesClient);
969 }
970
971 if (this.pushNotificationClient is null && ServiceRef.TagProfile.SupportsPushNotification)
972 this.pushNotificationClient = new PushNotificationClient(this.xmppClient);
973
974 if (this.pepClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.PubSubJid))
975 {
976 this.pepClient = new PepClient(this.xmppClient, ServiceRef.TagProfile.PubSubJid);
977 this.ReregisterPepEventHandlers(this.pepClient);
978 //this.RegisterPubSubEventHandlers(this.pubSubClient);
979 }
980 }
981
982 // Check is xmpp password needs updating.
984 await ServiceRef.XmppService.TryGenerateAndChangePassword();
985
986 if (this.xmppFilteredEventSink is not null)
987 ServiceRef.LogService.AddListener(this.xmppFilteredEventSink);
988 break;
989
990 case XmppState.Offline:
991 case XmppState.Error:
992 if (this.xmppConnected && !this.IsUnloading)
993 {
994 this.xmppConnected = false;
995
996 try
997 {
998 if (this.xmppClient is not null && !this.xmppClient.Disposed)
999 await this.xmppClient.Reconnect();
1000 }
1001 catch (Exception)
1002 {
1003 // Ignore
1004 }
1005 }
1006 break;
1007 }
1008
1009 await this.OnConnectionStateChanged(NewState);
1010 }
1011
1015 public event EventHandlerAsync<XmppState>? ConnectionStateChanged;
1016
1017 private async Task OnConnectionStateChanged(XmppState NewState)
1018 {
1019 await this.ConnectionStateChanged.Raise(this, NewState);
1020 }
1021
1022 #endregion
1023
1024 #region State
1025
1026 public bool IsLoggedOut { get; private set; }
1027 public bool IsOnline => (this.xmppClient is not null) && this.xmppClient.State == XmppState.Connected;
1028 public XmppState State => this.xmppClient?.State ?? XmppState.Offline;
1029 public string BareJid => this.xmppClient?.BareJID ?? string.Empty;
1030
1031 public string? LatestError { get; private set; }
1032 public string? LatestConnectionError { get; private set; }
1033
1034 #endregion
1035
1036 #region Connections
1037
1038 private enum ConnectOperation
1039 {
1040 Connect,
1041 ConnectAndCreateAccount,
1042 ConnectToAccount
1043 }
1044
1045 public Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnect(string domain, bool isIpAddress, string hostName, int portNumber,
1046 string languageCode, Assembly applicationAssembly, Func<XmppClient, Task> connectedFunc)
1047 {
1048 return this.TryConnectInner(domain, isIpAddress, hostName, portNumber, string.Empty, string.Empty, string.Empty, languageCode,
1049 string.Empty, string.Empty, applicationAssembly, connectedFunc, ConnectOperation.Connect);
1050 }
1051
1052 public Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnectAndCreateAccount(string domain, bool isIpAddress, string hostName,
1053 int portNumber, string userName, string password, string languageCode, string ApiKey, string ApiSecret,
1054 Assembly applicationAssembly, Func<XmppClient, Task> connectedFunc)
1055 {
1056 return this.TryConnectInner(domain, isIpAddress, hostName, portNumber, userName, password, string.Empty, languageCode,
1057 ApiKey, ApiSecret, applicationAssembly, connectedFunc, ConnectOperation.ConnectAndCreateAccount);
1058 }
1059
1060 public Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnectAndConnectToAccount(string domain, bool isIpAddress, string hostName,
1061 int portNumber, string userName, string password, string passwordMethod, string languageCode, Assembly applicationAssembly,
1062 Func<XmppClient, Task> connectedFunc)
1063 {
1064 return this.TryConnectInner(domain, isIpAddress, hostName, portNumber, userName, password, passwordMethod, languageCode,
1065 string.Empty, string.Empty, applicationAssembly, connectedFunc, ConnectOperation.ConnectToAccount);
1066 }
1067
1068 private async Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnectInner(string Domain, bool IsIpAddress, string HostName,
1069 int PortNumber, string UserName, string Password, string PasswordMethod, string LanguageCode, string ApiKey, string ApiSecret,
1070 Assembly ApplicationAssembly, Func<XmppClient, Task> ConnectedFunc, ConnectOperation Operation)
1071 {
1072 // Use TaskCompletionSource for single completion
1073 TaskCompletionSource<bool> Tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
1074
1075 // Flags for tracking progress and outcome
1076 bool StreamNegotiation = false, StreamOpened = false, StartingEncryption = false, Authenticating = false, Registering = false, IsTimeout = false;
1077 string? ConnectionError = null;
1078 string? ErrorMessage = null;
1079 string[]? Alternatives = null;
1080
1081 XmppClient? Client = null;
1082 int Disposed = 0; // 0 = not disposed, 1 = disposing/disposing done
1083
1084 // Local guard function for tcs
1085 void TrySetResult(bool result)
1086 {
1087 // Ensure only one completion and don't run during/after dispose
1088 if (Interlocked.CompareExchange(ref Disposed, 0, 0) == 0)
1089 Tcs.TrySetResult(result);
1090 }
1091
1092 // Connection error event handler
1093 Task OnConnectionError(object? _, Exception ex)
1094 {
1095 if (Interlocked.CompareExchange(ref Disposed, 0, 0) == 1)
1096 return Task.CompletedTask; // Ignore after dispose
1097
1098 switch (ex)
1099 {
1100 case ObjectDisposedException:
1101 ConnectionError = ServiceRef.Localizer[nameof(AppResources.UnableToConnect)];
1102 break;
1104 this.reconnectTimer?.Dispose();
1105 this.reconnectTimer = null;
1106 break;
1108 Alternatives = Conflict.Alternatives;
1109 break;
1110 default:
1111 ConnectionError = ex.Message;
1112 break;
1113 }
1114
1115 TrySetResult(false);
1116 return Task.CompletedTask;
1117 }
1118
1119 // State change event handler
1120 async Task OnStateChanged(object? _, XmppState newState)
1121 {
1122 if (Interlocked.CompareExchange(ref Disposed, 0, 0) == 1)
1123 return;
1124
1125 switch (newState)
1126 {
1127 case XmppState.StreamNegotiation:
1128 StreamNegotiation = true;
1129 break;
1130 case XmppState.StreamOpened:
1131 StreamOpened = true;
1132 break;
1133 case XmppState.StartingEncryption:
1134 StartingEncryption = true;
1135 break;
1136 case XmppState.Authenticating:
1137 Authenticating = true;
1138 if (Operation == ConnectOperation.Connect)
1139 TrySetResult(true);
1140 break;
1141 case XmppState.Registering:
1142 Registering = true;
1143 break;
1144 case XmppState.Connected:
1145 TrySetResult(true);
1146 break;
1147 case XmppState.Offline:
1148 TrySetResult(false);
1149 break;
1150 case XmppState.Error:
1151 // Wait a bit for error event, but still time out if nothing else happens
1152 await Task.Delay(Constants.Timeouts.XmppConnect);
1153 TrySetResult(false);
1154 break;
1155 }
1156 }
1157
1158 try
1159 {
1160 if (string.IsNullOrEmpty(PasswordMethod))
1161 Client = new XmppClient(HostName, PortNumber, UserName, Password, LanguageCode, ApplicationAssembly, this.sniffer);
1162 else
1163 Client = new XmppClient(HostName, PortNumber, UserName, Password, PasswordMethod, LanguageCode, ApplicationAssembly, this.sniffer);
1164
1165 if (Operation == ConnectOperation.ConnectAndCreateAccount)
1166 {
1167 if (!string.IsNullOrEmpty(ApiKey) && !string.IsNullOrEmpty(ApiSecret))
1168 Client.AllowRegistration(ApiKey, ApiSecret);
1169 else
1170 Client.AllowRegistration();
1171 }
1172
1173 Client.TrustServer = !IsIpAddress;
1174 Client.AllowCramMD5 = false;
1175 Client.AllowDigestMD5 = false;
1176 Client.AllowPlain = false;
1177 Client.AllowEncryption = true;
1178 Client.AllowScramSHA1 = true;
1179 Client.AllowScramSHA256 = true;
1180 Client.AllowQuickLogin = true;
1181
1182 // Register handlers
1183 Client.OnConnectionError += OnConnectionError;
1184 Client.OnStateChanged += OnStateChanged;
1185
1186 // Begin connect
1187 Task ConnectTask = Client.Connect(IsIpAddress ? string.Empty : Domain);
1188
1189 // Set up timeout with cancellation
1190 using CancellationTokenSource Cts = new (Constants.Timeouts.XmppConnect);
1191
1192 Task CompletedTask = await Task.WhenAny(Tcs.Task, ConnectTask, Task.Delay(TimeSpan.FromSeconds(5), Cts.Token));
1193 bool Succeeded = false;
1194
1195 if (CompletedTask == Tcs.Task)
1196 {
1197 Succeeded = Tcs.Task.Result;
1198 }
1199 else if (CompletedTask == ConnectTask)
1200 {
1201 // The connect operation finished before the state machine did,
1202 // but we need to wait for state change events
1203 Succeeded = await Tcs.Task;
1204 }
1205 else
1206 {
1207 // Timeout
1208 IsTimeout = true;
1209 TrySetResult(false); // Attempt to signal timeout if not already completed
1210 Succeeded = false;
1211 }
1212
1213 // Call ConnectedFunc if successful
1214 if (Succeeded && ConnectedFunc is not null)
1215 await ConnectedFunc(Client);
1216
1217 // Remove event handlers BEFORE disposal
1218 Interlocked.Exchange(ref Disposed, 1);
1219 Client.OnStateChanged -= OnStateChanged;
1220 Client.OnConnectionError -= OnConnectionError;
1221
1222 await Client.DisposeAsync();
1223 Client = null;
1224
1225 // Set error message if needed
1226 if (!Succeeded && string.IsNullOrEmpty(ErrorMessage))
1227 {
1228 if (this.sniffer is not null)
1229 System.Diagnostics.Debug.WriteLine(await this.sniffer.SnifferToTextAsync(), "Sniffer");
1230
1231 if (!StreamNegotiation || IsTimeout)
1232 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.CantConnectTo), Domain];
1233 else if (!StreamOpened)
1234 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.DomainIsNotAValidOperator), Domain];
1235 else if (!StartingEncryption)
1237 else if (!Authenticating)
1238 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.UnableToAuthenticateWith), Domain];
1239 else if (!Registering)
1240 {
1241 if (!string.IsNullOrWhiteSpace(ConnectionError))
1242 ErrorMessage = ConnectionError;
1243 else
1245 }
1246 else if (Operation == ConnectOperation.ConnectAndCreateAccount)
1247 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.UsernameNameAlreadyTaken), this.accountName ?? string.Empty];
1248 else if (Operation == ConnectOperation.ConnectToAccount)
1249 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.InvalidUsernameOrPassword), this.accountName ?? string.Empty];
1250 else
1251 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.UnableToConnectTo), Domain];
1252 }
1253
1254 return (Succeeded, ErrorMessage, Alternatives);
1255 }
1256 catch (Exception ex)
1257 {
1258 ServiceRef.LogService.LogException(ex, new KeyValuePair<string, object?>(nameof(ConnectOperation), Operation.ToString()));
1259 return (false, ServiceRef.Localizer[nameof(AppResources.UnableToConnectTo), Domain], null);
1260 }
1261 finally
1262 {
1263 // Final fallback cleanup if not already disposed
1264 if (Client is not null)
1265 {
1266 try
1267 {
1268 Interlocked.Exchange(ref Disposed, 1);
1269 Client.OnStateChanged -= OnStateChanged;
1270 Client.OnConnectionError -= OnConnectionError;
1271 await Client.DisposeAsync();
1272 Client = null;
1273
1274 }
1275 catch { /* Swallow to avoid masking original exception */ }
1276 }
1277 }
1278 }
1279
1280 private void ReconnectTimer_Tick(object? _)
1281 {
1282 if (this.xmppClient is null)
1283 return;
1284
1285 if (!ServiceRef.NetworkService.IsOnline)
1286 return;
1287
1288 if (this.XmppStale())
1289 {
1290 this.xmppLastStateChange = DateTime.Now;
1291
1292 if (!this.xmppClient.Disposed)
1293 SafeFireAndForget(this.xmppClient.Reconnect());
1294 }
1295 }
1300 private static void SafeFireAndForget(Task task)
1301 {
1302 if (task is null)
1303 return;
1304
1305 _ = task.ContinueWith(t =>
1306 {
1307 if (t.Exception is not null)
1308 ServiceRef.LogService.LogException(t.Exception);
1309 }, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.RunContinuationsAsynchronously);
1310 }
1311 #endregion
1312
1313 #region Password
1314
1320 public Task<bool> ChangePassword(string NewPassword)
1321 {
1322 TaskCompletionSource<bool> PasswordChanged = new();
1323
1324 this.XmppClient.ChangePassword(NewPassword, (sender, e) =>
1325 {
1326 PasswordChanged.TrySetResult(e.Ok);
1327 return Task.CompletedTask;
1328 }, null);
1329
1330 return PasswordChanged.Task;
1331 }
1332
1339 public async Task<bool> TryGenerateAndChangePassword()
1340 {
1341 bool ChangeSucceeded = false;
1342
1343 try
1344 {
1345 string NewPassword = ServiceRef.CryptoService.CreateRandomPassword();
1346 if (await this.ChangePassword(NewPassword))
1347 {
1348 ServiceRef.TagProfile.SetAccount(ServiceRef.TagProfile.Account!, NewPassword, string.Empty);
1349 ChangeSucceeded = true;
1350 }
1351 }
1352 catch (Exception Ex)
1353 {
1354 ServiceRef.LogService.LogException(Ex, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
1355 }
1356
1357 // Update the profile & timer based on success/failure:
1359 if (ChangeSucceeded)
1360 {
1361 this.updatePasswordTimer?.Dispose();
1362 this.updatePasswordTimer = null;
1363 }
1364 else
1365 {
1366 this.RecreateUpdatePasswordTimer();
1367 }
1368
1369 return ChangeSucceeded;
1370 }
1371
1372
1373 private async void UpdatePasswordTimer_Tick(object? _)
1374 {
1375 if (this.xmppClient is null)
1376 return;
1377
1378 if (!ServiceRef.NetworkService.IsOnline)
1379 return;
1380
1381 await this.TryGenerateAndChangePassword();
1382 }
1383
1384 private void RecreateUpdatePasswordTimer()
1385 {
1386 this.updatePasswordTimer?.Dispose();
1387 this.updatePasswordTimer = new Timer(this.UpdatePasswordTimer_Tick, null, Constants.Intervals.Reconnect, Constants.Intervals.Reconnect);
1388 }
1389
1390 #endregion
1391
1392 #region Components & Services
1393
1399 public Task<ServiceDiscoveryEventArgs> SendServiceDiscoveryRequest(string FullJid)
1400 {
1401 TaskCompletionSource<ServiceDiscoveryEventArgs> Result = new();
1402
1403 this.XmppClient.SendServiceDiscoveryRequest(FullJid, (_, e) =>
1404 {
1405 Result.TrySetResult(e);
1406 return Task.CompletedTask;
1407 }, null);
1408
1409 return Result.Task;
1410 }
1411
1417 public async Task<bool> DiscoverServices(XmppClient? Client = null)
1418 {
1419 Client ??= this.xmppClient;
1420
1421 if (Client is null)
1422 return false;
1423
1425
1426 try
1427 {
1428 Response = await Client.ServiceItemsDiscoveryAsync(null, string.Empty, string.Empty);
1429 }
1430 catch (Exception ex)
1431 {
1432 if (this.sniffer is not null)
1433 {
1434 string CommsDump = await this.sniffer.SnifferToTextAsync();
1435 ServiceRef.LogService.LogException(ex, new KeyValuePair<string, object?>("Sniffer", CommsDump));
1436 }
1437
1438 return false;
1439 }
1440
1441 List<Task> Tasks = [];
1442 object SynchObject = new();
1443
1444 Tasks.Add(CheckFeatures(Client, SynchObject));
1445
1446 foreach (Item Item in Response.Items)
1447 Tasks.Add(CheckComponent(Client, Item, SynchObject));
1448
1449 await Task.WhenAll([.. Tasks]);
1450
1451 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LegalJid))
1452 return false;
1453
1454 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.HttpFileUploadJid) || (ServiceRef.TagProfile.HttpFileUploadMaxSize <= 0))
1455 return false;
1456
1457 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LogJid))
1458 return false;
1459
1460 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.EDalerJid))
1461 return false;
1462
1463 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.NeuroFeaturesJid))
1464 return false;
1465
1466 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.PubSubJid))
1467 return false;
1468
1470 return false;
1471
1472 return true;
1473 }
1474
1475 private static async Task CheckFeatures(XmppClient Client, object SynchObject)
1476 {
1477 ServiceDiscoveryEventArgs e = await Client.ServiceDiscoveryAsync(string.Empty);
1478
1479 lock (SynchObject)
1480 {
1481 ServiceRef.TagProfile.SupportsPushNotification = e.HasFeature(PushNotificationClient.MessagePushNamespace);
1482 }
1483 }
1484
1485 private static async Task CheckComponent(XmppClient Client, Item Item, object SynchObject)
1486 {
1487 ServiceDiscoveryEventArgs ItemResponse = await Client.ServiceDiscoveryAsync(null, Item.JID, Item.Node);
1488
1489 lock (SynchObject)
1490 {
1492 ServiceRef.TagProfile.LegalJid = Item.JID;
1493
1495 ServiceRef.TagProfile.RegistryJid = Item.JID;
1496
1500 {
1501 ServiceRef.TagProfile.ProvisioningJid = Item.JID;
1502 }
1503
1504 if (ItemResponse.HasFeature(HttpFileUploadClient.Namespace))
1505 {
1506 long MaxSize = HttpFileUploadClient.FindMaxFileSize(Client, ItemResponse) ?? 0;
1508 }
1509
1511 ServiceRef.TagProfile.LogJid = Item.JID;
1512
1514 ServiceRef.TagProfile.LogJid = Item.JID;
1515
1516 if (ItemResponse.HasFeature(EDalerClient.NamespaceEDaler))
1517 ServiceRef.TagProfile.EDalerJid = Item.JID;
1518
1520 ServiceRef.TagProfile.NeuroFeaturesJid = Item.JID;
1521
1522 if (ItemResponse.HasFeature(PubSubClient.NamespacePubSub))
1523 ServiceRef.TagProfile.PubSubJid = Item.JID;
1524 }
1525 }
1526
1527 #endregion
1528
1529 #region Transfer
1530
1531 private async Task TransferIdDelivered(object? Sender, MessageEventArgs e)
1532 {
1534 return;
1535
1536 string Code = XML.Attribute(e.Content, "code");
1537 bool Deleted = XML.Attribute(e.Content, "deleted", false);
1538
1539 if (!Deleted)
1540 return;
1541
1542 string CodesGenerated = await RuntimeSettings.GetAsync(Constants.Settings.TransferIdCodeSent, string.Empty);
1543 string[] Codes = CodesGenerated.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
1544
1545 if (Array.IndexOf<string>(Codes, Code) < 0)
1546 return;
1547
1548 await this.DestroyXmppClient();
1549
1550 this.domainName = string.Empty;
1551 this.accountName = string.Empty;
1552 this.passwordHash = string.Empty;
1553 this.passwordHashMethod = string.Empty;
1554 this.xmppConnected = false;
1555
1557 if (App.Current is not null)
1558 await App.Current.ForceSaveAsync();
1560 await Database.Provider.Flush();
1561 WeakReferenceMessenger.Default.Send(new RegistrationPageMessage(ServiceRef.TagProfile.Step));
1562 await ServiceRef.NavigationService.GoToAsync(nameof(OnboardingPage), new OnboardingNavigationArgs() { Scenario = OnboardingScenario.FullSetup });
1563 }
1564
1569 public async Task AddTransferCode(string Code)
1570 {
1571 string CodesGenerated = await RuntimeSettings.GetAsync(Constants.Settings.TransferIdCodeSent, string.Empty);
1572
1573 if (string.IsNullOrEmpty(CodesGenerated))
1574 CodesGenerated = Code;
1575 else
1576 CodesGenerated += "\r\n" + Code;
1577
1579 await Database.Provider.Flush();
1580 }
1581
1582 #endregion
1583
1584 #region Presence Subscriptions
1585
1586 private async Task XmppClient_OnPresenceSubscribe(object? Sender, PresenceEventArgs e)
1587 {
1588 LegalIdentity? RemoteIdentity = null;
1589 string FriendlyName = string.IsNullOrWhiteSpace(e.NickName) ? e.FromBareJID : e.NickName;
1590 string? PhotoUrl = null;
1591 int PhotoWidth = 0;
1592 int PhotoHeight = 0;
1593
1594 foreach (XmlNode N in e.Presence.ChildNodes)
1595 {
1596 if (N is XmlElement E && E.LocalName == "identity" && E.NamespaceURI == ContractsClient.NamespaceLegalIdentitiesCurrent)
1597 {
1598 RemoteIdentity = LegalIdentity.Parse(E);
1599 if (RemoteIdentity is not null)
1600 {
1601 FriendlyName = ContactInfo.GetFriendlyName(RemoteIdentity);
1602
1605 if (Status != IdentityStatus.Valid)
1606 {
1607 await e.Decline();
1608
1609 Log.Warning("Invalid ID received. Presence subscription declined.", e.FromBareJID, RemoteIdentity.Id, "IdValidationError",
1610 new KeyValuePair<string, object?>("Recipient JID", this.BareJid),
1611 new KeyValuePair<string, object?>("Sender JID", e.FromBareJID),
1612 new KeyValuePair<string, object?>("Legal ID", RemoteIdentity.Id),
1613 new KeyValuePair<string, object?>("Validation", Status));
1614 return;
1615 }
1616
1617 break;
1618 }
1619 }
1620 }
1621
1623 if ((Info is not null) && Info.AllowSubscriptionFrom.HasValue)
1624 {
1625 if (Info.AllowSubscriptionFrom.Value)
1626 await e.Accept();
1627 else
1628 await e.Decline();
1629
1630 if (Info.FriendlyName != FriendlyName || ((RemoteIdentity is not null) && Info.LegalId != RemoteIdentity.Id))
1631 {
1632 if (RemoteIdentity is not null)
1633 {
1634 Info.LegalId = RemoteIdentity.Id;
1635 Info.LegalIdentity = RemoteIdentity;
1636 }
1637
1638 Info.FriendlyName = FriendlyName;
1639 await Database.Update(Info);
1640 }
1641
1642 return;
1643 }
1644
1645 if ((RemoteIdentity is not null) && (RemoteIdentity.Attachments is not null))
1646 {
1647 (PhotoUrl, PhotoWidth, PhotoHeight) = await PhotosLoader.LoadPhotoAsTemporaryFile(RemoteIdentity.Attachments,
1649 }
1650
1651 SubscriptionRequestViewModel SubscriptionRequestViewModel = new(e.FromBareJID, FriendlyName, PhotoUrl, PhotoWidth, PhotoHeight);
1653
1656
1657 switch (Action)
1658 {
1659 case PresenceRequestAction.Accept:
1660 await e.Accept();
1661
1662 if (Info is null)
1663 {
1664 Info = new ContactInfo()
1665 {
1666 AllowSubscriptionFrom = true,
1667 BareJid = e.FromBareJID,
1668 FriendlyName = string.IsNullOrWhiteSpace(e.NickName) ? e.FromBareJID : e.NickName,
1669 IsThing = false
1670 };
1671
1672 await Database.Insert(Info);
1673 }
1674 else if (!Info.AllowSubscriptionFrom.HasValue || !Info.AllowSubscriptionFrom.Value)
1675 {
1676 Info.AllowSubscriptionFrom = true;
1677 await Database.Update(Info);
1678 }
1679
1681
1682 if (Item is null || (Item.State != SubscriptionState.Both && Item.State != SubscriptionState.To))
1683 {
1686
1687 await ServiceRef.PopupService.PushAsync(SubscribeToPopup);
1688 bool? SubscribeTo = await SubscribeToViewModel.Result;
1689
1690 if (SubscribeTo.HasValue && SubscribeTo.Value)
1691 {
1692 string IdXml;
1693
1695 IdXml = string.Empty;
1696 else
1697 {
1698 StringBuilder Xml = new();
1699 ServiceRef.TagProfile.LegalIdentity.Serialize(Xml, true, true, true, true, true, true, true);
1700 IdXml = Xml.ToString();
1701 }
1702
1704 }
1705 }
1706 break;
1707
1708 case PresenceRequestAction.Reject:
1709 await e.Decline();
1710
1711 if (this.abuseClient is null)
1712 break;
1713
1716
1719
1720 if (ReportOrBlock == ReportOrBlockAction.Block || ReportOrBlock == ReportOrBlockAction.Report)
1721 {
1722 if (Info is null)
1723 {
1724 Info = new ContactInfo()
1725 {
1726 AllowSubscriptionFrom = false,
1727 BareJid = e.FromBareJID,
1728 FriendlyName = string.IsNullOrWhiteSpace(e.NickName) ? e.FromBareJID : e.NickName,
1729 IsThing = false
1730 };
1731
1732 await Database.Insert(Info);
1733 }
1734 else if (!Info.AllowSubscriptionFrom.HasValue || Info.AllowSubscriptionFrom.Value)
1735 {
1736 Info.AllowSubscriptionFrom = false;
1737 await Database.Update(Info);
1738 }
1739
1740 if (ReportOrBlock == ReportOrBlockAction.Report)
1741 {
1744
1745 await ServiceRef.PopupService.PushAsync(ReportTypePopup);
1747
1748 if (ReportType.HasValue)
1749 {
1750 TaskCompletionSource<bool> Result = new();
1751
1752 await this.abuseClient.BlockJID(e.FromBareJID, ReportType.Value, (sender2, e2) =>
1753 {
1754 Result.TrySetResult(e.Ok);
1755 return Task.CompletedTask;
1756 }, null);
1757
1758 await Result.Task;
1759 }
1760 }
1761 }
1762 break;
1763
1764 case PresenceRequestAction.Ignore:
1765 default:
1766 break;
1767 }
1768
1769 await this.OnPresenceSubscribe.Raise(this, e);
1770 }
1771
1772 private async Task XmppClient_OnPresenceUnsubscribed(object? Sender, PresenceEventArgs e)
1773 {
1775 if ((ContactInfo is not null) && ContactInfo.AllowSubscriptionFrom.HasValue && ContactInfo.AllowSubscriptionFrom.Value)
1776 {
1777 ContactInfo.AllowSubscriptionFrom = null;
1779 }
1780
1781 await this.OnPresenceUnsubscribed.Raise(this, e);
1782 }
1783
1784 #endregion
1785
1786 #region IQ Stanzas (Information Query)
1787
1796 public Task<XmlElement> IqSetAsync(string To, string Xml)
1797 {
1798 return this.XmppClient.IqSetAsync(To, Xml);
1799 }
1800
1804 private XmppClient XmppClient
1805 {
1806 get
1807 {
1808 if (this.xmppClient is null)
1809 throw new Exception("Not connected to XMPP network.");
1810
1811 return this.xmppClient;
1812 }
1813 }
1814
1815 #endregion
1816
1817 #region Messages
1818
1834 public void SendMessage(QoSLevel QoS, Waher.Networking.XMPP.MessageType Type, string Id, string To, string CustomXml, string Body,
1835 string Subject, string Language, string ThreadId, string ParentThreadId, EventHandlerAsync<DeliveryEventArgs>? DeliveryCallback, object? State)
1836 {
1837 this.XmppClient.SendMessage(QoS, Type, Id, To, CustomXml, Body, Subject, Language, ThreadId, ParentThreadId, DeliveryCallback, State);
1838 //TODO: ENABLE E2E
1839 }
1840
1841 private Task XmppClient_OnNormalMessage(object? Sender, MessageEventArgs e)
1842 {
1843 Log.Warning("Unhandled message received.", e.To, e.From,
1844 new KeyValuePair<string, object?>("Stanza", e.Message.OuterXml));
1845
1846 return Task.CompletedTask;
1847 }
1848
1849 private async Task XmppClient_OnChatMessage(object? Sender, MessageEventArgs e)
1850 {
1851 string RemoteBareJid = e.FromBareJID;
1852
1853 foreach (XmlNode N in e.Message.ChildNodes)
1854 {
1855 if (N is XmlElement E &&
1856 E.LocalName == "qlRef" &&
1857 E.NamespaceURI == XmppClient.NamespaceQuickLogin &&
1858 RemoteBareJid.IndexOf('@') < 0 &&
1859 RemoteBareJid.IndexOf('/') < 0)
1860 {
1861 LegalIdentity? RemoteIdentity = null;
1862
1863 foreach (XmlNode N2 in E.ChildNodes)
1864 {
1865 if (N2 is XmlElement E2 &&
1866 E2.LocalName == "identity" &&
1868 {
1869 RemoteIdentity = LegalIdentity.Parse(E2);
1870 break;
1871 }
1872 }
1873
1874 if (RemoteIdentity is not null)
1875 {
1876 IdentityStatus Status = await this.ValidateIdentity(RemoteIdentity);
1877 if (Status != IdentityStatus.Valid)
1878 {
1879 Log.Warning("Message rejected because the embedded legal identity was not valid.",
1880 new KeyValuePair<string, object?>("Identity", RemoteIdentity.Id),
1881 new KeyValuePair<string, object?>("From", RemoteBareJid),
1882 new KeyValuePair<string, object?>("Status", Status));
1883 return;
1884 }
1885
1886 string Jid = RemoteIdentity["JID"];
1887
1888 if (string.IsNullOrEmpty(Jid))
1889 {
1890 Log.Warning("Message rejected because the embedded legal identity lacked JID.",
1891 new KeyValuePair<string, object?>("Identity", RemoteIdentity.Id),
1892 new KeyValuePair<string, object?>("From", RemoteBareJid),
1893 new KeyValuePair<string, object?>("Status", Status));
1894 return;
1895 }
1896
1897 if (!string.Equals(XML.Attribute(E, "bareJid", string.Empty), Jid, StringComparison.OrdinalIgnoreCase))
1898 {
1899 Log.Warning("Message rejected because the embedded legal identity had a different JID compared to the JID of the quick-login reference.",
1900 new KeyValuePair<string, object?>("Identity", RemoteIdentity.Id),
1901 new KeyValuePair<string, object?>("From", RemoteBareJid),
1902 new KeyValuePair<string, object?>("Status", Status));
1903 return;
1904 }
1905
1906 RemoteBareJid = Jid;
1907 }
1908 }
1909 }
1910
1911 ContactInfo ContactInfo = await ContactInfo.FindByBareJid(RemoteBareJid);
1912 string FriendlyName = ContactInfo?.FriendlyName ?? RemoteBareJid;
1913 string? ReplaceObjectId = null;
1914
1915 ChatMessage Message = new()
1916 {
1917 Created = DateTime.UtcNow,
1918 RemoteBareJid = RemoteBareJid,
1919 RemoteObjectId = e.Id,
1920 MessageType = NeuroAccessMaui.UI.Pages.Contacts.Chat.MessageType.Received,
1921 Html = string.Empty,
1922 PlainText = e.Body,
1923 Markdown = string.Empty
1924 };
1925
1926 foreach (XmlNode N in e.Message.ChildNodes)
1927 {
1928 if (N is XmlElement E)
1929 {
1930 switch (N.LocalName)
1931 {
1932 case "content":
1933 if (E.NamespaceURI == "urn:xmpp:content")
1934 {
1935 string Type = XML.Attribute(E, "type");
1936
1937 switch (Type)
1938 {
1939 case "text/markdown":
1940 Message.Markdown = E.InnerText;
1941 break;
1942
1943 case "text/plain":
1944 Message.PlainText = E.InnerText;
1945 break;
1946
1947 case "text/html":
1948 Message.Html = E.InnerText;
1949 break;
1950 }
1951 }
1952 break;
1953
1954 case "html":
1955 if (E.NamespaceURI == "http://jabber.org/protocol/xhtml-im")
1956 {
1957 string Html = E.InnerXml;
1958
1959 int i = Html.IndexOf("<body", StringComparison.OrdinalIgnoreCase);
1960 if (i >= 0)
1961 {
1962 i = Html.IndexOf('>', i + 5);
1963 if (i >= 0)
1964 Html = Html[(i + 1)..].TrimStart();
1965
1966 i = Html.LastIndexOf("</body>", StringComparison.OrdinalIgnoreCase);
1967 if (i >= 0)
1968 Html = Html[..i].TrimEnd();
1969 }
1970
1971 Message.Html = Html;
1972 }
1973 break;
1974
1975 case "replace":
1976 if (E.NamespaceURI == "urn:xmpp:message-correct:0")
1977 ReplaceObjectId = XML.Attribute(E, "id");
1978 break;
1979
1980 case "delay":
1981 if (E.NamespaceURI == PubSubClient.NamespaceDelayedDelivery &&
1982 E.HasAttribute("stamp") &&
1983 XML.TryParse(E.GetAttribute("stamp"), out DateTime Timestamp2))
1984 {
1985 Message.Created = Timestamp2.ToUniversalTime();
1986 }
1987 break;
1988 }
1989 }
1990 }
1991
1992 if (!string.IsNullOrEmpty(Message.Markdown))
1993 {
1994 try
1995 {
1996 MarkdownSettings Settings = new()
1997 {
1998 AllowScriptTag = false,
1999 EmbedEmojis = false, // TODO: Emojis
2000 AudioAutoplay = false,
2001 AudioControls = false,
2002 ParseMetaData = false,
2003 VideoAutoplay = false,
2004 VideoControls = false
2005 };
2006
2007 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(Message.Markdown, Settings);
2008
2009 if (string.IsNullOrEmpty(Message.PlainText))
2010 Message.PlainText = (await Doc.GeneratePlainText()).Trim();
2011
2012 if (string.IsNullOrEmpty(Message.Html))
2013 Message.Html = HtmlDocument.GetBody(await Doc.GenerateHTML());
2014 }
2015 catch (Exception ex)
2016 {
2017 ServiceRef.LogService.LogException(ex);
2018 Message.Markdown = string.Empty;
2019 }
2020 }
2021
2022 if (string.IsNullOrEmpty(ReplaceObjectId))
2023 await Database.Insert(Message);
2024 else
2025 {
2026 ChatMessage Old = await Database.FindFirstIgnoreRest<ChatMessage>(new FilterAnd(
2027 new FilterFieldEqualTo("RemoteBareJid", RemoteBareJid),
2028 new FilterFieldEqualTo("RemoteObjectId", ReplaceObjectId)));
2029
2030 if (Old is null)
2031 {
2032 ReplaceObjectId = null;
2033 await Database.Insert(Message);
2034 }
2035 else
2036 {
2037 Old.Updated = Message.Created;
2038 Old.Html = Message.Html;
2039 Old.PlainText = Message.PlainText;
2040 Old.Markdown = Message.Markdown;
2041
2042 await Database.Update(Old);
2043
2044 Message = Old;
2045 }
2046 }
2047
2048 MainThread.BeginInvokeOnMainThread(async () =>
2049 {
2050 if (ServiceRef.NavigationService.CurrentPage is ChatPage &&
2051 ServiceRef.NavigationService.CurrentPage.BindingContext is ChatViewModel ChatViewModel &&
2052 string.Equals(ChatViewModel.BareJid, RemoteBareJid, StringComparison.OrdinalIgnoreCase))
2053 {
2054 if (string.IsNullOrEmpty(ReplaceObjectId))
2055 await ChatViewModel.MessageAddedAsync(Message);
2056 else
2057 await ChatViewModel.MessageUpdatedAsync(Message);
2058 }
2059 else
2060 {
2061 string Title = ServiceRef.Localizer[nameof(AppResources.NotificationChatTitle), RemoteBareJid];
2063
2064 NotificationIntent Intent = new()
2065 {
2067 Title = Title,
2068 Body = Body,
2069 Action = NotificationAction.OpenChat,
2070 EntityId = RemoteBareJid,
2071 CorrelationId = RemoteBareJid,
2072 Presentation = NotificationPresentation.StoreOnly
2073 };
2074
2075 await ServiceRef.Provider.GetRequiredService<INotificationServiceV2>().AddAsync(Intent, NotificationSource.Xmpp, null, CancellationToken.None);
2076 }
2077 });
2078 }
2079
2080 private Task ContractsClient_ClientMessage(object? Sender, ClientMessageEventArgs e)
2081 {
2082 string Message = e.Body ?? string.Empty;
2083
2084
2085 if (!string.IsNullOrEmpty(e.Code))
2086 {
2087 try
2088 {
2089 string Key = "ClientMessage" + e.Code;
2090 string LocalizedMessage = ServiceRef.Localizer[Key, false];
2091
2092 if (!string.IsNullOrEmpty(LocalizedMessage) && !LocalizedMessage.Equals(Key, StringComparison.Ordinal))
2093 {
2094 Message = LocalizedMessage;
2095 }
2096 }
2097 catch (Exception)
2098 {
2099 // Ignore localization lookup issues.
2100 }
2101 }
2102
2103 ApplicationReview? Review = BuildApplicationReview(e, Message);
2104
2105 // Persist rejection details on current KYC reference, and reflect in UI if open
2106 MainThread.BeginInvokeOnMainThread(async () =>
2107 {
2108 try
2109 {
2110 KycReference? Ref = null;
2112
2113 if (AppId is not null)
2114 {
2115 try
2116 {
2117 Ref = await Database.FindFirstIgnoreRest<KycReference>(new FilterFieldEqualTo(nameof(KycReference.CreatedIdentityId), AppId.Id));
2118 }
2119 catch (Exception Ex2)
2120 {
2121 ServiceRef.LogService.LogException(Ex2);
2122 }
2123 }
2124
2125 if (Ref is null)
2126 {
2127 try
2128 {
2129 List<KycReference> All = [.. await Database.Find<KycReference>()];
2130 if (AppId is not null)
2131 {
2132 Ref = All.FirstOrDefault(r => string.Equals(r.CreatedIdentityId, AppId.Id, StringComparison.OrdinalIgnoreCase));
2133 }
2134
2135 Ref ??= All
2136 .Where(r => r.CreatedIdentityState == IdentityState.Created && !string.IsNullOrEmpty(r.CreatedIdentityId))
2137 .OrderByDescending(r => r.UpdatedUtc)
2138 .FirstOrDefault();
2139
2140 Ref ??= All
2141 .Where(r => !string.IsNullOrEmpty(r.CreatedIdentityId))
2142 .OrderByDescending(r => r.UpdatedUtc)
2143 .FirstOrDefault();
2144 }
2145 catch (Exception Ex3)
2146 {
2147 ServiceRef.LogService.LogException(Ex3);
2148 }
2149 }
2150
2151 if (Ref is not null && Review is not null)
2152 {
2153 await ServiceRef.KycService.ApplyApplicationReviewAsync(Ref, Review);
2154
2155 if (ServiceRef.NavigationService.CurrentPage is KycProcessPage Page && Page.BindingContext is KycProcessViewModel Vm)
2156 {
2157 await Vm.ApplyApplicationReviewAsync(Review);
2158 }
2159 }
2160 }
2161 catch (Exception Ex)
2162 {
2163 ServiceRef.LogService.LogException(Ex);
2164 }
2165 });
2166
2167 // TODO: Event arguments contain more detailed information about:
2168 //
2169 // Properties & attachments that have been validated: e.ValidClaims, e.ValidPhotos
2170 // Properties & attachments that have been invalidated: e.InvalidClaims, e.InvalidPhotos
2171 // Properties & attachments that are still unvalidated: e.UnvalidatedClaims, e.UnvalidatedPhotos
2172 //
2173 // Body message only contains first message reported.
2174 //
2175 // Codes that need localized messages (defined in broker & services):
2176 //
2177 // ManualReview: Unable to validate application automatically. The application needs to be validated manually, or by peer review.
2178 // UnableReview: Unable to validate the review.
2179 // InvalidType: "Expected value of type " + typeof(T).FullName + "."
2180 // IdMismatch: Identifier does not correspond to created identifier.
2181 // JidMismatch: JID does not correspond to client JID.
2182 // AccountMismatch: Account name does not match account.
2183 // ProviderMismatch: Provider does not match legal component address.
2184 // StateMismatch: State does not match identity state.
2185 // CreatedMismatch: Created does not match identity creation timestamp.
2186 // UpdatedMismatch: Updated does not match identity update timestamp.
2187 // FromMismatch: From does not match identity creation timestamp.
2188 // ToMismatch: To does not match identity creation timestamp.
2189 // NoClientURL: No Client URL provided.
2190 // ReviewerExternal: Peer reviewer is external. Peer-review request should be sent directly to reviewer.
2191 // ServiceNotConfigured: Service not configured correctly. Please contact operator.
2192 // MissingCountry: Application does not contain country information.
2193 // CountryNotSupported: Service not available in your country.
2194 // MissingPNr: Application does not contain a personal number.
2195 // NoCompanyId: Service cannot be used to review company IDs.
2196 // ServiceClientTimeout: Service timed out waiting for user to approve request.
2197 // ServiceFailed: Service failed to process request.
2198 // PNrMismatch: Personal number mismatch.
2199 // FirstNameMismatch: First name mismatch.
2200 // LastNameMismatch: Last name mismatch.
2201 // NameMismatch: Name mismatch.
2202 // InvalidJid: Invalid JID.
2203 // NoLogin: No login registered on Neuron.
2204 // UnexpectedOnboardingServer: Unexpected response received from onboarding server.
2205 // PersonDead: Person is dead.
2206 // BirthDateMismatch: Birth date mismatch.
2207 // AddressMismatch: Address mismatch.
2208 // ZipMismatch: Postal Code mismatch.
2209 // AreaMismatch: Area mismatch.
2210 // CityMismatch: City mismatch.
2211 // RegionMismatch: Region mismatch.
2212 // LivenessFailed: Liveness check failed.
2213 // PhotoFake: Photo is fake.
2214 // PhotoPoor: Photo has poor quality.
2215 // BankIdRFA1: Start your BankID app.
2216 // BankIdRFA2: The BankID app is not installed. Please contact your internet bank.
2217 // BankIdRFA3: Action cancelled. Please try again.
2218 // BankIdRFA4: An identification or signing for this personal number is already started. Please try again.
2219 // BankIdRFA5: Internal error. Please try again.
2220 // BankIdRFA6: Action cancelled.
2221 // BankIdRFA8: The BankID app is not responding. Please check that the program is started and that you have internet access. If you don�t have a valid BankID you can get one from your bank. Try again.
2222 // BankIdRFA9: Enter your security code in the BankID app and select Identify or Sign.
2223 // BankIdRFA13: Trying to start your BankID app.
2224 // BankIdRFA14A: Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don�t have a BankID which can be used for this identification/signing on this computer. If you have a BankID card, please insert it into your card reader. If you don�t have a BankID you can order one from your internet bank. If you have a BankID on another device you can start the BankID app on that device.
2225 // BankIdRFA14B: Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don�t have a BankID which can be used for this identification/signing on this device. If you don�t have a BankID you can order one from your internet bank. If you have a BankID on another device you can start the BankID app on that device.
2226 // BankIdRFA15A: Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don�t have a BankID which can be used for this identification/signing on this computer. If you have a BankID card, please insert it into your card reader. If you don�t have a BankID you can order one from your internet bank.
2227 // BankIdRFA15B: Searching for BankID:s, it may take a little while... If a few seconds have passed and still no BankID has been found, you probably don�t have a BankID which can be used for this identification/signing on this device. If you don�t have a BankID you can order one from your internet bank
2228 // BankIdRFA16: The BankID you are trying to use is revoked or too old. Please use another BankID or order a new one from your internet bank.
2229 // BankIdRFA17A: The BankID app couldn�t be found on your computer or mobile device. Please install it and order a BankID from your internet bank. Install the app from your app store or https://install.bankid.com.
2230 // BankIdRFA17B: Failed to scan the QR code. Start the BankID app and scan the QR code. Check that the BankID app is up to date. If you don't have the BankID app, you need to install it and order a BankID from your internet bank. Install the app from your app store or https://install.bankid.com.
2231 // BankIdRFA18: Start the BankID app
2232 // BankIdRFA19: Would you like to identify yourself or sign with a BankID on this computer or with a Mobile BankID?
2233 // BankIdRFA20: Would you like to identify yourself or sign with a BankID on this device or with a BankID on another device?
2234 // BankIdRFA21: Identification or signing in progress.
2235 // BankIdRFA22: Unknown error. Please try again.
2236 Message = Message.Trim();
2237
2238 bool ShouldShowAlert = Review is null ||
2239 (Review.InvalidClaims.Length == 0 &&
2240 Review.InvalidPhotos.Length == 0 &&
2241 Review.UnvalidatedClaims.Length == 0 &&
2242 Review.UnvalidatedPhotos.Length == 0);
2243
2244 if (ShouldShowAlert)
2245 {
2246 MainThread.BeginInvokeOnMainThread(async () =>
2247 {
2248 await ServiceRef.UiService.DisplayAlert(
2249 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], string.IsNullOrEmpty(Message) ? ServiceRef.Localizer[nameof(AppResources.SomethingWentWrong)] : Message,
2251 });
2252 }
2253
2254 return Task.CompletedTask;
2255 }
2256
2257 #endregion
2258
2259 private static ApplicationReview? BuildApplicationReview(ClientMessageEventArgs e, string message)
2260 {
2261 try
2262 {
2263 ApplicationReview Candidate = new()
2264 {
2265 Message = message,
2266 Code = e.Code,
2267 ReceivedUtc = DateTime.UtcNow
2268 };
2269
2270 try
2271 {
2272 IEnumerable<InvalidClaim> InvalidClaimsEnumerable = e.InvalidClaims as IEnumerable<InvalidClaim> ?? Array.Empty<InvalidClaim>();
2273 List<string> InvalidClaimNames = [];
2274 List<ApplicationReviewClaimDetail> InvalidClaimDetailList = [];
2275
2276 foreach (InvalidClaim InvalidClaim in InvalidClaimsEnumerable)
2277 {
2278 if (InvalidClaim is null || string.IsNullOrWhiteSpace(InvalidClaim.Claim))
2279 continue;
2280
2281 string ClaimValue = InvalidClaim.Claim.Trim();
2282 if (ClaimValue.Length == 0)
2283 continue;
2284
2285 InvalidClaimNames.Add(ClaimValue);
2286
2287 ApplicationReviewClaimDetail Detail = new(
2288 ClaimValue,
2289 InvalidClaim.Reason ?? string.Empty,
2292 InvalidClaim.Service ?? string.Empty);
2293 InvalidClaimDetailList.Add(Detail);
2294 }
2295
2296 Candidate.InvalidClaims = InvalidClaimNames.Count > 0 ? [.. InvalidClaimNames] : [];
2297 Candidate.InvalidClaimDetails = InvalidClaimDetailList.Count > 0 ? [.. InvalidClaimDetailList] : [];
2298 }
2299 catch
2300 {
2301 // Ignore conversion issues.
2302 }
2303
2304 try
2305 {
2306 IEnumerable<InvalidPhoto> InvalidPhotosEnumerable = e.InvalidPhotos as IEnumerable<InvalidPhoto> ?? Array.Empty<InvalidPhoto>();
2307 List<string> InvalidPhotoNames = [];
2308 List<ApplicationReviewPhotoDetail> InvalidPhotoDetailList = [];
2309
2310 foreach (InvalidPhoto InvalidPhoto in InvalidPhotosEnumerable)
2311 {
2312 if (InvalidPhoto is null || string.IsNullOrWhiteSpace(InvalidPhoto.FileName))
2313 continue;
2314
2315 string FileName = InvalidPhoto.FileName.Trim();
2316 if (FileName.Length == 0)
2317 continue;
2318
2319 string FileNameWithoutExtension = Path.GetFileNameWithoutExtension(FileName);
2320 string DisplayName = string.IsNullOrEmpty(FileNameWithoutExtension) ? FileName : FileNameWithoutExtension;
2321 DisplayName = DisplayName.Trim();
2322 if (DisplayName.Length == 0)
2323 DisplayName = FileName;
2324
2325 InvalidPhotoNames.Add(DisplayName);
2326
2327 ApplicationReviewPhotoDetail Detail = new(
2328 FileName,
2329 DisplayName,
2330 InvalidPhoto.Reason ?? string.Empty,
2333 InvalidPhoto.Service ?? string.Empty);
2334 InvalidPhotoDetailList.Add(Detail);
2335 }
2336
2337 Candidate.InvalidPhotos = InvalidPhotoNames.Count > 0 ? [.. InvalidPhotoNames] : [];
2338 Candidate.InvalidPhotoDetails = InvalidPhotoDetailList.Count > 0 ? [.. InvalidPhotoDetailList] : [];
2339 }
2340 catch
2341 {
2342 // Ignore conversion issues.
2343 }
2344
2345 try
2346 {
2347 IEnumerable<string> UnvalidatedClaimsEnumerable = e.UnvalidatedClaims as IEnumerable<string> ?? [];
2348 List<string> UnvalidatedClaimList = [];
2349 foreach (string Claim in UnvalidatedClaimsEnumerable)
2350 {
2351 if (string.IsNullOrWhiteSpace(Claim))
2352 continue;
2353
2354 string TrimmedClaim = Claim.Trim();
2355 if (TrimmedClaim.Length > 0)
2356 UnvalidatedClaimList.Add(TrimmedClaim);
2357 }
2358
2359 Candidate.UnvalidatedClaims = UnvalidatedClaimList.Count > 0 ? [.. UnvalidatedClaimList] : [];
2360 }
2361 catch
2362 {
2363 // Ignore conversion issues.
2364 }
2365
2366 try
2367 {
2368 IEnumerable<string> UnvalidatedPhotosEnumerable = e.UnvalidatedPhotos as IEnumerable<string> ?? [];
2369 List<string> UnvalidatedPhotoList = [];
2370 foreach (string Photo in UnvalidatedPhotosEnumerable)
2371 {
2372 if (string.IsNullOrWhiteSpace(Photo))
2373 continue;
2374
2375 string TrimmedPhoto = Photo.Trim();
2376 if (TrimmedPhoto.Length > 0)
2377 UnvalidatedPhotoList.Add(TrimmedPhoto);
2378 }
2379
2380 Candidate.UnvalidatedPhotos = UnvalidatedPhotoList.Count > 0 ? [.. UnvalidatedPhotoList] : [];
2381 }
2382 catch
2383 {
2384 // Ignore conversion issues.
2385 }
2386
2387 bool HasMeaningfulData =
2388 !string.IsNullOrEmpty(Candidate.Message) ||
2389 !string.IsNullOrEmpty(Candidate.Code) ||
2390 Candidate.InvalidClaims.Length > 0 ||
2391 Candidate.InvalidPhotos.Length > 0 ||
2392 Candidate.UnvalidatedClaims.Length > 0 ||
2393 Candidate.UnvalidatedPhotos.Length > 0;
2394
2395 return HasMeaningfulData ? Candidate : null;
2396 }
2397 catch (Exception Ex)
2398 {
2399 ServiceRef.LogService.LogException(Ex);
2400 return null;
2401 }
2402 }
2403
2404 #region Presence
2405
2406 private async Task XmppClient_OnPresence(object? Sender, PresenceEventArgs e)
2407 {
2408 await this.OnPresence.Raise(this, e);
2409 }
2410
2414 public event EventHandlerAsync<PresenceEventArgs>? OnPresence;
2415
2419 public event EventHandlerAsync<PresenceEventArgs>? OnPresenceSubscribe;
2420
2424 public event EventHandlerAsync<PresenceEventArgs>? OnPresenceUnsubscribed;
2425
2430 public void RequestPresenceSubscription(string BareJid)
2431 {
2433 }
2434
2440 public void RequestPresenceSubscription(string BareJid, string CustomXml)
2441 {
2442 this.XmppClient.RequestPresenceSubscription(BareJid, CustomXml);
2443 }
2444
2449 public void RequestPresenceUnsubscription(string BareJid)
2450 {
2452 }
2453
2458 public void RequestRevokePresenceSubscription(string BareJid)
2459 {
2461 }
2462
2463 #endregion
2464
2465 #region Roster
2466
2470 public RosterItem[] Roster => this.xmppClient?.Roster ?? [];
2471
2477 public RosterItem? GetRosterItem(string BareJid)
2478 {
2479 return this.XmppClient?.GetRosterItem(BareJid);
2480 }
2481
2486 public void AddRosterItem(RosterItem Item)
2487 {
2489 }
2490
2495 public void RemoveRosterItem(string BareJid)
2496 {
2497 this.XmppClient.RemoveRosterItem(BareJid);
2498 }
2499
2500 private async Task XmppClient_OnRosterItemAdded(object? Sender, RosterItem Item)
2501 {
2502 await this.OnRosterItemAdded.Raise(this, Item);
2503 }
2504
2508 public event EventHandlerAsync<RosterItem>? OnRosterItemAdded;
2509
2510 private async Task XmppClient_OnRosterItemUpdated(object? Sender, RosterItem Item)
2511 {
2512 await this.OnRosterItemUpdated.Raise(this, Item);
2513 }
2514
2518 public event EventHandlerAsync<RosterItem>? OnRosterItemUpdated;
2519
2520 private async Task XmppClient_OnRosterItemRemoved(object? Sender, RosterItem Item)
2521 {
2522 await this.OnRosterItemRemoved.Raise(this, Item);
2523 }
2524
2528 public event EventHandlerAsync<RosterItem>? OnRosterItemRemoved;
2529
2530 #endregion
2531
2532 #region Push Notification
2533
2537 public bool SupportsPushNotification => this.pushNotificationClient is not null;
2538
2539
2544 {
2545 get
2546 {
2547 if (this.pushNotificationClient is null)
2548 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.PushNotificationServiceNotFound)]);
2549
2550 return this.pushNotificationClient;
2551 }
2552 }
2553
2559 public async Task<bool> NewPushNotificationToken(TokenInformation TokenInformation)
2560 {
2561 // TODO: Check if started
2562
2563 if (this.pushNotificationClient is null || !this.IsOnline || string.IsNullOrEmpty(TokenInformation.Token))
2564 return false;
2565 else
2566 {
2567 await this.ReportNewPushNotificationToken(TokenInformation.Token, TokenInformation.Service, TokenInformation.ClientType);
2568
2569 return true;
2570 }
2571 }
2572
2579 public Task ReportNewPushNotificationToken(string Token, PushMessagingService Service, ClientType ClientType)
2580 {
2582 }
2583
2587 public Task ClearPushNotificationRules()
2588 {
2590 }
2591
2602 public Task AddPushNotificationRule(Waher.Networking.XMPP.MessageType MessageType, string LocalName, string Namespace,
2603 string Channel, string MessageVariable, string PatternMatchingScript, string ContentScript)
2604 {
2605 return this.PushNotificationClient.AddRuleAsync(MessageType, LocalName, Namespace, Channel, MessageVariable,
2606 PatternMatchingScript, ContentScript);
2607 }
2608
2609 #endregion
2610
2611 #region Tokens
2612
2620 public async Task<string?> GetApiToken(int Seconds)
2621 {
2622 DateTime Now = DateTime.UtcNow;
2623
2624 if (!string.IsNullOrEmpty(this.token) && Now.Subtract(this.tokenCreated).TotalSeconds < Seconds - 10)
2625 return this.token;
2626
2627 if (!this.IsOnline)
2628 {
2629 if (!await this.WaitForConnectedState(TimeSpan.FromSeconds(20)))
2630 return this.token;
2631 }
2632
2633 if (this.httpxClient is null)
2634 throw new Exception("Not connected to XMPP network.");
2635
2636 this.token = await this.httpxClient.GetJwtTokenAsync(Seconds);
2637 this.tokenCreated = Now;
2638
2639 return this.token;
2640 }
2641
2651 public async Task<object> PostToProtectedApi(string LocalResource, object Data, params KeyValuePair<string, string>[] Headers)
2652 {
2653 StringBuilder Url = new();
2654
2655 if (this.IsOnline)
2656 Url.Append("httpx://");
2657 else if (!string.IsNullOrEmpty(this.token)) // Token needs to be retrieved regularly when connected, if protected APIs are to be used when disconnected or during connection.
2658 {
2659 Url.Append("https://");
2660
2661 KeyValuePair<string, string> Authorization = new("Authorization", "Bearer " + this.token);
2662
2663 if (Headers is null)
2664 Headers = [Authorization];
2665 else
2666 {
2667 int c = Headers.Length;
2668
2669 Array.Resize(ref Headers, c + 1);
2670 Headers[c] = Authorization;
2671 }
2672 }
2673 else
2674 throw new IOException("No connection and no token available for call to protect API.");
2675
2676 Url.Append(ServiceRef.TagProfile.Domain);
2677 Url.Append(LocalResource);
2678
2679 ContentResponse Response = await InternetContent.PostAsync(new Uri(Url.ToString()), Data, Headers);
2680 Response.AssertOk();
2681
2682 return Response.Decoded;
2683 }
2684
2685 #endregion
2686
2687 #region HTTP File Upload
2688
2693 private HttpFileUploadClient FileUploadClient
2694 {
2695 get
2696 {
2697 if (this.fileUploadClient is null)
2698 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.FileUploadServiceNotFound)]);
2699
2700 return this.fileUploadClient;
2701 }
2702 }
2703
2707 public bool FileUploadIsSupported
2708 {
2709 get
2710 {
2711 try
2712 {
2713 return ServiceRef.TagProfile.FileUploadIsSupported &&
2714 this.fileUploadClient is not null &&
2715 this.fileUploadClient.HasSupport;
2716 }
2717 catch (Exception ex)
2718 {
2719 ServiceRef.LogService.LogException(ex);
2720 return false;
2721 }
2722 }
2723 }
2724
2731 public Task<HttpFileUploadEventArgs> RequestUploadSlotAsync(string FileName, string ContentType, long ContentSize)
2732 {
2733 return this.FileUploadClient.RequestUploadSlotAsync(FileName, ContentType, ContentSize);
2734 }
2735
2736
2737 #endregion
2738
2739 #region Personal Eventing Protocol (PEP)
2740
2741 private readonly LinkedList<KeyValuePair<Type, EventHandlerAsync<PersonalEventNotificationEventArgs>>> pepHandlers = new();
2742
2747 private PepClient PepClient
2748 {
2749 get
2750 {
2751 if (this.pepClient is null)
2752 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.PepServiceNotFound)]);
2753
2754 return this.pepClient;
2755 }
2756 }
2757
2763 public void RegisterPepHandler(Type PersonalEventType, EventHandlerAsync<PersonalEventNotificationEventArgs> Handler)
2764 {
2765 lock (this.pepHandlers)
2766 {
2767 this.pepHandlers.AddLast(new KeyValuePair<Type, EventHandlerAsync<PersonalEventNotificationEventArgs>>(PersonalEventType, Handler));
2768 }
2769
2770 this.PepClient.RegisterHandler(PersonalEventType, Handler);
2771 }
2772
2779 public bool UnregisterPepHandler(Type PersonalEventType, EventHandlerAsync<PersonalEventNotificationEventArgs> Handler)
2780 {
2781 lock (this.pepHandlers)
2782 {
2783 LinkedListNode<KeyValuePair<Type, EventHandlerAsync<PersonalEventNotificationEventArgs>>>? Node = this.pepHandlers.First;
2784
2785 while (Node is not null)
2786 {
2787 if (Node.Value.Key == PersonalEventType &&
2788 (Node.Value.Value.Target?.Equals(Handler.Target) ?? Handler.Target is null) &&
2789 Node.Value.Value.Method.Equals(Handler.Method))
2790 {
2791 this.pepHandlers.Remove(Node);
2792 break;
2793 }
2794
2795 Node = Node.Next;
2796 }
2797 }
2798
2799 return this.PepClient.UnregisterHandler(PersonalEventType, Handler);
2800 }
2801
2802 private void ReregisterPepEventHandlers(PepClient PepClient)
2803 {
2804 lock (this.pepHandlers)
2805 {
2806 foreach (KeyValuePair<Type, EventHandlerAsync<PersonalEventNotificationEventArgs>> P in this.pepHandlers)
2807 PepClient.RegisterHandler(P.Key, P.Value);
2808 }
2809 }
2810
2811 #endregion
2812
2813 #region Thing Registries & Discovery
2814
2820 {
2821 get
2822 {
2823 if (this.thingRegistryClient is null)
2824 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ThingRegistryServiceNotFound)]);
2825
2826 return this.thingRegistryClient;
2827 }
2828 }
2829
2833 public string RegistryServiceJid => this.ThingRegistryClient.ThingRegistryAddress;
2834
2840 public bool IsIoTDiscoClaimURI(string DiscoUri)
2841 {
2842 return ThingRegistryClient.IsIoTDiscoClaimURI(DiscoUri);
2843 }
2844
2850 public bool IsIoTDiscoSearchURI(string DiscoUri)
2851 {
2853 }
2854
2860 public bool IsIoTDiscoDirectURI(string DiscoUri)
2861 {
2863 }
2864
2871 public bool TryDecodeIoTDiscoClaimURI(string DiscoUri, [NotNullWhen(true)] out MetaDataTag[]? Tags)
2872 {
2873 return ThingRegistryClient.TryDecodeIoTDiscoClaimURI(DiscoUri, out Tags);
2874 }
2875
2883 public bool TryDecodeIoTDiscoSearchURI(string DiscoUri, [NotNullWhen(true)] out SearchOperator[]? Operators,
2884 out string? RegistryJid)
2885 {
2886 RegistryJid = null;
2887 Operators = null;
2888 if (!ThingRegistryClient.TryDecodeIoTDiscoURI(DiscoUri, out IEnumerable<SearchOperator> Operators2))
2889 return false;
2890
2891 List<SearchOperator> List = [];
2892
2893 foreach (SearchOperator Operator in Operators2)
2894 {
2895 if (Operator.Name.Equals("R", StringComparison.OrdinalIgnoreCase))
2896 {
2897 if (!string.IsNullOrEmpty(RegistryJid))
2898 return false;
2899
2900 if (Operator is not StringTagEqualTo StrEqOp)
2901 return false;
2902
2903 RegistryJid = StrEqOp.Value;
2904 }
2905 else
2906 List.Add(Operator);
2907 }
2908
2909 Operators = [.. List];
2910
2911 return true;
2912 }
2913
2924 public bool TryDecodeIoTDiscoDirectURI(string DiscoUri, [NotNullWhen(true)] out string? Jid, out string? SourceId, out string? NodeId,
2925 out string? PartitionId, [NotNullWhen(true)] out MetaDataTag[]? Tags)
2926 {
2927 Jid = null;
2928 SourceId = null;
2929 NodeId = null;
2930 PartitionId = null;
2931
2932 if (!ThingRegistryClient.TryDecodeIoTDiscoURI(DiscoUri, out IEnumerable<SearchOperator> Operators2))
2933 {
2934 Tags = null;
2935 return false;
2936 }
2937
2938 List<MetaDataTag> TagsFound = [];
2939
2940 foreach (SearchOperator Operator in Operators2)
2941 {
2942 if (Operator is StringTagEqualTo S)
2943 {
2944 switch (S.Name.ToUpper(CultureInfo.InvariantCulture))
2945 {
2947 Jid = S.Value;
2948 break;
2949
2951 SourceId = S.Value;
2952 break;
2953
2955 NodeId = S.Value;
2956 break;
2957
2959 PartitionId = S.Value;
2960 break;
2961
2962 default:
2963 TagsFound.Add(new MetaDataStringTag(S.Name, S.Value));
2964 break;
2965 }
2966 }
2967 else if (Operator is NumericTagEqualTo N)
2968 TagsFound.Add(new MetaDataNumericTag(N.Name, N.Value));
2969 else
2970 {
2971 Tags = null;
2972 return false;
2973 }
2974 }
2975
2976 Tags = [.. TagsFound];
2977
2978 return !string.IsNullOrEmpty(Jid);
2979 }
2980
2987 public Task<NodeResultEventArgs> ClaimThing(string DiscoUri, bool MakePublic)
2988 {
2989 if (!this.TryDecodeIoTDiscoClaimURI(DiscoUri, out MetaDataTag[]? Tags))
2990 throw new ArgumentException(ServiceRef.Localizer[nameof(AppResources.InvalidIoTDiscoClaimUri)], nameof(DiscoUri));
2991
2992 TaskCompletionSource<NodeResultEventArgs> Result = new();
2993
2994 this.ThingRegistryClient.Mine(MakePublic, Tags, (sender, e) =>
2995 {
2996 Result.TrySetResult(e);
2997 return Task.CompletedTask;
2998 }, null);
2999
3000 return Result.Task;
3001 }
3002
3012 public Task<bool> Disown(string RegistryJid, string ThingJid, string SourceId, string Partition, string NodeId)
3013 {
3014 TaskCompletionSource<bool> Result = new();
3015
3016 this.ThingRegistryClient.Disown(RegistryJid, ThingJid, NodeId, SourceId, Partition, (sender, e) =>
3017 {
3018 Result.TrySetResult(e.Ok);
3019 return Task.CompletedTask;
3020 }, null);
3021
3022 return Result.Task;
3023 }
3024
3032 public async Task<(SearchResultThing[], string?, bool)> Search(int Offset, int MaxCount, string DiscoUri)
3033 {
3034 if (!this.TryDecodeIoTDiscoSearchURI(DiscoUri, out SearchOperator[]? Operators, out string? RegistryJid))
3035 return (Array.Empty<SearchResultThing>(), RegistryJid, false);
3036
3037 (SearchResultThing[] Things, bool More) = await this.Search(Offset, MaxCount, RegistryJid, Operators);
3038
3039 return (Things, RegistryJid, More);
3040 }
3041
3050 public Task<(SearchResultThing[], bool)> Search(int Offset, int MaxCount, string? RegistryJid, params SearchOperator[] Operators)
3051 {
3052 TaskCompletionSource<(SearchResultThing[], bool)> Result = new();
3053
3054 this.ThingRegistryClient.Search(RegistryJid ?? ServiceRef.TagProfile.RegistryJid, Offset, MaxCount, Operators, (sender, e) =>
3055 {
3056 if (e.Ok)
3057 Result.TrySetResult((e.Things, e.More));
3058 else
3059 Result.TrySetException(e.StanzaError ?? new Exception("Unable to perform search."));
3060
3061 return Task.CompletedTask;
3062 }, null);
3063
3064 return Result.Task;
3065 }
3066
3072 public async Task<(SearchResultThing[], string?)> SearchAll(string DiscoUri)
3073 {
3074 if (!this.TryDecodeIoTDiscoSearchURI(DiscoUri, out SearchOperator[]? Operators, out string? RegistryJid))
3075 return (Array.Empty<SearchResultThing>(), RegistryJid);
3076
3077 SearchResultThing[] Things = await this.SearchAll(RegistryJid ?? ServiceRef.TagProfile.RegistryJid, Operators);
3078
3079 return (Things, RegistryJid);
3080 }
3081
3088 public async Task<SearchResultThing[]> SearchAll(string? RegistryJid, params SearchOperator[] Operators)
3089 {
3090 (SearchResultThing[] Things, bool More) = await this.Search(0, Constants.BatchSizes.DeviceBatchSize, RegistryJid, Operators);
3091 if (!More)
3092 return Things;
3093
3094 List<SearchResultThing> Result = [];
3095 int Offset = Things.Length;
3096
3097 Result.AddRange(Things);
3098
3099 while (More)
3100 {
3101 (Things, More) = await this.Search(Offset, Constants.BatchSizes.DeviceBatchSize, RegistryJid, Operators);
3102 Result.AddRange(Things);
3103 Offset += Things.Length;
3104 }
3105
3106 return [.. Result];
3107 }
3108
3109 #endregion
3110
3111 #region Legal Identities
3112
3116 public async Task GenerateNewKeys()
3117 {
3118 await this.ContractsClient.GenerateNewKeys();
3119
3120 if (this.ContractsClient.Client.State == XmppState.Connected)
3122 }
3123
3128 public async Task<IdApplicationAttributesEventArgs> GetIdApplicationAttributes()
3129 {
3131 }
3132
3140 public async Task<LegalIdentity> AddLegalIdentity(RegisterIdentityModel Model, bool GenerateNewKeys,
3141 params LegalIdentityAttachment[] Attachments)
3142 {
3143 return await this.AddLegalIdentity(Model.ToProperties(ServiceRef.XmppService), GenerateNewKeys, Attachments);
3144 }
3145
3153 public async Task<LegalIdentity> AddLegalIdentity(Property[] Props, bool GenerateNewKeys,
3154 params LegalIdentityAttachment[] Attachments)
3155 {
3156 if (GenerateNewKeys)
3157 await this.GenerateNewKeys();
3158
3160
3161 foreach (LegalIdentityAttachment Attachment in Attachments)
3162 {
3164 Path.GetFileName(Attachment.FileName), Attachment.Data, Attachment.ContentType);
3165 }
3166
3168
3169 return Identity;
3170 }
3171
3177 public async Task<LegalIdentity[]> GetLegalIdentities(XmppClient? client = null)
3178 {
3179 if (client is null)
3180 return await this.ContractsClient.GetLegalIdentitiesAsync();
3181 else
3182 {
3183 using ContractsClient cc = new(client, ServiceRef.TagProfile.LegalJid); // No need to load keys for this operation.
3184 return await cc.GetLegalIdentitiesAsync();
3185 }
3186 }
3187
3193 public async Task<LegalIdentity> GetLegalIdentity(CaseInsensitiveString legalIdentityId)
3194 {
3195 ContactInfo? Info = await ContactInfo.FindByLegalId(legalIdentityId);
3196
3197 if (Info is not null && Info.LegalIdentity is not null)
3198 return Info.LegalIdentity;
3199
3200 return await this.ContractsClient.GetLegalIdentityAsync(legalIdentityId);
3201 }
3202
3208 public async Task<bool> IsContact(CaseInsensitiveString legalIdentityId)
3209 {
3210 ContactInfo? Info = await ContactInfo.FindByLegalId(legalIdentityId);
3211 return (Info is not null && Info.LegalIdentity is not null);
3212 }
3213
3219 public Task<bool> HasPrivateKey(CaseInsensitiveString LegalIdentityId)
3220 {
3221 return this.ContractsClient.HasPrivateKey(LegalIdentityId);
3222 }
3223
3229 public Task<LegalIdentity> ObsoleteLegalIdentity(CaseInsensitiveString legalIdentityId)
3230 {
3231 return this.ContractsClient.ObsoleteLegalIdentityAsync(legalIdentityId);
3232 }
3233
3239 public Task<LegalIdentity> CompromiseLegalIdentity(CaseInsensitiveString legalIdentityId)
3240 {
3241 return this.ContractsClient.CompromisedLegalIdentityAsync(legalIdentityId);
3242 }
3243
3250 public async Task PetitionIdentity(CaseInsensitiveString LegalId, string PetitionId, string Purpose)
3251 {
3253 throw new Exception("No Legal Identity registered.");
3254
3256
3257 this.StartPetition(PetitionId);
3258 await this.ContractsClient.PetitionIdentityAsync(LegalId, PetitionId, Purpose);
3259 }
3260
3261 private void StartPetition(string PetitionId)
3262 {
3263 lock (this.currentPetitions)
3264 {
3265 this.currentPetitions[PetitionId] = true;
3266 }
3267 }
3268
3269 private bool EndPetition(string PetitionId)
3270 {
3271 lock (this.currentPetitions)
3272 {
3273 return this.currentPetitions.Remove(PetitionId);
3274 }
3275 }
3276
3284 public Task SendPetitionIdentityResponse(CaseInsensitiveString LegalId, string PetitionId, string RequestorFullJid, bool Response)
3285 {
3286 return this.ContractsClient.PetitionIdentityResponseAsync(LegalId, PetitionId, RequestorFullJid, Response);
3287 }
3288
3292 public event EventHandlerAsync<LegalIdentityEventArgs>? LegalIdentityChanged;
3293
3297 public event EventHandlerAsync<LegalIdentityEventArgs>? IdentityApplicationChanged;
3298
3299 private async Task ContractsClient_IdentityUpdated(object? Sender, LegalIdentityEventArgs e)
3300 {
3301 try
3302 {
3303 KycReference? Ref = await Database.FindFirstIgnoreRest<KycReference>(new FilterFieldEqualTo(nameof(KycReference.CreatedIdentityId), e.Identity.Id));
3304 if (Ref is not null)
3305 {
3306 Ref.UpdatedUtc = DateTime.UtcNow;
3307 Ref.CreatedIdentityState = e.Identity.State;
3308 await Database.Update(Ref);
3309 await Database.Provider.Flush();
3310 }
3311
3312 if (ServiceRef.NavigationService.CurrentPage is ApplicationsPage AppPage)
3313 {
3314 if (AppPage.BindingContext is ApplicationsViewModel Model)
3315 Model.Loader.Reload();
3316 }
3317 }
3318 catch (Exception Ex)
3319 {
3320 ServiceRef.LogService.LogException(Ex);
3321 }
3322
3323 try
3324 {
3326 {
3328 return;
3329
3331 await this.LegalIdentityChanged.Raise(this, e);
3332
3333 if (e.Identity.IsDiscarded() && !e.Identity.IsPersonal() && !e.Identity.IsOrganizational() && Shell.Current.CurrentState.Location.OriginalString != Constants.Pages.RegistrationPage)
3334 {
3335 MainThread.BeginInvokeOnMainThread(async () =>
3336 {
3337 try
3338 {
3340 ServiceRef.TagProfile.GoToStep(RegistrationStep.ValidatePhone, true);
3341 await Shell.Current.GoToAsync(Constants.Pages.RegistrationPage);
3342 }
3343 catch (Exception ex)
3344 {
3345 ServiceRef.LogService.LogException(ex);
3346 await App.StopAsync();
3347 }
3348 });
3349 }
3350 }
3352 {
3354 return;
3355
3356 if (e.Identity.IsDiscarded())
3357 {
3358 await ServiceRef.TagProfile.SetIdentityApplication(null, false);
3359 await this.IdentityApplicationChanged.Raise(this, e);
3360 }
3361 else if (e.Identity.IsApproved())
3362 {
3364
3365 if (ToObsolete is not null)
3366 {
3368 NotificationService.AddIgnoreFilter((NotificationIntent Intent) =>
3369 {
3370 if (!string.Equals(Intent.Channel, NeuroAccessMaui.Constants.PushChannels.Identities, StringComparison.OrdinalIgnoreCase))
3372
3373 if (!string.Equals(Intent.EntityId, ToObsolete.Id, StringComparison.OrdinalIgnoreCase))
3375
3376 return new NotificationFilterDecision(true, true, true);
3377 });
3378 }
3379
3381 await ServiceRef.TagProfile.SetIdentityApplication(null, false);
3382
3383 await this.LegalIdentityChanged.Raise(this, e);
3384 await this.IdentityApplicationChanged.Raise(this, e);
3385
3386 if (ToObsolete is not null && !ToObsolete.IsDiscarded())
3387 await this.ObsoleteLegalIdentity(ToObsolete.Id);
3388 }
3389 else
3390 {
3392 await this.IdentityApplicationChanged.Raise(this, e);
3393 }
3394 }
3395 else if (ServiceRef.TagProfile.LegalIdentity is null)
3396 {
3397 if (e.Identity.IsDiscarded())
3398 return;
3399
3401 await this.LegalIdentityChanged.Raise(this, e);
3402 }
3403 }
3404 catch (Exception ex)
3405 {
3406 ServiceRef.LogService.LogException(ex);
3407 await ServiceRef.UiService.DisplayException(ex);
3408 }
3409
3410 try
3411 {
3412 string Title;
3413 string Body;
3414
3415 switch (e.Identity.State)
3416 {
3417 case IdentityState.Approved:
3420 break;
3421 case IdentityState.Obsoleted:
3424 break;
3425 case IdentityState.Rejected:
3428 break;
3429 case IdentityState.Compromised:
3432 break;
3433 default:
3434 return;
3435 }
3436
3437 NotificationIntent Intent = new()
3438 {
3440 Title = Title,
3441 Body = Body,
3442 Action = NotificationAction.OpenIdentity,
3443 EntityId = e.Identity.Id,
3444 CorrelationId = e.Identity.Id,
3445 Presentation = NotificationPresentation.StoreOnly
3446 };
3447
3448 Intent.Extras["state"] = e.Identity.State.ToString();
3449
3450 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
3451 }
3452 catch (Exception ex)
3453 {
3454 ServiceRef.LogService.LogException(ex);
3455 }
3456 }
3457
3461 public event EventHandlerAsync<LegalIdentityPetitionEventArgs>? PetitionForIdentityReceived;
3462
3463 private async Task ContractsClient_PetitionForIdentityReceived(object? Sender, LegalIdentityPetitionEventArgs e)
3464 {
3467
3468 NotificationIntent Intent = new()
3469 {
3471 Title = Title,
3472 Body = Body,
3473 Action = NotificationAction.OpenPetition,
3474 EntityId = e.RequestorFullJid,
3475 CorrelationId = e.PetitionId,
3476 Presentation = NotificationPresentation.StoreOnly
3477 };
3478
3479 Intent.Extras["petitionId"] = e.PetitionId ?? string.Empty;
3480 Intent.Extras["requestedIdentityId"] = e.RequestedIdentityId ?? string.Empty;
3481 Intent.Extras["requestorIdentityId"] = e.RequestorIdentity?.Id ?? string.Empty;
3482
3483 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
3484 await this.PetitionForIdentityReceived.Raise(this, e);
3485 }
3486
3490 public event EventHandlerAsync<LegalIdentityPetitionResponseEventArgs>? PetitionedIdentityResponseReceived;
3491
3492 private async Task ContractsClient_PetitionedIdentityResponseReceived(object? Sender, LegalIdentityPetitionResponseEventArgs e)
3493 {
3494 try
3495 {
3496 this.EndPetition(e.PetitionId);
3497 await this.PetitionedIdentityResponseReceived.Raise(this, e);
3498 }
3499 catch (Exception ex)
3500 {
3501 ServiceRef.LogService.LogException(ex);
3502 await ServiceRef.UiService.DisplayException(ex);
3503 }
3504 }
3505
3510 public Task ExportSigningKeys(XmlWriter Output)
3511 {
3512 return this.ContractsClient.ExportKeys(Output);
3513 }
3514
3520 public Task<bool> ImportSigningKeys(XmlElement Xml)
3521 {
3522 return this.ContractsClient.ImportKeys(Xml);
3523 }
3524
3530 public async Task<IdentityStatus> ValidateIdentity(LegalIdentity Identity)
3531 {
3533 return Result.Status;
3534 }
3535
3536 #endregion
3537
3538 #region Smart Contracts
3539
3540 private readonly Dictionary<CaseInsensitiveString, DateTime> lastContractEvent = [];
3541
3547 {
3548 get
3549 {
3550 if (this.contractsClient is null)
3551 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.LegalServiceNotFound)]);
3552
3553 return this.contractsClient;
3554 }
3555 }
3556
3557 private void RegisterContractsEventHandlers()
3558 {
3560
3561 this.ContractsClient.IdentityUpdated += this.ContractsClient_IdentityUpdated;
3562 this.ContractsClient.PetitionForIdentityReceived += this.ContractsClient_PetitionForIdentityReceived;
3563 this.ContractsClient.PetitionedIdentityResponseReceived += this.ContractsClient_PetitionedIdentityResponseReceived;
3564 this.ContractsClient.PetitionForContractReceived += this.ContractsClient_PetitionForContractReceived;
3565 this.ContractsClient.PetitionedContractResponseReceived += this.ContractsClient_PetitionedContractResponseReceived;
3566 this.ContractsClient.PetitionForSignatureReceived += this.ContractsClient_PetitionForSignatureReceived;
3567 this.ContractsClient.PetitionedSignatureResponseReceived += this.ContractsClient_PetitionedSignatureResponseReceived;
3568 this.ContractsClient.PetitionForPeerReviewIDReceived += this.ContractsClient_PetitionForPeerReviewIdReceived;
3569 this.ContractsClient.PetitionedPeerReviewIDResponseReceived += this.ContractsClient_PetitionedPeerReviewIdResponseReceived;
3570 this.ContractsClient.PetitionClientUrlReceived += this.ContractsClient_PetitionClientUrlReceived;
3571 this.ContractsClient.ContractProposalReceived += this.ContractsClient_ContractProposalReceived;
3572 this.ContractsClient.ContractUpdated += this.ContractsClient_ContractUpdated;
3573 this.ContractsClient.ContractSigned += this.ContractsClient_ContractSigned;
3574 this.ContractsClient.ClientMessage += this.ContractsClient_ClientMessage;
3575 }
3576
3582 public Task<Contract> GetContract(CaseInsensitiveString ContractId)
3583 {
3584 return this.ContractsClient.GetContractAsync(ContractId);
3585 }
3586
3591 public async Task<string[]> GetCreatedContractReferences()
3592 {
3593 List<string> Result = [];
3594 string[] ContractIds;
3595 int Offset = 0;
3596 int Nr;
3597
3598 do
3599 {
3600 ContractIds = await this.ContractsClient.GetCreatedContractReferencesAsync(Offset, 20);
3601 Result.AddRange(ContractIds);
3602 Nr = ContractIds.Length;
3603 Offset += Nr;
3604 }
3605 while (Nr == 20);
3606
3607 return [.. Result];
3608 }
3609
3614 public async Task<string[]> GetSignedContractReferences()
3615 {
3616 List<string> Result = [];
3617 string[] ContractIds;
3618 int Offset = 0;
3619 int Nr;
3620
3621 do
3622 {
3623 ContractIds = await this.ContractsClient.GetSignedContractReferencesAsync(Offset, 20);
3624 Result.AddRange(ContractIds);
3625 Nr = ContractIds.Length;
3626 Offset += Nr;
3627 }
3628 while (Nr == 20);
3629
3630 return [.. Result];
3631 }
3632
3640 public async Task<Contract> SignContract(Contract Contract, string Role, bool Transferable)
3641 {
3645 {
3646 lock (this.currentTransactions)
3647 {
3648 string TransactionId = Contract.ContractId;
3649 string Currency = Contract["Currency"]?.ToString() ?? string.Empty;
3650
3651 this.currentTransactions[Contract.ContractId] = new PaymentTransaction(TransactionId, Currency);
3652 }
3653 }
3654
3655 Contract Result = await this.ContractsClient.SignContractAsync(Contract, Role, Transferable);
3656 await UpdateContractReference(Result);
3657 return Result;
3658 }
3659
3665 public async Task<Contract> ObsoleteContract(CaseInsensitiveString ContractId)
3666 {
3667 Contract Result = await this.ContractsClient.ObsoleteContractAsync(ContractId);
3668 await UpdateContractReference(Result);
3669 return Result;
3670 }
3671
3687 public async Task<Contract> CreateContract(
3688 CaseInsensitiveString TemplateId,
3689 Part[] Parts,
3691 ContractVisibility Visibility,
3692 ContractParts PartsMode,
3694 Duration ArchiveRequired,
3695 Duration ArchiveOptional,
3696 DateTime? SignAfter,
3697 DateTime? SignBefore,
3698 bool CanActAsTemplate)
3699 {
3700 Contract Result = await this.ContractsClient.CreateContractAsync(TemplateId, Parts, Parameters, Visibility, PartsMode, Duration, ArchiveRequired, ArchiveOptional, SignAfter, SignBefore, CanActAsTemplate);
3701 await UpdateContractReference(Result);
3702 return Result;
3703 }
3704
3710 public async Task<Contract> DeleteContract(CaseInsensitiveString ContractId)
3711 {
3712 Contract Contract = await this.ContractsClient.DeleteContractAsync(ContractId);
3714 return Contract;
3715 }
3716
3723 public Task PetitionContract(CaseInsensitiveString ContractId, string PetitionId, string Purpose)
3724 {
3725 this.StartPetition(PetitionId);
3726 return this.ContractsClient.PetitionContractAsync(ContractId, PetitionId, Purpose);
3727 }
3728
3736 public Task SendPetitionContractResponse(CaseInsensitiveString ContractId, string PetitionId, string RequestorFullJid, bool Response)
3737 {
3738 return this.ContractsClient.PetitionContractResponseAsync(ContractId, PetitionId, RequestorFullJid, Response);
3739 }
3740
3744 public event EventHandlerAsync<ContractPetitionEventArgs>? PetitionForContractReceived;
3745
3746 private async Task ContractsClient_PetitionForContractReceived(object? Sender, ContractPetitionEventArgs e)
3747 {
3750
3751 NotificationIntent Intent = new()
3752 {
3754 Title = Title,
3755 Body = Body,
3756 Action = NotificationAction.OpenPetition,
3757 EntityId = e.RequestorFullJid,
3758 CorrelationId = e.PetitionId,
3759 Presentation = NotificationPresentation.StoreOnly
3760 };
3761
3762 Intent.Extras["contractId"] = e.RequestedContractId ?? string.Empty;
3763 Intent.Extras["petitionId"] = e.PetitionId ?? string.Empty;
3764
3765 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
3766 await this.PetitionForContractReceived.Raise(this, e);
3767 }
3768
3772 public event EventHandlerAsync<ContractPetitionResponseEventArgs>? PetitionedContractResponseReceived;
3773
3774 private async Task ContractsClient_PetitionedContractResponseReceived(object? Sender, ContractPetitionResponseEventArgs e)
3775 {
3776 try
3777 {
3778 this.EndPetition(e.PetitionId);
3779 await this.PetitionedContractResponseReceived.Raise(this, e);
3780 }
3781 catch (Exception ex)
3782 {
3783 ServiceRef.LogService.LogException(ex);
3784 await ServiceRef.UiService.DisplayException(ex);
3785 }
3786 }
3787
3793 public DateTime GetTimeOfLastContractEvent(CaseInsensitiveString ContractId)
3794 {
3795 lock (this.lastContractEvent)
3796 {
3797 if (this.lastContractEvent.TryGetValue(ContractId, out DateTime TP))
3798 return TP;
3799 else
3800 return DateTime.MinValue;
3801 }
3802 }
3803
3804 private static async Task UpdateContractReference(Contract Contract)
3805 {
3806 ContractReference Ref = await Database.FindFirstDeleteRest<ContractReference>(
3807 new FilterFieldEqualTo("ContractId", Contract.ContractId));
3808
3809 if (Ref is null)
3810 {
3811 Ref = new ContractReference()
3812 {
3813 ContractId = Contract.ContractId
3814 };
3815
3816 await Ref.SetContract(Contract);
3817 await Database.Insert(Ref);
3818 }
3819 else
3820 {
3821 await Ref.SetContract(Contract);
3822 await Database.Update(Ref);
3823 }
3824
3826 }
3827
3831 public event EventHandlerAsync<ContractProposalEventArgs>? ContractProposalReceived;
3832
3833 private async Task ContractsClient_ContractProposalReceived(object? Sender, ContractProposalEventArgs e)
3834 {
3837
3838 NotificationIntent Intent = new()
3839 {
3841 Title = Title,
3842 Body = Body,
3843 Action = NotificationAction.OpenContract,
3844 EntityId = e.ContractId,
3845 CorrelationId = e.ContractId,
3846 Presentation = NotificationPresentation.StoreOnly
3847 };
3848
3849 Intent.Extras["role"] = e.Role ?? string.Empty;
3850 Intent.Extras["fromJid"] = e.FromBareJID ?? string.Empty;
3851
3852 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
3853 await this.ContractProposalReceived.Raise(this, e);
3854 }
3855
3859 public event EventHandlerAsync<ContractReferenceEventArgs>? ContractUpdated;
3860
3861 private async Task ContractsClient_ContractUpdated(object? Sender, ContractReferenceEventArgs e)
3862 {
3863 await this.ContractUpdatedOrSigned(e);
3864
3867
3868 NotificationIntent Intent = new()
3869 {
3871 Title = Title,
3872 Body = Body,
3873 Action = NotificationAction.OpenContract,
3874 EntityId = e.ContractId,
3875 CorrelationId = e.ContractId,
3876 Presentation = NotificationPresentation.StoreOnly
3877 };
3878
3879 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
3880 await this.ContractUpdated.Raise(this, e);
3881 }
3882
3883 private Task ContractUpdatedOrSigned(ContractReferenceEventArgs e)
3884 {
3885 lock (this.lastContractEvent)
3886 {
3887 this.lastContractEvent[e.ContractId] = DateTime.Now;
3888 }
3889
3890 return Task.CompletedTask;
3891 }
3892
3896 public event EventHandlerAsync<ContractSignedEventArgs>? ContractSigned;
3897
3898 private async Task ContractsClient_ContractSigned(object? Sender, ContractSignedEventArgs e)
3899 {
3900 await this.ContractUpdatedOrSigned(e);
3901
3904
3905 NotificationIntent Intent = new()
3906 {
3908 Title = Title,
3909 Body = Body,
3910 Action = NotificationAction.OpenContract,
3911 EntityId = e.ContractId,
3912 CorrelationId = e.ContractId,
3913 Presentation = NotificationPresentation.StoreOnly
3914 };
3915
3916 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
3917 await this.ContractSigned.Raise(this, e);
3918 }
3919
3927 public Task SendContractProposal(Contract Contract, string Role, string To, string Message)
3928 {
3929 return this.ContractsClient.SendContractProposal(Contract, Role, To, Message);
3930 }
3931
3932 #endregion
3933
3934 #region Attachments
3935
3943 public Task<KeyValuePair<string, TemporaryFile>> GetAttachment(string Url, SignWith SignWith, TimeSpan Timeout)
3944 {
3945 return this.ContractsClient.GetAttachmentAsync(Url, SignWith, (int)Timeout.TotalMilliseconds);
3946 }
3947
3948 #endregion
3949
3950 #region Peer Review
3951
3959 public async Task PetitionPeerReviewId(CaseInsensitiveString LegalId, LegalIdentity Identity, string PetitionId, string Purpose)
3960 {
3961 await this.ContractsClient.AuthorizeAccessToIdAsync(Identity.Id, LegalId, true);
3962
3963 this.StartPetition(PetitionId);
3964 await this.ContractsClient.PetitionPeerReviewIDAsync(LegalId, Identity, PetitionId, Purpose);
3965 }
3966
3974 public Task<LegalIdentity> AddPeerReviewIdAttachment(LegalIdentity Identity, LegalIdentity ReviewerLegalIdentity, byte[] PeerSignature)
3975 {
3976 return this.ContractsClient.AddPeerReviewIDAttachment(Identity, ReviewerLegalIdentity, PeerSignature);
3977 }
3978
3982 public event EventHandlerAsync<SignaturePetitionEventArgs>? PetitionForPeerReviewIdReceived;
3983
3984 private async Task ContractsClient_PetitionForPeerReviewIdReceived(object? Sender, SignaturePetitionEventArgs e)
3985 {
3986 await this.PetitionForPeerReviewIdReceived.Raise(this, e);
3987 }
3988
3992 public event EventHandlerAsync<SignaturePetitionResponseEventArgs>? PetitionedPeerReviewIdResponseReceived;
3993
3994 private async Task ContractsClient_PetitionedPeerReviewIdResponseReceived(object? Sender, SignaturePetitionResponseEventArgs e)
3995 {
3996 try
3997 {
3998 this.EndPetition(e.PetitionId);
3999 await this.PetitionedPeerReviewIdResponseReceived.Raise(this, e);
4000 }
4001 catch (Exception ex)
4002 {
4003 ServiceRef.LogService.LogException(ex);
4004 await ServiceRef.UiService.DisplayException(ex);
4005 }
4006 }
4007
4012 public async Task<ServiceProviderWithLegalId[]> GetServiceProvidersForPeerReviewAsync()
4013 {
4014 try
4015 {
4017 }
4018 catch (Exception Ex)
4019 {
4020 ServiceRef.LogService.LogException(Ex);
4021 }
4022
4023 return [];
4024 }
4025
4032 public async Task SelectPeerReviewService(string ServiceId, string ServiceProvider)
4033 {
4035 }
4036
4037 private readonly Dictionary<string, bool> currentPetitions = [];
4038
4039 private async Task ContractsClient_PetitionClientUrlReceived(object? Sender, PetitionClientUrlEventArgs e)
4040 {
4041 lock (this.currentPetitions)
4042 {
4043 if (!this.currentPetitions.ContainsKey(e.PetitionId))
4044 {
4045 ServiceRef.LogService.LogWarning("Client URL message for a petition is ignored. Petition ID not recognized.",
4046 new KeyValuePair<string, object?>("PetitionId", e.PetitionId),
4047 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
4048 return;
4049 }
4050 }
4051
4052 await App.OpenUrlAsync(e.ClientUrl);
4053 }
4054
4055 #endregion
4056
4057 #region Signatures
4058
4065 public Task<byte[]> Sign(byte[] data, SignWith signWith)
4066 {
4067 return this.ContractsClient.SignAsync(data, signWith);
4068 }
4069
4079 public bool? ValidateSignature(LegalIdentity legalIdentity, byte[] data, byte[] signature)
4080 {
4081 return this.ContractsClient.ValidateSignature(legalIdentity, data, signature);
4082 }
4083
4094 public Task SendPetitionSignatureResponse(CaseInsensitiveString LegalId, byte[] Content, byte[] Signature, string PetitionId, string RequestorFullJid, bool Response)
4095 {
4096 return this.ContractsClient.PetitionSignatureResponseAsync(LegalId, Content, Signature, PetitionId, RequestorFullJid, Response);
4097 }
4098
4102 public event EventHandlerAsync<SignaturePetitionEventArgs>? PetitionForSignatureReceived;
4103
4104 private async Task ContractsClient_PetitionForSignatureReceived(object? Sender, SignaturePetitionEventArgs e)
4105 {
4108
4109 NotificationIntent Intent = new()
4110 {
4112 Title = Title,
4113 Body = Body,
4114 Action = NotificationAction.OpenPetition,
4115 EntityId = e.RequestorFullJid,
4116 CorrelationId = e.PetitionId,
4117 Presentation = NotificationPresentation.StoreOnly
4118 };
4119
4120 byte[] ContentToSign = e.ContentToSign ?? [];
4121 string ContentToSignBase64 = Convert.ToBase64String(ContentToSign);
4122 string Purpose = e.Purpose ?? string.Empty;
4123 string RequestorIdentityId = e.RequestorIdentity?.Id ?? string.Empty;
4124
4125 Intent.Extras["signatoryId"] = e.SignatoryIdentityId ?? string.Empty;
4126 Intent.Extras["petitionId"] = e.PetitionId ?? string.Empty;
4127 Intent.Extras["requestorIdentityId"] = RequestorIdentityId;
4128 Intent.Extras["purpose"] = Purpose;
4129 Intent.Extras["contentToSign"] = ContentToSignBase64;
4130
4131 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
4132 await this.PetitionForSignatureReceived.Raise(this, e);
4133 }
4134
4138 public event EventHandlerAsync<SignaturePetitionResponseEventArgs>? SignaturePetitionResponseReceived;
4139
4140 private async Task ContractsClient_PetitionedSignatureResponseReceived(object? Sender, SignaturePetitionResponseEventArgs e)
4141 {
4142 try
4143 {
4144 this.EndPetition(e.PetitionId);
4145 await this.SignaturePetitionResponseReceived.Raise(this, e);
4146 }
4147 catch (Exception ex)
4148 {
4149 ServiceRef.LogService.LogException(ex);
4150 await ServiceRef.UiService.DisplayException(ex);
4151 }
4152 }
4153
4154 #endregion
4155
4156 #region Provisioning
4157
4163 {
4164 get
4165 {
4166 if (this.provisioningClient is null)
4167 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ProvisioningServiceNotFound)]);
4168
4169 return this.provisioningClient;
4170 }
4171 }
4172
4173 private async Task ProvisioningClient_IsFriendQuestion(object? Sender, IsFriendEventArgs e)
4174 {
4175 if (e.From.IndexOfAny(clientChars) < 0)
4176 {
4179
4180 NotificationIntent Intent = new()
4181 {
4183 Title = Title,
4184 Body = Body,
4185 Action = NotificationAction.OpenPresenceRequest,
4186 EntityId = e.From,
4187 CorrelationId = e.Key
4188 };
4189
4190 await ServiceRef.Provider.GetRequiredService<INotificationServiceV2>().AddAsync(Intent, NotificationSource.Xmpp, null, CancellationToken.None);
4191 }
4192 }
4193
4194 private async Task ProvisioningClient_CanReadQuestion(object? Sender, CanReadEventArgs e)
4195 {
4196 if (e.From.IndexOfAny(clientChars) < 0)
4197 {
4200
4201 NotificationIntent Intent = new()
4202 {
4204 Title = Title,
4205 Body = Body,
4206 Action = NotificationAction.OpenPresenceRequest,
4207 EntityId = e.From,
4208 CorrelationId = e.Key
4209 };
4210
4211 await ServiceRef.Provider.GetRequiredService<INotificationServiceV2>().AddAsync(Intent, NotificationSource.Xmpp, null, CancellationToken.None);
4212 }
4213 }
4214
4215 private async Task ProvisioningClient_CanControlQuestion(object? Sender, CanControlEventArgs e)
4216 {
4217 if (e.From.IndexOfAny(clientChars) < 0)
4218 {
4221
4222 NotificationIntent Intent = new()
4223 {
4225 Title = Title,
4226 Body = Body,
4227 Action = NotificationAction.OpenPresenceRequest,
4228 EntityId = e.From,
4229 CorrelationId = e.Key
4230 };
4231
4232 await ServiceRef.Provider.GetRequiredService<INotificationServiceV2>().AddAsync(Intent, NotificationSource.Xmpp, null, CancellationToken.None);
4233 }
4234 }
4235
4236 private static readonly char[] clientChars = ['@', '/'];
4237
4241 public string ProvisioningServiceJid => this.ProvisioningClient.ProvisioningServerAddress;
4242
4254 public void IsFriendResponse(string ProvisioningServiceJID, string JID, string RemoteJID, string Key, bool IsFriend,
4255 RuleRange Range, EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4256 {
4257 this.ProvisioningClient.IsFriendResponse(ProvisioningServiceJID, JID, RemoteJID, Key, IsFriend, Range, Callback, State);
4258 }
4259
4272 public void CanControlResponseAll(string ProvisioningServiceJID, string JID, string RemoteJID, string Key, bool CanControl,
4273 string[]? ParameterNames, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4274 {
4275 this.ProvisioningClient.CanControlResponseAll(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl, ParameterNames,
4276 Node, Callback, State);
4277 }
4278
4291 public void CanControlResponseCaller(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4292 bool CanControl, string[]? ParameterNames, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4293 {
4294 this.ProvisioningClient.CanControlResponseCaller(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
4295 ParameterNames, Node, Callback, State);
4296 }
4297
4310 public void CanControlResponseDomain(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4311 bool CanControl, string[]? ParameterNames, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4312 {
4313 this.ProvisioningClient.CanControlResponseDomain(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
4314 ParameterNames, Node, Callback, State);
4315 }
4316
4330 public void CanControlResponseDevice(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4331 bool CanControl, string[]? ParameterNames, string Token, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback,
4332 object? State)
4333 {
4334 this.ProvisioningClient.CanControlResponseDevice(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
4335 ParameterNames, Token, Node, Callback, State);
4336 }
4337
4351 public void CanControlResponseService(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4352 bool CanControl, string[]? ParameterNames, string Token, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback,
4353 object? State)
4354 {
4355 this.ProvisioningClient.CanControlResponseService(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
4356 ParameterNames, Token, Node, Callback, State);
4357 }
4358
4372 public void CanControlResponseUser(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4373 bool CanControl, string[]? ParameterNames, string Token, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback,
4374 object? State)
4375 {
4376 this.ProvisioningClient.CanControlResponseUser(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
4377 ParameterNames, Token, Node, Callback, State);
4378 }
4379
4393 public void CanReadResponseAll(string ProvisioningServiceJID, string JID, string RemoteJID, string Key, bool CanRead,
4394 FieldType FieldTypes, string[]? FieldNames, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4395 {
4396 this.ProvisioningClient.CanReadResponseAll(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead, FieldTypes, FieldNames,
4397 Node, Callback, State);
4398 }
4399
4413 public void CanReadResponseCaller(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4414 bool CanRead, FieldType FieldTypes, string[]? FieldNames, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4415 {
4416 this.ProvisioningClient.CanReadResponseCaller(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
4417 FieldTypes, FieldNames, Node, Callback, State);
4418 }
4419
4433 public void CanReadResponseDomain(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4434 bool CanRead, FieldType FieldTypes, string[]? FieldNames, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4435 {
4436 this.ProvisioningClient.CanReadResponseDomain(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
4437 FieldTypes, FieldNames, Node, Callback, State);
4438 }
4439
4454 public void CanReadResponseDevice(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4455 bool CanRead, FieldType FieldTypes, string[]? FieldNames, string Token, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback,
4456 object? State)
4457 {
4458 this.ProvisioningClient.CanReadResponseDevice(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
4459 FieldTypes, FieldNames, Token, Node, Callback, State);
4460 }
4461
4476 public void CanReadResponseService(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4477 bool CanRead, FieldType FieldTypes, string[]? FieldNames, string Token, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback,
4478 object? State)
4479 {
4480 this.ProvisioningClient.CanReadResponseService(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
4481 FieldTypes, FieldNames, Token, Node, Callback, State);
4482 }
4483
4498 public void CanReadResponseUser(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
4499 bool CanRead, FieldType FieldTypes, string[]? FieldNames, string Token, IThingReference Node, EventHandlerAsync<IqResultEventArgs> Callback,
4500 object? State)
4501 {
4502 this.ProvisioningClient.CanReadResponseUser(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
4503 FieldTypes, FieldNames, Token, Node, Callback, State);
4504 }
4505
4516 public void DeleteDeviceRules(string ServiceJID, string DeviceJID, string NodeId, string SourceId, string Partition,
4517 EventHandlerAsync<IqResultEventArgs> Callback, object? State)
4518 {
4519 this.ProvisioningClient.DeleteDeviceRules(ServiceJID, DeviceJID, NodeId, SourceId, Partition, Callback, State);
4520 }
4521
4522 #endregion
4523
4524 #region IoT
4525
4531 {
4532 get
4533 {
4534 if (this.sensorClient is null)
4535 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.SensorServiceNotFound)]);
4536
4537 return this.sensorClient;
4538 }
4539 }
4540
4546 {
4547 get
4548 {
4549 if (this.controlClient is null)
4550 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ControlServiceNotFound)]);
4551
4552 return this.controlClient;
4553 }
4554 }
4555
4561 {
4562 get
4563 {
4564 if (this.concentratorClient is null)
4565 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ConcentratorServiceNotFound)]);
4566
4567 return this.concentratorClient;
4568 }
4569 }
4570
4577 public Task<(SearchResultThing[], bool)> GetMyDevices(int Offset, int MaxCount)
4578 {
4579 TaskCompletionSource<(SearchResultThing[], bool)> Result = new();
4580
4581 this.ProvisioningClient.GetDevices(Offset, MaxCount, (sender, e) =>
4582 {
4583 if (e.Ok)
4584 Result.TrySetResult((e.Things, e.More));
4585 else
4586 Result.TrySetException(e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetListOfMyDevices)]));
4587
4588 return Task.CompletedTask;
4589 }, null);
4590
4591 return Result.Task;
4592 }
4593
4598 public async Task<SearchResultThing[]> GetAllMyDevices()
4599 {
4600 (SearchResultThing[] Things, bool More) = await this.GetMyDevices(0, Constants.BatchSizes.DeviceBatchSize);
4601 if (!More)
4602 return Things;
4603
4604 List<SearchResultThing> Result = [];
4605 int Offset = Things.Length;
4606
4607 Result.AddRange(Things);
4608
4609 while (More)
4610 {
4611 (Things, More) = await this.GetMyDevices(Offset, Constants.BatchSizes.DeviceBatchSize);
4612 Result.AddRange(Things);
4613 Offset += Things.Length;
4614 }
4615
4616 return [.. Result];
4617 }
4618
4627 public void GetCertificate(string Token, EventHandlerAsync<CertificateEventArgs> Callback, object? State)
4628 {
4629 this.ProvisioningClient.GetCertificate(Token, Callback, State);
4630 }
4631
4640 public void GetControlForm(string To, string Language, EventHandlerAsync<DataFormEventArgs> Callback, object? State,
4641 params ThingReference[] Nodes)
4642 {
4643 this.ControlClient.GetForm(To, Language, Callback, State, Nodes);
4644 }
4645
4652 public Task<SensorDataClientRequest> RequestSensorReadout(string Destination, FieldType Types)
4653 {
4654 return this.SensorClient.RequestReadout(Destination, Types);
4655 }
4656
4664 public Task<SensorDataClientRequest> RequestSensorReadout(string Destination, ThingReference[] Nodes, FieldType Types)
4665 {
4666 return this.SensorClient.RequestReadout(Destination, Nodes, Types);
4667 }
4668
4669 #endregion
4670
4671 #region e-Daler
4672
4673 private readonly Dictionary<string, Wallet.Transaction> currentTransactions = [];
4674 private Balance? lastBalance = null;
4675 private DateTime lastEDalerEvent = DateTime.MinValue;
4676
4682 {
4683 get
4684 {
4685 if (this.eDalerClient is null)
4686 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.EDalerServiceNotFound)]);
4687
4688 return this.eDalerClient;
4689 }
4690 }
4691
4692 private void RegisterEDalerEventHandlers(EDalerClient Client)
4693 {
4694 Client.BalanceUpdated += this.EDalerClient_BalanceUpdated;
4695 Client.BuyEDalerOptionsClientUrlReceived += this.NeuroWallet_BuyEDalerOptionsClientUrlReceived;
4696 Client.BuyEDalerOptionsCompleted += this.NeuroWallet_BuyEDalerOptionsCompleted;
4697 Client.BuyEDalerOptionsError += this.NeuroWallet_BuyEDalerOptionsError;
4698 Client.BuyEDalerClientUrlReceived += this.NeuroWallet_BuyEDalerClientUrlReceived;
4699 Client.BuyEDalerCompleted += this.NeuroWallet_BuyEDalerCompleted;
4700 Client.BuyEDalerError += this.NeuroWallet_BuyEDalerError;
4701 Client.SellEDalerOptionsClientUrlReceived += this.NeuroWallet_SellEDalerOptionsClientUrlReceived;
4702 Client.SellEDalerOptionsCompleted += this.NeuroWallet_SellEDalerOptionsCompleted;
4703 Client.SellEDalerOptionsError += this.NeuroWallet_SellEDalerOptionsError;
4704 Client.SellEDalerClientUrlReceived += this.NeuroWallet_SellEDalerClientUrlReceived;
4705 Client.SellEDalerCompleted += this.NeuroWallet_SellEDalerCompleted;
4706 Client.SellEDalerError += this.NeuroWallet_SellEDalerError;
4707 }
4708
4709 private async Task EDalerClient_BalanceUpdated(object? _, BalanceEventArgs e)
4710 {
4711 this.lastBalance = e.Balance;
4712 this.lastEDalerEvent = DateTime.Now;
4713
4716
4717 NotificationIntent Intent = new()
4718 {
4719 Channel = Constants.PushChannels.EDaler,
4720 Title = Title,
4721 Body = Body,
4722 Action = NotificationAction.OpenBalance,
4723 EntityId = e.Balance?.Currency,
4724 CorrelationId = e.Balance?.Currency,
4725 Presentation = NotificationPresentation.StoreOnly
4726 };
4727
4728 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
4729 await this.EDalerBalanceUpdated.Raise(this, e);
4730 }
4731
4735 public event EventHandlerAsync<BalanceEventArgs>? EDalerBalanceUpdated;
4736
4740 public Balance? LastEDalerBalance => this.lastBalance;
4741
4745 public DateTime LastEDalerEvent => this.lastEDalerEvent;
4746
4754 public bool TryParseEDalerUri(string Uri, out EDalerUri Parsed, out string Reason)
4755 {
4756 return EDalerUri.TryParse(Uri, out Parsed, out Reason);
4757 }
4758
4767 public async Task<string> TryDecryptMessage(byte[] EncryptedMessage, byte[] PublicKey, Guid TransactionId, string RemoteEndpoint, bool LocalIsRecipient)
4768 {
4769 try
4770 {
4771 return await this.EDalerClient.DecryptMessage(EncryptedMessage, PublicKey, TransactionId, RemoteEndpoint, LocalIsRecipient);
4772 }
4773 catch (Exception ex)
4774 {
4775 ServiceRef.LogService.LogException(ex);
4776 return string.Empty;
4777 }
4778 }
4779
4785 public Task<EDaler.Transaction> SendEDalerUri(string Uri)
4786 {
4787 return this.EDalerClient.SendEDalerUriAsync(Uri);
4788 }
4789
4795 public Task<(AccountEvent[], bool)> GetEDalerAccountEvents(int MaxCount)
4796 {
4797 return this.EDalerClient.GetAccountEventsAsync(MaxCount);
4798 }
4799
4806 public Task<(AccountEvent[], bool)> GetEDalerAccountEvents(int MaxCount, DateTime From)
4807 {
4808 return this.EDalerClient.GetAccountEventsAsync(MaxCount, From);
4809 }
4810
4815 public Task<Balance> GetEDalerBalance()
4816 {
4817 return this.EDalerClient.GetBalanceAsync();
4818 }
4819
4824 public Task<(decimal, string, PendingPayment[])> GetPendingEDalerPayments()
4825 {
4826 return this.EDalerClient.GetPendingPayments();
4827 }
4828
4838 public Task<string> CreateFullEDalerPaymentUri(string ToBareJid, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays)
4839 {
4840 this.lastEDalerEvent = DateTime.Now;
4841 return this.EDalerClient.CreateFullPaymentUri(ToBareJid, Amount, AmountExtra, Currency, ValidNrDays);
4842 }
4843
4854 public Task<string> CreateFullEDalerPaymentUri(string ToBareJid, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays, string Message)
4855 {
4856 this.lastEDalerEvent = DateTime.Now;
4857 return this.EDalerClient.CreateFullPaymentUri(ToBareJid, Amount, AmountExtra, Currency, ValidNrDays, Message);
4858 }
4859
4869 public Task<string> CreateFullEDalerPaymentUri(LegalIdentity To, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays)
4870 {
4871 this.lastEDalerEvent = DateTime.Now;
4872 return this.EDalerClient.CreateFullPaymentUri(To, Amount, AmountExtra, Currency, ValidNrDays);
4873 }
4874
4885 public Task<string> CreateFullEDalerPaymentUri(LegalIdentity To, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays, string PrivateMessage)
4886 {
4887 this.lastEDalerEvent = DateTime.Now;
4888 return this.EDalerClient.CreateFullPaymentUri(To, Amount, AmountExtra, Currency, ValidNrDays, PrivateMessage);
4889 }
4890
4900 public string CreateIncompleteEDalerPayMeUri(string BareJid, decimal? Amount, decimal? AmountExtra, string Currency, string Message)
4901 {
4902 return this.EDalerClient.CreateIncompletePayMeUri(BareJid, Amount, AmountExtra, Currency, Message);
4903 }
4904
4915 public string CreateIncompleteEDalerPayMeUri(LegalIdentity To, decimal? Amount, decimal? AmountExtra, string Currency, string PrivateMessage)
4916 {
4917 return this.EDalerClient.CreateIncompletePayMeUri(To, Amount, AmountExtra, Currency, PrivateMessage);
4918 }
4919
4924 public async Task<IBuyEDalerServiceProvider[]> GetServiceProvidersForBuyingEDalerAsync()
4925 {
4927 }
4928
4936 public async Task<OptionsTransaction> InitiateBuyEDalerGetOptions(string ServiceId, string ServiceProvider)
4937 {
4938 string TransactionId = Guid.NewGuid().ToString();
4939 string SuccessUrl = await GenerateNeuroAccessUrl(
4940 new KeyValuePair<string, object?>("cmd", "beos"),
4941 new KeyValuePair<string, object?>("tid", TransactionId),
4942 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4943 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4944 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4945 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4946 string FailureUrl = await GenerateNeuroAccessUrl(
4947 new KeyValuePair<string, object?>("cmd", "beof"),
4948 new KeyValuePair<string, object?>("tid", TransactionId),
4949 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4950 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4951 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4952 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4953 string CancelUrl = await GenerateNeuroAccessUrl(
4954 new KeyValuePair<string, object?>("cmd", "beoc"),
4955 new KeyValuePair<string, object?>("tid", TransactionId),
4956 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4957 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4958 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4959 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4960
4961 TransactionId = await this.EDalerClient.InitiateGetOptionsBuyEDalerAsync(ServiceId, ServiceProvider, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
4962 OptionsTransaction Result = new(TransactionId);
4963
4964 lock (this.currentTransactions)
4965 {
4966 this.currentTransactions[TransactionId] = Result;
4967 }
4968
4969 return Result;
4970 }
4971
4972 private async Task NeuroWallet_BuyEDalerOptionsClientUrlReceived(object? Sender, BuyEDalerClientUrlEventArgs e)
4973 {
4974 lock (this.currentTransactions)
4975 {
4976 if (!this.currentTransactions.ContainsKey(e.TransactionId))
4977 {
4978 ServiceRef.LogService.LogWarning("Client URL message for getting options for buying eDaler ignored. Transaction ID not recognized.",
4979 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
4980 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
4981 return;
4982 }
4983 }
4984
4985 await Wallet.Transaction.OpenUrl(e.ClientUrl);
4986 }
4987
4988 private Task NeuroWallet_BuyEDalerOptionsCompleted(object? _, PaymentOptionsEventArgs e)
4989 {
4990 this.BuyEDalerGetOptionsCompleted(e.TransactionId, e.Options);
4991 return Task.CompletedTask;
4992 }
4993
4999 public void BuyEDalerGetOptionsCompleted(string TransactionId, IDictionary<CaseInsensitiveString, object>[] Options)
5000 {
5001 Wallet.Transaction? Transaction;
5002
5003 lock (this.currentTransactions)
5004 {
5005 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5006 return;
5007
5008 this.currentTransactions.Remove(TransactionId);
5009 }
5010
5012 OptionsTransaction.Completed(Options);
5013 }
5014
5015 private Task NeuroWallet_BuyEDalerOptionsError(object? _, PaymentErrorEventArgs e)
5016 {
5017 this.BuyEDalerGetOptionsFailed(e.TransactionId, e.Message);
5018 return Task.CompletedTask;
5019 }
5020
5026 public void BuyEDalerGetOptionsFailed(string TransactionId, string Message)
5027 {
5028 Wallet.Transaction? Transaction;
5029
5030 lock (this.currentTransactions)
5031 {
5032 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5033 return;
5034
5035 this.currentTransactions.Remove(TransactionId);
5036 }
5037
5038 Transaction.ErrorReported(Message);
5039 }
5040
5049 public async Task<PaymentTransaction> InitiateBuyEDaler(string ServiceId, string ServiceProvider, decimal Amount, string Currency)
5050 {
5051 string TransactionId = Guid.NewGuid().ToString();
5052 string SuccessUrl = await GenerateNeuroAccessUrl(
5053 new KeyValuePair<string, object?>("cmd", "bes"),
5054 new KeyValuePair<string, object?>("tid", TransactionId),
5055 new KeyValuePair<string, object?>("amt", Amount),
5056 new KeyValuePair<string, object?>("cur", Currency),
5057 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5058 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5059 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5060 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5061 string FailureUrl = await GenerateNeuroAccessUrl(
5062 new KeyValuePair<string, object?>("cmd", "bef"),
5063 new KeyValuePair<string, object?>("tid", TransactionId),
5064 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5065 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5066 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5067 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5068 string CancelUrl = await GenerateNeuroAccessUrl(
5069 new KeyValuePair<string, object?>("cmd", "bec"),
5070 new KeyValuePair<string, object?>("tid", TransactionId),
5071 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5072 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5073 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5074 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5075
5076 TransactionId = await this.EDalerClient.InitiateBuyEDalerAsync(ServiceId, ServiceProvider, Amount, Currency, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
5077 PaymentTransaction Result = new(TransactionId, Currency);
5078
5079 lock (this.currentTransactions)
5080 {
5081 this.currentTransactions[TransactionId] = Result;
5082 }
5083
5084 return Result;
5085 }
5086
5087 private static async Task<string> GenerateNeuroAccessUrl(params KeyValuePair<string, object?>[] Claims)
5088 {
5089 string Token = await ServiceRef.CryptoService.GenerateJwtToken(Claims);
5090 return Constants.UriSchemes.NeuroAccess + ":" + Token;
5091 }
5092
5093 private async Task NeuroWallet_BuyEDalerClientUrlReceived(object? Sender, BuyEDalerClientUrlEventArgs e)
5094 {
5095 lock (this.currentTransactions)
5096 {
5097 if (!this.currentTransactions.ContainsKey(e.TransactionId))
5098 {
5099 ServiceRef.LogService.LogWarning("Client URL message for buying eDaler ignored. Transaction ID not recognized.",
5100 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
5101 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
5102 return;
5103 }
5104 }
5105
5106 await Wallet.Transaction.OpenUrl(e.ClientUrl);
5107 }
5108
5109 private Task NeuroWallet_BuyEDalerCompleted(object? _, PaymentCompletedEventArgs e)
5110 {
5111 this.BuyEDalerCompleted(e.TransactionId, e.Amount, e.Currency);
5112 return Task.CompletedTask;
5113 }
5114
5121 public void BuyEDalerCompleted(string TransactionId, decimal Amount, string Currency)
5122 {
5123 Wallet.Transaction? Transaction;
5124
5125 lock (this.currentTransactions)
5126 {
5127 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5128 return;
5129
5130 this.currentTransactions.Remove(TransactionId);
5131 }
5132
5134 PaymentTransaction.Completed(Amount, Currency);
5135 }
5136
5137 private Task NeuroWallet_BuyEDalerError(object? _, PaymentErrorEventArgs e)
5138 {
5139 this.BuyEDalerFailed(e.TransactionId, e.Message);
5140 return Task.CompletedTask;
5141 }
5142
5148 public void BuyEDalerFailed(string TransactionId, string Message)
5149 {
5150 Wallet.Transaction? Transaction;
5151
5152 lock (this.currentTransactions)
5153 {
5154 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5155 return;
5156
5157 this.currentTransactions.Remove(TransactionId);
5158 }
5159
5160 Transaction.ErrorReported(Message);
5161 }
5162
5167 public async Task<ISellEDalerServiceProvider[]> GetServiceProvidersForSellingEDalerAsync()
5168 {
5170 }
5171
5179 public async Task<OptionsTransaction> InitiateSellEDalerGetOptions(string ServiceId, string ServiceProvider)
5180 {
5181 string TransactionId = Guid.NewGuid().ToString();
5182 string SuccessUrl = await GenerateNeuroAccessUrl(
5183 new KeyValuePair<string, object?>("cmd", "seos"),
5184 new KeyValuePair<string, object?>("tid", TransactionId),
5185 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5186 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5187 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5188 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5189 string FailureUrl = await GenerateNeuroAccessUrl(
5190 new KeyValuePair<string, object?>("cmd", "seof"),
5191 new KeyValuePair<string, object?>("tid", TransactionId),
5192 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5193 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5194 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5195 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5196 string CancelUrl = await GenerateNeuroAccessUrl(
5197 new KeyValuePair<string, object?>("cmd", "seoc"),
5198 new KeyValuePair<string, object?>("tid", TransactionId),
5199 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5200 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5201 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5202 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5203
5204 TransactionId = await this.EDalerClient.InitiateGetOptionsSellEDalerAsync(ServiceId, ServiceProvider, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
5205 OptionsTransaction Result = new(TransactionId);
5206
5207 lock (this.currentTransactions)
5208 {
5209 this.currentTransactions[TransactionId] = Result;
5210 }
5211
5212 return Result;
5213 }
5214
5215 private async Task NeuroWallet_SellEDalerOptionsClientUrlReceived(object? Sender, SellEDalerClientUrlEventArgs e)
5216 {
5217 lock (this.currentTransactions)
5218 {
5219 if (!this.currentTransactions.ContainsKey(e.TransactionId))
5220 {
5221 ServiceRef.LogService.LogWarning("Client URL message for getting options for selling eDaler ignored. Transaction ID not recognized.",
5222 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
5223 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
5224 return;
5225 }
5226 }
5227
5228 await Wallet.Transaction.OpenUrl(e.ClientUrl);
5229 }
5230
5231 private Task NeuroWallet_SellEDalerOptionsCompleted(object? _, PaymentOptionsEventArgs e)
5232 {
5233 this.SellEDalerGetOptionsCompleted(e.TransactionId, e.Options);
5234 return Task.CompletedTask;
5235 }
5236
5242 public void SellEDalerGetOptionsCompleted(string TransactionId, IDictionary<CaseInsensitiveString, object>[] Options)
5243 {
5244 Wallet.Transaction? Transaction;
5245
5246 lock (this.currentTransactions)
5247 {
5248 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5249 return;
5250
5251 this.currentTransactions.Remove(TransactionId);
5252 }
5253
5255 OptionsTransaction.Completed(Options);
5256 }
5257
5258 private Task NeuroWallet_SellEDalerOptionsError(object? _, PaymentErrorEventArgs e)
5259 {
5260 this.SellEDalerGetOptionsFailed(e.TransactionId, e.Message);
5261 return Task.CompletedTask;
5262 }
5263
5269 public void SellEDalerGetOptionsFailed(string TransactionId, string Message)
5270 {
5271 Wallet.Transaction? Transaction;
5272
5273 lock (this.currentTransactions)
5274 {
5275 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5276 return;
5277
5278 this.currentTransactions.Remove(TransactionId);
5279 }
5280
5281 Transaction.ErrorReported(Message);
5282 }
5283
5292 public async Task<PaymentTransaction> InitiateSellEDaler(string ServiceId, string ServiceProvider, decimal Amount, string Currency)
5293 {
5294 string TransactionId = Guid.NewGuid().ToString();
5295 string SuccessUrl = await GenerateNeuroAccessUrl(
5296 new KeyValuePair<string, object?>("cmd", "ses"),
5297 new KeyValuePair<string, object?>("tid", TransactionId),
5298 new KeyValuePair<string, object?>("amt", Amount),
5299 new KeyValuePair<string, object?>("cur", Currency),
5300 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5301 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5302 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5303 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5304 string FailureUrl = await GenerateNeuroAccessUrl(
5305 new KeyValuePair<string, object?>("cmd", "sef"),
5306 new KeyValuePair<string, object?>("tid", TransactionId),
5307 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5308 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5309 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5310 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5311 string CancelUrl = await GenerateNeuroAccessUrl(
5312 new KeyValuePair<string, object?>("cmd", "sec"),
5313 new KeyValuePair<string, object?>("tid", TransactionId),
5314 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
5315 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
5316 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
5317 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
5318
5319 TransactionId = await this.EDalerClient.InitiateSellEDalerAsync(ServiceId, ServiceProvider, Amount, Currency, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
5320 PaymentTransaction Result = new(TransactionId, Currency);
5321
5322 lock (this.currentTransactions)
5323 {
5324 this.currentTransactions[TransactionId] = Result;
5325 }
5326
5327 return Result;
5328 }
5329
5330 private async Task NeuroWallet_SellEDalerClientUrlReceived(object? Sender, SellEDalerClientUrlEventArgs e)
5331 {
5332 lock (this.currentTransactions)
5333 {
5334 if (!this.currentTransactions.ContainsKey(e.TransactionId))
5335 {
5336 ServiceRef.LogService.LogWarning("Client URL message ignored. Transaction ID not recognized.",
5337 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
5338 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
5339 return;
5340 }
5341 }
5342
5343 await Wallet.Transaction.OpenUrl(e.ClientUrl);
5344 }
5345
5346 private Task NeuroWallet_SellEDalerError(object? _, PaymentErrorEventArgs e)
5347 {
5348 this.SellEDalerFailed(e.TransactionId, e.Message);
5349 return Task.CompletedTask;
5350 }
5351
5357 public void SellEDalerFailed(string TransactionId, string Message)
5358 {
5359 Wallet.Transaction? Transaction;
5360
5361 lock (this.currentTransactions)
5362 {
5363 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5364 return;
5365
5366 this.currentTransactions.Remove(TransactionId);
5367 }
5368
5369 Transaction.ErrorReported(Message);
5370 }
5371
5372 private Task NeuroWallet_SellEDalerCompleted(object? _, PaymentCompletedEventArgs e)
5373 {
5374 this.SellEDalerCompleted(e.TransactionId, e.Amount, e.Currency);
5375 return Task.CompletedTask;
5376 }
5377
5384 public void SellEDalerCompleted(string TransactionId, decimal Amount, string Currency)
5385 {
5386 Wallet.Transaction? Transaction;
5387
5388 lock (this.currentTransactions)
5389 {
5390 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
5391 return;
5392
5393 this.currentTransactions.Remove(TransactionId);
5394 }
5395
5397 PaymentTransaction.Completed(Amount, Currency);
5398 }
5399
5400 #endregion
5401
5402 #region Neuro-Features
5403
5404 private DateTime lastTokenEvent = DateTime.MinValue;
5405
5410 {
5411 get
5412 {
5413 if (this.neuroFeaturesClient is null)
5414 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.NeuroFeaturesServiceNotFound)]);
5415
5416 return this.neuroFeaturesClient;
5417 }
5418 }
5419
5420 private void RegisterNeuroFeatureEventHandlers(NeuroFeaturesClient Client)
5421 {
5422 Client.TokenAdded += this.NeuroFeaturesClient_TokenAdded;
5423 Client.TokenRemoved += this.NeuroFeaturesClient_TokenRemoved;
5424
5425 Client.StateUpdated += this.NeuroFeaturesClient_StateUpdated;
5426 Client.VariablesUpdated += this.NeuroFeaturesClient_VariablesUpdated;
5427 }
5428
5432 public DateTime LastNeuroFeatureEvent => this.lastTokenEvent;
5433
5434 private async Task NeuroFeaturesClient_TokenRemoved(object _, NeuroFeatures.EventArguments.TokenEventArgs e)
5435 {
5436 this.lastTokenEvent = DateTime.Now;
5437
5440
5441 NotificationIntent Intent = new()
5442 {
5443 Channel = Constants.PushChannels.Tokens,
5444 Title = Title,
5445 Body = Body,
5446 Action = NotificationAction.OpenToken,
5447 EntityId = e.Token?.TokenId,
5448 CorrelationId = e.Token?.TokenId,
5449 Presentation = NotificationPresentation.StoreOnly
5450 };
5451
5452 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
5453 await this.NeuroFeatureRemoved.Raise(this, e);
5454 }
5455
5459 public event EventHandlerAsync<NeuroFeatures.EventArguments.TokenEventArgs>? NeuroFeatureRemoved;
5460
5461 private async Task NeuroFeaturesClient_TokenAdded(object _, NeuroFeatures.EventArguments.TokenEventArgs e)
5462 {
5463 this.lastTokenEvent = DateTime.Now;
5464
5467
5468 NotificationIntent Intent = new()
5469 {
5470 Channel = Constants.PushChannels.Tokens,
5471 Title = Title,
5472 Body = Body,
5473 Action = NotificationAction.OpenToken,
5474 EntityId = e.Token?.TokenId,
5475 CorrelationId = e.Token?.TokenId,
5476 Presentation = NotificationPresentation.StoreOnly
5477 };
5478
5479 await AddNotificationAsync(Intent, NotificationSource.Xmpp, null);
5480 await this.NeuroFeatureAdded.Raise(this, e);
5481 }
5482
5486 public event EventHandlerAsync<NeuroFeatures.EventArguments.TokenEventArgs>? NeuroFeatureAdded;
5487
5488 private async Task NeuroFeaturesClient_VariablesUpdated(object? _, VariablesUpdatedEventArgs e)
5489 {
5490 await this.NeuroFeatureVariablesUpdated.Raise(this, e);
5491 }
5492
5496 public event EventHandlerAsync<VariablesUpdatedEventArgs>? NeuroFeatureVariablesUpdated;
5497
5498 private async Task NeuroFeaturesClient_StateUpdated(object? _, NewStateEventArgs e)
5499 {
5500 await this.NeuroFeatureStateUpdated.Raise(this, e);
5501 }
5502
5506 public event EventHandlerAsync<NewStateEventArgs>? NeuroFeatureStateUpdated;
5507
5512 public Task<TokensEventArgs> GetNeuroFeatures()
5513 {
5514 return this.GetNeuroFeatures(0, int.MaxValue);
5515 }
5516
5523 public Task<TokensEventArgs> GetNeuroFeatures(int Offset, int MaxCount)
5524 {
5525 return this.NeuroFeaturesClient.GetTokensAsync(Offset, MaxCount);
5526 }
5527
5532 public Task<string[]> GetNeuroFeatureReferences()
5533 {
5534 return this.GetNeuroFeatureReferences(0, int.MaxValue);
5535 }
5536
5543 public Task<string[]> GetNeuroFeatureReferences(int Offset, int MaxCount)
5544 {
5545 return this.NeuroFeaturesClient.GetTokenReferencesAsync(Offset, MaxCount);
5546 }
5547
5552 public Task<TokenTotalsEventArgs> GetNeuroFeatureTotals()
5553 {
5554 return this.NeuroFeaturesClient.GetTotalsAsync();
5555 }
5556
5562 public Task<TokensEventArgs> GetNeuroFeaturesForContract(string ContractId)
5563 {
5564 return this.NeuroFeaturesClient.GetContractTokensAsync(ContractId);
5565 }
5566
5574 public Task<TokensEventArgs> GetNeuroFeaturesForContract(string ContractId, int Offset, int MaxCount)
5575 {
5576 return this.NeuroFeaturesClient.GetContractTokensAsync(ContractId, Offset, MaxCount);
5577 }
5578
5584 public Task<string[]> GetNeuroFeatureReferencesForContract(string ContractId)
5585 {
5587 }
5588
5596 public Task<string[]> GetNeuroFeatureReferencesForContract(string ContractId, int Offset, int MaxCount)
5597 {
5598 return this.NeuroFeaturesClient.GetContractTokenReferencesAsync(ContractId, Offset, MaxCount);
5599 }
5600
5606 public Task<Token> GetNeuroFeature(string TokenId)
5607 {
5608 return this.NeuroFeaturesClient.GetTokenAsync(TokenId);
5609 }
5610
5616 public Task<TokenEvent[]> GetNeuroFeatureEvents(string TokenId)
5617 {
5618 return this.GetNeuroFeatureEvents(TokenId, 0, int.MaxValue);
5619 }
5620
5628 public Task<TokenEvent[]> GetNeuroFeatureEvents(string TokenId, int Offset, int MaxCount)
5629 {
5630 return this.NeuroFeaturesClient.GetEventsAsync(TokenId, Offset, MaxCount);
5631 }
5632
5638 public Task AddNeuroFeatureTextNote(string TokenId, string TextNote)
5639 {
5640 return this.AddNeuroFeatureTextNote(TokenId, TextNote, false);
5641 }
5642
5651 public Task AddNeuroFeatureTextNote(string TokenId, string TextNote, bool Personal)
5652 {
5653 this.lastTokenEvent = DateTime.Now;
5654
5655 return this.NeuroFeaturesClient.AddTextNoteAsync(TokenId, TextNote, Personal);
5656 }
5657
5663 public Task AddNeuroFeatureXmlNote(string TokenId, string XmlNote)
5664 {
5665 return this.AddNeuroFeatureXmlNote(TokenId, XmlNote, false);
5666 }
5667
5676 public Task AddNeuroFeatureXmlNote(string TokenId, string XmlNote, bool Personal)
5677 {
5678 this.lastTokenEvent = DateTime.Now;
5679
5680 return this.NeuroFeaturesClient.AddXmlNoteAsync(TokenId, XmlNote, Personal);
5681 }
5682
5687 public Task<CreationAttributesEventArgs> GetNeuroFeatureCreationAttributes()
5688 {
5690 }
5691
5696 public async Task<string> GenerateNeuroFeatureStateDiagramReport(string TokenId)
5697 {
5699 if (!e.Ok)
5700 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetStateDiagram)]);
5701
5702 return await e.ReportText.MarkdownToXaml();
5703 }
5704
5709 public async Task<VerticalStackLayout> GenerateNeuroFeatureStateDiagramReportMaui(string TokenId)
5710 {
5712 if (!e.Ok)
5713 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetStateDiagram)]);
5714
5715 return await e.ReportText.MarkdownToMaui();
5716 }
5717
5722 public async Task<string> GenerateNeuroFeatureProfilingReport(string TokenId)
5723 {
5725 if (!e.Ok)
5726 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetProfiling)]);
5727
5728 return await e.ReportText.MarkdownToXaml();
5729 }
5730
5735 public async Task<VerticalStackLayout> GenerateNeuroFeatureProfilingReportMaui(string TokenId)
5736 {
5738 if (!e.Ok)
5739 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetProfiling)]);
5740
5741 return await e.ReportText.MarkdownToMaui();
5742 }
5743
5748 public async Task<string> GenerateNeuroFeaturePresentReport(string TokenId)
5749 {
5751 if (!e.Ok)
5752 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetPresent)]);
5753
5754 return await e.ReportText.MarkdownToXaml();
5755 }
5756
5761 public async Task<VerticalStackLayout> GenerateNeuroFeaturePresentReportMaui(string TokenId)
5762 {
5764 if (!e.Ok)
5765 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetPresent)]);
5766
5767 return await e.ReportText.MarkdownToMaui();
5768 }
5769
5774 public async Task<string> GenerateNeuroFeatureHistoryReport(string TokenId)
5775 {
5777 if (!e.Ok)
5778 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetHistory)]);
5779
5780 return await e.ReportText.MarkdownToXaml();
5781 }
5782
5786 public async Task<VerticalStackLayout> GenerateNeuroFeatureHistoryReportMaui(string TokenId)
5787 {
5789 if (!e.Ok)
5790 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetHistory)]);
5791
5792 return await e.ReportText.MarkdownToMaui();
5793 }
5794
5795
5801 public Task<CurrentStateEventArgs> GetNeuroFeatureCurrentState(string TokenId)
5802 {
5803 return this.NeuroFeaturesClient.GetCurrentStateAsync(TokenId);
5804 }
5805
5806 #endregion
5807
5808 #region Private XML
5809
5817 public Task SavePrivateXml(string Xml)
5818 {
5819 return this.xmppClient?.SetPrivateXmlElementAsync(Xml)
5820 ?? throw new Exception("Not connected to XMPP network.");
5821 }
5822
5830 public Task SavePrivateXml(XmlElement Xml)
5831 {
5832 return this.xmppClient?.SetPrivateXmlElementAsync(Xml)
5833 ?? throw new Exception("Not connected to XMPP network.");
5834 }
5835
5843 public async Task<XmlElement?> LoadPrivateXml(string LocalName, string Namespace)
5844 {
5845 return await this.XmppClient.GetPrivateXmlElementAsync(LocalName, Namespace);
5846 }
5847
5853 public Task DeletePrivateXml(string LocalName, string Namespace)
5854 {
5855 StringBuilder Xml = new();
5856
5857 Xml.Append('<');
5858 Xml.Append(XML.Encode(LocalName));
5859 Xml.Append(" xmlns='");
5860 Xml.Append(XML.Encode(Namespace));
5861 Xml.Append("'/>");
5862 return this.SavePrivateXml(Xml.ToString());
5863 }
5864
5865 #endregion
5866
5867 #region PubSub
5868
5873 {
5874 get
5875 {
5876 if (this.pepClient is null)
5877 {
5878 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.PubSubServiceNotFound)]);
5879 }
5880
5881 return this.pepClient.PubSubClient;
5882 }
5883 }
5884
5886 public async Task<Item[]?> GetAllNodeIdsAsync()
5887 {
5888 try
5889 {
5890 TaskCompletionSource<ServiceItemsDiscoveryEventArgs> Tcs = new();
5893 (s, e) => { Tcs.TrySetResult(e); return Task.CompletedTask; },
5894 null);
5895 ServiceItemsDiscoveryEventArgs Result = await Tcs.Task;
5896 return Result.Items;
5897 }
5898 catch
5899 {
5900 return null;
5901 }
5902 }
5903
5905 public async Task<PubSubItem[]?> GetItemsAsync(string NodeId)
5906 {
5907 try
5908 {
5909 TaskCompletionSource<ItemsEventArgs> Tcs = new();
5910 await this.PubSubClient.GetItems(NodeId, (s, e) => HandleResult(e, Tcs), null);
5911 ItemsEventArgs Result = await Tcs.Task;
5912 return Result.Items;
5913 }
5914 catch
5915 {
5916 return null;
5917 }
5918 }
5919
5921 public async Task<PubSubItem[]?> GetItemsAsync(string NodeId, string[] ItemIds)
5922 {
5923 try
5924 {
5925 TaskCompletionSource<ItemsEventArgs> Tcs = new();
5926 await this.PubSubClient.GetItems(NodeId, ItemIds, (s, e) => HandleResult(e, Tcs), null);
5927 ItemsEventArgs Result = await Tcs.Task;
5928 return Result.Items;
5929 }
5930 catch
5931 {
5932 return null;
5933 }
5934 }
5935
5937 public async Task<PubSubItem?> GetItemAsync(string NodeId, string ItemId)
5938 {
5939 try
5940 {
5941 TaskCompletionSource<ItemsEventArgs> Tcs = new();
5942 await this.PubSubClient.GetItems(NodeId, [ItemId], (s, e) => HandleResult(e, Tcs), null);
5943 ItemsEventArgs Result = await Tcs.Task;
5944 return Result.Items.FirstOrDefault();
5945 }
5946 catch
5947 {
5948 return null;
5949 }
5950 }
5951
5953 public async Task<PubSubItem[]?> GetLatestItemsAsync(string NodeId, int Count)
5954 {
5955 try
5956 {
5957 TaskCompletionSource<ItemsEventArgs> Tcs = new();
5958 await this.PubSubClient.GetLatestItems(NodeId, Count, (s, e) => HandleResult(e, Tcs), null);
5959 ItemsEventArgs Result = await Tcs.Task;
5960 return Result.Items;
5961 }
5962 catch
5963 {
5964 return null;
5965 }
5966 }
5967
5969 public async Task<PubSubPageResult?> GetItemsPageAsync(string NodeId, string? ServiceAddress = null, string? After = null, string? Before = null, int? Index = null, int? Max = null)
5970 {
5971 try
5972 {
5973 RestrictedQuery? Query = null;
5974 bool HasCursor = !string.IsNullOrWhiteSpace(After) || !string.IsNullOrWhiteSpace(Before) || Index.HasValue || Max.HasValue;
5975 if (HasCursor)
5976 {
5977 Query = new RestrictedQuery(After, Before, Index, Max);
5978 }
5979
5980 TaskCompletionSource<ItemsEventArgs> Tcs = new();
5981 string? EffectiveServiceAddress = ServiceAddress ?? this.PubSubClient.ComponentAddress;
5982
5983 if (Query is null)
5984 {
5985 if (string.IsNullOrWhiteSpace(EffectiveServiceAddress))
5986 {
5987 await this.PubSubClient.GetItems(NodeId, (s, e) => HandleResult(e, Tcs), null);
5988 }
5989 else
5990 {
5991 await this.PubSubClient.GetItems(EffectiveServiceAddress, NodeId, (s, e) => HandleResult(e, Tcs), null);
5992 }
5993 }
5994 else
5995 {
5996 if (string.IsNullOrWhiteSpace(EffectiveServiceAddress))
5997 {
5998 await this.PubSubClient.GetItems(NodeId, Query, (s, e) => HandleResult(e, Tcs), null);
5999 }
6000 else
6001 {
6002 await this.PubSubClient.GetItems(EffectiveServiceAddress, NodeId, Query, (s, e) => HandleResult(e, Tcs), null);
6003 }
6004 }
6005
6006 ItemsEventArgs Result = await Tcs.Task;
6007 PubSubItem[] Items = Result.Items ?? [];
6008 ResultPage? Page = Result.Page;
6009 return new PubSubPageResult(NodeId, Items, Page);
6010 }
6011 catch
6012 {
6013 return null;
6014 }
6015 }
6016
6018 public async Task<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs?> CreateNodeAsync(string NodeId, NodeConfiguration? Config = null)
6019 {
6020 TaskCompletionSource<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs> Tcs = new();
6021 if (Config is null)
6022 await this.PubSubClient.CreateNode(NodeId, (s, e) => HandleResult(e, Tcs), null);
6023 else
6024 await this.PubSubClient.CreateNode(NodeId, Config, (s, e) => HandleResult(e, Tcs), null);
6025 return await Tcs.Task;
6026 }
6027
6029 public async Task<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs?> TryCreateNodeAsync(string NodeId, NodeConfiguration? Config = null)
6030 {
6031 try { return await this.CreateNodeAsync(NodeId, Config); }
6032 catch { return null; }
6033 }
6034
6036 public async Task<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs?> DeleteNodeAsync(string NodeId, string? RedirectUri = null)
6037 {
6038 TaskCompletionSource<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs> Tcs = new();
6039 await this.PubSubClient.DeleteNode(NodeId, RedirectUri, (s, e) => HandleResult(e, Tcs), null);
6040 return await Tcs.Task;
6041 }
6042
6044 public async Task<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs?> TryDeleteNodeAsync(string NodeId, string? RedirectUri = null)
6045 {
6046 try { return await this.DeleteNodeAsync(NodeId, RedirectUri); }
6047 catch { return null; }
6048 }
6049
6051 public async Task<SubscriptionEventArgs> SubscribeAsync(string NodeId, string? Jid = null, SubscriptionOptions? Options = null)
6052 {
6053 TaskCompletionSource<SubscriptionEventArgs> Tcs = new();
6054 if (Options is null)
6055 await this.PubSubClient.Subscribe(NodeId, Jid, (s, e) => HandleResult(e, Tcs), null);
6056 else
6057 await this.PubSubClient.Subscribe(NodeId, Jid, Options, (s, e) => HandleResult(e, Tcs), null);
6058 return await Tcs.Task;
6059 }
6060
6062 public async Task<SubscriptionEventArgs?> TrySubscribeAsync(string NodeId, string? Jid = null, SubscriptionOptions? Options = null)
6063 {
6064 try { return await this.SubscribeAsync(NodeId, Jid, Options); }
6065 catch { return null; }
6066 }
6067
6069 public async Task<SubscriptionEventArgs> UnsubscribeAsync(string NodeId, string? Jid = null, string? SubscriptionId = null)
6070 {
6071 TaskCompletionSource<SubscriptionEventArgs> Tcs = new();
6072 await this.PubSubClient.Unsubscribe(NodeId, Jid, SubscriptionId, (s, e) => HandleResult(e, Tcs), null);
6073 return await Tcs.Task;
6074 }
6075
6077 public async Task<SubscriptionEventArgs?> TryUnsubscribeAsync(string NodeId, string? Jid = null, string? SubscriptionId = null)
6078 {
6079 try { return await this.UnsubscribeAsync(NodeId, Jid, SubscriptionId); }
6080 catch { return null; }
6081 }
6082
6084 public async Task<ItemResultEventArgs> PublishAsync(string NodeId, string? ItemId = null, string PayloadXml = "")
6085 {
6086 TaskCompletionSource<ItemResultEventArgs> Tcs = new();
6087 await this.PubSubClient.Publish(NodeId, ItemId ?? string.Empty, PayloadXml, (s, e) => HandleResult(e, Tcs), null);
6088 return await Tcs.Task;
6089 }
6090
6092 public async Task<ItemResultEventArgs?> TryPublishAsync(string NodeId, string? ItemId = null, string PayloadXml = "")
6093 {
6094 try { return await this.PublishAsync(NodeId, ItemId, PayloadXml); }
6095 catch { return null; }
6096 }
6097
6099 public async Task<IqResultEventArgs> RetractAsync(string NodeId, string ItemId)
6100 {
6101 TaskCompletionSource<IqResultEventArgs> Tcs = new();
6102 await this.PubSubClient.Retract(NodeId, ItemId, (s, e) => HandleResult(e, Tcs), null);
6103 return await Tcs.Task;
6104 }
6105
6107 public async Task<IqResultEventArgs?> TryRetractAsync(string NodeId, string ItemId)
6108 {
6109 try { return await this.RetractAsync(NodeId, ItemId); }
6110 catch { return null; }
6111 }
6112
6114 public async Task<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs> PurgeNodeAsync(string NodeId)
6115 {
6116 TaskCompletionSource<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs> Tcs = new();
6117 await this.PubSubClient.PurgeNode(NodeId, (s, e) => HandleResult(e, Tcs), null);
6118 return await Tcs.Task;
6119 }
6120
6122 public async Task<Waher.Networking.XMPP.PubSub.Events.NodeEventArgs?> TryPurgeNodeAsync(string NodeId)
6123 {
6124 try { return await this.PurgeNodeAsync(NodeId); }
6125 catch { return null; }
6126 }
6127
6128 private static Task HandleResult<T>(T e, TaskCompletionSource<T> Tcs) where T : IqResultEventArgs
6129 {
6130 if (e.Ok)
6131 Tcs.TrySetResult(e);
6132 else
6133 Tcs.TrySetException(e.StanzaError ?? new Exception());
6134 return Task.CompletedTask;
6135 }
6136 #endregion
6137
6138 #region Helpers
6139 private static async Task AddNotificationAsync(NotificationIntent Intent, NotificationSource Source, string? RawPayload)
6140 {
6141 try
6142 {
6144 await NotificationService.AddAsync(Intent, Source, RawPayload, CancellationToken.None);
6145 }
6146 catch (Exception Ex)
6147 {
6148 ServiceRef.LogService.LogException(Ex);
6149 }
6150 }
6151
6152 private static string ToBareJid(string Jid)
6153 {
6154 if (string.IsNullOrWhiteSpace(Jid))
6155 return Jid;
6156
6157 int SlashIndex = Jid.IndexOf('/');
6158 return SlashIndex > -1 ? Jid.Substring(0, SlashIndex) : Jid;
6159 }
6160 #endregion
6161 }
6162}
Account event
Definition: AccountEvent.cs:16
Contains information about a balance.
Definition: Balance.cs:11
CaseInsensitiveString Currency
Currency of amount.
Definition: Balance.cs:54
eDaler XMPP client.
Definition: EDalerClient.cs:23
Task<(AccountEvent[], bool)> GetAccountEventsAsync(int MaxEvents)
Gets account events associated with the wallet of the account.
const string NamespaceEDaler
Namespace of eDaler component.
Definition: EDalerClient.cs:27
Task< Balance > GetBalanceAsync()
Gets the current balance of the eDaler wallet associated with the account.
override void Dispose()
IDisposable.Dispose
Definition: EDalerClient.cs:66
string DecryptMessage(byte[] EncryptedMessage, byte[] PublicKey, Guid TransactionId, bool LocalIsRecipient)
Decrypts a message that was aimed at the client using the current keys.
string CreateIncompletePayMeUri(string BareJid, decimal? Amount, decimal? AmountExtra, string Currency, string Message)
Generates an incomplete eDaler PayMe URI.
async Task<(decimal, string, PendingPayment[])> GetPendingPayments()
Gets the amount of payments pending to be processed.
Task< string > InitiateBuyEDalerAsync(string ServiceId, string ServiceProvider, decimal Amount, string Currency)
Initiates a process for buying eDaler.
Task< Transaction > SendEDalerUriAsync(string Uri)
Sends an eDaler URI to the server
Task< string > CreateFullPaymentUri(decimal Amount, decimal? AmountExtra, CaseInsensitiveString Currency, int ValidNrDays)
Creates a full payment URI to anyone who is the first in claiming the URI.
Task< IBuyEDalerServiceProvider[]> GetServiceProvidersForBuyingEDalerAsync()
Gets available service providers who can help the user buy eDaler.
Task< ISellEDalerServiceProvider[]> GetServiceProvidersForSellingEDalerAsync()
Gets available service providers who can help the user sell eDaler.
Task< string > InitiateGetOptionsSellEDalerAsync(string ServiceId, string ServiceProvider)
Initiates a process for getting payment options for selling eDaler.
Task< string > InitiateGetOptionsBuyEDalerAsync(string ServiceId, string ServiceProvider)
Initiates a process for getting payment options for buying eDaler.
Task< string > InitiateSellEDalerAsync(string ServiceId, string ServiceProvider, decimal Amount, string Currency)
Initiates a process for selling eDaler.
Wallet balance event arguments.
Event arguments for events where a client URL needs to be displayed when buying eDaler.
string ClientUrl
URL client needs to open to complete the transaction of buying eDaler.
string TransactionId
Transaction ID, if available in the response.
Event arguments for event signalling the completion of a payment operation.
string TransactionId
Transaction ID, if available in the response.
Event arguments for event signalling an error of a payment operation.
string TransactionId
Transaction ID, if available in the response.
Event arguments for operations returning payment options.
IDictionary< CaseInsensitiveString, object >[] Options
Payment options.
string TransactionId
Transaction ID, if available in the response.
Event arguments for events where a client URL needs to be displayed when selling eDaler.
string TransactionId
Transaction ID, if available in the response.
string ClientUrl
URL client needs to open to complete the transaction of selling eDaler.
Contains information about a pending payment.
Represents a transaction in the eDaler network.
Definition: Transaction.cs:36
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:14
static bool TryParse(string Uri, out EDalerUri Result)
Tries to parse an eDaler URI
Definition: EDalerUri.cs:192
Represents an instance of the Neuro-Access app.
Definition: App.xaml.cs:125
static new? App Current
Gets the current application instance.
Definition: App.xaml.cs:169
const int DeviceBatchSize
Number of devices to load in a single batch.
Definition: Constants.cs:931
Machine-readable names in contracts.
Definition: Constants.cs:938
const string PaymentInstructionsNamespace
Namespace for payment instructions
Definition: Constants.cs:942
const string BuyEDaler
Local name for contracts for buying eDaler.
Definition: Constants.cs:947
const string SellEDaler
Local name for contracts for selling eDaler.
Definition: Constants.cs:952
const string OnboardingDomain
Neuro-Access onboarding domain.
Definition: Constants.cs:312
static readonly TimeSpan Reconnect
Reconnect interval
Definition: Constants.cs:671
const string Default
The default language code.
Definition: Constants.cs:105
Absolute paths to important pages.
Definition: Constants.cs:875
const string RegistrationPage
Path to registration page.
Definition: Constants.cs:884
const string Provisioning
Provisioning channel
Definition: Constants.cs:769
const string Petitions
Petitions channel
Definition: Constants.cs:744
const string Identities
Identities channel
Definition: Constants.cs:749
const string Messages
Messages channel
Definition: Constants.cs:739
const string EDaler
eDaler channel
Definition: Constants.cs:759
const string Tokens
Tokens channel
Definition: Constants.cs:764
const string Contracts
Contracts channel
Definition: Constants.cs:754
const int DefaultImageHeight
The default height to use when generating QR Code images.
Definition: Constants.cs:1013
const int DefaultImageWidth
The default width to use when generating QR Code images.
Definition: Constants.cs:1009
Runtime setting key names.
Definition: Constants.cs:1024
const string TransferIdCodeSent
Transfer ID code
Definition: Constants.cs:1053
static readonly TimeSpan XmppConnect
XMPP Connect timeout
Definition: Constants.cs:702
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
A strongly-typed resource class, for looking up localized strings, etc.
static string UsernameNameAlreadyTaken
Looks up a localized string similar to Username already exists. Please try another....
static string InvalidUsernameOrPassword
Looks up a localized string similar to Invalid username or password.
static string NotificationPetitionContractTitle
Looks up a localized string similar to Contract request from {0}.
static string ControlServiceNotFound
Looks up a localized string similar to No control service found..
static string NeuroFeaturesServiceNotFound
Looks up a localized string similar to No Neuro-Features service found on the Service Provider....
static string NotificationIdentityRejectedBody
Looks up a localized string similar to Your identity was rejected..
static string NotificationPetitionSignatureBody
Looks up a localized string similar to A new signature request is waiting..
static string NotificationReadAccessBody
Looks up a localized string similar to Requested read access..
static string NotificationContractProposalTitle
Looks up a localized string similar to Contract proposal.
static string NotificationTokenRemovedBody
Looks up a localized string similar to Your wallet was updated..
static string DomainIsNotAValidOperator
Looks up a localized string similar to {0} is not a valid operator.
static string NotificationReadAccessTitle
Looks up a localized string similar to Read access request from {0}.
static string NotificationIdentityRejectedTitle
Looks up a localized string similar to Identity status updated.
static string NotificationContractProposalBody
Looks up a localized string similar to A contract proposal is available..
static string UnableToGetPresent
Looks up a localized string similar to Unable to get present report..
static string NotificationIdentityCompromisedBody
Looks up a localized string similar to Your identity may be compromised..
static string FileUploadServiceNotFound
Looks up a localized string similar to No file upload service found on the Service Provider....
static string PushNotificationServiceNotFound
Looks up a localized string similar to No push-notification service found..
static string UnableToGetListOfMyDevices
Looks up a localized string similar to Unable to get list of my devices..
static string NotificationTokenRemovedTitle
Looks up a localized string similar to Token removed.
static string NotificationIdentityObsoletedBody
Looks up a localized string similar to Your identity has been obsoleted..
static string PepServiceNotFound
Looks up a localized string similar to No personal eventing service found..
static string ProvisioningServiceNotFound
Looks up a localized string similar to No provisioning service found on the Service Provider....
static string NotificationBalanceUpdatedTitle
Looks up a localized string similar to Balance updated.
static string UnableToConnectTo
Looks up a localized string similar to Unable to connect to {0}.
static string NotificationPetitionIdentityBody
Looks up a localized string similar to A new identity request is waiting..
static string NotificationIdentityApprovedBody
Looks up a localized string similar to Your identity was approved..
static string UnableToGetStateDiagram
Looks up a localized string similar to Unable to get state diagram report..
static string NotificationControlAccessBody
Looks up a localized string similar to Requested control access..
static string NotificationContractUpdatedTitle
Looks up a localized string similar to Contract updated.
static string NotificationPetitionIdentityTitle
Looks up a localized string similar to Identity request from {0}.
static string OperatorDoesNotSupportRegisteringNewAccounts
Looks up a localized string similar to The operator {0} does not support registration of new accounts...
static string ThingRegistryServiceNotFound
Looks up a localized string similar to No thing registry service found on the Service Provider....
static string NotificationContractUpdatedBody
Looks up a localized string similar to A contract was updated..
static string ConcentratorServiceNotFound
Looks up a localized string similar to No concentrator service found..
static string NotificationContractSignedTitle
Looks up a localized string similar to Contract signed.
static string NotificationTokenAddedBody
Looks up a localized string similar to Your wallet was updated..
static string PubSubServiceNotFound
Looks up a localized string similar to Messaging service not available.
static string NotificationBalanceUpdatedBody
Looks up a localized string similar to Your balance was updated..
static string NotificationControlAccessTitle
Looks up a localized string similar to Control request from {0}.
static string NotificationIdentityApprovedTitle
Looks up a localized string similar to Identity status updated.
static string LegalServiceNotFound
Looks up a localized string similar to No legal service found on the Service Provider....
static string NotificationChatTitle
Looks up a localized string similar to {0}.
static string NotificationIdentityCompromisedTitle
Looks up a localized string similar to Identity status updated.
static string NotificationTokenAddedTitle
Looks up a localized string similar to Token added.
static string Ok
Looks up a localized string similar to OK.
static string UnableToGetProfiling
Looks up a localized string similar to Unable to get profiling report..
static string NotificationChatBody
Looks up a localized string similar to Has sent you a message..
static string UnableToAuthenticateWith
Looks up a localized string similar to Unable to authenticate with {0}.
static string SomethingWentWrong
Looks up a localized string similar to Something went wrong.
static string NotificationPresenceAccessBody
Looks up a localized string similar to Requested access to your identity..
static string EDalerServiceNotFound
Looks up a localized string similar to No eDaler service found on the Service Provider....
static string NotificationPetitionSignatureTitle
Looks up a localized string similar to Signature request from {0}.
static string UnableToGetHistory
Looks up a localized string similar to Unable to get history report..
static string NotificationPetitionContractBody
Looks up a localized string similar to A new contract request is waiting..
static string SensorServiceNotFound
Looks up a localized string similar to No sensor service found..
static string DomainDoesNotFollowEncryptionPolicy
Looks up a localized string similar to {0} does not follow the ubiquitous encryption policy.
static string NotificationPresenceAccessTitle
Looks up a localized string similar to Access request from {0}.
static string UnableToConnect
Looks up a localized string similar to Unable to connect..
static string NotificationIdentityObsoletedTitle
Looks up a localized string similar to Identity status updated.
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
static string NotificationContractSignedBody
Looks up a localized string similar to A contract was signed..
static string CantConnectTo
Looks up a localized string similar to Can't connect to {0}.
static string InvalidIoTDiscoClaimUri
Looks up a localized string similar to Not a valid iotdisco claim URI..
Contains information about a contact.
Definition: ContactInfo.cs:22
static async Task< string > GetFriendlyName(CaseInsensitiveString RemoteId)
Gets the friendly name of a remote identity (Legal ID or Bare JID).
Definition: ContactInfo.cs:258
static async Task< ContactInfo?> FindByLegalId(string LegalId)
Finds information about a contact, given its Legal ID.
Definition: ContactInfo.cs:248
LegalIdentity? LegalIdentity
Legal Identity object.
Definition: ContactInfo.cs:82
bool? AllowSubscriptionFrom
Allow subscriptions from this contact
Definition: ContactInfo.cs:146
static Task< ContactInfo > FindByBareJid(string BareJid)
Finds information about a contact, given its Bare JID.
Definition: ContactInfo.cs:221
CaseInsensitiveString LegalId
Legal ID of contact.
Definition: ContactInfo.cs:72
Contains a local reference to a contract that the user has created or signed.
async Task SetContract(Contract Contract)
Sets a parsed contract.
Contains additional data about an invalid claim.
Captures the result of an application review returned from backend services.
string? Code
Optional machine readable code identifying the review reason.
string Message
The localized or raw message describing the review result.
Contains additional data about an invalid photo.
Contains a local reference to a KYC process.
Definition: KycReference.cs:22
string? CreatedIdentityId
The legal ID of the created identity (if any).
Definition: KycReference.cs:75
bool IsUnloading
Gets whether the service is being unloaded.
bool BeginLoad(bool IsResuming, CancellationToken CancellationToken)
Sets the IsLoading flag if the service isn't already loading.
void EndLoad(bool isLoaded)
Sets the IsLoading and IsLoaded flags and fires an event representing the current load state of the s...
bool IsResuming
If App is resuming service.
bool BeginUnload()
Sets the IsLoading flag if the service isn't already unloading.
void EndUnload()
Sets the IsLoading and IsLoaded flags and fires an event representing the current load state of the s...
bool IsLoaded
Gets whether the service is loaded.
Represents a filter decision for notification handling.
static NotificationFilterDecision None
Gets a decision that does not ignore any operation.
Platform-neutral intent describing how to route a notification.
string? EntityId
Gets or sets an entity identifier associated with the action.
string? Channel
Gets or sets the push channel identifier.
Dictionary< string, string > Extras
Gets or sets extra data used for routing.
Contains information about a push notification token.
PushMessagingService Service
Service issuing the token
Base class that references services in the app.
Definition: ServiceRef.cs:43
static IServiceProvider Provider
The service provider for the app. This is set before the app is started, and will be used to resolve ...
Definition: ServiceRef.cs:48
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:226
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:130
static INavigationService NavigationService
The navigation service for navigating between pages.
Definition: ServiceRef.cs:178
static IPopupService PopupService
Popup service for presenting application popups.
Definition: ServiceRef.cs:142
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:202
static ICryptoService CryptoService
Crypto service.
Definition: ServiceRef.cs:286
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:190
The view model to bind to for when displaying the applications page.
DateTime Created
When message was created
Definition: ChatMessage.cs:90
A page that displays a list of the current user's contacts.
The view model to bind to when displaying the list of contacts.
async Task MessageAddedAsync(ChatMessage Message)
External message has been received
async Task MessageUpdatedAsync(ChatMessage Message)
External message has been updated
Orchestrates the interactive KYC flow, covering navigation, validation, summary projection,...
Navigation arguments for onboarding flow. Scenario determines dynamic starting step.
Property[] ToProperties(IXmppService XmppService)
Converts the RegisterIdentityModel to an array of .
Prompts the user for a response of a presence subscription request.
Task< ReportOrBlockAction > Result
Result will be provided here.
Prompts the user for a response of a presence subscription request.
Task< ReportingReason?> Result
Result will be provided here.
Asks the user if it wants to remove an existing presence subscription request as well.
Task< bool?> Result
Result will be provided here. If dialog is cancelled, null is returned.
Prompts the user for a response of a presence subscription request.
Event arguments events when the current state of a state-machine has changed.
Event arguments for report callback methods.
Event arguments for token events.
Event arguments events when the variables of a state-machine has changed.
Event raised when a token has been created.
Definition: Created.cs:10
async Task< ReportEventArgs > GenerateHistoryReportAsync(string TokenId, ReportFormat Format)
Generates a history report of a state-machine belonging to a token.
Task< TokensEventArgs > GetTokensAsync()
Get tokens the account owns.
Task< TokensEventArgs > GetContractTokensAsync(string ContractId)
Get tokens created by a contract the account has access to.
Task< string[]> GetContractTokenReferencesAsync(string ContractId)
Get references to tokens created by a contract the account has access to.
override void Dispose()
IDisposable.Dispose
async Task< ReportEventArgs > GeneratePresentReportAsync(string TokenId, ReportFormat Format)
Generates a present report of a state-machine belonging to a token.
async Task< CurrentStateEventArgs > GetCurrentStateAsync(string TokenId)
Gets the current state of a state-machine belonging to a token.
async Task< CreationAttributesEventArgs > GetCreationAttributesAsync()
Gets attributes relevant for creating tokens on the broker.
async Task< ReportEventArgs > GenerateProfilingReportAsync(string TokenId, ReportFormat Format)
Generates a profiling report of a state-machine belonging to a token.
Task AddTextNoteAsync(string TokenId, string Note)
Adds a text note to a token. Notes attached to a token can be retrieved by calling GetEvents.
const string NamespaceNeuroFeatures
Namespace for Neuro-Features.
Task< TokenTotalsEventArgs > GetTotalsAsync()
Get totals of tokens the sender owns.
Task< string[]> GetTokenReferencesAsync()
Get references to tokens the account owns.
Task< TokenEvent[]> GetEventsAsync(string TokenId)
Get events registered for a token the account owns.
async Task< ReportEventArgs > GenerateStateDiagramAsync(string TokenId, ReportFormat Format)
Generates a state diagram of a state-machine belonging to a token.
async Task< Token > GetTokenAsync(string TokenId)
Gets a token, given its full ID.
Task AddXmlNoteAsync(string TokenId, string Note)
Adds a xml note to a token. Notes attached to a token can be retrieved by calling GetEvents.
Neuro-Feature Token
Definition: Token.cs:46
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.
object Decoded
Decoded object.
void AssertOk()
Asserts response is OK.
static string GetBody(string Html)
Extracts the contents of the BODY element in a HTML string.
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 readonly DateTime UnixEpoch
Unix Date and Time epoch, starting at 1970-01-01T00:00:00Z
Definition: JSON.cs:20
Contains a markdown document. This markdown document class supports original markdown,...
async Task< string > GeneratePlainText()
Generates Plain Text from the markdown text.
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:892
Class representing an event.
Definition: Event.cs:11
Filters incoming events and passes remaining events to a secondary event sink.
Definition: EventFilter.cs:11
IEventSink SecondarySink
Secondary event sink receiving the events passing the filter.
Definition: EventFilter.cs:243
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:14
static void Register(IEventSink EventSink)
Registers an event sink with the event log. Call Unregister(IEventSink) to unregister it,...
Definition: Log.cs:30
static void Warning(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs a warning event.
Definition: Log.cs:576
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:828
static bool Unregister(IEventSink EventSink)
Unregisters an event sink from the event log.
Definition: Log.cs:47
void Dispose()
IDisposable.Dispose()
Definition: LogObject.cs:1136
virtual Task DisposeAsync()
IDisposableAsync.DisposeAsync()
Definition: LogObject.cs:1144
Event sink sending events to a destination over the XMPP network.
const string NamespaceEventLogging
urn:xmpp:eventlog
Outputs sniffed data to Debug.
Definition: DebugSniffer.cs:10
Sniffer that stores events in memory.
void Warning(string Warning)
Called to inform the viewer of a warning state.
Definition: SnifferBase.cs:324
void Error(string Error)
Called to inform the viewer of an error state.
Definition: SnifferBase.cs:343
void TransmitText(string Text)
Called when text has been transmitted.
Definition: SnifferBase.cs:286
void Information(string Comment)
Called to inform the viewer of something.
Definition: SnifferBase.cs:305
void ReceiveText(string Text)
Called when text has been received.
Definition: SnifferBase.cs:267
Class implementing blocking (XEP-0191) and spam reporting (XEP-0377).
Definition: AbuseClient.cs:37
override void Dispose()
IDisposable.Dispose
Definition: AbuseClient.cs:118
async Task BlockJID(string JID, ReportingReason Reason, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Blocks a JID
Definition: AbuseClient.cs:303
The authentication failed because the initiating entity did not provide proper credentials,...
Implements an XMPP concentrator client interface.
override void Dispose()
Disposes of the extension.
Implements an XMPP concentrator server interface.
const string NamespaceConcentratorCurrent
Neuro-Foundation v1 namespace
Static class managing editable parameters in objects. Editable parameters are defined by using the at...
Definition: Parameters.cs:26
Contains a reference to an attachment assigned to a legal object.
Definition: Attachment.cs:10
string FileName
Filename of attachment.
Definition: Attachment.cs:57
string ContentType
Internet Content Type of binary attachment.
Definition: Attachment.cs:48
Contains the definition of a contract
Definition: Contract.cs:22
string ForMachinesLocalName
Local name used by the root node of the machine-readable contents of the contract (ForMachines).
Definition: Contract.cs:294
string ContractId
Contract identity
Definition: Contract.cs:65
string ForMachinesNamespace
Namespace used by the root node of the machine-readable contents of the contract (ForMachines).
Definition: Contract.cs:289
Adds support for legal identities, smart contracts and signatures to an XMPP client.
Task< bool > ImportKeys(string Xml)
Imports keys
async Task GenerateNewKeys()
Generates new keys for the contracts clients.
Task< LegalIdentity > ApplyAsync(Property[] Properties)
Applies for a legal identity to be registered.
Task SelectPeerReviewServiceAsync(string Provider, string ServiceId)
Selects a service provider for peer review. This needs to be done before requesting the trust provide...
Task PetitionIdentityResponseAsync(string LegalId, string PetitionId, string RequestorFullJid, bool Response)
Sends a response to a petition for information about a legal identity. When a petition is received,...
bool? ValidateSignature(LegalIdentity Identity, byte[] Data, byte[] Signature)
Validates a signature of binary data.
Task< KeyValuePair< string, TemporaryFile > > GetAttachmentAsync(string Url, SignWith SignWith)
Gets an attachment from a Trust Provider
Task< LegalIdentity > ObsoleteLegalIdentityAsync(string LegalIdentityId)
Obsoletes one of the legal identities of the account, given its ID.
Task SendContractProposal(Contract Contract, string Role, string To)
Sends a contract proposal to a recipient. If the contract contains encrypted parameters,...
Task< ServiceProviderWithLegalId[]> GetPeerReviewIdServiceProvidersAsync()
Gets available service providers who can help review an ID application.
Task PetitionIdentityAsync(string LegalId, string PetitionId, string Purpose)
Sends a petition to the owner of a legal identity, to access the information in the identity....
async Task< IdApplicationAttributesEventArgs > GetIdApplicationAttributesAsync()
Gets attributes relevant for application for legal identities on the broker.
Task< bool > LoadKeys(bool CreateIfNone)
Loads keys from the underlying persistence layer.
Task< LegalIdentity > GetLegalIdentityAsync(string LegalIdentityId)
Gets legal identity registered with the account.
Task AuthorizeAccessToIdAsync(string LegalId, string RemoteId, bool Authorized)
Authorizes access to (or revokes access to) a Legal ID of the caller.
Task PetitionSignatureResponseAsync(string LegalId, byte[] Content, byte[] Signature, string PetitionId, string RequestorFullJid, bool Response)
Sends a response to a petition for a signature by the client. When a petition is received,...
Task< bool > HasPrivateKey(LegalIdentity Identity)
Checks if the private key of a legal identity is available. Private keys are required to be able to s...
Task PetitionContractAsync(string ContractId, string PetitionId, string Purpose)
Sends a petition to the parts of a smart contract, to access the information in the contract....
Task< LegalIdentity > CompromisedLegalIdentityAsync(string LegalIdentityId)
Reports as Compromised one of the legal identities of the account, given its ID.
Task< Contract > GetContractAsync(string ContractId)
Gets a contract
Task< IdentityValidationEventArgs > ValidateAsync(LegalIdentity Identity)
Validates a legal identity.
Task< byte[]> SignAsync(byte[] Data, SignWith SignWith)
Signs binary data with the corresponding private key.
const string NamespaceOnboarding
http://waher.se/schema/Onboarding/v1.xsd
Task PetitionContractResponseAsync(string ContractId, string PetitionId, string RequestorFullJid, bool Response)
Sends a response to a petition to access a smart contract. When a petition for a contract is received...
Task ReadyForApprovalAsync(string LegalIdentityId)
Marks an Identity as Ready for Approval. Call this after necessary attachments have been added....
async Task< LegalIdentity > AddPeerReviewIDAttachment(LegalIdentity Identity, LegalIdentity ReviewerLegalIdentity, byte[] PeerSignature)
Adds an attachment to a legal identity with information about a peer review of the identity.
Task< LegalIdentity[]> GetLegalIdentitiesAsync()
Gets legal identities registered with the account.
async Task< string > ExportKeys()
Exports Keys to XML.
Task< Contract > SignContractAsync(Contract Contract, string Role, bool Transferable)
Signs a contract
override void Dispose()
Disposes of the extension.
Task< string[]> GetSignedContractReferencesAsync()
Get references to contracts the account has signed.
Task PetitionPeerReviewIDAsync(string LegalId, LegalIdentity Identity, string PetitionId, string Purpose)
Sends a petition to a third party to peer review a new legal identity. The petition is not guaranteed...
async Task EnableE2eEncryption()
Enables the keys of the Contracts Client to be used for End-to-End Encrypted communication over the X...
Task< Contract > CreateContractAsync(XmlElement ForMachines, HumanReadableText[] ForHumans, Role[] Roles, Part[] Parts, Parameter[] Parameters, ContractVisibility Visibility, ContractParts PartsMode, Duration? Duration, Duration? ArchiveRequired, Duration? ArchiveOptional, DateTime? SignAfter, DateTime? SignBefore, bool CanActAsTemplate)
Creates a new contract.
Task< Contract > DeleteContractAsync(string ContractId)
Deletes a contract
Task< string[]> GetCreatedContractReferencesAsync()
Get references to contracts the account has created.
async Task< LegalIdentity > UploadLegalIdAttachmentAsync(string LegalId, string FileName, byte[] Data, string ContentType)
Uploads an attachment to a Legal Identity application.
Task< Contract > ObsoleteContractAsync(string ContractId)
Obsoletes a contract
static readonly string[] NamespacesLegalIdentities
Namespaces supported for legal identities.
const string NamespaceLegalIdentitiesCurrent
Current namespace for legal identities.
string Code
Machine-readable code corresponding to the first error message.
Event arguments for events where a client URL needs to be displayed when performing a petition.
string ClientUrl
URL client needs to open to complete the peer review.
LegalIdentity RequestorIdentity
Legal Identity of requesting entity.
Represents an invalidated claim.
Definition: InvalidClaim.cs:7
string ReasonCode
A machine-readable code for the reason for invalidating the claim. (Each service can define its own r...
Definition: InvalidClaim.cs:51
string ReasonLanguage
ISO code of language used for Reason.
Definition: InvalidClaim.cs:45
Represents an invalidated photo.
Definition: InvalidPhoto.cs:7
string ReasonCode
A machine-readable code for the reason for invalidating the photo. (Each service can define its own r...
Definition: InvalidPhoto.cs:51
string FileName
File name of Invalidated photo.
Definition: InvalidPhoto.cs:30
string ReasonLanguage
ISO code of language used for Reason.
Definition: InvalidPhoto.cs:45
Abstract base class for contractual parameters
Definition: Parameter.cs:17
Class defining a part in a contract
Definition: Part.cs:30
Class defining a role
Definition: Role.cs:7
Contains information about a service provider.
Abstract base class of signatures
Definition: Signature.cs:10
Implements an XMPP control client interface.
Task GetForm(string To, string Language, params ThingReference[] Nodes)
Gets a control form.
Event arguments for responses to IQ queries.
bool Ok
If the response is an OK result response (true), or an error response (false).
XmppException StanzaError
Any stanza error returned.
Event arguments for message events.
string Id
ID attribute of message stanza.
string From
From where the message was received.
string FromBareJID
Bare JID of resource sending the message.
bool Ok
If the response is an OK result response (true), or an error response (false).
string To
To whom the message was sent.
XmlElement Content
Content of the message. For messages that are processed by registered message handlers,...
XmppException StanzaError
Any stanza error returned.
Event arguments for presence events.
bool Ok
If the response is an OK result response (true), or an error response (false).
string FromBareJID
Bare JID of resource sending the presence.
virtual string NickName
NickName, if available, as defined in XEP-0172. Can be sent in presence subscription requests,...
async Task Decline()
Declines a subscription or unsubscription request.
async Task Accept()
Accepts a subscription or unsubscription request.
XmppClient Client
XMPP Client. Is null if event raised by a component.
Task< string > GetJwtTokenAsync(int Seconds)
Gets a JWT token from the server to which the client is connceted. The JWT token encodes the current ...
Class managing HTTP File uploads, as defined in XEP-0363.
static ? long FindMaxFileSize(XmppClient Client, ServiceDiscoveryEventArgs e)
Finds the maximum file size supported by the file upload service.
Client managing the Personal Eventing Protocol (XEP-0163). https://xmpp.org/extensions/xep-0163....
Definition: PepClient.cs:19
void RegisterHandler(Type PersonalEventType, EventHandlerAsync< PersonalEventNotificationEventArgs > Handler)
Registers an event handler of a specific type of personal events.
Definition: PepClient.cs:345
override void Dispose()
Disposes of the extension.
Definition: PepClient.cs:55
PubSubClient PubSubClient
PubSubClient used for the Personal Eventing Protocol. Use this client to perform administrative tasks...
Definition: PepClient.cs:95
bool UnregisterHandler(Type PersonalEventType, EventHandlerAsync< PersonalEventNotificationEventArgs > Handler)
Unregisters an event handler of a specific type of personal events.
Definition: PepClient.cs:380
Abstract base class for all meta-data tags.
Definition: MetaDataTag.cs:10
Implements an XMPP provisioning client interface.
Task CanControlResponseDevice(string JID, string RemoteJID, string Key, bool CanControl, string[] ParameterNames, string Token, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Control" question, based on a device token.
Task GetDevices(int Offset, int MaxCount, EventHandlerAsync< SearchResultEventArgs > Callback, object State)
Gets devices owned by the caller.
Task CanReadResponseService(string JID, string RemoteJID, string Key, bool CanRead, FieldType FieldTypes, string[] FieldNames, string Token, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Read" question, based on a service token.
Task CanReadResponseUser(string JID, string RemoteJID, string Key, bool CanRead, FieldType FieldTypes, string[] FieldNames, string Token, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Read" question, based on a user token.
Task CanReadResponseAll(string JID, string RemoteJID, string Key, bool CanRead, FieldType FieldTypes, string[] FieldNames, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Read" question, for all future requests.
static readonly string[] NamespacesProvisioningDevice
Namespaces supported for provisioning devices.
Task CanControlResponseCaller(string JID, string RemoteJID, string Key, bool CanControl, string[] ParameterNames, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Control" question, based on the JID of the caller.
static readonly string[] NamespacesProvisioningOwner
Namespaces supported for provisioning owners.
Task CanControlResponseService(string JID, string RemoteJID, string Key, bool CanControl, string[] ParameterNames, string Token, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Control" question, based on a service token.
Task DeleteDeviceRules()
Deletes te device rules of all owned devices.
string ProvisioningServerAddress
Provisioning server XMPP address.
Task CanReadResponseCaller(string JID, string RemoteJID, string Key, bool CanRead, FieldType FieldTypes, string[] FieldNames, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Read" question, based on the JID of the caller.
Task CanReadResponseDomain(string JID, string RemoteJID, string Key, bool CanRead, FieldType FieldTypes, string[] FieldNames, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Read" question, based on the domain of the caller.
override void Dispose()
Disposes of the extension.
Task CanControlResponseUser(string JID, string RemoteJID, string Key, bool CanControl, string[] ParameterNames, string Token, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Control" question, based on a user token.
Task CanControlResponseDomain(string JID, string RemoteJID, string Key, bool CanControl, string[] ParameterNames, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Control" question, based on the domain of the caller.
Task CanReadResponseDevice(string JID, string RemoteJID, string Key, bool CanRead, FieldType FieldTypes, string[] FieldNames, string Token, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Read" question, based on a device token.
Task CanControlResponseAll(string JID, string RemoteJID, string Key, bool CanControl, string[] ParameterNames, IThingReference Node, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Can Control" question, for all future requests.
Task GetCertificate(string Token, EventHandlerAsync< CertificateEventArgs > Callback, object State)
Gets the certificate the corresponds to a token. This certificate can be used to identify services,...
Task IsFriendResponse(string JID, string RemoteJID, string Key, bool IsFriend, RuleRange Range, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Sends a response to a previous "Is Friend" question.
static readonly string[] NamespacesProvisioningToken
Namespaces supported for provisioning tokens.
Filters things with a named numeric-valued tag equal to a given value.
Abstract base class for all search operators.
Filters things with a named string-valued tag equal to a given value.
Contains information about a thing in a search result.
Implements an XMPP thing registry client interface.
static bool TryDecodeIoTDiscoURI(string DiscoUri, out IEnumerable< SearchOperator > Operators)
Decodes an IoTDisco URI.
static bool TryDecodeIoTDiscoClaimURI(string DiscoUri, out MetaDataTag[] Tags)
Tries to decode an IoTDisco Claim URI (subset of all possible IoTDisco URIs).
static bool IsIoTDiscoDirectURI(string DiscoUri)
Checks if a URI is a direct reference URI.
static bool IsIoTDiscoClaimURI(string DiscoUri)
Checks if a URI is a claim URI.
override void Dispose()
Disposes of the extension.
Task Disown(string ThingJid, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Disowns a thing, so that it can be claimed by another.
static bool IsIoTDiscoSearchURI(string DiscoUri)
Checks if a URI is a search URI.
Task Mine(MetaDataTag[] MetaDataTags, EventHandlerAsync< NodeResultEventArgs > Callback, object State)
Claims a thing.
static readonly string[] NamespacesDiscovery
Namespaces supported for discovery.
Task Search(int Offset, int MaxCount, SearchOperator[] SearchOperators, EventHandlerAsync< SearchResultEventArgs > Callback, object State)
Searches for publically available things in the thing registry.
Event arguments for items callback events.
ResultPage Page
Pagination information, if available, null otherwise.
Event arguments for node callback events.
Definition: NodeEventArgs.cs:9
Contains information about the configuration of a node.
Client managing communication with a Publish/Subscribe component. https://xmpp.org/extensions/xep-006...
Definition: PubSubClient.cs:20
Task DeleteNode(string Name, EventHandlerAsync< NodeEventArgs > Callback, object State)
Deletes a node.
Task GetLatestItems(string NodeName, int Count, EventHandlerAsync< ItemsEventArgs > Callback, object State)
Gets the latest items from a node.
Task PurgeNode(string Name, EventHandlerAsync< NodeEventArgs > Callback, object State)
Purges a node (deletes all items persisted on the node).
Task GetItems(string NodeName, EventHandlerAsync< ItemsEventArgs > Callback, object State)
Gets items from a node.
Task CreateNode(string Name, EventHandlerAsync< NodeEventArgs > Callback, object State)
Creates a node on the server.
Task Retract(string Node, string ItemId, EventHandlerAsync< IqResultEventArgs > Callback, object State)
Retracts an item from a node.
const string NamespaceDelayedDelivery
urn:xmpp:delay (XEP-0203)
Definition: PubSubClient.cs:44
const string NamespacePubSub
http://jabber.org/protocol/pubsub
Definition: PubSubClient.cs:24
string ComponentAddress
Publish/Subscribe component address.
Task Subscribe(string NodeName, EventHandlerAsync< SubscriptionEventArgs > Callback, object State)
Subscribes to a node.
Task Unsubscribe(string NodeName, EventHandlerAsync< SubscriptionEventArgs > Callback, object State)
Unsubscribes from a node.
Task Publish(string Node, EventHandlerAsync< ItemResultEventArgs > Callback, object State)
Publishes an item on a node.
Represents a published item.
Definition: PubSubItem.cs:12
Contains options for a node subscription
async Task NewTokenAsync(string Token, PushMessagingService Service, ClientType ClientType)
Reports a new push token to the server.
const string MessagePushNamespace
http://waher.se/Schema/PushNotification.xsd
async Task AddRuleAsync(MessageType MessageType, string LocalName, string Namespace, string Channel, string MessageVariable, string PatternMatchingScript, string ContentScript)
Adds a push notification rule to the client account.
async Task ClearRulesAsync()
Clears available push notification rules for the client.
Class redirecting sniffer output to a remote client.
Contains information about a restricted query, as deinfed in XEP-0059: Result Set Management
Contains information about a result page, as deinfed in XEP-0059: Result Set Management
Definition: ResultPage.cs:11
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
Implements an XMPP sensor client interface.
Definition: SensorClient.cs:21
Task< SensorDataClientRequest > RequestReadout(string Destination, FieldType Types)
Requests a sensor data readout.
override void Dispose()
Disposes of the extension.
Contains information about an identity of an entity.
Definition: Identity.cs:11
Contains information about an item of an entity.
Definition: Item.cs:11
bool HasFeature(string Feature)
Checks if the remote entity supports a specific feature.
bool HasAnyFeature(params string[] Features)
Checks if the remote entity supports any of a set of features.
Access cannot be granted because an existing resource exists with the same name or address; the assoc...
The entity has attempted to send XML stanzas or other outbound data before the stream has been authen...
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:58
Task RequestRevokePresenceSubscription(string BareJid)
Requests a previous presence subscription request revoked.
Definition: XmppClient.cs:5060
Task ChangePassword(string NewPassword)
Changes the password of the current user.
Definition: XmppClient.cs:4299
Task< ServiceItemsDiscoveryEventArgs > ServiceItemsDiscoveryAsync(string To)
Performs an asynchronous service items discovery request
Definition: XmppClient.cs:6298
XmppState State
Current state of connection.
Definition: XmppClient.cs:985
Task RemoveRosterItem(string BareJID)
Removes an item from the roster.
Definition: XmppClient.cs:4680
Task SendMessage(MessageType Type, string To, string CustomXml, string Body, string Subject, string Language, string ThreadId, string ParentThreadId)
Sends a simple chat message
Definition: XmppClient.cs:5447
async Task DisposeAsync()
Closes the connection and disposes of all resources.
Definition: XmppClient.cs:1145
Task< ServiceDiscoveryEventArgs > ServiceDiscoveryAsync(string To)
Performs an asynchronous service discovery request
Definition: XmppClient.cs:6060
Task RequestPresenceSubscription(string BareJid)
Requests subscription of presence information from a contact.
Definition: XmppClient.cs:4969
async Task< XmlElement > GetPrivateXmlElementAsync(string LocalName, string Namespace)
Gets an XML element from the Private XML Storage for the current account.
Definition: XmppClient.cs:7500
Task AddRosterItem(RosterItem Item)
Adds an item to the roster. If an item with the same Bare JID is found in the roster,...
Definition: XmppClient.cs:4591
async Task< XmlElement > IqSetAsync(string To, string Xml)
Performs an asynchronous IQ Set request/response operation.
Definition: XmppClient.cs:4101
Task RequestPresenceUnsubscription(string BareJid)
Requests unssubscription of presence information from a contact.
Definition: XmppClient.cs:5033
Task SendServiceDiscoveryRequest(string To, EventHandlerAsync< ServiceDiscoveryEventArgs > Callback, object State)
Sends a service discovery request
Definition: XmppClient.cs:5863
const string NamespaceQuickLogin
http://waher.se/Schema/QL.xsd
Definition: XmppClient.cs:172
Task Connect()
Connects the client.
Definition: XmppClient.cs:641
async Task SetPresenceAsync(Availability Availability, params KeyValuePair< string, string >[] Status)
Sets the presence of the connection. Add a CustomPresenceXml event handler to add custom presence XML...
Definition: XmppClient.cs:4936
void AllowRegistration()
If registration of a new account is allowed. Requires a password. Having a password hash is not suffi...
Definition: XmppClient.cs:3572
Task SendServiceItemsDiscoveryRequest(string To, EventHandlerAsync< ServiceItemsDiscoveryEventArgs > Callback, object State)
Sends a service items discovery request
Definition: XmppClient.cs:6118
RosterItem GetRosterItem(string BareJID)
Gets a roster item.
Definition: XmppClient.cs:4571
Class containing credentials for an XMPP client connection.
const int DefaultPort
Default XMPP Server port.
virtual void Dispose()
Disposes of the extension.
XmppClient Client
XMPP Client.
Represents a case-insensitive string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:1442
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:59
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
Static interface for ledger persistence. In order to work, a ledger provider has to be assigned to it...
Definition: Ledger.cs:14
static void Register(ILedgerProvider LedgerProvider)
Registers a ledger provider for use from the static Ledger class, throughout the lifetime of the appl...
Definition: Ledger.cs:25
static bool HasProvider
If a ledger provider is registered.
Definition: Ledger.cs:105
static void StartListeningToDatabaseEvents()
Makes the ledger listen on database events. Each call to StartListeningToDatabaseEvents must be follo...
Definition: Ledger.cs:273
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static void SetModuleParameter(string Name, object Value)
Sets a module parameter. This parameter value will be accessible to modules when they are loaded.
Definition: Types.cs:584
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
static async Task< bool > SetAsync(string Key, string Value)
Sets a string-valued setting.
Euler's number.
Definition: E.cs:12
Current date and time.
Definition: Now.cs:12
Static class containing predefined JWT claim names.
Definition: JwtClaims.cs:10
const string Issuer
Issuer of the JWT
Definition: JwtClaims.cs:14
const string Subject
Subject of the JWT (the user)
Definition: JwtClaims.cs:19
const string ClientId
Client identifier
Definition: JwtClaims.cs:154
const string ExpirationTime
Time after which the JWT expires
Definition: JwtClaims.cs:29
Contains a reference to a thing
Task ApplyApplicationReviewAsync(KycReference Reference, ApplicationReview Review)
Persists review metadata from the provider.
Interface for the redesigned notification service.
The TAG Profile is the heart of the digital identity for a specific user/device. Use this instance to...
Definition: ITagProfile.cs:18
string? NeuroFeaturesJid
The XMPP server's Neuro-Features service JID.
Definition: ITagProfile.cs:152
RegistrationStep Step
This profile's current registration step.
Definition: ITagProfile.cs:172
bool IsCompleteOrWaitingForValidation()
Returns true if the registration process for this ITagProfile is either fully complete or is just awa...
string? HttpFileUploadJid
The XMPP server's file upload Jid.
Definition: ITagProfile.cs:132
Task SetLegalIdentity(LegalIdentity? Identity, bool RemoveOldAttachments)
Sets the legal identity of the profile.
void GoToStep(RegistrationStep NewStep, bool SupressEvent=false)
Changes the current onboarding step.
long HttpFileUploadMaxSize
The XMPP server's max size for file uploads.
Definition: ITagProfile.cs:137
string? Account
The account name for this profile
Definition: ITagProfile.cs:102
void SetDomain(string DomainName, bool DefaultXmppConnectivity, string Key, string Secret)
Set the domain name to connect to.
void SetXmppPasswordNeedsUpdating(bool Value)
Sets the local flag for if xmpp password needs updating.
string? RegistryJid
The Thing Registry JID
Definition: ITagProfile.cs:122
string? EDalerJid
The XMPP server's eDaler service JID.
Definition: ITagProfile.cs:147
Task SetIdentityApplication(LegalIdentity? Identity, bool RemoveOldAttachments)
Sets the legal identity of the profile.
bool ShouldCreateClient()
Returns true if the registration process for this ITagProfile has an account but not a legal id,...
LegalIdentity? IdentityApplication
Any current Identity application.
Definition: ITagProfile.cs:227
string? LogJid
The XMPP server's log Jid.
Definition: ITagProfile.cs:142
string? ApiKey
API Key, for creating new account.
Definition: ITagProfile.cs:62
string? LegalJid
The Jabber Legal JID for this user/profile.
Definition: ITagProfile.cs:117
string? ApiSecret
API Secret, for creating new account.
Definition: ITagProfile.cs:67
bool DefaultXmppConnectivity
If connecting to the domain can be done using default parameters (host=domain, default c2s port).
Definition: ITagProfile.cs:57
bool SupportsPushNotification
If Push Notification is supported by server.
Definition: ITagProfile.cs:167
string? XmppPasswordHash
A hash of the current XMPP password.
Definition: ITagProfile.cs:107
Task ClearLegalIdentity()
Revert the Set LegalIdentity
void SetFileUploadParameters(string httpFileUploadJid, long maxSize)
Used during XMPP service discovery. Sets the file upload parameters.
void CheckContractReference(ContractReference Reference)
Checks if Tag Profile properties need to be changed, with regards to a current ContractReference obje...
void ClearAll()
Clears the entire profile.
string? XmppPasswordHashMethod
The hash method used for hashing the XMPP password.
Definition: ITagProfile.cs:112
string? ProvisioningJid
The XMPP server's provisioning Jid.
Definition: ITagProfile.cs:127
LegalIdentity? LegalIdentity
The legal identity of the current user/profile.
Definition: ITagProfile.cs:222
bool NeedsUpdating()
Returns true if the current ITagProfile needs to have its values updated, false otherwise.
void SetAccount(string AccountName, string ClientPasswordHash, string ClientPasswordHashMethod)
Set the account name and password for a new account.
string? Domain
The domain this profile is connected to.
Definition: ITagProfile.cs:52
string? PubSubJid
The XMPP server's PubSub JID.
Definition: ITagProfile.cs:157
bool GetXmppPasswordNeedsUpdating()
Returns true if the current ITagProfile needs to have its Xmpp Password updated, false otherwise.
string BareJid
The Bare Jid of the current connection, or null.
Definition: IXmppService.cs:82
Interface for asynchronously disposable objects.
Task DisposeAsync()
Disposes of the object, asynchronously.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
Task Flush()
Persists any pending changes.
Interface for thing references.
Definition: ImplTypes.g.cs:58
NotificationAction
Actions that can be routed from notifications.
NotificationSource
Describes the source producing a notification.
RegistrationStep
The different steps of a TAG Profile registration journey.
class Photo(byte[] Binary, int Rotation, Attachment? Attachment)
Class containing information about a photo.
Definition: Photo.cs:10
class OptionsTransaction(string TransactionId)
Maintains the status of an ongoing retrieval of payment options.
class PaymentTransaction(string TransactionId, string Currency)
Maintains the status of an ongoing payment transaction.
ReportOrBlockAction
How to continue when rejecting a subscription request.
PresenceRequestAction
How to respond to a presence subscription request.
class RegistrationPageMessage(RegistrationStep Step)
RegistrationPage view change message
Definition: Messages.cs:9
ReportFormat
Desired report format
Definition: ReportFormat.cs:7
Action
The Action field indicates the action performed by the Reporting-MTA as a result of its attempt to de...
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.
delegate Task EventHandlerAsync(object Sender, EventArgs e)
Asynchronous version of EventArgs.
EventType
Type of event.
Definition: EventType.cs:7
BinaryPresentationMethod
How binary data is to be presented.
ReportingReason
Reason for blocking.
Definition: AbuseClient.cs:16
IdentityState
Lists recognized legal identity states.
SignWith
Options on what keys to use when signing data.
Definition: Enumerations.cs:82
ContractParts
How the parts of the contract are defined.
Definition: Part.cs:9
ContractVisibility
Visibility types for contracts.
Definition: Enumerations.cs:56
IdentityStatus
Validation Status of legal identity
RuleRange
Range of a rule change
Definition: RuleRange.cs:7
ClientType
Type of client requesting notification.
Definition: ClientType.cs:7
PushMessagingService
Push messaging service used.
Availability
Resource availability.
Definition: Availability.cs:7
QoSLevel
Quality of Service Level for asynchronous messages. Support for QoS Levels must be supported by the r...
Definition: QoSLevel.cs:8
SubscriptionState
State of a presence subscription.
Definition: RosterItem.cs:16
MessageType
Type of message received.
Definition: MessageType.cs:7
XmppState
State of XMPP connection.
Definition: XmppState.cs:7
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:15
FieldType
Field Type flags
Definition: FieldType.cs:10
Definition: App.xaml.cs:4
Represents a duration value, as defined by the xsd:duration data type: http://www....
Definition: Duration.cs:14