Skip to content

Commit

Permalink
feat(sample): add backup to sample app
Browse files Browse the repository at this point in the history
Fixes an issue with other SDKs interoperability

Signed-off-by: goncalo-frade-iohk <[email protected]>
  • Loading branch information
goncalo-frade-iohk committed Jun 17, 2024
1 parent f042691 commit 72c965f
Show file tree
Hide file tree
Showing 28 changed files with 303 additions and 67 deletions.
12 changes: 12 additions & 0 deletions Core/Sources/Helpers/JSONEncoder+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,16 @@ public extension JSONEncoder {
encoder.outputFormatting = [.withoutEscapingSlashes, .sortedKeys]
return encoder
}

static func backup() -> JSONEncoder {
let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .base64
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.dateEncodingStrategy = .custom({ date, encoder in
var container = encoder.singleValueContainer()
try container.encode(Int(date.timeIntervalSince1970))
})
encoder.outputFormatting = [.withoutEscapingSlashes, .sortedKeys]
return encoder
}
}
2 changes: 2 additions & 0 deletions EdgeAgentSDK/Domain/Sources/AnyCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ extension AnyCodable: Codable {
var container = encoder.singleValueContainer()

switch self.value {
case is NSNull:
try container.encodeNil()
case is Void:
try container.encodeNil()
case let bool as Bool:
Expand Down
7 changes: 7 additions & 0 deletions EdgeAgentSDK/Domain/Sources/BBs/Pluto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ public protocol Pluto {
mediatorDID: DID
) -> AnyPublisher<Void, Error>

/// Stores multiple verifiable credentials in the data store.
/// - Parameter credentials: The credentials to store.
/// - Returns: A publisher that completes when the operation finishes.
func storeCredentials(
credentials: [StorableCredential]
) -> AnyPublisher<Void, Error>

/// Stores a verifiable credential in the data store.
/// - Parameter credential: The credential to store.
/// - Returns: A publisher that completes when the operation finishes.
Expand Down
21 changes: 14 additions & 7 deletions EdgeAgentSDK/Domain/Sources/Models/Message+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,22 @@ extension Message: Codable {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(piuri, forKey: .piuri)
if let dic = try? JSONSerialization.jsonObject(with: body) as? [String: Any] {
try container.encode(AnyCodable(dic), forKey: .body)
if let dic = try? JSONSerialization.jsonObject(with: body) as? [String: Any?] {
var filteredDictionary = dic
for (key, value) in dic {
if value == nil || value is NSNull {
filteredDictionary.removeValue(forKey: key)
}
}
try container.encode(AnyCodable(filteredDictionary), forKey: .body)
} else {
try container.encode(body, forKey: .body)
}
try container.encode(extraHeaders, forKey: .extraHeaders)
try container.encode(createdTime, forKey: .createdTime)
try container.encode(expiresTimePlus, forKey: .expiresTimePlus)
try container.encode(attachments, forKey: .attachments)
try container.encode(ack, forKey: .ack)
try container.encodeIfPresent(extraHeaders, forKey: .extraHeaders)
try container.encodeIfPresent(createdTime, forKey: .createdTime)
try container.encodeIfPresent(expiresTimePlus, forKey: .expiresTimePlus)
try container.encodeIfPresent(attachments, forKey: .attachments)
try container.encodeIfPresent(ack, forKey: .ack)
try from.map { try container.encode($0.string, forKey: .from) }
try to.map { try container.encode($0.string, forKey: .to) }
try fromPrior.map { try container.encode($0, forKey: .fromPrior) }
Expand All @@ -48,6 +54,7 @@ extension Message: Codable {
if
let bodyCodable = try? container.decodeIfPresent(AnyCodable.self, forKey: .body),
(bodyCodable.value is [String: Any] || bodyCodable.value is [String]),
JSONSerialization.isValidJSONObject(bodyCodable.value),
let bodyData = try? JSONSerialization.data(withJSONObject: bodyCodable.value)
{
body = bodyData
Expand Down
6 changes: 3 additions & 3 deletions EdgeAgentSDK/Domain/Sources/Models/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import Foundation
/// 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.
public struct Message: Identifiable, Hashable {
/// The direction of the message (sent or received).
public enum Direction: String, Codable {
case sent
case received
public enum Direction: Int, Codable {
case sent = 0
case received = 1
}

/// The unique identifier of the message.
Expand Down
12 changes: 6 additions & 6 deletions EdgeAgentSDK/Domain/Sources/Models/MessageAttachment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,13 @@ extension AttachmentDescriptor: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(mediaType, forKey: .mediaType)
try container.encodeIfPresent(mediaType, forKey: .mediaType)
try container.encode(data, forKey: .data)
try container.encode(filename, forKey: .filename)
try container.encode(format, forKey: .format)
try container.encode(lastmodTime, forKey: .lastmodTime)
try container.encode(byteCount, forKey: .byteCount)
try container.encode(description, forKey: .description)
try container.encodeIfPresent(filename, forKey: .filename)
try container.encodeIfPresent(format, forKey: .format)
try container.encodeIfPresent(lastmodTime, forKey: .lastmodTime)
try container.encodeIfPresent(byteCount, forKey: .byteCount)
try container.encodeIfPresent(description, forKey: .description)
}

public init(from decoder: Decoder) throws {
Expand Down
20 changes: 7 additions & 13 deletions EdgeAgentSDK/EdgeAgent/Sources/EdgeAgent+Backup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ extension EdgeAgent {
let backupData = try JWE(compactString: encrypted)
.decrypt(recipientKey: try JSONDecoder.didComm().decode(JSONWebKey.JWK.self, from: jwk))

print(try backupData.tryToString())

let backup = try JSONDecoder.didComm().decode(Backup.self, from: backupData)

try await recoverDidsWithKeys(dids: backup.dids, keys: backup.keys)
Expand Down Expand Up @@ -208,26 +206,24 @@ extension EdgeAgent {
}

func recoverMessages(messages: [String]) async throws {
let messages = try messages.compactMap { messageStr -> (Message, Message.Direction)? in
let messages = messages.compactMap { messageStr -> (Message, Message.Direction)? in
guard
let messageData = Data(base64URLEncoded: messageStr)
let messageData = Data(base64URLEncoded: messageStr),
let message = try? JSONDecoder.didComm().decode(Message.self, from: messageData)
else {
return nil
}
let message = try JSONDecoder.didComm().decode(Message.self, from: messageData)

return (message, message.direction)
}

try await pluto.storeMessages(messages: messages)
.first()
.await()
try await pluto.storeMessages(messages: messages).first().await()
}

func recoverCredentials(credentials: [Backup.Credential]) async throws {
let downloader = DownloadDataWithResolver(castor: castor)
let pollux = self.pollux
return try await credentials
let storableCredentials = try await credentials
.asyncCompactMap { bakCredential -> StorableCredential? in
guard
let data = Data(base64URLEncoded: bakCredential.data)
Expand All @@ -243,9 +239,7 @@ extension EdgeAgent {
]
).storable
}
.asyncForEach { [weak self] in
try await self?.pluto.storeCredential(credential: $0).first().await()
}
try await self.pluto.storeCredentials(credentials: storableCredentials).first().await()
}

func recoverMediators(mediators: [Backup.Mediator]) async throws {
Expand Down Expand Up @@ -330,7 +324,7 @@ extension EdgeAgent {
.first()
.await()
.compactMap {
try JSONEncoder.didComm().encode($0).base64UrlEncodedString()
return try JSONEncoder.backup().encode($0).base64UrlEncodedString()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ struct PrismOnboardingInvitation {
let body: Body

init(jsonString: String) throws {
guard let jsonData = jsonString.data(using: .utf8) else { throw EdgeAgentError.invitationIsInvalidError }
guard let jsonData = jsonString.data(using: .utf8) else {
throw EdgeAgentError.invitationIsInvalidError
}
let object = try JSONDecoder.didComm().decode(Body.self, from: jsonData)
guard object.type == ProtocolTypes.prismOnboarding.rawValue else {
throw EdgeAgentError.unknownInvitationTypeError
Expand Down
40 changes: 21 additions & 19 deletions EdgeAgentSDK/EdgeAgent/Tests/BackupWalletTests.swift

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions EdgeAgentSDK/EdgeAgent/Tests/Helper/MockPluto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ class MockPluto: Pluto {
return Just(()).tryMap { $0 }.eraseToAnyPublisher()
}

func storeCredentials(credentials: [any StorableCredential]) -> AnyPublisher<Void, any Error> {
self.credentials.append(contentsOf: credentials)
return Just(()).tryMap { $0 }.eraseToAnyPublisher()
}

func storeCredential(credential: Domain.StorableCredential) -> AnyPublisher<Void, Error> {
credentials.append(credential)
return Just(()).tryMap { $0 }.eraseToAnyPublisher()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ extension CoreDataDAO {
}

extension CoreDataDAO where CoreDataObject: Identifiable {
func batchUpdateOrCreate(
_ ids: [CoreDataObject.ID],
context: NSManagedObjectContext,
modify: @escaping (CoreDataObject.ID, CoreDataObject, NSManagedObjectContext) throws -> Void
) -> AnyPublisher<[CoreDataObject.ID], Error> {
context.write { context in
try ids.forEach { id in
try modify(id, self.fetchByID(id, context: context) ?? self.newEntity(context: context), context)
}
return ids
}
.eraseToAnyPublisher()
}
func updateOrCreate(
_ id: CoreDataObject.ID,
context: NSManagedObjectContext,
Expand Down
2 changes: 1 addition & 1 deletion EdgeAgentSDK/Pluto/Sources/Helpers/Message+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct CodableMessage: Codable {
let fromPrior = try? container.decodeIfPresent(String.self, forKey: .fromPrior)
let thid = try? container.decodeIfPresent(String.self, forKey: .thid)
let pthid = try? container.decodeIfPresent(String.self, forKey: .pthid)
let directionRaw = try container.decodeIfPresent(String.self, forKey: .direction)
let directionRaw = try container.decodeIfPresent(Int.self, forKey: .direction)
let direction = directionRaw.flatMap { Message.Direction(rawValue: $0) }

self.init(message: .init(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,25 @@ extension CDCredentialDAO: CredentialStore {
}

func addCredentials(credentials: [StorableCredential]) -> AnyPublisher<Void, Error> {
credentials.publisher.flatMap {
self.addCredential(credential: $0)
batchUpdateOrCreate(
credentials.map(\.storingId),
context: writeContext
) { id, cdobj, context in
guard let credential = credentials.first(where: { $0.storingId == id}) else {
throw PlutoError.unknownCredentialTypeError
}
let claimsObjs = credential.queryAvailableClaims.map {
let obj = CDAvailableClaim(
entity: CDAvailableClaim.entity(),
insertInto: context
)
obj.value = $0
return obj
}
try cdobj.parseFromDomain(
from: credential,
withClaims: Set(claimsObjs)
)
}
.map { _ in }
.eraseToAnyPublisher()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension CDMessageDAO: MessageProvider {

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

func getAllReceived() -> AnyPublisher<[Message], Error> {
fetchController(
predicate: NSPredicate(format: "(direction == %@)", "received"),
predicate: NSPredicate(format: "(direction == %d)", 1),
context: readContext
)
.tryMap { try $0.map { try $0.toDomain() } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,25 @@ extension CDMessageDAO: MessageStore {
func addMessages(messages: [(Message, Message.Direction)]) -> AnyPublisher<Void, Error> {
messages
.publisher
.flatMap {
self.addMessage(msg: $0.0, direction: $0.1)
.flatMap { (message, direction) in
self.fetchDIDPair(from: message.from, to: message.to)
.map {
(message, direction, $0)
}
}
.collect()
.eraseToAnyPublisher()
.flatMap { messages in
self.batchUpdateOrCreate(
messages.map(\.0.id),
context: writeContext
) { id, cdobj, _ in
guard let domainObjs = messages.first(where: { $0.0.id == id }) else {
return
}
try cdobj.fromDomain(msg: domainObjs.0, direction: domainObjs.1, pair: domainObjs.2)
}
}
.map { _ in () }
.eraseToAnyPublisher()
}
Expand Down Expand Up @@ -37,6 +52,10 @@ extension CDMessageDAO: MessageStore {
}
}
.map { _ in }
.mapError {
print($0)
return $0
}
.eraseToAnyPublisher()
}

Expand All @@ -47,6 +66,23 @@ extension CDMessageDAO: MessageStore {
func removeAll() -> AnyPublisher<Void, Error> {
deleteAllPublisher(context: writeContext)
}

private func fetchDIDPair(from: DID?, to: DID?) -> AnyPublisher<CDDIDPair?, Error> {
pairDAO
.fetchController(
predicate: NSPredicate(
format: "(holderDID.did == %@) OR (holderDID.did == %@) OR (did == %@) OR (did == %@)",
from?.string ?? "",
to?.string ?? "",
from?.string ?? "",
to?.string ?? ""
),
context: writeContext
)
.first()
.map { $0.first }
.eraseToAnyPublisher()
}
}

private extension CDMessage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension CDMessage {
@NSManaged var from: String?
@NSManaged var to: String?
@NSManaged var thid: String?
@NSManaged var direction: String?
@NSManaged var direction: Int
@NSManaged var pair: CDDIDPair?
}

Expand Down
4 changes: 4 additions & 0 deletions EdgeAgentSDK/Pluto/Sources/PlutoImpl+Public.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ extension PlutoImpl: Pluto {
mediatorDAO.addMediator(peer: peer, routingDID: routingDID, mediatorDID: mediatorDID)
}

public func storeCredentials(credentials: [StorableCredential]) -> AnyPublisher<Void, Error> {
credentialsDAO.addCredentials(credentials: credentials)
}

public func storeCredential(credential: StorableCredential) -> AnyPublisher<Void, Error> {
credentialsDAO.addCredential(credential: credential)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23A344" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22757" systemVersion="23F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="CDAvailableClaim" representedClassName="CDAvailableClaim" syncable="YES">
<attribute name="value" attributeType="String"/>
<relationship name="credential" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="CDCredential" inverseName="queryAvailableClaims" inverseEntity="CDCredential"/>
Expand Down Expand Up @@ -58,7 +58,7 @@
<entity name="CDMessage" representedClassName="CDMessage" syncable="YES">
<attribute name="createdTime" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="dataJson" attributeType="Binary"/>
<attribute name="direction" optional="YES" attributeType="String"/>
<attribute name="direction" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="from" optional="YES" attributeType="String"/>
<attribute name="messageId" attributeType="String"/>
<attribute name="thid" optional="YES" attributeType="String"/>
Expand Down
11 changes: 7 additions & 4 deletions EdgeAgentSDK/Pollux/Sources/PolluxImpl+CredentialImporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,14 @@ private func importAnoncredCredential(
let domainCred = try JSONDecoder().decode(AnonCredential.self, from: credentialData)
let credentialDefinitionData = try? await credentialDefinitionDownloader
.downloadFromEndpoint(urlOrDID: domainCred.credentialDefinitionId)
let schemaData = try? await schemaDownloader
.downloadFromEndpoint(urlOrDID: domainCred.schemaId)
let cdData = try credentialDefinitionData.map { try JSONDecoder.didComm().decode(AnonCredentialDefinition.self, from: $0) }
let schemaData = (try? await schemaDownloader
.downloadFromEndpoint(urlOrDID: domainCred.schemaId))
.flatMap { try? JSONDecoder.didComm().decode(AnonCredentialSchema.self, from: $0) }

return AnoncredsCredentialStack(
schema: schemaData.flatMap { try? JSONDecoder.didComm().decode(AnonCredentialSchema.self, from: $0) },
definition: try credentialDefinitionData.map { try JSONDecoder.didComm().decode(AnonCredentialDefinition.self, from: $0) },
schema: schemaData,
definition: cdData,
credential: domainCred
)
}
Loading

0 comments on commit 72c965f

Please sign in to comment.