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

Corrideat/task/#2341 anyone can join invite issue #2366

Open
wants to merge 30 commits into
base: master
Choose a base branch
from

Conversation

corrideat
Copy link
Member

No description provided.

Copy link

cypress bot commented Sep 30, 2024

group-income    Run #3258

Run Properties:  status check passed Passed #3258  •  git commit dd61181395 ℹ️: Merge d0ec792a690c74cbb02f151bc5819de95b32e27f into b64ecda81d1b1c9574b1b4490945...
Project group-income
Run status status check passed Passed #3258
Run duration 09m 20s
Commit git commit dd61181395 ℹ️: Merge d0ec792a690c74cbb02f151bc5819de95b32e27f into b64ecda81d1b1c9574b1b4490945...
Committer Ricardo Iván Vieitez Parra
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 10
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 111

@corrideat corrideat marked this pull request as ready for review October 2, 2024 17:47
@corrideat corrideat force-pushed the corrideat/task/#2341-anyone-can-join-invite-issue branch from 1d7a207 to 196a7b9 Compare October 2, 2024 17:47
@corrideat corrideat force-pushed the corrideat/task/#2341-anyone-can-join-invite-issue branch from 196a7b9 to 1459e8a Compare October 2, 2024 18:09
@corrideat
Copy link
Member Author

@greg Looks like this should be ready!

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Prelim

frontend/model/contracts/shared/time.js Outdated Show resolved Hide resolved
frontend/model/contracts/shared/time.js Outdated Show resolved Hide resolved
Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Running "exec:flow" (exec) task
Error -------------------------------------------------------------------- frontend/model/contracts/shared/time.js:177:4

Cannot expect string as the return type of function because string [1] is incompatible with implicitly-returned
undefined. [incompatible-return]

   177| ): string {
           ^^^^^^ [1]


Error ------------------------------------------------------------------- frontend/model/contracts/shared/time.js:263:46

Cannot build a typed interface for this module. You should annotate the exports of this module with types. Missing type
annotation at function return: [signature-verification-failure]

   263| export function timeLeft (expiryTime: number) {
                                                     

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

A few weird errors after visiting http://localhost:3000/app/group-settings using GI_PERSIST=sqlite grunt dev with the data I sent to you on GIG DM.

First there was this:

Screenshot 2024-10-04 at 11 58 07 AM

Not sure if that's a big deal or not.

But then 3 different invites were created, when there should only be one:

Screenshot 2024-10-04 at 11 58 33 AM

And they are showing 0/60, when they should show 0/150.

It also took a while for the new invites to appear...

@corrideat
Copy link
Member Author

  1. The lastLoggedIn appears to be unrelated
  2. 3 different invites, I'll see what you're doing.
  3. And they are showing 0/60, when they should show 0/150. Not sure where you're getting the 150 number (maybe you mean 180?), but it's correct that each has 60, or whatever the number is.

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Nice improvements @corrideat!

I left a few comments (see above), and also tried testing it again.

I wasn't successful though with the test data that I sent you. Logged in as u1 and switched to Turtles (4) group.

Old invite still there, showing wrong expiry too:

Screenshot 2024-10-09 at 12 10 05 PM

Using that invite shows it's expired.

This PR needs to also update the UI to say that this invite doesn't expire (if it doesn't have an expiry).

@corrideat corrideat force-pushed the corrideat/task/#2341-anyone-can-join-invite-issue branch from e8ba426 to 7900236 Compare October 10, 2024 19:22
@corrideat corrideat force-pushed the corrideat/task/#2341-anyone-can-join-invite-issue branch from 7900236 to 330624d Compare October 10, 2024 19:33
@corrideat
Copy link
Member Author

@taoeffect I think this should be ready now

Comment on lines 28 to 58
// Wrapper function for performing contract upgrades and migrations
const contractUpdate = (updateFn: (contractIDHints: ?string[]) => any) => {
const loginErrorHandler = () => {
sbp('okTurtles.events/off', CONTRACTS_MODIFIED, modifiedHandled)
sbp('okTurtles.events/off', LOGIN_COMPLETE, loginCompleteHandler)
sbp('okTurtles.events/off', LOGOUT, logoutHandler)
}
const logoutHandler = () => {
sbp('okTurtles.events/off', CONTRACTS_MODIFIED, modifiedHandled)
}
const loginCompleteHandler = () => {
sbp('okTurtles.events/off', LOGIN_ERROR, loginErrorHandler)
}
// This function is called when the set of subscribed contracts is modified
const modifiedHandled = (_, { added }) => {
// Wait for the added contracts to be ready, then call the update function
sbp('chelonia/contract/wait', added).then(() => {
updateFn(added)
})
}

// Add event listeners for CONTRACTS_MODIFIED, LOGOUT, LOGIN_COMPLETE and
// LOGIN_ERROR
sbp('okTurtles.events/on', CONTRACTS_MODIFIED, modifiedHandled)
sbp('okTurtles.events/once', LOGOUT, logoutHandler)
sbp('okTurtles.events/once', LOGIN_COMPLETE, loginCompleteHandler)
sbp('okTurtles.events/once', LOGIN_ERROR, loginErrorHandler)

// Call the update function in the next tick
setTimeout(updateFn, 0)
}
Copy link
Member

Choose a reason for hiding this comment

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

So we're just going to keep calling the postUpgrade functions every time the contracts are modified?

That seems ugly.

  1. Is there someway to make it so that after the upgrade gets run, it's the modifiedHandled is no longer called?
  2. This seems like a potentially useful utility function. Can we import it from some other location instead of here? (Is there a way to make this available as a Chelonia dev utility)?

Copy link
Member

Choose a reason for hiding this comment

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

Is there someway to make it so that after the upgrade gets run, it's the modifiedHandled is no longer called?

One idea that occurred to me, is that updateFn can pass in a function that will unregister the listener that can be called once a successful upgrade is done.

Copy link
Member Author

@corrideat corrideat Oct 11, 2024

Choose a reason for hiding this comment

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

Is there someway to make it so that after the upgrade gets run, it's the modifiedHandled is no longer called?

Well, no, because that's the entire point of this change not to have to refresh the page. The contractIDHints argument is there to allow returning early.

Copy link
Member

Choose a reason for hiding this comment

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

But some of these functions only need to be run once, right? No need to call them every single time a contract is added or removed.

Did you see this?

One idea that occurred to me, is that updateFn can pass in a function that will unregister the listener that can be called once a successful upgrade is done.

Copy link
Member Author

@corrideat corrideat Oct 11, 2024

Choose a reason for hiding this comment

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

This seems like a potentially useful utility function. Can we import it from some other location instead of here? (Is there a way to make this available as a Chelonia dev utility)?

Useful for what, other than upgrades? Like, I'm all for moving it, but I don't see it as being particularly useful as a generic function outside it being used here.

Is there a way to make this available as a Chelonia dev utility

One issue with that is that all of the events other than CONTRACTS_MODIFIED are GI events, not Chelonia events.

Copy link
Member

Choose a reason for hiding this comment

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

Useful for what, other than upgrades? Like, I'm all for moving it, but I don't see it as being particularly useful as a generic function outside it being used here.

Useful for anyone writing Chelonia-based apps. This is one of 2 ways we've got for doing upgrades (and the only way that exists currently). Seems like every dev would need it.

One issue with that is that all of the events other than CONTRACTS_MODIFIED are GI events, not Chelonia events.

Honestly, with how complicated logging in and logging out is, I think we will be forced to move that code into Chelonia.

Copy link
Member Author

Choose a reason for hiding this comment

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

with how complicated logging in and logging out is, I think we will be forced to move that code into Chelonia.

I'm not sure that feasible nor a good idea, and it's not really that logging in or out is complicated: it is, in a way, but that's because there are things that need to be done in specific places (the SW and the app), and because of specific things related to saving and loading the state, which can also need encryption and decryption.

In this case, the LOGIN_COMPLETE and LOGIN_ERROR events are also only on the application / browsing-context side (implementing this function in the actions / SW side wouldn't need these events, maybe it'd need LOGIN, but you asked to use the existing selector we have).

Moving logging in and out into Chelonia means that certain types of contracts need to be defined, rather than having a generic system.

In another type of application, you only really need to listen for the CONTRACTS_MODIFIED modified event.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can we leave this without major changes and improve it later? Like I said, I'm happy to put it in a different file (in fact, I think it doesn't belong in this already complicated file). My question is more about the 'moving it to Chelonia' part, as this is feeling like an ever-growing PR.

frontend/model/state.js Outdated Show resolved Hide resolved
frontend/model/state.js Outdated Show resolved Hide resolved
frontend/model/state.js Outdated Show resolved Hide resolved
Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

OK, think I've finished the review, sorry it took so long!

frontend/model/state.js Outdated Show resolved Hide resolved
Comment on lines 39 to 41
if (!contractIDHints.reduce((acc, contractID) => {
return (acc || state.contracts[contractID]?.type === contractType)
}, false)) return
Copy link
Member

@taoeffect taoeffect Oct 13, 2024

Choose a reason for hiding this comment

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

This code seems to me, unnecessarily confusing. Could you please replace this with something simpler, e.g.:

if (!contractIDHints.some(contractID => state.contracts[contractID]?.type === contractType)) {
  return
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure


// Wrapper function for performing contract upgrades and migrations
// TODO: Consider moving this function into a different file
const contractUpdate = (initialState: Object, updateFn: (state: Object, contractIDHints: ?string[]) => any, contractType: ?string) => {
Copy link
Member

Choose a reason for hiding this comment

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

Could you please move contractType to be the 2nd or 1st parameter?

Copy link
Member Author

Choose a reason for hiding this comment

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

It wouldn't work that well because it's meant to be an optional parameter, as you may want to update contracts regardless of type.

Comment on lines +47 to +49
const resetHandler = () => {
sbp('okTurtles.events/off', CONTRACTS_MODIFIED, modifiedHandler)
}
Copy link
Member

Choose a reason for hiding this comment

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

Since this is 1 line and it is only used once with a /once listener, it can be inlined as an anonymous function below.

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought that you preferred named functions over inlined ones (and it's also more consistent in this case)

Copy link
Member

Choose a reason for hiding this comment

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

I do, usually, it especially helps when there's a large or confusing chunk of logic to simplify, or to DRY things. In this case it's simple enough to inline and there's no DRY violation. However, it's not a big to leave as-is too of you prefer.

@@ -101,6 +169,7 @@ sbp('sbp/selectors/register', {
if (!ourIdentityContractId || !state[ourIdentityContractId]?.groups) return
Object.entries(state[ourIdentityContractId].groups).map(([groupID, { hasLeft }]: [string, Object]) => {
if (hasLeft || !state[groupID]?.chatRooms) return undefined
if (Array.isArray(contractIDHints) && !contractIDHints.includes(groupID)) return undefined
Copy link
Member

@taoeffect taoeffect Oct 13, 2024

Choose a reason for hiding this comment

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

Isn't the point of the wrappedUpdateFn to handle this?

If this line is needed, can you add a 1-line comment above it explaining why wrappedUpdateFn is insufficient?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll add a comment. It's not sufficient because wrappedUpdateFn is filtering contracts by type, while this is filtering out contracts (groups) that haven't been recently added.

@@ -90,7 +158,7 @@ sbp('sbp/selectors/register', {
// $FlowFixMe[incompatible-call]
Vue.set(state, 'reverseNamespaceLookups', Object.fromEntries(Object.entries(state.namespaceLookups).map(([k, v]: [string, string]) => [v, k])))
}
(() => {
contractUpdate(state, (state: Object, contractIDHints: ?string[]) => {
Copy link
Member

Choose a reason for hiding this comment

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

I asked Claude if it could make the code from line 170 to 182 more readable, and here's what it suggested:

Object.entries(state[ourIdentityContractId].groups)
  .filter(([groupID, { hasLeft }]) => {
    return !hasLeft && 
           state[groupID]?.chatRooms && 
           (!Array.isArray(contractIDHints) || contractIDHints.includes(groupID))
  })
  .map(([groupID]) => {
    // $FlowFixMe[incompatible-use]
    const chatRooms = state[groupID].chatRooms
    const needsUpgrade = Object.values(chatRooms)
      .flatMap(({ members }) => Object.values(members))
      .some(member => 
        member.status === PROFILE_STATUS.ACTIVE && member.joinedHeight == null
      )

    return needsUpgrade ? groupID : null
  })
  .filter(Boolean)

If this accomplishes the same thing, do you think it's safe to replace it with this?

Copy link
Member Author

@corrideat corrideat Oct 13, 2024

Choose a reason for hiding this comment

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

Maybe, I'd need to see if they do the same thing or not. So far, it seems like it might do the same thing.

frontend/model/state.js Outdated Show resolved Hide resolved
const minutes = Math.ceil(remainder / MIL_MIN)
if (expiryTime == null) return L("Doesn't expire")
const { expired, years, months, days, hours, minutes } = timeLeft(expiryTime)
if (expired) L('Expired')
Copy link
Member

Choose a reason for hiding this comment

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

Missing return here

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed

Gruntfile.js Outdated
@@ -223,7 +224,8 @@ module.exports = (grunt) => {
'process.env.MAX_EVENTS_AFTER': `'${MAX_EVENTS_AFTER}'`,
'process.env.NODE_ENV': `'${NODE_ENV}'`,
'process.env.EXPOSE_SBP': `'${EXPOSE_SBP}'`,
'process.env.ENABLE_UNSAFE_NULL_CRYPTO': `'${ENABLE_UNSAFE_NULL_CRYPTO}'`
'process.env.ENABLE_UNSAFE_NULL_CRYPTO': `'${ENABLE_UNSAFE_NULL_CRYPTO}'`,
'process.env.UNSAFE_TRUST_ALL_MANIFEST_SIGNING_KEYS': UNSAFE_TRUST_ALL_MANIFEST_SIGNING_KEYS
Copy link
Member

Choose a reason for hiding this comment

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

Inconsistent handling of this environment variable, could you please wrap it the same way as the others?

Copy link
Member Author

Choose a reason for hiding this comment

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

but this is a boolean, not a string

Copy link
Member

Choose a reason for hiding this comment

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

So is ENABLE_UNSAFE_NULL_CRYPTO, I don't think it matters

Copy link
Member Author

Choose a reason for hiding this comment

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

It does matter (although I can see the argument for consistency). See:

if (process.env.ENABLE_UNSAFE_NULL_CRYPTO === 'true' && ...

If you make it a string, then it's a string. If you don't quote it, it's whatever the type of that expression is.

Copy link
Member

Choose a reason for hiding this comment

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

I see. Well, can we make it consistent anyway, for consistency's sake?

Copy link
Member

@taoeffect taoeffect left a comment

Choose a reason for hiding this comment

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

Another round of review ready!

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.

3 participants