@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
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const onSuccess = ({ hookOptions, options, data, }) => {
|
|
2
|
+
hookOptions?.onSuccess?.(data);
|
|
3
|
+
hookOptions?.onSettled?.(data, null);
|
|
4
|
+
options?.onSuccess?.(data);
|
|
5
|
+
options?.onSettled?.(data, null);
|
|
6
|
+
return data;
|
|
7
|
+
};
|
|
8
|
+
export const onError = ({ hookOptions, options, error, }) => {
|
|
9
|
+
hookOptions?.onError?.(error);
|
|
10
|
+
hookOptions?.onSettled?.(null, error);
|
|
11
|
+
options?.onError?.(error);
|
|
12
|
+
options?.onSettled?.(null, error);
|
|
13
|
+
if (hookOptions?.throwOnError || options?.throwOnError)
|
|
14
|
+
throw error;
|
|
15
|
+
return { error };
|
|
16
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// WebView integration
|
|
2
|
+
export { EmbeddedWalletWebView, WebViewUtils } from './webview';
|
|
3
|
+
// Storage utilities
|
|
4
|
+
export { isSecureStorageMessage, handleSecureStorageMessage, NativeStorageUtils, } from './storage';
|
|
5
|
+
// OAuth flows
|
|
6
|
+
export { openOAuthSession, authenticateWithApple, isAppleSignInAvailable, parseOAuthUrl, createOAuthRedirectUri, OAuthUtils, } from './oauth';
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import * as WebBrowser from 'expo-web-browser';
|
|
3
|
+
import * as Linking from 'expo-linking';
|
|
4
|
+
import * as AppleAuthentication from 'expo-apple-authentication';
|
|
5
|
+
import { OAuthProvider as OAuthProviderType } from '@openfort/openfort-js';
|
|
6
|
+
/**
|
|
7
|
+
* Opens an OAuth authentication session
|
|
8
|
+
*/
|
|
9
|
+
export async function openOAuthSession(config) {
|
|
10
|
+
try {
|
|
11
|
+
const result = await WebBrowser.openAuthSessionAsync(config.url, config.redirectUri, {
|
|
12
|
+
// Additional options can be configured here
|
|
13
|
+
showInRecents: false,
|
|
14
|
+
});
|
|
15
|
+
if (result.type === 'success') {
|
|
16
|
+
return {
|
|
17
|
+
type: 'success',
|
|
18
|
+
url: result.url,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (result.type === 'cancel' || result.type === 'dismiss') {
|
|
22
|
+
return {
|
|
23
|
+
type: 'cancel',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
type: 'error',
|
|
28
|
+
error: 'OAuth session failed',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'error',
|
|
34
|
+
error: error instanceof Error ? error.message : 'Unknown OAuth error',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Handles Apple Sign-In authentication for iOS
|
|
40
|
+
*/
|
|
41
|
+
export async function authenticateWithApple(options) {
|
|
42
|
+
if (Platform.OS !== 'ios') {
|
|
43
|
+
throw new Error('Apple Sign-In is only available on iOS');
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const result = await AppleAuthentication.signInAsync({
|
|
47
|
+
state: options.state,
|
|
48
|
+
requestedScopes: [
|
|
49
|
+
AppleAuthentication.AppleAuthenticationScope.EMAIL,
|
|
50
|
+
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
if (!result.authorizationCode || !result.state) {
|
|
54
|
+
throw new Error('Invalid Apple authentication response');
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
authorizationCode: result.authorizationCode,
|
|
58
|
+
state: result.state,
|
|
59
|
+
identityToken: result.identityToken || undefined,
|
|
60
|
+
email: result.email || undefined,
|
|
61
|
+
fullName: result.fullName || undefined,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof Error && 'code' in error && error.code === 'ERR_REQUEST_CANCELED') {
|
|
66
|
+
const errorCode = options.isLogin
|
|
67
|
+
? 'login_with_oauth_was_cancelled_by_user'
|
|
68
|
+
: 'link_with_oauth_was_cancelled_by_user';
|
|
69
|
+
throw {
|
|
70
|
+
code: errorCode,
|
|
71
|
+
error: 'Apple login was cancelled',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Checks if Apple Sign-In is available on the current device
|
|
79
|
+
*/
|
|
80
|
+
export async function isAppleSignInAvailable() {
|
|
81
|
+
if (Platform.OS !== 'ios') {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
return await AppleAuthentication.isAvailableAsync();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Parses OAuth parameters from a URL
|
|
93
|
+
*/
|
|
94
|
+
export function parseOAuthUrl(url) {
|
|
95
|
+
try {
|
|
96
|
+
const { queryParams } = Linking.parse(url);
|
|
97
|
+
console.log('Parsed OAuth URL:', queryParams);
|
|
98
|
+
return {
|
|
99
|
+
access_token: queryParams?.access_token,
|
|
100
|
+
refresh_token: queryParams?.refresh_token,
|
|
101
|
+
player_id: queryParams?.player_id,
|
|
102
|
+
error: queryParams?.error,
|
|
103
|
+
errorDescription: queryParams?.error_description,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.warn('Failed to parse OAuth URL:', error);
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Creates a redirect URI for OAuth flows
|
|
113
|
+
*/
|
|
114
|
+
export function createOAuthRedirectUri(path = '/') {
|
|
115
|
+
return Linking.createURL(path);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* OAuth provider utilities
|
|
119
|
+
*/
|
|
120
|
+
export const OAuthUtils = {
|
|
121
|
+
/**
|
|
122
|
+
* Checks if a provider should use native authentication
|
|
123
|
+
*/
|
|
124
|
+
shouldUseNativeAuth(provider, isLegacyAppleIosBehaviorEnabled = false) {
|
|
125
|
+
return (Platform.OS === 'ios' &&
|
|
126
|
+
provider === 'apple' &&
|
|
127
|
+
!isLegacyAppleIosBehaviorEnabled);
|
|
128
|
+
},
|
|
129
|
+
/**
|
|
130
|
+
* Gets platform-specific OAuth configuration
|
|
131
|
+
*/
|
|
132
|
+
getPlatformConfig() {
|
|
133
|
+
return {
|
|
134
|
+
supportsNativeApple: Platform.OS === 'ios',
|
|
135
|
+
platform: Platform.OS,
|
|
136
|
+
supportsWebBrowser: true,
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* Validates OAuth provider support
|
|
141
|
+
*/
|
|
142
|
+
isProviderSupported(provider) {
|
|
143
|
+
const supportedProviders = [
|
|
144
|
+
OAuthProviderType.GOOGLE,
|
|
145
|
+
OAuthProviderType.TWITTER,
|
|
146
|
+
OAuthProviderType.DISCORD,
|
|
147
|
+
OAuthProviderType.LINE,
|
|
148
|
+
OAuthProviderType.FACEBOOK,
|
|
149
|
+
OAuthProviderType.EPIC_GAMES,
|
|
150
|
+
OAuthProviderType.APPLE
|
|
151
|
+
];
|
|
152
|
+
return supportedProviders.includes(provider);
|
|
153
|
+
},
|
|
154
|
+
/**
|
|
155
|
+
* Gets the appropriate OAuth URL for a provider
|
|
156
|
+
*/
|
|
157
|
+
getProviderUrl(provider, baseUrl) {
|
|
158
|
+
// Handle Twitter URL compatibility between platforms
|
|
159
|
+
if (provider === 'twitter' && Platform.OS === 'android') {
|
|
160
|
+
return baseUrl.replace('x.com', 'twitter.com');
|
|
161
|
+
}
|
|
162
|
+
return baseUrl;
|
|
163
|
+
},
|
|
164
|
+
/**
|
|
165
|
+
* Handles OAuth session timeout
|
|
166
|
+
*/
|
|
167
|
+
createTimeoutPromise(timeoutMs = 120000) {
|
|
168
|
+
return new Promise((_, reject) => {
|
|
169
|
+
setTimeout(() => {
|
|
170
|
+
reject(new Error('OAuth session timed out'));
|
|
171
|
+
}, timeoutMs);
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
/**
|
|
175
|
+
* Combines OAuth session with timeout
|
|
176
|
+
*/
|
|
177
|
+
async withTimeout(promise, timeoutMs = 120000) {
|
|
178
|
+
return Promise.race([
|
|
179
|
+
promise,
|
|
180
|
+
this.createTimeoutPromise(timeoutMs),
|
|
181
|
+
]);
|
|
182
|
+
},
|
|
183
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import * as SecureStore from 'expo-secure-store';
|
|
3
|
+
import { Platform } from 'react-native';
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a message is a secure storage related message
|
|
6
|
+
*/
|
|
7
|
+
export function isSecureStorageMessage(message) {
|
|
8
|
+
if (typeof message !== 'object' ||
|
|
9
|
+
message === null ||
|
|
10
|
+
!('event' in message) ||
|
|
11
|
+
typeof message.event !== 'string' ||
|
|
12
|
+
!('id' in message) ||
|
|
13
|
+
typeof message.id !== 'string' ||
|
|
14
|
+
!('data' in message) ||
|
|
15
|
+
typeof message.data !== 'object' ||
|
|
16
|
+
message.data === null) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return message.event.startsWith('app:secure-storage:');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Handles secure storage operations from WebView messages
|
|
23
|
+
*/
|
|
24
|
+
export async function handleSecureStorageMessage(message) {
|
|
25
|
+
console.log('Handling secure storage message:', message);
|
|
26
|
+
switch (message.event) {
|
|
27
|
+
case 'app:secure-storage:get': {
|
|
28
|
+
const { key } = message.data;
|
|
29
|
+
try {
|
|
30
|
+
const value = await SecureStore.getItemAsync(normalizeKey(key), {
|
|
31
|
+
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
event: message.event,
|
|
35
|
+
id: message.id,
|
|
36
|
+
data: { value },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.warn('Failed to get the value from secure store', error);
|
|
41
|
+
return {
|
|
42
|
+
event: message.event,
|
|
43
|
+
id: message.id,
|
|
44
|
+
data: { value: null },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
case 'app:secure-storage:set': {
|
|
49
|
+
const { key, value } = message.data;
|
|
50
|
+
try {
|
|
51
|
+
await SecureStore.setItemAsync(normalizeKey(key), value, {
|
|
52
|
+
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
event: message.event,
|
|
56
|
+
id: message.id,
|
|
57
|
+
data: { success: true },
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.warn('Failed to write the value to secure store', error);
|
|
62
|
+
return {
|
|
63
|
+
event: message.event,
|
|
64
|
+
id: message.id,
|
|
65
|
+
data: { success: false },
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
case 'app:secure-storage:remove': {
|
|
70
|
+
const { key } = message.data;
|
|
71
|
+
try {
|
|
72
|
+
await SecureStore.deleteItemAsync(normalizeKey(key), {
|
|
73
|
+
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
event: message.event,
|
|
77
|
+
id: message.id,
|
|
78
|
+
data: { success: true },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.warn('Failed to remove the value from secure store', error);
|
|
83
|
+
return {
|
|
84
|
+
event: message.event,
|
|
85
|
+
id: message.id,
|
|
86
|
+
data: { success: false },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
case 'app:secure-storage:flush': {
|
|
91
|
+
const { origin } = message.data;
|
|
92
|
+
try {
|
|
93
|
+
// Systematically delete all known storage keys for this origin
|
|
94
|
+
// These are the keys used by the iframe signature service
|
|
95
|
+
const storageKeys = [
|
|
96
|
+
'playerID', 'chainId', 'deviceID', 'accountType', 'address',
|
|
97
|
+
'ownerAddress', 'share', 'account', 'chainType', 'signerId'
|
|
98
|
+
];
|
|
99
|
+
const deletePromises = storageKeys.map(async (key) => {
|
|
100
|
+
const fullKey = normalizeKey(`${origin}:${key}`);
|
|
101
|
+
try {
|
|
102
|
+
await SecureStore.deleteItemAsync(fullKey, {
|
|
103
|
+
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
// Ignore errors for keys that don't exist
|
|
108
|
+
console.debug(`Key ${fullKey} not found during flush:`, error);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
await Promise.all(deletePromises);
|
|
112
|
+
console.log('Flushed secure storage for origin:', origin);
|
|
113
|
+
return {
|
|
114
|
+
event: message.event,
|
|
115
|
+
id: message.id,
|
|
116
|
+
data: { success: true },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.warn('Failed to flush secure store', error);
|
|
121
|
+
return {
|
|
122
|
+
event: message.event,
|
|
123
|
+
id: message.id,
|
|
124
|
+
data: { success: false },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
default:
|
|
129
|
+
throw new Error(`Unknown secure storage event: ${message.event}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Normalizes storage keys for compatibility with secure storage
|
|
134
|
+
*/
|
|
135
|
+
function normalizeKey(key) {
|
|
136
|
+
return key.replaceAll(':', '-');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Native storage utilities for platform-specific operations
|
|
140
|
+
*/
|
|
141
|
+
export const NativeStorageUtils = {
|
|
142
|
+
/**
|
|
143
|
+
* Checks if secure storage is available on the current platform
|
|
144
|
+
*/
|
|
145
|
+
isAvailable() {
|
|
146
|
+
return Platform.OS === 'ios' || Platform.OS === 'android';
|
|
147
|
+
},
|
|
148
|
+
/**
|
|
149
|
+
* Gets the platform-specific storage options
|
|
150
|
+
*/
|
|
151
|
+
getStorageOptions() {
|
|
152
|
+
return {
|
|
153
|
+
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
/**
|
|
157
|
+
* Safely checks if a key exists in secure storage
|
|
158
|
+
*/
|
|
159
|
+
async keyExists(key) {
|
|
160
|
+
try {
|
|
161
|
+
const value = await SecureStore.getItemAsync(normalizeKey(key), this.getStorageOptions());
|
|
162
|
+
return value !== null;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Gets all available storage information
|
|
170
|
+
*/
|
|
171
|
+
async getStorageInfo() {
|
|
172
|
+
return {
|
|
173
|
+
isAvailable: this.isAvailable(),
|
|
174
|
+
platform: Platform.OS,
|
|
175
|
+
keychainAccessible: SecureStore.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY,
|
|
176
|
+
};
|
|
177
|
+
},
|
|
178
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import React, { useRef, useCallback, useEffect } from 'react';
|
|
3
|
+
import { AppState, Platform, View } from 'react-native';
|
|
4
|
+
import WebView from 'react-native-webview';
|
|
5
|
+
import { isSecureStorageMessage, handleSecureStorageMessage } from './storage';
|
|
6
|
+
/**
|
|
7
|
+
* WebView component for embedded wallet integration
|
|
8
|
+
* Handles secure communication between React Native and the embedded wallet WebView
|
|
9
|
+
* This component is hidden and only used for wallet communication
|
|
10
|
+
*/
|
|
11
|
+
export const EmbeddedWalletWebView = ({ client, isClientReady, onProxyStatusChange, }) => {
|
|
12
|
+
const webViewRef = useRef(null);
|
|
13
|
+
// Handle app state changes to monitor WebView health
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleAppStateChange = async (nextAppState) => {
|
|
16
|
+
if (nextAppState === 'active') {
|
|
17
|
+
// Check if embedded wallet is still responsive
|
|
18
|
+
try {
|
|
19
|
+
await client.embeddedWallet.ping(500);
|
|
20
|
+
// if (!isResponsive) {
|
|
21
|
+
// onProxyStatusChange?.('reloading');
|
|
22
|
+
// // client.embeddedWallet.reload();
|
|
23
|
+
// }
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
console.warn('Failed to ping embedded wallet:', error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
|
31
|
+
return () => subscription?.remove();
|
|
32
|
+
}, [client, onProxyStatusChange]);
|
|
33
|
+
// Handle WebView load events
|
|
34
|
+
const handleLoad = useCallback(() => {
|
|
35
|
+
onProxyStatusChange?.('loaded');
|
|
36
|
+
}, [onProxyStatusChange]);
|
|
37
|
+
const handleError = useCallback((error) => {
|
|
38
|
+
console.error('WebView error:', error);
|
|
39
|
+
}, []);
|
|
40
|
+
// Set up WebView reference with client immediately when both are available
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (webViewRef.current) {
|
|
43
|
+
// Simple message poster that uses WebView's postMessage directly
|
|
44
|
+
const messagePoster = {
|
|
45
|
+
postMessage: (message) => {
|
|
46
|
+
webViewRef.current?.postMessage(message);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
client.embeddedWallet.setMessagePoster(messagePoster);
|
|
50
|
+
}
|
|
51
|
+
}, [client, isClientReady]);
|
|
52
|
+
// Clean message handler using the new penpal bridge
|
|
53
|
+
const handleMessage = useCallback(async (event) => {
|
|
54
|
+
try {
|
|
55
|
+
const messageData = JSON.parse(event?.nativeEvent?.data);
|
|
56
|
+
if (!messageData)
|
|
57
|
+
return;
|
|
58
|
+
// Handle secure storage messages
|
|
59
|
+
if (isSecureStorageMessage(messageData)) {
|
|
60
|
+
const response = await handleSecureStorageMessage(messageData);
|
|
61
|
+
webViewRef.current?.postMessage(JSON.stringify(response));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Forward all messages to the embedded wallet
|
|
65
|
+
client.embeddedWallet.onMessage(messageData);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('Failed to handle WebView message:', error);
|
|
69
|
+
// Don't crash the app on message handling errors
|
|
70
|
+
}
|
|
71
|
+
}, [client]);
|
|
72
|
+
// Ref callback to set up message poster immediately
|
|
73
|
+
const handleWebViewRef = useCallback((ref) => {
|
|
74
|
+
if (webViewRef.current !== ref) {
|
|
75
|
+
webViewRef.current = ref;
|
|
76
|
+
}
|
|
77
|
+
if (ref) {
|
|
78
|
+
const messagePoster = {
|
|
79
|
+
postMessage: (message) => {
|
|
80
|
+
ref.postMessage(message);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
client.embeddedWallet.setMessagePoster(messagePoster);
|
|
84
|
+
}
|
|
85
|
+
}, [client]);
|
|
86
|
+
return (React.createElement(View, { style: { width: 0, height: 0, overflow: 'hidden' } },
|
|
87
|
+
React.createElement(WebView, { ref: handleWebViewRef, source: {
|
|
88
|
+
uri: client.embeddedWallet.getURL(),
|
|
89
|
+
}, webviewDebuggingEnabled: true, cacheEnabled: false, injectedJavaScriptObject: { shouldUseAppBackedStorage: true }, cacheMode: "LOAD_NO_CACHE", onLoad: handleLoad, onError: handleError, onMessage: handleMessage })));
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Utilities for WebView integration
|
|
93
|
+
*/
|
|
94
|
+
export const WebViewUtils = {
|
|
95
|
+
/**
|
|
96
|
+
* Checks if WebView is supported on the current platform
|
|
97
|
+
*/
|
|
98
|
+
isSupported() {
|
|
99
|
+
return Platform.OS === 'ios' || Platform.OS === 'android';
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* Gets platform-specific WebView configuration
|
|
103
|
+
*/
|
|
104
|
+
getPlatformConfig() {
|
|
105
|
+
if (Platform.OS === 'ios') {
|
|
106
|
+
return {
|
|
107
|
+
allowsInlineMediaPlayback: false,
|
|
108
|
+
allowsLinkPreview: false,
|
|
109
|
+
bounces: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (Platform.OS === 'android') {
|
|
113
|
+
return {
|
|
114
|
+
domStorageEnabled: false,
|
|
115
|
+
javaScriptCanOpenWindowsAutomatically: false,
|
|
116
|
+
mixedContentMode: 'never',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return {};
|
|
120
|
+
},
|
|
121
|
+
/**
|
|
122
|
+
* Creates a secure message for WebView communication
|
|
123
|
+
*/
|
|
124
|
+
createSecureMessage(data) {
|
|
125
|
+
return JSON.stringify({
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
platform: Platform.OS,
|
|
128
|
+
data,
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* Validates a message received from WebView
|
|
133
|
+
*/
|
|
134
|
+
validateMessage(message) {
|
|
135
|
+
try {
|
|
136
|
+
const parsed = JSON.parse(message);
|
|
137
|
+
// Basic validation
|
|
138
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
139
|
+
return { isValid: false, error: 'Invalid message format' };
|
|
140
|
+
}
|
|
141
|
+
return { isValid: true, data: parsed };
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return {
|
|
145
|
+
isValid: false,
|
|
146
|
+
error: error instanceof Error ? error.message : 'Failed to parse message'
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
/**
|
|
151
|
+
* Gets WebView user agent for the current platform
|
|
152
|
+
*/
|
|
153
|
+
getUserAgent() {
|
|
154
|
+
const baseAgent = 'OpenfortEmbeddedWallet/1.0';
|
|
155
|
+
return `${baseAgent} (${Platform.OS}; ${Platform.Version})`;
|
|
156
|
+
},
|
|
157
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Props for the AuthBoundary component
|
|
4
|
+
*/
|
|
5
|
+
export interface AuthBoundaryProps {
|
|
6
|
+
/**
|
|
7
|
+
* Component to render while the SDK is initializing and not ready
|
|
8
|
+
*/
|
|
9
|
+
loading: React.ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* Component to render when the user is not authenticated
|
|
12
|
+
*/
|
|
13
|
+
unauthenticated: React.ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* Optional component to render when there's an error during SDK initialization
|
|
16
|
+
* Can be a component or a function that receives the error and returns a component
|
|
17
|
+
*/
|
|
18
|
+
error?: React.ReactNode | ((error: Error) => React.ReactNode);
|
|
19
|
+
/**
|
|
20
|
+
* Children to render when the user is authenticated and the SDK is ready
|
|
21
|
+
*/
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Authentication boundary component that conditionally renders content based on
|
|
26
|
+
* the user's authentication status and SDK readiness.
|
|
27
|
+
*
|
|
28
|
+
* This component simplifies protecting routes and content based on authentication state.
|
|
29
|
+
* It handles three main states:
|
|
30
|
+
* 1. Loading - SDK is initializing
|
|
31
|
+
* 2. Error - SDK encountered an initialization error
|
|
32
|
+
* 3. Unauthenticated - User is not logged in
|
|
33
|
+
* 4. Authenticated - User is logged in and SDK is ready
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* import { AuthBoundary } from '@openfort/react-native';
|
|
38
|
+
* import { Text, ActivityIndicator } from 'react-native';
|
|
39
|
+
*
|
|
40
|
+
* function App() {
|
|
41
|
+
* return (
|
|
42
|
+
* <AuthBoundary
|
|
43
|
+
* loading={<ActivityIndicator size="large" />}
|
|
44
|
+
* unauthenticated={<LoginScreen />}
|
|
45
|
+
* error={(error) => <Text>Error: {error.message}</Text>}
|
|
46
|
+
* >
|
|
47
|
+
* <AuthenticatedApp />
|
|
48
|
+
* </AuthBoundary>
|
|
49
|
+
* );
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* // With React Navigation
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import { AuthBoundary } from '@openfort/react-native';
|
|
57
|
+
* import { NavigationContainer } from '@react-navigation/native';
|
|
58
|
+
* import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|
59
|
+
*
|
|
60
|
+
* const Stack = createNativeStackNavigator();
|
|
61
|
+
*
|
|
62
|
+
* function App() {
|
|
63
|
+
* return (
|
|
64
|
+
* <NavigationContainer>
|
|
65
|
+
* <AuthBoundary
|
|
66
|
+
* loading={<SplashScreen />}
|
|
67
|
+
* unauthenticated={
|
|
68
|
+
* <Stack.Navigator>
|
|
69
|
+
* <Stack.Screen name="Login" component={LoginScreen} />
|
|
70
|
+
* <Stack.Screen name="Signup" component={SignupScreen} />
|
|
71
|
+
* </Stack.Navigator>
|
|
72
|
+
* }
|
|
73
|
+
* >
|
|
74
|
+
* <Stack.Navigator>
|
|
75
|
+
* <Stack.Screen name="Home" component={HomeScreen} />
|
|
76
|
+
* <Stack.Screen name="Profile" component={ProfileScreen} />
|
|
77
|
+
* </Stack.Navigator>
|
|
78
|
+
* </AuthBoundary>
|
|
79
|
+
* </NavigationContainer>
|
|
80
|
+
* );
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare const AuthBoundary: React.FC<AuthBoundaryProps>;
|
|
85
|
+
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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './config';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Openfort as OpenfortClient, OpenfortSDKConfiguration } from '@openfort/openfort-js';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an instance of the Openfort client configured for Expo/React Native
|
|
4
|
+
*
|
|
5
|
+
* @param options Configuration options for the Openfort client
|
|
6
|
+
* @returns Configured Openfort client instance
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const client = createOpenfortClient({
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* const token = await client.getAccessToken();
|
|
13
|
+
*/
|
|
14
|
+
export declare function createOpenfortClient({ baseConfiguration, overrides, shieldConfiguration, }: OpenfortSDKConfiguration): OpenfortClient;
|
|
15
|
+
/**
|
|
16
|
+
* Gets or creates the default Openfort client instance
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export declare function getDefaultClient(options?: OpenfortSDKConfiguration): OpenfortClient;
|
|
20
|
+
/**
|
|
21
|
+
* Sets the default Openfort client instance
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export declare function setDefaultClient(client: OpenfortClient): void;
|