@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.
Files changed (155) hide show
  1. package/README.md +23 -3
  2. package/lib/commonjs/api/index.js +5 -1
  3. package/lib/commonjs/api/index.js.map +1 -1
  4. package/lib/commonjs/components/OnairosButton.js +5 -3
  5. package/lib/commonjs/components/OnairosButton.js.map +1 -1
  6. package/lib/commonjs/components/Overlay.js +274 -104
  7. package/lib/commonjs/components/Overlay.js.map +1 -1
  8. package/lib/commonjs/components/UniversalOnboarding.js +12 -0
  9. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
  10. package/lib/commonjs/components/onboarding/OAuthWebView.js +28 -9
  11. package/lib/commonjs/components/onboarding/OAuthWebView.js.map +1 -1
  12. package/lib/commonjs/components/onboarding/PlatformConnector.js +6 -1
  13. package/lib/commonjs/components/onboarding/PlatformConnector.js.map +1 -1
  14. package/lib/commonjs/components/screens/ConnectorScreen.js +3 -2
  15. package/lib/commonjs/components/screens/ConnectorScreen.js.map +1 -1
  16. package/lib/commonjs/constants/index.js +1 -1
  17. package/lib/commonjs/hooks/useConnections.js +77 -15
  18. package/lib/commonjs/hooks/useConnections.js.map +1 -1
  19. package/lib/commonjs/hooks/useCredentials.js +2 -0
  20. package/lib/commonjs/hooks/useCredentials.js.map +1 -1
  21. package/lib/commonjs/index.js +51 -56
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/commonjs/services/oauthService.js +26 -51
  24. package/lib/commonjs/services/oauthService.js.map +1 -1
  25. package/lib/commonjs/types/ambient.d.js +2 -0
  26. package/lib/commonjs/types/ambient.d.js.map +1 -0
  27. package/lib/commonjs/types/node-fix.d.js +2 -0
  28. package/lib/commonjs/types/node-fix.d.js.map +1 -0
  29. package/lib/commonjs/types/node-override.d.js +2 -0
  30. package/lib/commonjs/types/node-override.d.js.map +1 -0
  31. package/lib/commonjs/types/types.d.js +2 -0
  32. package/lib/commonjs/types/types.d.js.map +1 -0
  33. package/lib/commonjs/utils/encryption.js +8 -2
  34. package/lib/commonjs/utils/encryption.js.map +1 -1
  35. package/lib/commonjs/utils/secureStorage.js +23 -4
  36. package/lib/commonjs/utils/secureStorage.js.map +1 -1
  37. package/lib/module/api/index.js +5 -1
  38. package/lib/module/api/index.js.map +1 -1
  39. package/lib/module/components/OnairosButton.js +6 -4
  40. package/lib/module/components/OnairosButton.js.map +1 -1
  41. package/lib/module/components/Overlay.js +275 -107
  42. package/lib/module/components/Overlay.js.map +1 -1
  43. package/lib/module/components/UniversalOnboarding.js +12 -0
  44. package/lib/module/components/UniversalOnboarding.js.map +1 -1
  45. package/lib/module/components/onboarding/OAuthWebView.js +28 -9
  46. package/lib/module/components/onboarding/OAuthWebView.js.map +1 -1
  47. package/lib/module/components/onboarding/PlatformConnector.js +6 -1
  48. package/lib/module/components/onboarding/PlatformConnector.js.map +1 -1
  49. package/lib/module/components/screens/ConnectorScreen.js +3 -2
  50. package/lib/module/components/screens/ConnectorScreen.js.map +1 -1
  51. package/lib/module/constants/index.js +1 -1
  52. package/lib/module/hooks/useConnections.js +77 -14
  53. package/lib/module/hooks/useConnections.js.map +1 -1
  54. package/lib/module/hooks/useCredentials.js +2 -0
  55. package/lib/module/hooks/useCredentials.js.map +1 -1
  56. package/lib/module/index.js +27 -9
  57. package/lib/module/index.js.map +1 -1
  58. package/lib/module/services/oauthService.js +26 -33
  59. package/lib/module/services/oauthService.js.map +1 -1
  60. package/lib/module/types/ambient.d.js +2 -0
  61. package/lib/module/types/ambient.d.js.map +1 -0
  62. package/lib/module/types/node-fix.d.js +2 -0
  63. package/lib/module/types/node-fix.d.js.map +1 -0
  64. package/lib/module/types/node-override.d.js +2 -0
  65. package/lib/module/types/node-override.d.js.map +1 -0
  66. package/lib/module/types/types.d.js +2 -0
  67. package/lib/module/types/types.d.js.map +1 -0
  68. package/lib/module/utils/encryption.js +8 -2
  69. package/lib/module/utils/encryption.js.map +1 -1
  70. package/lib/module/utils/secureStorage.js +23 -3
  71. package/lib/module/utils/secureStorage.js.map +1 -1
  72. package/lib/typescript/api/index.d.ts +8 -0
  73. package/lib/typescript/api/index.d.ts.map +1 -0
  74. package/lib/typescript/components/DataRequestModal.d.ts +11 -0
  75. package/lib/typescript/components/DataRequestModal.d.ts.map +1 -0
  76. package/lib/typescript/components/Onairos.d.ts +29 -0
  77. package/lib/typescript/components/Onairos.d.ts.map +1 -0
  78. package/lib/typescript/components/OnairosButton.d.ts +7 -0
  79. package/lib/typescript/components/OnairosButton.d.ts.map +1 -0
  80. package/lib/typescript/components/Overlay.d.ts +18 -0
  81. package/lib/typescript/components/Overlay.d.ts.map +1 -0
  82. package/lib/typescript/components/PinInput.d.ts +4 -0
  83. package/lib/typescript/components/PinInput.d.ts.map +1 -0
  84. package/lib/typescript/components/PlatformList.d.ts +4 -0
  85. package/lib/typescript/components/PlatformList.d.ts.map +1 -0
  86. package/lib/typescript/components/TrainingModal.d.ts +4 -0
  87. package/lib/typescript/components/TrainingModal.d.ts.map +1 -0
  88. package/lib/typescript/components/UniversalOnboarding.d.ts +4 -0
  89. package/lib/typescript/components/UniversalOnboarding.d.ts.map +1 -0
  90. package/lib/typescript/components/onboarding/OAuthWebView.d.ts +4 -0
  91. package/lib/typescript/components/onboarding/OAuthWebView.d.ts.map +1 -0
  92. package/lib/typescript/components/onboarding/OnboardingHeader.d.ts +11 -0
  93. package/lib/typescript/components/onboarding/OnboardingHeader.d.ts.map +1 -0
  94. package/lib/typescript/components/onboarding/PinInput.d.ts +4 -0
  95. package/lib/typescript/components/onboarding/PinInput.d.ts.map +1 -0
  96. package/lib/typescript/components/onboarding/PlatformConnector.d.ts +13 -0
  97. package/lib/typescript/components/onboarding/PlatformConnector.d.ts.map +1 -0
  98. package/lib/typescript/components/screens/ConnectorScreen.d.ts +9 -0
  99. package/lib/typescript/components/screens/ConnectorScreen.d.ts.map +1 -0
  100. package/lib/typescript/components/screens/LoadingScreen.d.ts +9 -0
  101. package/lib/typescript/components/screens/LoadingScreen.d.ts.map +1 -0
  102. package/lib/typescript/components/screens/PinCreationScreen.d.ts +10 -0
  103. package/lib/typescript/components/screens/PinCreationScreen.d.ts.map +1 -0
  104. package/lib/typescript/constants/index.d.ts +52 -0
  105. package/lib/typescript/constants/index.d.ts.map +1 -0
  106. package/lib/typescript/hooks/useConnections.d.ts +9 -0
  107. package/lib/typescript/hooks/useConnections.d.ts.map +1 -0
  108. package/lib/typescript/hooks/useCredentials.d.ts +9 -0
  109. package/lib/typescript/hooks/useCredentials.d.ts.map +1 -0
  110. package/lib/typescript/index.d.ts +45 -0
  111. package/lib/typescript/index.d.ts.map +1 -0
  112. package/lib/typescript/services/oauthService.d.ts +50 -0
  113. package/lib/typescript/services/oauthService.d.ts.map +1 -0
  114. package/lib/typescript/types/index.d.ts +145 -0
  115. package/lib/typescript/types/index.d.ts.map +1 -0
  116. package/lib/typescript/types.d.ts +135 -0
  117. package/lib/typescript/types.d.ts.map +1 -0
  118. package/lib/typescript/utils/api.d.ts +6 -0
  119. package/lib/typescript/utils/api.d.ts.map +1 -0
  120. package/lib/typescript/utils/auth.d.ts +6 -0
  121. package/lib/typescript/utils/auth.d.ts.map +1 -0
  122. package/lib/typescript/utils/crypto.d.ts +4 -0
  123. package/lib/typescript/utils/crypto.d.ts.map +1 -0
  124. package/lib/typescript/utils/debugHelper.d.ts +29 -0
  125. package/lib/typescript/utils/debugHelper.d.ts.map +1 -0
  126. package/lib/typescript/utils/encryption.d.ts +19 -0
  127. package/lib/typescript/utils/encryption.d.ts.map +1 -0
  128. package/lib/typescript/utils/onairosApi.d.ts +72 -0
  129. package/lib/typescript/utils/onairosApi.d.ts.map +1 -0
  130. package/lib/typescript/utils/secureStorage.d.ts +63 -0
  131. package/lib/typescript/utils/secureStorage.d.ts.map +1 -0
  132. package/package.json +16 -4
  133. package/src/api/index.ts +11 -11
  134. package/src/components/OnairosButton.tsx +5 -3
  135. package/src/components/Overlay.tsx +319 -135
  136. package/src/components/UniversalOnboarding.tsx +12 -0
  137. package/src/components/onboarding/OAuthWebView.tsx +27 -7
  138. package/src/components/onboarding/PlatformConnector.tsx +5 -0
  139. package/src/components/screens/ConnectorScreen.tsx +3 -2
  140. package/src/constants/index.ts +81 -81
  141. package/src/hooks/useConnections.ts +76 -16
  142. package/src/hooks/useCredentials.ts +5 -1
  143. package/src/index.ts +29 -1
  144. package/src/services/oauthService.ts +412 -419
  145. package/src/types/ambient.d.ts +29 -0
  146. package/src/types/index.d.ts +48 -8
  147. package/src/types/index.ts +21 -15
  148. package/src/types/node-fix.d.ts +19 -0
  149. package/src/types/node-override.d.ts +24 -0
  150. package/src/types/types.d.ts +18 -0
  151. package/src/types.ts +121 -1
  152. package/src/utils/encryption.ts +7 -2
  153. package/src/utils/secureStorage.ts +25 -9
  154. package/types/index.d.ts +210 -0
  155. 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: 'onairosreact://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: 'onairosreact://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: 'onairosreact://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: 'onairosreact://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('onairosreact://auth/')) {
93
- return;
94
- }
95
-
96
- // Extract platform from URL path
97
- const platform = url.split('onairosreact://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
- };
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
+ };