@onairos/react-native 3.0.29 → 3.0.31

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 (25) hide show
  1. package/lib/commonjs/components/PinInput.js +1 -1
  2. package/lib/commonjs/components/PinInput.js.map +1 -1
  3. package/lib/commonjs/components/UniversalOnboarding.js +6 -2
  4. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
  5. package/lib/commonjs/components/onboarding/OAuthWebView.js +217 -94
  6. package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -1
  7. package/lib/commonjs/services/platformAuthService.js +48 -6
  8. package/lib/commonjs/services/platformAuthService.js.map +1 -1
  9. package/lib/module/components/PinInput.js +1 -1
  10. package/lib/module/components/PinInput.js.map +1 -1
  11. package/lib/module/components/UniversalOnboarding.js +6 -2
  12. package/lib/module/components/UniversalOnboarding.js.map +1 -1
  13. package/lib/module/components/onboarding/OAuthWebView.js +219 -96
  14. package/lib/module/components/onboarding/OAuthWebView.js.map +1 -1
  15. package/lib/module/services/platformAuthService.js +48 -6
  16. package/lib/module/services/platformAuthService.js.map +1 -1
  17. package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -1
  18. package/lib/typescript/components/onboarding/OAuthWebView.d.ts +7 -1
  19. package/lib/typescript/components/onboarding/OAuthWebView.d.ts.map +1 -1
  20. package/lib/typescript/services/platformAuthService.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/PinInput.tsx +1 -1
  23. package/src/components/UniversalOnboarding.tsx +5 -1
  24. package/src/components/onboarding/OAuthWebView.tsx +254 -116
  25. package/src/services/platformAuthService.ts +45 -5
@@ -1,92 +1,218 @@
1
- import React, { useCallback, useState } from 'react';
2
- import { View, StyleSheet, ActivityIndicator, SafeAreaView, TouchableOpacity } from 'react-native';
3
- import { WebView, WebViewNavigation } from 'react-native-webview';
1
+ import React, { useState, useRef } from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ ActivityIndicator,
8
+ SafeAreaView,
9
+ Dimensions,
10
+ Linking,
11
+ } from 'react-native';
12
+ import { WebView } from 'react-native-webview';
4
13
  import Icon from 'react-native-vector-icons/MaterialIcons';
5
14
  import { COLORS } from '../../constants';
6
- import type { OAuthWebViewProps } from '../../types';
15
+
16
+ const { width, height } = Dimensions.get('window');
17
+
18
+ export interface OAuthWebViewProps {
19
+ url: string;
20
+ onClose: () => void;
21
+ onSuccess: (code: string) => void;
22
+ platform?: string;
23
+ onComplete?: () => void;
24
+ }
7
25
 
8
26
  export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
9
27
  url,
10
- platform,
11
- onComplete,
12
28
  onClose,
13
29
  onSuccess,
30
+ platform = 'platform',
31
+ onComplete,
14
32
  }) => {
15
- const [loading, setLoading] = useState(true);
33
+ const [isLoading, setIsLoading] = useState(true);
34
+ const [error, setError] = useState<string | null>(null);
35
+ const webViewRef = useRef<WebView>(null);
36
+
37
+ console.log('Opening OAuth WebView with URL:', url);
16
38
 
17
- const handleNavigationStateChange = useCallback(
18
- (navState: WebViewNavigation) => {
19
- console.log('Navigation state changed:', navState.url);
39
+ const handleNavigationStateChange = (navState: any) => {
40
+ console.log(`Navigation state changed for ${platform}:`, navState.url);
41
+
42
+ // Check for the final redirect to onairos.uk domain (this means backend callback completed)
43
+ const isFinalRedirect = (
44
+ navState.url.includes('onairos.uk/Home') ||
45
+ navState.url.includes('onairos.uk/home') ||
46
+ navState.url.includes('onairos.uk/success') ||
47
+ navState.url.startsWith('https://onairos.uk/Home')
48
+ );
49
+
50
+ // Platform-specific success patterns
51
+ const platformSuccessPatterns: Record<string, RegExp[]> = {
52
+ reddit: [
53
+ /reddit\.com\/api\/v1\/authorize\?done=true/,
54
+ /reddit\.com\/api\/v1\/authorize\/success/
55
+ ],
56
+ pinterest: [
57
+ /pinterest\.com\/oauth\/success/,
58
+ /pinterest\.com\/oauth\/complete/
59
+ ],
60
+ linkedin: [
61
+ /linkedin\.com\/oauth\/success/,
62
+ /linkedin\.com\/oauth\/complete/,
63
+ /linkedin\.com\/uas\/oauth2\/authorization\/success/
64
+ ],
65
+ email: [/success/],
66
+ instagram: [
67
+ /instagram\.com\/oauth\/authorize\?done=true/,
68
+ /instagram\.com\/oauth\/success/
69
+ ],
70
+ youtube: [
71
+ /accounts\.google\.com\/o\/oauth2\/approval/,
72
+ /youtube\.com\/oauth\/success/
73
+ ]
74
+ };
75
+
76
+ // Check for platform-specific success patterns
77
+ const isPlatformSuccess = platform && platformSuccessPatterns[platform] ?
78
+ platformSuccessPatterns[platform].some(pattern => pattern.test(navState.url)) :
79
+ false;
80
+
81
+ // Check for callback URLs that might contain the authorization code
82
+ const isCallbackUrl = (
83
+ navState.url.includes('/callback') ||
84
+ navState.url.includes('code=') ||
85
+ navState.url.includes('token=') ||
86
+ navState.url.includes('access_token=')
87
+ );
88
+
89
+ // Extract authorization code or token if present
90
+ let authCode = null;
91
+ if (isCallbackUrl) {
92
+ console.log('Detected callback URL with possible code/token');
20
93
 
21
- // Check for the final redirect to onairos.uk/Home
22
- const isFinalRedirect = navState.url.includes('onairos.uk/Home');
94
+ // Try to extract code or token using different patterns
95
+ const codeMatch = navState.url.match(/code=([^&]+)/);
96
+ const tokenMatch = navState.url.match(/(?:token|access_token)=([^&]+)/);
23
97
 
24
- // Check for Instagram-specific success patterns
25
- const isInstagramSuccess = (
26
- platform === 'instagram' &&
27
- (navState.url.includes('instagram.com/oauth/authorize') ||
28
- navState.url.includes('instagram.com/accounts/login') ||
29
- navState.url.includes('code='))
30
- );
31
-
32
- // Check for platform-specific success patterns
33
- const isYouTubeSuccess = (
34
- platform === 'youtube' &&
35
- navState.url.includes('accounts.google.com/o/oauth2/approval')
36
- );
37
-
38
- const isLinkedInSuccess = (
39
- platform === 'linkedin' &&
40
- navState.url.includes('linkedin.com/oauth/v2/authorization/success')
41
- );
98
+ if (codeMatch && codeMatch[1]) {
99
+ authCode = codeMatch[1];
100
+ console.log('OAuth code extracted:', authCode);
101
+ } else if (tokenMatch && tokenMatch[1]) {
102
+ authCode = tokenMatch[1];
103
+ console.log('OAuth token extracted:', authCode);
104
+ }
42
105
 
43
- // Check for callback URLs with authorization codes
44
- const isCallbackUrl = navState.url.includes('/callback') || navState.url.includes('code=');
106
+ // Call onSuccess with the extracted code/token
107
+ if (authCode) {
108
+ onSuccess(authCode);
109
+ }
110
+ }
111
+
112
+ // If we see the final redirect or platform-specific success, close the OAuth window
113
+ if (isFinalRedirect || isPlatformSuccess) {
114
+ console.log(`Detected success for ${platform}`);
45
115
 
46
- // If we detect success, close the window and update connection status
47
- if (isFinalRedirect || isInstagramSuccess || isYouTubeSuccess || isLinkedInSuccess) {
116
+ // If we haven't already extracted a code/token, consider this a generic success
117
+ if (!authCode) {
48
118
  onSuccess('success');
49
- if (onComplete) {
50
- onComplete();
51
- }
52
- return;
53
- } else if (isCallbackUrl) {
54
- // Extract the authorization code
55
- const authCode = extractAuthCode(navState.url);
56
- if (authCode) {
57
- onSuccess(authCode);
58
- if (onComplete) {
59
- onComplete();
60
- }
61
- }
62
- return;
63
119
  }
64
- },
65
- [platform, onComplete, onSuccess]
66
- );
120
+
121
+ // Close the OAuth window
122
+ if (onComplete) {
123
+ console.log('Calling onComplete to close OAuth window');
124
+ onComplete();
125
+ }
126
+ }
127
+ };
67
128
 
68
- const handleLoadEnd = useCallback(() => {
69
- setLoading(false);
70
- }, []);
129
+ const handleLoadEnd = () => {
130
+ setIsLoading(false);
131
+ setError(null);
132
+ };
71
133
 
72
- // Extract auth code from redirect URL
73
- const extractAuthCode = (redirectUrl: string): string => {
74
- try {
75
- // First try to extract code from URL parameters
76
- const codeMatch = redirectUrl.match(/code=([^&]+)/);
77
- if (codeMatch && codeMatch[1]) {
78
- return codeMatch[1];
79
- }
80
-
81
- // If that fails, try parsing as URL
82
- const url = new URL(redirectUrl);
83
- return url.searchParams.get('code') || '';
84
- } catch (error) {
85
- console.error('Error extracting auth code:', error);
86
- return '';
134
+ const handleError = (syntheticEvent: any) => {
135
+ const { nativeEvent } = syntheticEvent;
136
+ console.error('WebView error:', nativeEvent);
137
+ setError('Failed to load OAuth page. Please try again.');
138
+ setIsLoading(false);
139
+ };
140
+
141
+ const handleOpenInBrowser = () => {
142
+ Linking.openURL(url).catch(err => {
143
+ console.error('Failed to open URL in browser:', err);
144
+ });
145
+ };
146
+
147
+ /**
148
+ * Get platform-specific icon
149
+ */
150
+ const getPlatformIcon = (platform: string): string => {
151
+ switch (platform) {
152
+ case 'instagram':
153
+ return 'photo-camera';
154
+ case 'youtube':
155
+ return 'smart-display';
156
+ case 'pinterest':
157
+ return 'push-pin';
158
+ case 'reddit':
159
+ return 'forum';
160
+ case 'email':
161
+ return 'email';
162
+ default:
163
+ return 'link';
164
+ }
165
+ };
166
+
167
+ /**
168
+ * Get platform-specific color
169
+ */
170
+ const getPlatformColor = (platform: string): string => {
171
+ switch (platform) {
172
+ case 'instagram':
173
+ return '#E1306C';
174
+ case 'youtube':
175
+ return '#FF0000';
176
+ case 'pinterest':
177
+ return '#E60023';
178
+ case 'reddit':
179
+ return '#FF4500';
180
+ case 'email':
181
+ return '#4285F4';
182
+ default:
183
+ return COLORS.primary;
87
184
  }
88
185
  };
89
186
 
187
+ if (error) {
188
+ return (
189
+ <SafeAreaView style={styles.container}>
190
+ <View style={styles.header}>
191
+ <TouchableOpacity onPress={onClose} style={styles.closeButton}>
192
+ <Icon name="close" size={24} color="#000" />
193
+ </TouchableOpacity>
194
+ <View style={styles.titleContainer}>
195
+ <Icon
196
+ name={getPlatformIcon(platform)}
197
+ size={20}
198
+ color={getPlatformColor(platform)}
199
+ />
200
+ <Text style={styles.titleText}>{platform.charAt(0).toUpperCase() + platform.slice(1)} OAuth</Text>
201
+ </View>
202
+ </View>
203
+
204
+ <View style={styles.errorContainer}>
205
+ <Icon name="error-outline" size={48} color="#FF6B6B" />
206
+ <Text style={styles.errorTitle}>Connection Error</Text>
207
+ <Text style={styles.errorMessage}>{error}</Text>
208
+ <TouchableOpacity style={styles.retryButton} onPress={handleOpenInBrowser}>
209
+ <Text style={styles.retryButtonText}>Open in Browser</Text>
210
+ </TouchableOpacity>
211
+ </View>
212
+ </SafeAreaView>
213
+ );
214
+ }
215
+
90
216
  return (
91
217
  <SafeAreaView style={styles.container}>
92
218
  <View style={styles.header}>
@@ -95,76 +221,39 @@ export const OAuthWebView: React.FC<OAuthWebViewProps> = ({
95
221
  </TouchableOpacity>
96
222
  <View style={styles.titleContainer}>
97
223
  <Icon
98
- name={getPlatformIcon(platform || 'default')}
224
+ name={getPlatformIcon(platform)}
99
225
  size={20}
100
- color={getPlatformColor(platform || 'default')}
226
+ color={getPlatformColor(platform)}
101
227
  />
228
+ <Text style={styles.titleText}>{platform.charAt(0).toUpperCase() + platform.slice(1)} OAuth</Text>
102
229
  </View>
230
+ <TouchableOpacity onPress={handleOpenInBrowser} style={styles.browserButton}>
231
+ <Icon name="open-in-browser" size={20} color="#666" />
232
+ </TouchableOpacity>
103
233
  </View>
104
234
 
105
235
  <WebView
236
+ ref={webViewRef}
106
237
  source={{ uri: url }}
107
238
  onNavigationStateChange={handleNavigationStateChange}
108
239
  onLoadEnd={handleLoadEnd}
109
- startInLoadingState={true}
240
+ onError={handleError}
110
241
  style={styles.webView}
111
242
  javaScriptEnabled={true}
112
243
  domStorageEnabled={true}
113
- sharedCookiesEnabled={true}
114
- thirdPartyCookiesEnabled={true}
115
- allowsInlineMediaPlayback={true}
116
- mediaPlaybackRequiresUserAction={false}
117
244
  userAgent="Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1"
118
- mixedContentMode="compatibility"
119
- allowsFullscreenVideo={true}
120
- allowsProtectedMedia={true}
121
245
  />
122
246
 
123
- {loading && (
247
+ {isLoading && (
124
248
  <View style={styles.loadingContainer}>
125
249
  <ActivityIndicator size="large" color={COLORS.primary} />
250
+ <Text style={styles.loadingText}>Loading {platform}...</Text>
126
251
  </View>
127
252
  )}
128
253
  </SafeAreaView>
129
254
  );
130
255
  };
131
256
 
132
- /**
133
- * Get platform-specific icon
134
- */
135
- const getPlatformIcon = (platform: string): string => {
136
- switch (platform) {
137
- case 'instagram':
138
- return 'photo-camera';
139
- case 'youtube':
140
- return 'smart-display';
141
- case 'pinterest':
142
- return 'push-pin';
143
- case 'reddit':
144
- return 'forum';
145
- default:
146
- return 'link';
147
- }
148
- };
149
-
150
- /**
151
- * Get platform-specific color
152
- */
153
- const getPlatformColor = (platform: string): string => {
154
- switch (platform) {
155
- case 'instagram':
156
- return '#E1306C';
157
- case 'youtube':
158
- return '#FF0000';
159
- case 'pinterest':
160
- return '#E60023';
161
- case 'reddit':
162
- return '#FF4500';
163
- default:
164
- return COLORS.primary;
165
- }
166
- };
167
-
168
257
  const styles = StyleSheet.create({
169
258
  container: {
170
259
  flex: 1,
@@ -181,7 +270,15 @@ const styles = StyleSheet.create({
181
270
  },
182
271
  titleContainer: {
183
272
  flex: 1,
273
+ flexDirection: 'row',
184
274
  alignItems: 'center',
275
+ justifyContent: 'center',
276
+ },
277
+ titleText: {
278
+ marginLeft: 8,
279
+ fontSize: 16,
280
+ fontWeight: '500',
281
+ color: '#000',
185
282
  },
186
283
  closeButton: {
187
284
  padding: 8,
@@ -189,6 +286,12 @@ const styles = StyleSheet.create({
189
286
  left: 16,
190
287
  zIndex: 10,
191
288
  },
289
+ browserButton: {
290
+ padding: 8,
291
+ position: 'absolute',
292
+ right: 16,
293
+ zIndex: 10,
294
+ },
192
295
  webView: {
193
296
  flex: 1,
194
297
  },
@@ -198,4 +301,39 @@ const styles = StyleSheet.create({
198
301
  alignItems: 'center',
199
302
  justifyContent: 'center',
200
303
  },
304
+ loadingText: {
305
+ marginTop: 10,
306
+ fontSize: 16,
307
+ color: '#666',
308
+ },
309
+ errorContainer: {
310
+ flex: 1,
311
+ alignItems: 'center',
312
+ justifyContent: 'center',
313
+ padding: 20,
314
+ },
315
+ errorTitle: {
316
+ fontSize: 18,
317
+ fontWeight: '600',
318
+ color: '#333',
319
+ marginTop: 16,
320
+ marginBottom: 8,
321
+ },
322
+ errorMessage: {
323
+ fontSize: 14,
324
+ color: '#666',
325
+ textAlign: 'center',
326
+ marginBottom: 20,
327
+ },
328
+ retryButton: {
329
+ backgroundColor: COLORS.primary,
330
+ paddingHorizontal: 24,
331
+ paddingVertical: 12,
332
+ borderRadius: 8,
333
+ },
334
+ retryButtonText: {
335
+ color: '#fff',
336
+ fontSize: 16,
337
+ fontWeight: '600',
338
+ },
201
339
  });
@@ -166,13 +166,53 @@ export const initiateNativeAuth = async (platform: string): Promise<boolean> =>
166
166
  try {
167
167
  // Currently only YouTube (Google Sign-In) is supported
168
168
  if (platform === 'youtube') {
169
- // This is a placeholder for the actual implementation
170
- // In a real implementation, you would import and use the Google Sign-In SDK
171
169
  console.log('Initiating native Google Sign-In for YouTube');
172
170
 
173
- // Simulate a successful authentication
174
- // In a real implementation, this would be the result of the Google Sign-In flow
175
- return true;
171
+ try {
172
+ // Import Google Sign-In dynamically to avoid errors if not installed
173
+ const { GoogleSignin, statusCodes } = require('@react-native-google-signin/google-signin');
174
+
175
+ // Configure Google Sign-In
176
+ await GoogleSignin.configure({
177
+ scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
178
+ webClientId: 'YOUR_WEB_CLIENT_ID', // Replace with your actual web client ID
179
+ offlineAccess: true,
180
+ hostedDomain: '',
181
+ forceCodeForRefreshToken: true,
182
+ });
183
+
184
+ // Check if device supports Google Play Services
185
+ await GoogleSignin.hasPlayServices();
186
+
187
+ // Sign in
188
+ const userInfo = await GoogleSignin.signIn();
189
+ console.log('Google Sign-In successful:', userInfo);
190
+
191
+ // Get access token
192
+ const tokens = await GoogleSignin.getTokens();
193
+ console.log('Google tokens:', tokens);
194
+
195
+ // Here you would typically send the tokens to your backend
196
+ // to associate the YouTube account with the user
197
+
198
+ return true;
199
+ } catch (error: any) {
200
+ console.error('Google Sign-In error:', error);
201
+
202
+ const { statusCodes: StatusCodes } = require('@react-native-google-signin/google-signin');
203
+
204
+ if (error.code === StatusCodes.SIGN_IN_CANCELLED) {
205
+ console.log('User cancelled the sign-in flow');
206
+ } else if (error.code === StatusCodes.IN_PROGRESS) {
207
+ console.log('Sign-in is in progress already');
208
+ } else if (error.code === StatusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
209
+ console.log('Play services not available or outdated');
210
+ } else {
211
+ console.log('Some other error happened');
212
+ }
213
+
214
+ return false;
215
+ }
176
216
  }
177
217
 
178
218
  throw new Error(`Native authentication not supported for ${platform}`);