Skip to content

Commit cb61ff6

Browse files
feat(sample): add backup to sample app
Fixes an issue with other SDKs interoperability Signed-off-by: goncalo-frade-iohk <[email protected]>
1 parent f042691 commit cb61ff6

File tree

25 files changed

+271
-47
lines changed

25 files changed

+271
-47
lines changed

Core/Sources/Helpers/JSONEncoder+Helper.swift

+12
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,16 @@ public extension JSONEncoder {
88
encoder.outputFormatting = [.withoutEscapingSlashes, .sortedKeys]
99
return encoder
1010
}
11+
12+
static func backup() -> JSONEncoder {
13+
let encoder = JSONEncoder()
14+
encoder.dataEncodingStrategy = .base64
15+
encoder.keyEncodingStrategy = .convertToSnakeCase
16+
encoder.dateEncodingStrategy = .custom({ date, encoder in
17+
var container = encoder.singleValueContainer()
18+
try container.encode(Int(date.timeIntervalSince1970))
19+
})
20+
encoder.outputFormatting = [.withoutEscapingSlashes, .sortedKeys]
21+
return encoder
22+
}
1123
}

EdgeAgentSDK/Domain/Sources/AnyCodable.swift

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ extension AnyCodable: Codable {
5252
var container = encoder.singleValueContainer()
5353

5454
switch self.value {
55+
case is NSNull:
56+
try container.encodeNil()
5557
case is Void:
5658
try container.encodeNil()
5759
case let bool as Bool:

EdgeAgentSDK/Domain/Sources/BBs/Pluto.swift

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ public protocol Pluto {
7474
mediatorDID: DID
7575
) -> AnyPublisher<Void, Error>
7676

77+
/// Stores multiple verifiable credentials in the data store.
78+
/// - Parameter credentials: The credentials to store.
79+
/// - Returns: A publisher that completes when the operation finishes.
80+
func storeCredentials(
81+
credentials: [StorableCredential]
82+
) -> AnyPublisher<Void, Error>
83+
7784
/// Stores a verifiable credential in the data store.
7885
/// - Parameter credential: The credential to store.
7986
/// - Returns: A publisher that completes when the operation finishes.

EdgeAgentSDK/Domain/Sources/Models/Message+Codable.swift

+14-7
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,22 @@ extension Message: Codable {
2222
var container = encoder.container(keyedBy: CodingKeys.self)
2323
try container.encode(id, forKey: .id)
2424
try container.encode(piuri, forKey: .piuri)
25-
if let dic = try? JSONSerialization.jsonObject(with: body) as? [String: Any] {
26-
try container.encode(AnyCodable(dic), forKey: .body)
25+
if let dic = try? JSONSerialization.jsonObject(with: body) as? [String: Any?] {
26+
var filteredDictionary = dic
27+
for (key, value) in dic {
28+
if value == nil || value is NSNull {
29+
filteredDictionary.removeValue(forKey: key)
30+
}
31+
}
32+
try container.encode(AnyCodable(filteredDictionary), forKey: .body)
2733
} else {
2834
try container.encode(body, forKey: .body)
2935
}
30-
try container.encode(extraHeaders, forKey: .extraHeaders)
31-
try container.encode(createdTime, forKey: .createdTime)
32-
try container.encode(expiresTimePlus, forKey: .expiresTimePlus)
33-
try container.encode(attachments, forKey: .attachments)
34-
try container.encode(ack, forKey: .ack)
36+
try container.encodeIfPresent(extraHeaders, forKey: .extraHeaders)
37+
try container.encodeIfPresent(createdTime, forKey: .createdTime)
38+
try container.encodeIfPresent(expiresTimePlus, forKey: .expiresTimePlus)
39+
try container.encodeIfPresent(attachments, forKey: .attachments)
40+
try container.encodeIfPresent(ack, forKey: .ack)
3541
try from.map { try container.encode($0.string, forKey: .from) }
3642
try to.map { try container.encode($0.string, forKey: .to) }
3743
try fromPrior.map { try container.encode($0, forKey: .fromPrior) }
@@ -48,6 +54,7 @@ extension Message: Codable {
4854
if
4955
let bodyCodable = try? container.decodeIfPresent(AnyCodable.self, forKey: .body),
5056
(bodyCodable.value is [String: Any] || bodyCodable.value is [String]),
57+
JSONSerialization.isValidJSONObject(bodyCodable.value),
5158
let bodyData = try? JSONSerialization.data(withJSONObject: bodyCodable.value)
5259
{
5360
body = bodyData

EdgeAgentSDK/Domain/Sources/Models/Message.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import Foundation
33
/// The `Message` struct represents a DIDComm message, which is used for secure, decentralized communication in the Atala PRISM architecture. A `Message` object includes information about the sender, recipient, message body, and other metadata. `Message` objects are typically exchanged between DID controllers using the `Mercury` building block.
44
public struct Message: Identifiable, Hashable {
55
/// The direction of the message (sent or received).
6-
public enum Direction: String, Codable {
7-
case sent
8-
case received
6+
public enum Direction: Int, Codable {
7+
case sent = 0
8+
case received = 1
99
}
1010

1111
/// The unique identifier of the message.

EdgeAgentSDK/Domain/Sources/Models/MessageAttachment.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,13 @@ extension AttachmentDescriptor: Codable {
178178
public func encode(to encoder: Encoder) throws {
179179
var container = encoder.container(keyedBy: CodingKeys.self)
180180
try container.encode(id, forKey: .id)
181-
try container.encode(mediaType, forKey: .mediaType)
181+
try container.encodeIfPresent(mediaType, forKey: .mediaType)
182182
try container.encode(data, forKey: .data)
183-
try container.encode(filename, forKey: .filename)
184-
try container.encode(format, forKey: .format)
185-
try container.encode(lastmodTime, forKey: .lastmodTime)
186-
try container.encode(byteCount, forKey: .byteCount)
187-
try container.encode(description, forKey: .description)
183+
try container.encodeIfPresent(filename, forKey: .filename)
184+
try container.encodeIfPresent(format, forKey: .format)
185+
try container.encodeIfPresent(lastmodTime, forKey: .lastmodTime)
186+
try container.encodeIfPresent(byteCount, forKey: .byteCount)
187+
try container.encodeIfPresent(description, forKey: .description)
188188
}
189189

190190
public init(from decoder: Decoder) throws {

EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Backup.swift

+7-13
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,6 @@ extension EdgeAgent {
120120
let backupData = try JWE(compactString: encrypted)
121121
.decrypt(recipientKey: try JSONDecoder.didComm().decode(JSONWebKey.JWK.self, from: jwk))
122122

123-
print(try backupData.tryToString())
124-
125123
let backup = try JSONDecoder.didComm().decode(Backup.self, from: backupData)
126124

127125
try await recoverDidsWithKeys(dids: backup.dids, keys: backup.keys)
@@ -208,26 +206,24 @@ extension EdgeAgent {
208206
}
209207

210208
func recoverMessages(messages: [String]) async throws {
211-
let messages = try messages.compactMap { messageStr -> (Message, Message.Direction)? in
209+
let messages = messages.compactMap { messageStr -> (Message, Message.Direction)? in
212210
guard
213-
let messageData = Data(base64URLEncoded: messageStr)
211+
let messageData = Data(base64URLEncoded: messageStr),
212+
let message = try? JSONDecoder.didComm().decode(Message.self, from: messageData)
214213
else {
215214
return nil
216215
}
217-
let message = try JSONDecoder.didComm().decode(Message.self, from: messageData)
218216

219217
return (message, message.direction)
220218
}
221219

222-
try await pluto.storeMessages(messages: messages)
223-
.first()
224-
.await()
220+
try await pluto.storeMessages(messages: messages).first().await()
225221
}
226222

227223
func recoverCredentials(credentials: [Backup.Credential]) async throws {
228224
let downloader = DownloadDataWithResolver(castor: castor)
229225
let pollux = self.pollux
230-
return try await credentials
226+
let storableCredentials = try await credentials
231227
.asyncCompactMap { bakCredential -> StorableCredential? in
232228
guard
233229
let data = Data(base64URLEncoded: bakCredential.data)
@@ -243,9 +239,7 @@ extension EdgeAgent {
243239
]
244240
).storable
245241
}
246-
.asyncForEach { [weak self] in
247-
try await self?.pluto.storeCredential(credential: $0).first().await()
248-
}
242+
try await self.pluto.storeCredentials(credentials: storableCredentials).first().await()
249243
}
250244

251245
func recoverMediators(mediators: [Backup.Mediator]) async throws {
@@ -330,7 +324,7 @@ extension EdgeAgent {
330324
.first()
331325
.await()
332326
.compactMap {
333-
try JSONEncoder.didComm().encode($0).base64UrlEncodedString()
327+
return try JSONEncoder.backup().encode($0).base64UrlEncodedString()
334328
}
335329
}
336330

EdgeAgentSDK/EdgeAgent/Sources/Protocols/PrismOnboarding/PrismOnboardingInvitation.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ struct PrismOnboardingInvitation {
1010
let body: Body
1111

1212
init(jsonString: String) throws {
13-
guard let jsonData = jsonString.data(using: .utf8) else { throw EdgeAgentError.invitationIsInvalidError }
13+
guard let jsonData = jsonString.data(using: .utf8) else {
14+
throw EdgeAgentError.invitationIsInvalidError
15+
}
1416
let object = try JSONDecoder.didComm().decode(Body.self, from: jsonData)
1517
guard object.type == ProtocolTypes.prismOnboarding.rawValue else {
1618
throw EdgeAgentError.unknownInvitationTypeError

EdgeAgentSDK/Pluto/Sources/Helpers/CoreData/CoreDataDAO+Combine.swift

+13
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ extension CoreDataDAO {
5252
}
5353

5454
extension CoreDataDAO where CoreDataObject: Identifiable {
55+
func batchUpdateOrCreate(
56+
_ ids: [CoreDataObject.ID],
57+
context: NSManagedObjectContext,
58+
modify: @escaping (CoreDataObject.ID, CoreDataObject, NSManagedObjectContext) throws -> Void
59+
) -> AnyPublisher<[CoreDataObject.ID], Error> {
60+
context.write { context in
61+
try ids.forEach { id in
62+
try modify(id, self.fetchByID(id, context: context) ?? self.newEntity(context: context), context)
63+
}
64+
return ids
65+
}
66+
.eraseToAnyPublisher()
67+
}
5568
func updateOrCreate(
5669
_ id: CoreDataObject.ID,
5770
context: NSManagedObjectContext,

EdgeAgentSDK/Pluto/Sources/Helpers/Message+Codable.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ struct CodableMessage: Codable {
5858
let fromPrior = try? container.decodeIfPresent(String.self, forKey: .fromPrior)
5959
let thid = try? container.decodeIfPresent(String.self, forKey: .thid)
6060
let pthid = try? container.decodeIfPresent(String.self, forKey: .pthid)
61-
let directionRaw = try container.decodeIfPresent(String.self, forKey: .direction)
61+
let directionRaw = try container.decodeIfPresent(Int.self, forKey: .direction)
6262
let direction = directionRaw.flatMap { Message.Direction(rawValue: $0) }
6363

6464
self.init(message: .init(

EdgeAgentSDK/Pluto/Sources/PersistentStorage/DAO/CDCredentialDAO+CredentialStore.swift

+19-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,25 @@ extension CDCredentialDAO: CredentialStore {
2323
}
2424

2525
func addCredentials(credentials: [StorableCredential]) -> AnyPublisher<Void, Error> {
26-
credentials.publisher.flatMap {
27-
self.addCredential(credential: $0)
26+
batchUpdateOrCreate(
27+
credentials.map(\.storingId),
28+
context: writeContext
29+
) { id, cdobj, context in
30+
guard let credential = credentials.first(where: { $0.storingId == id}) else {
31+
throw PlutoError.unknownCredentialTypeError
32+
}
33+
let claimsObjs = credential.queryAvailableClaims.map {
34+
let obj = CDAvailableClaim(
35+
entity: CDAvailableClaim.entity(),
36+
insertInto: context
37+
)
38+
obj.value = $0
39+
return obj
40+
}
41+
try cdobj.parseFromDomain(
42+
from: credential,
43+
withClaims: Set(claimsObjs)
44+
)
2845
}
2946
.map { _ in }
3047
.eraseToAnyPublisher()

EdgeAgentSDK/Pluto/Sources/PersistentStorage/DAO/CDMessageDAO+MessageProvider.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension CDMessageDAO: MessageProvider {
2323

2424
func getAllSent() -> AnyPublisher<[Message], Error> {
2525
fetchController(
26-
predicate: NSPredicate(format: "(direction == %@)", "sent"),
26+
predicate: NSPredicate(format: "(direction == %d)", 0),
2727
context: readContext
2828
)
2929
.tryMap { try $0.map { try $0.toDomain() } }
@@ -32,7 +32,7 @@ extension CDMessageDAO: MessageProvider {
3232

3333
func getAllReceived() -> AnyPublisher<[Message], Error> {
3434
fetchController(
35-
predicate: NSPredicate(format: "(direction == %@)", "received"),
35+
predicate: NSPredicate(format: "(direction == %d)", 1),
3636
context: readContext
3737
)
3838
.tryMap { try $0.map { try $0.toDomain() } }

EdgeAgentSDK/Pluto/Sources/PersistentStorage/DAO/CDMessageDAO+MessageStore.swift

+38-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,25 @@ extension CDMessageDAO: MessageStore {
66
func addMessages(messages: [(Message, Message.Direction)]) -> AnyPublisher<Void, Error> {
77
messages
88
.publisher
9-
.flatMap {
10-
self.addMessage(msg: $0.0, direction: $0.1)
9+
.flatMap { (message, direction) in
10+
self.fetchDIDPair(from: message.from, to: message.to)
11+
.map {
12+
(message, direction, $0)
13+
}
1114
}
1215
.collect()
16+
.eraseToAnyPublisher()
17+
.flatMap { messages in
18+
self.batchUpdateOrCreate(
19+
messages.map(\.0.id),
20+
context: writeContext
21+
) { id, cdobj, _ in
22+
guard let domainObjs = messages.first(where: { $0.0.id == id }) else {
23+
return
24+
}
25+
try cdobj.fromDomain(msg: domainObjs.0, direction: domainObjs.1, pair: domainObjs.2)
26+
}
27+
}
1328
.map { _ in () }
1429
.eraseToAnyPublisher()
1530
}
@@ -37,6 +52,10 @@ extension CDMessageDAO: MessageStore {
3752
}
3853
}
3954
.map { _ in }
55+
.mapError {
56+
print($0)
57+
return $0
58+
}
4059
.eraseToAnyPublisher()
4160
}
4261

@@ -47,6 +66,23 @@ extension CDMessageDAO: MessageStore {
4766
func removeAll() -> AnyPublisher<Void, Error> {
4867
deleteAllPublisher(context: writeContext)
4968
}
69+
70+
private func fetchDIDPair(from: DID?, to: DID?) -> AnyPublisher<CDDIDPair?, Error> {
71+
pairDAO
72+
.fetchController(
73+
predicate: NSPredicate(
74+
format: "(holderDID.did == %@) OR (holderDID.did == %@) OR (did == %@) OR (did == %@)",
75+
from?.string ?? "",
76+
to?.string ?? "",
77+
from?.string ?? "",
78+
to?.string ?? ""
79+
),
80+
context: writeContext
81+
)
82+
.first()
83+
.map { $0.first }
84+
.eraseToAnyPublisher()
85+
}
5086
}
5187

5288
private extension CDMessage {

EdgeAgentSDK/Pluto/Sources/PersistentStorage/Models/CDMessage+CoreDataProperties.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extension CDMessage {
1313
@NSManaged var from: String?
1414
@NSManaged var to: String?
1515
@NSManaged var thid: String?
16-
@NSManaged var direction: String?
16+
@NSManaged var direction: Int
1717
@NSManaged var pair: CDDIDPair?
1818
}
1919

EdgeAgentSDK/Pluto/Sources/PlutoImpl+Public.swift

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ extension PlutoImpl: Pluto {
5050
mediatorDAO.addMediator(peer: peer, routingDID: routingDID, mediatorDID: mediatorDID)
5151
}
5252

53+
public func storeCredentials(credentials: [StorableCredential]) -> AnyPublisher<Void, Error> {
54+
credentialsDAO.addCredentials(credentials: credentials)
55+
}
56+
5357
public func storeCredential(credential: StorableCredential) -> AnyPublisher<Void, Error> {
5458
credentialsDAO.addCredential(credential: credential)
5559
}

EdgeAgentSDK/Pluto/Sources/Resources/PrismPluto.xcdatamodeld/PrismPluto.xcdatamodel/contents

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23A344" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
2+
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
33
<entity name="CDAvailableClaim" representedClassName="CDAvailableClaim" syncable="YES">
44
<attribute name="value" attributeType="String"/>
55
<relationship name="credential" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CDCredential" inverseName="queryAvailableClaims" inverseEntity="CDCredential"/>
@@ -58,7 +58,7 @@
5858
<entity name="CDMessage" representedClassName="CDMessage" syncable="YES">
5959
<attribute name="createdTime" attributeType="Date" usesScalarValueType="NO"/>
6060
<attribute name="dataJson" attributeType="Binary"/>
61-
<attribute name="direction" optional="YES" attributeType="String"/>
61+
<attribute name="direction" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
6262
<attribute name="from" optional="YES" attributeType="String"/>
6363
<attribute name="messageId" attributeType="String"/>
6464
<attribute name="thid" optional="YES" attributeType="String"/>

EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialImporter.swift

+7-4
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,14 @@ private func importAnoncredCredential(
4646
let domainCred = try JSONDecoder().decode(AnonCredential.self, from: credentialData)
4747
let credentialDefinitionData = try? await credentialDefinitionDownloader
4848
.downloadFromEndpoint(urlOrDID: domainCred.credentialDefinitionId)
49-
let schemaData = try? await schemaDownloader
50-
.downloadFromEndpoint(urlOrDID: domainCred.schemaId)
49+
let cdData = try credentialDefinitionData.map { try JSONDecoder.didComm().decode(AnonCredentialDefinition.self, from: $0) }
50+
let schemaData = (try? await schemaDownloader
51+
.downloadFromEndpoint(urlOrDID: domainCred.schemaId))
52+
.flatMap { try? JSONDecoder.didComm().decode(AnonCredentialSchema.self, from: $0) }
53+
5154
return AnoncredsCredentialStack(
52-
schema: schemaData.flatMap { try? JSONDecoder.didComm().decode(AnonCredentialSchema.self, from: $0) },
53-
definition: try credentialDefinitionData.map { try JSONDecoder.didComm().decode(AnonCredentialDefinition.self, from: $0) },
55+
schema: schemaData,
56+
definition: cdData,
5457
credential: domainCred
5558
)
5659
}

0 commit comments

Comments
 (0)