-
Notifications
You must be signed in to change notification settings - Fork 9
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
[ABW-2214] Shared persona data not persisted in profile #734
Changes from all commits
105c690
0cc3e11
76fb812
6fdbe71
fd3eb27
c1b8ac6
37bb0d9
ecdd8bc
8ffad72
c9d18ec
bd33190
2069f8c
0e04719
d39b310
39a568c
df6331c
68e4f1f
18fdb22
2d874a8
f89fb8d
729876c
9d83bb5
1efa8d5
35bee56
1ee5db9
ae8a69e
4b9b6e7
ef07c89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,7 +36,7 @@ extension AuthorizedDappsClient { | |
public typealias DetailsForAuthorizedDapp = @Sendable (Profile.Network.AuthorizedDapp) async throws -> Profile.Network.AuthorizedDappDetailed | ||
public typealias AddAuthorizedDapp = @Sendable (Profile.Network.AuthorizedDapp) async throws -> Void | ||
public typealias UpdateOrAddAuthorizedDapp = @Sendable (Profile.Network.AuthorizedDapp) async throws -> Void | ||
public typealias ForgetAuthorizedDapp = @Sendable (Profile.Network.AuthorizedDapp.ID, NetworkID) async throws -> Void | ||
public typealias ForgetAuthorizedDapp = @Sendable (Profile.Network.AuthorizedDapp.ID, NetworkID?) async throws -> Void | ||
public typealias UpdateAuthorizedDapp = @Sendable (Profile.Network.AuthorizedDapp) async throws -> Void | ||
public typealias DeauthorizePersonaFromDapp = @Sendable (Profile.Network.Persona.ID, Profile.Network.AuthorizedDapp.ID, NetworkID) async throws -> Void | ||
} | ||
|
@@ -57,4 +57,119 @@ extension AuthorizedDappsClient { | |
) async throws -> IdentifiedArrayOf<Profile.Network.AuthorizedDapp> { | ||
try await getAuthorizedDapps().filter { $0.referencesToAuthorizedPersonas.ids.contains(id) } | ||
} | ||
|
||
public func removeBrokenReferencesToSharedPersonaData( | ||
personaCurrent: Profile.Network.Persona, | ||
personaUpdated: Profile.Network.Persona | ||
) async throws { | ||
guard personaCurrent.id == personaUpdated.id else { | ||
struct PersonaIDMismatch: Swift.Error {} | ||
throw PersonaIDMismatch() | ||
} | ||
let identityAddress = personaCurrent.address | ||
let dApps = try await getAuthorizedDapps() | ||
|
||
// We only care about the updated persona | ||
let idsOfEntriesToKeep = Set(personaUpdated.personaData.entries.map(\.id)) | ||
|
||
for authorizedDapp in dApps { | ||
var updatedAuthedDapp = authorizedDapp | ||
for personaSimple in authorizedDapp.referencesToAuthorizedPersonas { | ||
guard personaSimple.identityAddress == identityAddress else { | ||
// irrelvant Persona | ||
continue | ||
} | ||
// Relevant Persona => check if there are any old PersonaData entries that needs deleting | ||
let idsOfEntriesToDelete = personaSimple.sharedPersonaData.entryIDs.subtracting(idsOfEntriesToKeep) | ||
|
||
guard !idsOfEntriesToDelete.isEmpty else { | ||
// No old entries needs to be deleted. | ||
continue | ||
} | ||
|
||
loggerGlobal.notice("Pruning stale PersonaData entries with IDs: \(idsOfEntriesToDelete), for persona: \(personaUpdated.address) (\(personaUpdated.displayName.rawValue)), for Dapp: \(authorizedDapp)") | ||
var authorizedPersonaSimple = personaSimple | ||
|
||
authorizedPersonaSimple.sharedPersonaData.remove(ids: idsOfEntriesToDelete) | ||
|
||
// Write back to `updatedAuthedDapp` | ||
updatedAuthedDapp.referencesToAuthorizedPersonas[id: authorizedPersonaSimple.id] = authorizedPersonaSimple | ||
|
||
// Soundness check | ||
if | ||
!Set(personaUpdated.personaData.entries.map(\.id)) | ||
.isSuperset( | ||
of: | ||
updatedAuthedDapp | ||
.referencesToAuthorizedPersonas[id: authorizedPersonaSimple.id]! | ||
.sharedPersonaData | ||
.entryIDs | ||
) | ||
{ | ||
let errMsg = "Incorrect implementation, failed to prune stale PersonaData entries for authorizedDapp" | ||
assertionFailure(errMsg) | ||
loggerGlobal.error(.init(stringLiteral: errMsg)) | ||
} | ||
} | ||
if updatedAuthedDapp != authorizedDapp { | ||
// Write back `updatedAuthedDapp` to Profile only if changes were needed | ||
try await updateAuthorizedDapp(updatedAuthedDapp) | ||
} else { | ||
loggerGlobal.feature("nothing to do... skipped updating authorizedDapp") | ||
} | ||
} | ||
} | ||
} | ||
|
||
extension Profile.Network.AuthorizedDapp.AuthorizedPersonaSimple.SharedPersonaData { | ||
private mutating func remove(id: PersonaDataEntryID) { | ||
func removeCollectionIfNeeded( | ||
at keyPath: WritableKeyPath<Self, Profile.Network.AuthorizedDapp.AuthorizedPersonaSimple.SharedPersonaData.SharedCollection?> | ||
) { | ||
guard | ||
var collection = self[keyPath: keyPath], | ||
collection.ids.contains(id) | ||
else { return } | ||
collection.ids.remove(id) | ||
switch collection.request.quantifier { | ||
case .atLeast: | ||
if collection.ids.count < collection.request.quantity { | ||
// must delete whole collection since requested quantity is no longer fulfilled. | ||
self[keyPath: keyPath] = nil | ||
} | ||
case .exactly: | ||
// Must delete whole collection since requested quantity is no longer fulfilled, | ||
// since we **just** deleted the id from `ids`. | ||
self[keyPath: keyPath] = nil | ||
} | ||
} | ||
|
||
func removeEntryIfNeeded( | ||
at keyPath: WritableKeyPath<Self, PersonaDataEntryID?> | ||
) { | ||
guard self[keyPath: keyPath] == id else { return } | ||
self[keyPath: keyPath] = nil | ||
} | ||
|
||
removeEntryIfNeeded(at: \.name) | ||
removeEntryIfNeeded(at: \.dateOfBirth) | ||
removeEntryIfNeeded(at: \.companyName) | ||
removeCollectionIfNeeded(at: \.emailAddresses) | ||
removeCollectionIfNeeded(at: \.phoneNumbers) | ||
removeCollectionIfNeeded(at: \.urls) | ||
removeCollectionIfNeeded(at: \.postalAddresses) | ||
removeCollectionIfNeeded(at: \.creditCards) | ||
|
||
// The only purpose of this switch is to make sure we get a compilation error when we add a new PersonaData.Entry kind, so | ||
// we do not forget to handle it here. | ||
switch PersonaData.Entry.Kind.fullName { | ||
case .fullName, .dateOfBirth, .companyName, .emailAddress, .phoneNumber, .url, .postalAddress, .creditCard: break | ||
} | ||
Comment on lines
+163
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally this should be covered by tests, instead of sprinkling this switch over the codebase... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree 100% with you :D There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when all other tickets are merged today and soundness check of a binary looks promising I can start write tests, but ATM no time :/ :/ :/ |
||
} | ||
|
||
mutating func remove(ids: Set<PersonaDataEntryID>) { | ||
ids.forEach { | ||
remove(id: $0) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,8 +20,10 @@ extension AuthorizedDappsClient: DependencyKey { | |
_ = try $0.addAuthorizedDapp(newDapp) | ||
} | ||
}, | ||
forgetAuthorizedDapp: { toForget, networkID in | ||
try await getProfileStore().updating { | ||
forgetAuthorizedDapp: { toForget, maybeNetworkID in | ||
let currentNetworkID = await getProfileStore().profile.networkID | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're doing this in any case, what is the purpose of even allowing caller to submit a networkID? It's not performance, so is it the case that you might want to use some other networkID? Either way, might as well do
And I would also rename There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
let networkID = maybeNetworkID ?? currentNetworkID | ||
return try await getProfileStore().updating { | ||
_ = try $0.forgetAuthorizedDapp(toForget, on: networkID) | ||
} | ||
}, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,7 @@ public struct AuthorizedDapps: Sendable, FeatureReducer { | |
case loadedDapps(TaskResult<Profile.Network.AuthorizedDapps>) | ||
case loadedThumbnail(URL, dApp: Profile.Network.AuthorizedDapp.ID) | ||
case presentDappDetails(DappDetails.State) | ||
case failedToGetDetailsOfDapp(id: Profile.Network.AuthorizedDapp.ID) | ||
} | ||
|
||
public enum ChildAction: Sendable, Equatable { | ||
|
@@ -56,17 +57,16 @@ public struct AuthorizedDapps: Sendable, FeatureReducer { | |
public func reduce(into state: inout State, viewAction: ViewAction) -> EffectTask<Action> { | ||
switch viewAction { | ||
case .appeared: | ||
return .task { | ||
await loadAuthorizedDapps() | ||
} | ||
return loadAuthorizedDapps() | ||
|
||
case let .didSelectDapp(dAppID): | ||
return .run { send in | ||
let details = try await authorizedDappsClient.getDetailedDapp(dAppID) | ||
let presentedDappState = DappDetails.State(dApp: details) | ||
await send(.internal(.presentDappDetails(presentedDappState))) | ||
} catch: { error, _ in | ||
} catch: { error, send in | ||
errorQueue.schedule(error) | ||
await send(.internal(.failedToGetDetailsOfDapp(id: dAppID))) | ||
} | ||
} | ||
} | ||
|
@@ -86,6 +86,19 @@ public struct AuthorizedDapps: Sendable, FeatureReducer { | |
} | ||
} | ||
} | ||
|
||
case let .failedToGetDetailsOfDapp(dappId): | ||
#if DEBUG | ||
return .run { _ in | ||
CyonAlexRDX marked this conversation as resolved.
Show resolved
Hide resolved
|
||
loggerGlobal.notice("DEBUG ONLY deleting authorized dapp since we failed to load detailed info about it") | ||
try? await authorizedDappsClient.forgetAuthorizedDapp(dappId, nil) | ||
|
||
}.concatenate(with: loadAuthorizedDapps()) | ||
#else | ||
// FIXME: Should we have to handle this, this is a discrepancy bug.. | ||
return .none | ||
#endif | ||
|
||
case let .loadedDapps(.failure(error)): | ||
errorQueue.schedule(error) | ||
return .none | ||
|
@@ -103,18 +116,19 @@ public struct AuthorizedDapps: Sendable, FeatureReducer { | |
case .presentedDapp(.presented(.delegate(.dAppForgotten))): | ||
return .run { send in | ||
await send(.child(.presentedDapp(.dismiss))) | ||
await send(loadAuthorizedDapps()) | ||
} | ||
}.concatenate(with: loadAuthorizedDapps()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: move on next line. |
||
|
||
case .presentedDapp: | ||
return .none | ||
} | ||
} | ||
|
||
private func loadAuthorizedDapps() async -> Action { | ||
let result = await TaskResult { | ||
try await authorizedDappsClient.getAuthorizedDapps() | ||
private func loadAuthorizedDapps() -> EffectTask<Action> { | ||
CyonAlexRDX marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.run { send in | ||
let result = await TaskResult { | ||
try await authorizedDappsClient.getAuthorizedDapps() | ||
} | ||
await send(.internal(.loadedDapps(result))) | ||
} | ||
return .internal(.loadedDapps(result)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't we need to check the count in the exact case? And why can't we keep what we have, even if it's not enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because we KNOW it cannot pass, when
exactly
is required, we know we can delete the whole collection, because we just decreased the number of IDs by one, thusexactly
cannot be fulfilled, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can add a clarifying comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, presuming it has been correctly checked previously, which is probably a reasonable assumption, but in other places here we seem to be very defensive.
But what is the high level idea, why do we need to delete anything at all? What's the harm in keeping it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kugel3 we dont want the
ids
to contradictrequest
ever, so we have to delete it if it does.