Skip to content

Persist state and sync between tabs#975

Merged
lukaw3d merged 45 commits intomasterfrom
lw/persist
Nov 24, 2022
Merged

Persist state and sync between tabs#975
lukaw3d merged 45 commits intomasterfrom
lw/persist

Conversation

@lukaw3d
Copy link
Copy Markdown
Contributor

@lukaw3d lukaw3d commented Aug 18, 2022

Part of #792

Temporarily deployed to https://lukaw3d.github.io/oasis-wallet-web/ce527a5/#/
Updated deploy https://lukaw3d.github.io/oasis-wallet-web/32fbe2b/#/

Old preview of 22369e0

profile

Preview of ce527a5

preview-ce527a5

TODO

  • don't store fatal errors
  • syncing multiple tabs and cleanup, e.g. clearAccount
    open account in two tabs http://localhost:3000/account/oasis1qzwl8nn8lt8cv4gxh85skv9rw3h0raudws66q4y5
    navigate to home on one tab (dispatches clearAccount)
    other tab loses data and shows loading
  • skip login:
    • refactor to make networkSaga maintainable:
      always show unlock modal and
      if profile: on unlock emit setUnlockedRootState
      if profile: on skip emit event like setUnlockedRootState to use in networkSaga
      if no profile: auto click skip login emit event like setUnlockedRootState to use in networkSaga
  • walletId global var
  • close wallet = lock
  • change password
  • choose password and create profile when importing
  • test ui
  • test persistence

Deferred and moved to issue:

  • auto lock timeout
  • multiple profiles
  • migrate old storage @metamask/browser-passworder
  • test syncing tabs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Aug 18, 2022

🦙 MegaLinter status: ✅ SUCCESS

Descriptor Linter Files Fixed Errors Elapsed time
✅ EDITORCONFIG editorconfig-checker 52 0 0.09s
✅ HTML htmlhint 1 0 0.26s
✅ JSON eslint-plugin-jsonc 1 0 0 0.93s
✅ JSON jsonlint 1 0 0.23s
✅ JSON prettier 1 0 0 0.8s
✅ JSON v8r 1 0 1.47s
✅ REPOSITORY checkov yes no 14.77s
✅ REPOSITORY git_diff yes no 0.0s
✅ TSX eslint 11 0 0 6.7s
✅ TYPESCRIPT eslint 34 0 0 7.75s

See errors details in artifact MegaLinter reports on CI Job page
Set VALIDATE_ALL_CODEBASE: true in mega-linter.yml to validate all sources, not only the diff

MegaLinter is graciously provided by OX Security

Copy link
Copy Markdown
Contributor

@pro-wh pro-wh left a comment

Choose a reason for hiding this comment

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

reading about how this works

return encryptedString
}

export async function decryptWithPassword<T>(password: string, encryptedString: EncryptedString): Promise<T> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

on the surface level it's an awkward asymmetry with encryptWithKey, but deeper down, it really would make a lot of sense to expose a decryptWithKey instead. the only callsite other than in tests pairs it up with another deriveKeyFromPassword, because it also needs the key itself (foreshadowed by encryptWithKey to save any updates will the key). the password-to-key derivation being something that's supposed to be expensive, it would be nice not to have to do it twice

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't understand :D

A subset of the comment: true, we could avoid deriveKeyFromPassword (with a new random salt) in decryptState (on unlock) by returning derivedKeyWithSalt (with old salt) from decryptWithPassword. Seems safe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ya I guess, if you prefer to couple the derivation and decryption into one call

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security review: we'd be fine using the same salt, as the password is still the same. I'm not aware of any problems with changing it every time either though

@lukaw3d lukaw3d force-pushed the lw/persist branch 2 times, most recently from 2673f2c to a11f453 Compare September 30, 2022 02:06
@lukaw3d lukaw3d mentioned this pull request Oct 6, 2022
@lukaw3d lukaw3d force-pushed the lw/persist branch 3 times, most recently from 8cbc6b0 to ce527a5 Compare October 13, 2022 03:31
@lukaw3d lukaw3d marked this pull request as ready for review October 13, 2022 04:02
@lukaw3d
Copy link
Copy Markdown
Contributor Author

lukaw3d commented Oct 13, 2022

I temporarily deployed it to https://lukaw3d.github.io/oasis-wallet-web/ce527a5/#/

@nikolaglumac
Copy link
Copy Markdown

Great work @lukaw3d 🎉
I will do a proper review later today or tomorrow - one thing that I want to mention now (and perhaps that is something we can address in a separate PR) is that we should think about password rules. Having users set a single-character password is definitely not what we should allow.

@nikolaglumac
Copy link
Copy Markdown

Hi @lukaw3d - the flow works pretty well! I think it is a good first version - we can later iterate on it and improve it over time (e.g. by adding "remove-account" options etc).

There is this minor thing I found:
close-wallet

It should be an easy fix.

Thanks!

@lukaw3d

This comment was marked as resolved.

@lukaw3d
Copy link
Copy Markdown
Contributor Author

lukaw3d commented Oct 13, 2022

@nikolaglumac
There is this minor thing I found: Once you create a profile, regardless of if you opened it later or not, "Close wallet" button is always visible

That was intentional so users can get back to login screen after clicking "Continue without the profile" (without it: user would need to create a wallet to get that same button)

@buberdds

This comment was marked as resolved.

@lukaw3d

This comment was marked as resolved.

@lukaw3d
Copy link
Copy Markdown
Contributor Author

lukaw3d commented Oct 22, 2022

Fixing 1. is annoying

  • I could refactor resetRootState to keep some slices, instead of resetting everything but: it doesn't feel correct to keep state after user clicks Close Wallet; and it probably introduces state combinations that previously weren't reachable and handled.
  • Another option: I could redirect home on resetRootState, but only in webapp takeLatest(resetRootState, () => { if (runtimeIs === 'webapp' && selectUnlockedStatus === 'emptyUnpersisted') location.href = '/' }). Luckily extension with saga in background page won't support multiple tabs / popups yet. And that still needs navigate('/'); dispatch(persistActions.lockAsync()) on click.

@csillag

This comment was marked as resolved.

Comment thread package.json Outdated
Comment thread src/app/components/Sidebar/index.tsx Outdated
Comment thread src/config.ts Outdated
<Button
label={t('persist.loginToProfile.skipUnlocking', 'Continue without the profile')}
onClick={() => {
navigate('/')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this redirect needed? when I open /account/oasis1.. I would expect that continue will allow me to see acc details.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good UX question. I've been considering the skipped profile like incognito mode, not read-only profile.

  • Against:
    • We've removed search for foreign accounts, so read-only view is barely supported.
    • I was considering removing remove account ID from URL. To have fewer sources of state. That would also increase privacy - after locking wallet, someone couldn't check browser history (or click skip) to get your address.
  • For:
    • Would allow wallet owner to quickly get their account's address. Pre-redirect URL might not be locked account (open a foreign account URL from bookmark and click skip). More complex proposal is to have a wallet pin to unlock read-only, and only ask for password before signing transactions.
    • Would allow quickly viewing foreign account's balance from URL/bookmark. But this is just worse search.

Comment thread src/app/index.tsx Outdated
Comment thread src/app/state/persist/syncTabs.ts
lukaw3d added 2 commits November 24, 2022 14:39
Fixes wrong selection when opening multiple wallets with persistence:
- open 2 accounts from mnemonic
- choose password
- walletOpened(account0): ??= selects account0
- selectWallet(undefined) and begin delay(1), selectWallet(account0)
- walletOpened(account1): ??= selects account1
- setPasswordAsync begins encrypting, decrypting, and setUnlockedRootState
- delay(1) ends, selectWallet(account0)
- encrypting, decrypting, and setUnlockedRootState ends with account1

const tab2 = await context.newPage()
await tab2.goto('/')
await expect(tab2.getByRole('button', { name: 'Unlock' })).toBeHidden()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is failing on CI, but not locally

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I haven't found a reason why it matches differently on CI, only fixed it with 77be857.
await expect(tab2.getByRole('button', { name: 'Unlock' })).toBeHidden() on CI matched

<a aria-label="Open wallet">
  <button tabindex="-1">
    <div>
      <svg aria-label="Unlock">..</svg>
      Open wallet
</

@iSQL
Copy link
Copy Markdown

iSQL commented Jan 19, 2023

Hello,

Is there any info when will this be published?

@lukaw3d
Copy link
Copy Markdown
Contributor Author

lukaw3d commented Jan 20, 2023

@iSQL I'm not sure. We'll want to deploy it after we move wallet to a new domain (so there's no confusion about saved data on current domain)

and perhaps finish more of #792 (comment)

lukaw3d pushed a commit that referenced this pull request Mar 21, 2023
Reverts part of c125984 and re-fixes
#975 (comment)

Instead of waiting for tabs to be synced before selecting network initially:
- initialize selecting network without syncing that action
- using takeLatest cancel initialization task if tabs sync during selectNetwork
lukaw3d pushed a commit that referenced this pull request Mar 21, 2023
Reverts part of c125984 and re-fixes
#975 (comment)

Instead of waiting for tabs to be synced before selecting network initially:
- initialize selecting network without syncing that action
- using takeLatest cancel initialization task if tabs sync during selectNetwork
lukaw3d pushed a commit that referenced this pull request Mar 21, 2023
Reverts part of c125984 and re-fixes
#975 (comment)

Instead of waiting for tabs to be synced before selecting network initially:
- initialize selecting network without syncing that action
- using takeLatest cancel initialization task if tabs sync during selectNetwork
lukaw3d pushed a commit that referenced this pull request Mar 21, 2023
Reverts part of c125984 and re-fixes
#975 (comment)

Instead of waiting for tabs to be synced before selecting network initially:
- initialize selecting network without syncing that action
- using takeLatest cancel initialization task if tabs sync during selectNetwork
@lukaw3d
Copy link
Copy Markdown
Contributor Author

lukaw3d commented Apr 21, 2023

@iSQL it is finally published

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.

6 participants