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

Seed Vault React Native Lib #140

Merged
merged 6 commits into from
Feb 27, 2024
Merged

Seed Vault React Native Lib #140

merged 6 commits into from
Feb 27, 2024

Conversation

Funkatronics
Copy link
Contributor

@Funkatronics Funkatronics commented Jun 13, 2023

Initial react native library for seed vault (#141)

Similar to how we handled the react native wallet lib, lets review this PR @Michaelsulistio, make any small changes here, and create project tasks for larger refactors/features.

@Funkatronics
Copy link
Contributor Author

Note @Michaelsulistio, this PR does not include an example app. I do have a react app that I used to exercise the lib and test it, but its pretty hacked together/scripty. We can iterate on that to create a proper example app.

@@ -0,0 +1,103 @@
import { useEffect } from 'react';

Choose a reason for hiding this comment

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

nitpick: The file name useSeedVault implies there is some custom hook function called useSeedVault. Maybe we can rename the exported function to useSeedVault? Or just rename the file itself?

Choose a reason for hiding this comment

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

The export must be renamed use* for sure, since it contains a useEffect itself.

try {
val authToken = Wallet.onAuthorizeSeedResult(resultCode, data)
Log.d(TAG, "Seed authorized, AuthToken=$authToken")
sendSeedVaultEvent(SeedVaultEvent.SeedAuthorized(authToken))

Choose a reason for hiding this comment

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

Is there anything that prevents this from engendering a race condition – ie. these events being fired before the app starts to listen for them?

Some things to consider:

  • fataling if there are no listeners
  • accumulating events and playing them all into the next listener to attach

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes good call out. Mike and I have been discussing this a lot.

With this current design, there is nothing stopping the consumer from calling a method like authorizeNewSeed before they add a listener. In this case the caller would get nothing back, not even an error. We should at the very least throw some errors if there are no listeners.

I think we can do even better tho. I think exposing an async-await/promise based API would be much more straightforward for js consumers, and hide the listener under the hood.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Given that this library is not yet released on npm, I think we can handle this feedback in a separate PR. I'd like to experiment with wrapping up the native API into an async-await on the js side.

@@ -0,0 +1,103 @@
import { useEffect } from 'react';

Choose a reason for hiding this comment

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

The export must be renamed use* for sure, since it contains a useEffect itself.

useEffect(() => {
const seedVaultEventEmitter = new NativeEventEmitter();
const listener = seedVaultEventEmitter.addListener(SEED_VAULT_EVENT_BRIDGE_NAME, (nativeEvent) => {
// console.log('Received seed vault event: ' + JSON.stringify(nativeEvent, null, 4));

Choose a reason for hiding this comment

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

giphy-1

js/packages/seed-vault/src/useSeedVault.ts Show resolved Hide resolved
js/.eslintrc Outdated
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:require-extensions/recommended"],
"rules": {
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"simple-import-sort/imports": "error"

Choose a reason for hiding this comment

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

Must add rules-of-hooks here, which would have caught that dependency related bug in useSeedVault.ts.

https://www.npmjs.com/package/eslint-plugin-react-hooks

}: {
bundleName: string;
format: 'cjs' | 'esm';
runtime: 'browser' | 'node' | 'react-native';

Choose a reason for hiding this comment

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

Kill the node and browser builds, right?

"include": ["src"],
"compilerOptions": {
"declarationDir": "./lib/types",
"outDir": "lib/esm"

Choose a reason for hiding this comment

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

I don't think you need CommonJS and ESM builds since this is exclusively for React Native. Just use ESM.

const { id: resolvedId } = resolved;
const directory = path.dirname(resolvedId);
const moduleFilename = path.basename(resolvedId);
const forkPath = path.join(directory, '__forks__', runtime, moduleFilename);

Choose a reason for hiding this comment

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

None of this is relevant. You won't have forks; this library will only ever be for React Native.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tbh, I have a very limited understanding of how any of this rollup/ts config stuff works. I copied fiels from the MWA repo until things started building successfully lol. I'll backtrack through and clean all this up, but might do that in a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Michaelsulistio should I refactor this lib in the same way you did the walletlib here?

Copy link

@Michaelsulistio Michaelsulistio Jun 28, 2023

Choose a reason for hiding this comment

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

Yup you can pretty much copy what I did there, although I used commonJS as that was the default in the bob builder config I copied over.

Looking at it now, we should probably refactor walletlib to use ESM.

Comment on lines 14 to 17
"browser": {
"./lib/cjs/index.js": "./lib/cjs/index.browser.js",
"./lib/esm/index.js": "./lib/esm/index.browser.js"
},

Choose a reason for hiding this comment

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

Kill all the browser and node exports. Make this package.json only work with React Native.

@@ -78,7 +76,7 @@ export function subscribeToNativeEvents(
return () => {
listener.remove();
};
}, []);
}, [handleContentChange, handleContentChange]);

Choose a reason for hiding this comment

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

Suggested change
}, [handleContentChange, handleContentChange]);
}, [handleContentChange, handleSeedVaultEvent]);

Comment on lines 56 to 57
handleSeedVaultEvent: (event: SeedVaultEvent) => void,
handleContentChange: (event: SeedVaultContentChange) => void,
Copy link

@steveluscher steveluscher Jul 6, 2023

Choose a reason for hiding this comment

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

So, there's a way of designing this hook so that the useEffect doesn't constantly fire. You can bet your bottom dollar that people are going to do this:

useSeedVault(
  e => // handle seed vault event,
  e => // handle content change,
);

Every single time that code runs, the non-memoized functions will be different which means that the useEffect's dependencies will be busted which means that the remove() will fire and the NativeEventEmitter will be set up all over again – on every render.

So here's the thing. Since these functions will only ever get called between renders (ie. when the native event emitter fires) you can:

  1. Store the functions in a ref – refs do not have to be part of the useEffect's dependencies
  2. Update that ref on every render
  3. Reach into the ref when you need the function to call

That way, the useEffect will only ever run once to set up the listener, and once when the whole thing unmounts.

https://legacy.reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often

Keyword: ‘Keep latest props in a ref’

@Funkatronics Funkatronics dismissed steveluscher’s stale review February 27, 2024 18:05

outdated, reviving this pr after months

@Funkatronics Funkatronics merged commit 068a89a into main Feb 27, 2024
3 checks passed
@Funkatronics Funkatronics deleted the seed-vault-react-native branch February 27, 2024 18:33
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.

None yet

3 participants