Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sample): add backup to sample app #148

Merged
merged 1 commit into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading