@onairos/react-native 3.0.2 → 3.0.5
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 +23 -3
- package/lib/commonjs/api/index.js +5 -1
- package/lib/commonjs/api/index.js.map +1 -1
- package/lib/commonjs/components/OnairosButton.js +5 -3
- package/lib/commonjs/components/OnairosButton.js.map +1 -1
- package/lib/commonjs/components/Overlay.js +274 -104
- package/lib/commonjs/components/Overlay.js.map +1 -1
- package/lib/commonjs/components/UniversalOnboarding.js +12 -0
- package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
- package/lib/commonjs/components/onboarding/OAuthWebView.js +28 -9
- package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -1
- package/lib/commonjs/components/onboarding/PlatformConnector.js +6 -1
- package/lib/commonjs/components/onboarding/PlatformConnector.js.map +1 -1
- package/lib/commonjs/components/screens/ConnectorScreen.js +3 -2
- package/lib/commonjs/components/screens/ConnectorScreen.js.map +1 -1
- package/lib/commonjs/constants/index.js +1 -1
- package/lib/commonjs/hooks/useConnections.js +77 -15
- package/lib/commonjs/hooks/useConnections.js.map +1 -1
- package/lib/commonjs/hooks/useCredentials.js +2 -0
- package/lib/commonjs/hooks/useCredentials.js.map +1 -1
- package/lib/commonjs/index.js +51 -56
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/services/oauthService.js +26 -51
- package/lib/commonjs/services/oauthService.js.map +1 -1
- package/lib/commonjs/types/ambient.d.js +2 -0
- package/lib/commonjs/types/ambient.d.js.map +1 -0
- package/lib/commonjs/types/node-fix.d.js +2 -0
- package/lib/commonjs/types/node-fix.d.js.map +1 -0
- package/lib/commonjs/types/node-override.d.js +2 -0
- package/lib/commonjs/types/node-override.d.js.map +1 -0
- package/lib/commonjs/types/types.d.js +2 -0
- package/lib/commonjs/types/types.d.js.map +1 -0
- package/lib/commonjs/utils/encryption.js +8 -2
- package/lib/commonjs/utils/encryption.js.map +1 -1
- package/lib/commonjs/utils/secureStorage.js +23 -4
- package/lib/commonjs/utils/secureStorage.js.map +1 -1
- package/lib/module/api/index.js +5 -1
- package/lib/module/api/index.js.map +1 -1
- package/lib/module/components/OnairosButton.js +6 -4
- package/lib/module/components/OnairosButton.js.map +1 -1
- package/lib/module/components/Overlay.js +275 -107
- package/lib/module/components/Overlay.js.map +1 -1
- package/lib/module/components/UniversalOnboarding.js +12 -0
- package/lib/module/components/UniversalOnboarding.js.map +1 -1
- package/lib/module/components/onboarding/OAuthWebView.js +28 -9
- package/lib/module/components/onboarding/OAuthWebView.js.map +1 -1
- package/lib/module/components/onboarding/PlatformConnector.js +6 -1
- package/lib/module/components/onboarding/PlatformConnector.js.map +1 -1
- package/lib/module/components/screens/ConnectorScreen.js +3 -2
- package/lib/module/components/screens/ConnectorScreen.js.map +1 -1
- package/lib/module/constants/index.js +1 -1
- package/lib/module/hooks/useConnections.js +77 -14
- package/lib/module/hooks/useConnections.js.map +1 -1
- package/lib/module/hooks/useCredentials.js +2 -0
- package/lib/module/hooks/useCredentials.js.map +1 -1
- package/lib/module/index.js +27 -9
- package/lib/module/index.js.map +1 -1
- package/lib/module/services/oauthService.js +26 -33
- package/lib/module/services/oauthService.js.map +1 -1
- package/lib/module/types/ambient.d.js +2 -0
- package/lib/module/types/ambient.d.js.map +1 -0
- package/lib/module/types/node-fix.d.js +2 -0
- package/lib/module/types/node-fix.d.js.map +1 -0
- package/lib/module/types/node-override.d.js +2 -0
- package/lib/module/types/node-override.d.js.map +1 -0
- package/lib/module/types/types.d.js +2 -0
- package/lib/module/types/types.d.js.map +1 -0
- package/lib/module/utils/encryption.js +8 -2
- package/lib/module/utils/encryption.js.map +1 -1
- package/lib/module/utils/secureStorage.js +23 -3
- package/lib/module/utils/secureStorage.js.map +1 -1
- package/lib/typescript/api/index.d.ts +8 -0
- package/lib/typescript/api/index.d.ts.map +1 -0
- package/lib/typescript/components/DataRequestModal.d.ts +11 -0
- package/lib/typescript/components/DataRequestModal.d.ts.map +1 -0
- package/lib/typescript/components/Onairos.d.ts +29 -0
- package/lib/typescript/components/Onairos.d.ts.map +1 -0
- package/lib/typescript/components/OnairosButton.d.ts +7 -0
- package/lib/typescript/components/OnairosButton.d.ts.map +1 -0
- package/lib/typescript/components/Overlay.d.ts +18 -0
- package/lib/typescript/components/Overlay.d.ts.map +1 -0
- package/lib/typescript/components/PinInput.d.ts +4 -0
- package/lib/typescript/components/PinInput.d.ts.map +1 -0
- package/lib/typescript/components/PlatformList.d.ts +4 -0
- package/lib/typescript/components/PlatformList.d.ts.map +1 -0
- package/lib/typescript/components/TrainingModal.d.ts +4 -0
- package/lib/typescript/components/TrainingModal.d.ts.map +1 -0
- package/lib/typescript/components/UniversalOnboarding.d.ts +4 -0
- package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -0
- package/lib/typescript/components/onboarding/OAuthWebView.d.ts +4 -0
- package/lib/typescript/components/onboarding/OAuthWebView.d.ts.map +1 -0
- package/lib/typescript/components/onboarding/OnboardingHeader.d.ts +11 -0
- package/lib/typescript/components/onboarding/OnboardingHeader.d.ts.map +1 -0
- package/lib/typescript/components/onboarding/PinInput.d.ts +4 -0
- package/lib/typescript/components/onboarding/PinInput.d.ts.map +1 -0
- package/lib/typescript/components/onboarding/PlatformConnector.d.ts +13 -0
- package/lib/typescript/components/onboarding/PlatformConnector.d.ts.map +1 -0
- package/lib/typescript/components/screens/ConnectorScreen.d.ts +9 -0
- package/lib/typescript/components/screens/ConnectorScreen.d.ts.map +1 -0
- package/lib/typescript/components/screens/LoadingScreen.d.ts +9 -0
- package/lib/typescript/components/screens/LoadingScreen.d.ts.map +1 -0
- package/lib/typescript/components/screens/PinCreationScreen.d.ts +10 -0
- package/lib/typescript/components/screens/PinCreationScreen.d.ts.map +1 -0
- package/lib/typescript/constants/index.d.ts +52 -0
- package/lib/typescript/constants/index.d.ts.map +1 -0
- package/lib/typescript/hooks/useConnections.d.ts +9 -0
- package/lib/typescript/hooks/useConnections.d.ts.map +1 -0
- package/lib/typescript/hooks/useCredentials.d.ts +9 -0
- package/lib/typescript/hooks/useCredentials.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +45 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/services/oauthService.d.ts +50 -0
- package/lib/typescript/services/oauthService.d.ts.map +1 -0
- package/lib/typescript/types/index.d.ts +145 -0
- package/lib/typescript/types/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +135 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/utils/api.d.ts +6 -0
- package/lib/typescript/utils/api.d.ts.map +1 -0
- package/lib/typescript/utils/auth.d.ts +6 -0
- package/lib/typescript/utils/auth.d.ts.map +1 -0
- package/lib/typescript/utils/crypto.d.ts +4 -0
- package/lib/typescript/utils/crypto.d.ts.map +1 -0
- package/lib/typescript/utils/debugHelper.d.ts +29 -0
- package/lib/typescript/utils/debugHelper.d.ts.map +1 -0
- package/lib/typescript/utils/encryption.d.ts +19 -0
- package/lib/typescript/utils/encryption.d.ts.map +1 -0
- package/lib/typescript/utils/onairosApi.d.ts +72 -0
- package/lib/typescript/utils/onairosApi.d.ts.map +1 -0
- package/lib/typescript/utils/secureStorage.d.ts +63 -0
- package/lib/typescript/utils/secureStorage.d.ts.map +1 -0
- package/package.json +16 -4
- package/src/api/index.ts +11 -11
- package/src/components/OnairosButton.tsx +5 -3
- package/src/components/Overlay.tsx +319 -135
- package/src/components/UniversalOnboarding.tsx +12 -0
- package/src/components/onboarding/OAuthWebView.tsx +27 -7
- package/src/components/onboarding/PlatformConnector.tsx +5 -0
- package/src/components/screens/ConnectorScreen.tsx +3 -2
- package/src/constants/index.ts +81 -81
- package/src/hooks/useConnections.ts +76 -16
- package/src/hooks/useCredentials.ts +5 -1
- package/src/index.ts +29 -1
- package/src/services/oauthService.ts +412 -419
- package/src/types/ambient.d.ts +29 -0
- package/src/types/index.d.ts +48 -8
- package/src/types/index.ts +21 -15
- package/src/types/node-fix.d.ts +19 -0
- package/src/types/node-override.d.ts +24 -0
- package/src/types/types.d.ts +18 -0
- package/src/types.ts +121 -1
- package/src/utils/encryption.ts +7 -2
- package/src/utils/secureStorage.ts +25 -9
- package/types/index.d.ts +210 -0
- package/types/node-env.d.ts +15 -0
|
@@ -1,419 +1,412 @@
|
|
|
1
|
-
import { Linking, Platform } from 'react-native';
|
|
2
|
-
import { updateCredentials, OnairosCredentials } from '../utils/secureStorage';
|
|
3
|
-
import { sha256 } from '../utils/crypto';
|
|
4
|
-
import { onairosApi } from '../api';
|
|
5
|
-
|
|
6
|
-
// Define OAuth configuration types
|
|
7
|
-
export interface OAuthConfig {
|
|
8
|
-
clientId: string;
|
|
9
|
-
redirectUri: string;
|
|
10
|
-
scope: string;
|
|
11
|
-
authorizationEndpoint: string;
|
|
12
|
-
tokenEndpoint: string;
|
|
13
|
-
responseType: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Platform-specific OAuth configurations
|
|
17
|
-
const OAUTH_CONFIGS: Record<string, OAuthConfig> = {
|
|
18
|
-
instagram: {
|
|
19
|
-
clientId: 'YOUR_INSTAGRAM_CLIENT_ID', // Replace with actual client ID
|
|
20
|
-
redirectUri: '
|
|
21
|
-
scope: 'user_profile,user_media',
|
|
22
|
-
authorizationEndpoint: 'https://api.instagram.com/oauth/authorize',
|
|
23
|
-
tokenEndpoint: 'https://api.instagram.com/oauth/access_token',
|
|
24
|
-
responseType: 'code',
|
|
25
|
-
},
|
|
26
|
-
youtube: {
|
|
27
|
-
clientId: 'YOUR_YOUTUBE_CLIENT_ID', // Replace with actual client ID
|
|
28
|
-
redirectUri: '
|
|
29
|
-
scope: 'https://www.googleapis.com/auth/youtube.readonly',
|
|
30
|
-
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth',
|
|
31
|
-
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
32
|
-
responseType: 'code',
|
|
33
|
-
},
|
|
34
|
-
pinterest: {
|
|
35
|
-
clientId: 'YOUR_PINTEREST_CLIENT_ID', // Replace with actual client ID
|
|
36
|
-
redirectUri: '
|
|
37
|
-
scope: 'boards:read,pins:read',
|
|
38
|
-
authorizationEndpoint: 'https://www.pinterest.com/oauth/',
|
|
39
|
-
tokenEndpoint: 'https://api.pinterest.com/v5/oauth/token',
|
|
40
|
-
responseType: 'code',
|
|
41
|
-
},
|
|
42
|
-
reddit: {
|
|
43
|
-
clientId: 'YOUR_REDDIT_CLIENT_ID', // Replace with actual client ID
|
|
44
|
-
redirectUri: '
|
|
45
|
-
scope: 'identity,read',
|
|
46
|
-
authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize',
|
|
47
|
-
tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
|
|
48
|
-
responseType: 'code',
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Generate a state value for OAuth to prevent CSRF attacks
|
|
54
|
-
*/
|
|
55
|
-
const generateState = (): string => {
|
|
56
|
-
const randomValue = Math.random().toString(36).substring(2, 15);
|
|
57
|
-
return sha256(randomValue).substring(0, 10);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Initialize OAuth service handlers and listeners
|
|
62
|
-
*/
|
|
63
|
-
export const initializeOAuthService = (): void => {
|
|
64
|
-
// Set up deep linking handlers for OAuth redirects
|
|
65
|
-
Linking.addEventListener('url', handleDeepLink);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Clean up OAuth service handlers and listeners
|
|
70
|
-
*/
|
|
71
|
-
export const cleanupOAuthService = (): void => {
|
|
72
|
-
// Use the modern React Native Linking API
|
|
73
|
-
if (typeof Linking.removeAllListeners === 'function') {
|
|
74
|
-
Linking.removeAllListeners('url');
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// Keep track of current OAuth state and callbacks
|
|
79
|
-
let currentOAuthState: string | null = null;
|
|
80
|
-
let currentOAuthPlatform: string | null = null;
|
|
81
|
-
let currentOAuthResolve: ((value: any) => void) | null = null;
|
|
82
|
-
let currentOAuthReject: ((error: Error) => void) | null = null;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Handle deep link callbacks from OAuth providers
|
|
86
|
-
*/
|
|
87
|
-
const handleDeepLink = async (event: { url: string }): Promise<void> => {
|
|
88
|
-
try {
|
|
89
|
-
const { url } = event;
|
|
90
|
-
|
|
91
|
-
// Check if this is an OAuth callback URL
|
|
92
|
-
if (!url.startsWith('
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Extract platform from URL path
|
|
97
|
-
const platform = url.split('
|
|
98
|
-
|
|
99
|
-
// Only handle if it matches current OAuth flow
|
|
100
|
-
if (platform !== currentOAuthPlatform) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Parse URL parameters
|
|
105
|
-
const params = new URL(url).searchParams;
|
|
106
|
-
const code = params.get('code');
|
|
107
|
-
const state = params.get('state');
|
|
108
|
-
const error = params.get('error');
|
|
109
|
-
|
|
110
|
-
// Validate state to prevent CSRF attacks
|
|
111
|
-
if (state !== currentOAuthState) {
|
|
112
|
-
if (currentOAuthReject) {
|
|
113
|
-
currentOAuthReject(new Error('OAuth state mismatch - possible CSRF attack'));
|
|
114
|
-
}
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Handle errors
|
|
119
|
-
if (error) {
|
|
120
|
-
if (currentOAuthReject) {
|
|
121
|
-
currentOAuthReject(new Error(`OAuth error: ${error}`));
|
|
122
|
-
}
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Proceed with token exchange if code is present
|
|
127
|
-
if (code) {
|
|
128
|
-
const tokenResult = await exchangeCodeForToken(platform, code);
|
|
129
|
-
|
|
130
|
-
if (currentOAuthResolve) {
|
|
131
|
-
currentOAuthResolve(tokenResult);
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
if (currentOAuthReject) {
|
|
135
|
-
currentOAuthReject(new Error('No authorization code received'));
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
} catch (error) {
|
|
139
|
-
console.error('Error handling OAuth deep link:', error);
|
|
140
|
-
if (currentOAuthReject) {
|
|
141
|
-
currentOAuthReject(error as Error);
|
|
142
|
-
}
|
|
143
|
-
} finally {
|
|
144
|
-
// Reset state
|
|
145
|
-
currentOAuthState = null;
|
|
146
|
-
currentOAuthPlatform = null;
|
|
147
|
-
currentOAuthResolve = null;
|
|
148
|
-
currentOAuthReject = null;
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Exchange OAuth authorization code for access token
|
|
154
|
-
*/
|
|
155
|
-
const exchangeCodeForToken = async (platform: string, code: string): Promise<any> => {
|
|
156
|
-
try {
|
|
157
|
-
const config = OAUTH_CONFIGS[platform];
|
|
158
|
-
|
|
159
|
-
if (!config) {
|
|
160
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Prepare token request parameters
|
|
164
|
-
const params = new URLSearchParams({
|
|
165
|
-
grant_type: 'authorization_code',
|
|
166
|
-
code,
|
|
167
|
-
redirect_uri: config.redirectUri,
|
|
168
|
-
client_id: config.clientId,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Exchange code for token
|
|
172
|
-
const response = await fetch(config.tokenEndpoint, {
|
|
173
|
-
method: 'POST',
|
|
174
|
-
headers: {
|
|
175
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
176
|
-
},
|
|
177
|
-
body: params.toString(),
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
const data = await response.json();
|
|
181
|
-
|
|
182
|
-
if (!response.ok) {
|
|
183
|
-
throw new Error(data.error || 'Failed to exchange code for token');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Fetch user information based on the platform
|
|
187
|
-
const userInfo = await fetchUserInfo(platform, data.access_token);
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
token: data.access_token,
|
|
191
|
-
refreshToken: data.refresh_token,
|
|
192
|
-
expiresIn: data.expires_in,
|
|
193
|
-
username: userInfo.username,
|
|
194
|
-
userId: userInfo.id,
|
|
195
|
-
};
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.error(`Error exchanging code for token (${platform}):`, error);
|
|
198
|
-
throw error;
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Fetch user information from the connected platform
|
|
204
|
-
*/
|
|
205
|
-
const fetchUserInfo = async (platform: string, accessToken: string): Promise<any> => {
|
|
206
|
-
try {
|
|
207
|
-
let endpoint;
|
|
208
|
-
let headers: Record<string, string> = {
|
|
209
|
-
Authorization: `Bearer ${accessToken}`,
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// Platform-specific API endpoints for user info
|
|
213
|
-
switch (platform) {
|
|
214
|
-
case 'instagram':
|
|
215
|
-
endpoint = 'https://graph.instagram.com/me?fields=id,username';
|
|
216
|
-
break;
|
|
217
|
-
case 'youtube':
|
|
218
|
-
endpoint = 'https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true';
|
|
219
|
-
break;
|
|
220
|
-
case 'pinterest':
|
|
221
|
-
endpoint = 'https://api.pinterest.com/v5/user_account';
|
|
222
|
-
break;
|
|
223
|
-
case 'reddit':
|
|
224
|
-
endpoint = 'https://oauth.reddit.com/api/v1/me';
|
|
225
|
-
break;
|
|
226
|
-
default:
|
|
227
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const response = await fetch(endpoint, { headers });
|
|
231
|
-
const data = await response.json();
|
|
232
|
-
|
|
233
|
-
if (!response.ok) {
|
|
234
|
-
throw new Error(data.error || 'Failed to fetch user info');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Extract user information based on platform-specific response format
|
|
238
|
-
switch (platform) {
|
|
239
|
-
case 'instagram':
|
|
240
|
-
return { id: data.id, username: data.username };
|
|
241
|
-
case 'youtube':
|
|
242
|
-
return {
|
|
243
|
-
id: data.items[0].id,
|
|
244
|
-
username: data.items[0].snippet.title
|
|
245
|
-
};
|
|
246
|
-
case 'pinterest':
|
|
247
|
-
return {
|
|
248
|
-
id: data.id,
|
|
249
|
-
username: data.username || data.full_name
|
|
250
|
-
};
|
|
251
|
-
case 'reddit':
|
|
252
|
-
return { id: data.id, username: data.name };
|
|
253
|
-
default:
|
|
254
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
255
|
-
}
|
|
256
|
-
} catch (error) {
|
|
257
|
-
console.error(`Error fetching user info (${platform}):`, error);
|
|
258
|
-
throw error;
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Connect to a platform using OAuth
|
|
264
|
-
*/
|
|
265
|
-
export const connectPlatform = (platform: string): Promise<any> => {
|
|
266
|
-
return new Promise((resolve, reject) => {
|
|
267
|
-
try {
|
|
268
|
-
const config = OAUTH_CONFIGS[platform];
|
|
269
|
-
|
|
270
|
-
if (!config) {
|
|
271
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Generate state for CSRF protection
|
|
275
|
-
const state = generateState();
|
|
276
|
-
|
|
277
|
-
// Build authorization URL
|
|
278
|
-
const authUrl = new URL(config.authorizationEndpoint);
|
|
279
|
-
authUrl.searchParams.append('client_id', config.clientId);
|
|
280
|
-
authUrl.searchParams.append('redirect_uri', config.redirectUri);
|
|
281
|
-
authUrl.searchParams.append('response_type', config.responseType);
|
|
282
|
-
authUrl.searchParams.append('scope', config.scope);
|
|
283
|
-
authUrl.searchParams.append('state', state);
|
|
284
|
-
|
|
285
|
-
// Set up current OAuth state for callback handling
|
|
286
|
-
currentOAuthState = state;
|
|
287
|
-
currentOAuthPlatform = platform;
|
|
288
|
-
currentOAuthResolve = resolve;
|
|
289
|
-
currentOAuthReject = reject;
|
|
290
|
-
|
|
291
|
-
// Open browser or WebView to the authorization URL
|
|
292
|
-
Linking.openURL(authUrl.toString());
|
|
293
|
-
} catch (error) {
|
|
294
|
-
reject(error);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Disconnect from a platform
|
|
301
|
-
*/
|
|
302
|
-
export const disconnectPlatform = async (
|
|
303
|
-
platform: string,
|
|
304
|
-
credentials: OnairosCredentials
|
|
305
|
-
): Promise<boolean> => {
|
|
306
|
-
try {
|
|
307
|
-
// Call Onairos API to disconnect platform
|
|
308
|
-
await onairosApi.post('/users/disconnect-platform', {
|
|
309
|
-
platform,
|
|
310
|
-
username: credentials.username,
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// Update local credentials to remove platform
|
|
314
|
-
const updatedPlatforms = { ...credentials.platforms };
|
|
315
|
-
|
|
316
|
-
// Type-safe platform removal using keyof operator
|
|
317
|
-
if (updatedPlatforms && platform in updatedPlatforms) {
|
|
318
|
-
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
await updateCredentials({
|
|
322
|
-
...credentials,
|
|
323
|
-
platforms: updatedPlatforms,
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
return true;
|
|
327
|
-
} catch (error) {
|
|
328
|
-
console.error(`Error disconnecting platform (${platform}):`, error);
|
|
329
|
-
return false;
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Store platform connection data in user credentials
|
|
335
|
-
*/
|
|
336
|
-
export const storePlatformConnection = async (
|
|
337
|
-
platform: string,
|
|
338
|
-
connectionData: any,
|
|
339
|
-
credentials: OnairosCredentials
|
|
340
|
-
): Promise<boolean> => {
|
|
341
|
-
try {
|
|
342
|
-
// Only accept valid platform types
|
|
343
|
-
const validPlatform = (
|
|
344
|
-
platform === 'instagram' ||
|
|
345
|
-
platform === 'youtube' ||
|
|
346
|
-
platform === 'pinterest' ||
|
|
347
|
-
platform === 'reddit'
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
if (!validPlatform) {
|
|
351
|
-
throw new Error(`Unsupported platform: ${platform}`);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Update platforms in credentials with type safety
|
|
355
|
-
const updatedPlatforms = {
|
|
356
|
-
...credentials.platforms,
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
// Type-safe assignment
|
|
360
|
-
const platformData = {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
token: connectionData.token,
|
|
364
|
-
refreshToken: connectionData.refreshToken,
|
|
365
|
-
expiresAt: connectionData.expiresIn ?
|
|
366
|
-
Date.now() + (connectionData.expiresIn * 1000) :
|
|
367
|
-
null,
|
|
368
|
-
connectedAt: Date.now(),
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
// Assign platform data based on platform type
|
|
372
|
-
if (platform === 'instagram') updatedPlatforms.instagram = platformData;
|
|
373
|
-
else if (platform === 'youtube') updatedPlatforms.youtube = platformData;
|
|
374
|
-
else if (platform === 'pinterest') updatedPlatforms.pinterest = platformData;
|
|
375
|
-
else if (platform === 'reddit') updatedPlatforms.reddit = platformData;
|
|
376
|
-
|
|
377
|
-
// Update stored credentials
|
|
378
|
-
await updateCredentials({
|
|
379
|
-
...credentials,
|
|
380
|
-
platforms: updatedPlatforms,
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
return true;
|
|
384
|
-
} catch (error) {
|
|
385
|
-
console.error(`Error storing platform connection (${platform}):`, error);
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
export interface AuthorizationData {
|
|
391
|
-
accountName: string;
|
|
392
|
-
authUrl: string;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
export interface PlatformConnectionResult {
|
|
396
|
-
success: boolean;
|
|
397
|
-
userName?: string;
|
|
398
|
-
error?: string;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Service for handling OAuth connections to various platforms
|
|
403
|
-
*/
|
|
404
|
-
export const OAuthService = {
|
|
405
|
-
initializeOAuthService,
|
|
406
|
-
cleanupOAuthService,
|
|
407
|
-
connectPlatform,
|
|
408
|
-
disconnectPlatform,
|
|
409
|
-
storePlatformConnection,
|
|
410
|
-
// Base API URL
|
|
411
|
-
_apiBaseUrl: 'https://api2.onairos.uk',
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
/**
|
|
415
|
-
* Re-export from oauthService.ts with correct capitalization
|
|
416
|
-
* This file exists to solve the casing issue in imports
|
|
417
|
-
*/
|
|
418
|
-
|
|
419
|
-
export * from './oauthService';
|
|
1
|
+
import { Linking, Platform } from 'react-native';
|
|
2
|
+
import { updateCredentials, OnairosCredentials } from '../utils/secureStorage';
|
|
3
|
+
import { sha256 } from '../utils/crypto';
|
|
4
|
+
import { onairosApi } from '../api';
|
|
5
|
+
|
|
6
|
+
// Define OAuth configuration types
|
|
7
|
+
export interface OAuthConfig {
|
|
8
|
+
clientId: string;
|
|
9
|
+
redirectUri: string;
|
|
10
|
+
scope: string;
|
|
11
|
+
authorizationEndpoint: string;
|
|
12
|
+
tokenEndpoint: string;
|
|
13
|
+
responseType: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Platform-specific OAuth configurations
|
|
17
|
+
const OAUTH_CONFIGS: Record<string, OAuthConfig> = {
|
|
18
|
+
instagram: {
|
|
19
|
+
clientId: 'YOUR_INSTAGRAM_CLIENT_ID', // Replace with actual client ID
|
|
20
|
+
redirectUri: 'onairosanime://auth/instagram',
|
|
21
|
+
scope: 'user_profile,user_media',
|
|
22
|
+
authorizationEndpoint: 'https://api.instagram.com/oauth/authorize',
|
|
23
|
+
tokenEndpoint: 'https://api.instagram.com/oauth/access_token',
|
|
24
|
+
responseType: 'code',
|
|
25
|
+
},
|
|
26
|
+
youtube: {
|
|
27
|
+
clientId: 'YOUR_YOUTUBE_CLIENT_ID', // Replace with actual client ID
|
|
28
|
+
redirectUri: 'onairosanime://auth/youtube',
|
|
29
|
+
scope: 'https://www.googleapis.com/auth/youtube.readonly',
|
|
30
|
+
authorizationEndpoint: 'https://accounts.google.com/o/oauth2/auth',
|
|
31
|
+
tokenEndpoint: 'https://oauth2.googleapis.com/token',
|
|
32
|
+
responseType: 'code',
|
|
33
|
+
},
|
|
34
|
+
pinterest: {
|
|
35
|
+
clientId: 'YOUR_PINTEREST_CLIENT_ID', // Replace with actual client ID
|
|
36
|
+
redirectUri: 'onairosanime://auth/pinterest',
|
|
37
|
+
scope: 'boards:read,pins:read',
|
|
38
|
+
authorizationEndpoint: 'https://www.pinterest.com/oauth/',
|
|
39
|
+
tokenEndpoint: 'https://api.pinterest.com/v5/oauth/token',
|
|
40
|
+
responseType: 'code',
|
|
41
|
+
},
|
|
42
|
+
reddit: {
|
|
43
|
+
clientId: 'YOUR_REDDIT_CLIENT_ID', // Replace with actual client ID
|
|
44
|
+
redirectUri: 'onairosanime://auth/reddit',
|
|
45
|
+
scope: 'identity,read',
|
|
46
|
+
authorizationEndpoint: 'https://www.reddit.com/api/v1/authorize',
|
|
47
|
+
tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
|
|
48
|
+
responseType: 'code',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate a state value for OAuth to prevent CSRF attacks
|
|
54
|
+
*/
|
|
55
|
+
const generateState = (): string => {
|
|
56
|
+
const randomValue = Math.random().toString(36).substring(2, 15);
|
|
57
|
+
return sha256(randomValue).substring(0, 10);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize OAuth service handlers and listeners
|
|
62
|
+
*/
|
|
63
|
+
export const initializeOAuthService = (): void => {
|
|
64
|
+
// Set up deep linking handlers for OAuth redirects
|
|
65
|
+
Linking.addEventListener('url', handleDeepLink);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Clean up OAuth service handlers and listeners
|
|
70
|
+
*/
|
|
71
|
+
export const cleanupOAuthService = (): void => {
|
|
72
|
+
// Use the modern React Native Linking API
|
|
73
|
+
if (typeof Linking.removeAllListeners === 'function') {
|
|
74
|
+
Linking.removeAllListeners('url');
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Keep track of current OAuth state and callbacks
|
|
79
|
+
let currentOAuthState: string | null = null;
|
|
80
|
+
let currentOAuthPlatform: string | null = null;
|
|
81
|
+
let currentOAuthResolve: ((value: any) => void) | null = null;
|
|
82
|
+
let currentOAuthReject: ((error: Error) => void) | null = null;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Handle deep link callbacks from OAuth providers
|
|
86
|
+
*/
|
|
87
|
+
const handleDeepLink = async (event: { url: string }): Promise<void> => {
|
|
88
|
+
try {
|
|
89
|
+
const { url } = event;
|
|
90
|
+
|
|
91
|
+
// Check if this is an OAuth callback URL
|
|
92
|
+
if (!url.startsWith('onairosanime://auth/')) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Extract platform from URL path
|
|
97
|
+
const platform = url.split('onairosanime://auth/')[1].split('?')[0];
|
|
98
|
+
|
|
99
|
+
// Only handle if it matches current OAuth flow
|
|
100
|
+
if (platform !== currentOAuthPlatform) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Parse URL parameters
|
|
105
|
+
const params = new URL(url).searchParams;
|
|
106
|
+
const code = params.get('code');
|
|
107
|
+
const state = params.get('state');
|
|
108
|
+
const error = params.get('error');
|
|
109
|
+
|
|
110
|
+
// Validate state to prevent CSRF attacks
|
|
111
|
+
if (state !== currentOAuthState) {
|
|
112
|
+
if (currentOAuthReject) {
|
|
113
|
+
currentOAuthReject(new Error('OAuth state mismatch - possible CSRF attack'));
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle errors
|
|
119
|
+
if (error) {
|
|
120
|
+
if (currentOAuthReject) {
|
|
121
|
+
currentOAuthReject(new Error(`OAuth error: ${error}`));
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Proceed with token exchange if code is present
|
|
127
|
+
if (code) {
|
|
128
|
+
const tokenResult = await exchangeCodeForToken(platform, code);
|
|
129
|
+
|
|
130
|
+
if (currentOAuthResolve) {
|
|
131
|
+
currentOAuthResolve(tokenResult);
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
if (currentOAuthReject) {
|
|
135
|
+
currentOAuthReject(new Error('No authorization code received'));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Error handling OAuth deep link:', error);
|
|
140
|
+
if (currentOAuthReject) {
|
|
141
|
+
currentOAuthReject(error as Error);
|
|
142
|
+
}
|
|
143
|
+
} finally {
|
|
144
|
+
// Reset state
|
|
145
|
+
currentOAuthState = null;
|
|
146
|
+
currentOAuthPlatform = null;
|
|
147
|
+
currentOAuthResolve = null;
|
|
148
|
+
currentOAuthReject = null;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Exchange OAuth authorization code for access token
|
|
154
|
+
*/
|
|
155
|
+
const exchangeCodeForToken = async (platform: string, code: string): Promise<any> => {
|
|
156
|
+
try {
|
|
157
|
+
const config = OAUTH_CONFIGS[platform];
|
|
158
|
+
|
|
159
|
+
if (!config) {
|
|
160
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Prepare token request parameters
|
|
164
|
+
const params = new URLSearchParams({
|
|
165
|
+
grant_type: 'authorization_code',
|
|
166
|
+
code,
|
|
167
|
+
redirect_uri: config.redirectUri,
|
|
168
|
+
client_id: config.clientId,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Exchange code for token
|
|
172
|
+
const response = await fetch(config.tokenEndpoint, {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: {
|
|
175
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
176
|
+
},
|
|
177
|
+
body: params.toString(),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const data = await response.json();
|
|
181
|
+
|
|
182
|
+
if (!response.ok) {
|
|
183
|
+
throw new Error(data.error || 'Failed to exchange code for token');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Fetch user information based on the platform
|
|
187
|
+
const userInfo = await fetchUserInfo(platform, data.access_token);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
token: data.access_token,
|
|
191
|
+
refreshToken: data.refresh_token,
|
|
192
|
+
expiresIn: data.expires_in,
|
|
193
|
+
username: userInfo.username,
|
|
194
|
+
userId: userInfo.id,
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(`Error exchanging code for token (${platform}):`, error);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Fetch user information from the connected platform
|
|
204
|
+
*/
|
|
205
|
+
const fetchUserInfo = async (platform: string, accessToken: string): Promise<any> => {
|
|
206
|
+
try {
|
|
207
|
+
let endpoint;
|
|
208
|
+
let headers: Record<string, string> = {
|
|
209
|
+
Authorization: `Bearer ${accessToken}`,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Platform-specific API endpoints for user info
|
|
213
|
+
switch (platform) {
|
|
214
|
+
case 'instagram':
|
|
215
|
+
endpoint = 'https://graph.instagram.com/me?fields=id,username';
|
|
216
|
+
break;
|
|
217
|
+
case 'youtube':
|
|
218
|
+
endpoint = 'https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true';
|
|
219
|
+
break;
|
|
220
|
+
case 'pinterest':
|
|
221
|
+
endpoint = 'https://api.pinterest.com/v5/user_account';
|
|
222
|
+
break;
|
|
223
|
+
case 'reddit':
|
|
224
|
+
endpoint = 'https://oauth.reddit.com/api/v1/me';
|
|
225
|
+
break;
|
|
226
|
+
default:
|
|
227
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const response = await fetch(endpoint, { headers });
|
|
231
|
+
const data = await response.json();
|
|
232
|
+
|
|
233
|
+
if (!response.ok) {
|
|
234
|
+
throw new Error(data.error || 'Failed to fetch user info');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Extract user information based on platform-specific response format
|
|
238
|
+
switch (platform) {
|
|
239
|
+
case 'instagram':
|
|
240
|
+
return { id: data.id, username: data.username };
|
|
241
|
+
case 'youtube':
|
|
242
|
+
return {
|
|
243
|
+
id: data.items[0].id,
|
|
244
|
+
username: data.items[0].snippet.title
|
|
245
|
+
};
|
|
246
|
+
case 'pinterest':
|
|
247
|
+
return {
|
|
248
|
+
id: data.id,
|
|
249
|
+
username: data.username || data.full_name
|
|
250
|
+
};
|
|
251
|
+
case 'reddit':
|
|
252
|
+
return { id: data.id, username: data.name };
|
|
253
|
+
default:
|
|
254
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error(`Error fetching user info (${platform}):`, error);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Connect to a platform using OAuth
|
|
264
|
+
*/
|
|
265
|
+
export const connectPlatform = (platform: string): Promise<any> => {
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
try {
|
|
268
|
+
const config = OAUTH_CONFIGS[platform];
|
|
269
|
+
|
|
270
|
+
if (!config) {
|
|
271
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Generate state for CSRF protection
|
|
275
|
+
const state = generateState();
|
|
276
|
+
|
|
277
|
+
// Build authorization URL
|
|
278
|
+
const authUrl = new URL(config.authorizationEndpoint);
|
|
279
|
+
authUrl.searchParams.append('client_id', config.clientId);
|
|
280
|
+
authUrl.searchParams.append('redirect_uri', config.redirectUri);
|
|
281
|
+
authUrl.searchParams.append('response_type', config.responseType);
|
|
282
|
+
authUrl.searchParams.append('scope', config.scope);
|
|
283
|
+
authUrl.searchParams.append('state', state);
|
|
284
|
+
|
|
285
|
+
// Set up current OAuth state for callback handling
|
|
286
|
+
currentOAuthState = state;
|
|
287
|
+
currentOAuthPlatform = platform;
|
|
288
|
+
currentOAuthResolve = resolve;
|
|
289
|
+
currentOAuthReject = reject;
|
|
290
|
+
|
|
291
|
+
// Open browser or WebView to the authorization URL
|
|
292
|
+
Linking.openURL(authUrl.toString());
|
|
293
|
+
} catch (error) {
|
|
294
|
+
reject(error);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Disconnect from a platform
|
|
301
|
+
*/
|
|
302
|
+
export const disconnectPlatform = async (
|
|
303
|
+
platform: string,
|
|
304
|
+
credentials: OnairosCredentials
|
|
305
|
+
): Promise<boolean> => {
|
|
306
|
+
try {
|
|
307
|
+
// Call Onairos API to disconnect platform
|
|
308
|
+
await onairosApi.post('/users/disconnect-platform', {
|
|
309
|
+
platform,
|
|
310
|
+
username: credentials.username,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Update local credentials to remove platform
|
|
314
|
+
const updatedPlatforms = { ...credentials.platforms };
|
|
315
|
+
|
|
316
|
+
// Type-safe platform removal using keyof operator
|
|
317
|
+
if (updatedPlatforms && platform in updatedPlatforms) {
|
|
318
|
+
delete updatedPlatforms[platform as keyof typeof updatedPlatforms];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await updateCredentials({
|
|
322
|
+
...credentials,
|
|
323
|
+
platforms: updatedPlatforms,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
return true;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error(`Error disconnecting platform (${platform}):`, error);
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Store platform connection data in user credentials
|
|
335
|
+
*/
|
|
336
|
+
export const storePlatformConnection = async (
|
|
337
|
+
platform: string,
|
|
338
|
+
connectionData: any,
|
|
339
|
+
credentials: OnairosCredentials
|
|
340
|
+
): Promise<boolean> => {
|
|
341
|
+
try {
|
|
342
|
+
// Only accept valid platform types
|
|
343
|
+
const validPlatform = (
|
|
344
|
+
platform === 'instagram' ||
|
|
345
|
+
platform === 'youtube' ||
|
|
346
|
+
platform === 'pinterest' ||
|
|
347
|
+
platform === 'reddit'
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
if (!validPlatform) {
|
|
351
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Update platforms in credentials with type safety
|
|
355
|
+
const updatedPlatforms = {
|
|
356
|
+
...credentials.platforms,
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Type-safe assignment
|
|
360
|
+
const platformData = {
|
|
361
|
+
username: connectionData.username,
|
|
362
|
+
userId: connectionData.userId,
|
|
363
|
+
token: connectionData.token,
|
|
364
|
+
refreshToken: connectionData.refreshToken,
|
|
365
|
+
expiresAt: connectionData.expiresIn ?
|
|
366
|
+
Date.now() + (connectionData.expiresIn * 1000) :
|
|
367
|
+
null,
|
|
368
|
+
connectedAt: Date.now(),
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Assign platform data based on platform type
|
|
372
|
+
if (platform === 'instagram') updatedPlatforms.instagram = platformData;
|
|
373
|
+
else if (platform === 'youtube') updatedPlatforms.youtube = platformData;
|
|
374
|
+
else if (platform === 'pinterest') updatedPlatforms.pinterest = platformData;
|
|
375
|
+
else if (platform === 'reddit') updatedPlatforms.reddit = platformData;
|
|
376
|
+
|
|
377
|
+
// Update stored credentials
|
|
378
|
+
await updateCredentials({
|
|
379
|
+
...credentials,
|
|
380
|
+
platforms: updatedPlatforms,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return true;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
console.error(`Error storing platform connection (${platform}):`, error);
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
export interface AuthorizationData {
|
|
391
|
+
accountName: string;
|
|
392
|
+
authUrl: string;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export interface PlatformConnectionResult {
|
|
396
|
+
success: boolean;
|
|
397
|
+
userName?: string;
|
|
398
|
+
error?: string;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Service for handling OAuth connections to various platforms
|
|
403
|
+
*/
|
|
404
|
+
export const OAuthService = {
|
|
405
|
+
initializeOAuthService,
|
|
406
|
+
cleanupOAuthService,
|
|
407
|
+
connectPlatform,
|
|
408
|
+
disconnectPlatform,
|
|
409
|
+
storePlatformConnection,
|
|
410
|
+
// Base API URL
|
|
411
|
+
_apiBaseUrl: 'https://api2.onairos.uk',
|
|
412
|
+
};
|