@onairos/react-native 2.0.8 → 2.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.
@@ -1,8 +1,10 @@
1
1
  import React, { useState } from 'react';
2
- import { TouchableOpacity, Text, StyleSheet, View, ViewStyle, TextStyle } from 'react-native';
2
+ import { TouchableOpacity, Text, StyleSheet, View, ViewStyle, TextStyle, Image } from 'react-native';
3
3
  import { UniversalOnboarding } from './UniversalOnboarding';
4
+ import { Overlay } from './Overlay';
4
5
  import { COLORS } from '../constants';
5
6
  import type { OnairosButtonProps } from '../types';
7
+ import { hasCredentials, getCredentials } from '../utils/secureStorage';
6
8
 
7
9
  export const OnairosButton: React.FC<OnairosButtonProps> = ({
8
10
  returnLink,
@@ -10,9 +12,9 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
10
12
  AppName,
11
13
  buttonType = 'normal',
12
14
  requestData,
13
- buttonWidth = 180,
14
- buttonHeight,
15
- hasStroke = false,
15
+ buttonWidth = 240,
16
+ buttonHeight = 48,
17
+ hasStroke = true,
16
18
  enabled = true,
17
19
  buttonForm = 'default',
18
20
  onRejection,
@@ -25,7 +27,11 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
25
27
  testMode = false,
26
28
  }) => {
27
29
  const [showOnboarding, setShowOnboarding] = useState(false);
30
+ const [showOverlay, setShowOverlay] = useState(false);
31
+ const [storedCredentials, setStoredCredentials] = useState<any>(null);
28
32
 
33
+ const isDarkMode = color === 'black' || (!color && !hasStroke);
34
+
29
35
  const handlePress = async () => {
30
36
  if (!enabled) return;
31
37
 
@@ -37,28 +43,48 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
37
43
  }
38
44
  }
39
45
 
40
- setShowOnboarding(true);
46
+ // Check if credentials exist
47
+ const hasStoredCreds = await hasCredentials();
48
+
49
+ if (hasStoredCreds) {
50
+ // If credentials exist, fetch them and display overlay
51
+ const credentials = await getCredentials();
52
+ setStoredCredentials(credentials);
53
+ setShowOverlay(true);
54
+ } else {
55
+ // If no credentials, show onboarding
56
+ setShowOnboarding(true);
57
+ }
41
58
  };
42
59
 
43
60
  const handleOnboardingComplete = (apiUrl: string, token: string, data: any) => {
44
61
  setShowOnboarding(false);
45
62
  onResolved?.(apiUrl, token, data);
46
63
  };
64
+
65
+ const handleOverlayResolved = (apiUrl: string, token: string, data: any) => {
66
+ setShowOverlay(false);
67
+ onResolved?.(apiUrl, token, data);
68
+ };
47
69
 
70
+ // Calculate button styles based on props
48
71
  const buttonStyle: ViewStyle[] = [
49
72
  styles.button,
50
73
  buttonType === 'pill' && styles.pillButton,
51
74
  hasStroke && styles.strokedButton,
52
- { width: buttonWidth },
53
- buttonHeight ? { height: buttonHeight } : null,
75
+ {
76
+ width: buttonWidth,
77
+ height: buttonHeight
78
+ },
54
79
  color ? { backgroundColor: color } : null,
80
+ isDarkMode ? styles.darkButton : styles.lightButton,
55
81
  swerv && styles.swervButton,
56
82
  ].filter(Boolean) as ViewStyle[];
57
83
 
84
+ // Calculate text styles based on props
58
85
  const textStyle: TextStyle[] = [
59
86
  styles.buttonText,
60
- hasStroke && styles.strokedButtonText,
61
- color ? styles.customColorText : null,
87
+ isDarkMode ? styles.lightText : styles.darkText,
62
88
  ].filter(Boolean) as TextStyle[];
63
89
 
64
90
  return (
@@ -67,36 +93,49 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
67
93
  style={buttonStyle}
68
94
  onPress={handlePress}
69
95
  disabled={!enabled}
96
+ accessibilityLabel={`Sign in with Onairos`}
70
97
  >
71
- <Text style={textStyle}>{AppName}</Text>
98
+ {/* Optional Onairos logo could be added here */}
99
+ <Text style={textStyle}>Sign in with Onairos</Text>
72
100
  </TouchableOpacity>
73
101
 
74
- <UniversalOnboarding
75
- visible={showOnboarding}
76
- onClose={() => setShowOnboarding(false)}
77
- AppName={AppName}
78
- requestData={requestData}
79
- returnLink={returnLink}
80
- onComplete={handleOnboardingComplete}
81
- preferredPlatform={preferredPlatform}
82
- debug={debug}
83
- test={testMode}
84
- />
102
+ {showOnboarding && (
103
+ <UniversalOnboarding
104
+ visible={showOnboarding}
105
+ onClose={() => setShowOnboarding(false)}
106
+ AppName={AppName}
107
+ requestData={requestData}
108
+ returnLink={returnLink}
109
+ onComplete={handleOnboardingComplete}
110
+ preferredPlatform={preferredPlatform}
111
+ debug={debug}
112
+ test={testMode}
113
+ />
114
+ )}
115
+
116
+ {showOverlay && storedCredentials && (
117
+ <Overlay
118
+ data={requestData || {}}
119
+ username={storedCredentials.username}
120
+ modelKey={storedCredentials.userPin || ''}
121
+ onResolved={handleOverlayResolved}
122
+ />
123
+ )}
85
124
  </View>
86
125
  );
87
126
  };
88
127
 
89
128
  const styles = StyleSheet.create({
90
129
  button: {
91
- backgroundColor: '#000',
92
- paddingVertical: 12,
93
- paddingHorizontal: 24,
94
- borderRadius: 8,
130
+ flexDirection: 'row',
95
131
  alignItems: 'center',
96
132
  justifyContent: 'center',
133
+ paddingVertical: 12,
134
+ paddingHorizontal: 16,
135
+ borderRadius: 4,
97
136
  },
98
137
  pillButton: {
99
- borderRadius: 20,
138
+ borderRadius: 24,
100
139
  },
101
140
  strokedButton: {
102
141
  backgroundColor: 'transparent',
@@ -106,15 +145,23 @@ const styles = StyleSheet.create({
106
145
  swervButton: {
107
146
  transform: [{ rotate: '-2deg' }],
108
147
  },
148
+ darkButton: {
149
+ backgroundColor: '#000',
150
+ borderColor: '#000',
151
+ },
152
+ lightButton: {
153
+ backgroundColor: '#fff',
154
+ borderColor: '#000',
155
+ },
109
156
  buttonText: {
110
- color: '#fff',
111
157
  fontSize: 16,
112
158
  fontWeight: '600',
159
+ textAlign: 'center',
113
160
  },
114
- strokedButtonText: {
115
- color: '#000',
116
- },
117
- customColorText: {
161
+ lightText: {
118
162
  color: '#fff',
119
163
  },
164
+ darkText: {
165
+ color: '#000',
166
+ },
120
167
  });
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect } from 'react';
1
+ import React, { useState, useEffect, useRef } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
@@ -7,9 +7,10 @@ import {
7
7
  ScrollView,
8
8
  Alert,
9
9
  Platform,
10
+ Dimensions,
10
11
  } from 'react-native';
11
12
  import { BottomSheetModal, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
12
- import DeviceInfo from 'react-native-device-info';
13
+ // import DeviceInfo from 'react-native-device-info'; // Comment out device info import
13
14
  import { COLORS } from '../constants';
14
15
  import { onairosApi } from '../api';
15
16
  import { encryptModelKey, getServerPublicKey } from '../utils/encryption';
@@ -35,7 +36,8 @@ export const Overlay: React.FC<OverlayProps> = ({
35
36
  }) => {
36
37
  const [selections, setSelections] = useState<{ [key: string]: boolean }>({});
37
38
  const [details, setDetails] = useState<string>('');
38
- const bottomSheetRef = React.useRef<BottomSheetModal>(null);
39
+ const bottomSheetRef = useRef<BottomSheetModal>(null);
40
+ const snapPoints = ['60%']; // Set to 60% of screen height
39
41
 
40
42
  useEffect(() => {
41
43
  // Initialize selection state
@@ -45,6 +47,11 @@ export const Overlay: React.FC<OverlayProps> = ({
45
47
  });
46
48
  setSelections(initialSelections);
47
49
  getDetails();
50
+
51
+ // Present the bottom sheet when component mounts
52
+ setTimeout(() => {
53
+ bottomSheetRef.current?.present();
54
+ }, 100);
48
55
  }, []);
49
56
 
50
57
  const getDetails = async () => {
@@ -66,17 +73,12 @@ export const Overlay: React.FC<OverlayProps> = ({
66
73
 
67
74
  const confirmSelection = async () => {
68
75
  try {
69
- let appId = 'unknown';
70
- try {
71
- // Get bundle ID/package name using react-native-device-info
72
- appId = Platform.select({
73
- ios: await DeviceInfo.getBundleId(),
74
- android: await DeviceInfo.getPackageName(),
75
- default: 'unknown'
76
- });
77
- } catch (e) {
78
- console.warn('Failed to get app identifier:', e);
79
- }
76
+ // Mock app identifier
77
+ const appId = Platform.select({
78
+ ios: 'com.onairos.mock',
79
+ android: 'com.onairos.mock',
80
+ default: 'unknown'
81
+ });
80
82
 
81
83
  const serverPublicKey = await getServerPublicKey();
82
84
  const encryptedModelKey = encryptModelKey(serverPublicKey, modelKey);
@@ -98,7 +100,7 @@ export const Overlay: React.FC<OverlayProps> = ({
98
100
  closeOverlay();
99
101
  }
100
102
  } catch (e) {
101
- console.error('Error in confirmSelection:', e);
103
+ console.error('Error confirming selection:', e);
102
104
  showErrorModal('Failed to confirm selection. Please try again.');
103
105
  }
104
106
  };
@@ -112,14 +114,19 @@ export const Overlay: React.FC<OverlayProps> = ({
112
114
  return (
113
115
  <BottomSheetModal
114
116
  ref={bottomSheetRef}
115
- snapPoints={['80%']}
117
+ snapPoints={snapPoints}
118
+ handleIndicatorStyle={styles.handleIndicator}
116
119
  backdropComponent={(props) => (
117
120
  <BottomSheetBackdrop
118
121
  {...props}
119
122
  appearsOnIndex={0}
120
123
  disappearsOnIndex={-1}
124
+ opacity={0.7}
121
125
  />
122
126
  )}
127
+ enablePanDownToClose={true}
128
+ keyboardBehavior="interactive"
129
+ keyboardBlurBehavior="restore"
123
130
  >
124
131
  <View style={styles.container}>
125
132
  <View style={styles.header}>
@@ -184,12 +191,18 @@ export const Overlay: React.FC<OverlayProps> = ({
184
191
  );
185
192
  };
186
193
 
194
+ const { width: SCREEN_WIDTH } = Dimensions.get('window');
195
+
187
196
  const styles = StyleSheet.create({
188
197
  container: {
189
198
  flex: 1,
190
199
  backgroundColor: COLORS.white,
191
- borderTopLeftRadius: 10,
192
- borderTopRightRadius: 10,
200
+ width: SCREEN_WIDTH,
201
+ },
202
+ handleIndicator: {
203
+ backgroundColor: '#999',
204
+ width: 40,
205
+ height: 5,
193
206
  },
194
207
  header: {
195
208
  flexDirection: 'row',
@@ -212,7 +225,7 @@ const styles = StyleSheet.create({
212
225
  },
213
226
  username: {
214
227
  fontSize: 18,
215
- color: COLORS.primary,
228
+ color: COLORS.white,
216
229
  },
217
230
  content: {
218
231
  flex: 1,
@@ -1,4 +1,3 @@
1
- import * as Keychain from 'react-native-keychain';
2
1
  import { Platform } from 'react-native';
3
2
  import { sha256 } from './crypto';
4
3
 
@@ -24,56 +23,19 @@ export interface StorageOptions {
24
23
  };
25
24
  }
26
25
 
27
- const CREDENTIALS_KEY = 'onairos_credentials';
26
+ // Temporary in-memory storage
27
+ let mockStorage: { [key: string]: string } = {};
28
28
 
29
29
  /**
30
- * Store credentials securely in the device keychain with optional biometric protection
30
+ * Store credentials in memory (temporary solution)
31
31
  */
32
32
  export const storeCredentials = async (
33
33
  credentials: OnairosCredentials,
34
34
  options: StorageOptions = {}
35
35
  ): Promise<boolean> => {
36
36
  try {
37
- const { useBiometrics = true, biometricPrompt } = options;
38
-
39
- // Create a JSON string of the credentials
40
- const credentialsString = JSON.stringify(credentials);
41
-
42
- // Configure security options based on platform
43
- const securityOptions: Keychain.Options = {
44
- service: CREDENTIALS_KEY,
45
- accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
46
- };
47
-
48
- // Add biometric protection if requested
49
- if (useBiometrics) {
50
- // iOS specific options
51
- if (Platform.OS === 'ios') {
52
- securityOptions.accessControl = Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE;
53
- }
54
-
55
- // Android specific options
56
- if (Platform.OS === 'android') {
57
- securityOptions.accessControl = Keychain.ACCESS_CONTROL.BIOMETRY_ANY;
58
- if (biometricPrompt) {
59
- securityOptions.authenticationType = Keychain.AUTHENTICATION_TYPE.BIOMETRIC;
60
- securityOptions.authenticationPrompt = {
61
- title: biometricPrompt.title || 'Biometric Authentication',
62
- subtitle: biometricPrompt.subtitle,
63
- description: 'Please authenticate to access your Onairos credentials',
64
- cancel: 'Cancel',
65
- };
66
- }
67
- }
68
- }
69
-
70
- // Store in the secure keychain
71
- await Keychain.setGenericPassword(
72
- credentials.username,
73
- credentialsString,
74
- securityOptions
75
- );
76
-
37
+ console.log('[Mock] Storing credentials:', credentials.username);
38
+ mockStorage[credentials.username] = JSON.stringify(credentials);
77
39
  return true;
78
40
  } catch (error) {
79
41
  console.error('Error storing credentials:', error);
@@ -82,39 +44,18 @@ export const storeCredentials = async (
82
44
  };
83
45
 
84
46
  /**
85
- * Retrieve credentials from secure storage, potentially requiring biometric authentication
47
+ * Retrieve credentials from memory (temporary solution)
86
48
  */
87
49
  export const getCredentials = async (
88
50
  options: StorageOptions = {}
89
51
  ): Promise<OnairosCredentials | null> => {
90
52
  try {
91
- const { useBiometrics = true, biometricPrompt } = options;
92
-
93
- // Configure security options
94
- const securityOptions: Keychain.Options = {
95
- service: CREDENTIALS_KEY,
96
- };
97
-
98
- // Add biometric prompt if required
99
- if (useBiometrics && biometricPrompt) {
100
- securityOptions.authenticationPrompt = {
101
- title: biometricPrompt.title || 'Biometric Authentication',
102
- subtitle: biometricPrompt.subtitle,
103
- description: 'Please authenticate to access your Onairos credentials',
104
- cancel: 'Cancel',
105
- };
106
- }
107
-
108
- // Retrieve from keychain
109
- const result = await Keychain.getGenericPassword(securityOptions);
110
-
111
- if (!result) {
53
+ // Get the first stored credential (temporary solution)
54
+ const storedCredential = Object.values(mockStorage)[0];
55
+ if (!storedCredential) {
112
56
  return null;
113
57
  }
114
-
115
- // Parse the stored JSON
116
- const credentials: OnairosCredentials = JSON.parse(result.password);
117
- return credentials;
58
+ return JSON.parse(storedCredential);
118
59
  } catch (error) {
119
60
  console.error('Error retrieving credentials:', error);
120
61
  return null;
@@ -126,11 +67,7 @@ export const getCredentials = async (
126
67
  */
127
68
  export const hasCredentials = async (): Promise<boolean> => {
128
69
  try {
129
- const result = await Keychain.getGenericPassword({
130
- service: CREDENTIALS_KEY,
131
- });
132
-
133
- return !!result;
70
+ return Object.keys(mockStorage).length > 0;
134
71
  } catch (error) {
135
72
  console.error('Error checking for credentials:', error);
136
73
  return false;
@@ -142,7 +79,7 @@ export const hasCredentials = async (): Promise<boolean> => {
142
79
  */
143
80
  export const deleteCredentials = async (): Promise<boolean> => {
144
81
  try {
145
- await Keychain.resetGenericPassword({ service: CREDENTIALS_KEY });
82
+ mockStorage = {};
146
83
  return true;
147
84
  } catch (error) {
148
85
  console.error('Error deleting credentials:', error);
@@ -158,20 +95,14 @@ export const updateCredentials = async (
158
95
  options: StorageOptions = {}
159
96
  ): Promise<boolean> => {
160
97
  try {
161
- // Get current credentials
162
98
  const currentCredentials = await getCredentials(options);
163
-
164
99
  if (!currentCredentials) {
165
100
  return false;
166
101
  }
167
-
168
- // Merge updates with current credentials
169
102
  const updatedCredentials: OnairosCredentials = {
170
103
  ...currentCredentials,
171
104
  ...updates,
172
105
  };
173
-
174
- // Store updated credentials
175
106
  return await storeCredentials(updatedCredentials, options);
176
107
  } catch (error) {
177
108
  console.error('Error updating credentials:', error);
@@ -184,44 +115,23 @@ export const updateCredentials = async (
184
115
  */
185
116
  export const generateDeviceUsername = async (): Promise<string> => {
186
117
  try {
187
- // Get a device-specific identifier that we can use
188
- // This is a simplified example - in production you might want to use
189
- // a more robust device identifier method
190
118
  const deviceInfo = `${Platform.OS}-${Platform.Version}-${Date.now()}`;
191
-
192
- // Hash it to create a unique identifier
193
119
  const username = `onairos_${sha256(deviceInfo).substring(0, 10)}`;
194
-
195
120
  return username;
196
121
  } catch (error) {
197
122
  console.error('Error generating device username:', error);
198
- // Fallback to a timestamp-based username if there's an error
199
123
  return `onairos_${Date.now().toString(36)}`;
200
124
  }
201
125
  };
202
126
 
203
127
  /**
204
- * Verify if credentials are valid (not expired, etc.)
128
+ * Verify credentials (temporary mock implementation)
205
129
  */
206
130
  export const verifyCredentials = async (
207
131
  credentials: OnairosCredentials
208
132
  ): Promise<boolean> => {
209
133
  try {
210
- // Basic verification - check if credentials exist and aren't too old
211
- if (!credentials || !credentials.accessToken || !credentials.username) {
212
- return false;
213
- }
214
-
215
- // Check for expiration (example: credentials expire after 30 days)
216
- const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
217
- const isExpired = Date.now() - credentials.createdAt > thirtyDaysMs;
218
-
219
- if (isExpired) {
220
- return false;
221
- }
222
-
223
- // Add any additional verification logic here
224
-
134
+ console.log('[Mock] Verifying credentials for:', credentials.username);
225
135
  return true;
226
136
  } catch (error) {
227
137
  console.error('Error verifying credentials:', error);