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

Identity Metadata Repository setup #2520

Merged
merged 11 commits into from
Jul 2, 2024
Merged

Identity Metadata Repository setup #2520

merged 11 commits into from
Jul 2, 2024

Conversation

lmuntaner
Copy link
Collaborator

@lmuntaner lmuntaner commented Jun 27, 2024

Main motivation is to set up the module to manage the anchor metadata.


🟡 Some screens were changed

@lmuntaner
Copy link
Collaborator Author

@frederikrothenberger before I continue with tests could you take a look and let me know if this is what you had in mind.

Copy link
Member

@frederikrothenberger frederikrothenberger left a 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;

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?

Copy link
Collaborator Author

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;

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 (

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.

Copy link
Collaborator Author

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<

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?

Copy link
Collaborator Author

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.

@lmuntaner
Copy link
Collaborator Author

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?

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 undefined or false accordingly?

@frederikrothenberger
Copy link
Member

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?

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 undefined or false accordingly?

Yes, a warning should be sufficient. 👍

@@ -212,6 +212,7 @@ const authenticate = async (
const derivationOrigin =
authContext.authRequest.derivationOrigin ?? authContext.requestOrigin;

// TODO: Commit state
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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.

@lmuntaner
Copy link
Collaborator Author

@frederikrothenberger ready for review

@lmuntaner lmuntaner marked this pull request as ready for review June 28, 2024 10:53
@lmuntaner lmuntaner requested a review from a team as a code owner June 28, 2024 10:53
Copy link
Member

@frederikrothenberger frederikrothenberger left a 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.

src/frontend/src/repositories/anchorMetadata.ts Outdated Show resolved Hide resolved
* 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 {

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".

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

src/frontend/src/utils/iiConnection.test.ts Outdated Show resolved Hide resolved
identity_metadata_replace: vi.fn().mockResolvedValue({ Ok: null }),
} as unknown as ActorSubclass<_SERVICE>;

test("gets anchor info from actor", async () => {

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?

Copy link
Collaborator Author

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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be readonly?

Copy link
Collaborator Author

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();

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?

Copy link
Collaborator Author

@lmuntaner lmuntaner Jul 1, 2024

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 () => {

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

src/frontend/src/repositories/anchorMetadata.test.ts Outdated Show resolved Hide resolved
@@ -0,0 +1,223 @@
import { MetadataMapV2 } from "$generated/internet_identity_types";

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.

Copy link
Collaborator Author

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 (

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
setPartialMetadata = async (
updateIdentityMetadata = async (

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@lmuntaner lmuntaner changed the title Anchor Metadata Repository setup Identity Metadata Repository setup Jul 1, 2024
@lmuntaner
Copy link
Collaborator Author

@frederikrothenberger ready for another review

@lmuntaner
Copy link
Collaborator Author

@frederikrothenberger wait a bit, I found a bug.

@lmuntaner
Copy link
Collaborator Author

@frederikrothenberger ready. I forgot to await for the rawMetadata.

Copy link
Member

@frederikrothenberger frederikrothenberger left a 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();

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.

Copy link
Collaborator Author

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.

@lmuntaner lmuntaner added this pull request to the merge queue Jul 2, 2024
Merged via the queue into main with commit 5a685b0 Jul 2, 2024
65 checks passed
@lmuntaner lmuntaner deleted the lm-metadata-repository branch July 2, 2024 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants