3using System.Collections.ObjectModel;
6using System.Reflection;
9using System.Threading.Tasks;
10using System.Threading.Channels;
26using System.Diagnostics;
29using Microsoft.Maui.Storage;
42 private static readonly
string backupKyc =
"TestKYCNeuro.xml";
43 private const string KycTemplateNodeId =
"NeuroAccessKyc";
44 private const int DefaultTemplatePageSize = 20;
47 private readonly Channel<string> autosaveChannel;
48 private readonly
System.
Collections.Concurrent.ConcurrentDictionary<string, AutosaveEntry> pendingAutosave =
new();
49 private readonly CancellationTokenSource autosaveCts =
new();
50 private readonly Task autosaveWorkerTask;
53 private long snapshotsPersisted = 0;
54 private long snapshotsSkipped = 0;
56 private sealed
class AutosaveEntry
60 this.Reference = reference;
61 this.Snapshot = snapshot;
67 private bool disposedValue;
68 public event EventHandler<ApplicationReviewEventArgs>? ApplicationReviewUpdated;
76 this.autosaveChannel = Channel.CreateUnbounded<
string>(
new UnboundedChannelOptions
80 AllowSynchronousContinuations =
false
82 this.autosaveWorkerTask = Task.Run(this.AutosaveWorkerLoop);
88 private async Task AutosaveWorkerLoop()
90 ChannelReader<string> Reader = this.autosaveChannel.Reader;
91 while (await Reader.WaitToReadAsync(
this.autosaveCts.Token).ConfigureAwait(
false))
93 while (Reader.TryRead(out
string? Key))
95 if (this.autosaveCts.IsCancellationRequested)
97 if (!this.pendingAutosave.TryRemove(Key, out AutosaveEntry? Entry))
101 await this.SaveSnapshotAsync(Entry.Reference, Entry.Snapshot,
false).ConfigureAwait(
false);
103 catch (OperationCanceledException)
109 ServiceRef.
LogService.LogException(Ex,
new KeyValuePair<string, object?>(
"Operation",
"KYC.AutosaveWorker"));
115 #region Loading & Persistence
130 catch (Exception FindEx)
132 ServiceRef.
LogService.LogException(FindEx, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
133 LoadedReference =
null;
136 if (LoadedReference is
null ||
string.IsNullOrEmpty(LoadedReference.
ObjectId))
140 CreatedUtc = DateTime.UtcNow
153 if (TemplateToApply is
null &&
string.IsNullOrEmpty(LoadedReference.
KycXml))
158 TemplateToApply = TemplatePage.
Templates.FirstOrDefault();
166 if (TemplateToApply is not
null)
170 await this.ApplyTemplateToReferenceAsync(LoadedReference, TemplateToApply, Lang).ConfigureAwait(
false);
178 if (
string.IsNullOrEmpty(LoadedReference.
KycXml))
182 await this.ApplyBundledTemplateAsync(LoadedReference, Lang).ConfigureAwait(
false);
194 if (Process?.Name is not
null)
197 if (!
string.Equals(LoadedReference.
FriendlyName, NewName, StringComparison.Ordinal))
199 LoadedReference.FriendlyName = NewName;
209 return LoadedReference;
220 if (
string.IsNullOrEmpty(Reference.
ObjectId))
225 catch (KeyNotFoundException)
241 List<KycReference> References =
new List<KycReference>();
255 public async Task<KycApplicationPage>
LoadKycApplicationsPageAsync(
string? After =
null,
string? Before =
null,
int? Index =
null,
int? Max =
null,
string? Lang =
null, CancellationToken CancellationToken =
default)
257 int PageSize = Max.HasValue && Max.Value > 0 ? Max.Value : DefaultTemplatePageSize;
258 string Domain = ServiceRef.TagProfile.Domain ??
string.Empty;
261 bool IsInitialRequest = After is
null && Before is
null && (!Index.HasValue || Index.Value == 0);
266 if (PageResult is
null && !
string.IsNullOrWhiteSpace(ServiceAddress))
270 if (PageResult is
null)
272 if (IsInitialRequest)
274 IReadOnlyList<KycApplicationTemplate> FallbackTemplates = await this.LoadFallbackTemplatesAsync(Lang, CancellationToken).ConfigureAwait(
false);
281 IReadOnlyList<KycApplicationTemplate> Templates = await this.ConvertToTemplatesAsync(PageResult.Items, Domain, Lang, CancellationToken).ConfigureAwait(
false);
282 if (Templates.Count == 0)
284 if (IsInitialRequest)
286 IReadOnlyList<KycApplicationTemplate> FallbackTemplates = await this.LoadFallbackTemplatesAsync(Lang, CancellationToken).ConfigureAwait(
false);
295 catch (OperationCanceledException)
302 if (IsInitialRequest)
304 IReadOnlyList<KycApplicationTemplate> FallbackTemplates = await this.LoadFallbackTemplatesAsync(Lang, CancellationToken).ConfigureAwait(
false);
311 private async Task<IReadOnlyList<KycApplicationTemplate>> ConvertToTemplatesAsync(
PubSubItem[]? Items,
string Domain,
string? Lang, CancellationToken CancellationToken)
313 List<KycApplicationTemplate> Templates =
new List<KycApplicationTemplate>();
314 if (Items is
null || Items.Length == 0)
319 CancellationToken.ThrowIfCancellationRequested();
326 KycApplicationTemplate? Template = await this.CreateTemplateAsync(Application, Application.ProcessXml, Domain, Lang, CancellationToken).ConfigureAwait(
false);
327 if (Template is not
null)
328 Templates.Add(Template);
330 catch (OperationCanceledException)
343 private async Task<KycApplicationTemplate?> CreateTemplateAsync(
KycApplicationItem? Application,
string Xml,
string Domain,
string? Lang, CancellationToken CancellationToken)
345 CancellationToken.ThrowIfCancellationRequested();
346 bool IsValid = await this.ValidateKycXmlAsync(
string.IsNullOrWhiteSpace(Domain) ?
"unknown" : Domain, Xml).ConfigureAwait(
false);
350 CancellationToken.ThrowIfCancellationRequested();
353 if (
string.IsNullOrWhiteSpace(FriendlyName) && Application is not
null)
354 FriendlyName = Application.PrimaryDisplayName ?? Application.DisplayName;
355 if (
string.IsNullOrWhiteSpace(FriendlyName) && Application is not
null)
356 FriendlyName = Application.ItemId;
357 if (
string.IsNullOrWhiteSpace(FriendlyName))
358 FriendlyName =
"KYC Application";
364 private async Task<IReadOnlyList<KycApplicationTemplate>> LoadFallbackTemplatesAsync(
string? Lang, CancellationToken CancellationToken)
366 List<KycApplicationTemplate> Templates =
new List<KycApplicationTemplate>();
369 string Xml = await this.LoadBundledKycXmlAsync(CancellationToken).ConfigureAwait(
false);
370 KycApplicationTemplate? Template = await this.CreateTemplateAsync(
null, Xml,
"fallback", Lang, CancellationToken).ConfigureAwait(
false);
371 if (Template is not
null)
372 Templates.Add(Template);
374 catch (OperationCanceledException)
386 private async Task<string> LoadBundledKycXmlAsync(CancellationToken CancellationToken)
388 CancellationToken.ThrowIfCancellationRequested();
389 using Stream Stream = await FileSystem.OpenAppPackageFileAsync(backupKyc);
390 using StreamReader Reader =
new(Stream);
391 string Xml = await Reader.ReadToEndAsync(CancellationToken).ConfigureAwait(
false);
395 private async Task<bool> ValidateKycXmlAsync(
string Domain,
string Xml)
402 ServiceRef.
LogService.LogDebug(
"KycXmlValidationPrimarySuccess",
new KeyValuePair<string, object?>(
"Domain", Domain));
410 "KycXmlValidationFallbackSuccess",
411 new KeyValuePair<string, object?>(
"Domain", Domain),
412 new KeyValuePair<string, object?>(
"LegacyKey",
Constants.
Schemes.KYCProcess));
417 "KycXmlValidationBothFailed",
418 new KeyValuePair<string, object?>(
"Domain", Domain),
419 new KeyValuePair<string, object?>(
"PrimaryKey",
Constants.
Schemes.NeuroAccessKycProcessUrl),
420 new KeyValuePair<string, object?>(
"LegacyKey",
Constants.
Schemes.KYCProcess));
440 Reference.
SetProcess(Process, Xml, DateTime.UtcNow, DateTime.UtcNow);
442 if (!
string.IsNullOrWhiteSpace(FriendlyName))
443 Reference.FriendlyName = FriendlyName;
446 private async Task ApplyBundledTemplateAsync(
KycReference Reference,
string? Lang)
448 string Xml = await this.LoadBundledKycXmlAsync(
default).ConfigureAwait(
false);
450 Reference.
SetProcess(Process, Xml, DateTime.UtcNow, DateTime.UtcNow);
451 string? FriendlyName = Process.
Name?.
Text;
452 if (!
string.IsNullOrWhiteSpace(FriendlyName))
453 Reference.FriendlyName = FriendlyName;
469 ReadOnlyObservableCollection<KycSection> Sections = Page.
VisibleSections;
470 if (Sections is not
null)
471 Fields = Fields.Concat(Sections.SelectMany(S => S.VisibleFields));
473 List<Task> Tasks =
new();
477 Field.ValidationTask.
Run();
478 Task T = MainThread.InvokeOnMainThreadAsync(async () =>
486 await Task.WhenAll(Tasks);
498 for (
int i = 0; i < Process.
Pages.Count; i++)
510 #region Data Preparation
516 List<Property> Mapped =
new();
517 List<LegalIdentityAttachment> Attachments =
new();
524 if (this.CheckAndHandleFile(Process, Field, Attachments))
526 foreach (
Property P
in await this.BuildPropertiesFromFieldAsync(Process, Field, CancellationToken))
533 if (this.CheckAndHandleFile(Process, Field, Attachments))
535 foreach (
Property P
in await this.BuildPropertiesFromFieldAsync(Process, Field, CancellationToken))
540 KycOrderingComparer Comparer = KycOrderingComparer.Create(Process);
541 Mapped.Sort(Comparer.PropertyComparer);
542 return (Mapped, Attachments);
550 if (Reference is
null || Process is
null)
551 return Task.CompletedTask;
552 AsyncLock Lock = this.GetLockFor(Reference);
553 return this.ScheduleSnapshotCoreAsync(Lock, Reference, Process, Navigation, Progress, CurrentPageId);
558 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
560 KycReferenceSnapshot Snapshot = CreateSnapshot(Reference, Process, Navigation, Progress, CurrentPageId);
561 string Key = Reference.ObjectId ??
string.Empty;
562 this.pendingAutosave[Key] =
new AutosaveEntry(Reference, Snapshot);
563 _ = this.autosaveChannel.Writer.TryWrite(Key);
572 if (Reference is
null || Process is
null)
574 AsyncLock Lock = this.GetLockFor(Reference);
576 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
578 Snapshot = CreateSnapshot(Reference, Process, Navigation, Progress, CurrentPageId);
582 await this.SaveSnapshotAsync(Reference, Snapshot,
true).ConfigureAwait(
false);
586 ServiceRef.
LogService.LogException(Ex,
new KeyValuePair<string, object?>(
"Operation",
"KYC.ImmediateAutosave"));
588 string Key = Reference.ObjectId ??
string.Empty;
589 this.pendingAutosave.TryRemove(Key, out
_);
597 if (Reference is
null || Identity is
null)
599 AsyncLock Lock = this.GetLockFor(Reference);
600 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
602 Reference.CreatedIdentityId = Identity.
Id;
603 Reference.CreatedIdentityState = Identity.
State;
604 Reference.ApplicationReview =
null;
605 Reference.RejectionMessage =
null;
606 Reference.RejectionCode =
null;
607 Reference.InvalidClaims =
null;
608 Reference.InvalidPhotos =
null;
609 Reference.InvalidClaimDetails =
null;
610 Reference.InvalidPhotoDetails =
null;
612 Reference.UpdatedUtc = DateTime.UtcNow;
613 await SaveReferenceAsync(Reference);
624 if (Reference is
null || Identity is
null)
627 AsyncLock Lock = this.GetLockFor(Reference);
628 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
630 Reference.CreatedIdentityId = Identity.
Id;
631 Reference.CreatedIdentityState = Identity.
State;
633 Reference.UpdatedUtc = DateTime.UtcNow;
634 await SaveReferenceAsync(Reference);
643 if (Reference is
null)
645 AsyncLock Lock = this.GetLockFor(Reference);
646 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
648 Reference.CreatedIdentityId =
null;
649 Reference.CreatedIdentityState =
null;
651 Reference.UpdatedUtc = DateTime.UtcNow;
652 await SaveReferenceAsync(Reference);
663 if (Reference is
null || Review is
null)
667 AsyncLock Lock = this.GetLockFor(Reference);
668 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
670 Reference.ApplicationReview = Clone;
671 Reference.RejectionMessage =
null;
672 Reference.RejectionCode =
null;
673 Reference.InvalidClaims =
null;
674 Reference.InvalidPhotos =
null;
675 Reference.InvalidClaimDetails =
null;
676 Reference.InvalidPhotoDetails =
null;
678 Reference.UpdatedUtc = DateTime.UtcNow;
679 await SaveReferenceAsync(Reference).ConfigureAwait(
false);
682 this.RaiseApplicationReviewUpdated(Reference, Clone);
690 if (Reference is
null)
692 AsyncLock Lock = this.GetLockFor(Reference);
693 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
695 Reference.LastVisitedMode =
"Form";
696 Reference.LastVisitedPageId =
null;
697 Reference.ApplicationReview =
null;
698 Reference.RejectionMessage =
null;
699 Reference.RejectionCode =
null;
700 Reference.InvalidClaims =
null;
701 Reference.InvalidPhotos =
null;
702 Reference.InvalidClaimDetails =
null;
703 Reference.InvalidPhotoDetails =
null;
704 Reference.CreatedIdentityId =
null;
705 Reference.CreatedIdentityState =
null;
706 if (SeedFields is not
null && SeedFields.Count > 0)
707 Reference.Fields = SeedFields.Select(Field =>
new KycFieldValue(Field.FieldId, Field.Value)).ToArray();
709 Reference.Fields =
null;
711 Reference.UpdatedUtc = DateTime.UtcNow;
712 await SaveReferenceAsync(Reference);
715 if (SeedFields is not
null && SeedFields.Count > 0 && !
string.IsNullOrWhiteSpace(Language))
721 catch (Exception Exception)
723 ServiceRef.
LogService.LogException(Exception, this.GetClassAndMethod(MethodBase.GetCurrentMethod()));
735 string? LastVisitedPageId = ResolveLastVisitedPageId(Reference, Process, Navigation, CurrentPageId);
736 string Mode = ResolveMode(Navigation);
737 DateTime Now = DateTime.UtcNow;
738 Reference.UpdatedUtc = Now;
739 Reference.Fields = Fields;
740 Reference.Progress = Progress;
741 Reference.LastVisitedPageId = LastVisitedPageId;
742 Reference.LastVisitedMode = Mode;
759 new KeyValuePair<string, object?>(
"ReferenceId", Reference.
ObjectId ??
string.Empty),
760 new KeyValuePair<string, object?>(
"Version", Snapshot.Version),
761 new KeyValuePair<string, object?>(
"FieldCount", Fields.Length));
770 string Key = Reference.ObjectId ??
string.Empty;
771 return this.referenceLocks.GetOrAdd(Key,
_ =>
new AsyncLock());
783 if (!
string.IsNullOrWhiteSpace(CurrentPageId))
784 return CurrentPageId;
785 if (Navigation.CurrentPageIndex >= 0 && Navigation.CurrentPageIndex < Process.
Pages.Count)
786 return Process.
Pages[Navigation.CurrentPageIndex].Id;
795 Stopwatch Sw = Stopwatch.StartNew();
796 AsyncLock Lock = this.GetLockFor(Reference);
799 await
using (await Lock.
LockAsync().ConfigureAwait(
false))
802 new KeyValuePair<string, object?>(
"ReferenceId", Reference.
ObjectId ??
string.Empty),
803 new KeyValuePair<string, object?>(
"Version", Snapshot.Version),
804 new KeyValuePair<string, object?>(
"IsImmediate", IsImmediate));
807 if (Snapshot.Version < Reference.
Version)
809 Interlocked.Increment(ref this.snapshotsSkipped);
811 new KeyValuePair<string, object?>(
"ReferenceId", Reference.
ObjectId ??
string.Empty),
812 new KeyValuePair<string, object?>(
"SnapshotVersion", Snapshot.Version),
813 new KeyValuePair<string, object?>(
"CurrentVersion", Reference.
Version));
817 if (Reference.
Fields != Snapshot.Fields)
818 Reference.Fields = Snapshot.Fields;
819 Reference.Progress = Snapshot.Progress;
820 Reference.LastVisitedPageId = Snapshot.LastVisitedPageId;
821 Reference.LastVisitedMode = Snapshot.LastVisitedMode;
822 Reference.ApplicationReview = CloneReview(Snapshot.ApplicationReview);
823 Reference.UpdatedUtc = Snapshot.UpdatedUtc;
824 await SaveReferenceAsync(Reference).ConfigureAwait(
false);
826 Interlocked.Increment(ref this.snapshotsPersisted);
827 string Hash = ComputeFieldsHash(Snapshot.Fields);
829 new KeyValuePair<string, object?>(
"ReferenceId", Reference.
ObjectId ??
string.Empty),
830 new KeyValuePair<string, object?>(
"Version", Snapshot.Version),
831 new KeyValuePair<string, object?>(
"FieldCount", Snapshot.Fields?.Length ?? 0),
832 new KeyValuePair<string, object?>(
"DurationMs", Sw.ElapsedMilliseconds),
833 new KeyValuePair<string, object?>(
"Hash", Hash));
842 private static string ComputeFieldsHash(
KycFieldValue[]? Fields)
844 if (Fields is
null || Fields.Length == 0)
848 using SHA256 Sha = SHA256.Create();
849 foreach (
KycFieldValue F
in Fields.OrderBy(f => f.FieldId, StringComparer.Ordinal))
851 string Line = F.FieldId +
"=" + (F.Value ??
string.Empty) +
"\n";
852 byte[] Bytes = Encoding.UTF8.GetBytes(Line);
853 Sha.TransformBlock(Bytes, 0, Bytes.Length,
null, 0);
855 Sha.TransformFinalBlock(Array.Empty<
byte>(), 0, 0);
856 return Convert.ToHexString(Sha.Hash);
874 InvalidClaims = Source.
InvalidClaims?.ToArray() ?? Array.Empty<
string>(),
875 InvalidPhotos = Source.
InvalidPhotos?.ToArray() ?? Array.Empty<
string>(),
876 UnvalidatedClaims = Source.
UnvalidatedClaims?.ToArray() ?? Array.Empty<
string>(),
885 DisplayName = d.DisplayName
900 if (Reference is
null)
906 this.ApplicationReviewUpdated?.Invoke(
this, Args);
914 private static async Task SaveReferenceAsync(
KycReference Reference)
918 if (
string.IsNullOrEmpty(Reference.
ObjectId))
927 catch (KeyNotFoundException)
935 List<Property> Result =
new();
940 string BaseValue = Field.
StringValue?.Trim() ??
string.Empty;
941 if (
string.IsNullOrEmpty(BaseValue))
945 if (
string.IsNullOrEmpty(Map.Key))
947 string Current = BaseValue;
948 foreach (
string Name
in Map.TransformNames)
950 if (
string.IsNullOrWhiteSpace(Name))
954 try { Current = await Transform.ApplyAsync(Field, Process, Current, Ct); }
957 if (
string.IsNullOrEmpty(Current))
960 if (!
string.IsNullOrEmpty(Current))
961 Result.Add(
new Property(Map.Key, Current));
972 byte[]? Data = ImageField.StringValue is
null ? null : this.CompressImage(this.Base64ToStream(ImageField.StringValue));
973 if (Data is not
null)
980 private MemoryStream Base64ToStream(
string Base64)
982 byte[] Bytes = Convert.FromBase64String(Base64);
983 return new MemoryStream(Bytes);
986 private byte[]? CompressImage(Stream InputStream)
990 using SKManagedStream Ms =
new(InputStream);
991 using SKData ImgData = SKData.Create(Ms);
992 using SKCodec Codec = SKCodec.Create(ImgData);
993 SKBitmap Bmp = SKBitmap.Decode(ImgData);
994 Bmp = this.HandleOrientation(Bmp, Codec.EncodedOrigin);
996 int H = Bmp.Height;
int W = Bmp.Width;
997 if (W >= H && W > 1920) { H = (int)(H * (1920.0 / W) + 0.5); W = 1920; Resize =
true; }
998 else if (H > W && H > 1920) { W = (int)(W * (1920.0 / H) + 0.5); H = 1920; Resize =
true; }
1001 SKImageInfo Info = Bmp.Info;
1002 SKImageInfo Ni =
new(W, H, Info.ColorType, Info.AlphaType, Info.ColorSpace);
1003 SKBitmap? Resized = Bmp.Resize(Ni, SKFilterQuality.High);
1004 if (Resized is not
null) { Bmp.Dispose(); Bmp = Resized; }
1007 using (SKData Encoded = Bmp.Encode(SKEncodedImageFormat.Jpeg, 80)) Bytes2 = Encoded.ToArray();
1014 private SKBitmap HandleOrientation(SKBitmap Bmp, SKEncodedOrigin O)
1019 case SKEncodedOrigin.BottomRight:
1020 Rotated =
new SKBitmap(Bmp.Width, Bmp.Height);
1021 using (SKCanvas Canvas =
new(Rotated)) { Canvas.RotateDegrees(180, Bmp.Width / 2, Bmp.Height / 2); Canvas.DrawBitmap(Bmp, 0, 0); }
1023 case SKEncodedOrigin.RightTop:
1024 Rotated =
new SKBitmap(Bmp.Height, Bmp.Width);
1025 using (SKCanvas Canvas =
new(Rotated)) { Canvas.Translate(Rotated.Width, 0); Canvas.RotateDegrees(90); Canvas.DrawBitmap(Bmp, 0, 0); }
1027 case SKEncodedOrigin.LeftBottom:
1028 Rotated =
new SKBitmap(Bmp.Height, Bmp.Width);
1029 using (SKCanvas Canvas =
new(Rotated)) { Canvas.Translate(0, Rotated.Height); Canvas.RotateDegrees(270); Canvas.DrawBitmap(Bmp, 0, 0); }
1031 default:
return Bmp;
1041 public async Task
FlushAsync(CancellationToken cancellationToken =
default)
1043 foreach (KeyValuePair<string, AutosaveEntry> Pair
in this.pendingAutosave.ToArray())
1045 if (cancellationToken.IsCancellationRequested)
1047 if (this.pendingAutosave.TryRemove(Pair.Key, out AutosaveEntry? Entry))
1049 try { await this.SaveSnapshotAsync(Entry.Reference, Entry.Snapshot,
true).ConfigureAwait(
false); }
1050 catch (Exception Ex) {
ServiceRef.
LogService.LogException(Ex,
new KeyValuePair<string, object?>(
"Operation",
"KYC.FlushAutosave")); }
1062 this.autosaveCts.Cancel();
1063 this.autosaveChannel.Writer.TryComplete();
1064 await Task.WhenAny(this.autosaveWorkerTask, Task.Delay(TimeSpan.FromSeconds(2), cancellationToken)).ConfigureAwait(
false);
1066 catch (Exception Ex)
1070 await this.
FlushAsync(cancellationToken).ConfigureAwait(
false);
1078 if (this.disposedValue)
1084 this.autosaveCts.Cancel();
1085 this.autosaveChannel.Writer.TryComplete();
1088 this.autosaveWorkerTask.Wait(TimeSpan.FromSeconds(2));
1090 catch (AggregateException ex)
1092 if (ex.InnerExceptions.Any(e => e is not TaskCanceledException and not OperationCanceledException))
1095 catch (OperationCanceledException)
1100 catch (Exception Ex)
1105 this.disposedValue =
true;
1114 GC.SuppressFinalize(
this);
1120 internal (
long Persisted,
long Skipped) GetSnapshotMetrics() => (Interlocked.Read(ref this.snapshotsPersisted), Interlocked.Read(ref this.snapshotsSkipped));
const string Jpeg
The JPEG MIME type.
XML Schemes (namespaces used also as schema registration keys) + packaged file names.
A set of never changing property constants and helpful values.
Contains additional data about an invalid claim.
Captures the result of an application review returned from backend services.
ApplicationReviewPhotoDetail[] InvalidPhotoDetails
Optional details about invalid photos.
string[] InvalidPhotos
Invalid photo identifiers reported by the service.
string[] InvalidClaims
Invalid claim identifiers reported by the service.
DateTime ReceivedUtc
Moment when the review was received.
string? Code
Optional machine readable code identifying the review reason.
string[] UnvalidatedPhotos
Photos still pending validation.
string Message
The localized or raw message describing the review result.
string[] UnvalidatedClaims
Claims still pending validation.
ApplicationReviewClaimDetail[] InvalidClaimDetails
Optional details about invalid claims.
Contains additional data about an invalid photo.
Event arguments emitted when a KYC application review changes.
Represents a KYC application template retrieved from PubSub storage.
static bool TryCreate(PubSubItem Item, out KycApplicationItem? Application)
Attempts to create an instance from a PubSub item.
Represents a page of KYC application templates along with pagination metadata.
IReadOnlyList< KycApplicationTemplate > Templates
Gets the application templates on this page.
Represents a parsed KYC application template along with optional PubSub metadata.
KycReference Reference
Gets the parsed reference constructed from the template XML.
Parses an XML-described KYC process into view-model objects.
static Task< KycProcess > LoadProcessAsync(string Xml, string? Lang=null)
Parses the KYC pages from an XML string.
Contains a local reference to a KYC process.
string? LastVisitedPageId
Last visited page identifier to support resuming.
static KycReference FromProcess(KycProcess process, string xml, string? friendlyName=null)
Creates a KycReference from a KycProcess, serializing its field values.
ApplicationReview? ApplicationReview
Latest backend review for this application, if any.
string FriendlyName
Optional friendly name.
async Task ApplyFieldsToProcessAsync(string? lang=null)
Ensures the current process reflects values in Fields by applying them into the cached process instan...
string? ObjectId
Object ID
async Task< KycProcess?> GetProcess(string? lang=null)
Gets a parsed KYC process, populating its fields from the reference.
KycFieldValue?[] Fields
Field values in the process.
int Version
Monotonically increasing version for optimistic concurrency and snapshot ordering....
DateTime CreatedUtc
When the reference was created.
void SetProcess(KycProcess process, string xml)
Stores a parsed KYC process into the reference.
string? KycXml
XML describing the KYC process.
Immutable snapshot of the state of a KycReference at a point in time. Used to persist ordered,...
Service providing KYC process loading, validation, snapshotting and persistence. Implements ordered,...
async Task< bool > ValidatePageAsync(KycPage Page)
Validates all visible fields on a page (including sections) using synchronous and asynchronous valida...
async Task< KycApplicationPage > LoadKycApplicationsPageAsync(string? After=null, string? Before=null, int? Index=null, int? Max=null, string? Lang=null, CancellationToken CancellationToken=default)
Loads a page of KYC application templates using PubSub pagination, with bundled fallback when remote ...
async Task FlushAsync(CancellationToken cancellationToken=default)
Flushes any pending coalesced snapshots by forcing immediate persistence.
async Task< IReadOnlyList< KycReference > > LoadAvailableKycReferencesAsync(string? Lang=null)
Loads available KYC references from the provider; falls back to local bundled test definition....
async Task ShutdownAsync(CancellationToken cancellationToken=default)
Performs an orderly shutdown of the service, stopping the worker and flushing snapshots.
async Task PrepareReferenceForNewApplicationAsync(KycReference Reference, string? Language, IReadOnlyList< KycFieldValue >? SeedFields)
Resets a reference for a new application session, optionally seeding field values.
async Task< KycReference > LoadKycReferenceAsync(string? Lang=null, KycApplicationTemplate? Template=null)
Loads the single persisted KycReference (creating a new one if none exists) and ensures KYC XML is av...
async Task ApplyApplicationReviewAsync(KycReference Reference, ApplicationReview Review)
Records the latest application review from the provider.
KycService()
Creates a new instance of the KycService class. Initializes the autosave channel and background worke...
async Task UpdateSubmissionStateAsync(KycReference Reference, LegalIdentity Identity)
Updates stored submission state without clearing any existing application review.
async Task ApplySubmissionAsync(KycReference Reference, LegalIdentity Identity)
Records submission data (identity id + state) and persists the reference.
async Task< int > GetFirstInvalidVisiblePageIndexAsync(KycProcess Process)
Finds the first visible page index that fails validation, or -1 if all visible pages are valid.
async Task ClearSubmissionAsync(KycReference Reference)
Clears stored submission information from the reference.
Task ScheduleSnapshotAsync(KycReference Reference, KycProcess Process, KycNavigationSnapshot Navigation, double Progress, string? CurrentPageId)
Captures a snapshot and schedules an asynchronous persistence operation (coalescing by reference).
async Task FlushSnapshotAsync(KycReference Reference, KycProcess Process, KycNavigationSnapshot Navigation, double Progress, string? CurrentPageId)
Captures a snapshot and persists it immediately (bypasses the autosave queue).
async Task<(IReadOnlyList< Property > Properties, IReadOnlyList< LegalIdentityAttachment > Attachments)> PreparePropertiesAndAttachmentsAsync(KycProcess Process, CancellationToken CancellationToken)
Builds identity properties and attachments (images) from a process, respecting visibility and transfo...
void Dispose()
Disposes the service.
virtual void Dispose(bool disposing)
Releases resources used by the service.
async Task SaveKycReferenceAsync(KycReference Reference)
Persists an existing KycReference instance (insert or update).
Represents a field value in a KYC process reference.
string? PrimaryText
Gets the first localized text that was added, regardless of current culture.
string Text
Gets the localized text for the current UI culture, or an appropriate fallback.
Mapping from a field value to an identity property, including optional transform pipeline.
Represents a page in a KYC process containing fields and sections.
ReadOnlyObservableCollection< ObservableKycField > VisibleFields
Gets the read-only collection of visible fields in the page.
ObservableCollection< KycSection > AllSections
Gets all sections contained in the page.
ReadOnlyObservableCollection< KycSection > VisibleSections
Gets the read-only collection of visible sections in the page.
bool IsVisible(IDictionary< string, string?> Values)
Gets a value indicating whether the page is visible based on current values and its condition.
Represents a parsed KYC process with pages, fields, and current values.
KycLocalizedText? Name
Optional process-level localized name for display.
ObservableCollection< KycPage > Pages
Gets the collection of pages in the KYC process.
IDictionary< string, string?> Values
Gets a modifiable dictionary of field values keyed by field identifier.
The base KYC field model. Use as-is for generic fields, or subclass for custom logic.
abstract ? string StringValue
Gets or sets the value as a string, parsing or formatting as needed for the field type.
ObservableCollection< KycMapping > Mappings
Mappings to apply output.
KycCondition? Condition
Conditional display of this field.
void ForceSynchronousValidation()
Forces immediate synchronous validation (used when advancing pages or explicit full validation is req...
Represent an attachment to a LegalIdentity.
Simple async-exclusive lock (lightweight). Each call to LockAsync yields a disposable releaser that m...
async ValueTask< Releaser > LockAsync(CancellationToken cancellationToken=default)
Acquires the lock asynchronously, returning a disposable releaser that must be disposed exactly once.
Base class that references services in the app.
static ILogService LogService
Log service.
static IStorageService StorageService
Storage service.
static ITagProfile TagProfile
TAG Profile service.
static IXmppService XmppService
The XMPP service for XMPP communication.
static IXmlSchemaValidationService XmlSchemaValidationService
XML schema validation service.
Represents a page of PubSub items along with associated pagination metadata.
async Task WaitAllAsync(bool waitWhilePending=false)
Asynchronously waits until all UI notifications and state updates for the current task have been proc...
void Run()
Start the configured task now (no CanExecute gating).
IdentityState State
Current state of identity
string Id
ID of the legal identity
Represents a published item.
Service for loading KYC processes, performing validation, building identity artifacts and managing pe...
Task< PubSubPageResult?> GetItemsPageAsync(string NodeId, string? ServiceAddress=null, string? After=null, string? Before=null, int? Index=null, int? Max=null)
Retrieves a page of items for a specified node using XEP-0059 result set management parameters.
KycFlowState
High-level flow states for the KYC process UI.
sealed record KycNavigationSnapshot(int CurrentPageIndex, int AnchorPageIndex, KycFlowState State)
Navigation snapshot capturing minimal mutable UI navigation state.