@openfort/react-native 0.0.3 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -85
- package/dist/components/AuthBoundary.js +83 -0
- package/dist/components/index.js +10 -0
- package/dist/constants/config.js +9 -0
- package/dist/constants/index.js +1 -0
- package/dist/core/client.js +78 -0
- package/dist/core/context.js +37 -0
- package/dist/core/index.js +10 -0
- package/dist/core/provider.js +224 -0
- package/dist/core/storage.js +141 -0
- package/dist/hooks/auth/index.js +14 -0
- package/dist/hooks/auth/useAuthCallback.js +1 -0
- package/dist/hooks/auth/useCreateWalletPostAuth.js +22 -0
- package/dist/hooks/auth/useEmailAuth.js +176 -0
- package/dist/hooks/auth/useGuestAuth.js +52 -0
- package/dist/hooks/auth/useOAuth.js +292 -0
- package/dist/hooks/auth/useSignOut.js +48 -0
- package/dist/hooks/auth/useWalletAuth.js +133 -0
- package/dist/hooks/core/index.js +9 -0
- package/dist/hooks/core/useOpenfort.js +50 -0
- package/dist/hooks/core/useOpenfortClient.js +29 -0
- package/dist/hooks/core/useUser.js +10 -0
- package/dist/hooks/index.js +15 -0
- package/dist/hooks/wallet/index.js +7 -0
- package/dist/hooks/wallet/useWallets.js +389 -0
- package/dist/index.js +24 -1
- package/dist/lib/hookConsistency.js +16 -0
- package/dist/native/index.js +6 -0
- package/dist/native/oauth.js +183 -0
- package/dist/native/storage.js +178 -0
- package/dist/native/webview.js +157 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/baseFlowState.js +8 -0
- package/dist/types/components/AuthBoundary.d.ts +85 -0
- package/dist/types/components/index.d.ts +10 -0
- package/dist/types/config.js +1 -0
- package/dist/types/constants/config.d.ts +9 -0
- package/dist/types/constants/index.d.ts +1 -0
- package/dist/types/core/client.d.ts +24 -0
- package/dist/types/core/context.d.ts +61 -0
- package/dist/types/core/index.d.ts +8 -0
- package/dist/types/core/provider.d.ts +126 -0
- package/dist/types/core/storage.d.ts +34 -0
- package/dist/types/hex.js +1 -0
- package/dist/types/hookOption.js +1 -0
- package/dist/types/hooks/auth/index.d.ts +10 -0
- package/dist/types/hooks/auth/useAuthCallback.d.ts +0 -0
- package/dist/types/hooks/auth/useCreateWalletPostAuth.d.ts +6 -0
- package/dist/types/hooks/auth/useEmailAuth.d.ts +59 -0
- package/dist/types/hooks/auth/useGuestAuth.d.ts +39 -0
- package/dist/types/hooks/auth/useOAuth.d.ts +62 -0
- package/dist/types/hooks/auth/useSignOut.d.ts +9 -0
- package/dist/types/hooks/auth/useWalletAuth.d.ts +48 -0
- package/dist/types/hooks/core/index.d.ts +8 -0
- package/dist/types/hooks/core/useOpenfort.d.ts +38 -0
- package/dist/types/hooks/core/useOpenfortClient.d.ts +29 -0
- package/dist/types/hooks/core/useUser.d.ts +5 -0
- package/dist/types/hooks/index.d.ts +12 -0
- package/dist/types/hooks/wallet/index.d.ts +6 -0
- package/dist/types/hooks/wallet/useWallets.d.ts +74 -0
- package/dist/types/index.d.ts +18 -1
- package/dist/types/index.js +2 -0
- package/dist/types/lib/hookConsistency.d.ts +14 -0
- package/dist/types/native/index.d.ts +5 -0
- package/dist/types/native/oauth.d.ts +91 -0
- package/dist/types/native/storage.d.ts +50 -0
- package/dist/types/native/webview.d.ts +50 -0
- package/dist/types/oauth.js +8 -0
- package/dist/types/openfortError.js +27 -0
- package/dist/types/predicates.js +101 -0
- package/dist/types/state.js +1 -0
- package/dist/types/types/auth.d.ts +168 -0
- package/dist/types/types/baseFlowState.d.ts +14 -0
- package/dist/types/types/config.d.ts +71 -0
- package/dist/types/types/hex.d.ts +1 -0
- package/dist/types/types/hookOption.d.ts +9 -0
- package/dist/types/types/index.d.ts +38 -0
- package/dist/types/types/oauth.d.ts +74 -0
- package/dist/types/types/openfortError.d.ts +13 -0
- package/dist/types/types/predicates.d.ts +64 -0
- package/dist/types/types/state.d.ts +0 -0
- package/dist/types/types/wallet.d.ts +262 -0
- package/dist/types/wallet.js +1 -0
- package/package.json +33 -19
- package/dist/Iframe.js +0 -84
- package/dist/types/Iframe.d.ts +0 -6
- package/polyfills/index.ts +0 -89
package/README.md
CHANGED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# Openfort React native SDK
|
|
2
|
-
|
|
3
|
-
## Install required packages:
|
|
4
|
-
|
|
5
|
-
Using the package manager of your preference, install the openfort-js react native library, e.g. with yarn: `yarn add @openfort/react-native @openfort/openfort-js`.
|
|
6
|
-
|
|
7
|
-
Since react native requires installing native dependencies directly, you also have to install these required dependencies
|
|
8
|
-
```
|
|
9
|
-
yarn add buffer react-native-crypto react-native-get-random-values react-native-randombytes stream-browserify react-native-mmkv
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Setup your metro config
|
|
13
|
-
|
|
14
|
-
If you do not already have a `metro.config.js`, create one with those required extra node modules:
|
|
15
|
-
```
|
|
16
|
-
// sample metro.config.js
|
|
17
|
-
const { getDefaultConfig } = require('expo/metro-config');
|
|
18
|
-
|
|
19
|
-
module.exports = (() => {
|
|
20
|
-
const config = getDefaultConfig(__dirname);
|
|
21
|
-
|
|
22
|
-
// Add shims for Node.js modules like crypto and stream
|
|
23
|
-
config.resolver.extraNodeModules = {
|
|
24
|
-
crypto: require.resolve('react-native-crypto'),
|
|
25
|
-
stream: require.resolve('stream-browserify'),
|
|
26
|
-
buffer: require.resolve('buffer'),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
return config;
|
|
30
|
-
})();
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
## Import `Openfort` at the top of your app
|
|
34
|
-
The first file loaded should be Openfort-js polyfills:
|
|
35
|
-
```
|
|
36
|
-
import "@openfort/react-native/polyfills";
|
|
37
|
-
```
|
|
38
|
-
This will ensure the correct modules are imported and will ensure `openfort-js` works properly.
|
|
39
|
-
|
|
40
|
-
## Configure `Openfort`
|
|
41
|
-
|
|
42
|
-
Configure openfort, the same way that you would with openfort-js
|
|
43
|
-
```
|
|
44
|
-
import Openfort from "@openfort/openfort-js";
|
|
45
|
-
|
|
46
|
-
const openfort = new Openfort({
|
|
47
|
-
baseConfiguration: {
|
|
48
|
-
publishableKey: "YOUR_OPENFORT_PUBLISHABLE_KEY",
|
|
49
|
-
},
|
|
50
|
-
shieldConfiguration: {
|
|
51
|
-
shieldPublishableKey: "YOUR_SHIELD_PUBLISHABLE_KEY",
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
Check out the documentation [here](https://www.openfort.xyz/docs/guides/getting-started#4-import-openfort-into-your-app)
|
|
56
|
-
|
|
57
|
-
## Render secure WebView
|
|
58
|
-
|
|
59
|
-
Openfort uses a `WebView` (from `react-native-webview`) to operate as a secure environment, managing the private key and executing wallet operations. [Learn more](https://www.openfort.xyz/docs/security#embedded-self-custodial-signer).
|
|
60
|
-
|
|
61
|
-
This WebView needs to always be displayed, it is recommended to put it on top of your app. It is wrapped inside a view with `flex: 0`
|
|
62
|
-
|
|
63
|
-
Simply import it from `@openfort/react-native`
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
// Sample app/_layout.tsx using expo router
|
|
67
|
-
import { Iframe } from '@openfort/react-native';
|
|
68
|
-
|
|
69
|
-
export default function RootLayout() {
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<>
|
|
73
|
-
<Iframe />
|
|
74
|
-
<Slot />
|
|
75
|
-
</>
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
## Notes
|
|
80
|
-
|
|
81
|
-
Because we are using `mmkv` storage, expo-go will not work. To run your app use `expo run:ios` or `expo run:android`.
|
|
82
|
-
|
|
83
|
-
# Sample
|
|
84
|
-
|
|
85
|
-
You can check out the [React native auth sample](https://github.com/openfort-xyz/react-native-auth-sample) to get your app running.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useOpenfort } from '../hooks/core/useOpenfort';
|
|
3
|
+
/**
|
|
4
|
+
* Authentication boundary component that conditionally renders content based on
|
|
5
|
+
* the user's authentication status and SDK readiness.
|
|
6
|
+
*
|
|
7
|
+
* This component simplifies protecting routes and content based on authentication state.
|
|
8
|
+
* It handles three main states:
|
|
9
|
+
* 1. Loading - SDK is initializing
|
|
10
|
+
* 2. Error - SDK encountered an initialization error
|
|
11
|
+
* 3. Unauthenticated - User is not logged in
|
|
12
|
+
* 4. Authenticated - User is logged in and SDK is ready
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* import { AuthBoundary } from '@openfort/react-native';
|
|
17
|
+
* import { Text, ActivityIndicator } from 'react-native';
|
|
18
|
+
*
|
|
19
|
+
* function App() {
|
|
20
|
+
* return (
|
|
21
|
+
* <AuthBoundary
|
|
22
|
+
* loading={<ActivityIndicator size="large" />}
|
|
23
|
+
* unauthenticated={<LoginScreen />}
|
|
24
|
+
* error={(error) => <Text>Error: {error.message}</Text>}
|
|
25
|
+
* >
|
|
26
|
+
* <AuthenticatedApp />
|
|
27
|
+
* </AuthBoundary>
|
|
28
|
+
* );
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // With React Navigation
|
|
34
|
+
* ```tsx
|
|
35
|
+
* import { AuthBoundary } from '@openfort/react-native';
|
|
36
|
+
* import { NavigationContainer } from '@react-navigation/native';
|
|
37
|
+
* import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
38
|
+
*
|
|
39
|
+
* const Stack = createNativeStackNavigator();
|
|
40
|
+
*
|
|
41
|
+
* function App() {
|
|
42
|
+
* return (
|
|
43
|
+
* <NavigationContainer>
|
|
44
|
+
* <AuthBoundary
|
|
45
|
+
* loading={<SplashScreen />}
|
|
46
|
+
* unauthenticated={
|
|
47
|
+
* <Stack.Navigator>
|
|
48
|
+
* <Stack.Screen name="Login" component={LoginScreen} />
|
|
49
|
+
* <Stack.Screen name="Signup" component={SignupScreen} />
|
|
50
|
+
* </Stack.Navigator>
|
|
51
|
+
* }
|
|
52
|
+
* >
|
|
53
|
+
* <Stack.Navigator>
|
|
54
|
+
* <Stack.Screen name="Home" component={HomeScreen} />
|
|
55
|
+
* <Stack.Screen name="Profile" component={ProfileScreen} />
|
|
56
|
+
* </Stack.Navigator>
|
|
57
|
+
* </AuthBoundary>
|
|
58
|
+
* </NavigationContainer>
|
|
59
|
+
* );
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export const AuthBoundary = ({ loading, unauthenticated, error: errorComponent, children, }) => {
|
|
64
|
+
const { user, isReady, error } = useOpenfort();
|
|
65
|
+
// SDK encountered an error during initialization
|
|
66
|
+
if (error && errorComponent) {
|
|
67
|
+
if (typeof errorComponent === 'function') {
|
|
68
|
+
return React.createElement(React.Fragment, null, errorComponent(error));
|
|
69
|
+
}
|
|
70
|
+
return React.createElement(React.Fragment, null, errorComponent);
|
|
71
|
+
}
|
|
72
|
+
// SDK is still initializing
|
|
73
|
+
if (!isReady) {
|
|
74
|
+
return React.createElement(React.Fragment, null, loading);
|
|
75
|
+
}
|
|
76
|
+
// User is not authenticated
|
|
77
|
+
if (!user) {
|
|
78
|
+
return React.createElement(React.Fragment, null, unauthenticated);
|
|
79
|
+
}
|
|
80
|
+
// User is authenticated and SDK is ready
|
|
81
|
+
return React.createElement(React.Fragment, null, children);
|
|
82
|
+
};
|
|
83
|
+
export default AuthBoundary;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Openfort React Native SDK Components
|
|
3
|
+
*
|
|
4
|
+
* This module provides React components and hooks for building user interfaces
|
|
5
|
+
* that integrate with the Openfort platform in React Native applications.
|
|
6
|
+
*
|
|
7
|
+
* The components are organized into the following categories:
|
|
8
|
+
* - Authentication boundaries and guards
|
|
9
|
+
*/
|
|
10
|
+
export { AuthBoundary } from './AuthBoundary';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './config';
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Openfort as OpenfortClient } from '@openfort/openfort-js';
|
|
2
|
+
import { digest } from 'expo-crypto';
|
|
3
|
+
import { applicationId } from 'expo-application';
|
|
4
|
+
import { SecureStorageAdapter, createNormalizedStorage } from './storage';
|
|
5
|
+
/**
|
|
6
|
+
* Creates an instance of the Openfort client configured for Expo/React Native
|
|
7
|
+
*
|
|
8
|
+
* @param options Configuration options for the Openfort client
|
|
9
|
+
* @returns Configured Openfort client instance
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const client = createOpenfortClient({
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* const token = await client.getAccessToken();
|
|
16
|
+
*/
|
|
17
|
+
export function createOpenfortClient({ baseConfiguration, overrides, shieldConfiguration, }) {
|
|
18
|
+
const nativeAppId = getNativeApplicationId();
|
|
19
|
+
console.log('Creating Openfort client with native app ID:', nativeAppId);
|
|
20
|
+
// appId,
|
|
21
|
+
// clientId,
|
|
22
|
+
// supportedChains,
|
|
23
|
+
// storage: createNormalizedStorage(storage),
|
|
24
|
+
// sdkVersion: `expo:${SDK_INFO.version}`,
|
|
25
|
+
// nativeAppIdentifier: nativeAppId,
|
|
26
|
+
// crypto: {
|
|
27
|
+
// digest,
|
|
28
|
+
// },
|
|
29
|
+
// baseUrl,
|
|
30
|
+
// logLevel,
|
|
31
|
+
return new OpenfortClient({
|
|
32
|
+
baseConfiguration,
|
|
33
|
+
overrides: {
|
|
34
|
+
...overrides,
|
|
35
|
+
crypto: {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
digest: digest,
|
|
38
|
+
},
|
|
39
|
+
storage: createNormalizedStorage(SecureStorageAdapter)
|
|
40
|
+
},
|
|
41
|
+
shieldConfiguration
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Gets the native application identifier from Expo
|
|
46
|
+
* Throws an error if the identifier cannot be determined
|
|
47
|
+
*/
|
|
48
|
+
function getNativeApplicationId() {
|
|
49
|
+
if (typeof applicationId !== 'string') {
|
|
50
|
+
throw new Error('Cannot determine native application ID. Please make sure `expo-application` is installed as a dependency and that `ios.bundleId` or `android.package` is set.');
|
|
51
|
+
}
|
|
52
|
+
return applicationId;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Default Openfort client instance - should only be used internally
|
|
56
|
+
* Applications should create their own client instances using createOpenfortClient
|
|
57
|
+
*/
|
|
58
|
+
let defaultClient = null;
|
|
59
|
+
/**
|
|
60
|
+
* Gets or creates the default Openfort client instance
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export function getDefaultClient(options) {
|
|
64
|
+
if (!defaultClient && options) {
|
|
65
|
+
defaultClient = new OpenfortClient(options);
|
|
66
|
+
}
|
|
67
|
+
if (!defaultClient) {
|
|
68
|
+
throw new Error('Openfort client not initialized. Make sure to wrap your app with OpenfortProvider.');
|
|
69
|
+
}
|
|
70
|
+
return defaultClient;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Sets the default Openfort client instance
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export function setDefaultClient(client) {
|
|
77
|
+
defaultClient = client;
|
|
78
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* React context for sharing Openfort SDK state throughout the component tree
|
|
4
|
+
*/
|
|
5
|
+
export const OpenfortContext = React.createContext(null);
|
|
6
|
+
/**
|
|
7
|
+
* Hook to access the Openfort context
|
|
8
|
+
* Throws an error if used outside of a OpenfortProvider
|
|
9
|
+
*/
|
|
10
|
+
export function useOpenfortContext() {
|
|
11
|
+
const context = React.useContext(OpenfortContext);
|
|
12
|
+
if (!context) {
|
|
13
|
+
throw new Error('useOpenfortContext must be used within a OpenfortProvider. ' +
|
|
14
|
+
'Make sure to wrap your app with <OpenfortProvider>.');
|
|
15
|
+
}
|
|
16
|
+
return context;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Hook to safely access the Openfort context
|
|
20
|
+
* Returns null if used outside of a OpenfortProvider
|
|
21
|
+
*/
|
|
22
|
+
export function useOpenfortContextSafe() {
|
|
23
|
+
return React.useContext(OpenfortContext);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Type guard to check if a value is a valid OpenfortContextValue
|
|
27
|
+
*/
|
|
28
|
+
export function isOpenfortContextValue(value) {
|
|
29
|
+
return (typeof value === 'object' &&
|
|
30
|
+
value !== null &&
|
|
31
|
+
'client' in value &&
|
|
32
|
+
'isReady' in value &&
|
|
33
|
+
'logout' in value &&
|
|
34
|
+
'getAccessToken' in value &&
|
|
35
|
+
'embeddedState' in value &&
|
|
36
|
+
'_internal' in value);
|
|
37
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Client creation and configuration
|
|
2
|
+
export { createOpenfortClient, getDefaultClient, setDefaultClient } from './client';
|
|
3
|
+
// Re-export important types and enums from openfort-js
|
|
4
|
+
export { RecoveryMethod } from '@openfort/openfort-js';
|
|
5
|
+
// React context and hooks
|
|
6
|
+
export { OpenfortContext, useOpenfortContext, useOpenfortContextSafe, isOpenfortContextValue } from './context';
|
|
7
|
+
// Main provider component
|
|
8
|
+
export { OpenfortProvider } from './provider';
|
|
9
|
+
// Storage adapters
|
|
10
|
+
export { SecureStorageAdapter, createNormalizedStorage } from './storage';
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
2
|
+
import { OpenfortConfiguration, ShieldConfiguration, EmbeddedState } from '@openfort/openfort-js';
|
|
3
|
+
import { OpenfortContext } from './context';
|
|
4
|
+
import { createOpenfortClient, setDefaultClient } from './client';
|
|
5
|
+
import { EmbeddedWalletWebView, WebViewUtils } from '../native';
|
|
6
|
+
/**
|
|
7
|
+
* Main provider component that wraps the entire application and provides
|
|
8
|
+
* Openfort SDK functionality through React context
|
|
9
|
+
*/
|
|
10
|
+
export const OpenfortProvider = ({ children, publishableKey, customAuth, supportedChains, walletConfig, overrides, }) => {
|
|
11
|
+
// Prevent multiple OpenfortProvider instances
|
|
12
|
+
const existingContext = React.useContext(OpenfortContext);
|
|
13
|
+
if (existingContext) {
|
|
14
|
+
throw new Error('Found multiple instances of OpenfortProvider. Ensure there is only one mounted in your application tree.');
|
|
15
|
+
}
|
|
16
|
+
// Create or use provided client
|
|
17
|
+
const client = useMemo(() => {
|
|
18
|
+
const newClient = createOpenfortClient({
|
|
19
|
+
baseConfiguration: new OpenfortConfiguration({
|
|
20
|
+
publishableKey: publishableKey,
|
|
21
|
+
}),
|
|
22
|
+
shieldConfiguration: walletConfig ? new ShieldConfiguration({
|
|
23
|
+
shieldPublishableKey: walletConfig.shieldPublishableKey,
|
|
24
|
+
shieldEncryptionKey: 'shieldEncryptionKey' in walletConfig ? walletConfig.shieldEncryptionKey : undefined,
|
|
25
|
+
shieldDebug: walletConfig.debug,
|
|
26
|
+
}) : undefined,
|
|
27
|
+
overrides: {
|
|
28
|
+
...overrides,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
setDefaultClient(newClient);
|
|
32
|
+
return newClient;
|
|
33
|
+
}, [publishableKey, walletConfig, overrides]);
|
|
34
|
+
// Embedded state
|
|
35
|
+
const [embeddedState, setEmbeddedState] = useState(EmbeddedState.NONE);
|
|
36
|
+
const pollingRef = useRef(null);
|
|
37
|
+
const pollEmbeddedState = useCallback(async () => {
|
|
38
|
+
if (!client)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
const state = await client.embeddedWallet.getEmbeddedState();
|
|
42
|
+
console.log('Current embedded state:', state);
|
|
43
|
+
setEmbeddedState(state);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('Error checking embedded state with Openfort:', error);
|
|
47
|
+
if (pollingRef.current)
|
|
48
|
+
clearInterval(pollingRef.current);
|
|
49
|
+
}
|
|
50
|
+
}, [client]);
|
|
51
|
+
const startPollingEmbeddedState = useCallback(() => {
|
|
52
|
+
if (pollingRef.current)
|
|
53
|
+
return;
|
|
54
|
+
pollingRef.current = setInterval(pollEmbeddedState, 1000);
|
|
55
|
+
}, [pollEmbeddedState]);
|
|
56
|
+
const stopPollingEmbeddedState = useCallback(() => {
|
|
57
|
+
clearInterval(pollingRef.current || undefined);
|
|
58
|
+
pollingRef.current = null;
|
|
59
|
+
}, []);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!client)
|
|
62
|
+
return;
|
|
63
|
+
startPollingEmbeddedState();
|
|
64
|
+
return () => {
|
|
65
|
+
stopPollingEmbeddedState();
|
|
66
|
+
};
|
|
67
|
+
}, [client]);
|
|
68
|
+
// Core state
|
|
69
|
+
const [user, setUser] = useState(null);
|
|
70
|
+
const [isUserInitialized, setIsUserInitialized] = useState(false);
|
|
71
|
+
const [isClientReady, setIsClientReady] = useState(false);
|
|
72
|
+
const [error, setError] = useState(null);
|
|
73
|
+
// Flow states
|
|
74
|
+
const [passwordState, setPasswordState] = useState({ status: 'initial' });
|
|
75
|
+
const [oAuthState, setOAuthState] = useState({ status: 'initial' });
|
|
76
|
+
const [siweState, setSiweState] = useState({ status: 'initial' });
|
|
77
|
+
const [recoveryFlowState, setRecoveryFlowState] = useState({ status: 'initial' });
|
|
78
|
+
// User state management
|
|
79
|
+
const handleUserChange = useCallback((newUser) => {
|
|
80
|
+
console.log('User state changed:', newUser);
|
|
81
|
+
setUser(newUser);
|
|
82
|
+
if (newUser) {
|
|
83
|
+
setError(null);
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
// Core methods
|
|
87
|
+
const logout = useCallback(async () => {
|
|
88
|
+
handleUserChange(null);
|
|
89
|
+
return client.auth.logout();
|
|
90
|
+
}, [client, handleUserChange]);
|
|
91
|
+
const getAccessToken = useCallback(async () => {
|
|
92
|
+
try {
|
|
93
|
+
return await client.getAccessToken();
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.debug('Failed to get access token:', err);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}, [client]);
|
|
100
|
+
// Initialize client and user
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
console.log('Initializing Openfort client and user state', isUserInitialized);
|
|
103
|
+
if (!isUserInitialized) {
|
|
104
|
+
(async () => {
|
|
105
|
+
console.log('Initializing Openfort client');
|
|
106
|
+
try {
|
|
107
|
+
// Openfort client doesn't need explicit initialization
|
|
108
|
+
setIsClientReady(true);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
console.log('Refreshing user state on initial load');
|
|
115
|
+
await refreshUserState();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// User not logged in, which is fine
|
|
119
|
+
handleUserChange(null);
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
setIsUserInitialized(true);
|
|
123
|
+
}
|
|
124
|
+
})();
|
|
125
|
+
}
|
|
126
|
+
}, [client, isUserInitialized, handleUserChange]);
|
|
127
|
+
// Internal refresh function for auth hooks to use
|
|
128
|
+
const refreshUserState = useCallback(async (user) => {
|
|
129
|
+
try {
|
|
130
|
+
console.log('Refreshing user state', user);
|
|
131
|
+
// If user is provided, use it directly instead of fetching from API
|
|
132
|
+
if (user !== undefined) {
|
|
133
|
+
handleUserChange(user);
|
|
134
|
+
return user;
|
|
135
|
+
}
|
|
136
|
+
// Otherwise, fetch from API
|
|
137
|
+
const currentUser = await client.user.get();
|
|
138
|
+
console.log('Refreshed user state:', currentUser);
|
|
139
|
+
handleUserChange(currentUser);
|
|
140
|
+
return currentUser;
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.log('Failed to refresh user state:', err);
|
|
144
|
+
handleUserChange(null);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}, [client, handleUserChange]);
|
|
148
|
+
// Custom auth state management
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (customAuth?.enabled && isUserInitialized && isClientReady) {
|
|
151
|
+
(async () => {
|
|
152
|
+
try {
|
|
153
|
+
const { getCustomAccessToken, isLoading } = customAuth;
|
|
154
|
+
if (isLoading)
|
|
155
|
+
return;
|
|
156
|
+
const customToken = await getCustomAccessToken();
|
|
157
|
+
if (customToken) {
|
|
158
|
+
// Custom auth sync implementation would go here
|
|
159
|
+
// This would typically handle SIWE authentication with the custom token
|
|
160
|
+
console.debug('Custom token available for authentication sync');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
console.error('Custom auth sync failed:', err);
|
|
165
|
+
}
|
|
166
|
+
})();
|
|
167
|
+
}
|
|
168
|
+
}, [client, customAuth, isUserInitialized, isClientReady]);
|
|
169
|
+
// Determine if SDK is ready
|
|
170
|
+
const isReady = useMemo(() => {
|
|
171
|
+
const customAuthReady = !customAuth?.enabled || !customAuth.isLoading;
|
|
172
|
+
return isUserInitialized && isClientReady && customAuthReady;
|
|
173
|
+
}, [isUserInitialized, isClientReady, customAuth?.enabled, customAuth?.isLoading]);
|
|
174
|
+
// Context value
|
|
175
|
+
const contextValue = useMemo(() => ({
|
|
176
|
+
client,
|
|
177
|
+
user,
|
|
178
|
+
isReady,
|
|
179
|
+
error,
|
|
180
|
+
supportedChains,
|
|
181
|
+
embeddedWallet: walletConfig,
|
|
182
|
+
embeddedState,
|
|
183
|
+
// Flow states
|
|
184
|
+
passwordState,
|
|
185
|
+
oAuthState,
|
|
186
|
+
siweState,
|
|
187
|
+
recoveryFlowState,
|
|
188
|
+
// State setters
|
|
189
|
+
setPasswordState,
|
|
190
|
+
setOAuthState,
|
|
191
|
+
setSiweState,
|
|
192
|
+
setRecoveryFlowState,
|
|
193
|
+
// Core methods
|
|
194
|
+
logout,
|
|
195
|
+
getAccessToken,
|
|
196
|
+
// Internal methods
|
|
197
|
+
_internal: {
|
|
198
|
+
refreshUserState,
|
|
199
|
+
},
|
|
200
|
+
}), [
|
|
201
|
+
client,
|
|
202
|
+
user,
|
|
203
|
+
isReady,
|
|
204
|
+
error,
|
|
205
|
+
supportedChains,
|
|
206
|
+
walletConfig,
|
|
207
|
+
embeddedState,
|
|
208
|
+
passwordState,
|
|
209
|
+
oAuthState,
|
|
210
|
+
siweState,
|
|
211
|
+
recoveryFlowState,
|
|
212
|
+
logout,
|
|
213
|
+
getAccessToken,
|
|
214
|
+
refreshUserState,
|
|
215
|
+
]);
|
|
216
|
+
return (React.createElement(OpenfortContext.Provider, { value: contextValue },
|
|
217
|
+
children,
|
|
218
|
+
client && isReady && WebViewUtils.isSupported() && (React.createElement(EmbeddedWalletWebView, { client: client, isClientReady: isReady, onProxyStatusChange: (status) => {
|
|
219
|
+
// Handle WebView status changes for debugging
|
|
220
|
+
if (process.env.NODE_ENV === 'development') {
|
|
221
|
+
console.debug('WebView status changed:', status);
|
|
222
|
+
}
|
|
223
|
+
} }))));
|
|
224
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import * as SecureStore from 'expo-secure-store';
|
|
3
|
+
import { NativeStorageUtils } from '../native';
|
|
4
|
+
// Define the StorageKeys enum values that match the Openfort SDK
|
|
5
|
+
var StorageKeys;
|
|
6
|
+
(function (StorageKeys) {
|
|
7
|
+
StorageKeys["AUTHENTICATION"] = "openfort.authentication";
|
|
8
|
+
StorageKeys["SIGNER"] = "openfort.signer";
|
|
9
|
+
StorageKeys["CONFIGURATION"] = "openfort.configuration";
|
|
10
|
+
StorageKeys["ACCOUNT"] = "openfort.account";
|
|
11
|
+
StorageKeys["TEST"] = "openfort.test";
|
|
12
|
+
StorageKeys["RECOVERY"] = "openfort.recovery";
|
|
13
|
+
StorageKeys["SESSION"] = "openfort.session";
|
|
14
|
+
StorageKeys["PKCE_STATE"] = "openfort.pkce_state";
|
|
15
|
+
StorageKeys["PKCE_VERIFIER"] = "openfort.pkce_verifier";
|
|
16
|
+
})(StorageKeys || (StorageKeys = {}));
|
|
17
|
+
/**
|
|
18
|
+
* Storage adapter using `expo-secure-store` intended for
|
|
19
|
+
* use with `Openfort` class from `@openfort/openfort-js`.
|
|
20
|
+
*/
|
|
21
|
+
export const SecureStorageAdapter = {
|
|
22
|
+
async get(key) {
|
|
23
|
+
try {
|
|
24
|
+
const normalizedKey = normalizeKey(key);
|
|
25
|
+
const result = await SecureStore.getItemAsync(normalizedKey, NativeStorageUtils.getStorageOptions());
|
|
26
|
+
// If result is a string (as expected), return it
|
|
27
|
+
if (typeof result === 'string' || result === null) {
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
// Handle unexpected Promise-like objects (shouldn't happen according to docs)
|
|
31
|
+
if (result && typeof result === 'object' && '_j' in result) {
|
|
32
|
+
console.warn('WARNING: SecureStore returned a Promise-like object instead of a string');
|
|
33
|
+
const actualValue = result._j;
|
|
34
|
+
return typeof actualValue === 'string' ? actualValue : null;
|
|
35
|
+
}
|
|
36
|
+
// If we get here, something is wrong
|
|
37
|
+
console.error('Unexpected result type from SecureStore:', result);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.warn('Failed to get item from secure store:', error);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
async save(key, value) {
|
|
46
|
+
try {
|
|
47
|
+
const normalizedKey = normalizeKey(key);
|
|
48
|
+
await SecureStore.setItemAsync(normalizedKey, value, NativeStorageUtils.getStorageOptions());
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.warn('Failed to set item in secure store:', error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
async remove(key) {
|
|
56
|
+
try {
|
|
57
|
+
const normalizedKey = normalizeKey(key);
|
|
58
|
+
await SecureStore.deleteItemAsync(normalizedKey, NativeStorageUtils.getStorageOptions());
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.warn('Failed to delete item from secure store:', error);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
flush() {
|
|
66
|
+
// SecureStore doesn't provide a way to list all keys
|
|
67
|
+
// This is a no-op for secure store
|
|
68
|
+
},
|
|
69
|
+
// Additional utility methods using native storage utilities
|
|
70
|
+
async keyExists(key) {
|
|
71
|
+
const normalizedKey = normalizeKey(key);
|
|
72
|
+
return await NativeStorageUtils.keyExists(normalizedKey);
|
|
73
|
+
},
|
|
74
|
+
async getStorageInfo() {
|
|
75
|
+
return await NativeStorageUtils.getStorageInfo();
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Normalizes storage keys by replacing colons with hyphens
|
|
80
|
+
* to ensure compatibility with expo-secure-store
|
|
81
|
+
*/
|
|
82
|
+
function normalizeKey(key) {
|
|
83
|
+
return key.replaceAll(':', '-');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Creates a type-safe storage adapter that bridges between the Openfort SDK's
|
|
87
|
+
* expected Storage interface and our React Native implementation
|
|
88
|
+
*/
|
|
89
|
+
export function createNormalizedStorage(customStorage) {
|
|
90
|
+
const baseStorage = customStorage || SecureStorageAdapter;
|
|
91
|
+
return {
|
|
92
|
+
async get(key) {
|
|
93
|
+
// Convert the unknown key to our StorageKeys enum
|
|
94
|
+
const storageKey = keyToStorageKeys(key);
|
|
95
|
+
const result = await baseStorage.get(storageKey);
|
|
96
|
+
return result;
|
|
97
|
+
},
|
|
98
|
+
save(key, value) {
|
|
99
|
+
console.log('storage save:', key, value);
|
|
100
|
+
const storageKey = keyToStorageKeys(key);
|
|
101
|
+
// Fire and forget - don't await as the SDK expects synchronous behavior
|
|
102
|
+
baseStorage.save(storageKey, value).catch(error => {
|
|
103
|
+
console.error('Failed to save to storage:', error);
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
remove(key) {
|
|
107
|
+
console.log('storage remove:', key);
|
|
108
|
+
const storageKey = keyToStorageKeys(key);
|
|
109
|
+
// Fire and forget - don't await as the SDK expects synchronous behavior
|
|
110
|
+
baseStorage.remove(storageKey).catch(error => {
|
|
111
|
+
console.error('Failed to remove from storage:', error);
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
flush() {
|
|
115
|
+
console.log('storage flush');
|
|
116
|
+
baseStorage.flush();
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Converts an unknown key (likely from the Openfort SDK) to our StorageKeys enum
|
|
122
|
+
*/
|
|
123
|
+
function keyToStorageKeys(key) {
|
|
124
|
+
if (typeof key === 'string') {
|
|
125
|
+
// Check if the string matches one of our enum values
|
|
126
|
+
const storageKey = Object.values(StorageKeys).find(value => value === key);
|
|
127
|
+
if (storageKey) {
|
|
128
|
+
return storageKey;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// If it's an enum-like object, try to get its value
|
|
132
|
+
if (typeof key === 'object' && key !== null && 'toString' in key) {
|
|
133
|
+
const keyString = key.toString();
|
|
134
|
+
const storageKey = Object.values(StorageKeys).find(value => value === keyString);
|
|
135
|
+
if (storageKey) {
|
|
136
|
+
return storageKey;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Fallback: throw an error for unknown keys
|
|
140
|
+
throw new Error(`Unknown storage key: ${key}. Expected one of: ${Object.values(StorageKeys).join(', ')}`);
|
|
141
|
+
}
|