@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.
- package/README.md +334 -0
- package/lib/commonjs/components/DataRequestModal.js +176 -0
- package/lib/commonjs/components/DataRequestModal.js.map +1 -0
- package/lib/commonjs/components/Notification.js +106 -0
- package/lib/commonjs/components/Notification.js.map +1 -0
- package/lib/commonjs/components/OnairosButton.js +575 -0
- package/lib/commonjs/components/OnairosButton.js.map +1 -0
- package/lib/commonjs/components/Overlay.js +818 -0
- package/lib/commonjs/components/Overlay.js.map +1 -0
- package/lib/commonjs/components/UniversalOnboarding.js +173 -0
- package/lib/commonjs/components/UniversalOnboarding.js.map +1 -0
- package/lib/commonjs/components/onboarding/OAuthWebView.js +137 -0
- package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -0
- package/lib/commonjs/components/onboarding/OnboardingHeader.js +74 -0
- package/lib/commonjs/components/onboarding/OnboardingHeader.js.map +1 -0
- package/lib/commonjs/components/onboarding/PinInput.js +283 -0
- package/lib/commonjs/components/onboarding/PinInput.js.map +1 -0
- package/lib/commonjs/components/onboarding/PlatformConnector.js +244 -0
- package/lib/commonjs/components/onboarding/PlatformConnector.js.map +1 -0
- package/lib/commonjs/components/screens/ConnectorScreen.js +145 -0
- package/lib/commonjs/components/screens/ConnectorScreen.js.map +1 -0
- package/lib/commonjs/components/screens/LoadingScreen.js +91 -0
- package/lib/commonjs/components/screens/LoadingScreen.js.map +1 -0
- package/lib/commonjs/components/screens/PinCreationScreen.js +61 -0
- package/lib/commonjs/components/screens/PinCreationScreen.js.map +1 -0
- package/lib/commonjs/constants/index.js +78 -0
- package/lib/commonjs/constants/index.js.map +1 -0
- package/lib/commonjs/hooks/useConnections.js +89 -0
- package/lib/commonjs/hooks/useConnections.js.map +1 -0
- package/lib/commonjs/hooks/useCredentials.js +85 -0
- package/lib/commonjs/hooks/useCredentials.js.map +1 -0
- package/lib/commonjs/index.js +282 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/services/oauthService.js +362 -0
- package/lib/commonjs/services/oauthService.js.map +1 -0
- package/lib/commonjs/types/declarations.d.js +2 -0
- package/lib/commonjs/types/declarations.d.js.map +1 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/utils/api.js +129 -0
- package/lib/commonjs/utils/api.js.map +1 -0
- package/lib/commonjs/utils/auth.js +111 -0
- package/lib/commonjs/utils/auth.js.map +1 -0
- package/lib/commonjs/utils/crypto.js +62 -0
- package/lib/commonjs/utils/crypto.js.map +1 -0
- package/lib/commonjs/utils/debugHelper.js +64 -0
- package/lib/commonjs/utils/debugHelper.js.map +1 -0
- package/lib/commonjs/utils/onairosApi.js +270 -0
- package/lib/commonjs/utils/onairosApi.js.map +1 -0
- package/lib/commonjs/utils/secureStorage.js +210 -0
- package/lib/commonjs/utils/secureStorage.js.map +1 -0
- package/lib/module/components/DataRequestModal.js +168 -0
- package/lib/module/components/DataRequestModal.js.map +1 -0
- package/lib/module/components/Notification.js +99 -0
- package/lib/module/components/Notification.js.map +1 -0
- package/lib/module/components/OnairosButton.js +550 -0
- package/lib/module/components/OnairosButton.js.map +1 -0
- package/lib/module/components/Overlay.js +825 -0
- package/lib/module/components/Overlay.js.map +1 -0
- package/lib/module/components/UniversalOnboarding.js +164 -0
- package/lib/module/components/UniversalOnboarding.js.map +1 -0
- package/lib/module/components/onboarding/OAuthWebView.js +128 -0
- package/lib/module/components/onboarding/OAuthWebView.js.map +1 -0
- package/lib/module/components/onboarding/OnboardingHeader.js +66 -0
- package/lib/module/components/onboarding/OnboardingHeader.js.map +1 -0
- package/lib/module/components/onboarding/PinInput.js +274 -0
- package/lib/module/components/onboarding/PinInput.js.map +1 -0
- package/lib/module/components/onboarding/PlatformConnector.js +235 -0
- package/lib/module/components/onboarding/PlatformConnector.js.map +1 -0
- package/lib/module/components/screens/ConnectorScreen.js +137 -0
- package/lib/module/components/screens/ConnectorScreen.js.map +1 -0
- package/lib/module/components/screens/LoadingScreen.js +83 -0
- package/lib/module/components/screens/LoadingScreen.js.map +1 -0
- package/lib/module/components/screens/PinCreationScreen.js +53 -0
- package/lib/module/components/screens/PinCreationScreen.js.map +1 -0
- package/lib/module/constants/index.js +72 -0
- package/lib/module/constants/index.js.map +1 -0
- package/lib/module/hooks/useConnections.js +81 -0
- package/lib/module/hooks/useConnections.js.map +1 -0
- package/lib/module/hooks/useCredentials.js +77 -0
- package/lib/module/hooks/useCredentials.js.map +1 -0
- package/lib/module/index.js +34 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/services/oauthService.js +352 -0
- package/lib/module/services/oauthService.js.map +1 -0
- package/lib/module/types/declarations.d.js +2 -0
- package/lib/module/types/declarations.d.js.map +1 -0
- package/lib/module/types/index.js +2 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/utils/api.js +117 -0
- package/lib/module/utils/api.js.map +1 -0
- package/lib/module/utils/auth.js +99 -0
- package/lib/module/utils/auth.js.map +1 -0
- package/lib/module/utils/crypto.js +54 -0
- package/lib/module/utils/crypto.js.map +1 -0
- package/lib/module/utils/debugHelper.js +54 -0
- package/lib/module/utils/debugHelper.js.map +1 -0
- package/lib/module/utils/onairosApi.js +256 -0
- package/lib/module/utils/onairosApi.js.map +1 -0
- package/lib/module/utils/secureStorage.js +196 -0
- package/lib/module/utils/secureStorage.js.map +1 -0
- package/package.json +115 -0
- package/src/components/DataRequestModal.tsx +187 -0
- package/src/components/Notification.js +101 -0
- package/src/components/OnairosButton.js +604 -0
- package/src/components/OnairosButton.tsx +182 -0
- package/src/components/Overlay.js +854 -0
- package/src/components/Overlay.tsx +272 -0
- package/src/components/UniversalOnboarding.tsx +184 -0
- package/src/components/onboarding/OAuthWebView.tsx +134 -0
- package/src/components/onboarding/OnboardingHeader.tsx +70 -0
- package/src/components/onboarding/PinInput.tsx +356 -0
- package/src/components/onboarding/PlatformConnector.tsx +297 -0
- package/src/components/screens/ConnectorScreen.tsx +152 -0
- package/src/components/screens/LoadingScreen.tsx +100 -0
- package/src/components/screens/PinCreationScreen.tsx +67 -0
- package/src/constants/index.ts +78 -0
- package/src/hooks/useConnections.ts +90 -0
- package/src/hooks/useCredentials.ts +83 -0
- package/src/index.js +14 -0
- package/src/index.ts +82 -0
- package/src/services/oauthService.ts +360 -0
- package/src/types/declarations.d.ts +26 -0
- package/src/types/index.ts +82 -0
- package/src/utils/api.js +112 -0
- package/src/utils/auth.js +104 -0
- package/src/utils/crypto.js +60 -0
- package/src/utils/debugHelper.ts +53 -0
- package/src/utils/onairosApi.ts +303 -0
- 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
|
+
};
|