-
Notifications
You must be signed in to change notification settings - Fork 18
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
Conversation
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'; |
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.
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?
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.
The export must be renamed use*
for sure, since it contains a useEffect
itself.
...droid/src/main/java/com/solanamobile/seedvault/reactnative/SolanaMobileSeedVaultLibModule.kt
Outdated
Show resolved
Hide resolved
try { | ||
val authToken = Wallet.onAuthorizeSeedResult(resultCode, data) | ||
Log.d(TAG, "Seed authorized, AuthToken=$authToken") | ||
sendSeedVaultEvent(SeedVaultEvent.SeedAuthorized(authToken)) |
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.
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
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 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.
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.
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'; |
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.
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)); |
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.
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" |
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.
Must add rules-of-hooks
here, which would have caught that dependency related bug in useSeedVault.ts
.
js/rollup.config.ts
Outdated
}: { | ||
bundleName: string; | ||
format: 'cjs' | 'esm'; | ||
runtime: 'browser' | 'node' | 'react-native'; |
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.
Kill the node
and browser
builds, right?
js/packages/seed-vault/tsconfig.json
Outdated
"include": ["src"], | ||
"compilerOptions": { | ||
"declarationDir": "./lib/types", | ||
"outDir": "lib/esm" |
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 don't think you need CommonJS and ESM builds since this is exclusively for React Native. Just use ESM.
js/rollup.config.ts
Outdated
const { id: resolvedId } = resolved; | ||
const directory = path.dirname(resolvedId); | ||
const moduleFilename = path.basename(resolvedId); | ||
const forkPath = path.join(directory, '__forks__', runtime, moduleFilename); |
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.
None of this is relevant. You won't have forks; this library will only ever be for React Native.
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.
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.
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.
@Michaelsulistio should I refactor this lib in the same way you did the walletlib here?
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.
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.
js/packages/seed-vault/package.json
Outdated
"browser": { | ||
"./lib/cjs/index.js": "./lib/cjs/index.browser.js", | ||
"./lib/esm/index.js": "./lib/esm/index.browser.js" | ||
}, |
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.
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]); |
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.
}, [handleContentChange, handleContentChange]); | |
}, [handleContentChange, handleSeedVaultEvent]); |
handleSeedVaultEvent: (event: SeedVaultEvent) => void, | ||
handleContentChange: (event: SeedVaultContentChange) => void, |
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.
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:
- Store the functions in a
ref
– refs do not have to be part of theuseEffect's
dependencies - Update that ref on every render
- 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.
Keyword: ‘Keep latest props in a ref’
outdated, reviving this pr after months
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.