@onairos/react-native 3.0.21 → 3.0.24
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/components/OnairosButton.js +62 -7
- package/lib/commonjs/components/OnairosButton.js.map +1 -1
- package/lib/commonjs/components/UniversalOnboarding.js +155 -7
- package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
- package/lib/commonjs/constants/index.js +2 -1
- package/lib/commonjs/constants/index.js.map +1 -1
- package/lib/commonjs/services/platformAuthService.js +180 -0
- package/lib/commonjs/services/platformAuthService.js.map +1 -0
- package/lib/module/components/OnairosButton.js +62 -7
- package/lib/module/components/OnairosButton.js.map +1 -1
- package/lib/module/components/UniversalOnboarding.js +156 -8
- package/lib/module/components/UniversalOnboarding.js.map +1 -1
- package/lib/module/constants/index.js +2 -1
- package/lib/module/constants/index.js.map +1 -1
- package/lib/module/services/platformAuthService.js +167 -0
- package/lib/module/services/platformAuthService.js.map +1 -0
- package/lib/typescript/components/OnairosButton.d.ts.map +1 -1
- package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
- package/lib/typescript/constants/index.d.ts +1 -0
- package/lib/typescript/constants/index.d.ts.map +1 -1
- package/lib/typescript/services/platformAuthService.d.ts +38 -0
- package/lib/typescript/services/platformAuthService.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +6 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/components/OnairosButton.tsx +365 -305
- package/src/components/UniversalOnboarding.tsx +153 -9
- package/src/constants/index.ts +1 -0
- package/src/services/platformAuthService.ts +175 -0
- package/src/types.ts +6 -0
|
@@ -14,13 +14,16 @@ import {
|
|
|
14
14
|
ScrollView,
|
|
15
15
|
Image,
|
|
16
16
|
Switch,
|
|
17
|
+
Linking,
|
|
17
18
|
} from 'react-native';
|
|
18
19
|
import Icon from 'react-native-vector-icons/MaterialIcons';
|
|
19
20
|
import { PlatformList } from './PlatformList';
|
|
20
21
|
import { PinInput } from './PinInput';
|
|
21
22
|
import { TrainingModal } from './TrainingModal';
|
|
23
|
+
import { OAuthWebView } from './onboarding/OAuthWebView';
|
|
22
24
|
import { useConnections } from '../hooks/useConnections';
|
|
23
|
-
import { COLORS } from '../constants';
|
|
25
|
+
import { COLORS, DEEP_LINK_CONFIG } from '../constants';
|
|
26
|
+
import { initiateOAuth, initiateNativeAuth, hasNativeSDK, isOAuthCallback } from '../services/platformAuthService';
|
|
24
27
|
import type { UniversalOnboardingProps, ConnectionStatus } from '../types';
|
|
25
28
|
|
|
26
29
|
const { height, width } = Dimensions.get('window');
|
|
@@ -37,7 +40,7 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
37
40
|
test = false,
|
|
38
41
|
preferredPlatform,
|
|
39
42
|
}) => {
|
|
40
|
-
const [step, setStep] = useState<'connect' | 'pin' | 'training'>('connect');
|
|
43
|
+
const [step, setStep] = useState<'connect' | 'pin' | 'training' | 'oauth'>('connect');
|
|
41
44
|
const [connections, setConnections] = useState<ConnectionStatus>({});
|
|
42
45
|
const [pin, setPin] = useState<string>('');
|
|
43
46
|
const [selectedTier, setSelectedTier] = useState<'Small' | 'Medium' | 'Large'>('Medium');
|
|
@@ -47,6 +50,9 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
47
50
|
}>({ progress: 0, eta: '' });
|
|
48
51
|
const [slideAnim] = useState(new Animated.Value(height));
|
|
49
52
|
const [platformToggles, setPlatformToggles] = useState<{[key: string]: boolean}>({});
|
|
53
|
+
const [oauthUrl, setOauthUrl] = useState<string>('');
|
|
54
|
+
const [currentPlatform, setCurrentPlatform] = useState<string>('');
|
|
55
|
+
const [username, setUsername] = useState<string>(`user_${Math.floor(Math.random() * 10000)}`);
|
|
50
56
|
|
|
51
57
|
const platforms = [
|
|
52
58
|
{ id: 'instagram', name: 'Instagram', icon: require('../assets/images/instagram.png') },
|
|
@@ -73,6 +79,27 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
73
79
|
bounciness: 0,
|
|
74
80
|
}).start();
|
|
75
81
|
|
|
82
|
+
// Set up deep link listener for OAuth callbacks
|
|
83
|
+
// Using the subscription pattern for React Native's Linking API
|
|
84
|
+
const subscription = Linking.addListener('url', ({ url }) => {
|
|
85
|
+
if (isOAuthCallback(url)) {
|
|
86
|
+
handleOAuthCallback(url);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Check for initial URL (app was opened via deep link)
|
|
91
|
+
Linking.getInitialURL().then(initialUrl => {
|
|
92
|
+
if (initialUrl && isOAuthCallback(initialUrl)) {
|
|
93
|
+
handleOAuthCallback(initialUrl);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Return cleanup function
|
|
98
|
+
return () => {
|
|
99
|
+
// Remove event listener using the subscription
|
|
100
|
+
subscription.remove();
|
|
101
|
+
};
|
|
102
|
+
|
|
76
103
|
// Initialize platform toggles
|
|
77
104
|
const initialToggles: { [key: string]: boolean } = {};
|
|
78
105
|
platforms.forEach((platform) => {
|
|
@@ -127,12 +154,116 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
127
154
|
setConnections(status);
|
|
128
155
|
}, [getConnectionStatus]);
|
|
129
156
|
|
|
130
|
-
const togglePlatform = useCallback((platformId: string) => {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
157
|
+
const togglePlatform = useCallback(async (platformId: string) => {
|
|
158
|
+
// If toggling on, initiate the OAuth flow for the platform
|
|
159
|
+
if (!platformToggles[platformId]) {
|
|
160
|
+
try {
|
|
161
|
+
// Special case for Instagram which uses the existing flow
|
|
162
|
+
if (platformId === 'instagram') {
|
|
163
|
+
setPlatformToggles(prev => ({
|
|
164
|
+
...prev,
|
|
165
|
+
[platformId]: !prev[platformId]
|
|
166
|
+
}));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if the platform has a native SDK
|
|
171
|
+
if (hasNativeSDK(platformId)) {
|
|
172
|
+
// Use native SDK authentication
|
|
173
|
+
setCurrentPlatform(platformId);
|
|
174
|
+
const success = await initiateNativeAuth(platformId);
|
|
175
|
+
|
|
176
|
+
if (success) {
|
|
177
|
+
// Update connections state
|
|
178
|
+
setConnections(prev => ({
|
|
179
|
+
...prev,
|
|
180
|
+
[platformId]: { userName: username, connected: true }
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
// Update platform toggles
|
|
184
|
+
setPlatformToggles(prev => ({
|
|
185
|
+
...prev,
|
|
186
|
+
[platformId]: true
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// For other platforms, use the web OAuth flow
|
|
193
|
+
setCurrentPlatform(platformId);
|
|
194
|
+
const oauthUrl = await initiateOAuth(platformId, username);
|
|
195
|
+
|
|
196
|
+
// If oauthUrl is null, it means we should use native SDK (which should have been caught above)
|
|
197
|
+
if (oauthUrl) {
|
|
198
|
+
setOauthUrl(oauthUrl);
|
|
199
|
+
setStep('oauth');
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`Error initiating OAuth for ${platformId}:`, error);
|
|
203
|
+
// If there's an error, don't toggle the platform
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
// If toggling off, just update the state
|
|
208
|
+
setPlatformToggles(prev => ({
|
|
209
|
+
...prev,
|
|
210
|
+
[platformId]: !prev[platformId]
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
}, [platformToggles, username]);
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Handles OAuth callback URLs
|
|
217
|
+
*/
|
|
218
|
+
const handleOAuthCallback = useCallback((url: string) => {
|
|
219
|
+
try {
|
|
220
|
+
// Extract the authorization code from the URL
|
|
221
|
+
const parsedUrl = new URL(url);
|
|
222
|
+
const code = parsedUrl.searchParams.get('code');
|
|
223
|
+
const platform = parsedUrl.searchParams.get('platform') || currentPlatform;
|
|
224
|
+
|
|
225
|
+
if (code && platform) {
|
|
226
|
+
// Update connections state
|
|
227
|
+
setConnections(prev => ({
|
|
228
|
+
...prev,
|
|
229
|
+
[platform]: { userName: username, connected: true }
|
|
230
|
+
}));
|
|
231
|
+
|
|
232
|
+
// Update platform toggles
|
|
233
|
+
setPlatformToggles(prev => ({
|
|
234
|
+
...prev,
|
|
235
|
+
[platform]: true
|
|
236
|
+
}));
|
|
237
|
+
|
|
238
|
+
// Return to the connect step
|
|
239
|
+
setStep('connect');
|
|
240
|
+
}
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error('Error handling OAuth callback:', error);
|
|
243
|
+
}
|
|
244
|
+
}, [currentPlatform, username]);
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Handles completion of the OAuth flow
|
|
248
|
+
*/
|
|
249
|
+
const handleOAuthSuccess = useCallback((code: string) => {
|
|
250
|
+
if (currentPlatform) {
|
|
251
|
+
// Update connections state
|
|
252
|
+
setConnections(prev => ({
|
|
253
|
+
...prev,
|
|
254
|
+
[currentPlatform]: { userName: username, connected: true }
|
|
255
|
+
}));
|
|
256
|
+
|
|
257
|
+
// Update platform toggles
|
|
258
|
+
setPlatformToggles(prev => ({
|
|
259
|
+
...prev,
|
|
260
|
+
[currentPlatform]: true
|
|
261
|
+
}));
|
|
262
|
+
|
|
263
|
+
// Return to the connect step
|
|
264
|
+
setStep('connect');
|
|
265
|
+
}
|
|
266
|
+
}, [currentPlatform, username]);
|
|
136
267
|
|
|
137
268
|
const handlePinSubmit = useCallback(async (userPin: string) => {
|
|
138
269
|
setPin(userPin);
|
|
@@ -294,7 +425,20 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
|
|
|
294
425
|
});
|
|
295
426
|
}}
|
|
296
427
|
modelKey="onairosTrainingModel"
|
|
297
|
-
username=
|
|
428
|
+
username={username}
|
|
429
|
+
/>
|
|
430
|
+
)}
|
|
431
|
+
|
|
432
|
+
{step === 'oauth' && oauthUrl && (
|
|
433
|
+
<OAuthWebView
|
|
434
|
+
url={oauthUrl}
|
|
435
|
+
platform={currentPlatform}
|
|
436
|
+
onClose={() => {
|
|
437
|
+
setStep('connect');
|
|
438
|
+
setOauthUrl('');
|
|
439
|
+
}}
|
|
440
|
+
onSuccess={handleOAuthSuccess}
|
|
441
|
+
onComplete={() => setStep('connect')}
|
|
298
442
|
/>
|
|
299
443
|
)}
|
|
300
444
|
</SafeAreaView>
|
package/src/constants/index.ts
CHANGED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Platform, Linking } from 'react-native';
|
|
2
|
+
import { DEEP_LINK_CONFIG } from '../constants';
|
|
3
|
+
|
|
4
|
+
// Define types for platform auth configuration
|
|
5
|
+
interface PlatformAuthConfig {
|
|
6
|
+
hasNativeSDK: boolean;
|
|
7
|
+
nativeSDKPackage?: string;
|
|
8
|
+
authEndpoint: string;
|
|
9
|
+
color: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Configuration for each platform's authentication
|
|
13
|
+
const PLATFORM_AUTH_CONFIG: Record<string, PlatformAuthConfig> = {
|
|
14
|
+
instagram: {
|
|
15
|
+
hasNativeSDK: false, // Instagram doesn't have a public OAuth SDK for React Native
|
|
16
|
+
authEndpoint: 'https://api2.onairos.uk/instagram/authorize',
|
|
17
|
+
color: '#E1306C',
|
|
18
|
+
},
|
|
19
|
+
youtube: {
|
|
20
|
+
hasNativeSDK: true, // YouTube uses Google Sign-In SDK
|
|
21
|
+
nativeSDKPackage: '@react-native-google-signin/google-signin',
|
|
22
|
+
authEndpoint: 'https://api2.onairos.uk/youtube/authorize',
|
|
23
|
+
color: '#FF0000',
|
|
24
|
+
},
|
|
25
|
+
reddit: {
|
|
26
|
+
hasNativeSDK: false,
|
|
27
|
+
authEndpoint: 'https://api2.onairos.uk/reddit/authorize',
|
|
28
|
+
color: '#FF4500',
|
|
29
|
+
},
|
|
30
|
+
pinterest: {
|
|
31
|
+
hasNativeSDK: false,
|
|
32
|
+
authEndpoint: 'https://api2.onairos.uk/pinterest/authorize',
|
|
33
|
+
color: '#E60023',
|
|
34
|
+
},
|
|
35
|
+
email: {
|
|
36
|
+
hasNativeSDK: false,
|
|
37
|
+
authEndpoint: 'https://api2.onairos.uk/email/authorize',
|
|
38
|
+
color: '#4285F4',
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks if a native SDK is available for the given platform
|
|
44
|
+
*/
|
|
45
|
+
export const hasNativeSDK = (platform: string): boolean => {
|
|
46
|
+
const config = PLATFORM_AUTH_CONFIG[platform];
|
|
47
|
+
return config?.hasNativeSDK || false;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Gets the auth endpoint URL for a platform
|
|
52
|
+
*/
|
|
53
|
+
export const getAuthEndpoint = (platform: string): string => {
|
|
54
|
+
const config = PLATFORM_AUTH_CONFIG[platform];
|
|
55
|
+
return config?.authEndpoint || '';
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Gets the color associated with a platform
|
|
60
|
+
*/
|
|
61
|
+
export const getPlatformColor = (platform: string): string => {
|
|
62
|
+
const config = PLATFORM_AUTH_CONFIG[platform];
|
|
63
|
+
return config?.color || '#000000';
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Initiates the OAuth flow for a platform
|
|
68
|
+
* @param platform The platform to authenticate with
|
|
69
|
+
* @param username The username to associate with the authentication
|
|
70
|
+
* @returns A Promise that resolves to the OAuth URL to open in a WebView or null if using native SDK
|
|
71
|
+
*/
|
|
72
|
+
export const initiateOAuth = async (platform: string, username: string): Promise<string | null> => {
|
|
73
|
+
try {
|
|
74
|
+
// Check if the platform is supported
|
|
75
|
+
if (!PLATFORM_AUTH_CONFIG[platform]) {
|
|
76
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if platform has a native SDK
|
|
80
|
+
if (PLATFORM_AUTH_CONFIG[platform].hasNativeSDK) {
|
|
81
|
+
// Return null to indicate that we should use the native SDK
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Prepare the request body
|
|
86
|
+
const jsonData = {
|
|
87
|
+
session: {
|
|
88
|
+
username: username || 'anonymous',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Make the request to get the OAuth URL
|
|
93
|
+
const response = await fetch(PLATFORM_AUTH_CONFIG[platform].authEndpoint, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Content-Type': 'application/json',
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify(jsonData),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Parse the response
|
|
102
|
+
const data = await response.json();
|
|
103
|
+
|
|
104
|
+
// Check if the response contains the OAuth URL
|
|
105
|
+
if (platform === 'reddit' && data.redditURL) {
|
|
106
|
+
return data.redditURL;
|
|
107
|
+
} else if (platform === 'pinterest' && data.pinterestURL) {
|
|
108
|
+
return data.pinterestURL;
|
|
109
|
+
} else if (platform === 'youtube' && data.youtubeURL) {
|
|
110
|
+
return data.youtubeURL;
|
|
111
|
+
} else if (platform === 'email' && data.emailURL) {
|
|
112
|
+
return data.emailURL;
|
|
113
|
+
} else if (data.url) {
|
|
114
|
+
return data.url;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
throw new Error(`No OAuth URL found in response for ${platform}`);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.error(`Error initiating OAuth for ${platform}:`, error);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Initiates the native SDK authentication flow for a platform
|
|
126
|
+
* @param platform The platform to authenticate with
|
|
127
|
+
* @returns A Promise that resolves to the authentication result
|
|
128
|
+
*/
|
|
129
|
+
export const initiateNativeAuth = async (platform: string): Promise<boolean> => {
|
|
130
|
+
try {
|
|
131
|
+
// Currently only YouTube (Google Sign-In) is supported
|
|
132
|
+
if (platform === 'youtube') {
|
|
133
|
+
// This is a placeholder for the actual implementation
|
|
134
|
+
// In a real implementation, you would import and use the Google Sign-In SDK
|
|
135
|
+
console.log('Initiating native Google Sign-In for YouTube');
|
|
136
|
+
|
|
137
|
+
// Simulate a successful authentication
|
|
138
|
+
// In a real implementation, this would be the result of the Google Sign-In flow
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
throw new Error(`Native authentication not supported for ${platform}`);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(`Error initiating native auth for ${platform}:`, error);
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Handles the OAuth callback
|
|
151
|
+
* @param url The callback URL
|
|
152
|
+
* @returns The authorization code extracted from the URL
|
|
153
|
+
*/
|
|
154
|
+
export const handleOAuthCallback = (url: string): string | null => {
|
|
155
|
+
try {
|
|
156
|
+
// Parse the URL
|
|
157
|
+
const parsedUrl = new URL(url);
|
|
158
|
+
|
|
159
|
+
// Extract the authorization code
|
|
160
|
+
return parsedUrl.searchParams.get('code');
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('Error handling OAuth callback:', error);
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Checks if a URL is an OAuth callback
|
|
169
|
+
* @param url The URL to check
|
|
170
|
+
* @returns True if the URL is an OAuth callback
|
|
171
|
+
*/
|
|
172
|
+
export const isOAuthCallback = (url: string): boolean => {
|
|
173
|
+
// Check if the URL starts with our redirect URI
|
|
174
|
+
return url.startsWith('onairosanime://auth/');
|
|
175
|
+
};
|
package/src/types.ts
CHANGED
|
@@ -54,6 +54,12 @@ export interface OnairosButtonProps {
|
|
|
54
54
|
darkMode?: boolean;
|
|
55
55
|
preferredPlatform?: string;
|
|
56
56
|
testMode?: boolean;
|
|
57
|
+
autoFetch?: boolean;
|
|
58
|
+
inferenceData?: any;
|
|
59
|
+
textLayout?: 'left' | 'right' | 'center';
|
|
60
|
+
textColor?: string;
|
|
61
|
+
proofMode?: boolean;
|
|
62
|
+
webpageName?: string;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
export interface PlatformListProps {
|