@onairos/react-native 2.0.9 → 3.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/lib/commonjs/api/index.js +102 -14
- package/lib/commonjs/api/index.js.map +1 -1
- package/lib/commonjs/components/Onairos.js +89 -0
- package/lib/commonjs/components/Onairos.js.map +1 -0
- package/lib/commonjs/components/OnairosButton.js +117 -33
- package/lib/commonjs/components/OnairosButton.js.map +1 -1
- package/lib/commonjs/components/Overlay.js +29 -9
- package/lib/commonjs/components/Overlay.js.map +1 -1
- package/lib/commonjs/index.js +12 -276
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/services/oauthService.js +145 -1
- package/lib/commonjs/services/oauthService.js.map +1 -1
- package/lib/commonjs/utils/encryption.js +45 -7
- package/lib/commonjs/utils/encryption.js.map +1 -1
- package/lib/module/api/index.js +103 -14
- package/lib/module/api/index.js.map +1 -1
- package/lib/module/components/Onairos.js +81 -0
- package/lib/module/components/Onairos.js.map +1 -0
- package/lib/module/components/OnairosButton.js +120 -35
- package/lib/module/components/OnairosButton.js.map +1 -1
- package/lib/module/components/Overlay.js +31 -11
- package/lib/module/components/Overlay.js.map +1 -1
- package/lib/module/index.js +15 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/oauthService.js +144 -0
- package/lib/module/services/oauthService.js.map +1 -1
- package/lib/module/utils/encryption.js +43 -6
- package/lib/module/utils/encryption.js.map +1 -1
- package/package.json +1 -1
- package/src/api/index.ts +113 -20
- package/src/components/Onairos.tsx +117 -0
- package/src/components/OnairosButton.tsx +157 -42
- package/src/components/Overlay.tsx +24 -6
- package/src/index.ts +19 -5
- package/src/services/oauthService.ts +174 -0
- package/src/types/index.ts +38 -7
- package/src/utils/encryption.ts +45 -6
|
@@ -1,18 +1,34 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
1
|
+
import React, { useState, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
TouchableOpacity,
|
|
4
|
+
Text,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
View,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
TextStyle,
|
|
9
|
+
Image,
|
|
10
|
+
ActivityIndicator,
|
|
11
|
+
Alert
|
|
12
|
+
} from 'react-native';
|
|
3
13
|
import { UniversalOnboarding } from './UniversalOnboarding';
|
|
14
|
+
import { Overlay } from './Overlay';
|
|
4
15
|
import { COLORS } from '../constants';
|
|
5
16
|
import type { OnairosButtonProps } from '../types';
|
|
17
|
+
import { hasCredentials, getCredentials, clearCredentials } from '../utils/secureStorage';
|
|
18
|
+
import { onairosApi } from '../api';
|
|
6
19
|
|
|
20
|
+
/**
|
|
21
|
+
* OnairosButton Component - A sign-in button similar to Google/Apple sign-in
|
|
22
|
+
*/
|
|
7
23
|
export const OnairosButton: React.FC<OnairosButtonProps> = ({
|
|
8
24
|
returnLink,
|
|
9
25
|
prefillUrl,
|
|
10
26
|
AppName,
|
|
11
27
|
buttonType = 'normal',
|
|
12
28
|
requestData,
|
|
13
|
-
buttonWidth =
|
|
14
|
-
buttonHeight,
|
|
15
|
-
hasStroke =
|
|
29
|
+
buttonWidth = 240,
|
|
30
|
+
buttonHeight = 48,
|
|
31
|
+
hasStroke = true,
|
|
16
32
|
enabled = true,
|
|
17
33
|
buttonForm = 'default',
|
|
18
34
|
onRejection,
|
|
@@ -25,40 +41,100 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
|
|
|
25
41
|
testMode = false,
|
|
26
42
|
}) => {
|
|
27
43
|
const [showOnboarding, setShowOnboarding] = useState(false);
|
|
44
|
+
const [showOverlay, setShowOverlay] = useState(false);
|
|
45
|
+
const [storedCredentials, setStoredCredentials] = useState<any>(null);
|
|
46
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
28
47
|
|
|
48
|
+
const isDarkMode = color === 'black' || (!color && !hasStroke);
|
|
49
|
+
|
|
29
50
|
const handlePress = async () => {
|
|
30
|
-
if (!enabled) return;
|
|
51
|
+
if (!enabled || isLoading) return;
|
|
52
|
+
|
|
53
|
+
setIsLoading(true);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
if (preCheck) {
|
|
57
|
+
const shouldProceed = await preCheck();
|
|
58
|
+
if (!shouldProceed) {
|
|
59
|
+
onRejection?.('Precheck validation failed');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
31
63
|
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
64
|
+
// Check if credentials exist
|
|
65
|
+
const hasStoredCreds = await hasCredentials();
|
|
66
|
+
|
|
67
|
+
if (hasStoredCreds) {
|
|
68
|
+
// If credentials exist, fetch them and verify
|
|
69
|
+
const credentials = await getCredentials();
|
|
70
|
+
|
|
71
|
+
if (!credentials || !credentials.username || !credentials.userPin) {
|
|
72
|
+
// Invalid credentials, clear and start fresh
|
|
73
|
+
await clearCredentials();
|
|
74
|
+
setShowOnboarding(true);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Validate credentials with server
|
|
79
|
+
const isValid = await onairosApi.validateCredentials(credentials.username);
|
|
80
|
+
|
|
81
|
+
if (!isValid) {
|
|
82
|
+
// Clear invalid credentials
|
|
83
|
+
await clearCredentials();
|
|
84
|
+
setShowOnboarding(true);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Store and display overlay with valid credentials
|
|
89
|
+
setStoredCredentials(credentials);
|
|
90
|
+
setShowOverlay(true);
|
|
91
|
+
} else {
|
|
92
|
+
// If no credentials, show onboarding
|
|
93
|
+
setShowOnboarding(true);
|
|
37
94
|
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Error during button press flow:', error);
|
|
97
|
+
Alert.alert('Error', 'An error occurred. Please try again later.');
|
|
98
|
+
onRejection?.(error instanceof Error ? error.message : 'Unknown error');
|
|
99
|
+
} finally {
|
|
100
|
+
setIsLoading(false);
|
|
38
101
|
}
|
|
39
|
-
|
|
40
|
-
setShowOnboarding(true);
|
|
41
102
|
};
|
|
42
103
|
|
|
43
|
-
const handleOnboardingComplete = (apiUrl: string, token: string, data: any) => {
|
|
104
|
+
const handleOnboardingComplete = useCallback((apiUrl: string, token: string, data: any) => {
|
|
44
105
|
setShowOnboarding(false);
|
|
45
|
-
onResolved
|
|
46
|
-
|
|
106
|
+
if (onResolved) {
|
|
107
|
+
onResolved(apiUrl, token, data);
|
|
108
|
+
}
|
|
109
|
+
}, [onResolved]);
|
|
110
|
+
|
|
111
|
+
const handleOverlayResolved = useCallback((apiUrl: string, token: string, data: any) => {
|
|
112
|
+
setShowOverlay(false);
|
|
113
|
+
if (onResolved) {
|
|
114
|
+
onResolved(apiUrl, token, data);
|
|
115
|
+
}
|
|
116
|
+
}, [onResolved]);
|
|
47
117
|
|
|
118
|
+
// Calculate button styles based on props
|
|
48
119
|
const buttonStyle: ViewStyle[] = [
|
|
49
120
|
styles.button,
|
|
50
121
|
buttonType === 'pill' && styles.pillButton,
|
|
51
122
|
hasStroke && styles.strokedButton,
|
|
52
|
-
{
|
|
53
|
-
|
|
123
|
+
{
|
|
124
|
+
width: buttonWidth,
|
|
125
|
+
height: buttonHeight
|
|
126
|
+
},
|
|
54
127
|
color ? { backgroundColor: color } : null,
|
|
128
|
+
isDarkMode ? styles.darkButton : styles.lightButton,
|
|
55
129
|
swerv && styles.swervButton,
|
|
130
|
+
!enabled && styles.disabledButton
|
|
56
131
|
].filter(Boolean) as ViewStyle[];
|
|
57
132
|
|
|
133
|
+
// Calculate text styles based on props
|
|
58
134
|
const textStyle: TextStyle[] = [
|
|
59
135
|
styles.buttonText,
|
|
60
|
-
|
|
61
|
-
|
|
136
|
+
isDarkMode ? styles.lightText : styles.darkText,
|
|
137
|
+
!enabled && styles.disabledText
|
|
62
138
|
].filter(Boolean) as TextStyle[];
|
|
63
139
|
|
|
64
140
|
return (
|
|
@@ -66,37 +142,62 @@ export const OnairosButton: React.FC<OnairosButtonProps> = ({
|
|
|
66
142
|
<TouchableOpacity
|
|
67
143
|
style={buttonStyle}
|
|
68
144
|
onPress={handlePress}
|
|
69
|
-
disabled={!enabled}
|
|
145
|
+
disabled={!enabled || isLoading}
|
|
146
|
+
accessibilityLabel={`Sign in with Onairos`}
|
|
70
147
|
>
|
|
71
|
-
|
|
148
|
+
{isLoading ? (
|
|
149
|
+
<ActivityIndicator
|
|
150
|
+
size="small"
|
|
151
|
+
color={isDarkMode ? '#fff' : '#000'}
|
|
152
|
+
/>
|
|
153
|
+
) : (
|
|
154
|
+
<>
|
|
155
|
+
{/* Optional Onairos logo could be added here */}
|
|
156
|
+
<Text style={textStyle}>Sign in with Onairos</Text>
|
|
157
|
+
</>
|
|
158
|
+
)}
|
|
72
159
|
</TouchableOpacity>
|
|
73
160
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
161
|
+
{showOnboarding && (
|
|
162
|
+
<UniversalOnboarding
|
|
163
|
+
visible={showOnboarding}
|
|
164
|
+
onClose={() => {
|
|
165
|
+
setShowOnboarding(false);
|
|
166
|
+
onRejection?.('User closed onboarding');
|
|
167
|
+
}}
|
|
168
|
+
AppName={AppName}
|
|
169
|
+
requestData={requestData}
|
|
170
|
+
returnLink={returnLink}
|
|
171
|
+
onComplete={handleOnboardingComplete}
|
|
172
|
+
preferredPlatform={preferredPlatform}
|
|
173
|
+
debug={debug}
|
|
174
|
+
test={testMode}
|
|
175
|
+
/>
|
|
176
|
+
)}
|
|
177
|
+
|
|
178
|
+
{showOverlay && storedCredentials && (
|
|
179
|
+
<Overlay
|
|
180
|
+
data={requestData || {}}
|
|
181
|
+
username={storedCredentials.username}
|
|
182
|
+
modelKey={storedCredentials.userPin || ''}
|
|
183
|
+
onResolved={handleOverlayResolved}
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
85
186
|
</View>
|
|
86
187
|
);
|
|
87
188
|
};
|
|
88
189
|
|
|
89
190
|
const styles = StyleSheet.create({
|
|
90
191
|
button: {
|
|
91
|
-
|
|
92
|
-
paddingVertical: 12,
|
|
93
|
-
paddingHorizontal: 24,
|
|
94
|
-
borderRadius: 8,
|
|
192
|
+
flexDirection: 'row',
|
|
95
193
|
alignItems: 'center',
|
|
96
194
|
justifyContent: 'center',
|
|
195
|
+
paddingVertical: 12,
|
|
196
|
+
paddingHorizontal: 16,
|
|
197
|
+
borderRadius: 4,
|
|
97
198
|
},
|
|
98
199
|
pillButton: {
|
|
99
|
-
borderRadius:
|
|
200
|
+
borderRadius: 24,
|
|
100
201
|
},
|
|
101
202
|
strokedButton: {
|
|
102
203
|
backgroundColor: 'transparent',
|
|
@@ -106,15 +207,29 @@ const styles = StyleSheet.create({
|
|
|
106
207
|
swervButton: {
|
|
107
208
|
transform: [{ rotate: '-2deg' }],
|
|
108
209
|
},
|
|
210
|
+
darkButton: {
|
|
211
|
+
backgroundColor: '#000',
|
|
212
|
+
borderColor: '#000',
|
|
213
|
+
},
|
|
214
|
+
lightButton: {
|
|
215
|
+
backgroundColor: '#fff',
|
|
216
|
+
borderColor: '#000',
|
|
217
|
+
},
|
|
218
|
+
disabledButton: {
|
|
219
|
+
opacity: 0.6,
|
|
220
|
+
},
|
|
109
221
|
buttonText: {
|
|
110
|
-
color: '#fff',
|
|
111
222
|
fontSize: 16,
|
|
112
223
|
fontWeight: '600',
|
|
224
|
+
textAlign: 'center',
|
|
225
|
+
},
|
|
226
|
+
lightText: {
|
|
227
|
+
color: '#fff',
|
|
113
228
|
},
|
|
114
|
-
|
|
229
|
+
darkText: {
|
|
115
230
|
color: '#000',
|
|
116
231
|
},
|
|
117
|
-
|
|
118
|
-
|
|
232
|
+
disabledText: {
|
|
233
|
+
opacity: 0.7,
|
|
119
234
|
},
|
|
120
235
|
});
|
|
@@ -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,6 +7,7 @@ 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
13
|
// import DeviceInfo from 'react-native-device-info'; // Comment out device info import
|
|
@@ -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 =
|
|
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 () => {
|
|
@@ -107,14 +114,19 @@ export const Overlay: React.FC<OverlayProps> = ({
|
|
|
107
114
|
return (
|
|
108
115
|
<BottomSheetModal
|
|
109
116
|
ref={bottomSheetRef}
|
|
110
|
-
snapPoints={
|
|
117
|
+
snapPoints={snapPoints}
|
|
118
|
+
handleIndicatorStyle={styles.handleIndicator}
|
|
111
119
|
backdropComponent={(props) => (
|
|
112
120
|
<BottomSheetBackdrop
|
|
113
121
|
{...props}
|
|
114
122
|
appearsOnIndex={0}
|
|
115
123
|
disappearsOnIndex={-1}
|
|
124
|
+
opacity={0.7}
|
|
116
125
|
/>
|
|
117
126
|
)}
|
|
127
|
+
enablePanDownToClose={true}
|
|
128
|
+
keyboardBehavior="interactive"
|
|
129
|
+
keyboardBlurBehavior="restore"
|
|
118
130
|
>
|
|
119
131
|
<View style={styles.container}>
|
|
120
132
|
<View style={styles.header}>
|
|
@@ -179,12 +191,18 @@ export const Overlay: React.FC<OverlayProps> = ({
|
|
|
179
191
|
);
|
|
180
192
|
};
|
|
181
193
|
|
|
194
|
+
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
195
|
+
|
|
182
196
|
const styles = StyleSheet.create({
|
|
183
197
|
container: {
|
|
184
198
|
flex: 1,
|
|
185
199
|
backgroundColor: COLORS.white,
|
|
186
|
-
|
|
187
|
-
|
|
200
|
+
width: SCREEN_WIDTH,
|
|
201
|
+
},
|
|
202
|
+
handleIndicator: {
|
|
203
|
+
backgroundColor: '#999',
|
|
204
|
+
width: 40,
|
|
205
|
+
height: 5,
|
|
188
206
|
},
|
|
189
207
|
header: {
|
|
190
208
|
flexDirection: 'row',
|
|
@@ -207,7 +225,7 @@ const styles = StyleSheet.create({
|
|
|
207
225
|
},
|
|
208
226
|
username: {
|
|
209
227
|
fontSize: 18,
|
|
210
|
-
color: COLORS.
|
|
228
|
+
color: COLORS.white,
|
|
211
229
|
},
|
|
212
230
|
content: {
|
|
213
231
|
flex: 1,
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Onairos React Native SDK
|
|
3
|
+
* A React Native implementation for Onairos personalized data integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Main components
|
|
7
|
+
export { Onairos } from './components/Onairos';
|
|
2
8
|
export { OnairosButton } from './components/OnairosButton';
|
|
9
|
+
export { Overlay as OnairosOverlay } from './components/Overlay';
|
|
3
10
|
export { UniversalOnboarding } from './components/UniversalOnboarding';
|
|
4
|
-
export { Overlay } from './components/Overlay';
|
|
5
11
|
|
|
6
12
|
// Screen Components
|
|
7
13
|
export { ConnectorScreen } from './components/screens/ConnectorScreen';
|
|
@@ -15,8 +21,8 @@ export { OnboardingHeader } from './components/onboarding/OnboardingHeader';
|
|
|
15
21
|
export { PinInput } from './components/onboarding/PinInput';
|
|
16
22
|
|
|
17
23
|
// Hooks
|
|
18
|
-
export { useConnections } from './hooks/useConnections';
|
|
19
24
|
export { useCredentials } from './hooks/useCredentials';
|
|
25
|
+
export { useConnections } from './hooks/useConnections';
|
|
20
26
|
|
|
21
27
|
// Utilities
|
|
22
28
|
export {
|
|
@@ -63,8 +69,10 @@ export {
|
|
|
63
69
|
// Types
|
|
64
70
|
export type {
|
|
65
71
|
OnairosButtonProps,
|
|
72
|
+
DataTier,
|
|
66
73
|
UniversalOnboardingProps,
|
|
67
74
|
ConnectionStatus,
|
|
75
|
+
OnairosCredentials,
|
|
68
76
|
PlatformListProps,
|
|
69
77
|
PinInputProps,
|
|
70
78
|
TrainingModalProps,
|
|
@@ -74,9 +82,15 @@ export type {
|
|
|
74
82
|
ApiResponse,
|
|
75
83
|
} from './types';
|
|
76
84
|
|
|
77
|
-
export type {
|
|
85
|
+
export type { StorageOptions } from './utils/secureStorage';
|
|
78
86
|
export type { OAuthConfig } from './services/oauthService';
|
|
79
87
|
export type { ApiErrorType, ApiError } from './utils/onairosApi';
|
|
80
88
|
|
|
81
89
|
// Constants
|
|
82
|
-
export { COLORS, PLATFORMS, API_ENDPOINTS, STORAGE_KEYS, PIN_REQUIREMENTS, DEEP_LINK_CONFIG } from './constants';
|
|
90
|
+
export { COLORS, PLATFORMS, API_ENDPOINTS, STORAGE_KEYS, PIN_REQUIREMENTS, DEEP_LINK_CONFIG } from './constants';
|
|
91
|
+
|
|
92
|
+
// API and Services
|
|
93
|
+
export { onairosApi } from './api';
|
|
94
|
+
export { OAuthService } from './services/OAuthService';
|
|
95
|
+
export * from './utils/secureStorage';
|
|
96
|
+
export * from './utils/encryption';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Linking, Platform } from 'react-native';
|
|
2
2
|
import { updateCredentials, OnairosCredentials } from '../utils/secureStorage';
|
|
3
3
|
import { sha256 } from '../utils/crypto';
|
|
4
|
+
import { onairosApi } from '../api';
|
|
4
5
|
|
|
5
6
|
// Define OAuth configuration types
|
|
6
7
|
export interface OAuthConfig {
|
|
@@ -358,3 +359,176 @@ export const storePlatformConnection = async (
|
|
|
358
359
|
return false;
|
|
359
360
|
}
|
|
360
361
|
};
|
|
362
|
+
|
|
363
|
+
export interface AuthorizationData {
|
|
364
|
+
accountName: string;
|
|
365
|
+
authUrl: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export interface PlatformConnectionResult {
|
|
369
|
+
success: boolean;
|
|
370
|
+
userName?: string;
|
|
371
|
+
error?: string;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Service for handling OAuth connections to various platforms
|
|
376
|
+
*/
|
|
377
|
+
export const OAuthService = {
|
|
378
|
+
// Base API URL
|
|
379
|
+
_apiBaseUrl: 'https://api2.onairos.uk',
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Connect to a specific platform using OAuth
|
|
383
|
+
* @param platform The platform to connect to (e.g., 'instagram', 'youtube')
|
|
384
|
+
* @returns A promise that resolves to a connection result
|
|
385
|
+
*/
|
|
386
|
+
connectPlatform: async (platform: string): Promise<PlatformConnectionResult> => {
|
|
387
|
+
try {
|
|
388
|
+
console.log(`[OAuth] Initiating connection to ${platform}`);
|
|
389
|
+
|
|
390
|
+
// Get authorization data from API
|
|
391
|
+
const authData = await OAuthService._getAuthorizationData(platform);
|
|
392
|
+
|
|
393
|
+
if (!authData || !authData.accountName) {
|
|
394
|
+
throw new Error(`Failed to get authorization data for ${platform}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Launch the OAuth flow in a WebView
|
|
398
|
+
const success = await OAuthService._launchOAuthFlow(
|
|
399
|
+
platform,
|
|
400
|
+
`${OAuthService._apiBaseUrl}/${authData.accountName}/authorize`,
|
|
401
|
+
`onairos://${platform.toLowerCase()}/callback`
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
success,
|
|
406
|
+
userName: success ? `User_${platform}` : undefined,
|
|
407
|
+
error: success ? undefined : `Failed to connect to ${platform}`
|
|
408
|
+
};
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error(`${platform} connection error:`, error);
|
|
411
|
+
return {
|
|
412
|
+
success: false,
|
|
413
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Get authorization data for a platform from the API
|
|
420
|
+
* @param platform The platform to get authorization data for
|
|
421
|
+
* @returns Authorization data for the platform
|
|
422
|
+
*/
|
|
423
|
+
_getAuthorizationData: async (platform: string): Promise<AuthorizationData> => {
|
|
424
|
+
try {
|
|
425
|
+
// For testing, we can use a mock app ID
|
|
426
|
+
let appId = 'com.onairos.mock';
|
|
427
|
+
|
|
428
|
+
// In real implementation, we would get this from the app's package info
|
|
429
|
+
try {
|
|
430
|
+
// This would normally use react-native-device-info or similar
|
|
431
|
+
// appId = await DeviceInfo.getBundleId();
|
|
432
|
+
} catch (e) {
|
|
433
|
+
console.warn('Failed to get app identifier:', e);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const response = await onairosApi.post('getOAuthData', {
|
|
437
|
+
platform,
|
|
438
|
+
appId: appId,
|
|
439
|
+
redirectUri: `onairos://${platform.toLowerCase()}/callback`,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
if (response && response.accountName) {
|
|
443
|
+
return {
|
|
444
|
+
accountName: response.accountName,
|
|
445
|
+
authUrl: `${OAuthService._apiBaseUrl}/${response.accountName}/authorize`,
|
|
446
|
+
};
|
|
447
|
+
} else {
|
|
448
|
+
throw new Error('Invalid response from getOAuthData');
|
|
449
|
+
}
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.error('Error getting authorization data:', error);
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Launch the OAuth flow for a platform
|
|
458
|
+
* @param platform The platform to launch the OAuth flow for
|
|
459
|
+
* @param authUrl The URL to authorize with
|
|
460
|
+
* @param callbackUrlPattern The URL pattern to expect as a callback
|
|
461
|
+
* @returns A promise that resolves to true if the connection was successful
|
|
462
|
+
*/
|
|
463
|
+
_launchOAuthFlow: async (
|
|
464
|
+
platform: string,
|
|
465
|
+
authUrl: string,
|
|
466
|
+
callbackUrlPattern: string
|
|
467
|
+
): Promise<boolean> => {
|
|
468
|
+
try {
|
|
469
|
+
console.log(`[OAuth] Opening URL for ${platform}: ${authUrl}`);
|
|
470
|
+
|
|
471
|
+
// For now, we'll use a simpler approach just to mock the flow
|
|
472
|
+
// In a real implementation, this would open a WebView in a modal
|
|
473
|
+
// and handle the OAuth callback
|
|
474
|
+
|
|
475
|
+
// Check if we can open the URL
|
|
476
|
+
const canOpen = await Linking.canOpenURL(authUrl);
|
|
477
|
+
|
|
478
|
+
if (!canOpen) {
|
|
479
|
+
throw new Error(`Cannot open URL: ${authUrl}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// We'll simulate a successful connection after a delay
|
|
483
|
+
// In a real app, this would be handled by the WebView navigation
|
|
484
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
485
|
+
|
|
486
|
+
// Return success
|
|
487
|
+
return true;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error(`Error launching OAuth flow for ${platform}:`, error);
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Handle an OAuth callback URL
|
|
496
|
+
* @param url The callback URL to handle
|
|
497
|
+
* @returns The result of processing the callback
|
|
498
|
+
*/
|
|
499
|
+
handleCallback: async (url: string): Promise<PlatformConnectionResult> => {
|
|
500
|
+
try {
|
|
501
|
+
console.log(`[OAuth] Handling callback URL: ${url}`);
|
|
502
|
+
|
|
503
|
+
// Extract the platform and parameters from the URL
|
|
504
|
+
const urlParts = url.split('/');
|
|
505
|
+
const platform = urlParts[2]; // Assuming format is onairos://platform/callback
|
|
506
|
+
|
|
507
|
+
// Extract query parameters
|
|
508
|
+
const params = new URLSearchParams(url.split('?')[1] || '');
|
|
509
|
+
const code = params.get('code');
|
|
510
|
+
|
|
511
|
+
if (!code) {
|
|
512
|
+
return {
|
|
513
|
+
success: false,
|
|
514
|
+
error: 'No authorization code found in callback URL'
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// In a real implementation, we would send the code to the API
|
|
519
|
+
// to get an access token
|
|
520
|
+
|
|
521
|
+
// Simulate a successful connection
|
|
522
|
+
return {
|
|
523
|
+
success: true,
|
|
524
|
+
userName: `User_${platform}`
|
|
525
|
+
};
|
|
526
|
+
} catch (error) {
|
|
527
|
+
console.error('Error handling OAuth callback:', error);
|
|
528
|
+
return {
|
|
529
|
+
success: false,
|
|
530
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
package/src/types/index.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface OnairosButtonProps {
|
|
|
19
19
|
hasStroke?: boolean;
|
|
20
20
|
enabled?: boolean;
|
|
21
21
|
buttonForm?: 'default' | 'login' | 'signup';
|
|
22
|
-
onRejection?: () => void;
|
|
22
|
+
onRejection?: (error?: string) => void;
|
|
23
23
|
onResolved?: (apiUrl: string, token: string, userData: any) => void;
|
|
24
24
|
preCheck?: () => Promise<boolean>;
|
|
25
25
|
color?: string;
|
|
@@ -81,13 +81,31 @@ export interface OAuthWebViewProps {
|
|
|
81
81
|
onClose: () => void;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export interface
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
84
|
+
export interface OnairosCredentials {
|
|
85
|
+
username: string;
|
|
86
|
+
accessToken?: string;
|
|
87
|
+
refreshToken?: string;
|
|
88
|
+
userPin?: string;
|
|
89
|
+
platforms?: {
|
|
90
|
+
instagram?: { token: string; username: string };
|
|
91
|
+
youtube?: { token: string; username: string };
|
|
92
|
+
pinterest?: { token: string; username: string };
|
|
93
|
+
reddit?: { token: string; username: string };
|
|
90
94
|
};
|
|
95
|
+
createdAt: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface OverlayProps {
|
|
99
|
+
data: {
|
|
100
|
+
[key: string]: {
|
|
101
|
+
type: string;
|
|
102
|
+
descriptions: string;
|
|
103
|
+
reward: string;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
username: string;
|
|
107
|
+
modelKey: string;
|
|
108
|
+
onResolved: (apiUrl: string, accessToken: string, loginDetails: any) => void;
|
|
91
109
|
}
|
|
92
110
|
|
|
93
111
|
export interface PlatformConfig {
|
|
@@ -97,6 +115,19 @@ export interface PlatformConfig {
|
|
|
97
115
|
description?: string;
|
|
98
116
|
}
|
|
99
117
|
|
|
118
|
+
export interface BiometricOptions {
|
|
119
|
+
title: string;
|
|
120
|
+
subtitle?: string;
|
|
121
|
+
description?: string;
|
|
122
|
+
cancelText?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface PinRequirements {
|
|
126
|
+
minLength: number;
|
|
127
|
+
requireSpecialChar: boolean;
|
|
128
|
+
requireNumber: boolean;
|
|
129
|
+
}
|
|
130
|
+
|
|
100
131
|
export interface ApiResponse<T> {
|
|
101
132
|
success: boolean;
|
|
102
133
|
data?: T;
|