-
Notifications
You must be signed in to change notification settings - Fork 135
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
Identity Metadata Repository setup #2520
Conversation
@frederikrothenberger before I continue with tests could you take a look and let me know if this is what you had in mind. |
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.
Yes, this is the right direction. Not sure about the complexity of retries though...
It makes the front-end more robust, but the PR #2517 didn't have it either?
import type { AuthenticatedConnection } from "$src/utils/iiConnection"; | ||
|
||
export type AnchorMetadata = { | ||
recoveryPageShownTimestampMillis?: number; |
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.
Doesn't this need to be bigint
?
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 will get it from the browser with Date.now()
which returns a number
.
From chatGPT:
Date.now() will return Number.MAX_SAFE_INTEGER sometime around the year 287,586
If we were using nanoseconds, then yes. But with milliseconds it should be enough.
|
||
const RECOVERY_PAGE_SHOW_TIMESTAMP_MILLIS = "recoveryPageShownTimestampMillis"; | ||
const METADATA_FETCH_TIMEOUT = 1_000; | ||
const METADATA_FETCH_RETRIES = 10; |
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.
10 seems like a lot, no?
break; | ||
} | ||
} | ||
if ( |
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'd suggest to extract a helper for this condition.
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.
makes sense
@@ -511,6 +521,34 @@ export class AuthenticatedConnection extends Connection { | |||
await actor.remove(this.userNumber, publicKey); | |||
}; | |||
|
|||
getIdentityInfo = async (): Promise< |
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.
A client should only use the getAnchorMetadata
, setPartialMetadata
and commitMetadata
functions. getIdentityInfo
and setIdentityMetadata
should be private
then, no?
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.
Then the repository won't be able to use it...
I could pass the getter and setter as functions only.
I was thinking and yes, I think we don't need retries. Even more, I think I want to swallow the errors and log a warning only. The metadata is not crucial, we can find sensible defaults in case something isn't working as expected. Moreover, with the imperative code, I don't want an error to get metadata to disrupt a user flow. @frederikrothenberger What do you think if I remove all the throws and instead swallow or return |
Yes, a warning should be sufficient. 👍 |
@@ -212,6 +212,7 @@ const authenticate = async ( | |||
const derivationOrigin = | |||
authContext.authRequest.derivationOrigin ?? authContext.requestOrigin; | |||
|
|||
// TODO: Commit state |
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.
To be done in another PR, but I want to remember exactly where to put it.
@@ -250,6 +250,7 @@ export const renderManage = async ({ | |||
for (;;) { | |||
let anchorInfo: IdentityAnchorInfo; | |||
try { | |||
// TODO: commit state |
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.
To be done in another PR, but I want to remember exactly where to put it.
@frederikrothenberger ready for review |
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.
Thanks! I still have a few questions.
* It can then be read and updated in memory. | ||
* The metadata needs to be committed to the canister with the `commitMetadata` method to persist the changes. | ||
*/ | ||
export class AnchorMetadataRepository { |
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.
Quite a while ago we decided to sunset the term "anchor" because it is confusing. So on the UI, this term is no longer used. In the code, we didn't change things.
But for new code, we now use "identity" rather than anchor. Hence, why on the API level it is called IdentityMetadata
rather than AnchorMetadata
.
I think it would be better to use the new terminology here too and keep going with the slow journey of eventually replacing all the occurrences of "anchor".
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.
Done!
identity_metadata_replace: vi.fn().mockResolvedValue({ Ok: null }), | ||
} as unknown as ActorSubclass<_SERVICE>; | ||
|
||
test("gets anchor info from actor", async () => { |
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.
While it is good to test things, I'm a bit confused as to how getAnchorInfo
relates to the identity metadata functionality added in this PR?
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 was trying that I could successfully test AuthenticatedConnection and then I thought it might be ok to leave it.
But let's do that in another PR if I want to add more tests. I removed it from this PR.
private rawMetadata: RawMetadataState; | ||
// Flag to keep track whether we need to commit the metadata to the canister. | ||
private updatedMetadata: boolean; | ||
private getter: MetadataGetter; |
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.
These should be readonly
?
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.
yes, done!
*/ | ||
getMetadata = async (): Promise<AnchorMetadata | undefined> => { | ||
if (!this.metadataIsLoaded(this.rawMetadata)) { | ||
await this.loadMetadata(); |
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.
Shouldn't we simply await this.rawMetadata
here? Otherwise you might load things twice / are actually implementing an implicit retry?
Given it's not critical, we should probably avoid that, no?
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.
As agreed, there won't be a loadMetadata
call again and this function just returns the pretty metadata if available.
expect(await instance.getMetadata()).toEqual(mockAnchorMetadata); | ||
}); | ||
|
||
test("AnchorMetadataRepository loads data again on init in the background", async () => { |
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.
See comment below, but I think the metadata should not be loaded again.
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.
Done.
@@ -0,0 +1,223 @@ | |||
import { MetadataMapV2 } from "$generated/internet_identity_types"; |
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 think it would be good to have a test to ensure that the repository leaves additional data unchanged in rawMetadata
.
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 had a test for this: "IdentityMetadataRepository commits additional metadata to canister after update"
.
I rephrased the description to use "additional", maybe that was the confusing part.
return this.metadataRepository.getMetadata(); | ||
}; | ||
|
||
setPartialMetadata = async ( |
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.
setPartialMetadata = async ( | |
updateIdentityMetadata = async ( |
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.
done
@frederikrothenberger ready for another review |
@frederikrothenberger wait a bit, I found a bug. |
@frederikrothenberger ready. I forgot to await for the rawMetadata. |
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.
Thanks, lgtm! I left one suggestion but I don't feel strongly about it.
this.rawMetadata = "loading"; | ||
try { | ||
this.updatedMetadata = false; | ||
this.rawMetadata = await this.getter(); |
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.
You could also just put the Promise
as a field. That way you could await it alongside a timer with Promise.race(...)
in getMetadata
, no?
Not sure about the error handling though.
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.
Yes, if I put the promise there I lose the states and I don't see the advantage. Unless I don't put the promise of the getter directly, but I wrap the getter so that it returns a promise of the states.
What do you think it's the advantage? I can do it in another PR. We would also lose the possibility of a client waiting on loadMetadata
.
Main motivation is to set up the module to manage the anchor metadata.
🟡 Some screens were changed