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_LOG_REMOTE
3//#define DEBUG_DB_REMOTE
4
5using EDaler;
6using EDaler.Uris;
7using Mopups.Services;
24using NeuroFeatures;
26using System.ComponentModel;
27using System.Diagnostics.CodeAnalysis;
28using System.Globalization;
29using System.Reflection;
30using System.Text;
31using System.Xml;
32using Waher.Content;
36using Waher.Events;
63using Waher.Things;
65
67{
74 [Singleton]
75 internal sealed class XmppService : LoadableService, IXmppService, IDisposable
76 {
77 //private bool isDisposed;
78 private XmppClient? xmppClient;
79 private ContractsClient? contractsClient;
80 private HttpFileUploadClient? fileUploadClient;
81 private ThingRegistryClient? thingRegistryClient;
82 private ProvisioningClient? provisioningClient;
83 private ControlClient? controlClient;
84 private SensorClient? sensorClient;
85 private ConcentratorClient? concentratorClient;
86 private EDalerClient? eDalerClient;
87 private NeuroFeaturesClient? neuroFeaturesClient;
88 private PushNotificationClient? pushNotificationClient;
89 private AbuseClient? abuseClient;
90 private PepClient? pepClient;
91 private HttpxClient? httpxClient;
92 private Timer? reconnectTimer;
93 private string? domainName;
94 private string? accountName;
95 private string? passwordHash;
96 private string? passwordHashMethod;
97 private bool xmppConnected = false;
98 private DateTime xmppLastStateChange = DateTime.MinValue;
99 private readonly InMemorySniffer? sniffer = new(250);
100 private bool isCreatingClient;
101 private EventFilter? xmppFilteredEventSink;
102 private string? token = null;
103 private DateTime tokenCreated = DateTime.MinValue;
104#if DEBUG_XMPP_REMOTE || DEBUG_LOG_REMOTE || DEBUG_DB_REMOTE
105 private const string debugRecipient = ""; // TODO: Set JID of recipient of debug messages.
106#endif
107#if DEBUG_XMPP_REMOTE || DEBUG_DB_REMOTE
108 private RemoteSniffer? debugSniffer = null;
109#endif
110#if DEBUG_LOG_REMOTE
111 private EventFilter? debugEventSink = null;
112#endif
113
114 #region Creation / Destruction
115
116 public XmppService()
117 {
118 }
119
120 private async Task CreateXmppClient()
121 {
122 if (this.isCreatingClient)
123 return;
124
125 try
126 {
127 this.isCreatingClient = true;
128
129 if (!this.XmppParametersCurrent() || this.XmppStale())
130 {
131 if (this.xmppClient is not null)
132 await this.DestroyXmppClient();
133
134 this.domainName = ServiceRef.TagProfile.Domain;
135 this.accountName = ServiceRef.TagProfile.Account;
136 this.passwordHash = ServiceRef.TagProfile.XmppPasswordHash;
137 this.passwordHashMethod = ServiceRef.TagProfile.XmppPasswordHashMethod;
138
139 string? HostName;
140 int PortNumber;
141 bool IsIpAddress;
142
144 {
145 HostName = this.domainName;
146 PortNumber = XmppCredentials.DefaultPort;
147 IsIpAddress = false;
148 }
149 else
150 {
151 (HostName, PortNumber, IsIpAddress) = await ServiceRef.NetworkService.LookupXmppHostnameAndPort(this.domainName!);
152
153 if (HostName == this.domainName && PortNumber == XmppCredentials.DefaultPort)
154 {
155 ServiceRef.TagProfile.SetDomain(this.domainName, true, ServiceRef.TagProfile.ApiKey ?? string.Empty,
156 ServiceRef.TagProfile.ApiSecret ?? string.Empty);
157 }
158 }
159
160 this.xmppLastStateChange = DateTime.Now;
161 this.xmppConnected = false;
162
163 Assembly AppAssembly = App.Current!.GetType().Assembly;
164
165 if (string.IsNullOrEmpty(this.passwordHashMethod))
166 {
167 this.xmppClient = new XmppClient(HostName, PortNumber, this.accountName, this.passwordHash,
168 Constants.LanguageCodes.Default, AppAssembly, this.sniffer);
169 }
170 else
171 {
172 this.xmppClient = new XmppClient(HostName, PortNumber, this.accountName, this.passwordHash, this.passwordHashMethod,
173 Constants.LanguageCodes.Default, AppAssembly, this.sniffer);
174 }
175
176#if DEBUG_XMPP_REMOTE || DEBUG_LOG_REMOTE || DEBUG_DB_REMOTE
177 if (!string.IsNullOrEmpty(debugRecipient))
178 {
179#endif
180#if DEBUG_XMPP_REMOTE || DEBUG_DB_REMOTE
181 this.debugSniffer = new RemoteSniffer(debugRecipient, DateTime.MaxValue, this.xmppClient, this.xmppClient,
183#endif
184#if DEBUG_XMPP_REMOTE
185 this.xmppClient.Add(this.debugSniffer);
186#endif
187#if DEBUG_LOG_REMOTE
188 if (this.debugEventSink is not null)
189 {
190 Log.Unregister(this.debugEventSink);
191 this.debugEventSink?.Dispose();
192 this.debugEventSink = null;
193 }
194
195 this.debugEventSink = new EventFilter("Debug Event Filter",
196 new XmppEventSink("Debug Event Sink", this.xmppClient, debugRecipient, false),
197 EventType.Informational, (Event) =>
198 {
199 if (this.xmppClient is null || this.xmppClient.State != XmppState.Connected)
200 return false;
201
202 return string.IsNullOrEmpty(Event.StackTrace) || !Event.StackTrace.Contains("XmppEventSink");
203 });
204
205 Log.Register(this.debugEventSink);
206#endif
207#if DEBUG_DB_REMOTE
208 if (!Ledger.HasProvider)
209 {
210 XmlFileLedger XmlFileLedger = new(new RemoteLedgerWriter());
212
213 await XmlFileLedger.Start();
214
216 }
217#endif
218#if DEBUG_XMPP_REMOTE || DEBUG_LOG_REMOTE || DEBUG_DB_REMOTE
219 }
220#endif
221
222 this.xmppClient.RequestRosterOnStartup = false;
223 this.xmppClient.TrustServer = !IsIpAddress;
224 this.xmppClient.AllowCramMD5 = false;
225 this.xmppClient.AllowDigestMD5 = false;
226 this.xmppClient.AllowPlain = false;
227 this.xmppClient.AllowEncryption = true;
228 this.xmppClient.AllowScramSHA1 = true;
229 this.xmppClient.AllowScramSHA256 = true;
230 this.xmppClient.AllowQuickLogin = true;
231
232 this.xmppClient.RequestRosterOnStartup = true;
233 this.xmppClient.OnStateChanged += this.XmppClient_StateChanged;
234 this.xmppClient.OnConnectionError += this.XmppClient_ConnectionError;
235 this.xmppClient.OnError += this.XmppClient_Error;
236 this.xmppClient.OnChatMessage += this.XmppClient_OnChatMessage;
237 this.xmppClient.OnNormalMessage += this.XmppClient_OnNormalMessage;
238 this.xmppClient.OnPresenceSubscribe += this.XmppClient_OnPresenceSubscribe;
239 this.xmppClient.OnPresenceUnsubscribed += this.XmppClient_OnPresenceUnsubscribed;
240 this.xmppClient.OnRosterItemAdded += this.XmppClient_OnRosterItemAdded;
241 this.xmppClient.OnRosterItemUpdated += this.XmppClient_OnRosterItemUpdated;
242 this.xmppClient.OnRosterItemRemoved += this.XmppClient_OnRosterItemRemoved;
243 this.xmppClient.OnPresence += this.XmppClient_OnPresence;
244
245 this.xmppClient.RegisterMessageHandler("Delivered", ContractsClient.NamespaceOnboarding, this.TransferIdDelivered, true);
246 this.xmppClient.RegisterMessageHandler("clientMessage", ContractsClient.NamespaceLegalIdentitiesCurrent, this.ClientMessage, true);
247
248 this.xmppFilteredEventSink = new EventFilter("XMPP Event Filter",
249 new XmppEventSink("XMPP Event Sink", this.xmppClient, ServiceRef.TagProfile.LogJid, false),
250 EventType.Error);
251
252 // Add extensions before connecting
253
254 this.abuseClient = new AbuseClient(this.xmppClient);
255
256 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LegalJid))
257 {
258 this.contractsClient = new ContractsClient(this.xmppClient, ServiceRef.TagProfile.LegalJid);
259 this.RegisterContractsEventHandlers();
260
261 if (!await this.contractsClient.LoadKeys(false))
262 {
264 {
265 Log.Alert("Regeneration of keys not permitted at this time.",
266 string.Empty, string.Empty, string.Empty, EventLevel.Major, string.Empty, string.Empty, Environment.StackTrace);
267
268 throw new Exception("Regeneration of keys not permitted at this time.");
269 }
270
271 await this.GenerateNewKeys();
272 }
273 }
274
275 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.HttpFileUploadJid) && (ServiceRef.TagProfile.HttpFileUploadMaxSize > 0))
277
278 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.RegistryJid))
279 this.thingRegistryClient = new ThingRegistryClient(this.xmppClient, ServiceRef.TagProfile.RegistryJid);
280
281 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.ProvisioningJid))
282 {
283 this.provisioningClient = new ProvisioningClient(this.xmppClient, ServiceRef.TagProfile.ProvisioningJid)
284 {
285 ManagePresenceSubscriptionRequests = false
286 };
287
288 this.provisioningClient.CanControlQuestion += this.ProvisioningClient_CanControlQuestion;
289 this.provisioningClient.CanReadQuestion += this.ProvisioningClient_CanReadQuestion;
290 this.provisioningClient.IsFriendQuestion += this.ProvisioningClient_IsFriendQuestion;
291 }
292
293 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.EDalerJid))
294 {
295 this.eDalerClient = new EDalerClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.EDalerJid);
296 this.RegisterEDalerEventHandlers(this.eDalerClient);
297 }
298
299 if (!string.IsNullOrWhiteSpace(ServiceRef.TagProfile.NeuroFeaturesJid))
300 {
301 this.neuroFeaturesClient = new NeuroFeaturesClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.NeuroFeaturesJid);
302 this.RegisterNeuroFeatureEventHandlers(this.neuroFeaturesClient);
303 }
304
306 this.pushNotificationClient = new PushNotificationClient(this.xmppClient);
307
308 this.sensorClient = new SensorClient(this.xmppClient);
309 this.controlClient = new ControlClient(this.xmppClient);
310 this.concentratorClient = new ConcentratorClient(this.xmppClient);
311
312 this.pepClient = new PepClient(this.xmppClient);
313 this.ReregisterPepEventHandlers(this.pepClient);
314
315 this.httpxClient = new HttpxClient(this.xmppClient, 8192);
316 Types.SetModuleParameter("XMPP", this.xmppClient); // Makes the XMPP Client the default XMPP client, when resolving HTTP over XMPP requests.
317
318 this.IsLoggedOut = false;
319 this.xmppClient.Connect(IsIpAddress ? string.Empty : this.domainName);
320 this.RecreateReconnectTimer();
321
322 // Await connected state during registration or user initiated log in, but not otherwise.
324 {
325 if (!await this.WaitForConnectedState(Constants.Timeouts.XmppConnect))
326 {
327 ServiceRef.LogService.LogWarning("Connection to XMPP server failed.",
328 new KeyValuePair<string, object?>("Domain", this.domainName ?? string.Empty),
329 new KeyValuePair<string, object?>("Account", this.accountName ?? string.Empty),
330 new KeyValuePair<string, object?>("Timeout", Constants.Timeouts.XmppConnect));
331 }
332 }
333 }
334 }
335 finally
336 {
337 this.isCreatingClient = false;
338 }
339 }
340
341#if DEBUG_DB_REMOTE
342 private class RemoteLedgerWriter()
343 : TextWriter(CultureInfo.CurrentCulture)
344 {
345 private readonly StringBuilder sb = new();
346
347 public override Encoding Encoding => Encoding.Unicode;
348 public override void Flush() => this.FlushAsync().Wait();
349 public override Task FlushAsync(CancellationToken cancellationToken) => this.FlushAsync();
350
351 public override async Task FlushAsync()
352 {
353 try
354 {
355 string s = this.sb.ToString();
356 string s2 = s.TrimStart();
357 if (string.IsNullOrEmpty(s2))
358 return;
359
360 if (ServiceRef.XmppService is not XmppService Service)
361 return;
362
363 RemoteSniffer? Sniffer = Service.debugSniffer;
364 if (Sniffer is null)
365 return;
366
367 this.sb.Clear();
368
369 int i = s2.IndexOf('<');
370 if (i > 0)
371 s2 = s2[i..];
372
373 string[] Rows = s.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n');
374
375 if (s2.StartsWith("<New", StringComparison.OrdinalIgnoreCase))
376 {
377 foreach (string Row in Rows)
378 await Sniffer.TransmitText(Row);
379 }
380 else if (s2.StartsWith("<Update", StringComparison.OrdinalIgnoreCase))
381 {
382 foreach (string Row in Rows)
383 await Sniffer.ReceiveText(Row);
384 }
385 else if (s2.StartsWith("<Delete", StringComparison.OrdinalIgnoreCase))
386 {
387 foreach (string Row in Rows)
388 await Sniffer.Error(Row);
389 }
390 else if (s2.StartsWith("<Clear", StringComparison.OrdinalIgnoreCase))
391 {
392 foreach (string Row in Rows)
393 await Sniffer.Warning(Row);
394 }
395 else
396 {
397 foreach (string Row in Rows)
398 await Sniffer.Information(Row);
399 }
400 }
401 catch (Exception)
402 {
403 // Ignore
404 }
405 }
406
407 public override void Write(char value) => this.sb.Append(value);
408 public override void Write(char[]? buffer) => this.sb.Append(buffer);
409 public override void Write(char[] buffer, int index, int count) => this.sb.Append(new string(buffer, index, count));
410 public override void Write(ReadOnlySpan<char> buffer) => this.sb.Append(new string(buffer));
411 public override void Write(bool value) => this.sb.Append(value);
412 public override void Write(int value) => this.sb.Append(value);
413 public override void Write(uint value) => this.sb.Append(value);
414 public override void Write(long value) => this.sb.Append(value);
415 public override void Write(ulong value) => this.sb.Append(value);
416 public override void Write(float value) => this.sb.Append(value);
417 public override void Write(double value) => this.sb.Append(value);
418 public override void Write(decimal value) => this.sb.Append(value);
419 public override void Write(string? value) => this.sb.Append(value);
420 public override void Write(object? value) => this.sb.Append(value);
421 public override void Write(StringBuilder? value) => this.sb.Append(value);
422 public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) => this.sb.Append(string.Format(this.FormatProvider, format, arg0));
423 public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) => this.sb.Append(string.Format(this.FormatProvider, format, arg0, arg1));
424 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));
425 public override void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) => this.sb.Append(string.Format(this.FormatProvider, format, arg));
426 public override Task WriteAsync(char value) { this.sb.Append(value); return Task.CompletedTask; }
427 public override Task WriteAsync(string? value) { this.sb.Append(value); return Task.CompletedTask; }
428 public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) { this.sb.Append(value); return Task.CompletedTask; }
429 public override Task WriteAsync(char[] buffer, int index, int count) { this.sb.Append(new string(buffer, index, count)); return Task.CompletedTask; }
430 public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) { this.sb.Append(buffer); return Task.CompletedTask; }
431 public override void WriteLine() => this.sb.AppendLine(string.Empty);
432 public override void WriteLine(char value) => this.sb.AppendLine(value.ToString());
433 public override void WriteLine(char[]? buffer) => this.sb.AppendLine(new string(buffer));
434 public override void WriteLine(char[] buffer, int index, int count) => this.sb.AppendLine(new string(buffer, index, count));
435 public override void WriteLine(ReadOnlySpan<char> buffer) => this.sb.AppendLine(new string(buffer));
436 public override void WriteLine(bool value) => this.sb.AppendLine(value.ToString());
437 public override void WriteLine(int value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
438 public override void WriteLine(uint value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
439 public override void WriteLine(long value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
440 public override void WriteLine(ulong value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
441 public override void WriteLine(float value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
442 public override void WriteLine(double value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
443 public override void WriteLine(decimal value) => this.sb.AppendLine(value.ToString(CultureInfo.CurrentCulture));
444 public override void WriteLine(string? value) => this.sb.AppendLine(value?.ToString());
445 public override void WriteLine(StringBuilder? value) => this.sb.AppendLine(value?.ToString());
446 public override void WriteLine(object? value) => this.sb.AppendLine(value?.ToString());
447 public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0) => this.sb.AppendLine(string.Format(this.FormatProvider, format, arg0));
448 public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, object? arg0, object? arg1) => this.sb.AppendLine(string.Format(this.FormatProvider, format, arg0, arg1));
449 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));
450 public override void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] arg) => this.sb.AppendLine(string.Format(this.FormatProvider, format, arg));
451 public override Task WriteLineAsync(char value) { this.sb.AppendLine(value.ToString()); return Task.CompletedTask; }
452 public override Task WriteLineAsync(string? value) { this.sb.AppendLine(value); return Task.CompletedTask; }
453 public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) { this.sb.AppendLine(value?.ToString()); return Task.CompletedTask; }
454 public override Task WriteLineAsync(char[] buffer, int index, int count) { this.sb.AppendLine(new string(buffer, index, count)); return Task.CompletedTask; }
455 public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) { this.sb.AppendLine(new string(buffer.Span)); return Task.CompletedTask; }
456 public override Task WriteLineAsync() { this.sb.AppendLine(string.Empty); return Task.CompletedTask; }
457 }
458#endif
459
460 private async Task DestroyXmppClient()
461 {
462 this.reconnectTimer?.Dispose();
463 this.reconnectTimer = null;
464
465 await this.OnConnectionStateChanged(XmppState.Offline);
466
467 if (this.xmppFilteredEventSink is not null)
468 {
469 ServiceRef.LogService.RemoveListener(this.xmppFilteredEventSink);
470 this.xmppFilteredEventSink.SecondarySink.Dispose();
471 this.xmppFilteredEventSink.Dispose();
472 this.xmppFilteredEventSink = null;
473 }
474
475 this.contractsClient?.Dispose();
476 this.contractsClient = null;
477
478 this.fileUploadClient?.Dispose();
479 this.fileUploadClient = null;
480
481 this.thingRegistryClient?.Dispose();
482 this.thingRegistryClient = null;
483
484 this.provisioningClient?.Dispose();
485 this.provisioningClient = null;
486
487 this.eDalerClient?.Dispose();
488 this.eDalerClient = null;
489
490 this.neuroFeaturesClient?.Dispose();
491 this.neuroFeaturesClient = null;
492
493 this.pushNotificationClient?.Dispose();
494 this.pushNotificationClient = null;
495
496 this.sensorClient?.Dispose();
497 this.sensorClient = null;
498
499 this.controlClient?.Dispose();
500 this.controlClient = null;
501
502 this.concentratorClient?.Dispose();
503 this.concentratorClient = null;
504
505 this.pepClient?.Dispose();
506 this.pepClient = null;
507
508 this.abuseClient?.Dispose();
509 this.abuseClient = null;
510
511#if DEBUG_DB_REMOTE
512 this.debugSniffer = null;
513#endif
514#if DEBUG_LOG_REMOTE
515 if (this.debugEventSink is not null)
516 {
517 Log.Unregister(this.debugEventSink);
518 this.debugEventSink?.Dispose();
519 this.debugEventSink = null;
520 }
521#endif
522 this.xmppClient?.Dispose();
523 this.xmppClient = null;
524 }
525
526 private bool XmppStale()
527 {
528 return this.xmppClient is null ||
529 this.xmppClient.State == XmppState.Offline ||
530 this.xmppClient.State == XmppState.Error ||
531 (this.xmppClient.State != XmppState.Connected && (DateTime.Now - this.xmppLastStateChange).TotalSeconds >= 10);
532 }
533
534 private bool XmppParametersCurrent()
535 {
536 if (this.xmppClient is null)
537 return false;
538
539 if (this.domainName != ServiceRef.TagProfile.Domain)
540 return false;
541
542 if (this.accountName != ServiceRef.TagProfile.Account)
543 return false;
544
545 if (this.passwordHash != ServiceRef.TagProfile.XmppPasswordHash)
546 return false;
547
548 if (this.passwordHashMethod != ServiceRef.TagProfile.XmppPasswordHashMethod)
549 return false;
550
551 if (this.contractsClient?.ComponentAddress != ServiceRef.TagProfile.LegalJid)
552 return false;
553
554 if (this.fileUploadClient?.FileUploadJid != ServiceRef.TagProfile.HttpFileUploadJid)
555 return false;
556
557 if (this.thingRegistryClient?.ThingRegistryAddress != ServiceRef.TagProfile.RegistryJid)
558 return false;
559
560 if (this.provisioningClient?.ProvisioningServerAddress != ServiceRef.TagProfile.ProvisioningJid)
561 return false;
562
563 if (this.eDalerClient?.ComponentAddress != ServiceRef.TagProfile.EDalerJid)
564 return false;
565
566 if (this.neuroFeaturesClient?.ComponentAddress != ServiceRef.TagProfile.NeuroFeaturesJid)
567 return false;
568
569 if ((this.pushNotificationClient is null) ^ !ServiceRef.TagProfile.SupportsPushNotification)
570 return false;
571
572 return true;
573 }
574
575 private void RecreateReconnectTimer()
576 {
577 this.reconnectTimer?.Dispose();
578 this.reconnectTimer = new Timer(this.ReconnectTimer_Tick, null, Constants.Intervals.Reconnect, Constants.Intervals.Reconnect);
579 }
580
584 public void Dispose()
585 {
586 this.reconnectTimer?.Dispose();
587 this.reconnectTimer = null;
588
589 if (this.xmppFilteredEventSink is not null)
590 {
591 ServiceRef.LogService.RemoveListener(this.xmppFilteredEventSink);
592 this.xmppFilteredEventSink.SecondarySink.Dispose();
593 this.xmppFilteredEventSink.Dispose();
594 this.xmppFilteredEventSink = null;
595 }
596
597 this.contractsClient?.Dispose();
598 this.contractsClient = null;
599
600 this.fileUploadClient?.Dispose();
601 this.fileUploadClient = null;
602
603 this.thingRegistryClient?.Dispose();
604 this.thingRegistryClient = null;
605
606 this.provisioningClient?.Dispose();
607 this.provisioningClient = null;
608
609 this.eDalerClient?.Dispose();
610 this.eDalerClient = null;
611
612 this.neuroFeaturesClient?.Dispose();
613 this.neuroFeaturesClient = null;
614
615 this.pushNotificationClient?.Dispose();
616 this.pushNotificationClient = null;
617
618 this.sensorClient?.Dispose();
619 this.sensorClient = null;
620
621 this.controlClient?.Dispose();
622 this.controlClient = null;
623
624 this.concentratorClient?.Dispose();
625 this.concentratorClient = null;
626
627 this.pepClient?.Dispose();
628 this.pepClient = null;
629
630 this.abuseClient?.Dispose();
631 this.abuseClient = null;
632
633 this.xmppClient?.Dispose();
634 this.xmppClient = null;
635
636 /*
637 this.Dispose(true);
638 GC.SuppressFinalize(this);
639 */
640 }
641
642 /*
646 protected virtual void Dispose(bool disposing)
647 {
648 if (this.isDisposed)
649 {
650 return;
651 }
652
653 if (disposing)
654 {
655 this.abuseClient.Dispose();
656 this.contractsClient.Dispose();
657 this.fileUploadClient.Dispose();
658 this.httpxClient.Dispose();
659 this.reconnectTimer.Dispose();
660 this.sniffer.Dispose();
661 this.xmppClient.Dispose();
662 this.xmppFilteredEventSink.SecondarySink.Dispose();
663 this.xmppFilteredEventSink.Dispose();
664 }
665
666 this.isDisposed = true;
667 }
668 */
669 #endregion
670
671 #region Lifecycle
672
673 public async Task<bool> WaitForConnectedState(TimeSpan Timeout)
674 {
675 if (this.xmppClient is null)
676 {
677 DateTime Start = DateTime.Now;
678
679 while (this.xmppClient is null && DateTime.Now - Start < Timeout)
680 await Task.Delay(1000);
681
682 if (this.xmppClient is null)
683 return false;
684
685 Timeout -= DateTime.Now - Start;
686 }
687
688 if (this.xmppClient.State == XmppState.Connected)
689 return true;
690
691 if (Timeout < TimeSpan.Zero)
692 return false;
693
694 int i = await this.xmppClient.WaitStateAsync((int)Timeout.TotalMilliseconds, XmppState.Connected);
695 return i >= 0;
696 }
697
698 public override async Task Load(bool IsResuming, CancellationToken CancellationToken)
699 {
700 if (this.BeginLoad(IsResuming, CancellationToken))
701 {
702 try
703 {
704 ServiceRef.TagProfile.StepChanged += this.TagProfile_StepChanged;
705 ServiceRef.TagProfile.Changed += this.TagProfile_Changed;
706
707 if (ServiceRef.TagProfile.ShouldCreateClient() && !this.XmppParametersCurrent())
708 await this.CreateXmppClient();
709
710 if ((this.xmppClient is not null) &&
711 this.xmppClient.State == XmppState.Connected &&
713 {
714 // Don't await this one, just fire and forget, to improve startup time.
715 _ = this.xmppClient.SetPresenceAsync(Availability.Online);
716 }
717
718 this.EndLoad(true);
719 }
720 catch (Exception ex)
721 {
722 ex = Log.UnnestException(ex);
723 ServiceRef.LogService.LogException(ex, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
724 this.EndLoad(false);
725 }
726 }
727 }
728
729 public override Task Unload()
730 {
731 return this.Unload(false);
732 }
733
734 public Task UnloadFast()
735 {
736 return this.Unload(true);
737 }
738
739 private async Task Unload(bool fast)
740 {
741 if (this.BeginUnload())
742 {
743 try
744 {
745 ServiceRef.TagProfile.StepChanged -= this.TagProfile_StepChanged;
746 ServiceRef.TagProfile.Changed -= this.TagProfile_Changed;
747
748 this.reconnectTimer?.Dispose();
749 this.reconnectTimer = null;
750
751 if (this.xmppClient is not null)
752 {
753 this.xmppClient.CheckConnection = false;
754
755 if (!fast)
756 {
757 try
758 {
759 await Task.WhenAny(
760 this.xmppClient.SetPresenceAsync(Availability.Offline),
761 Task.Delay(1000) // Wait at most 1000 ms.
762 );
763 }
764 catch (Exception)
765 {
766 // Ignore
767 }
768 }
769 }
770
771 await this.DestroyXmppClient();
772 }
773 catch (Exception ex)
774 {
775 ServiceRef.LogService.LogException(ex, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
776 }
777
778 this.EndUnload();
779 }
780 }
781
782 private void TagProfile_StepChanged(object? Sender, EventArgs e)
783 {
784 if (!this.IsLoaded)
785 return;
786
787 Task ExecutionTask = Task.Run(async () =>
788 {
789 try
790 {
791 bool CreateXmppClient = ServiceRef.TagProfile.ShouldCreateClient();
792
793 if (CreateXmppClient && !this.XmppParametersCurrent())
794 await this.CreateXmppClient();
795 else if (!CreateXmppClient)
796 await this.DestroyXmppClient();
797 }
798 catch (Exception ex)
799 {
800 ServiceRef.LogService.LogException(ex);
801 }
802 });
803 }
804
805 private void TagProfile_Changed(object? Sender, PropertyChangedEventArgs e)
806 {
807 if (e.PropertyName == nameof(ITagProfile.Account))
808 this.TagProfile_StepChanged(Sender, new EventArgs());
809 }
810
811 private Task XmppClient_Error(object? _, Exception e)
812 {
813 this.LatestError = e.Message;
814 return Task.CompletedTask;
815 }
816
817 private Task XmppClient_ConnectionError(object? _, Exception e)
818 {
819 if (e is ObjectDisposedException)
820 this.LatestConnectionError = ServiceRef.Localizer[nameof(AppResources.UnableToConnect)];
821 else
822 this.LatestConnectionError = e.Message;
823
824 return Task.CompletedTask;
825 }
826
827 private async Task XmppClient_StateChanged(object? Sender, XmppState NewState)
828 {
829 this.xmppLastStateChange = DateTime.Now;
830
831 switch (NewState)
832 {
833 case XmppState.Connecting:
834 this.LatestError = string.Empty;
835 this.LatestConnectionError = string.Empty;
836 break;
837
838 case XmppState.Connected:
839 this.LatestError = string.Empty;
840 this.LatestConnectionError = string.Empty;
841
842 this.xmppConnected = true;
843
844 this.RecreateReconnectTimer();
845
846 if (string.IsNullOrEmpty(ServiceRef.TagProfile.XmppPasswordHashMethod))
847 {
849 this.xmppClient?.PasswordHash ?? string.Empty,
850 this.xmppClient?.PasswordHashMethod ?? string.Empty);
851 }
852
853 if (ServiceRef.TagProfile.NeedsUpdating() && await this.DiscoverServices())
854 {
855 if (this.contractsClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LegalJid))
856 {
857 this.contractsClient = new ContractsClient(this.xmppClient, ServiceRef.TagProfile.LegalJid);
858 this.RegisterContractsEventHandlers();
859
860 if (!await this.contractsClient.LoadKeys(false))
861 {
862 this.contractsClient.Dispose();
863 this.contractsClient = null;
864 }
865 }
866
867 if (this.fileUploadClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.HttpFileUploadJid) && (ServiceRef.TagProfile.HttpFileUploadMaxSize > 0))
869
870 if (this.thingRegistryClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.RegistryJid))
871 this.thingRegistryClient = new ThingRegistryClient(this.xmppClient, ServiceRef.TagProfile.RegistryJid);
872
873 if (this.provisioningClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.RegistryJid))
874 {
875 this.provisioningClient = new ProvisioningClient(this.xmppClient, ServiceRef.TagProfile.ProvisioningJid)
876 {
877 ManagePresenceSubscriptionRequests = false
878 };
879
880 this.provisioningClient.CanControlQuestion += this.ProvisioningClient_CanControlQuestion;
881 this.provisioningClient.CanReadQuestion += this.ProvisioningClient_CanReadQuestion;
882 this.provisioningClient.IsFriendQuestion += this.ProvisioningClient_IsFriendQuestion;
883 }
884
885 if (this.eDalerClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.EDalerJid))
886 {
887 this.eDalerClient = new EDalerClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.EDalerJid);
888 this.RegisterEDalerEventHandlers(this.eDalerClient);
889 }
890
891 if (this.neuroFeaturesClient is null && !string.IsNullOrWhiteSpace(ServiceRef.TagProfile.NeuroFeaturesJid))
892 {
893 this.neuroFeaturesClient = new NeuroFeaturesClient(this.xmppClient, this.contractsClient, ServiceRef.TagProfile.NeuroFeaturesJid);
894 this.RegisterNeuroFeatureEventHandlers(this.neuroFeaturesClient);
895 }
896
897 if (this.pushNotificationClient is null && ServiceRef.TagProfile.SupportsPushNotification)
898 this.pushNotificationClient = new PushNotificationClient(this.xmppClient);
899 }
900
901 ServiceRef.LogService.AddListener(this.xmppFilteredEventSink!);
902
904 break;
905
906 case XmppState.Offline:
907 case XmppState.Error:
908 if (this.xmppConnected && !this.IsUnloading)
909 {
910 this.xmppConnected = false;
911
912 if (this.xmppClient is not null && !this.xmppClient.Disposed)
913 this.xmppClient.Reconnect();
914 }
915 break;
916 }
917
918 await this.OnConnectionStateChanged(NewState);
919 }
920
924 public event StateChangedEventHandler? ConnectionStateChanged;
925
926 private async Task OnConnectionStateChanged(XmppState NewState)
927 {
928 try
929 {
930 Task? T = this.ConnectionStateChanged?.Invoke(this, NewState);
931
932 if (T is not null)
933 await T;
934 }
935 catch (Exception ex)
936 {
937 ServiceRef.LogService.LogException(ex);
938 }
939 }
940
941 #endregion
942
943 #region State
944
945 public bool IsLoggedOut { get; private set; }
946 public bool IsOnline => (this.xmppClient is not null) && this.xmppClient.State == XmppState.Connected;
947 public XmppState State => this.xmppClient?.State ?? XmppState.Offline;
948 public string BareJid => this.xmppClient?.BareJID ?? string.Empty;
949
950 public string? LatestError { get; private set; }
951 public string? LatestConnectionError { get; private set; }
952
953 #endregion
954
955 #region Connections
956
957 private enum ConnectOperation
958 {
959 Connect,
960 ConnectAndCreateAccount,
961 ConnectToAccount
962 }
963
964 public Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnect(string domain, bool isIpAddress, string hostName, int portNumber,
965 string languageCode, Assembly applicationAssembly, Func<XmppClient, Task> connectedFunc)
966 {
967 return this.TryConnectInner(domain, isIpAddress, hostName, portNumber, string.Empty, string.Empty, string.Empty, languageCode,
968 string.Empty, string.Empty, applicationAssembly, connectedFunc, ConnectOperation.Connect);
969 }
970
971 public Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnectAndCreateAccount(string domain, bool isIpAddress, string hostName,
972 int portNumber, string userName, string password, string languageCode, string ApiKey, string ApiSecret,
973 Assembly applicationAssembly, Func<XmppClient, Task> connectedFunc)
974 {
975 return this.TryConnectInner(domain, isIpAddress, hostName, portNumber, userName, password, string.Empty, languageCode,
976 ApiKey, ApiSecret, applicationAssembly, connectedFunc, ConnectOperation.ConnectAndCreateAccount);
977 }
978
979 public Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnectAndConnectToAccount(string domain, bool isIpAddress, string hostName,
980 int portNumber, string userName, string password, string passwordMethod, string languageCode, Assembly applicationAssembly,
981 Func<XmppClient, Task> connectedFunc)
982 {
983 return this.TryConnectInner(domain, isIpAddress, hostName, portNumber, userName, password, passwordMethod, languageCode,
984 string.Empty, string.Empty, applicationAssembly, connectedFunc, ConnectOperation.ConnectToAccount);
985 }
986
987 private async Task<(bool Succeeded, string? ErrorMessage, string[]? Alternatives)> TryConnectInner(string Domain, bool IsIpAddress, string HostName,
988 int PortNumber, string UserName, string Password, string PasswordMethod, string LanguageCode, string ApiKey, string ApiSecret,
989 Assembly ApplicationAssembly, Func<XmppClient, Task> ConnectedFunc, ConnectOperation Operation)
990 {
991 TaskCompletionSource<bool> Connected = new();
992 bool Succeeded;
993 string? ErrorMessage = null;
994 bool StreamNegotiation = false;
995 bool StreamOpened = false;
996 bool StartingEncryption = false;
997 bool Authenticating = false;
998 bool Registering = false;
999 bool IsTimeout = false;
1000 string? ConnectionError = null;
1001 string[]? Alternatives = null;
1002
1003 Task OnConnectionError(object _, Exception e)
1004 {
1005 if (e is ObjectDisposedException)
1006 ConnectionError = ServiceRef.Localizer[nameof(AppResources.UnableToConnect)];
1007 else if (e is ConflictException ConflictInfo)
1008 Alternatives = ConflictInfo.Alternatives;
1009 else
1010 ConnectionError = e.Message;
1011
1012 Connected.TrySetResult(false);
1013 return Task.CompletedTask;
1014 }
1015
1016 async Task OnStateChanged(object _, XmppState newState)
1017 {
1018 switch (newState)
1019 {
1020 case XmppState.StreamNegotiation:
1021 StreamNegotiation = true;
1022 break;
1023
1024 case XmppState.StreamOpened:
1025 StreamOpened = true;
1026 break;
1027
1028 case XmppState.StartingEncryption:
1029 StartingEncryption = true;
1030 break;
1031
1032 case XmppState.Authenticating:
1033 Authenticating = true;
1034
1035 if (Operation == ConnectOperation.Connect)
1036 Connected.TrySetResult(true);
1037
1038 break;
1039
1040 case XmppState.Registering:
1041 Registering = true;
1042 break;
1043
1044 case XmppState.Connected:
1045 Connected.TrySetResult(true);
1046 break;
1047
1048 case XmppState.Offline:
1049 Connected.TrySetResult(false);
1050 break;
1051
1052 case XmppState.Error:
1053 // When State = Error, wait for the OnConnectionError event to arrive also, as it holds more/direct information.
1054 // Just in case it never would - set state error and result.
1055 await Task.Delay(Constants.Timeouts.XmppConnect);
1056 Connected.TrySetResult(false);
1057 break;
1058 }
1059 }
1060
1061 XmppClient? Client = null;
1062 try
1063 {
1064 if (string.IsNullOrEmpty(PasswordMethod))
1065 Client = new XmppClient(HostName, PortNumber, UserName, Password, LanguageCode, ApplicationAssembly, this.sniffer);
1066 else
1067 Client = new XmppClient(HostName, PortNumber, UserName, Password, PasswordMethod, LanguageCode, ApplicationAssembly, this.sniffer);
1068
1069 if (Operation == ConnectOperation.ConnectAndCreateAccount)
1070 {
1071 if (!string.IsNullOrEmpty(ApiKey) && !string.IsNullOrEmpty(ApiSecret))
1072 Client.AllowRegistration(ApiKey, ApiSecret);
1073 else
1074 Client.AllowRegistration();
1075 }
1076
1077 Client.TrustServer = !IsIpAddress;
1078 Client.AllowCramMD5 = false;
1079 Client.AllowDigestMD5 = false;
1080 Client.AllowPlain = false;
1081 Client.AllowEncryption = true;
1082 Client.AllowScramSHA1 = true;
1083 Client.AllowScramSHA256 = true;
1084 Client.AllowQuickLogin = true;
1085
1086 Client.OnConnectionError += OnConnectionError;
1087 Client.OnStateChanged += OnStateChanged;
1088
1089 Client.Connect(IsIpAddress ? string.Empty : Domain);
1090
1091 void TimerCallback(object? _)
1092 {
1093 IsTimeout = true;
1094 Connected.TrySetResult(false);
1095 }
1096
1097 using Timer _ = new(TimerCallback, null, (int)Constants.Timeouts.XmppConnect.TotalMilliseconds, Timeout.Infinite);
1098 Succeeded = await Connected.Task;
1099
1100 if (Succeeded && (ConnectedFunc is not null))
1101 await ConnectedFunc(Client);
1102
1103 Client.OnStateChanged -= OnStateChanged;
1104 Client.OnConnectionError -= OnConnectionError;
1105 }
1106 catch (Exception ex)
1107 {
1108 ServiceRef.LogService.LogException(ex, new KeyValuePair<string, object?>(nameof(ConnectOperation), Operation.ToString()));
1109 Succeeded = false;
1110 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.UnableToConnectTo), Domain];
1111 }
1112 finally
1113 {
1114 Client?.Dispose();
1115 Client = null;
1116 }
1117
1118 if (!Succeeded && string.IsNullOrEmpty(ErrorMessage))
1119 {
1120 if (this.sniffer is not null)
1121 System.Diagnostics.Debug.WriteLine("Sniffer: ", await this.sniffer.SnifferToText());
1122
1123 if (!StreamNegotiation || IsTimeout)
1124 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.CantConnectTo), Domain];
1125 else if (!StreamOpened)
1126 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.DomainIsNotAValidOperator), Domain];
1127 else if (!StartingEncryption)
1128 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.DomainDoesNotFollowEncryptionPolicy), Domain];
1129 else if (!Authenticating)
1130 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.UnableToAuthenticateWith), Domain];
1131 else if (!Registering)
1132 {
1133 if (!string.IsNullOrWhiteSpace(ConnectionError))
1134 ErrorMessage = ConnectionError;
1135 else
1136 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.OperatorDoesNotSupportRegisteringNewAccounts), Domain];
1137 }
1138 else if (Operation == ConnectOperation.ConnectAndCreateAccount)
1139 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.UsernameNameAlreadyTaken), this.accountName ?? string.Empty];
1140 else if (Operation == ConnectOperation.ConnectToAccount)
1141 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.InvalidUsernameOrPassword), this.accountName ?? string.Empty];
1142 else
1143 ErrorMessage = ServiceRef.Localizer[nameof(AppResources.UnableToConnectTo), Domain];
1144 }
1145
1146 return (Succeeded, ErrorMessage, Alternatives);
1147 }
1148
1149 private void ReconnectTimer_Tick(object? _)
1150 {
1151 if (this.xmppClient is null)
1152 return;
1153
1154 if (!ServiceRef.NetworkService.IsOnline)
1155 return;
1156
1157 if (this.XmppStale())
1158 {
1159 this.xmppLastStateChange = DateTime.Now;
1160
1161 if (!this.xmppClient.Disposed)
1162 this.xmppClient.Reconnect();
1163 }
1164 }
1165
1166 #endregion
1167
1168 #region Password
1169
1175 public Task<bool> ChangePassword(string NewPassword)
1176 {
1177 TaskCompletionSource<bool> PasswordChanged = new();
1178
1179 this.XmppClient.ChangePassword(NewPassword, (sender, e) =>
1180 {
1181 PasswordChanged.TrySetResult(e.Ok);
1182 return Task.CompletedTask;
1183 }, null);
1184
1185 return PasswordChanged.Task;
1186 }
1187
1188 #endregion
1189
1190 #region Components & Services
1191
1197 public Task<ServiceDiscoveryEventArgs> SendServiceDiscoveryRequest(string FullJid)
1198 {
1199 TaskCompletionSource<ServiceDiscoveryEventArgs> Result = new();
1200
1201 this.XmppClient.SendServiceDiscoveryRequest(FullJid, (_, e) =>
1202 {
1203 Result.TrySetResult(e);
1204 return Task.CompletedTask;
1205 }, null);
1206
1207 return Result.Task;
1208 }
1209
1215 public async Task<bool> DiscoverServices(XmppClient? Client = null)
1216 {
1217 Client ??= this.xmppClient;
1218
1219 if (Client is null)
1220 return false;
1221
1223
1224 try
1225 {
1226 response = await Client.ServiceItemsDiscoveryAsync(null, string.Empty, string.Empty);
1227 }
1228 catch (Exception ex)
1229 {
1230 if (this.sniffer is not null)
1231 {
1232 string commsDump = await this.sniffer.SnifferToText();
1233 ServiceRef.LogService.LogException(ex, new KeyValuePair<string, object?>("Sniffer", commsDump));
1234 }
1235
1236 return false;
1237 }
1238
1239 List<Task> Tasks = [];
1240 object SynchObject = new();
1241
1242 Tasks.Add(CheckFeatures(Client, SynchObject));
1243
1244 foreach (Item Item in response.Items)
1245 Tasks.Add(CheckComponent(Client, Item, SynchObject));
1246
1247 await Task.WhenAll([.. Tasks]);
1248
1249 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LegalJid))
1250 return false;
1251
1252 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.HttpFileUploadJid) || (ServiceRef.TagProfile.HttpFileUploadMaxSize <= 0))
1253 return false;
1254
1255 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.LogJid))
1256 return false;
1257
1258 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.EDalerJid))
1259 return false;
1260
1261 if (string.IsNullOrWhiteSpace(ServiceRef.TagProfile.NeuroFeaturesJid))
1262 return false;
1263
1265 return false;
1266
1267 return true;
1268 }
1269
1270 private static async Task CheckFeatures(XmppClient Client, object SynchObject)
1271 {
1272 ServiceDiscoveryEventArgs e = await Client.ServiceDiscoveryAsync(string.Empty);
1273
1274 lock (SynchObject)
1275 {
1276 ServiceRef.TagProfile.SupportsPushNotification = e.HasFeature(PushNotificationClient.MessagePushNamespace);
1277 }
1278 }
1279
1280 private static async Task CheckComponent(XmppClient Client, Item Item, object SynchObject)
1281 {
1282 ServiceDiscoveryEventArgs itemResponse = await Client.ServiceDiscoveryAsync(null, Item.JID, Item.Node);
1283
1284 lock (SynchObject)
1285 {
1287 ServiceRef.TagProfile.LegalJid = Item.JID;
1288
1290 ServiceRef.TagProfile.RegistryJid = Item.JID;
1291
1295 {
1296 ServiceRef.TagProfile.ProvisioningJid = Item.JID;
1297 }
1298
1299 if (itemResponse.HasFeature(HttpFileUploadClient.Namespace))
1300 {
1301 long maxSize = HttpFileUploadClient.FindMaxFileSize(Client, itemResponse) ?? 0;
1303 }
1304
1306 ServiceRef.TagProfile.LogJid = Item.JID;
1307
1309 ServiceRef.TagProfile.LogJid = Item.JID;
1310
1311 if (itemResponse.HasFeature(EDalerClient.NamespaceEDaler))
1312 ServiceRef.TagProfile.EDalerJid = Item.JID;
1313
1315 ServiceRef.TagProfile.NeuroFeaturesJid = Item.JID;
1316 }
1317 }
1318
1319 #endregion
1320
1321 #region Transfer
1322
1323 private async Task TransferIdDelivered(object? Sender, MessageEventArgs e)
1324 {
1325 if (e.From != Constants.Domains.OnboardingDomain)
1326 {
1327 return;
1328 }
1329
1330 string Code = XML.Attribute(e.Content, "code");
1331 bool Deleted = XML.Attribute(e.Content, "deleted", false);
1332
1333 if (!Deleted)
1334 return;
1335
1336 string CodesGenerated = await RuntimeSettings.GetAsync(Constants.Settings.TransferIdCodeSent, string.Empty);
1337 string[] Codes = CodesGenerated.Split(CommonTypes.CRLF, StringSplitOptions.RemoveEmptyEntries);
1338
1339 if (Array.IndexOf<string>(Codes, Code) < 0)
1340 return;
1341
1342 await this.DestroyXmppClient();
1343
1344 this.domainName = string.Empty;
1345 this.accountName = string.Empty;
1346 this.passwordHash = string.Empty;
1347 this.passwordHashMethod = string.Empty;
1348 this.xmppConnected = false;
1349
1352 await Database.Provider.Flush();
1353
1355 }
1356
1361 public async Task AddTransferCode(string Code)
1362 {
1363 string CodesGenerated = await RuntimeSettings.GetAsync(Constants.Settings.TransferIdCodeSent, string.Empty);
1364
1365 if (string.IsNullOrEmpty(CodesGenerated))
1366 CodesGenerated = Code;
1367 else
1368 CodesGenerated += "\r\n" + Code;
1369
1371 await Database.Provider.Flush();
1372 }
1373
1374 #endregion
1375
1376 #region Presence Subscriptions
1377
1378 private async Task XmppClient_OnPresenceSubscribe(object? Sender, PresenceEventArgs e)
1379 {
1380 LegalIdentity? RemoteIdentity = null;
1381 string FriendlyName = string.IsNullOrWhiteSpace(e.NickName) ? e.FromBareJID : e.NickName;
1382 string? PhotoUrl = null;
1383 int PhotoWidth = 0;
1384 int PhotoHeight = 0;
1385
1386 foreach (XmlNode N in e.Presence.ChildNodes)
1387 {
1388 if (N is XmlElement E && E.LocalName == "identity" && E.NamespaceURI == ContractsClient.NamespaceLegalIdentitiesCurrent)
1389 {
1390 RemoteIdentity = LegalIdentity.Parse(E);
1391 if (RemoteIdentity is not null)
1392 {
1393 FriendlyName = ContactInfo.GetFriendlyName(RemoteIdentity);
1394
1395 IdentityStatus Status = await this.ContractsClient.ValidateAsync(RemoteIdentity);
1396 if (Status != IdentityStatus.Valid)
1397 {
1398 e.Decline();
1399
1400 Log.Warning("Invalid ID received. Presence subscription declined.", e.FromBareJID, RemoteIdentity.Id, "IdValidationError",
1401 new KeyValuePair<string, object?>("Recipient JID", this.BareJid),
1402 new KeyValuePair<string, object?>("Sender JID", e.FromBareJID),
1403 new KeyValuePair<string, object?>("Legal ID", RemoteIdentity.Id),
1404 new KeyValuePair<string, object?>("Validation", Status));
1405 return;
1406 }
1407
1408 break;
1409 }
1410 }
1411 }
1412
1413 ContactInfo Info = await ContactInfo.FindByBareJid(e.FromBareJID);
1414 if ((Info is not null) && Info.AllowSubscriptionFrom.HasValue)
1415 {
1416 if (Info.AllowSubscriptionFrom.Value)
1417 e.Accept();
1418 else
1419 e.Decline();
1420
1421 if (Info.FriendlyName != FriendlyName || ((RemoteIdentity is not null) && Info.LegalId != RemoteIdentity.Id))
1422 {
1423 if (RemoteIdentity is not null)
1424 {
1425 Info.LegalId = RemoteIdentity.Id;
1426 Info.LegalIdentity = RemoteIdentity;
1427 }
1428
1429 Info.FriendlyName = FriendlyName;
1430 await Database.Update(Info);
1431 }
1432
1433 return;
1434 }
1435
1436 if ((RemoteIdentity is not null) && (RemoteIdentity.Attachments is not null))
1437 {
1438 (PhotoUrl, PhotoWidth, PhotoHeight) = await PhotosLoader.LoadPhotoAsTemporaryFile(RemoteIdentity.Attachments,
1440 }
1441
1442 SubscriptionRequestViewModel SubscriptionRequestViewModel = new(e.FromBareJID, FriendlyName, PhotoUrl, PhotoWidth, PhotoHeight);
1444
1445 await MopupService.Instance.PushAsync(SubscriptionRequestPopup);
1447
1448 switch (Action)
1449 {
1450 case PresenceRequestAction.Accept:
1451 e.Accept();
1452
1453 if (Info is null)
1454 {
1455 Info = new ContactInfo()
1456 {
1457 AllowSubscriptionFrom = true,
1458 BareJid = e.FromBareJID,
1459 FriendlyName = string.IsNullOrWhiteSpace(e.NickName) ? e.FromBareJID : e.NickName,
1460 IsThing = false
1461 };
1462
1463 await Database.Insert(Info);
1464 }
1465 else if (!Info.AllowSubscriptionFrom.HasValue || !Info.AllowSubscriptionFrom.Value)
1466 {
1467 Info.AllowSubscriptionFrom = true;
1468 await Database.Update(Info);
1469 }
1470
1471 RosterItem? Item = this.XmppClient[e.FromBareJID];
1472
1473 if (Item is null || (Item.State != SubscriptionState.Both && Item.State != SubscriptionState.To))
1474 {
1475 SubscribeToViewModel SubscribeToViewModel = new(e.FromBareJID);
1477
1478 await MopupService.Instance.PushAsync(SubscribeToPopup);
1479 bool? SubscribeTo = await SubscribeToViewModel.Result;
1480
1481 if (SubscribeTo.HasValue && SubscribeTo.Value)
1482 {
1483 string IdXml;
1484
1486 IdXml = string.Empty;
1487 else
1488 {
1489 StringBuilder Xml = new();
1490 ServiceRef.TagProfile.LegalIdentity.Serialize(Xml, true, true, true, true, true, true, true);
1491 IdXml = Xml.ToString();
1492 }
1493
1494 e.Client.RequestPresenceSubscription(e.FromBareJID, IdXml);
1495 }
1496 }
1497 break;
1498
1499 case PresenceRequestAction.Reject:
1500 e.Decline();
1501
1502 if (this.abuseClient is null)
1503 break;
1504
1507
1508 await MopupService.Instance.PushAsync(ReportOrBlockPopup);
1510
1511 if (ReportOrBlock == ReportOrBlockAction.Block || ReportOrBlock == ReportOrBlockAction.Report)
1512 {
1513 if (Info is null)
1514 {
1515 Info = new ContactInfo()
1516 {
1517 AllowSubscriptionFrom = false,
1518 BareJid = e.FromBareJID,
1519 FriendlyName = string.IsNullOrWhiteSpace(e.NickName) ? e.FromBareJID : e.NickName,
1520 IsThing = false
1521 };
1522
1523 await Database.Insert(Info);
1524 }
1525 else if (!Info.AllowSubscriptionFrom.HasValue || Info.AllowSubscriptionFrom.Value)
1526 {
1527 Info.AllowSubscriptionFrom = false;
1528 await Database.Update(Info);
1529 }
1530
1531 if (ReportOrBlock == ReportOrBlockAction.Report)
1532 {
1533 ReportTypeViewModel ReportTypeViewModel = new(e.FromBareJID);
1535
1536 await MopupService.Instance.PushAsync(ReportOrBlockPopup);
1538
1539 if (ReportType.HasValue)
1540 {
1541 TaskCompletionSource<bool> Result = new();
1542
1543 await this.abuseClient.BlockJID(e.FromBareJID, ReportType.Value, (sender2, e2) =>
1544 {
1545 Result.TrySetResult(e.Ok);
1546 return Task.CompletedTask;
1547 }, null);
1548
1549 await Result.Task;
1550 }
1551 }
1552 }
1553 break;
1554
1555 case PresenceRequestAction.Ignore:
1556 default:
1557 break;
1558 }
1559 }
1560
1561 private async Task XmppClient_OnPresenceUnsubscribed(object? Sender, PresenceEventArgs e)
1562 {
1563 ContactInfo ContactInfo = await ContactInfo.FindByBareJid(e.FromBareJID);
1564 if ((ContactInfo is not null) && ContactInfo.AllowSubscriptionFrom.HasValue && ContactInfo.AllowSubscriptionFrom.Value)
1565 {
1566 ContactInfo.AllowSubscriptionFrom = null;
1568 }
1569 }
1570
1571 #endregion
1572
1573 #region IQ Stanzas (Information Query)
1574
1583 public Task<XmlElement> IqSetAsync(string To, string Xml)
1584 {
1585 return this.XmppClient.IqSetAsync(To, Xml);
1586 }
1587
1591 private XmppClient XmppClient
1592 {
1593 get
1594 {
1595 if (this.xmppClient is null)
1596 throw new Exception("Not connected to XMPP network.");
1597
1598 return this.xmppClient;
1599 }
1600 }
1601
1602 #endregion
1603
1604 #region Messages
1605
1621 public void SendMessage(QoSLevel QoS, Waher.Networking.XMPP.MessageType Type, string Id, string To, string CustomXml, string Body,
1622 string Subject, string Language, string ThreadId, string ParentThreadId, DeliveryEventHandler? DeliveryCallback, object? State)
1623 {
1624 this.ContractsClient.LocalE2eEndpoint.SendMessage(this.XmppClient, E2ETransmission.NormalIfNotE2E,
1625 QoS, Type, Id, To, CustomXml, Body, Subject, Language, ThreadId, ParentThreadId, DeliveryCallback, State);
1626 }
1627
1628 private Task XmppClient_OnNormalMessage(object? Sender, MessageEventArgs e)
1629 {
1630 Log.Warning("Unhandled message received.", e.To, e.From,
1631 new KeyValuePair<string, object?>("Stanza", e.Message.OuterXml));
1632
1633 return Task.CompletedTask;
1634 }
1635
1636 private async Task XmppClient_OnChatMessage(object? Sender, MessageEventArgs e)
1637 {
1638 string RemoteBareJid = e.FromBareJID;
1639
1640 foreach (XmlNode N in e.Message.ChildNodes)
1641 {
1642 if (N is XmlElement E &&
1643 E.LocalName == "qlRef" &&
1644 E.NamespaceURI == XmppClient.NamespaceQuickLogin &&
1645 RemoteBareJid.IndexOf('@') < 0 &&
1646 RemoteBareJid.IndexOf('/') < 0)
1647 {
1648 LegalIdentity? RemoteIdentity = null;
1649
1650 foreach (XmlNode N2 in E.ChildNodes)
1651 {
1652 if (N2 is XmlElement E2 &&
1653 E2.LocalName == "identity" &&
1655 {
1656 RemoteIdentity = LegalIdentity.Parse(E2);
1657 break;
1658 }
1659 }
1660
1661 if (RemoteIdentity is not null)
1662 {
1663 IdentityStatus Status = await this.ValidateIdentity(RemoteIdentity);
1664 if (Status != IdentityStatus.Valid)
1665 {
1666 Log.Warning("Message rejected because the embedded legal identity was not valid.",
1667 new KeyValuePair<string, object?>("Identity", RemoteIdentity.Id),
1668 new KeyValuePair<string, object?>("From", RemoteBareJid),
1669 new KeyValuePair<string, object?>("Status", Status));
1670 return;
1671 }
1672
1673 string Jid = RemoteIdentity["JID"];
1674
1675 if (string.IsNullOrEmpty(Jid))
1676 {
1677 Log.Warning("Message rejected because the embedded legal identity lacked JID.",
1678 new KeyValuePair<string, object?>("Identity", RemoteIdentity.Id),
1679 new KeyValuePair<string, object?>("From", RemoteBareJid),
1680 new KeyValuePair<string, object?>("Status", Status));
1681 return;
1682 }
1683
1684 if (!string.Equals(XML.Attribute(E, "bareJid", string.Empty), Jid, StringComparison.OrdinalIgnoreCase))
1685 {
1686 Log.Warning("Message rejected because the embedded legal identity had a different JID compared to the JID of the quick-login reference.",
1687 new KeyValuePair<string, object?>("Identity", RemoteIdentity.Id),
1688 new KeyValuePair<string, object?>("From", RemoteBareJid),
1689 new KeyValuePair<string, object?>("Status", Status));
1690 return;
1691 }
1692
1693 RemoteBareJid = Jid;
1694 }
1695 }
1696 }
1697
1698 ContactInfo ContactInfo = await ContactInfo.FindByBareJid(RemoteBareJid);
1699 string FriendlyName = ContactInfo?.FriendlyName ?? RemoteBareJid;
1700 string? ReplaceObjectId = null;
1701
1702 ChatMessage Message = new()
1703 {
1704 Created = DateTime.UtcNow,
1705 RemoteBareJid = RemoteBareJid,
1706 RemoteObjectId = e.Id,
1707 MessageType = NeuroAccessMaui.UI.Pages.Contacts.Chat.MessageType.Received,
1708 Html = string.Empty,
1709 PlainText = e.Body,
1710 Markdown = string.Empty
1711 };
1712
1713 foreach (XmlNode N in e.Message.ChildNodes)
1714 {
1715 if (N is XmlElement E)
1716 {
1717 switch (N.LocalName)
1718 {
1719 case "content":
1720 if (E.NamespaceURI == "urn:xmpp:content")
1721 {
1722 string Type = XML.Attribute(E, "type");
1723
1724 switch (Type)
1725 {
1726 case "text/markdown":
1727 Message.Markdown = E.InnerText;
1728 break;
1729
1730 case "text/plain":
1731 Message.PlainText = E.InnerText;
1732 break;
1733
1734 case "text/html":
1735 Message.Html = E.InnerText;
1736 break;
1737 }
1738 }
1739 break;
1740
1741 case "html":
1742 if (E.NamespaceURI == "http://jabber.org/protocol/xhtml-im")
1743 {
1744 string Html = E.InnerXml;
1745
1746 int i = Html.IndexOf("<body", StringComparison.OrdinalIgnoreCase);
1747 if (i >= 0)
1748 {
1749 i = Html.IndexOf('>', i + 5);
1750 if (i >= 0)
1751 Html = Html[(i + 1)..].TrimStart();
1752
1753 i = Html.LastIndexOf("</body>", StringComparison.OrdinalIgnoreCase);
1754 if (i >= 0)
1755 Html = Html[..i].TrimEnd();
1756 }
1757
1758 Message.Html = Html;
1759 }
1760 break;
1761
1762 case "replace":
1763 if (E.NamespaceURI == "urn:xmpp:message-correct:0")
1764 ReplaceObjectId = XML.Attribute(E, "id");
1765 break;
1766
1767 case "delay":
1768 if (E.NamespaceURI == PubSubClient.NamespaceDelayedDelivery &&
1769 E.HasAttribute("stamp") &&
1770 XML.TryParse(E.GetAttribute("stamp"), out DateTime Timestamp2))
1771 {
1772 Message.Created = Timestamp2.ToUniversalTime();
1773 }
1774 break;
1775 }
1776 }
1777 }
1778
1779 if (!string.IsNullOrEmpty(Message.Markdown))
1780 {
1781 try
1782 {
1783 MarkdownSettings Settings = new()
1784 {
1785 AllowScriptTag = false,
1786 EmbedEmojis = false, // TODO: Emojis
1787 AudioAutoplay = false,
1788 AudioControls = false,
1789 ParseMetaData = false,
1790 VideoAutoplay = false,
1791 VideoControls = false
1792 };
1793
1794 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(Message.Markdown, Settings);
1795
1796 if (string.IsNullOrEmpty(Message.PlainText))
1797 Message.PlainText = (await Doc.GeneratePlainText()).Trim();
1798
1799 if (string.IsNullOrEmpty(Message.Html))
1800 Message.Html = HtmlDocument.GetBody(await Doc.GenerateHTML());
1801 }
1802 catch (Exception ex)
1803 {
1804 ServiceRef.LogService.LogException(ex);
1805 Message.Markdown = string.Empty;
1806 }
1807 }
1808
1809 if (string.IsNullOrEmpty(ReplaceObjectId))
1810 await Database.Insert(Message);
1811 else
1812 {
1813 ChatMessage Old = await Database.FindFirstIgnoreRest<ChatMessage>(new FilterAnd(
1814 new FilterFieldEqualTo("RemoteBareJid", RemoteBareJid),
1815 new FilterFieldEqualTo("RemoteObjectId", ReplaceObjectId)));
1816
1817 if (Old is null)
1818 {
1819 ReplaceObjectId = null;
1820 await Database.Insert(Message);
1821 }
1822 else
1823 {
1824 Old.Updated = Message.Created;
1825 Old.Html = Message.Html;
1826 Old.PlainText = Message.PlainText;
1827 Old.Markdown = Message.Markdown;
1828
1829 await Database.Update(Old);
1830
1831 Message = Old;
1832 }
1833 }
1834
1835 MainThread.BeginInvokeOnMainThread(async () =>
1836 {
1837 if (ServiceRef.UiService.CurrentPage is ChatPage &&
1838 ServiceRef.UiService.CurrentPage.BindingContext is ChatViewModel ChatViewModel &&
1839 string.Equals(ChatViewModel.BareJid, RemoteBareJid, StringComparison.OrdinalIgnoreCase))
1840 {
1841 if (string.IsNullOrEmpty(ReplaceObjectId))
1842 await ChatViewModel.MessageAddedAsync(Message);
1843 else
1844 await ChatViewModel.MessageUpdatedAsync(Message);
1845 }
1846 else
1847 {
1849 {
1850 ReplaceObjectId = ReplaceObjectId,
1851 BareJid = RemoteBareJid,
1852 Category = RemoteBareJid
1853 });
1854 }
1855 });
1856 }
1857
1858 private Task ClientMessage(object? Sender, MessageEventArgs e)
1859 {
1860 string Code = XML.Attribute(e.Content, "code");
1861 string Type = XML.Attribute(e.Content, "type");
1862 string Message = e.Body;
1863
1864 if (!string.IsNullOrEmpty(Code))
1865 {
1866 try
1867 {
1868 string LocalizedMessage = ServiceRef.Localizer["ClientMessage" + Code];
1869
1870 if (!string.IsNullOrEmpty(LocalizedMessage))
1871 Message = LocalizedMessage;
1872 }
1873 catch (Exception)
1874 {
1875 // Ignore
1876 }
1877 }
1878
1879 MainThread.BeginInvokeOnMainThread(async () =>
1880 {
1881 switch (Type.ToUpperInvariant())
1882 {
1883 case "NONE":
1884 default:
1885 await ServiceRef.UiService.DisplayAlert(
1886 ServiceRef.Localizer[nameof(AppResources.Information)], Message,
1887 ServiceRef.Localizer[nameof(AppResources.Ok)]);
1888 break;
1889
1890 case "CLIENT":
1891 case "SERVER":
1892 case "SERVICE":
1893 await ServiceRef.UiService.DisplayAlert(
1894 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], Message,
1895 ServiceRef.Localizer[nameof(AppResources.Ok)]);
1896 break;
1897
1898 }
1899 });
1900
1901 return Task.CompletedTask;
1902 }
1903
1904 #endregion
1905
1906 #region Presence
1907
1908 private async Task XmppClient_OnPresence(object? Sender, PresenceEventArgs e)
1909 {
1910 try
1911 {
1912 Task? T = this.OnPresence?.Invoke(this, e);
1913 if (T is not null)
1914 await T;
1915 }
1916 catch (Exception ex)
1917 {
1918 ServiceRef.LogService.LogException(ex);
1919 }
1920 }
1921
1925 public event PresenceEventHandlerAsync? OnPresence;
1926
1931 public void RequestPresenceSubscription(string BareJid)
1932 {
1934 }
1935
1941 public void RequestPresenceSubscription(string BareJid, string CustomXml)
1942 {
1943 this.XmppClient.RequestPresenceSubscription(BareJid, CustomXml);
1944 }
1945
1950 public void RequestPresenceUnsubscription(string BareJid)
1951 {
1953 }
1954
1959 public void RequestRevokePresenceSubscription(string BareJid)
1960 {
1962 }
1963
1964 #endregion
1965
1966 #region Roster
1967
1971 public RosterItem[] Roster => this.xmppClient?.Roster ?? [];
1972
1978 public RosterItem? GetRosterItem(string BareJid)
1979 {
1980 return this.XmppClient?.GetRosterItem(BareJid);
1981 }
1982
1987 public void AddRosterItem(RosterItem Item)
1988 {
1990 }
1991
1996 public void RemoveRosterItem(string BareJid)
1997 {
1998 this.XmppClient.RemoveRosterItem(BareJid);
1999 }
2000
2001 private async Task XmppClient_OnRosterItemAdded(object? Sender, RosterItem Item)
2002 {
2003 try
2004 {
2005 Task? T = this.OnRosterItemAdded?.Invoke(this, Item);
2006 if (T is not null)
2007 await T;
2008 }
2009 catch (Exception ex)
2010 {
2011 ServiceRef.LogService.LogException(ex);
2012 }
2013 }
2014
2018 public event RosterItemEventHandlerAsync? OnRosterItemAdded;
2019
2020 private async Task XmppClient_OnRosterItemUpdated(object? Sender, RosterItem Item)
2021 {
2022 try
2023 {
2024 Task? T = this.OnRosterItemUpdated?.Invoke(this, Item);
2025 if (T is not null)
2026 await T;
2027 }
2028 catch (Exception ex)
2029 {
2030 ServiceRef.LogService.LogException(ex);
2031 }
2032 }
2033
2037 public event RosterItemEventHandlerAsync? OnRosterItemUpdated;
2038
2039 private async Task XmppClient_OnRosterItemRemoved(object? Sender, RosterItem Item)
2040 {
2041 try
2042 {
2043 Task? T = this.OnRosterItemRemoved?.Invoke(this, Item);
2044 if (T is not null)
2045 await T;
2046 }
2047 catch (Exception ex)
2048 {
2049 ServiceRef.LogService.LogException(ex);
2050 }
2051 }
2052
2056 public event RosterItemEventHandlerAsync? OnRosterItemRemoved;
2057
2058 #endregion
2059
2060 #region Push Notification
2061
2065 public bool SupportsPushNotification => this.pushNotificationClient is not null;
2066
2067
2072 {
2073 get
2074 {
2075 if (this.pushNotificationClient is null)
2076 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.PushNotificationServiceNotFound)]);
2077
2078 return this.pushNotificationClient;
2079 }
2080 }
2081
2087 public async Task<bool> NewPushNotificationToken(TokenInformation TokenInformation)
2088 {
2089 // TODO: Check if started
2090
2091 if (this.pushNotificationClient is null || !this.IsOnline || string.IsNullOrEmpty(TokenInformation.Token))
2092 return false;
2093 else
2094 {
2095 await this.ReportNewPushNotificationToken(TokenInformation.Token, TokenInformation.Service, TokenInformation.ClientType);
2096
2097 return true;
2098 }
2099 }
2100
2107 public Task ReportNewPushNotificationToken(string Token, PushMessagingService Service, ClientType ClientType)
2108 {
2110 }
2111
2115 public Task ClearPushNotificationRules()
2116 {
2118 }
2119
2130 public Task AddPushNotificationRule(Waher.Networking.XMPP.MessageType MessageType, string LocalName, string Namespace,
2131 string Channel, string MessageVariable, string PatternMatchingScript, string ContentScript)
2132 {
2133 return this.PushNotificationClient.AddRuleAsync(MessageType, LocalName, Namespace, Channel, MessageVariable,
2134 PatternMatchingScript, ContentScript);
2135 }
2136
2137 #endregion
2138
2139 #region Tokens
2140
2148 public async Task<string?> GetApiToken(int Seconds)
2149 {
2150 DateTime Now = DateTime.UtcNow;
2151
2152 if (!string.IsNullOrEmpty(this.token) && Now.Subtract(this.tokenCreated).TotalSeconds < Seconds - 10)
2153 return this.token;
2154
2155 if (!this.IsOnline)
2156 {
2157 if (!await this.WaitForConnectedState(TimeSpan.FromSeconds(20)))
2158 return this.token;
2159 }
2160
2161 if (this.httpxClient is null)
2162 throw new Exception("Not connected to XMPP network.");
2163
2164 this.token = await this.httpxClient.GetJwtTokenAsync(Seconds);
2165 this.tokenCreated = Now;
2166
2167 return this.token;
2168 }
2169
2179 public async Task<object> PostToProtectedApi(string LocalResource, object Data, params KeyValuePair<string, string>[] Headers)
2180 {
2181 StringBuilder Url = new();
2182
2183 if (this.IsOnline)
2184 Url.Append("httpx://");
2185 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.
2186 {
2187 Url.Append("https://");
2188
2189 KeyValuePair<string, string> Authorization = new("Authorization", "Bearer " + this.token);
2190
2191 if (Headers is null)
2192 Headers = [Authorization];
2193 else
2194 {
2195 int c = Headers.Length;
2196
2197 Array.Resize(ref Headers, c + 1);
2198 Headers[c] = Authorization;
2199 }
2200 }
2201 else
2202 throw new IOException("No connection and no token available for call to protect API.");
2203
2204 Url.Append(ServiceRef.TagProfile.Domain);
2205 Url.Append(LocalResource);
2206
2207 return await InternetContent.PostAsync(new Uri(Url.ToString()), Data, Headers);
2208 }
2209
2210 #endregion
2211
2212 #region HTTP File Upload
2213
2218 private HttpFileUploadClient FileUploadClient
2219 {
2220 get
2221 {
2222 if (this.fileUploadClient is null)
2223 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.FileUploadServiceNotFound)]);
2224
2225 return this.fileUploadClient;
2226 }
2227 }
2228
2232 public bool FileUploadIsSupported
2233 {
2234 get
2235 {
2236 try
2237 {
2238 return ServiceRef.TagProfile.FileUploadIsSupported &&
2239 this.fileUploadClient is not null &&
2240 this.fileUploadClient.HasSupport;
2241 }
2242 catch (Exception ex)
2243 {
2244 ServiceRef.LogService.LogException(ex);
2245 return false;
2246 }
2247 }
2248 }
2249
2256 public Task<HttpFileUploadEventArgs> RequestUploadSlotAsync(string FileName, string ContentType, long ContentSize)
2257 {
2258 return this.FileUploadClient.RequestUploadSlotAsync(FileName, ContentType, ContentSize);
2259 }
2260
2261
2262 #endregion
2263
2264 #region Personal Eventing Protocol (PEP)
2265
2266 private readonly LinkedList<KeyValuePair<Type, PersonalEventNotificationEventHandler>> pepHandlers = new();
2267
2272 private PepClient PepClient
2273 {
2274 get
2275 {
2276 if (this.pepClient is null)
2277 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.PepServiceNotFound)]);
2278
2279 return this.pepClient;
2280 }
2281 }
2282
2288 public void RegisterPepHandler(Type PersonalEventType, PersonalEventNotificationEventHandler Handler)
2289 {
2290 lock (this.pepHandlers)
2291 {
2292 this.pepHandlers.AddLast(new KeyValuePair<Type, PersonalEventNotificationEventHandler>(PersonalEventType, Handler));
2293 }
2294
2295 this.PepClient.RegisterHandler(PersonalEventType, Handler);
2296 }
2297
2304 public bool UnregisterPepHandler(Type PersonalEventType, PersonalEventNotificationEventHandler Handler)
2305 {
2306 lock (this.pepHandlers)
2307 {
2308 LinkedListNode<KeyValuePair<Type, PersonalEventNotificationEventHandler>>? Node = this.pepHandlers.First;
2309
2310 while (Node is not null)
2311 {
2312 if (Node.Value.Key == PersonalEventType &&
2313 (Node.Value.Value.Target?.Equals(Handler.Target) ?? Handler.Target is null) &&
2314 Node.Value.Value.Method.Equals(Handler.Method))
2315 {
2316 this.pepHandlers.Remove(Node);
2317 break;
2318 }
2319
2320 Node = Node.Next;
2321 }
2322 }
2323
2324 return this.PepClient.UnregisterHandler(PersonalEventType, Handler);
2325 }
2326
2327 private void ReregisterPepEventHandlers(PepClient PepClient)
2328 {
2329 lock (this.pepHandlers)
2330 {
2331 foreach (KeyValuePair<Type, PersonalEventNotificationEventHandler> P in this.pepHandlers)
2332 PepClient.RegisterHandler(P.Key, P.Value);
2333 }
2334 }
2335
2336 #endregion
2337
2338 #region Thing Registries & Discovery
2339
2345 {
2346 get
2347 {
2348 if (this.thingRegistryClient is null)
2349 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ThingRegistryServiceNotFound)]);
2350
2351 return this.thingRegistryClient;
2352 }
2353 }
2354
2358 public string RegistryServiceJid => this.ThingRegistryClient.ThingRegistryAddress;
2359
2365 public bool IsIoTDiscoClaimURI(string DiscoUri)
2366 {
2367 return ThingRegistryClient.IsIoTDiscoClaimURI(DiscoUri);
2368 }
2369
2375 public bool IsIoTDiscoSearchURI(string DiscoUri)
2376 {
2378 }
2379
2385 public bool IsIoTDiscoDirectURI(string DiscoUri)
2386 {
2388 }
2389
2396 public bool TryDecodeIoTDiscoClaimURI(string DiscoUri, [NotNullWhen(true)] out MetaDataTag[]? Tags)
2397 {
2398 return ThingRegistryClient.TryDecodeIoTDiscoClaimURI(DiscoUri, out Tags);
2399 }
2400
2408 public bool TryDecodeIoTDiscoSearchURI(string DiscoUri, [NotNullWhen(true)] out SearchOperator[]? Operators,
2409 out string? RegistryJid)
2410 {
2411 RegistryJid = null;
2412 Operators = null;
2413 if (!ThingRegistryClient.TryDecodeIoTDiscoURI(DiscoUri, out IEnumerable<SearchOperator> Operators2))
2414 return false;
2415
2416 List<SearchOperator> List = [];
2417
2418 foreach (SearchOperator Operator in Operators2)
2419 {
2420 if (Operator.Name.Equals("R", StringComparison.OrdinalIgnoreCase))
2421 {
2422 if (!string.IsNullOrEmpty(RegistryJid))
2423 return false;
2424
2425 if (Operator is not StringTagEqualTo StrEqOp)
2426 return false;
2427
2428 RegistryJid = StrEqOp.Value;
2429 }
2430 else
2431 List.Add(Operator);
2432 }
2433
2434 Operators = [.. List];
2435
2436 return true;
2437 }
2438
2449 public bool TryDecodeIoTDiscoDirectURI(string DiscoUri, [NotNullWhen(true)] out string? Jid, out string? SourceId, out string? NodeId,
2450 out string? PartitionId, [NotNullWhen(true)] out MetaDataTag[]? Tags)
2451 {
2452 Jid = null;
2453 SourceId = null;
2454 NodeId = null;
2455 PartitionId = null;
2456
2457 if (!ThingRegistryClient.TryDecodeIoTDiscoURI(DiscoUri, out IEnumerable<SearchOperator> Operators2))
2458 {
2459 Tags = null;
2460 return false;
2461 }
2462
2463 List<MetaDataTag> TagsFound = [];
2464
2465 foreach (SearchOperator Operator in Operators2)
2466 {
2467 if (Operator is StringTagEqualTo S)
2468 {
2469 switch (S.Name.ToUpper(CultureInfo.InvariantCulture))
2470 {
2472 Jid = S.Value;
2473 break;
2474
2476 SourceId = S.Value;
2477 break;
2478
2480 NodeId = S.Value;
2481 break;
2482
2484 PartitionId = S.Value;
2485 break;
2486
2487 default:
2488 TagsFound.Add(new MetaDataStringTag(S.Name, S.Value));
2489 break;
2490 }
2491 }
2492 else if (Operator is NumericTagEqualTo N)
2493 TagsFound.Add(new MetaDataNumericTag(N.Name, N.Value));
2494 else
2495 {
2496 Tags = null;
2497 return false;
2498 }
2499 }
2500
2501 Tags = [.. TagsFound];
2502
2503 return !string.IsNullOrEmpty(Jid);
2504 }
2505
2512 public Task<NodeResultEventArgs> ClaimThing(string DiscoUri, bool MakePublic)
2513 {
2514 if (!this.TryDecodeIoTDiscoClaimURI(DiscoUri, out MetaDataTag[]? Tags))
2515 throw new ArgumentException(ServiceRef.Localizer[nameof(AppResources.InvalidIoTDiscoClaimUri)], nameof(DiscoUri));
2516
2517 TaskCompletionSource<NodeResultEventArgs> Result = new();
2518
2519 this.ThingRegistryClient.Mine(MakePublic, Tags, (sender, e) =>
2520 {
2521 Result.TrySetResult(e);
2522 return Task.CompletedTask;
2523 }, null);
2524
2525 return Result.Task;
2526 }
2527
2537 public Task<bool> Disown(string RegistryJid, string ThingJid, string SourceId, string Partition, string NodeId)
2538 {
2539 TaskCompletionSource<bool> Result = new();
2540
2541 this.ThingRegistryClient.Disown(RegistryJid, ThingJid, NodeId, SourceId, Partition, (sender, e) =>
2542 {
2543 Result.TrySetResult(e.Ok);
2544 return Task.CompletedTask;
2545 }, null);
2546
2547 return Result.Task;
2548 }
2549
2557 public async Task<(SearchResultThing[], string?, bool)> Search(int Offset, int MaxCount, string DiscoUri)
2558 {
2559 if (!this.TryDecodeIoTDiscoSearchURI(DiscoUri, out SearchOperator[]? Operators, out string? RegistryJid))
2560 return (Array.Empty<SearchResultThing>(), RegistryJid, false);
2561
2562 (SearchResultThing[] Things, bool More) = await this.Search(Offset, MaxCount, RegistryJid, Operators);
2563
2564 return (Things, RegistryJid, More);
2565 }
2566
2575 public Task<(SearchResultThing[], bool)> Search(int Offset, int MaxCount, string? RegistryJid, params SearchOperator[] Operators)
2576 {
2577 TaskCompletionSource<(SearchResultThing[], bool)> Result = new();
2578
2579 this.ThingRegistryClient.Search(RegistryJid ?? ServiceRef.TagProfile.RegistryJid, Offset, MaxCount, Operators, (sender, e) =>
2580 {
2581 if (e.Ok)
2582 Result.TrySetResult((e.Things, e.More));
2583 else
2584 Result.TrySetException(e.StanzaError ?? new Exception("Unable to perform search."));
2585
2586 return Task.CompletedTask;
2587 }, null);
2588
2589 return Result.Task;
2590 }
2591
2597 public async Task<(SearchResultThing[], string?)> SearchAll(string DiscoUri)
2598 {
2599 if (!this.TryDecodeIoTDiscoSearchURI(DiscoUri, out SearchOperator[]? Operators, out string? RegistryJid))
2600 return (Array.Empty<SearchResultThing>(), RegistryJid);
2601
2602 SearchResultThing[] Things = await this.SearchAll(RegistryJid ?? ServiceRef.TagProfile.RegistryJid, Operators);
2603
2604 return (Things, RegistryJid);
2605 }
2606
2613 public async Task<SearchResultThing[]> SearchAll(string? RegistryJid, params SearchOperator[] Operators)
2614 {
2615 (SearchResultThing[] Things, bool More) = await this.Search(0, Constants.BatchSizes.DeviceBatchSize, RegistryJid, Operators);
2616 if (!More)
2617 return Things;
2618
2619 List<SearchResultThing> Result = [];
2620 int Offset = Things.Length;
2621
2622 Result.AddRange(Things);
2623
2624 while (More)
2625 {
2626 (Things, More) = await this.Search(Offset, Constants.BatchSizes.DeviceBatchSize, RegistryJid, Operators);
2627 Result.AddRange(Things);
2628 Offset += Things.Length;
2629 }
2630
2631 return [.. Result];
2632 }
2633
2634 #endregion
2635
2636 #region Legal Identities
2637
2641 public async Task GenerateNewKeys()
2642 {
2643 await this.ContractsClient.GenerateNewKeys();
2644
2645 if (this.ContractsClient.Client.State == XmppState.Connected)
2647 }
2648
2653 public async Task<IdApplicationAttributesEventArgs> GetIdApplicationAttributes()
2654 {
2656 }
2657
2665 public async Task<LegalIdentity> AddLegalIdentity(RegisterIdentityModel Model, bool GenerateNewKeys,
2666 params LegalIdentityAttachment[] Attachments)
2667 {
2668 if (GenerateNewKeys)
2669 await this.GenerateNewKeys();
2670
2672
2673 foreach (LegalIdentityAttachment Attachment in Attachments)
2674 {
2676 Path.GetFileName(Attachment.FileName!)!, Attachment.ContentType!, Attachment.ContentLength);
2677
2678 if (!e2.Ok)
2679 throw e2.StanzaError ?? new Exception(e2.ErrorText);
2680
2681 await e2.PUT(Attachment.Data, Attachment.ContentType, (int)Constants.Timeouts.UploadFile.TotalMilliseconds);
2682 byte[] Signature = await this.ContractsClient.SignAsync(Attachment.Data, SignWith.CurrentKeys);
2683
2685 }
2686
2688
2689 return Identity;
2690 }
2691
2697 public async Task<LegalIdentity[]> GetLegalIdentities(XmppClient? client = null)
2698 {
2699 if (client is null)
2700 return await this.ContractsClient.GetLegalIdentitiesAsync();
2701 else
2702 {
2703 using ContractsClient cc = new(client, ServiceRef.TagProfile.LegalJid); // No need to load keys for this operation.
2704 return await cc.GetLegalIdentitiesAsync();
2705 }
2706 }
2707
2713 public async Task<LegalIdentity> GetLegalIdentity(CaseInsensitiveString legalIdentityId)
2714 {
2715 ContactInfo Info = await ContactInfo.FindByLegalId(legalIdentityId);
2716
2717 if (Info is not null && Info.LegalIdentity is not null)
2718 return Info.LegalIdentity;
2719
2720 return await this.ContractsClient.GetLegalIdentityAsync(legalIdentityId);
2721 }
2722
2728 public async Task<bool> IsContact(CaseInsensitiveString legalIdentityId)
2729 {
2730 ContactInfo Info = await ContactInfo.FindByLegalId(legalIdentityId);
2731 return (Info is not null && Info.LegalIdentity is not null);
2732 }
2733
2739 public Task<bool> HasPrivateKey(CaseInsensitiveString LegalIdentityId)
2740 {
2741 return this.ContractsClient.HasPrivateKey(LegalIdentityId);
2742 }
2743
2749 public Task<LegalIdentity> ObsoleteLegalIdentity(CaseInsensitiveString legalIdentityId)
2750 {
2751 return this.ContractsClient.ObsoleteLegalIdentityAsync(legalIdentityId);
2752 }
2753
2759 public Task<LegalIdentity> CompromiseLegalIdentity(CaseInsensitiveString legalIdentityId)
2760 {
2761 return this.ContractsClient.CompromisedLegalIdentityAsync(legalIdentityId);
2762 }
2763
2770 public async Task PetitionIdentity(CaseInsensitiveString LegalId, string PetitionId, string Purpose)
2771 {
2773 throw new Exception("No Legal Identity registered.");
2774
2776
2777 this.StartPetition(PetitionId);
2778 await this.ContractsClient.PetitionIdentityAsync(LegalId, PetitionId, Purpose);
2779 }
2780
2781 private void StartPetition(string PetitionId)
2782 {
2783 lock (this.currentPetitions)
2784 {
2785 this.currentPetitions[PetitionId] = true;
2786 }
2787 }
2788
2789 private bool EndPetition(string PetitionId)
2790 {
2791 lock (this.currentPetitions)
2792 {
2793 return this.currentPetitions.Remove(PetitionId);
2794 }
2795 }
2796
2804 public Task SendPetitionIdentityResponse(CaseInsensitiveString LegalId, string PetitionId, string RequestorFullJid, bool Response)
2805 {
2806 return this.ContractsClient.PetitionIdentityResponseAsync(LegalId, PetitionId, RequestorFullJid, Response);
2807 }
2808
2812 public event LegalIdentityEventHandler? LegalIdentityChanged;
2813
2817 public event LegalIdentityEventHandler? IdentityApplicationChanged;
2818
2819 private async Task ContractsClient_IdentityUpdated(object? Sender, LegalIdentityEventArgs e)
2820 {
2821 try
2822 {
2823 if (ServiceRef.TagProfile.LegalIdentity is not null && ServiceRef.TagProfile.LegalIdentity.Id == e.Identity.Id)
2824 {
2825 if (ServiceRef.TagProfile.LegalIdentity.Created > e.Identity.Created)
2826 return;
2827
2828 await ServiceRef.TagProfile.SetLegalIdentity(e.Identity, true);
2829 this.LegalIdentityChanged?.Invoke(this, e);
2830
2831 if (e.Identity.IsDiscarded() && Shell.Current.CurrentState.Location.OriginalString != Constants.Pages.RegistrationPage)
2832 {
2833 MainThread.BeginInvokeOnMainThread(async () =>
2834 {
2835 try
2836 {
2838 ServiceRef.TagProfile.GoToStep(RegistrationStep.ValidatePhone, true);
2839 await Shell.Current.GoToAsync(Constants.Pages.RegistrationPage);
2840 }
2841 catch (Exception ex)
2842 {
2843 ServiceRef.LogService.LogException(ex);
2844 await App.Stop();
2845 }
2846 });
2847 }
2848 }
2849 else if (ServiceRef.TagProfile.IdentityApplication is not null && ServiceRef.TagProfile.IdentityApplication.Id == e.Identity.Id)
2850 {
2851 if (ServiceRef.TagProfile.IdentityApplication.Created > e.Identity.Created)
2852 return;
2853
2854 if (e.Identity.IsDiscarded())
2855 {
2857 this.IdentityApplicationChanged?.Invoke(this, e);
2858 }
2859 else if (e.Identity.IsApproved())
2860 {
2862
2863 await ServiceRef.TagProfile.SetLegalIdentity(e.Identity, true);
2864 await ServiceRef.TagProfile.SetIdentityApplication(null, false);
2865
2866 this.LegalIdentityChanged?.Invoke(this, e);
2867 this.IdentityApplicationChanged?.Invoke(this, e);
2868
2869 if (ToObsolete is not null && !ToObsolete.IsDiscarded())
2870 await this.ObsoleteLegalIdentity(ToObsolete.Id);
2871 }
2872 else
2873 {
2874 await ServiceRef.TagProfile.SetIdentityApplication(e.Identity, false);
2875 this.IdentityApplicationChanged?.Invoke(this, e);
2876 }
2877 }
2878 else if (ServiceRef.TagProfile.LegalIdentity is null)
2879 {
2880 if (e.Identity.IsDiscarded())
2881 return;
2882
2883 await ServiceRef.TagProfile.SetLegalIdentity(e.Identity, true);
2884 this.LegalIdentityChanged?.Invoke(this, e);
2885 }
2886 }
2887 catch (Exception ex)
2888 {
2889 ServiceRef.LogService.LogException(ex);
2890 await ServiceRef.UiService.DisplayException(ex);
2891 }
2892 }
2893
2897 public event LegalIdentityPetitionEventHandler? PetitionForIdentityReceived;
2898
2899 private async Task ContractsClient_PetitionForIdentityReceived(object? Sender, LegalIdentityPetitionEventArgs e)
2900 {
2901 try
2902 {
2903 this.PetitionForIdentityReceived?.Invoke(this, e);
2904 }
2905 catch (Exception ex)
2906 {
2907 ServiceRef.LogService.LogException(ex);
2908 await ServiceRef.UiService.DisplayException(ex);
2909 }
2910 }
2911
2915 public event LegalIdentityPetitionResponseEventHandler? PetitionedIdentityResponseReceived;
2916
2917 private async Task ContractsClient_PetitionedIdentityResponseReceived(object? Sender, LegalIdentityPetitionResponseEventArgs e)
2918 {
2919 try
2920 {
2921 this.EndPetition(e.PetitionId);
2922 this.PetitionedIdentityResponseReceived?.Invoke(this, e);
2923 }
2924 catch (Exception ex)
2925 {
2926 ServiceRef.LogService.LogException(ex);
2927 await ServiceRef.UiService.DisplayException(ex);
2928 }
2929 }
2930
2935 public Task ExportSigningKeys(XmlWriter Output)
2936 {
2937 return this.ContractsClient.ExportKeys(Output);
2938 }
2939
2945 public Task<bool> ImportSigningKeys(XmlElement Xml)
2946 {
2947 return this.ContractsClient.ImportKeys(Xml);
2948 }
2949
2955 public Task<IdentityStatus> ValidateIdentity(LegalIdentity Identity)
2956 {
2957 return this.ContractsClient.ValidateAsync(Identity, true);
2958 }
2959
2960 #endregion
2961
2962 #region Smart Contracts
2963
2964 private readonly Dictionary<CaseInsensitiveString, DateTime> lastContractEvent = [];
2965
2971 {
2972 get
2973 {
2974 if (this.contractsClient is null)
2975 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.LegalServiceNotFound)]);
2976
2977 return this.contractsClient;
2978 }
2979 }
2980
2981 private void RegisterContractsEventHandlers()
2982 {
2983 this.ContractsClient.EnableE2eEncryption(true, false);
2984
2985 this.ContractsClient.IdentityUpdated += this.ContractsClient_IdentityUpdated;
2986 this.ContractsClient.PetitionForIdentityReceived += this.ContractsClient_PetitionForIdentityReceived;
2987 this.ContractsClient.PetitionedIdentityResponseReceived += this.ContractsClient_PetitionedIdentityResponseReceived;
2988 this.ContractsClient.PetitionForContractReceived += this.ContractsClient_PetitionForContractReceived;
2989 this.ContractsClient.PetitionedContractResponseReceived += this.ContractsClient_PetitionedContractResponseReceived;
2990 this.ContractsClient.PetitionForSignatureReceived += this.ContractsClient_PetitionForSignatureReceived;
2991 this.ContractsClient.PetitionedSignatureResponseReceived += this.ContractsClient_PetitionedSignatureResponseReceived;
2992 this.ContractsClient.PetitionForPeerReviewIDReceived += this.ContractsClient_PetitionForPeerReviewIdReceived;
2993 this.ContractsClient.PetitionedPeerReviewIDResponseReceived += this.ContractsClient_PetitionedPeerReviewIdResponseReceived;
2994 this.ContractsClient.PetitionClientUrlReceived += this.ContractsClient_PetitionClientUrlReceived;
2995 this.ContractsClient.ContractProposalReceived += this.ContractsClient_ContractProposalReceived;
2996 this.ContractsClient.ContractUpdated += this.ContractsClient_ContractUpdated;
2997 this.ContractsClient.ContractSigned += this.ContractsClient_ContractSigned;
2998 }
2999
3005 public Task<Contract> GetContract(CaseInsensitiveString ContractId)
3006 {
3007 return this.ContractsClient.GetContractAsync(ContractId);
3008 }
3009
3014 public async Task<string[]> GetCreatedContractReferences()
3015 {
3016 List<string> Result = [];
3017 string[] ContractIds;
3018 int Offset = 0;
3019 int Nr;
3020
3021 do
3022 {
3023 ContractIds = await this.ContractsClient.GetCreatedContractReferencesAsync(Offset, 20);
3024 Result.AddRange(ContractIds);
3025 Nr = ContractIds.Length;
3026 Offset += Nr;
3027 }
3028 while (Nr == 20);
3029
3030 return [.. Result];
3031 }
3032
3037 public async Task<string[]> GetSignedContractReferences()
3038 {
3039 List<string> Result = [];
3040 string[] ContractIds;
3041 int Offset = 0;
3042 int Nr;
3043
3044 do
3045 {
3046 ContractIds = await this.ContractsClient.GetSignedContractReferencesAsync(Offset, 20);
3047 Result.AddRange(ContractIds);
3048 Nr = ContractIds.Length;
3049 Offset += Nr;
3050 }
3051 while (Nr == 20);
3052
3053 return [.. Result];
3054 }
3055
3063 public async Task<Contract> SignContract(Contract Contract, string Role, bool Transferable)
3064 {
3068 {
3069 lock (this.currentTransactions)
3070 {
3071 string TransactionId = Contract.ContractId;
3072 string Currency = Contract["Currency"]?.ToString() ?? string.Empty;
3073
3074 this.currentTransactions[Contract.ContractId] = new PaymentTransaction(TransactionId, Currency);
3075 }
3076 }
3077
3078 Contract Result = await this.ContractsClient.SignContractAsync(Contract, Role, Transferable);
3079 await UpdateContractReference(Result);
3080 return Result;
3081 }
3082
3088 public async Task<Contract> ObsoleteContract(CaseInsensitiveString ContractId)
3089 {
3090 Contract Result = await this.ContractsClient.ObsoleteContractAsync(ContractId);
3091 await UpdateContractReference(Result);
3092 return Result;
3093 }
3094
3110 public async Task<Contract> CreateContract(
3111 CaseInsensitiveString TemplateId,
3112 Part[] Parts,
3114 ContractVisibility Visibility,
3115 ContractParts PartsMode,
3117 Duration ArchiveRequired,
3118 Duration ArchiveOptional,
3119 DateTime? SignAfter,
3120 DateTime? SignBefore,
3121 bool CanActAsTemplate)
3122 {
3123 Contract Result = await this.ContractsClient.CreateContractAsync(TemplateId, Parts, Parameters, Visibility, PartsMode, Duration, ArchiveRequired, ArchiveOptional, SignAfter, SignBefore, CanActAsTemplate);
3124 await UpdateContractReference(Result);
3125 return Result;
3126 }
3127
3133 public async Task<Contract> DeleteContract(CaseInsensitiveString ContractId)
3134 {
3135 Contract Contract = await this.ContractsClient.DeleteContractAsync(ContractId);
3137 return Contract;
3138 }
3139
3146 public Task PetitionContract(CaseInsensitiveString ContractId, string PetitionId, string Purpose)
3147 {
3148 this.StartPetition(PetitionId);
3149 return this.ContractsClient.PetitionContractAsync(ContractId, PetitionId, Purpose);
3150 }
3151
3159 public Task SendPetitionContractResponse(CaseInsensitiveString ContractId, string PetitionId, string RequestorFullJid, bool Response)
3160 {
3161 return this.ContractsClient.PetitionContractResponseAsync(ContractId, PetitionId, RequestorFullJid, Response);
3162 }
3163
3167 public event ContractPetitionEventHandler? PetitionForContractReceived;
3168
3169 private async Task ContractsClient_PetitionForContractReceived(object? Sender, ContractPetitionEventArgs e)
3170 {
3171 try
3172 {
3173 this.PetitionForContractReceived?.Invoke(this, e);
3174 }
3175 catch (Exception ex)
3176 {
3177 ServiceRef.LogService.LogException(ex);
3178 await ServiceRef.UiService.DisplayException(ex);
3179 }
3180 }
3181
3185 public event ContractPetitionResponseEventHandler? PetitionedContractResponseReceived;
3186
3187 private async Task ContractsClient_PetitionedContractResponseReceived(object? Sender, ContractPetitionResponseEventArgs e)
3188 {
3189 try
3190 {
3191 this.EndPetition(e.PetitionId);
3192 this.PetitionedContractResponseReceived?.Invoke(this, e);
3193 }
3194 catch (Exception ex)
3195 {
3196 ServiceRef.LogService.LogException(ex);
3197 await ServiceRef.UiService.DisplayException(ex);
3198 }
3199 }
3200
3206 public DateTime GetTimeOfLastContractEvent(CaseInsensitiveString ContractId)
3207 {
3208 lock (this.lastContractEvent)
3209 {
3210 if (this.lastContractEvent.TryGetValue(ContractId, out DateTime TP))
3211 return TP;
3212 else
3213 return DateTime.MinValue;
3214 }
3215 }
3216
3217 private static async Task UpdateContractReference(Contract Contract)
3218 {
3219 ContractReference Ref = await Database.FindFirstDeleteRest<ContractReference>(
3220 new FilterFieldEqualTo("ContractId", Contract.ContractId));
3221
3222 if (Ref is null)
3223 {
3224 Ref = new ContractReference()
3225 {
3226 ContractId = Contract.ContractId
3227 };
3228
3229 await Ref.SetContract(Contract);
3230 await Database.Insert(Ref);
3231 }
3232 else
3233 {
3234 await Ref.SetContract(Contract);
3235 await Database.Update(Ref);
3236 }
3237
3239 }
3240
3244 public event ContractProposalEventHandler? ContractProposalReceived;
3245
3246 private async Task ContractsClient_ContractProposalReceived(object? Sender, ContractProposalEventArgs e)
3247 {
3248 try
3249 {
3250 this.ContractProposalReceived?.Invoke(this, e);
3251 }
3252 catch (Exception ex)
3253 {
3254 ServiceRef.LogService.LogException(ex);
3255 await ServiceRef.UiService.DisplayException(ex);
3256 }
3257 }
3258
3262 public event ContractReferenceEventHandler? ContractUpdated;
3263
3264 private async Task ContractsClient_ContractUpdated(object? Sender, ContractReferenceEventArgs e)
3265 {
3266 await this.ContractUpdatedOrSigned(e);
3267
3268 try
3269 {
3270 this.ContractUpdated?.Invoke(this, e);
3271 }
3272 catch (Exception ex)
3273 {
3274 ServiceRef.LogService?.LogException(ex);
3275 }
3276 }
3277
3278 private Task ContractUpdatedOrSigned(ContractReferenceEventArgs e)
3279 {
3280 lock (this.lastContractEvent)
3281 {
3282 this.lastContractEvent[e.ContractId] = DateTime.Now;
3283 }
3284
3285 return Task.CompletedTask;
3286 }
3287
3291 public event ContractSignedEventHandler? ContractSigned;
3292
3293 private async Task ContractsClient_ContractSigned(object? Sender, ContractSignedEventArgs e)
3294 {
3295 await this.ContractUpdatedOrSigned(e);
3296
3297 try
3298 {
3299 this.ContractSigned?.Invoke(this, e);
3300 }
3301 catch (Exception ex)
3302 {
3303 ServiceRef.LogService?.LogException(ex);
3304 }
3305 }
3306
3314 public Task SendContractProposal(Contract Contract, string Role, string To, string Message)
3315 {
3316 return this.ContractsClient.SendContractProposal(Contract, Role, To, Message);
3317 }
3318
3319 #endregion
3320
3321 #region Attachments
3322
3330 public Task<KeyValuePair<string, TemporaryFile>> GetAttachment(string Url, SignWith SignWith, TimeSpan Timeout)
3331 {
3332 return this.ContractsClient.GetAttachmentAsync(Url, SignWith, (int)Timeout.TotalMilliseconds);
3333 }
3334
3335 #endregion
3336
3337 #region Peer Review
3338
3346 public async Task PetitionPeerReviewId(CaseInsensitiveString LegalId, LegalIdentity Identity, string PetitionId, string Purpose)
3347 {
3348 await this.ContractsClient.AuthorizeAccessToIdAsync(Identity.Id, LegalId, true);
3349
3350 this.StartPetition(PetitionId);
3351 await this.ContractsClient.PetitionPeerReviewIDAsync(LegalId, Identity, PetitionId, Purpose);
3352 }
3353
3361 public Task<LegalIdentity> AddPeerReviewIdAttachment(LegalIdentity Identity, LegalIdentity ReviewerLegalIdentity, byte[] PeerSignature)
3362 {
3363 return this.ContractsClient.AddPeerReviewIDAttachment(Identity, ReviewerLegalIdentity, PeerSignature);
3364 }
3365
3369 public event SignaturePetitionEventHandler? PetitionForPeerReviewIdReceived;
3370
3371 private async Task ContractsClient_PetitionForPeerReviewIdReceived(object? Sender, SignaturePetitionEventArgs e)
3372 {
3373 try
3374 {
3375 this.PetitionForPeerReviewIdReceived?.Invoke(this, e);
3376 }
3377 catch (Exception ex)
3378 {
3379 ServiceRef.LogService.LogException(ex);
3380 await ServiceRef.UiService.DisplayException(ex);
3381 }
3382 }
3383
3387 public event SignaturePetitionResponseEventHandler? PetitionedPeerReviewIdResponseReceived;
3388
3389 private async Task ContractsClient_PetitionedPeerReviewIdResponseReceived(object? Sender, SignaturePetitionResponseEventArgs e)
3390 {
3391 try
3392 {
3393 this.EndPetition(e.PetitionId);
3394 this.PetitionedPeerReviewIdResponseReceived?.Invoke(this, e);
3395 }
3396 catch (Exception ex)
3397 {
3398 ServiceRef.LogService.LogException(ex);
3399 await ServiceRef.UiService.DisplayException(ex);
3400 }
3401 }
3402
3407 public async Task<ServiceProviderWithLegalId[]> GetServiceProvidersForPeerReviewAsync()
3408 {
3410 }
3411
3418 public async Task SelectPeerReviewService(string ServiceId, string ServiceProvider)
3419 {
3421 }
3422
3423 private readonly Dictionary<string, bool> currentPetitions = [];
3424
3425 private async Task ContractsClient_PetitionClientUrlReceived(object? Sender, PetitionClientUrlEventArgs e)
3426 {
3427 lock (this.currentPetitions)
3428 {
3429 if (!this.currentPetitions.ContainsKey(e.PetitionId))
3430 {
3431 ServiceRef.LogService.LogWarning("Client URL message for a petition is ignored. Petition ID not recognized.",
3432 new KeyValuePair<string, object?>("PetitionId", e.PetitionId),
3433 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
3434 return;
3435 }
3436 }
3437
3438 await App.OpenUrlAsync(e.ClientUrl);
3439 }
3440
3441 #endregion
3442
3443 #region Signatures
3444
3451 public Task<byte[]> Sign(byte[] data, SignWith signWith)
3452 {
3453 return this.ContractsClient.SignAsync(data, signWith);
3454 }
3455
3465 public bool? ValidateSignature(LegalIdentity legalIdentity, byte[] data, byte[] signature)
3466 {
3467 return this.ContractsClient.ValidateSignature(legalIdentity, data, signature);
3468 }
3469
3480 public Task SendPetitionSignatureResponse(CaseInsensitiveString LegalId, byte[] Content, byte[] Signature, string PetitionId, string RequestorFullJid, bool Response)
3481 {
3482 return this.ContractsClient.PetitionSignatureResponseAsync(LegalId, Content, Signature, PetitionId, RequestorFullJid, Response);
3483 }
3484
3488 public event SignaturePetitionEventHandler? PetitionForSignatureReceived;
3489
3490 private async Task ContractsClient_PetitionForSignatureReceived(object? Sender, SignaturePetitionEventArgs e)
3491 {
3492 try
3493 {
3494 this.PetitionForSignatureReceived?.Invoke(this, e);
3495 }
3496 catch (Exception ex)
3497 {
3498 ServiceRef.LogService.LogException(ex);
3499 await ServiceRef.UiService.DisplayException(ex);
3500 }
3501 }
3502
3506 public event SignaturePetitionResponseEventHandler? SignaturePetitionResponseReceived;
3507
3508 private async Task ContractsClient_PetitionedSignatureResponseReceived(object? Sender, SignaturePetitionResponseEventArgs e)
3509 {
3510 try
3511 {
3512 this.EndPetition(e.PetitionId);
3513 this.SignaturePetitionResponseReceived?.Invoke(this, e);
3514 }
3515 catch (Exception ex)
3516 {
3517 ServiceRef.LogService.LogException(ex);
3518 await ServiceRef.UiService.DisplayException(ex);
3519 }
3520 }
3521
3522 #endregion
3523
3524 #region Provisioning
3525
3531 {
3532 get
3533 {
3534 if (this.provisioningClient is null)
3535 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ProvisioningServiceNotFound)]);
3536
3537 return this.provisioningClient;
3538 }
3539 }
3540
3541 private async Task ProvisioningClient_IsFriendQuestion(object? Sender, IsFriendEventArgs e)
3542 {
3543 if (e.From.IndexOfAny(clientChars) < 0)
3545 }
3546
3547 private async Task ProvisioningClient_CanReadQuestion(object? Sender, CanReadEventArgs e)
3548 {
3549 if (e.From.IndexOfAny(clientChars) < 0)
3551 }
3552
3553 private async Task ProvisioningClient_CanControlQuestion(object? Sender, CanControlEventArgs e)
3554 {
3555 if (e.From.IndexOfAny(clientChars) < 0)
3557 }
3558
3559 private static readonly char[] clientChars = ['@', '/'];
3560
3564 public string ProvisioningServiceJid => this.ProvisioningClient.ProvisioningServerAddress;
3565
3577 public void IsFriendResponse(string ProvisioningServiceJID, string JID, string RemoteJID, string Key, bool IsFriend,
3578 RuleRange Range, IqResultEventHandlerAsync Callback, object? State)
3579 {
3580 this.ProvisioningClient.IsFriendResponse(ProvisioningServiceJID, JID, RemoteJID, Key, IsFriend, Range, Callback, State);
3581 }
3582
3595 public void CanControlResponseAll(string ProvisioningServiceJID, string JID, string RemoteJID, string Key, bool CanControl,
3596 string[]? ParameterNames, IThingReference Node, IqResultEventHandlerAsync Callback, object? State)
3597 {
3598 this.ProvisioningClient.CanControlResponseAll(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl, ParameterNames,
3599 Node, Callback, State);
3600 }
3601
3614 public void CanControlResponseCaller(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3615 bool CanControl, string[]? ParameterNames, IThingReference Node, IqResultEventHandlerAsync Callback, object? State)
3616 {
3617 this.ProvisioningClient.CanControlResponseCaller(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
3618 ParameterNames, Node, Callback, State);
3619 }
3620
3633 public void CanControlResponseDomain(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3634 bool CanControl, string[]? ParameterNames, IThingReference Node, IqResultEventHandlerAsync Callback, object? State)
3635 {
3636 this.ProvisioningClient.CanControlResponseDomain(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
3637 ParameterNames, Node, Callback, State);
3638 }
3639
3653 public void CanControlResponseDevice(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3654 bool CanControl, string[]? ParameterNames, string Token, IThingReference Node, IqResultEventHandlerAsync Callback,
3655 object? State)
3656 {
3657 this.ProvisioningClient.CanControlResponseDevice(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
3658 ParameterNames, Token, Node, Callback, State);
3659 }
3660
3674 public void CanControlResponseService(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3675 bool CanControl, string[]? ParameterNames, string Token, IThingReference Node, IqResultEventHandlerAsync Callback,
3676 object? State)
3677 {
3678 this.ProvisioningClient.CanControlResponseService(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
3679 ParameterNames, Token, Node, Callback, State);
3680 }
3681
3695 public void CanControlResponseUser(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3696 bool CanControl, string[]? ParameterNames, string Token, IThingReference Node, IqResultEventHandlerAsync Callback,
3697 object? State)
3698 {
3699 this.ProvisioningClient.CanControlResponseUser(ProvisioningServiceJID, JID, RemoteJID, Key, CanControl,
3700 ParameterNames, Token, Node, Callback, State);
3701 }
3702
3716 public void CanReadResponseAll(string ProvisioningServiceJID, string JID, string RemoteJID, string Key, bool CanRead,
3717 FieldType FieldTypes, string[]? FieldNames, IThingReference Node, IqResultEventHandlerAsync Callback, object? State)
3718 {
3719 this.ProvisioningClient.CanReadResponseAll(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead, FieldTypes, FieldNames,
3720 Node, Callback, State);
3721 }
3722
3736 public void CanReadResponseCaller(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3737 bool CanRead, FieldType FieldTypes, string[]? FieldNames, IThingReference Node, IqResultEventHandlerAsync Callback, object? State)
3738 {
3739 this.ProvisioningClient.CanReadResponseCaller(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
3740 FieldTypes, FieldNames, Node, Callback, State);
3741 }
3742
3756 public void CanReadResponseDomain(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3757 bool CanRead, FieldType FieldTypes, string[]? FieldNames, IThingReference Node, IqResultEventHandlerAsync Callback, object? State)
3758 {
3759 this.ProvisioningClient.CanReadResponseDomain(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
3760 FieldTypes, FieldNames, Node, Callback, State);
3761 }
3762
3777 public void CanReadResponseDevice(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3778 bool CanRead, FieldType FieldTypes, string[]? FieldNames, string Token, IThingReference Node, IqResultEventHandlerAsync Callback,
3779 object? State)
3780 {
3781 this.ProvisioningClient.CanReadResponseDevice(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
3782 FieldTypes, FieldNames, Token, Node, Callback, State);
3783 }
3784
3799 public void CanReadResponseService(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3800 bool CanRead, FieldType FieldTypes, string[]? FieldNames, string Token, IThingReference Node, IqResultEventHandlerAsync Callback,
3801 object? State)
3802 {
3803 this.ProvisioningClient.CanReadResponseService(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
3804 FieldTypes, FieldNames, Token, Node, Callback, State);
3805 }
3806
3821 public void CanReadResponseUser(string ProvisioningServiceJID, string JID, string RemoteJID, string Key,
3822 bool CanRead, FieldType FieldTypes, string[]? FieldNames, string Token, IThingReference Node, IqResultEventHandlerAsync Callback,
3823 object? State)
3824 {
3825 this.ProvisioningClient.CanReadResponseUser(ProvisioningServiceJID, JID, RemoteJID, Key, CanRead,
3826 FieldTypes, FieldNames, Token, Node, Callback, State);
3827 }
3828
3839 public void DeleteDeviceRules(string ServiceJID, string DeviceJID, string NodeId, string SourceId, string Partition,
3840 IqResultEventHandlerAsync Callback, object? State)
3841 {
3842 this.ProvisioningClient.DeleteDeviceRules(ServiceJID, DeviceJID, NodeId, SourceId, Partition, Callback, State);
3843 }
3844
3845 #endregion
3846
3847 #region IoT
3848
3854 {
3855 get
3856 {
3857 if (this.sensorClient is null)
3858 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.SensorServiceNotFound)]);
3859
3860 return this.sensorClient;
3861 }
3862 }
3863
3869 {
3870 get
3871 {
3872 if (this.controlClient is null)
3873 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ControlServiceNotFound)]);
3874
3875 return this.controlClient;
3876 }
3877 }
3878
3884 {
3885 get
3886 {
3887 if (this.concentratorClient is null)
3888 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.ConcentratorServiceNotFound)]);
3889
3890 return this.concentratorClient;
3891 }
3892 }
3893
3900 public Task<(SearchResultThing[], bool)> GetMyDevices(int Offset, int MaxCount)
3901 {
3902 TaskCompletionSource<(SearchResultThing[], bool)> Result = new();
3903
3904 this.ProvisioningClient.GetDevices(Offset, MaxCount, (sender, e) =>
3905 {
3906 if (e.Ok)
3907 Result.TrySetResult((e.Things, e.More));
3908 else
3909 Result.TrySetException(e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetListOfMyDevices)]));
3910
3911 return Task.CompletedTask;
3912 }, null);
3913
3914 return Result.Task;
3915 }
3916
3921 public async Task<SearchResultThing[]> GetAllMyDevices()
3922 {
3923 (SearchResultThing[] Things, bool More) = await this.GetMyDevices(0, Constants.BatchSizes.DeviceBatchSize);
3924 if (!More)
3925 return Things;
3926
3927 List<SearchResultThing> Result = [];
3928 int Offset = Things.Length;
3929
3930 Result.AddRange(Things);
3931
3932 while (More)
3933 {
3934 (Things, More) = await this.GetMyDevices(Offset, Constants.BatchSizes.DeviceBatchSize);
3935 Result.AddRange(Things);
3936 Offset += Things.Length;
3937 }
3938
3939 return [.. Result];
3940 }
3941
3950 public void GetCertificate(string Token, CertificateCallback Callback, object? State)
3951 {
3952 this.ProvisioningClient.GetCertificate(Token, Callback, State);
3953 }
3954
3963 public void GetControlForm(string To, string Language, DataFormResultEventHandler Callback, object? State,
3964 params ThingReference[] Nodes)
3965 {
3966 this.ControlClient.GetForm(To, Language, Callback, State, Nodes);
3967 }
3968
3975 public SensorDataClientRequest RequestSensorReadout(string Destination, FieldType Types)
3976 {
3977 return this.SensorClient.RequestReadout(Destination, Types);
3978 }
3979
3987 public SensorDataClientRequest RequestSensorReadout(string Destination, ThingReference[] Nodes, FieldType Types)
3988 {
3989 return this.SensorClient.RequestReadout(Destination, Nodes, Types);
3990 }
3991
3992 #endregion
3993
3994 #region e-Daler
3995
3996 private readonly Dictionary<string, Wallet.Transaction> currentTransactions = [];
3997 private Balance? lastBalance = null;
3998 private DateTime lastEDalerEvent = DateTime.MinValue;
3999
4005 {
4006 get
4007 {
4008 if (this.eDalerClient is null)
4009 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.EDalerServiceNotFound)]);
4010
4011 return this.eDalerClient;
4012 }
4013 }
4014
4015 private void RegisterEDalerEventHandlers(EDalerClient Client)
4016 {
4017 Client.BalanceUpdated += this.EDalerClient_BalanceUpdated;
4018 Client.BuyEDalerOptionsClientUrlReceived += this.NeuroWallet_BuyEDalerOptionsClientUrlReceived;
4019 Client.BuyEDalerOptionsCompleted += this.NeuroWallet_BuyEDalerOptionsCompleted;
4020 Client.BuyEDalerOptionsError += this.NeuroWallet_BuyEDalerOptionsError;
4021 Client.BuyEDalerClientUrlReceived += this.NeuroWallet_BuyEDalerClientUrlReceived;
4022 Client.BuyEDalerCompleted += this.NeuroWallet_BuyEDalerCompleted;
4023 Client.BuyEDalerError += this.NeuroWallet_BuyEDalerError;
4024 Client.SellEDalerOptionsClientUrlReceived += this.NeuroWallet_SellEDalerOptionsClientUrlReceived;
4025 Client.SellEDalerOptionsCompleted += this.NeuroWallet_SellEDalerOptionsCompleted;
4026 Client.SellEDalerOptionsError += this.NeuroWallet_SellEDalerOptionsError;
4027 Client.SellEDalerClientUrlReceived += this.NeuroWallet_SellEDalerClientUrlReceived;
4028 Client.SellEDalerCompleted += this.NeuroWallet_SellEDalerCompleted;
4029 Client.SellEDalerError += this.NeuroWallet_SellEDalerError;
4030 }
4031
4032 private async Task EDalerClient_BalanceUpdated(object? _, BalanceEventArgs e)
4033 {
4034 this.lastBalance = e.Balance;
4035 this.lastEDalerEvent = DateTime.Now;
4036
4037 BalanceEventHandler? h = this.EDalerBalanceUpdated;
4038 if (h is not null)
4039 {
4040 try
4041 {
4042 await h(this, e);
4043 }
4044 catch (Exception ex)
4045 {
4046 ServiceRef.LogService.LogException(ex);
4047 }
4048 }
4049 }
4050
4054 public event BalanceEventHandler? EDalerBalanceUpdated;
4055
4059 public Balance? LastEDalerBalance => this.lastBalance;
4060
4064 public DateTime LastEDalerEvent => this.lastEDalerEvent;
4065
4073 public bool TryParseEDalerUri(string Uri, out EDalerUri Parsed, out string Reason)
4074 {
4075 return EDalerUri.TryParse(Uri, out Parsed, out Reason);
4076 }
4077
4086 public async Task<string> TryDecryptMessage(byte[] EncryptedMessage, byte[] PublicKey, Guid TransactionId, string RemoteEndpoint)
4087 {
4088 try
4089 {
4090 return await this.EDalerClient.DecryptMessage(EncryptedMessage, PublicKey, TransactionId, RemoteEndpoint);
4091 }
4092 catch (Exception ex)
4093 {
4094 ServiceRef.LogService.LogException(ex);
4095 return string.Empty;
4096 }
4097 }
4098
4104 public Task<EDaler.Transaction> SendEDalerUri(string Uri)
4105 {
4106 return this.EDalerClient.SendEDalerUriAsync(Uri);
4107 }
4108
4114 public Task<(AccountEvent[], bool)> GetEDalerAccountEvents(int MaxCount)
4115 {
4116 return this.EDalerClient.GetAccountEventsAsync(MaxCount);
4117 }
4118
4125 public Task<(AccountEvent[], bool)> GetEDalerAccountEvents(int MaxCount, DateTime From)
4126 {
4127 return this.EDalerClient.GetAccountEventsAsync(MaxCount, From);
4128 }
4129
4134 public Task<Balance> GetEDalerBalance()
4135 {
4136 return this.EDalerClient.GetBalanceAsync();
4137 }
4138
4143 public Task<(decimal, string, PendingPayment[])> GetPendingEDalerPayments()
4144 {
4145 return this.EDalerClient.GetPendingPayments();
4146 }
4147
4157 public Task<string> CreateFullEDalerPaymentUri(string ToBareJid, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays)
4158 {
4159 this.lastEDalerEvent = DateTime.Now;
4160 return this.EDalerClient.CreateFullPaymentUri(ToBareJid, Amount, AmountExtra, Currency, ValidNrDays);
4161 }
4162
4173 public Task<string> CreateFullEDalerPaymentUri(string ToBareJid, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays, string Message)
4174 {
4175 this.lastEDalerEvent = DateTime.Now;
4176 return this.EDalerClient.CreateFullPaymentUri(ToBareJid, Amount, AmountExtra, Currency, ValidNrDays, Message);
4177 }
4178
4188 public Task<string> CreateFullEDalerPaymentUri(LegalIdentity To, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays)
4189 {
4190 this.lastEDalerEvent = DateTime.Now;
4191 return this.EDalerClient.CreateFullPaymentUri(To, Amount, AmountExtra, Currency, ValidNrDays);
4192 }
4193
4204 public Task<string> CreateFullEDalerPaymentUri(LegalIdentity To, decimal Amount, decimal? AmountExtra, string Currency, int ValidNrDays, string PrivateMessage)
4205 {
4206 this.lastEDalerEvent = DateTime.Now;
4207 return this.EDalerClient.CreateFullPaymentUri(To, Amount, AmountExtra, Currency, ValidNrDays, PrivateMessage);
4208 }
4209
4219 public string CreateIncompleteEDalerPayMeUri(string BareJid, decimal? Amount, decimal? AmountExtra, string Currency, string Message)
4220 {
4221 return this.EDalerClient.CreateIncompletePayMeUri(BareJid, Amount, AmountExtra, Currency, Message);
4222 }
4223
4234 public string CreateIncompleteEDalerPayMeUri(LegalIdentity To, decimal? Amount, decimal? AmountExtra, string Currency, string PrivateMessage)
4235 {
4236 return this.EDalerClient.CreateIncompletePayMeUri(To, Amount, AmountExtra, Currency, PrivateMessage);
4237 }
4238
4243 public async Task<IBuyEDalerServiceProvider[]> GetServiceProvidersForBuyingEDalerAsync()
4244 {
4246 }
4247
4255 public async Task<OptionsTransaction> InitiateBuyEDalerGetOptions(string ServiceId, string ServiceProvider)
4256 {
4257 string TransactionId = Guid.NewGuid().ToString();
4258 string SuccessUrl = GenerateNeuroAccessUrl(
4259 new KeyValuePair<string, object?>("cmd", "beos"),
4260 new KeyValuePair<string, object?>("tid", TransactionId),
4261 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4262 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4263 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4264 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4265 string FailureUrl = GenerateNeuroAccessUrl(
4266 new KeyValuePair<string, object?>("cmd", "beof"),
4267 new KeyValuePair<string, object?>("tid", TransactionId),
4268 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4269 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4270 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4271 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4272 string CancelUrl = GenerateNeuroAccessUrl(
4273 new KeyValuePair<string, object?>("cmd", "beoc"),
4274 new KeyValuePair<string, object?>("tid", TransactionId),
4275 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4276 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4277 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4278 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4279
4280 TransactionId = await this.EDalerClient.InitiateGetOptionsBuyEDalerAsync(ServiceId, ServiceProvider, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
4281 OptionsTransaction Result = new(TransactionId);
4282
4283 lock (this.currentTransactions)
4284 {
4285 this.currentTransactions[TransactionId] = Result;
4286 }
4287
4288 return Result;
4289 }
4290
4291 private async Task NeuroWallet_BuyEDalerOptionsClientUrlReceived(object? Sender, BuyEDalerClientUrlEventArgs e)
4292 {
4293 lock (this.currentTransactions)
4294 {
4295 if (!this.currentTransactions.ContainsKey(e.TransactionId))
4296 {
4297 ServiceRef.LogService.LogWarning("Client URL message for getting options for buying eDaler ignored. Transaction ID not recognized.",
4298 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
4299 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
4300 return;
4301 }
4302 }
4303
4304 await Wallet.Transaction.OpenUrl(e.ClientUrl);
4305 }
4306
4307 private Task NeuroWallet_BuyEDalerOptionsCompleted(object? _, PaymentOptionsEventArgs e)
4308 {
4309 this.BuyEDalerGetOptionsCompleted(e.TransactionId, e.Options);
4310 return Task.CompletedTask;
4311 }
4312
4318 public void BuyEDalerGetOptionsCompleted(string TransactionId, IDictionary<CaseInsensitiveString, object>[] Options)
4319 {
4320 Wallet.Transaction? Transaction;
4321
4322 lock (this.currentTransactions)
4323 {
4324 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4325 return;
4326
4327 this.currentTransactions.Remove(TransactionId);
4328 }
4329
4331 OptionsTransaction.Completed(Options);
4332 }
4333
4334 private Task NeuroWallet_BuyEDalerOptionsError(object? _, PaymentErrorEventArgs e)
4335 {
4336 this.BuyEDalerGetOptionsFailed(e.TransactionId, e.Message);
4337 return Task.CompletedTask;
4338 }
4339
4345 public void BuyEDalerGetOptionsFailed(string TransactionId, string Message)
4346 {
4347 Wallet.Transaction? Transaction;
4348
4349 lock (this.currentTransactions)
4350 {
4351 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4352 return;
4353
4354 this.currentTransactions.Remove(TransactionId);
4355 }
4356
4357 Transaction.ErrorReported(Message);
4358 }
4359
4368 public async Task<PaymentTransaction> InitiateBuyEDaler(string ServiceId, string ServiceProvider, decimal Amount, string Currency)
4369 {
4370 string TransactionId = Guid.NewGuid().ToString();
4371 string SuccessUrl = GenerateNeuroAccessUrl(
4372 new KeyValuePair<string, object?>("cmd", "bes"),
4373 new KeyValuePair<string, object?>("tid", TransactionId),
4374 new KeyValuePair<string, object?>("amt", Amount),
4375 new KeyValuePair<string, object?>("cur", Currency),
4376 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4377 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4378 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4379 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4380 string FailureUrl = GenerateNeuroAccessUrl(
4381 new KeyValuePair<string, object?>("cmd", "bef"),
4382 new KeyValuePair<string, object?>("tid", TransactionId),
4383 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4384 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4385 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4386 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4387 string CancelUrl = GenerateNeuroAccessUrl(
4388 new KeyValuePair<string, object?>("cmd", "bec"),
4389 new KeyValuePair<string, object?>("tid", TransactionId),
4390 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4391 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4392 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4393 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4394
4395 TransactionId = await this.EDalerClient.InitiateBuyEDalerAsync(ServiceId, ServiceProvider, Amount, Currency, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
4396 PaymentTransaction Result = new(TransactionId, Currency);
4397
4398 lock (this.currentTransactions)
4399 {
4400 this.currentTransactions[TransactionId] = Result;
4401 }
4402
4403 return Result;
4404 }
4405
4406 private static string GenerateNeuroAccessUrl(params KeyValuePair<string, object?>[] Claims)
4407 {
4408 string Token = ServiceRef.CryptoService.GenerateJwtToken(Claims);
4409 return Constants.UriSchemes.NeuroAccess + ":" + Token;
4410 }
4411
4412 private async Task NeuroWallet_BuyEDalerClientUrlReceived(object? Sender, BuyEDalerClientUrlEventArgs e)
4413 {
4414 lock (this.currentTransactions)
4415 {
4416 if (!this.currentTransactions.ContainsKey(e.TransactionId))
4417 {
4418 ServiceRef.LogService.LogWarning("Client URL message for buying eDaler ignored. Transaction ID not recognized.",
4419 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
4420 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
4421 return;
4422 }
4423 }
4424
4425 await Wallet.Transaction.OpenUrl(e.ClientUrl);
4426 }
4427
4428 private Task NeuroWallet_BuyEDalerCompleted(object? _, PaymentCompletedEventArgs e)
4429 {
4430 this.BuyEDalerCompleted(e.TransactionId, e.Amount, e.Currency);
4431 return Task.CompletedTask;
4432 }
4433
4440 public void BuyEDalerCompleted(string TransactionId, decimal Amount, string Currency)
4441 {
4442 Wallet.Transaction? Transaction;
4443
4444 lock (this.currentTransactions)
4445 {
4446 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4447 return;
4448
4449 this.currentTransactions.Remove(TransactionId);
4450 }
4451
4453 PaymentTransaction.Completed(Amount, Currency);
4454 }
4455
4456 private Task NeuroWallet_BuyEDalerError(object? _, PaymentErrorEventArgs e)
4457 {
4458 this.BuyEDalerFailed(e.TransactionId, e.Message);
4459 return Task.CompletedTask;
4460 }
4461
4467 public void BuyEDalerFailed(string TransactionId, string Message)
4468 {
4469 Wallet.Transaction? Transaction;
4470
4471 lock (this.currentTransactions)
4472 {
4473 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4474 return;
4475
4476 this.currentTransactions.Remove(TransactionId);
4477 }
4478
4479 Transaction.ErrorReported(Message);
4480 }
4481
4486 public async Task<ISellEDalerServiceProvider[]> GetServiceProvidersForSellingEDalerAsync()
4487 {
4489 }
4490
4498 public async Task<OptionsTransaction> InitiateSellEDalerGetOptions(string ServiceId, string ServiceProvider)
4499 {
4500 string TransactionId = Guid.NewGuid().ToString();
4501 string SuccessUrl = GenerateNeuroAccessUrl(
4502 new KeyValuePair<string, object?>("cmd", "seos"),
4503 new KeyValuePair<string, object?>("tid", TransactionId),
4504 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4505 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4506 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4507 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4508 string FailureUrl = GenerateNeuroAccessUrl(
4509 new KeyValuePair<string, object?>("cmd", "seof"),
4510 new KeyValuePair<string, object?>("tid", TransactionId),
4511 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4512 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4513 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4514 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4515 string CancelUrl = GenerateNeuroAccessUrl(
4516 new KeyValuePair<string, object?>("cmd", "seoc"),
4517 new KeyValuePair<string, object?>("tid", TransactionId),
4518 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4519 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4520 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4521 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4522
4523 TransactionId = await this.EDalerClient.InitiateGetOptionsSellEDalerAsync(ServiceId, ServiceProvider, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
4524 OptionsTransaction Result = new(TransactionId);
4525
4526 lock (this.currentTransactions)
4527 {
4528 this.currentTransactions[TransactionId] = Result;
4529 }
4530
4531 return Result;
4532 }
4533
4534 private async Task NeuroWallet_SellEDalerOptionsClientUrlReceived(object? Sender, SellEDalerClientUrlEventArgs e)
4535 {
4536 lock (this.currentTransactions)
4537 {
4538 if (!this.currentTransactions.ContainsKey(e.TransactionId))
4539 {
4540 ServiceRef.LogService.LogWarning("Client URL message for getting options for selling eDaler ignored. Transaction ID not recognized.",
4541 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
4542 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
4543 return;
4544 }
4545 }
4546
4547 await Wallet.Transaction.OpenUrl(e.ClientUrl);
4548 }
4549
4550 private Task NeuroWallet_SellEDalerOptionsCompleted(object? _, PaymentOptionsEventArgs e)
4551 {
4552 this.SellEDalerGetOptionsCompleted(e.TransactionId, e.Options);
4553 return Task.CompletedTask;
4554 }
4555
4561 public void SellEDalerGetOptionsCompleted(string TransactionId, IDictionary<CaseInsensitiveString, object>[] Options)
4562 {
4563 Wallet.Transaction? Transaction;
4564
4565 lock (this.currentTransactions)
4566 {
4567 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4568 return;
4569
4570 this.currentTransactions.Remove(TransactionId);
4571 }
4572
4574 OptionsTransaction.Completed(Options);
4575 }
4576
4577 private Task NeuroWallet_SellEDalerOptionsError(object? _, PaymentErrorEventArgs e)
4578 {
4579 this.SellEDalerGetOptionsFailed(e.TransactionId, e.Message);
4580 return Task.CompletedTask;
4581 }
4582
4588 public void SellEDalerGetOptionsFailed(string TransactionId, string Message)
4589 {
4590 Wallet.Transaction? Transaction;
4591
4592 lock (this.currentTransactions)
4593 {
4594 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4595 return;
4596
4597 this.currentTransactions.Remove(TransactionId);
4598 }
4599
4600 Transaction.ErrorReported(Message);
4601 }
4602
4611 public async Task<PaymentTransaction> InitiateSellEDaler(string ServiceId, string ServiceProvider, decimal Amount, string Currency)
4612 {
4613 string TransactionId = Guid.NewGuid().ToString();
4614 string SuccessUrl = GenerateNeuroAccessUrl(
4615 new KeyValuePair<string, object?>("cmd", "ses"),
4616 new KeyValuePair<string, object?>("tid", TransactionId),
4617 new KeyValuePair<string, object?>("amt", Amount),
4618 new KeyValuePair<string, object?>("cur", Currency),
4619 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4620 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4621 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4622 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4623 string FailureUrl = GenerateNeuroAccessUrl(
4624 new KeyValuePair<string, object?>("cmd", "sef"),
4625 new KeyValuePair<string, object?>("tid", TransactionId),
4626 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4627 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4628 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4629 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4630 string CancelUrl = GenerateNeuroAccessUrl(
4631 new KeyValuePair<string, object?>("cmd", "sec"),
4632 new KeyValuePair<string, object?>("tid", TransactionId),
4633 new KeyValuePair<string, object?>(JwtClaims.ClientId, ServiceRef.CryptoService.DeviceID),
4634 new KeyValuePair<string, object?>(JwtClaims.Issuer, ServiceRef.CryptoService.DeviceID),
4635 new KeyValuePair<string, object?>(JwtClaims.Subject, ServiceRef.XmppService.BareJid),
4636 new KeyValuePair<string, object?>(JwtClaims.ExpirationTime, (int)DateTime.UtcNow.AddHours(1).Subtract(JSON.UnixEpoch).TotalSeconds));
4637
4638 TransactionId = await this.EDalerClient.InitiateSellEDalerAsync(ServiceId, ServiceProvider, Amount, Currency, TransactionId, SuccessUrl, FailureUrl, CancelUrl);
4639 PaymentTransaction Result = new(TransactionId, Currency);
4640
4641 lock (this.currentTransactions)
4642 {
4643 this.currentTransactions[TransactionId] = Result;
4644 }
4645
4646 return Result;
4647 }
4648
4649 private async Task NeuroWallet_SellEDalerClientUrlReceived(object? Sender, SellEDalerClientUrlEventArgs e)
4650 {
4651 lock (this.currentTransactions)
4652 {
4653 if (!this.currentTransactions.ContainsKey(e.TransactionId))
4654 {
4655 ServiceRef.LogService.LogWarning("Client URL message ignored. Transaction ID not recognized.",
4656 new KeyValuePair<string, object?>("TransactionId", e.TransactionId),
4657 new KeyValuePair<string, object?>("ClientUrl", e.ClientUrl));
4658 return;
4659 }
4660 }
4661
4662 await Wallet.Transaction.OpenUrl(e.ClientUrl);
4663 }
4664
4665 private Task NeuroWallet_SellEDalerError(object? _, PaymentErrorEventArgs e)
4666 {
4667 this.SellEDalerFailed(e.TransactionId, e.Message);
4668 return Task.CompletedTask;
4669 }
4670
4676 public void SellEDalerFailed(string TransactionId, string Message)
4677 {
4678 Wallet.Transaction? Transaction;
4679
4680 lock (this.currentTransactions)
4681 {
4682 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4683 return;
4684
4685 this.currentTransactions.Remove(TransactionId);
4686 }
4687
4688 Transaction.ErrorReported(Message);
4689 }
4690
4691 private Task NeuroWallet_SellEDalerCompleted(object? _, PaymentCompletedEventArgs e)
4692 {
4693 this.SellEDalerCompleted(e.TransactionId, e.Amount, e.Currency);
4694 return Task.CompletedTask;
4695 }
4696
4703 public void SellEDalerCompleted(string TransactionId, decimal Amount, string Currency)
4704 {
4705 Wallet.Transaction? Transaction;
4706
4707 lock (this.currentTransactions)
4708 {
4709 if (!this.currentTransactions.TryGetValue(TransactionId, out Transaction))
4710 return;
4711
4712 this.currentTransactions.Remove(TransactionId);
4713 }
4714
4716 PaymentTransaction.Completed(Amount, Currency);
4717 }
4718
4719 #endregion
4720
4721 #region Neuro-Features
4722
4723 private DateTime lastTokenEvent = DateTime.MinValue;
4724
4729 {
4730 get
4731 {
4732 if (this.neuroFeaturesClient is null)
4733 throw new InvalidOperationException(ServiceRef.Localizer[nameof(AppResources.NeuroFeaturesServiceNotFound)]);
4734
4735 return this.neuroFeaturesClient;
4736 }
4737 }
4738
4739 private void RegisterNeuroFeatureEventHandlers(NeuroFeaturesClient Client)
4740 {
4741 Client.TokenAdded += this.NeuroFeaturesClient_TokenAdded;
4742 Client.TokenRemoved += this.NeuroFeaturesClient_TokenRemoved;
4743
4744 Client.StateUpdated += this.NeuroFeaturesClient_StateUpdated;
4745 Client.VariablesUpdated += this.NeuroFeaturesClient_VariablesUpdated;
4746 }
4747
4751 public DateTime LastNeuroFeatureEvent => this.lastTokenEvent;
4752
4753 private async Task NeuroFeaturesClient_TokenRemoved(object _, NeuroFeatures.TokenEventArgs e)
4754 {
4755 this.lastTokenEvent = DateTime.Now;
4756
4757 NeuroFeatures.TokenEventHandler? h = this.NeuroFeatureRemoved;
4758 if (h is not null)
4759 {
4760 try
4761 {
4762 await h(this, e);
4763 }
4764 catch (Exception ex)
4765 {
4766 ServiceRef.LogService.LogException(ex);
4767 }
4768 }
4769 }
4770
4774 public event NeuroFeatures.TokenEventHandler? NeuroFeatureRemoved;
4775
4776 private async Task NeuroFeaturesClient_TokenAdded(object _, NeuroFeatures.TokenEventArgs e)
4777 {
4778 this.lastTokenEvent = DateTime.Now;
4779
4780 NeuroFeatures.TokenEventHandler? h = this.NeuroFeatureAdded;
4781 if (h is not null)
4782 {
4783 try
4784 {
4785 await h(this, e);
4786 }
4787 catch (Exception ex)
4788 {
4789 ServiceRef.LogService.LogException(ex);
4790 }
4791 }
4792 }
4793
4797 public event NeuroFeatures.TokenEventHandler? NeuroFeatureAdded;
4798
4799 private async Task NeuroFeaturesClient_VariablesUpdated(object? _, VariablesUpdatedEventArgs e)
4800 {
4801 VariablesUpdatedEventHandler? h = this.NeuroFeatureVariablesUpdated;
4802 if (h is not null)
4803 {
4804 try
4805 {
4806 await h(this, e);
4807 }
4808 catch (Exception ex)
4809 {
4810 ServiceRef.LogService.LogException(ex);
4811 }
4812 }
4813 }
4814
4818 public event VariablesUpdatedEventHandler? NeuroFeatureVariablesUpdated;
4819
4820 private async Task NeuroFeaturesClient_StateUpdated(object? _, NewStateEventArgs e)
4821 {
4822 NewStateEventHandler? h = this.NeuroFeatureStateUpdated;
4823 if (h is not null)
4824 {
4825 try
4826 {
4827 await h(this, e);
4828 }
4829 catch (Exception ex)
4830 {
4831 ServiceRef.LogService.LogException(ex);
4832 }
4833 }
4834 }
4835
4839 public event NewStateEventHandler? NeuroFeatureStateUpdated;
4840
4845 public Task<TokensEventArgs> GetNeuroFeatures()
4846 {
4847 return this.GetNeuroFeatures(0, int.MaxValue);
4848 }
4849
4856 public Task<TokensEventArgs> GetNeuroFeatures(int Offset, int MaxCount)
4857 {
4858 return this.NeuroFeaturesClient.GetTokensAsync(Offset, MaxCount);
4859 }
4860
4865 public Task<string[]> GetNeuroFeatureReferences()
4866 {
4867 return this.GetNeuroFeatureReferences(0, int.MaxValue);
4868 }
4869
4876 public Task<string[]> GetNeuroFeatureReferences(int Offset, int MaxCount)
4877 {
4878 return this.NeuroFeaturesClient.GetTokenReferencesAsync(Offset, MaxCount);
4879 }
4880
4885 public Task<TokenTotalsEventArgs> GetNeuroFeatureTotals()
4886 {
4887 return this.NeuroFeaturesClient.GetTotalsAsync();
4888 }
4889
4895 public Task<TokensEventArgs> GetNeuroFeaturesForContract(string ContractId)
4896 {
4897 return this.NeuroFeaturesClient.GetContractTokensAsync(ContractId);
4898 }
4899
4907 public Task<TokensEventArgs> GetNeuroFeaturesForContract(string ContractId, int Offset, int MaxCount)
4908 {
4909 return this.NeuroFeaturesClient.GetContractTokensAsync(ContractId, Offset, MaxCount);
4910 }
4911
4917 public Task<string[]> GetNeuroFeatureReferencesForContract(string ContractId)
4918 {
4920 }
4921
4929 public Task<string[]> GetNeuroFeatureReferencesForContract(string ContractId, int Offset, int MaxCount)
4930 {
4931 return this.NeuroFeaturesClient.GetContractTokenReferencesAsync(ContractId, Offset, MaxCount);
4932 }
4933
4939 public Task<Token> GetNeuroFeature(string TokenId)
4940 {
4941 return this.NeuroFeaturesClient.GetTokenAsync(TokenId);
4942 }
4943
4949 public Task<TokenEvent[]> GetNeuroFeatureEvents(string TokenId)
4950 {
4951 return this.GetNeuroFeatureEvents(TokenId, 0, int.MaxValue);
4952 }
4953
4961 public Task<TokenEvent[]> GetNeuroFeatureEvents(string TokenId, int Offset, int MaxCount)
4962 {
4963 return this.NeuroFeaturesClient.GetEventsAsync(TokenId, Offset, MaxCount);
4964 }
4965
4971 public Task AddNeuroFeatureTextNote(string TokenId, string TextNote)
4972 {
4973 return this.AddNeuroFeatureTextNote(TokenId, TextNote, false);
4974 }
4975
4984 public Task AddNeuroFeatureTextNote(string TokenId, string TextNote, bool Personal)
4985 {
4986 this.lastTokenEvent = DateTime.Now;
4987
4988 return this.NeuroFeaturesClient.AddTextNoteAsync(TokenId, TextNote, Personal);
4989 }
4990
4996 public Task AddNeuroFeatureXmlNote(string TokenId, string XmlNote)
4997 {
4998 return this.AddNeuroFeatureXmlNote(TokenId, XmlNote, false);
4999 }
5000
5009 public Task AddNeuroFeatureXmlNote(string TokenId, string XmlNote, bool Personal)
5010 {
5011 this.lastTokenEvent = DateTime.Now;
5012
5013 return this.NeuroFeaturesClient.AddXmlNoteAsync(TokenId, XmlNote, Personal);
5014 }
5015
5020 public Task<CreationAttributesEventArgs> GetNeuroFeatureCreationAttributes()
5021 {
5023 }
5024
5029 public async Task<string> GenerateNeuroFeatureStateDiagramReport(string TokenId)
5030 {
5031 ReportEventArgs e = await this.NeuroFeaturesClient.GenerateStateDiagramAsync(TokenId, ReportFormat.Markdown);
5032 if (!e.Ok)
5033 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetStateDiagram)]);
5034
5035 return await e.ReportText.MarkdownToXaml();
5036 }
5037
5042 public async Task<string> GenerateNeuroFeatureProfilingReport(string TokenId)
5043 {
5044 ReportEventArgs e = await this.NeuroFeaturesClient.GenerateProfilingReportAsync(TokenId, ReportFormat.Markdown);
5045 if (!e.Ok)
5046 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetProfiling)]);
5047
5048 return await e.ReportText.MarkdownToXaml();
5049 }
5050
5055 public async Task<string> GenerateNeuroFeaturePresentReport(string TokenId)
5056 {
5057 ReportEventArgs e = await this.NeuroFeaturesClient.GeneratePresentReportAsync(TokenId, ReportFormat.Markdown);
5058 if (!e.Ok)
5059 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetPresent)]);
5060
5061 return await e.ReportText.MarkdownToXaml();
5062 }
5063
5068 public async Task<string> GenerateNeuroFeatureHistoryReport(string TokenId)
5069 {
5070 ReportEventArgs e = await this.NeuroFeaturesClient.GenerateHistoryReportAsync(TokenId, ReportFormat.Markdown);
5071 if (!e.Ok)
5072 throw e.StanzaError ?? new Exception(ServiceRef.Localizer[nameof(AppResources.UnableToGetHistory)]);
5073
5074 return await e.ReportText.MarkdownToXaml();
5075 }
5076
5082 public Task<CurrentStateEventArgs> GetNeuroFeatureCurrentState(string TokenId)
5083 {
5084 return this.NeuroFeaturesClient.GetCurrentStateAsync(TokenId);
5085 }
5086
5087 #endregion
5088
5089 #region Private XML
5090
5098 public Task SavePrivateXml(string Xml)
5099 {
5100 return this.xmppClient?.SetPrivateXmlElementAsync(Xml)
5101 ?? throw new Exception("Not connected to XMPP network.");
5102 }
5103
5111 public Task SavePrivateXml(XmlElement Xml)
5112 {
5113 return this.xmppClient?.SetPrivateXmlElementAsync(Xml)
5114 ?? throw new Exception("Not connected to XMPP network.");
5115 }
5116
5124 public async Task<XmlElement?> LoadPrivateXml(string LocalName, string Namespace)
5125 {
5126 return await this.XmppClient.GetPrivateXmlElementAsync(LocalName, Namespace);
5127 }
5128
5134 public Task DeletePrivateXml(string LocalName, string Namespace)
5135 {
5136 StringBuilder Xml = new();
5137
5138 Xml.Append('<');
5139 Xml.Append(XML.Encode(LocalName));
5140 Xml.Append(" xmlns='");
5141 Xml.Append(XML.Encode(Namespace));
5142 Xml.Append("'/>");
5143 return this.SavePrivateXml(Xml.ToString());
5144 }
5145
5146 #endregion
5147
5148 }
5149}
Account event
Definition: AccountEvent.cs:16
Contains information about a balance.
Definition: Balance.cs:11
eDaler XMPP client.
Definition: EDalerClient.cs:24
async Task< string > DecryptMessage(byte[] EncryptedMessage, byte[] PublicKey, Guid TransactionId)
Decrypts a message that was aimed at the client using the current keys.
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:28
Task< Balance > GetBalanceAsync()
Gets the current balance of the eDaler wallet associated with the account.
override void Dispose()
IDisposable.Dispose
Definition: EDalerClient.cs:67
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.
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
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
static Task SetRegistrationPageAsync()
Switches the application to the on-boarding experience.
Definition: App.xaml.cs:658
static Task< bool > OpenUrlAsync(string Url)
Opens an URL in the application.
Definition: App.xaml.cs:919
static new? App Current
Gets the current application, type casted to App.
Definition: App.xaml.cs:99
const int DeviceBatchSize
Number of devices to load in a single batch.
Definition: Constants.cs:784
Machine-readable names in contracts.
Definition: Constants.cs:791
const string PaymentInstructionsNamespace
Namespace for payment instructions
Definition: Constants.cs:795
const string BuyEDaler
Local name for contracts for buying eDaler.
Definition: Constants.cs:800
const string SellEDaler
Local name for contracts for selling eDaler.
Definition: Constants.cs:805
const string OnboardingDomain
Neuro-Access onboarding domain.
Definition: Constants.cs:258
static readonly TimeSpan Reconnect
Reconnect interval
Definition: Constants.cs:550
const string Default
The default language code.
Definition: Constants.cs:83
Absolute paths to important pages.
Definition: Constants.cs:733
const string RegistrationPage
Path to registration page.
Definition: Constants.cs:742
const int DefaultImageHeight
The default height to use when generating QR Code images.
Definition: Constants.cs:866
const int DefaultImageWidth
The default width to use when generating QR Code images.
Definition: Constants.cs:862
Runtime setting key names.
Definition: Constants.cs:877
const string TransferIdCodeSent
Transfer ID code
Definition: Constants.cs:896
static readonly TimeSpan XmppConnect
XMPP Connect timeout
Definition: Constants.cs:571
static readonly TimeSpan UploadFile
Upload file timeout
Definition: Constants.cs:581
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Contains information about a contact.
Definition: ContactInfo.cs:21
static async Task< string > GetFriendlyName(CaseInsensitiveString RemoteId)
Gets the friendly name of a remote identity (Legal ID or Bare JID).
Definition: ContactInfo.cs:257
LegalIdentity? LegalIdentity
Legal Identity object.
Definition: ContactInfo.cs:81
bool? AllowSubscriptionFrom
Allow subscriptions from this contact
Definition: ContactInfo.cs:145
static Task< ContactInfo > FindByLegalId(string LegalId)
Finds information about a contact, given its Legal ID.
Definition: ContactInfo.cs:247
static Task< ContactInfo > FindByBareJid(string BareJid)
Finds information about a contact, given its Bare JID.
Definition: ContactInfo.cs:220
CaseInsensitiveString LegalId
Legal ID of contact.
Definition: ContactInfo.cs:71
Contains a local reference to a contract that the user has created or signed.
async Task SetContract(Contract Contract)
Sets a parsed contract.
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.
Contains information about a request to become "friends", i.e. subscribe to presence.
Contains information about a push notification token.
PushMessagingService Service
Service issuing the token
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:103
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static IPushNotificationService PushNotificationService
Service for managing push notifications from the network.
Definition: ServiceRef.cs:223
static INotificationService NotificationService
Service for managing notifications for the user.
Definition: ServiceRef.cs:211
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static ICryptoService CryptoService
Crypto service.
Definition: ServiceRef.cs:163
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
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
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 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:43
Helps with parsing of commong data types.
Definition: CommonTypes.cs:13
static readonly char[] CRLF
Contains the CR LF character sequence.
Definition: CommonTypes.cs:17
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< object > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
Helps with common JSON-related tasks.
Definition: JSON.cs:14
static readonly DateTime UnixEpoch
Unix Date and Time epoch, starting at 1970-01-01T00:00:00Z
Definition: JSON.cs:18
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:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
static bool TryParse(string s, out DateTime Value)
Tries to decode a string encoded DateTime.
Definition: XML.cs:744
Class representing an event.
Definition: Event.cs:10
Filters incoming events and passes remaining events to a secondary event sink.
Definition: EventFilter.cs:9
IEventSink SecondarySink
Secondary event sink receiving the events passing the filter.
Definition: EventFilter.cs:164
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Register(IEventSink EventSink)
Registers an event sink with the event log. Call Unregister(IEventSink) to unregister it,...
Definition: Log.cs:29
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:566
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:818
static bool Unregister(IEventSink EventSink)
Unregisters an event sink from the event log.
Definition: Log.cs:46
static void Alert(string Message, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, string StackTrace, params KeyValuePair< string, object >[] Tags)
Logs an alert event.
Definition: Log.cs:1227
virtual void Dispose()
IDisposable.Dispose()
Definition: LogObject.cs:1134
Event sink sending events to a destination over the XMPP network.
const string NamespaceEventLogging
urn:xmpp:eventlog
Sniffer that stores events in memory.
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
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:25
Contains a reference to an attachment assigned to a legal object.
Definition: Attachment.cs:9
string FileName
Filename of attachment.
Definition: Attachment.cs:56
string ContentType
Internet Content Type of binary attachment.
Definition: Attachment.cs:47
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
EndpointSecurity LocalE2eEndpoint
Endpoint used for End-to-End encryption.
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< IdentityStatus > ValidateAsync(LegalIdentity Identity)
Validates a legal identity.
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....
async Task< LegalIdentity > AddLegalIdAttachmentAsync(string LegalId, string GetUrl, byte[] Signature)
Adds an attachment to a newly created legal identity.
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< 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 EnableE2eEncryption(bool UseLocalKeys)
Defines if End-to-End encryption should use the keys used by the contracts client to perform signatur...
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...
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.
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.
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.
bool Ok
If the response is an OK result response (true), or an error response (false).
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.
Event arguments for HTTP File Upload callback methods.
async Task PUT(byte[] Content, string ContentType, int Timeout)
Uploads file content to the server.
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
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:9
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.
Client managing communication with a Publish/Subscribe component. https://xmpp.org/extensions/xep-006...
Definition: PubSubClient.cs:20
const string NamespaceDelayedDelivery
urn:xmpp:delay (XEP-0203)
Definition: PubSubClient.cs:44
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.
override void Dispose()
Disposes of the extension.
async Task ClearRulesAsync()
Clears available push notification rules for the client.
Class redirecting sniffer output to a remote client.
override Task Information(DateTime Timestamp, string Comment)
Called to inform the viewer of something.
override Task ReceiveText(DateTime Timestamp, string Text)
Called when text has been received.
override Task TransmitText(DateTime Timestamp, string Text)
Called when text has been transmitted.
override Task Error(DateTime Timestamp, string Error)
Called to inform the viewer of an error state.
override Task Warning(DateTime Timestamp, string Warning)
Called to inform the viewer of a warning state.
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...
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:59
Task RequestRevokePresenceSubscription(string BareJid)
Requests a previous presence subscription request revoked.
Definition: XmppClient.cs:5007
Task ChangePassword(string NewPassword)
Changes the password of the current user.
Definition: XmppClient.cs:4256
Task< ServiceItemsDiscoveryEventArgs > ServiceItemsDiscoveryAsync(string To)
Performs an asynchronous service items discovery request
Definition: XmppClient.cs:6241
XmppState State
Current state of connection.
Definition: XmppClient.cs:985
Task RemoveRosterItem(string BareJID)
Removes an item from the roster.
Definition: XmppClient.cs:4631
async void Dispose()
Closes the connection and disposes of all resources.
Definition: XmppClient.cs:1103
Task< ServiceDiscoveryEventArgs > ServiceDiscoveryAsync(string To)
Performs an asynchronous service discovery request
Definition: XmppClient.cs:6003
Task RequestPresenceSubscription(string BareJid)
Requests subscription of presence information from a contact.
Definition: XmppClient.cs:4919
async Task< XmlElement > GetPrivateXmlElementAsync(string LocalName, string Namespace)
Gets an XML element from the Private XML Storage for the current account.
Definition: XmppClient.cs:7427
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:4542
async Task< XmlElement > IqSetAsync(string To, string Xml)
Performs an asynchronous IQ Set request/response operation.
Definition: XmppClient.cs:4064
Task RequestPresenceUnsubscription(string BareJid)
Requests unssubscription of presence information from a contact.
Definition: XmppClient.cs:4980
Task SendServiceDiscoveryRequest(string To, EventHandlerAsync< ServiceDiscoveryEventArgs > Callback, object State)
Sends a service discovery request
Definition: XmppClient.cs:5806
const string NamespaceQuickLogin
http://waher.se/Schema/QL.xsd
Definition: XmppClient.cs:173
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:4886
void AllowRegistration()
If registration of a new account is allowed. Requires a password. Having a password hash is not suffi...
Definition: XmppClient.cs:3533
RosterItem GetRosterItem(string BareJID)
Gets a roster item.
Definition: XmppClient.cs:4522
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:19
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:879
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:57
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
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:310
Simple ledger that records anything that happens in the database to XML files in the program data fol...
Task Start()
Called when processing starts.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:14
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:560
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.
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 CheckPushNotificationToken(TokenInformation? TokenInformation=null)
Checks if the Push Notification Token is current and registered properly.
The TAG Profile is the heart of the digital identity for a specific user/device. Use this instance to...
Definition: ITagProfile.cs:17
string? NeuroFeaturesJid
The XMPP server's Neuro-Features service JID.
Definition: ITagProfile.cs:151
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:131
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:136
string? Account
The account name for this profile
Definition: ITagProfile.cs:101
void SetDomain(string DomainName, bool DefaultXmppConnectivity, string Key, string Secret)
Set the domain name to connect to.
string? RegistryJid
The Thing Registry JID
Definition: ITagProfile.cs:121
string? EDalerJid
The XMPP server's eDaler service JID.
Definition: ITagProfile.cs:146
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:216
string? LogJid
The XMPP server's log Jid.
Definition: ITagProfile.cs:141
string? ApiKey
API Key, for creating new account.
Definition: ITagProfile.cs:61
string? LegalJid
The Jabber Legal JID for this user/profile.
Definition: ITagProfile.cs:116
string? ApiSecret
API Secret, for creating new account.
Definition: ITagProfile.cs:66
bool DefaultXmppConnectivity
If connecting to the domain can be done using default parameters (host=domain, default c2s port).
Definition: ITagProfile.cs:56
bool SupportsPushNotification
If Push Notification is supported by server.
Definition: ITagProfile.cs:156
string? XmppPasswordHash
A hash of the current XMPP password.
Definition: ITagProfile.cs:106
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:111
string? ProvisioningJid
The XMPP server's provisioning Jid.
Definition: ITagProfile.cs:126
LegalIdentity? LegalIdentity
The legal identity of the current user/profile.
Definition: ITagProfile.cs:211
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:51
string BareJid
The Bare Jid of the current connection, or null.
Definition: IXmppService.cs:72
Task< HttpFileUploadEventArgs > RequestUploadSlotAsync(string FileName, string ContentType, long ContentSize)
Uploads a file to the upload component.
Task Flush()
Persists any pending changes.
Interface for thing references.
RegistrationStep
The different steps of a TAG Profile registration journey.
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.
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.
EventLevel
Event level.
Definition: EventLevel.cs:7
EventType
Type of event.
Definition: EventType.cs:7
ReportingReason
Reason for blocking.
Definition: AbuseClient.cs:16
SignWith
Options on what keys to use when signing data.
Definition: Enumerations.cs:84
ContractParts
How the parts of the contract are defined.
Definition: Part.cs:9
ContractVisibility
Visibility types for contracts.
Definition: Enumerations.cs:58
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
E2ETransmission
End-to-end encryption mode.
Reason
Reason a token is not valid.
Definition: JwtFactory.cs:12
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:13