recoder-shared
Version:
Shared types, utilities, and configurations for Recoder
319 lines • 9.86 kB
JavaScript
"use strict";
/**
* Mobile OAuth Implementation
* Handles OAuth flows for mobile platforms (React Native)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MobileOAuthHandler = void 0;
const oauth_handler_1 = require("./oauth-handler");
class MobileOAuthHandler {
constructor(authClient, config = {}) {
/**
* Handle linking URL event
*/
this.handleLinkingUrl = (event) => {
const url = typeof event === 'string' ? event : event.url;
if (this.deepLinkHandler && url.startsWith(`${this.config.customScheme}://oauth/callback/`)) {
this.deepLinkHandler(url);
}
};
this.authClient = authClient;
this.oauthHandler = new oauth_handler_1.OAuthHandler(authClient);
this.config = {
customScheme: config.customScheme || 'recoder',
useInAppBrowser: config.useInAppBrowser ?? true,
browserOptions: {
dismissButtonStyle: 'done',
preferredBarTintColor: '#000000',
preferredControlTintColor: '#ffffff',
readerMode: false,
animated: true,
modalPresentationStyle: 'overCurrentContext',
modalTransitionStyle: 'coverVertical',
...config.browserOptions
}
};
this.setupEventHandlers();
}
setupEventHandlers() {
this.oauthHandler.on('authSuccess', ({ provider, user, isNewUser }) => {
console.log(`Successfully authenticated with ${provider}!`, { user, isNewUser });
});
this.oauthHandler.on('authError', ({ provider, error }) => {
console.error(`Authentication failed with ${provider}:`, error);
});
}
/**
* Start OAuth flow for mobile
*/
async login(provider) {
const authUrl = this.oauthHandler.generateAuthUrl(provider, 'mobile', {
redirectUri: `${this.config.customScheme}://oauth/callback/${provider}`
});
return new Promise((resolve, reject) => {
// Set up deep link handler
const handleDeepLink = async (url) => {
try {
const callbackData = this.parseCallbackUrl(url);
const result = await this.oauthHandler.handleCallback(provider, 'mobile', callbackData);
// Clean up
this.removeDeepLinkHandler();
resolve(result);
}
catch (error) {
this.removeDeepLinkHandler();
reject(error);
}
};
this.setDeepLinkHandler(handleDeepLink);
// Open OAuth URL
this.openAuthUrl(authUrl);
// Timeout after 5 minutes
setTimeout(() => {
this.removeDeepLinkHandler();
reject(new Error('Authentication timed out'));
}, 5 * 60 * 1000);
});
}
/**
* Login with Google
*/
async loginWithGoogle() {
return this.login('google');
}
/**
* Login with GitHub
*/
async loginWithGitHub() {
return this.login('github');
}
/**
* Login with Microsoft
*/
async loginWithMicrosoft() {
return this.login('microsoft');
}
/**
* Handle deep link callback
*/
async handleDeepLink(url) {
const callbackData = this.parseCallbackUrl(url);
const provider = this.extractProviderFromUrl(url);
if (!provider) {
throw new Error('Unable to determine OAuth provider from callback URL');
}
return await this.oauthHandler.handleCallback(provider, 'mobile', callbackData);
}
/**
* Open OAuth URL using platform-specific method
*/
openAuthUrl(url) {
try {
// Try React Native Linking first
const Linking = this.getLinking();
if (Linking) {
Linking.openURL(url);
return;
}
// Try Expo WebBrowser
const WebBrowser = this.getWebBrowser();
if (WebBrowser) {
WebBrowser.openBrowserAsync(url, {
...this.config.browserOptions,
dismissButtonStyle: this.config.browserOptions.dismissButtonStyle
});
return;
}
// Fallback
console.log('Open this URL in your browser:', url);
}
catch (error) {
console.error('Failed to open OAuth URL:', error);
console.log('Please open this URL manually:', url);
}
}
/**
* Set up deep link handler
*/
setDeepLinkHandler(handler) {
this.deepLinkHandler = handler;
try {
// React Native Linking
const Linking = this.getLinking();
if (Linking) {
Linking.addEventListener('url', this.handleLinkingUrl);
return;
}
// Expo Linking
const ExpoLinking = this.getExpoLinking();
if (ExpoLinking) {
const subscription = ExpoLinking.addEventListener('url', this.handleLinkingUrl);
// Store subscription for cleanup
this._linkingSubscription = subscription;
return;
}
}
catch (error) {
console.error('Failed to set up deep link handler:', error);
}
}
/**
* Remove deep link handler
*/
removeDeepLinkHandler() {
try {
// React Native Linking
const Linking = this.getLinking();
if (Linking && Linking.removeEventListener) {
Linking.removeEventListener('url', this.handleLinkingUrl);
}
// Expo Linking
if (this._linkingSubscription) {
this._linkingSubscription.remove();
delete this._linkingSubscription;
}
}
catch (error) {
console.error('Failed to remove deep link handler:', error);
}
this.deepLinkHandler = undefined;
}
/**
* Parse callback URL parameters
*/
parseCallbackUrl(url) {
try {
const urlObj = new URL(url);
return {
code: urlObj.searchParams.get('code') || '',
state: urlObj.searchParams.get('state') || '',
error: urlObj.searchParams.get('error') || '',
errorDescription: urlObj.searchParams.get('error_description') || ''
};
}
catch (error) {
throw new Error('Invalid callback URL format');
}
}
/**
* Extract provider from callback URL
*/
extractProviderFromUrl(url) {
const match = url.match(/\/oauth\/callback\/([^?]+)/);
return match ? match[1] : null;
}
/**
* Get React Native Linking module
*/
getLinking() {
try {
return require('react-native').Linking;
}
catch {
return null;
}
}
/**
* Get Expo WebBrowser module
*/
getWebBrowser() {
try {
return require('expo-web-browser');
}
catch {
return null;
}
}
/**
* Get Expo Linking module
*/
getExpoLinking() {
try {
return require('expo-linking');
}
catch {
return null;
}
}
/**
* React Native Hook for OAuth
*/
static createUseAuth() {
return function useAuth() {
// This would need React Native context implementation
return {
isAuthenticated: false,
user: null,
loading: true,
login: async (provider) => {
// Implementation would go here
},
logout: async () => {
// Implementation would go here
}
};
};
}
/**
* Setup deep linking for the app
*/
static setupDeepLinking(customScheme = 'recoder') {
return {
// For React Navigation
linking: {
prefixes: [customScheme + '://'],
config: {
screens: {
OAuthCallback: 'oauth/callback/:provider',
},
},
},
// For Expo app.json
expoConfig: {
scheme: customScheme,
ios: {
bundleIdentifier: 'com.recoder.app',
},
android: {
package: 'com.recoder.app',
intentFilters: [
{
action: 'VIEW',
data: {
scheme: customScheme,
},
category: ['BROWSABLE', 'DEFAULT'],
},
],
},
}
};
}
/**
* Get available OAuth providers
*/
getAvailableProviders() {
return this.oauthHandler.getSupportedProviders().map(p => p.name);
}
/**
* Check if user is authenticated
*/
isAuthenticated() {
return this.authClient.isAuthenticated();
}
/**
* Get current user
*/
getCurrentUser() {
return this.authClient.getUser();
}
/**
* Logout
*/
async logout() {
await this.authClient.logout();
}
}
exports.MobileOAuthHandler = MobileOAuthHandler;
exports.default = MobileOAuthHandler;
//# sourceMappingURL=oauth-mobile.js.map