@onairos/react-native 1.0.0

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.
Files changed (130) hide show
  1. package/README.md +334 -0
  2. package/lib/commonjs/components/DataRequestModal.js +176 -0
  3. package/lib/commonjs/components/DataRequestModal.js.map +1 -0
  4. package/lib/commonjs/components/Notification.js +106 -0
  5. package/lib/commonjs/components/Notification.js.map +1 -0
  6. package/lib/commonjs/components/OnairosButton.js +575 -0
  7. package/lib/commonjs/components/OnairosButton.js.map +1 -0
  8. package/lib/commonjs/components/Overlay.js +818 -0
  9. package/lib/commonjs/components/Overlay.js.map +1 -0
  10. package/lib/commonjs/components/UniversalOnboarding.js +173 -0
  11. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -0
  12. package/lib/commonjs/components/onboarding/OAuthWebView.js +137 -0
  13. package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -0
  14. package/lib/commonjs/components/onboarding/OnboardingHeader.js +74 -0
  15. package/lib/commonjs/components/onboarding/OnboardingHeader.js.map +1 -0
  16. package/lib/commonjs/components/onboarding/PinInput.js +283 -0
  17. package/lib/commonjs/components/onboarding/PinInput.js.map +1 -0
  18. package/lib/commonjs/components/onboarding/PlatformConnector.js +244 -0
  19. package/lib/commonjs/components/onboarding/PlatformConnector.js.map +1 -0
  20. package/lib/commonjs/components/screens/ConnectorScreen.js +145 -0
  21. package/lib/commonjs/components/screens/ConnectorScreen.js.map +1 -0
  22. package/lib/commonjs/components/screens/LoadingScreen.js +91 -0
  23. package/lib/commonjs/components/screens/LoadingScreen.js.map +1 -0
  24. package/lib/commonjs/components/screens/PinCreationScreen.js +61 -0
  25. package/lib/commonjs/components/screens/PinCreationScreen.js.map +1 -0
  26. package/lib/commonjs/constants/index.js +78 -0
  27. package/lib/commonjs/constants/index.js.map +1 -0
  28. package/lib/commonjs/hooks/useConnections.js +89 -0
  29. package/lib/commonjs/hooks/useConnections.js.map +1 -0
  30. package/lib/commonjs/hooks/useCredentials.js +85 -0
  31. package/lib/commonjs/hooks/useCredentials.js.map +1 -0
  32. package/lib/commonjs/index.js +282 -0
  33. package/lib/commonjs/index.js.map +1 -0
  34. package/lib/commonjs/services/oauthService.js +362 -0
  35. package/lib/commonjs/services/oauthService.js.map +1 -0
  36. package/lib/commonjs/types/declarations.d.js +2 -0
  37. package/lib/commonjs/types/declarations.d.js.map +1 -0
  38. package/lib/commonjs/types/index.js +2 -0
  39. package/lib/commonjs/types/index.js.map +1 -0
  40. package/lib/commonjs/utils/api.js +129 -0
  41. package/lib/commonjs/utils/api.js.map +1 -0
  42. package/lib/commonjs/utils/auth.js +111 -0
  43. package/lib/commonjs/utils/auth.js.map +1 -0
  44. package/lib/commonjs/utils/crypto.js +62 -0
  45. package/lib/commonjs/utils/crypto.js.map +1 -0
  46. package/lib/commonjs/utils/debugHelper.js +64 -0
  47. package/lib/commonjs/utils/debugHelper.js.map +1 -0
  48. package/lib/commonjs/utils/onairosApi.js +270 -0
  49. package/lib/commonjs/utils/onairosApi.js.map +1 -0
  50. package/lib/commonjs/utils/secureStorage.js +210 -0
  51. package/lib/commonjs/utils/secureStorage.js.map +1 -0
  52. package/lib/module/components/DataRequestModal.js +168 -0
  53. package/lib/module/components/DataRequestModal.js.map +1 -0
  54. package/lib/module/components/Notification.js +99 -0
  55. package/lib/module/components/Notification.js.map +1 -0
  56. package/lib/module/components/OnairosButton.js +550 -0
  57. package/lib/module/components/OnairosButton.js.map +1 -0
  58. package/lib/module/components/Overlay.js +825 -0
  59. package/lib/module/components/Overlay.js.map +1 -0
  60. package/lib/module/components/UniversalOnboarding.js +164 -0
  61. package/lib/module/components/UniversalOnboarding.js.map +1 -0
  62. package/lib/module/components/onboarding/OAuthWebView.js +128 -0
  63. package/lib/module/components/onboarding/OAuthWebView.js.map +1 -0
  64. package/lib/module/components/onboarding/OnboardingHeader.js +66 -0
  65. package/lib/module/components/onboarding/OnboardingHeader.js.map +1 -0
  66. package/lib/module/components/onboarding/PinInput.js +274 -0
  67. package/lib/module/components/onboarding/PinInput.js.map +1 -0
  68. package/lib/module/components/onboarding/PlatformConnector.js +235 -0
  69. package/lib/module/components/onboarding/PlatformConnector.js.map +1 -0
  70. package/lib/module/components/screens/ConnectorScreen.js +137 -0
  71. package/lib/module/components/screens/ConnectorScreen.js.map +1 -0
  72. package/lib/module/components/screens/LoadingScreen.js +83 -0
  73. package/lib/module/components/screens/LoadingScreen.js.map +1 -0
  74. package/lib/module/components/screens/PinCreationScreen.js +53 -0
  75. package/lib/module/components/screens/PinCreationScreen.js.map +1 -0
  76. package/lib/module/constants/index.js +72 -0
  77. package/lib/module/constants/index.js.map +1 -0
  78. package/lib/module/hooks/useConnections.js +81 -0
  79. package/lib/module/hooks/useConnections.js.map +1 -0
  80. package/lib/module/hooks/useCredentials.js +77 -0
  81. package/lib/module/hooks/useCredentials.js.map +1 -0
  82. package/lib/module/index.js +34 -0
  83. package/lib/module/index.js.map +1 -0
  84. package/lib/module/services/oauthService.js +352 -0
  85. package/lib/module/services/oauthService.js.map +1 -0
  86. package/lib/module/types/declarations.d.js +2 -0
  87. package/lib/module/types/declarations.d.js.map +1 -0
  88. package/lib/module/types/index.js +2 -0
  89. package/lib/module/types/index.js.map +1 -0
  90. package/lib/module/utils/api.js +117 -0
  91. package/lib/module/utils/api.js.map +1 -0
  92. package/lib/module/utils/auth.js +99 -0
  93. package/lib/module/utils/auth.js.map +1 -0
  94. package/lib/module/utils/crypto.js +54 -0
  95. package/lib/module/utils/crypto.js.map +1 -0
  96. package/lib/module/utils/debugHelper.js +54 -0
  97. package/lib/module/utils/debugHelper.js.map +1 -0
  98. package/lib/module/utils/onairosApi.js +256 -0
  99. package/lib/module/utils/onairosApi.js.map +1 -0
  100. package/lib/module/utils/secureStorage.js +196 -0
  101. package/lib/module/utils/secureStorage.js.map +1 -0
  102. package/package.json +115 -0
  103. package/src/components/DataRequestModal.tsx +187 -0
  104. package/src/components/Notification.js +101 -0
  105. package/src/components/OnairosButton.js +604 -0
  106. package/src/components/OnairosButton.tsx +182 -0
  107. package/src/components/Overlay.js +854 -0
  108. package/src/components/Overlay.tsx +272 -0
  109. package/src/components/UniversalOnboarding.tsx +184 -0
  110. package/src/components/onboarding/OAuthWebView.tsx +134 -0
  111. package/src/components/onboarding/OnboardingHeader.tsx +70 -0
  112. package/src/components/onboarding/PinInput.tsx +356 -0
  113. package/src/components/onboarding/PlatformConnector.tsx +297 -0
  114. package/src/components/screens/ConnectorScreen.tsx +152 -0
  115. package/src/components/screens/LoadingScreen.tsx +100 -0
  116. package/src/components/screens/PinCreationScreen.tsx +67 -0
  117. package/src/constants/index.ts +78 -0
  118. package/src/hooks/useConnections.ts +90 -0
  119. package/src/hooks/useCredentials.ts +83 -0
  120. package/src/index.js +14 -0
  121. package/src/index.ts +82 -0
  122. package/src/services/oauthService.ts +360 -0
  123. package/src/types/declarations.d.ts +26 -0
  124. package/src/types/index.ts +82 -0
  125. package/src/utils/api.js +112 -0
  126. package/src/utils/auth.js +104 -0
  127. package/src/utils/crypto.js +60 -0
  128. package/src/utils/debugHelper.ts +53 -0
  129. package/src/utils/onairosApi.ts +303 -0
  130. package/src/utils/secureStorage.ts +230 -0
@@ -0,0 +1,60 @@
1
+ import { RSA } from 'react-native-rsa-native';
2
+ import { sha256 as cryptoSha256 } from 'react-native-crypto-js';
3
+
4
+ /**
5
+ * Encrypt data using RSA
6
+ * @param {string} publicKey - The RSA public key
7
+ * @param {string} data - The data to encrypt
8
+ * @returns {Promise<string>} - The encrypted data
9
+ */
10
+ export const rsaEncrypt = async (publicKey, data) => {
11
+ try {
12
+ // Clean the public key format
13
+ const cleanedKey = publicKey
14
+ .replace(/\\n/g, '')
15
+ .replace(/^\s+|\s+$/g, '')
16
+ .replace('-----BEGIN PUBLIC KEY-----', '')
17
+ .replace('-----END PUBLIC KEY-----', '')
18
+ .trim();
19
+
20
+ // Format the key properly for the library
21
+ const formattedKey = `-----BEGIN PUBLIC KEY-----\n${cleanedKey}\n-----END PUBLIC KEY-----`;
22
+
23
+ // Encrypt the data
24
+ const encrypted = await RSA.encrypt(data, formattedKey);
25
+ return encrypted;
26
+ } catch (error) {
27
+ console.error('RSA encryption failed:', error);
28
+ throw error;
29
+ }
30
+ };
31
+
32
+ /**
33
+ * Hash data using SHA-256
34
+ * @param {string} data - The data to hash
35
+ * @returns {string} - The hashed data
36
+ */
37
+ export const sha256 = (data) => {
38
+ return cryptoSha256(data).toString();
39
+ };
40
+
41
+ /**
42
+ * Convert a base64 string to a buffer
43
+ * @param {string} base64String - The base64 string to convert
44
+ * @returns {ArrayBuffer} - The converted buffer
45
+ */
46
+ export const base64ToBuffer = (base64String) => {
47
+ try {
48
+ const binaryString = Buffer.from(base64String, 'base64').toString('binary');
49
+ const bytes = new Uint8Array(binaryString.length);
50
+
51
+ for (let i = 0; i < binaryString.length; i++) {
52
+ bytes[i] = binaryString.charCodeAt(i);
53
+ }
54
+
55
+ return bytes.buffer;
56
+ } catch (error) {
57
+ console.error('Error converting base64 to buffer:', error);
58
+ throw error;
59
+ }
60
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Debug utility functions for Onairos React Native SDK
3
+ */
4
+
5
+ // Determine if we're in dev mode
6
+ const __DEV__ = process.env.NODE_ENV !== 'production';
7
+
8
+ /**
9
+ * Log debug message to console when in development mode
10
+ * @param label Label for the debug message
11
+ * @param data Data to log
12
+ */
13
+ export const logDebug = (label: string, data?: any): void => {
14
+ if (__DEV__) {
15
+ console.log(`[Onairos Debug] ${label}:`, data || '');
16
+ }
17
+ };
18
+
19
+ /**
20
+ * Log error message to console
21
+ * @param label Label for the error message
22
+ * @param error Error object or message
23
+ */
24
+ export const logError = (label: string, error: any): void => {
25
+ if (__DEV__) {
26
+ console.error(`[Onairos Error] ${label}:`, error);
27
+ }
28
+ };
29
+
30
+ /**
31
+ * Check if debug mode is enabled
32
+ * @param debug Debug flag passed to component
33
+ * @returns Whether debug mode is enabled
34
+ */
35
+ export const isDebugMode = (debug?: boolean): boolean => {
36
+ return debug === true || __DEV__;
37
+ };
38
+
39
+ /**
40
+ * Format and log API request details
41
+ * @param method HTTP method
42
+ * @param url API URL
43
+ * @param data Request data
44
+ */
45
+ export const logApiRequest = (method: string, url: string, data?: any): void => {
46
+ if (__DEV__) {
47
+ logDebug('API Request', {
48
+ method,
49
+ url,
50
+ data: data || {},
51
+ });
52
+ }
53
+ };
@@ -0,0 +1,303 @@
1
+ import { Platform } from 'react-native';
2
+ import NetInfo from '@react-native-community/netinfo';
3
+ import { OnairosCredentials } from './secureStorage';
4
+ import { logDebug } from './debugHelper';
5
+
6
+ // API configuration
7
+ const API_CONFIG = {
8
+ baseUrl: 'https://api2.onairos.uk',
9
+ version: 'v1',
10
+ timeout: 30000, // 30 seconds
11
+ };
12
+
13
+ // API response types
14
+ export interface ApiResponse<T = any> {
15
+ success: boolean;
16
+ data?: T;
17
+ error?: {
18
+ code: string;
19
+ message: string;
20
+ details?: any;
21
+ };
22
+ }
23
+
24
+ // Error types
25
+ export type ApiErrorType =
26
+ | 'network_error'
27
+ | 'timeout_error'
28
+ | 'auth_error'
29
+ | 'server_error'
30
+ | 'validation_error'
31
+ | 'unknown_error';
32
+
33
+ // API error structure
34
+ export class ApiError extends Error {
35
+ type: ApiErrorType;
36
+ code?: string;
37
+ details?: any;
38
+
39
+ constructor(message: string, type: ApiErrorType, code?: string, details?: any) {
40
+ super(message);
41
+ this.type = type;
42
+ this.code = code;
43
+ this.details = details;
44
+ this.name = 'ApiError';
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Check if the device has an active network connection
50
+ */
51
+ const checkNetworkConnection = async (): Promise<boolean> => {
52
+ const networkState = await NetInfo.fetch();
53
+ return networkState.isConnected === true;
54
+ };
55
+
56
+ /**
57
+ * Build API request headers
58
+ */
59
+ const buildHeaders = (accessToken?: string, additionalHeaders = {}): Record<string, string> => {
60
+ const headers: Record<string, string> = {
61
+ 'Content-Type': 'application/json',
62
+ 'Accept': 'application/json',
63
+ 'User-Agent': `OnairosReactNative/${Platform.OS}/${Platform.Version}`,
64
+ ...additionalHeaders,
65
+ };
66
+
67
+ if (accessToken) {
68
+ headers['Authorization'] = `Bearer ${accessToken}`;
69
+ }
70
+
71
+ return headers;
72
+ };
73
+
74
+ /**
75
+ * Make API request with proper error handling
76
+ */
77
+ const apiRequest = async <T>(
78
+ endpoint: string,
79
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
80
+ options: {
81
+ data?: any;
82
+ accessToken?: string;
83
+ headers?: Record<string, string>;
84
+ timeout?: number;
85
+ debug?: boolean;
86
+ } = {}
87
+ ): Promise<ApiResponse<T>> => {
88
+ const {
89
+ data,
90
+ accessToken,
91
+ headers = {},
92
+ timeout = API_CONFIG.timeout,
93
+ debug = false,
94
+ } = options;
95
+
96
+ try {
97
+ // Check for network connectivity
98
+ const isConnected = await checkNetworkConnection();
99
+ if (!isConnected) {
100
+ throw new ApiError(
101
+ 'No network connection available',
102
+ 'network_error'
103
+ );
104
+ }
105
+
106
+ // Build request URL
107
+ const url = `${API_CONFIG.baseUrl}/${API_CONFIG.version}/${endpoint}`;
108
+
109
+ // Log request information if debug mode is enabled
110
+ if (debug) {
111
+ logDebug('API Request', {
112
+ url,
113
+ method,
114
+ headers: { ...buildHeaders(accessToken, headers), Authorization: accessToken ? '[REDACTED]' : undefined },
115
+ data: data || null,
116
+ });
117
+ }
118
+
119
+ // Set up request options
120
+ const fetchOptions: RequestInit = {
121
+ method,
122
+ headers: buildHeaders(accessToken, headers),
123
+ ...(data ? { body: JSON.stringify(data) } : {}),
124
+ };
125
+
126
+ // Create fetch request with timeout
127
+ const controller = new AbortController();
128
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
129
+
130
+ // Execute request
131
+ const response = await fetch(url, {
132
+ ...fetchOptions,
133
+ signal: controller.signal,
134
+ });
135
+
136
+ // Clear timeout
137
+ clearTimeout(timeoutId);
138
+
139
+ // Parse response as JSON
140
+ const responseData = await response.json();
141
+
142
+ // Log response if debug mode is enabled
143
+ if (debug) {
144
+ logDebug('API Response', {
145
+ status: response.status,
146
+ data: responseData,
147
+ });
148
+ }
149
+
150
+ // Handle API error responses
151
+ if (!response.ok) {
152
+ const errorType: ApiErrorType =
153
+ response.status === 401 || response.status === 403 ? 'auth_error' :
154
+ response.status === 400 ? 'validation_error' :
155
+ response.status >= 500 ? 'server_error' : 'unknown_error';
156
+
157
+ throw new ApiError(
158
+ responseData.error?.message || 'API request failed',
159
+ errorType,
160
+ responseData.error?.code,
161
+ responseData.error?.details
162
+ );
163
+ }
164
+
165
+ return responseData as ApiResponse<T>;
166
+ } catch (error) {
167
+ // Handle specific error types
168
+ if (error instanceof ApiError) {
169
+ throw error;
170
+ }
171
+
172
+ if (error.name === 'AbortError') {
173
+ throw new ApiError('Request timed out', 'timeout_error');
174
+ }
175
+
176
+ // Log error if debug mode is enabled
177
+ if (debug) {
178
+ logDebug('API Error', {
179
+ endpoint,
180
+ method,
181
+ error: error.message || 'Unknown error',
182
+ });
183
+ }
184
+
185
+ // Return a generic error for all other cases
186
+ throw new ApiError(
187
+ error.message || 'An unexpected error occurred',
188
+ 'unknown_error'
189
+ );
190
+ }
191
+ };
192
+
193
+ /**
194
+ * Validate user credentials with the API
195
+ */
196
+ export const validateCredentials = async (
197
+ username: string,
198
+ options = { debug: false }
199
+ ): Promise<{ isValid: boolean; message?: string }> => {
200
+ try {
201
+ const response = await apiRequest<{ isValid: boolean }>('auth/validate', 'POST', {
202
+ data: { username },
203
+ debug: options.debug,
204
+ });
205
+
206
+ return {
207
+ isValid: response.success && response.data?.isValid === true,
208
+ message: response.error?.message,
209
+ };
210
+ } catch (error) {
211
+ return {
212
+ isValid: false,
213
+ message: error.message,
214
+ };
215
+ }
216
+ };
217
+
218
+ /**
219
+ * Create a new user account
220
+ */
221
+ export const createAccount = async (
222
+ credentials: Partial<OnairosCredentials>,
223
+ options = { debug: false }
224
+ ): Promise<ApiResponse<{ accessToken: string; userId: string }>> => {
225
+ return apiRequest('auth/register', 'POST', {
226
+ data: {
227
+ username: credentials.username,
228
+ platforms: credentials.platforms,
229
+ },
230
+ debug: options.debug,
231
+ });
232
+ };
233
+
234
+ /**
235
+ * Authenticate with the API using credentials
236
+ */
237
+ export const authenticate = async (
238
+ credentials: Partial<OnairosCredentials>,
239
+ options = { debug: false }
240
+ ): Promise<ApiResponse<{ accessToken: string; refreshToken: string }>> => {
241
+ return apiRequest('auth/login', 'POST', {
242
+ data: {
243
+ username: credentials.username,
244
+ pin: credentials.userPin,
245
+ },
246
+ debug: options.debug,
247
+ });
248
+ };
249
+
250
+ /**
251
+ * Refresh authentication token
252
+ */
253
+ export const refreshToken = async (
254
+ refreshToken: string,
255
+ options = { debug: false }
256
+ ): Promise<ApiResponse<{ accessToken: string; refreshToken: string }>> => {
257
+ return apiRequest('auth/refresh', 'POST', {
258
+ data: { refreshToken },
259
+ debug: options.debug,
260
+ });
261
+ };
262
+
263
+ /**
264
+ * Get user's connected platform data
265
+ */
266
+ export const getPlatformData = async (
267
+ accessToken: string,
268
+ platform: string,
269
+ options = { debug: false }
270
+ ): Promise<ApiResponse<any>> => {
271
+ return apiRequest(`platforms/${platform}/data`, 'GET', {
272
+ accessToken,
273
+ debug: options.debug,
274
+ });
275
+ };
276
+
277
+ /**
278
+ * Get user profile information
279
+ */
280
+ export const getUserProfile = async (
281
+ accessToken: string,
282
+ options = { debug: false }
283
+ ): Promise<ApiResponse<any>> => {
284
+ return apiRequest('user/profile', 'GET', {
285
+ accessToken,
286
+ debug: options.debug,
287
+ });
288
+ };
289
+
290
+ /**
291
+ * Update user platform connections
292
+ */
293
+ export const updatePlatformConnections = async (
294
+ accessToken: string,
295
+ platforms: Record<string, any>,
296
+ options = { debug: false }
297
+ ): Promise<ApiResponse<any>> => {
298
+ return apiRequest('user/platforms', 'PUT', {
299
+ accessToken,
300
+ data: { platforms },
301
+ debug: options.debug,
302
+ });
303
+ };
@@ -0,0 +1,230 @@
1
+ import * as Keychain from 'react-native-keychain';
2
+ import { Platform } from 'react-native';
3
+ import { sha256 } from './crypto';
4
+
5
+ export interface OnairosCredentials {
6
+ username: string;
7
+ accessToken: string;
8
+ refreshToken?: string;
9
+ userPin?: string;
10
+ platforms?: {
11
+ instagram?: { token: string; username: string };
12
+ youtube?: { token: string; username: string };
13
+ pinterest?: { token: string; username: string };
14
+ reddit?: { token: string; username: string };
15
+ };
16
+ createdAt: number;
17
+ }
18
+
19
+ export interface StorageOptions {
20
+ useBiometrics?: boolean;
21
+ biometricPrompt?: {
22
+ title: string;
23
+ subtitle?: string;
24
+ };
25
+ }
26
+
27
+ const CREDENTIALS_KEY = 'onairos_credentials';
28
+
29
+ /**
30
+ * Store credentials securely in the device keychain with optional biometric protection
31
+ */
32
+ export const storeCredentials = async (
33
+ credentials: OnairosCredentials,
34
+ options: StorageOptions = {}
35
+ ): Promise<boolean> => {
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
+
77
+ return true;
78
+ } catch (error) {
79
+ console.error('Error storing credentials:', error);
80
+ return false;
81
+ }
82
+ };
83
+
84
+ /**
85
+ * Retrieve credentials from secure storage, potentially requiring biometric authentication
86
+ */
87
+ export const getCredentials = async (
88
+ options: StorageOptions = {}
89
+ ): Promise<OnairosCredentials | null> => {
90
+ 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) {
112
+ return null;
113
+ }
114
+
115
+ // Parse the stored JSON
116
+ const credentials: OnairosCredentials = JSON.parse(result.password);
117
+ return credentials;
118
+ } catch (error) {
119
+ console.error('Error retrieving credentials:', error);
120
+ return null;
121
+ }
122
+ };
123
+
124
+ /**
125
+ * Check if the user has stored credentials
126
+ */
127
+ export const hasCredentials = async (): Promise<boolean> => {
128
+ try {
129
+ const result = await Keychain.getGenericPassword({
130
+ service: CREDENTIALS_KEY,
131
+ });
132
+
133
+ return !!result;
134
+ } catch (error) {
135
+ console.error('Error checking for credentials:', error);
136
+ return false;
137
+ }
138
+ };
139
+
140
+ /**
141
+ * Delete stored credentials
142
+ */
143
+ export const deleteCredentials = async (): Promise<boolean> => {
144
+ try {
145
+ await Keychain.resetGenericPassword({ service: CREDENTIALS_KEY });
146
+ return true;
147
+ } catch (error) {
148
+ console.error('Error deleting credentials:', error);
149
+ return false;
150
+ }
151
+ };
152
+
153
+ /**
154
+ * Update specific fields in the stored credentials
155
+ */
156
+ export const updateCredentials = async (
157
+ updates: Partial<OnairosCredentials>,
158
+ options: StorageOptions = {}
159
+ ): Promise<boolean> => {
160
+ try {
161
+ // Get current credentials
162
+ const currentCredentials = await getCredentials(options);
163
+
164
+ if (!currentCredentials) {
165
+ return false;
166
+ }
167
+
168
+ // Merge updates with current credentials
169
+ const updatedCredentials: OnairosCredentials = {
170
+ ...currentCredentials,
171
+ ...updates,
172
+ };
173
+
174
+ // Store updated credentials
175
+ return await storeCredentials(updatedCredentials, options);
176
+ } catch (error) {
177
+ console.error('Error updating credentials:', error);
178
+ return false;
179
+ }
180
+ };
181
+
182
+ /**
183
+ * Generate a device-specific unique username
184
+ */
185
+ export const generateDeviceUsername = async (): Promise<string> => {
186
+ 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
+ const deviceInfo = `${Platform.OS}-${Platform.Version}-${Date.now()}`;
191
+
192
+ // Hash it to create a unique identifier
193
+ const username = `onairos_${sha256(deviceInfo).substring(0, 10)}`;
194
+
195
+ return username;
196
+ } catch (error) {
197
+ console.error('Error generating device username:', error);
198
+ // Fallback to a timestamp-based username if there's an error
199
+ return `onairos_${Date.now().toString(36)}`;
200
+ }
201
+ };
202
+
203
+ /**
204
+ * Verify if credentials are valid (not expired, etc.)
205
+ */
206
+ export const verifyCredentials = async (
207
+ credentials: OnairosCredentials
208
+ ): Promise<boolean> => {
209
+ 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
+
225
+ return true;
226
+ } catch (error) {
227
+ console.error('Error verifying credentials:', error);
228
+ return false;
229
+ }
230
+ };