@@ -470,6 +470,18 @@ public static async Task UpdateKeeperNSFRecordInternal(this VaultOnline vault, s
470470 if ( ! vault . TryGetKeeperNSFRecord ( recordUid , out var record ) )
471471 throw new VaultException ( $ "Keeper NSF record '{ recordUid } ' not found") ;
472472
473+ await vault . UpdateKeeperNSFRecordInternal ( record , title , recordType , notes , fields ) . ConfigureAwait ( false ) ;
474+ }
475+
476+ public static async Task UpdateKeeperNSFRecordInternal ( this VaultOnline vault , KeeperNSFRecord record , string title , string recordType , string notes , IDictionary < string , object > fields )
477+ {
478+ if ( record == null )
479+ throw new ArgumentNullException ( nameof ( record ) ) ;
480+
481+ var recordUid = record . RecordUid ;
482+ if ( string . IsNullOrEmpty ( recordUid ) )
483+ throw new VaultException ( "Record UID cannot be empty" ) ;
484+
473485 if ( record . RecordKey == null )
474486 throw new VaultException ( $ "Record key not available for record '{ recordUid } '") ;
475487
@@ -529,7 +541,13 @@ public static async Task UpdateKeeperNSFRecordInternal(this VaultOnline vault, s
529541 var result = rs . Records [ 0 ] ;
530542 if ( result . Status != RecordModifyResult . RsSuccess )
531543 {
532- throw new VaultException ( $ "Failed to update record: { result . Message } ") ;
544+ var status = Enum . GetName ( typeof ( RecordModifyResult ) , result . Status ) ?? "" ;
545+ if ( status . StartsWith ( "Rs" , StringComparison . Ordinal ) )
546+ {
547+ status = status . Substring ( 2 ) ;
548+ }
549+
550+ throw new KeeperApiException ( status . ToSnakeCase ( ) , result . Message ?? "Failed to update record" ) ;
533551 }
534552 }
535553 }
@@ -1062,35 +1080,7 @@ private static string GetFolderName(VaultOnline vault, string folderUid)
10621080 public static async Task < KeeperNSFRecordDetailsResult > GetKeeperNSFRecordDetailsInternal (
10631081 this VaultOnline vault , IReadOnlyList < string > recordUids )
10641082 {
1065- if ( recordUids == null || recordUids . Count == 0 )
1066- {
1067- throw new KeeperInvalidParameter ( "GetKeeperNSFRecordDetails" , nameof ( recordUids ) , "" , "at least one record UID required" ) ;
1068- }
1069-
1070- var request = new RecordDetailsProto . RecordDataRequest
1071- {
1072- ClientTime = DateTimeOffset . UtcNow . ToUnixTimeMilliseconds ( ) ,
1073- } ;
1074-
1075- foreach ( var uid in recordUids . Where ( u => ! string . IsNullOrWhiteSpace ( u ) ) )
1076- {
1077- var uidBytes = uid . Trim ( ) . Base64UrlDecode ( ) ;
1078- if ( uidBytes == null || uidBytes . Length == 0 )
1079- {
1080- Trace . TraceWarning ( $ "KeeperNSF: Skipping record details request with malformed UID '{ uid } '") ;
1081- continue ;
1082- }
1083-
1084- request . RecordUids . Add ( ByteString . CopyFrom ( uidBytes ) ) ;
1085- }
1086-
1087- if ( request . RecordUids . Count == 0 )
1088- {
1089- throw new KeeperInvalidParameter ( "GetKeeperNSFRecordDetails" , nameof ( recordUids ) , "" , "no valid record UIDs" ) ;
1090- }
1091-
1092- var response = await vault . Auth . ExecuteAuthRest < RecordDetailsProto . RecordDataRequest , RecordDetailsProto . RecordDataResponse > (
1093- "vault/records/v3/details/data" , request ) . ConfigureAwait ( false ) ;
1083+ var response = await vault . FetchKeeperNSFRecordDetailsDataAsync ( recordUids ) . ConfigureAwait ( false ) ;
10941084
10951085 var result = new KeeperNSFRecordDetailsResult ( ) ;
10961086 foreach ( var forbiddenUid in response . ForbiddenRecords )
@@ -1129,6 +1119,98 @@ public static async Task<KeeperNSFRecordDetailsResult> GetKeeperNSFRecordDetails
11291119 return result ;
11301120 }
11311121
1122+ private static async Task < RecordDetailsProto . RecordDataResponse > FetchKeeperNSFRecordDetailsDataAsync (
1123+ this VaultOnline vault , IReadOnlyList < string > recordUids )
1124+ {
1125+ if ( recordUids == null || recordUids . Count == 0 )
1126+ {
1127+ throw new KeeperInvalidParameter ( "GetKeeperNSFRecordDetails" , nameof ( recordUids ) , "" , "at least one record UID required" ) ;
1128+ }
1129+
1130+ var request = new RecordDetailsProto . RecordDataRequest
1131+ {
1132+ ClientTime = DateTimeOffset . UtcNow . ToUnixTimeMilliseconds ( ) ,
1133+ } ;
1134+
1135+ foreach ( var uid in recordUids . Where ( u => ! string . IsNullOrWhiteSpace ( u ) ) )
1136+ {
1137+ var uidBytes = uid . Trim ( ) . Base64UrlDecode ( ) ;
1138+ if ( uidBytes == null || uidBytes . Length == 0 )
1139+ {
1140+ Trace . TraceWarning ( $ "KeeperNSF: Skipping record details request with malformed UID '{ uid } '") ;
1141+ continue ;
1142+ }
1143+
1144+ request . RecordUids . Add ( ByteString . CopyFrom ( uidBytes ) ) ;
1145+ }
1146+
1147+ if ( request . RecordUids . Count == 0 )
1148+ {
1149+ throw new KeeperInvalidParameter ( "GetKeeperNSFRecordDetails" , nameof ( recordUids ) , "" , "no valid record UIDs" ) ;
1150+ }
1151+
1152+ return await vault . Auth . ExecuteAuthRest < RecordDetailsProto . RecordDataRequest , RecordDetailsProto . RecordDataResponse > (
1153+ "vault/records/v3/details/data" , request ) . ConfigureAwait ( false ) ;
1154+ }
1155+
1156+ internal static async Task < KeeperNSFRecord > GetRefreshedKeeperNSFRecordAsync ( this VaultOnline vault , string recordUid )
1157+ {
1158+ if ( string . IsNullOrWhiteSpace ( recordUid ) )
1159+ return null ;
1160+
1161+ var trimmedUid = recordUid . Trim ( ) ;
1162+ var response = await vault . FetchKeeperNSFRecordDetailsDataAsync ( new [ ] { trimmedUid } ) . ConfigureAwait ( false ) ;
1163+
1164+ var recordData = ( response . Data ?? Enumerable . Empty < Records . RecordData > ( ) )
1165+ . FirstOrDefault ( x =>
1166+ {
1167+ if ( x . RecordUid == null || x . RecordUid . IsEmpty )
1168+ return false ;
1169+ var uid = CryptoUtils . Base64UrlEncode ( x . RecordUid . ToByteArray ( ) ) ;
1170+ return string . Equals ( uid , trimmedUid , StringComparison . OrdinalIgnoreCase ) ;
1171+ } ) ;
1172+
1173+ return recordData != null && TryBuildKeeperNSFRecordFromDetailsData ( vault , recordData , trimmedUid , out var record )
1174+ ? record
1175+ : null ;
1176+ }
1177+
1178+ private static bool TryBuildKeeperNSFRecordFromDetailsData (
1179+ VaultOnline vault , Records . RecordData recordData , string recordUid , out KeeperNSFRecord record )
1180+ {
1181+ record = null ;
1182+ if ( recordData == null || string . IsNullOrWhiteSpace ( recordUid ) )
1183+ return false ;
1184+
1185+ if ( ! TryDecryptKeeperNSFRecordDetailsData ( vault , recordData , recordUid , out var data ) )
1186+ return false ;
1187+
1188+ var recordKey = TryDecryptKeeperNSFRecordKeyFromDetails ( vault , recordData , recordUid ) ;
1189+ if ( recordKey == null || recordKey . Length == 0 )
1190+ return false ;
1191+
1192+ vault . TryGetKeeperNSFRecord ( recordUid , out var cached ) ;
1193+
1194+ record = new KeeperNSFRecord
1195+ {
1196+ RecordUid = recordUid ,
1197+ Title = ! string . IsNullOrEmpty ( data ? . Title ) ? data . Title : data ? . Name ,
1198+ Type = data ? . Type ,
1199+ Notes = data ? . Notes ,
1200+ Revision = recordData . Revision ,
1201+ Version = recordData . Version ,
1202+ Shared = cached ? . Shared ?? false ,
1203+ ClientModifiedTime = cached ? . ClientModifiedTime ?? 0 ,
1204+ FileSize = cached ? . FileSize ?? 0 ,
1205+ ThumbnailSize = cached ? . ThumbnailSize ?? 0 ,
1206+ FolderUid = cached ? . FolderUid ,
1207+ FolderName = cached ? . FolderName ,
1208+ RecordKey = recordKey ,
1209+ Data = data ,
1210+ } ;
1211+ return true ;
1212+ }
1213+
11321214 private static void ResolveKeeperNSFRecordTitleAndType ( KeeperNSFRecord record , out string title , out string type )
11331215 {
11341216 if ( ! string . IsNullOrEmpty ( record ? . Type ) )
0 commit comments